Instrument Triton-compiled GPU kernels with Omniprobe for runtime memory analysis. Omniprobe injects LLVM IR passes during Triton's JIT compilation to produce instrumented kernel variants that report cache line utilization, bank conflicts, and memory access patterns — without modifying your Triton source.
Omniprobe's instrumentation works by injecting LLVM IR passes (shared library
plugins, .so files) into the compilation pipeline. These plugins link against
LLVM's C++ API at load time.
Triton JIT-compiles kernels using its own bundled LLVM, which it downloads to
~/.triton/llvm/. That bundled LLVM is built with static libraries — our
pass plugins cannot dlopen alongside a statically-linked LLVM because the
LLVM symbols are not exported.
We need to rebuild LLVM with -DBUILD_SHARED_LIBS=ON so that our
instrumentation plugins can link against the same LLVM instance that Triton
uses. The triton_install.sh script automates this: it builds LLVM as shared
libraries, then builds Triton against that LLVM.
| Requirement | Notes |
|---|---|
| ROCm | Tested with 7.2.0. ROCM_PATH must be set. |
| Python >= 3.10 | PyTorch ROCm wheels require 3.10+. |
| AMD GPU | gfx90a, gfx942, etc. |
| Network access | Unless using --local-sources (see below). |
| ~50 GB disk space | LLVM build is large. |
| cmake, ninja | Installed via pip if not available. |
The triton_install.sh script handles everything: cloning Triton, building
LLVM with shared libraries, creating a Python venv, installing PyTorch, and
building Triton against the custom LLVM.
cd ~/repos
ROCM_PATH=/opt/rocm-7.2.0 /path/to/omniprobe/containers/triton_install.shThis runs in the current directory and creates a triton/ subdirectory
containing the full build. The script takes 15-90 minutes depending on
available cores and network speed.
| Flag | Description | Default |
|---|---|---|
--triton-version TAG (or -g TAG) |
Triton version to build (git tag or commit hash) | Latest release from GitHub API |
--pytorch-rocm VER |
PyTorch ROCm wheel index version (e.g., 7.1) |
Highest stable index <= installed ROCm |
--local-sources DIR |
Use pre-staged local sources instead of network | Fetch from network |
triton/ # Triton repository
├── .venv/ # Python venv with Triton, PyTorch, dependencies
├── llvm-project/
│ └── build/ # LLVM build with shared libraries
│ └── lib/libLLVM*.so # ← these are what make plugins work
└── third_party/amd/backend/
└── compiler.py # Patched for instrumentation compatibility
The key output paths:
- Triton venv:
~/repos/triton/.venv— activate this before using Triton - LLVM build:
~/repos/triton/llvm-project/build— pass to Omniprobe's CMake
Use this when network access is slow, unreliable, or unavailable (air-gapped environments).
1. Clone the Triton repository:
git clone https://github.com/triton-lang/triton.git ~/repos/sandbox/triton
cd ~/repos/sandbox/triton
git checkout v3.7.1 # or your desired version2. Clone the LLVM submodule at the correct commit:
The required LLVM commit hash is in cmake/llvm-hash.txt:
LLVM_HASH=$(cat cmake/llvm-hash.txt)
git clone https://github.com/llvm/llvm-project.git ~/repos/sandbox/triton/llvm-project
cd ~/repos/sandbox/triton/llvm-project
git checkout $LLVM_HASH3. Download PyTorch and torchvision ROCm wheels:
mkdir -p ~/repos/sandbox/triton/wheels
cd ~/repos/sandbox/triton/wheels
# Find the appropriate wheels at https://download.pytorch.org/whl/rocmX.Y/
# Download torch and torchvision for your Python version and ROCm version
pip download torch torchvision \
--index-url https://download.pytorch.org/whl/rocm7.1 \
--dest . --no-depscd ~/repos
ROCM_PATH=/opt/rocm-7.2.0 /path/to/omniprobe/containers/triton_install.sh \
--local-sources ~/repos/sandbox/tritonThe script clones from the local repo (no network), uses the pre-populated
llvm-project/ directory, and installs wheels from the wheels/ subdirectory.
Activate the Triton venv (Omniprobe needs the Python environment for Triton
integration), then build with -DTRITON_LLVM pointing to the LLVM build:
cd /path/to/omniprobe
source ~/repos/triton/.venv/bin/activate
TRITON_DIR=~/repos/triton
cmake -B build \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_HIP_ARCHITECTURES=gfx90a \
-DTRITON_LLVM=$(realpath $TRITON_DIR/llvm-project/build) \
-DCMAKE_INSTALL_PREFIX=$(pwd)/install
cmake --build build -j$(nproc)Important:
-DTRITON_LLVMmust be an absolute path. Do not use~or shell variables that haven't been expanded — CMake passes this value to anExternalProjectsub-build which does not perform tilde expansion, causing the sub-build to fail with "LLVM_INSTALL_DIR is invalid". Use$(realpath ...)or write out the full path.-DCMAKE_INSTALL_PREFIXmust be set. The default (/) causes permission errors during the install step of theinstrument-amdgpu-kernelssub-builds. Setting it to$(pwd)/installor any writable path avoids this.
Setting -DTRITON_LLVM tells CMake to build the -triton instrumentation
plugins in addition to the standard -rocm plugins. Both plugin variants are
built from the same source (external/instrument-amdgpu-kernels) but link
against different LLVM installations:
| Plugin | Links against | Used by |
|---|---|---|
libAMDGCNSubmitAddressMessages-rocm.so |
ROCm's LLVM ($ROCM_PATH/llvm) |
rocBLAS, hipBLASLt, hipcc-compiled kernels |
libAMDGCNSubmitAddressMessages-triton.so |
Triton's LLVM ($TRITON_LLVM) |
Triton JIT-compiled kernels |
When running from a build tree (without cmake --install), the Omniprobe
handler shared libraries (e.g., libdefaultMessageHandlers64.so) live in
build/ but the omniprobe script's library search path doesn't include
it automatically. Set LD_LIBRARY_PATH to include the build directory:
export LD_LIBRARY_PATH=/path/to/omniprobe/build:$LD_LIBRARY_PATHThis is only needed when running from the build tree. After cmake --install,
the libraries are copied to the install prefix and found automatically.
These test the core Omniprobe runtime (message handlers, analyzers, filters) using pre-compiled test kernels. They do not require Triton.
./tests/run_handler_tests.shExpected: all tests pass (currently 19/19 with ROCm 7.2.0 on gfx90a).
These verify that Omniprobe can instrument Triton-compiled kernels end-to-end: plugin invocation during JIT, instrumented kernel dispatch, and report generation.
export TRITON_DIR=~/repos/triton
./tests/triton/run_test.shThe test suite runs 5 tests:
| Test | Verifies |
|---|---|
triton_instrumentation_plugin |
Instrumentation plugin runs during Triton JIT compilation |
triton_instrumented_dispatch |
Instrumented kernel alternative is found for add_kernel |
triton_cache_line_report |
L2 cache line use report is generated |
triton_bank_conflicts_report |
Bank conflicts report is generated |
triton_scope_no_match |
--instrumentation-scope filtering produces 0 instrumented instructions when scope doesn't match |
This example uses a minimal vector-add kernel (based on Triton's first tutorial) to demonstrate the instrumentation workflow.
import torch
import triton
import triton.language as tl
DEVICE = triton.runtime.driver.active.get_active_torch_device()
@triton.jit
def add_kernel(
x_ptr,
y_ptr,
output_ptr,
n_elements,
BLOCK_SIZE: tl.constexpr,
):
pid = tl.program_id(axis=0)
block_start = pid * BLOCK_SIZE
offsets = block_start + tl.arange(0, BLOCK_SIZE)
mask = offsets < n_elements
x = tl.load(x_ptr + offsets, mask=mask)
y = tl.load(y_ptr + offsets, mask=mask)
output = x + y
tl.store(output_ptr + offsets, output, mask=mask)
def add(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
output = torch.empty_like(x)
n_elements = output.numel()
grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),)
add_kernel[grid](x, y, output, n_elements, BLOCK_SIZE=1024)
return outputsource ~/repos/triton/.venv/bin/activate
export TRITON_HIP_LLD_PATH=${ROCM_PATH}/llvm/bin/ld.lld
omniprobe -a MemoryAnalysis -i -c ~/.triton/cache \
-- python vector_add.pyFlags explained:
-a MemoryAnalysis— use the memory analysis handler (cache lines + bank conflicts)-i— run instrumented kernel variants instead of originals-c ~/.triton/cache— tell Omniprobe where Triton caches compiled kernels (this is where instrumented alternatives are stored)
The output has several sections:
Compile-time (instrumentation plugin log):
Running AMDGCNSubmitAddressMessage on module ...
Confirms the instrumentation plugin was invoked during Triton's JIT compilation. You see this once per kernel compilation (cached on subsequent runs).
Dispatch-time:
Found instrumented alternative for add_kernel
Omniprobe found and loaded the instrumented variant of add_kernel from the
Triton cache.
L2 cache line use report: Shows how efficiently each memory instruction uses L2 cache lines. A utilization of 100% means every byte in every fetched cache line was used by the kernel. Lower values indicate spatial locality issues.
Bank conflicts report: Shows LDS (shared memory) bank conflicts per instruction. Zero conflicts is ideal. Non-zero values indicate address patterns that serialize memory access across wavefront lanes.
To limit instrumentation to specific source lines (useful for large kernels):
omniprobe -a MemoryAnalysis -i -c ~/.triton/cache \
--instrumentation-scope "vector_add.py:21,22,23" \
-- python vector_add.pyThis instruments only the tl.load and tl.store lines, reducing overhead
and focusing the report on the memory operations you care about.
| Flag | Description |
|---|---|
-a, --analyzers |
Analyzer(s) to use. Common values: MemoryAnalysis, AddressLogger, Heatmap. |
-i, --instrumented |
Run instrumented kernel variants instead of originals. |
-c, --cache-location |
Path to Triton's kernel cache (typically ~/.triton/cache). |
-k, --kernels |
Kernel name filter (ECMAScript regex). Only matching kernels are instrumented. |
-d, --dispatches |
Which dispatches to capture. Options: all, random, 1. |
--instrumentation-scope |
Limit instrumentation to specific source locations. Format: file[:line_spec,...][;file...]. |
--instrumentation-scope-file |
File containing scope definitions (same syntax, one per line). |
--filter-x, --filter-y, --filter-z |
Filter output by block index. Format: N (single) or N:M (half-open range). |
--library-filter |
JSON config for filtering which libraries are scanned. |
-l, --log-location |
Output destination. Default: console. |
-t, --log-format |
Output format: csv or json. |
-v, --verbose |
Verbose output. |