RDNA4 (gfx1201 / Radeon AI PRO R9700) support - #1296
Open
The-Monk wants to merge 12 commits into
Open
Conversation
CCA (Claude Code Augmented)
Upstream maps consumer arches to None, so a Radeon box resolves to no runner
and no dispatch identity. Makes gfx1201 a first-class target.
gpu_identity.py r9700 -> (gfx1201, 64). CU count read from the board
(amd-smi NUM_COMPUTE_UNITS), not a spec sheet. Because
_AMD_GPU_TYPES and the rocm-smi product tags derive from
this table, one row also makes the board accepted by the
CLI and detectable by autodetect.
gpu_types.py gfx1201 -> r9700 runner. gfx1200 (Navi 44) deliberately
left unmapped: same ISA family, never measured here, and a
runner label is a claim of support.
examples/rdna/ custom_r9700.sh — llama.cpp scriptable runner. No core
change was needed: the bypass path already resolves an
operator script by the custom_{runner_type}.sh convention.
quantization Documents why mxfp4 is unavailable on RDNA4 — the MX
scaled-converts are gfx950/gfx1250-gated silicon, so a
capability probe grades them REJECTED. fp8 stays available.
VERIFIED ON HARDWARE (2x Radeon AI PRO R9700, gfx1201), not asserted:
- rocm-smi autodetect resolves r9700 -> ('gfx1201', 64)
- the runner benchmarks end to end: prefill 4488.33 t/s, decode 165.81 t/s
(Bonsai-8B Q2_0, pp512/tg64/r2)
- its inferencex_result.json is accepted by Hyperloom's OWN validator:
is_valid_measurement() -> True, scriptable detected. The harness judges
the output, not the author of the script.
- 756 upstream tests pass (gpu/quant/preflight/parser/provenance), 0 failures
Two negatives are deliberate and tested: gfx1200 unmapped, mxfp4 refused.
docs/rdna-port/SCOPE.md is corrected rather than deleted — it was written
against a 402-commit-stale clone and scoped a much larger change than upstream
now needs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013oPUZALTu4ZPwMdHDdwiQt
The issue AMD-AGI#1196 comment sends people here, and the landing page was still upstream's README with nothing identifying it as the RDNA port or disclaiming AMD endorsement. Banner only; upstream's text is untouched below it, and PR branches are cut from main, so this adds no diff noise upstream. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013oPUZALTu4ZPwMdHDdwiQt
Upstream AMD-AGI#1041 (@kyuubyN) is the RDNA3 counterpart to AMD-AGI#1196: Hyperloom's methodology independently re-implemented against their OWN HIP engine on gfx1102 (RX 7600) and gfx1100 (W7900), with measured results including a run whose honest verdict was "revert everything". Stated precisely, because the easy overstatement is wrong: that validates the APPROACH on gfx11. It does NOT establish Hyperloom running on RDNA3 — the runner map still excludes gfx11 and upstream's suite asserts the exclusion (_GFX_TO_RUNNER.get("gfx1100") is None). A result measured on A does not close B until B is measured; that rule is what the rest of this port is built on. Also carries over their engagement check: compare the actual rendered source each instance compiled rather than inferring engagement from log markers. It caught a knob that looked plausible but never reached the GPU — the same failure mode as optimizing a path that is not the live dispatched one. Invites RDNA3 support here and says what it needs. AMD-AGI#1041 was auto-close-bot pressured and closed by its own author earlier today; the work behind it is real and should not be stranded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013oPUZALTu4ZPwMdHDdwiQt
The banner named only AMD-AGI#1196. Anyone arriving from the RDNA3 side should see their thread acknowledged and the invitation, without digging into docs/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013oPUZALTu4ZPwMdHDdwiQt
Agents arriving on someone else's RDNA box need three things: what to do, a way to produce evidence, and somewhere to put it. AGENTS.md THE ONE RULE: a claim is MEASURED or ASSUMED, and an ASSUMED claim never becomes a support-matrix entry. A support entry is a promise to a stranger whose hardware you cannot see. §1 confirm support on hardware you have; §2 add a target that is not mapped (one identities row + _GFX_TO_RUNNER + runner + tests incl. the negatives + a matrix row); §3 the playbook for when it does not work. §3 is the part that makes a remote agent useful rather than just loud: exhaust the mundane before the exotic (perf-level gating -> env traps -> vendor tracker -> versions -> build flags -> only then kernel internals); isolate bottom-up by layer with that layer's own ground-truth tool, because cross-layer misattribution is the biggest time-waster there is; a symptom table for the failures this port actually produces; and the rules for claiming a fix (it ran, a correctness gate passed, the path you changed is the one that executed, speedup = baseline / candidate, suspect the baseline when a win looks huge, and a result above the roofline means you measured cache). examples/rdna/rdna-support-probe.sh Turns all of that into one command. Prints a paste-ready report and exits CONFIRMED (0) / PARTIAL (1) / UNSUPPORTED (2) — and on UNSUPPORTED it emits the exact rows needed to map that arch. The CONFIRMED verdict requires Hyperloom's own is_valid_measurement() to accept the run: the harness grades the output, never the agent that produced it. Dogfooded here, and three bugs it exposed are fixed: VRAM lives in a nested block (not a flat key), ROCm version comes from amd-smi, and the GPU count now counts amdgpu cards ONLY -- the first version counted this board's ASPEED BMC and reported a 2-GPU host as 3-GPU, which is exactly the sort of wrong detail that makes a whole report untrustworthy. Verified end to end on 2x R9700: CONFIRMED, exit 0, prefill 4448.74 t/s / decode 168.11 t/s -- within ~1% of the independent earlier run. .github/ISSUE_TEMPLATE/rdna-hardware-report.yml Requires the verbatim probe block AND a "what you did NOT test" field, since that is what keeps the matrix honest. Says plainly that negative results are welcome: nobody without your hardware can produce them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013oPUZALTu4ZPwMdHDdwiQt
Reported by JM: correctness was never mentioned. It was worse than an omission.
AGENTS.md preached "a wrong-but-fast kernel is worth zero" in §3.4 while the
probe stamped CONFIRMED on throughput alone, and the run I reported as our own
confirmation had quality_gate: None — no correctness evidence of any kind.
Hyperloom's is_valid_measurement() treats a MISSING quality_gate as
non-blocking. That is right for the harness (a scriptable run need not carry
one) and the wrong bar for a support claim, so the probe now adds its own gate
instead of leaning on the harness for something the harness never promised.
probe --bench now runs llama.cpp test-backend-ops FIRST, comparing ops
against the CPU reference on this arch, and CONFIRMED requires it to
pass AND a harness-accepted measurement. A fast run with no
correctness evidence now prints, in the verdict line itself,
"PARTIAL (throughput only — CORRECTNESS UNVERIFIED)" rather than
passing quietly. Default CORRECTNESS_OPS=MUL_MAT,MUL_MAT_ID (the
matmul paths a quant or arch port actually breaks); CORRECTNESS_OPS=ALL
widens it. The model-level PPL gate is surfaced as its own row.
AGENTS New §1.1a: correctness is half the verdict, not a footnote. Says
explicitly what is_valid_measurement() does not do, so nobody infers
a correctness claim from an ACCEPTED measurement. §2.3: if
correctness FAILS on a new arch, report it and STOP — do not tune a
kernel computing the wrong answer, and do not add a matrix row.
docs Matrix cites the correctness gate for gfx1201, not just the benchmark.
template Correctness is now a REQUIRED field, and a correctness failure is
declared a first-class report.
Op-level and model-level gates fail differently and both are worth having: ops
prove the kernels compute the right thing, perplexity proves the assembled
model still produces sane output.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013oPUZALTu4ZPwMdHDdwiQt
…cost The row read "(MUL_MAT,MUL_MAT_ID):0 MUL_MAT:1196/1196 ..." because the accumulator was initialised to 0 rather than empty. Cosmetic, but this string is the artifact strangers paste into issues, so it should not carry a phantom count next to real ones. Default gate measured at ~90s on gfx1201 (MUL_MAT 1196/1196, MUL_MAT_ID 865/865 vs the CPU reference) — cheap enough to be non-negotiable. Documented in AGENTS.md and carried into the support matrix alongside the throughput, so the matrix states both halves of the claim rather than speed alone. Our own CONFIRMED re-earned under the corrected gate: correctness PASSED + prefill 4476.34 t/s / decode 169.14 t/s ACCEPTED by is_valid_measurement(). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013oPUZALTu4ZPwMdHDdwiQt
The port was validated only against our own llama.cpp fork and a pinned ROCm.
That proves it runs here and says nothing about anyone else's machine, which is
the only question that matters for someone with their own card.
examples/rdna/container/ builds a clean rocm/dev-ubuntu-24.04 image, compiles
STOCK upstream llama.cpp from source for gfx1201, clones this fork from GitHub,
and runs the probe. Nothing of ours is in the stack: if the runner only worked
against our kernels, that would be a fork feature, not RDNA support.
RESULT — CONFIRMED, exit 0, in-container, ROCm 7.14.60850, upstream llama.cpp:
correctness MUL_MAT 1193/1193, MUL_MAT_ID 869/869 vs the CPU reference
benchmark prefill 3883.66 t/s / decode 93.97 t/s (Qwen3-8B-Q4_K_M)
harness ACCEPTED by is_valid_measurement()
Not comparable to the host figures: different model AND different quant
(Bonsai-8B Q2_0 there, Qwen3-8B Q4_K_M here, since Q2_0 is a fork-specific type
upstream cannot load). The claim is portability, not a speed comparison.
TWO REAL BUGS, both found by running it, both of which would have read as
"RDNA support is broken" to a newcomer:
libhipblas.so.3 not found — the ROCm dev image ships libraries in
/opt/rocm/lib but registers that path with NEITHER ld.so.conf.d NOR
LD_LIBRARY_PATH, so ldconfig -p knows no hipblas. Compilation succeeds
(cmake passes -L), then every ROCm-linked binary dies at LOAD time. Fixed
in the image, and the fix verifies itself in the same layer so a regression
fails the build instead of the run.
unable to find group render — --group-add resolves the group name INSIDE the
image, which has none. The host's numeric GID is what grants /dev/kfd and
/dev/dri. Corrected in both the Containerfile header and the README.
Worth recording: during the broken run the probe reported PARTIAL with the
linker error in the correctness row rather than a false CONFIRMED. The gate
refusing to pass is the gate working.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013oPUZALTu4ZPwMdHDdwiQt
Bug 1 (libhipblas.so.3 not found) was already fixed in the image. Verified it is a real fix and not a masked one: ldconfig -p now reports 4 hipblas entries, and llama-bench and test-backend-ops each have 0 unresolved libraries. Bug 2 (unable to find group render) had only been documented — I wrote the numeric-GID workaround into the README and moved on without asking whether the flag was needed at all. It is not. The container runs as root, which reaches /dev/kfd and /dev/dri through CAP_DAC_OVERRIDE; verified with no group flags at all: rocminfo sees both gfx1201 GPUs and test-backend-ops passes. --group-add is now gone from the documented command and is added only when dropping to a non-root user, where the host's numeric GID is genuinely required. Adds run.sh so the incantation does not have to be rediscovered: resolves the model path and mounts its directory read-only, passes the devices, forwards the knobs, supports DOCKER=podman and RUN_AS_USER, and fails immediately with a clear message when /dev/kfd is absent instead of 90 seconds into a run. Verified end to end through the wrapper: CONFIRMED, exit 0, correctness MUL_MAT_ID 869/869 vs the CPU reference, on stock upstream llama.cpp. A workaround in a README is not a fix; it is a note asking the next person to pay the same cost. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013oPUZALTu4ZPwMdHDdwiQt
The README claimed DOCKER=podman works. It did not: podman has no unqualified-search registry by default, so the short name rocm/dev-ubuntu-24.04 fails with "did not resolve to an alias and no unqualified-search registries are defined". Docker silently assumes docker.io; podman does not. Found while porting this recipe into the Rocky toolkit, where podman is the auto-detected engine — i.e. the claim was falsified by the first person to use the other engine, which is the whole reason to state such claims testably. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013oPUZALTu4ZPwMdHDdwiQt
…aim was engine-specific I verified that root in the container reaches /dev/kfd via CAP_DAC_OVERRIDE and wrote it up as a general fact. It is true for DOCKER (rootful) and false for ROOTLESS PODMAN, which is what podman defaults to here. Under rootless podman, in-container uid 0 maps to the invoking host uid, so /dev/kfd shows as nobody:nogroup and CAP_DAC_OVERRIDE does not apply. Host supplementary groups are dropped without --group-add keep-groups. The failure mode is the dangerous kind, and I hit it: rocminfo reported ZERO GPUs, test-backend-ops silently fell back to CPU-only and printed "1/1 backends passed" (vs 2/2 with a GPU), and the run exited 0. A green verify with no GPU attached is worse than a red one. run.sh now detects rootless podman and adds keep-groups; the README documents both engines with the measured evidence for each. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013oPUZALTu4ZPwMdHDdwiQt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description: what and why
Adds RDNA4 / gfx1201 (Radeon AI PRO R9700) as a first-class target. Opening this at AMD's request, following up on #1196.
Upstream validates against Instinct only:
_GFX_TO_RUNNERmaps consumer arches toNone, so a Radeon box resolves to no runner and no dispatch identity. This makes gfx1201 resolve, run, and report.The port is smaller than #1196 originally scoped — 4 modified files, +21 lines of core change, no refactors:
common/gpu_identity.pyr9700 → (gfx1201, 64). Thegpu_identityrefactor means_AMD_GPU_TYPESand the rocm-smi product tags both derive from this table, so the row also makes the board CLI-accepted and autodetectable. CU count read off the board (amd-smi NUM_COMPUTE_UNITS), not a spec sheet.inference_optimizer/gpu_types.pygfx1201 → r9700in_GFX_TO_RUNNER.orchestrator/phases/quantization_schemes.pyinference_optimizer/tests/test_rdna4_r9700_support.pyEverything else is additive and touches no core path:
examples/rdna/custom_r9700.sh(llama.cpp scriptable runner — the bypass path already resolves an operator script by thecustom_{runner_type}.shconvention, and it writes the InferenceX-shapedinferencex_result.jsonyour collectors already consume, so nothing downstream needed changing), a container recipe, a support probe,docs/rdna-port/, and an issue template.Why llama.cpp and not vLLM/SGLang: below ~32 GB the serving stacks' VRAM and concurrency assumptions do not transfer, and llama.cpp is what this class of card is actually served with.
Two things left deliberately negative, and tested that way
Three files in this diff are fork-only — flagging rather than pre-deciding
This branch doubles as the fork's landing page, so it carries framing that is correct there and wrong here. I've left it visible instead of quietly stripping it, because where the line falls is your call, not mine:
README.md(+23) — a "community port, not an AMD product, not endorsed by AMD" banner. Obviously not something upstream's README should say about itself. Assume this one is dropped unless you want some form of it.docs/rdna-port/README.md— line 3 opens with the same disclaimer; line 149 describes the fork's own branch layout.AGENTS.md— line 249 carries the disclaimer in the SPDX/licensing note.Say the word and I'll strip all three in a follow-up commit — it's a five-minute change and it does not touch the core diff. Separately:
AGENTS.mdand theREADME.mdbanner are the only root-level files here, so if you'd rather this PR carry nothing project-wide, dropping both leaves everything else intact.Linked issue(s)
magpie compareproduces no performance ranking on RDNA4/gfx1201 with backend: metrix Magpie#70 —magpie compareproduces no performance ranking on gfx1201.Tests: added/updated? commands run?
Added
test_rdna4_r9700_support.py(8 tests), including the two negative cases above.Measured on 2× Radeon AI PRO R9700 (gfx1201, ROCm 7.14, amdgpu 6.19.4) — verified, not asserted:
r9700 → ('gfx1201', 64).test-backend-opsMUL_MAT 1196/1196, MUL_MAT_ID 865/865.is_valid_measurement(), not by my reading of it.examples/rdna/container/.main:ruff checkclean,ruff format --checkclean,reuse lintcompliant.Not tested, and labelled as such in the docs: gfx1200, anything above 2 cards, RDNA3. The 2-card host reports
peers: falsewith host↔GPU capped at PCIe Gen3 (an on-board switch's upstream port downgrades the link; the leaf link reads Gen5 and hides it). A 4-card rig will hit walls this one can't see.Breaking changes: no
Additive. No existing target's behaviour changes; the Instinct rows and paths are untouched.
One thing this port surfaces: the profile-configuration feedback loop
Raising this as a question, not a change — it needs your call on a cross-system contract, so I haven't touched it.
The optimizer's whole value here is finding a profile configuration that beats the shipped baseline. On RDNA it does. But storing that result so it survives is where the current KB doesn't quite reach:
inference:{model}:{hardware}:{framework_name}:{model_type}:{architectures}:{framework_version}:{precision}—hardwareis a dimension, so anr9700recipe is already a distinct row and can't collide with an Instinct one. Good.StackFingerprintisvllm_version/aiter_commit/rocm_version— vLLM/Instinct-shaped by construction, and its docstring says it mirrors arbor's exactly "to keep binary readability." On the llama.cpp scriptable path there is no vLLM and no aiter, so two of three fields are empty.detect_framework_versionresolves via_FRAMEWORK_VERSION_MODULES = {sglang, vllm, atom}by importing the package. llama.cpp is a binary, not an importable module, so it falls through toDEFAULT_FRAMEWORK_VERSION_SLUG = "unknown_version".Net effect: a better-than-baseline configuration measured on gfx1201 can be written, but never invalidated or attributed to the build that produced it. Every llama.cpp recipe lands in the same
unknown_versionbucket with a fingerprint that can't answer "is this stale?" — so the loop that should make the optimizer compound across runs and across users doesn't close on this path.The ask: is there an intended way to record an engine identity for a non-importable, non-serving runner? A
llama_cpp_commit(or a genericengine_version) onStackFingerprintwould do it, but that field mirrors arbor and I'm not going to unilaterally change a contract shared with a system I can't see. If you point me at the right shape I'll implement it and measure it on the R9700s — and if the answer is "out of scope for now," that's a fine answer too; the port stands without it.The broader version of the same point: contributors with hardware AMD doesn't have will find configurations that beat the provided defaults. Right now there's no path for those to flow back.
examples/rdna/rdna-support-probe.shand the RDNA hardware-report issue template in this PR are a deliberately low-tech stand-in — one command, paste-ready output,CONFIRMED/PARTIAL/UNSUPPORTED, with a required "what you did NOT test" field. That's a bug tracker being used as a database, and it would be better as a real one.Related work — RDNA4 findings this port draws on
All measured on gfx1201; linked so the numbers above have provenance rather than being claims.
The stack this runner drives
roc8— the gfx1201 kernel work: int4 decode, 2:4-sparse SWMMAC prefill, e4m3 dot2, and aMUL_MAT_IDout-of-bounds fix.Upstream fixes filed from this hardware
__byte_permsign-replicate selector modes on the device path. Open.rocprofv3gaps thatdocs/rdna-port/README.mddocuments (host_trap-only PC sampling,FETCH_SIZEzeros, GL2C perf-level gating), and host tuning.The profiler gaps are worth a look for anyone porting Instinct-shaped tooling:
profile_standardun-gates the counters but throttles the card to ~1593 MHz vs ~2330, so it silently suppresses anything benchmarking alongside it.Hardware I don't own: @kyuubyN reported gfx1100/gfx1102 results in #1041, and a field-tester independently validated the ISA census and cache behaviour on an RX 7600 XT (gfx1102, ROCm 7.2.4). Both are RDNA3 — out of scope for this PR, and the reason the support matrix in
docs/rdna-port/README.mdmarks RDNA3 as methodology validated, runner map not covered./cc @arsalanfrq-a11y @l33tm4st3r @kyuubyN