Pick one entry path to populate bin/ and lib/, point $ROOT at it, then
follow the shared Testing & Benchmarking section — every tool runs the same
way regardless of which entry path you chose.
- Build from source, native (developers).
python build.pybuilds directly on the host (no Docker) into<workspace>/install/. - Build from source, Docker (developers who prefer isolation).
./docker/run.sh buildruns the samebuild.pyinside a container. - Use the prebuilt package (testers).
gh run download linux-gpu-test-packagelands the binaries under<workspace>/prebuilt/<run-id>/; no rebuild needed.
All C++ dependencies (LLVM/MLIR/LLD, protobuf, flatbuffers, ONNX Runtime) and
the TheRock ROCm SDK are resolved automatically by cmake/deps.cmake: LLVM is
built from source, so the host needs no system LLVM. The cold from-source LLVM
build is the long pole (multi-hour); it lands under <workspace>/build/ and is
reused across rebuilds.
See quick_start.md for the Windows flow. If your target is an
AMD Instinct MI350X (gfx950, CDNA4), read this guide first and then
quick_start_mi350.md, which covers what wave64 changes.
| Tool | Purpose |
|---|---|
| cmake ≥ 3.29, ninja, git, a C++ compiler, python3 | Native build path |
| Docker 26+ | Docker build path (optional) |
AMD gfx1151 GPU + /dev/kfd + /dev/dri/renderD* |
HIP runtime |
gh CLI (gh auth login) |
Download the prebuilt package (that path only) |
For the Docker path, the current user must be in the host docker group. The user does
not need to be in render / video — GPU passthrough is handled by
docker's --device=/dev/kfd, and the container entrypoint
(docker/entrypoint.sh) detects the host GID
on /dev/kfd and adds the in-container user to that group automatically.
All commands below assume you are in <workspace>/hip-ep/ (the
project root). The build writes to sibling directories under <workspace>/
(build/, install/); the Docker path auto-mounts the whole workspace. The
TheRock ROCm SDK is auto-downloaded into the build tree at
build/hip-ep/_therock/.
git clone https://github.com/ROCm/hip-ep.git
cd hip-epbuild.py is a plain cross-platform driver: it
checks the toolchain, auto-detects the GPU arch (from /sys/class/kfd), then
runs the cmake configure/build/install into <workspace>/install/. All
dependencies (incl. from-source LLVM) are resolved by cmake/deps.cmake.
python3 build.py
# --hip_arch gfx1151 override target GPU (else auto-detected; falls
# back to --mock if no AMD GPU is present)
# --mock mock runtime (no GPU/HIP/TheRock)
# --config RelWithDebInfo build type (default Release)
# --skip_tests skip the LIT tests (run by default after install)
# --clean remove build/ and install/./docker/run.sh image # build the image once (first time only)
./docker/run.sh build # runs build.py inside the container
# HIP_ARCHITECTURES=gfx942 ./docker/run.sh build # override target GPUThe result tree under <workspace>/install/:
bin/hip-onnx-runner,bin/hip-compiler,bin/hip-mlir-opt,bin/hip-testlib/libhip-compiler.so,lib/libhipgpu.so
A locally-built install/ is not fully self-contained: libonnxruntime.so
lives in the ONNX Runtime prefix and the ROCm libs in TheRock, so run with
LD_LIBRARY_PATH=<workspace>/install/lib:<ort-prefix>/lib:$THEROCK_DIST/lib.
(The prebuilt package below already bundles these into install/lib, so
install/lib:$THEROCK_DIST/lib is enough.)
ONNX Runtime is downloaded automatically (Microsoft's official prebuilt release) — you don't fetch it yourself.
model_benchmark(OGA) is not part of the local build; use the prebuilt package below if you need it.
Skip to the Open a container shell
section once install/bin/ is populated.
A ready-to-run package is published for each green build. It contains every
.so / binary the host needs (libhipgpu,
hip-onnx-runner, onnxruntime_perf_test, model_benchmark,
libonnxruntime, libonnxruntime-genai) plus a clang/lld toolchain in
bin/ (next to the other tools). TheRock is not included — install ROCm on
the host. Download it with gh (no compile needed):
git clone https://github.com/ROCm/hip-ep.git
cd hip-ep
./docker/run.sh image # skip if the image is already built
# Pick the latest green run from
# https://github.com/ROCm/hip-ep/actions/workflows/linux-build.yml
RUN_ID=<paste-id>
mkdir -p ../prebuilt/$RUN_ID
( cd ../prebuilt/$RUN_ID && \
gh run download $RUN_ID --repo ROCm/hip-ep \
--name linux-gpu-test-package --dir . && \
chmod +x bin/* )After extraction, <workspace>/prebuilt/$RUN_ID/ matches the
build-from-source install/ layout (bin/ — which also carries the bundled
clang/lld — lib/, and etc/).
If you built/ran natively (no Docker), skip the container and just set
ROOT/THEROCK_DIST/LD_LIBRARY_PATH in your own shell using the explicit
paths below (replace $WORKSPACE with your <workspace> dir). Otherwise,
./docker/run.sh shell opens a long-lived container with GPU passthrough and
UID alignment:
./docker/run.sh shell
# Inside the container — pick ONE depending on which entry path you used:
export ROOT="$WORKSPACE/install" # built from source
# export ROOT="$WORKSPACE/prebuilt/$RUN_ID" # downloaded prebuilt package
# THEROCK_DIST points the in-process tooling at the TheRock SDK that
# `cmake/deps.cmake` auto-downloaded into the build tree at
# $WORKSPACE/build/hip-ep/_therock during the build. It is
# required at runtime even though the prebuilt package bundles transitive .so
# files into $ROOT/lib, because:
# 1. CompilerDriver::discoverLibraries() reads $THEROCK_DIST to
# construct `-L<therock>/lib` paths it passes to ld.lld when
# compiling per-model DLLs (otherwise the link errors with
# "undefined symbol: hipGetDeviceCount" etc.).
# 2. libamd_comgr_loader.so.1 (shipped in $ROOT/lib) is a stub that
# dlopen()s the real libamd_comgr.so.3 at runtime; the real lib
# lives only in $THEROCK_DIST/lib and is not in ldd's view of
# transitive deps, so it has to be on LD_LIBRARY_PATH explicitly.
#
# LIBRARY_PATH is GCC/clang's build-time linker search-path env;
# `hip-compiler` keeps it on the search path for any future offline-link
# step that needs it.
#
# Per-arch GPU kernels ship as `libcustom_kernels_gfx<arch>.so` next to
# the EP `.so` under `$ROOT/lib`. The EP `.so` is built with
# `RPATH=$ORIGIN`, so LlvmIrJit's `dlopen` of the matching variant
# resolves through the loader without needing it on `LD_LIBRARY_PATH`.
export THEROCK_DIST="$WORKSPACE/therock-dist"
export LD_LIBRARY_PATH="$ROOT/lib:$THEROCK_DIST/lib"
export LIBRARY_PATH="$ROOT/lib:$THEROCK_DIST/lib"
# clang/lld for the per-model DLL link (the prebuilt package ships it in
# $ROOT/bin, alongside the other tools).
export PATH="$ROOT/bin:$PATH"
# Sanity check
ldd "$ROOT/lib/libhipgpu.so" | grep "not found" # expect empty
"$ROOT/bin/hip-onnx-runner" --help | head -5$WORKSPACE is set automatically by
docker/entrypoint.sh to the bind-mounted
host workspace path. Every command below uses $ROOT/bin/<tool>
exclusively, so the snippets are identical regardless of entry path.
hip-onnx-runner runs a single ONNX model through hipgpu EP and reports
timing. It is built by build.py and also ships in the prebuilt
package.
hip-onnx-runnerruns with random inputs by default. LLM models need in-rangeinput_ids(< vocab size); usetools/hip-onnx-runner/gen_hip_onnx_runner_inputs.pyto produce agen_inputs/directory and pass it via-i:python tools/hip-onnx-runner/gen_hip_onnx_runner_inputs.py \ -o gen_inputs /path/to/MODEL.onnx
# Run with hipgpu EP (default), on your model directly (dynamic shape)
$ROOT/bin/hip-onnx-runner -m /path/to/model.onnx -i gen_inputs
# Resolve symbolic input dims at runtime (the EP still compiles the dynamic graph)
$ROOT/bin/hip-onnx-runner -m /path/to/model.onnx -i gen_inputs -f sequence_length:128
# Run with CPU only (no EP)
$ROOT/bin/hip-onnx-runner -m /path/to/model.onnx -n -i gen_inputs
# Dump outputs for comparison
$ROOT/bin/hip-onnx-runner -m /path/to/model.onnx -i gen_inputs -d 2
# L2-norm compare EP vs CPU outputs
$ROOT/bin/hip-onnx-runner -L ep_o_dump,cpu_o_dumpKey flags:
| Flag | Description |
|---|---|
-m |
Path to .onnx model |
-n |
CPU only, skip EP registration |
-d 0|1|2|3 |
Dump level: 0=off, 1=inputs, 2=outputs, 3=both |
-i <dir> |
Load inputs from directory instead of random |
-f <name>:<val> |
Resolve a symbolic input dim at runtime (repeatable/comma-separated); EP still compiles the dynamic graph, unmatched symbolic dims default to 1 |
-L dir1,dir2 |
L2-norm comparison of two output directories |
onnxruntime_perf_test benchmarks inference latency. It ships in the prebuilt
package; a local build may not include it. The examples below compare hipgpu
EP against the CPU EP baseline (DML is Windows-only).
# CPU baseline (no EP; useful to size the EP speedup)
$ROOT/bin/onnxruntime_perf_test -e cpu -t 30 -c 1 -s /path/to/MODEL.onnx
# hipgpu EP
$ROOT/bin/onnxruntime_perf_test \
--plugin_ep_libs "hipgpu|$ROOT/lib/libhipgpu.so" \
--plugin_eps "hipgpu" \
-C "session.disable_cpu_ep_fallback|1" \
-t 60 -c 1 -s -I \
/path/to/MODEL.onnxKey flags:
| Flag | Description |
|---|---|
-t 60 |
Run for 60 seconds |
-c 1 |
1 concurrent thread |
-s |
Show per-iteration latency statistics |
-I |
Use sequential inputs (do not randomize) |
The first iteration triggers hipgpu's HIP kernel JIT compile (multi-minute); bump
-tso steady-state samples dominate the average.
model_benchmark benchmarks the full generative pipeline (prefill +
decode token generation). It is not part of the local build, so use the
prebuilt package to get it.
The EP is selected by the model's genai_config.json provider_options and
auto-discovered next to the OGA runtime lib -- do NOT pass --ep_library
(upstream model_benchmark rejects it). With the upstream OGA (v0.14.0 + PR2194)
the EP is the AMD GPU umbrella (provider_options [{ "AMDGPU": {"profile": "hip"} }]);
the prebuilt package bundles the umbrella libs.
# Auto-generated prompt (128 tokens, generate 32)
$ROOT/bin/model_benchmark \
-i /path/to/oga-model-dir \
-l 128 -g 32 -ml -1 \
-r 5 -w 1
# Prompt from file
$ROOT/bin/model_benchmark \
-i /path/to/oga-model-dir \
--prompt_file /path/to/prompt.txt -g 128 -ml -1 \
-r 5 -w 1Note on
-ml -1: required when the model uses a fixed-shape pipeline (prefill_*.onnx+decode_*.onnxwith a baked-inmax_lengthsuch as 256). Without it, model_benchmark overridesgenai_config.json'ssearch.max_lengthwithprompt_length + generation_length, causingGot invalid dimensions for input: attention_mask Got: <l+g> Expected: <max_length>at decode time. Pass-ml -1to keep the config's value.
Key flags:
| Flag | Description |
|---|---|
-i <path> |
Path to OGA model directory (with genai_config.json) |
-l <n> |
Length of auto-generated prompt (exclusive with --prompt_file) |
--prompt_file <path> |
Load prompt text from a file (exclusive with -l) |
-g <n> |
Max number of tokens to generate |
-r <n> |
Number of benchmark repetitions |
-w <n> |
Number of warmup runs |
The bin/hip-compiler invokes clang++ at runtime to link the per-model DLL
(driver-mediated link via ld.lld). The prebuilt package includes a
clang/lld toolchain in $ROOT/bin/ (next to the other tools), so the host
needs no separate clang install — just put $ROOT/bin on $PATH:
export PATH="$ROOT/bin:$PATH"No other LLVM/GCC tools are needed at runtime — the clang driver handles crt
selection, sysroot, multiarch -L paths, and the libstdc++ link line internally
(the host's system libstdc++/gcc supplies crt). A local build instead uses the
clang++ it was configured against.
Got invalid dimensions for input: attention_mask Got: X Expected: Y
Either:
model_benchmark -l <prompt_length>doesn't match the OGA model directory'sgenai_config.json→model.decoder.fixed_prompt_length. Pass the same value to-l, or pick a differentgenai_config_*.jsonthat matches your intended length.-ml(max_length) flag is missing — see the note in 6.3 OGA End-to-End Benchmarking. Without-ml -1, model_benchmark overrides the config'ssearch.max_lengthwithprompt_length + generation_length, breaking the static mask shape that decode_*.onnx expects.
Failed to get HIP device count or no devices available on bare-metal host
Most common cause: the host user is not in the render group, so opening
/dev/kfd fails silently and hipGetDeviceCount returns 0. Check:
ls -la /dev/kfd # expect group=render
groups # expect 'render' in the listFix: sudo usermod -aG render $USER && newgrp render (or relogin).
Inside ./docker/run.sh shell this is handled automatically — the
container entrypoint reads the host GID off /dev/kfd and adds the
in-container user to it, so you don't need host render membership.
EP library not found: libhipgpu.so from hip-onnx-runner
The runner's search order is $MORPHIZEN_EP_LIB (full path) → cwd →
<exe-dir>/libhipgpu.so → <exe-dir>/../lib/libhipgpu.so.
If you're running out of install/bin/, no env vars are needed — the
sibling install/lib/ is auto-discovered. If you've copied the binary
elsewhere, set MORPHIZEN_EP_LIB=/full/path/to/libhipgpu.so.
clang++ not found on PATH and HIPDNN_CLANG_PATH is unset or stale from hip-compiler
The per-model DLL link calls clang++ -shared as a subprocess; the
driver handles crt / sysroot / multiarch / libstdc++ for us. The path
is baked at configure time (HIPDNN_CLANG_PATH) with findProgramByName
as the runtime fallback, so this error only fires when both the
baked path is invalid (e.g. the package deployed to a host where the
build-time clang path no longer exists) and clang++ is absent from
PATH.
Fix on the deploy host: put the bundled clang on PATH —
export PATH="$ROOT/bin:$PATH" (the prebuilt package ships
clang++/ld.lld in $ROOT/bin, alongside the other tools).