Skip to content

llama.cpp: pin e6c80014e, a fused HIP matcher can claim the node it is rooted at (#679) - #2315

Merged
bong-water-water-bong merged 1 commit into
mainfrom
cl-hrxmem/pin-332
Nov 12, 2026
Merged

bong-water-water-bong merged 1 commit into
mainfrom
cl-hrxmem/pin-332

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

What

Moves the llama.cpp (hrx) pin e7796262e → e6c80014e: third_party/llama.cpp gitlink + registry/architectures.json, the same two files as #2312. Refs #679.

The range is one commit, llama.cpp #332: hip_declare_tensor_claim / hip_tensor_claimed, consulted by device_supports_op.

  • A fused HIP matcher can now claim, for the scheduler, the node its fusion is rooted at. Before, supports_op judged that node alone and sent it to the CPU.
  • Without a registration (no private add-on), every supports_op answer is unchanged.

Why it matters

With the private add-on (gpu-kernels #69, hip.qsa_pool_blocks on the default f16 indexer cache), Flash-Next decode at 18,782 tokens:

per decode step at e7796262e at e6c80014e + #69
qsa_pool_blocks MATCHED cache=f16 0 64
scheduler splits 74 50
tensors copied into CPU splits 12 × 4.85 MB cache_idx_k_l* 0
greedy text – md5 identical, fusion on vs off

Verified at this pin

  • Pinned tree's own suite (gfx1151, e6c80014e, no add-on): hrx-backend-test host 85/85, device 13/13, including hip_tensor_claim and its scheduler arms.
  • With add-on 6a8922e on top: ggml-hrx-qsa-pool-check f16 and f32 at the 18.8k shape, 0 of 606,208 differ; the odd-stride arm refuses.
  • Not run here: the engine's own ctest at this pin (no engine checkout; disk is under the 175G floor). The diff is the two pin files only.

Decode tok/s is not claimed: one clean pair (off 8.79 / on 12.24 t/s) is not a row (see #679).

🤖 Generated with Claude Code

…s rooted at (engine #679)

e7796262e -> e6c80014e is one commit, llama.cpp #332: hip_declare_tensor_claim / hip_tensor_claimed, consulted by
device_supports_op. With the private add-on (gpu-kernels #69) the QSA indexer's f16 gather now reaches
hip.qsa_pool_blocks in Flash-Next decode. At 18.8k: 74 -> 50 splits per step, 0 tensors copied into CPU splits
(12 x 4.85 MB before), greedy text identical. Without an add-on registration, every supports_op answer is unchanged.

Pinned tree's own suite (gfx1151, e6c80014e, no add-on): hrx-backend-test host 85/85, device 13/13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

679 - Partially compliant

Compliant requirements:

  • Pin llama.cpp to commit e6c80014e
  • Update registry/architectures.json to reflect the new commit

Non-compliant requirements:

Requires further human verification:

  • Engine's own ctest verification
  • Private add-on testing to confirm fusion behavior

332 - Partially compliant

Compliant requirements:

  • No merge conflicts introduced by this PR

Non-compliant requirements:

  • The actual merge conflict resolution is not part of this PR

Requires further human verification:

  • The bump-hrx workflow still needs to be run to resolve merge conflicts
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Outdated Commit Reference

The commit reference in registry/architectures.json is outdated and should be updated to reflect the new pinned commit e6c80014e. This is a minor issue but ensures consistency with the actual pinned commit in third_party/llama.cpp.

"llama.cpp (hrx)": "e6c80014eb1c00bd4c1900d18379752227303fbb"

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: READY at e538cd6

The pin carries both fixes its own PR needed, and I checked that at the pin rather than by ancestry -- which for this fork is the only check that means anything, since it re-applies commits.

$ git rev-list --count e7796262e..e6c80014e          (in the fork)
1
$ git log --oneline e7796262e..e6c80014e
e6c80014e hrx: a fused HIP matcher can claim the node it is rooted at (engine #679) (#332)

And the content I asked for on #332 is in the pinned tree, not merely in the PR that produced it:

what at e6c80014e
GGML_HRX_HIP_TENSOR_CLAIM ggml-hrx.cpp:1, hip-capabilities.h:4 (the #if and the #ifndef guard)
the #389 arm tests/hrx-backend-test.cpp:16 (GGML_STATUS_SUCCESS), 13 hits for hip_tensor_claim

So the dead #define and the unarmed graph-level constraint are both closed at this pin -- a bump that landed the earlier 9b61cf9/755da13 shape instead would have carried a switch nothing read.

The registry line is load-bearing and I ran its negative control on this head:

$ python3 tools/registry_build.py --check-pins     # registry set back to e7796262e, gitlink at e6c80014e
registry/architectures.json: llama.cpp (hrx) recorded at e7796262e, pinned at e6c80014e
a pin moved without regenerating the registry …     rc=1

and the tree as pushed passes (records the committed pins, rc 0). Same check that ties the two lines together; nothing else in the tree names either sha except the historical sentence in docs/hrx.md.

The per-step table is the right way to present this, because the numbers are the PR's reason to exist and they are all downstream of the placement:

per decode step before after
qsa_pool_blocks MATCHED cache=f16 0 64
scheduler splits 74 50
tensors copied into CPU splits 12 × 4.85 MB 0
greedy text – md5 identical, fusion on vs off

And the honest line at the end is the one I would keep: "Decode tok/s is not claimed: one clean pair (off 8.79 / on 12.24 t/s) is not a row (see #679)." A 39 % single pair next to a 24-split reduction is exactly the number that would get quoted as a win, and it is labelled as not being one.

Noted, not blocking: "the engine's own ctest at this pin" was not run because there is no checkout and disk is under the floor -- the diff is two lines in one registry entry plus a gitlink, and the pinned tree's own suite (85/85 host, 13/13 device) is the check that matters for a pin. If a later lane runs the engine ctest at this pin, registry_pins and npu_pack_import_test are the two rows I would expect to be sensitive; neither is touched by #332.

Not run: the device tier and the paired add-on run (yours).

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY at 3985fd92f — verified against the artefact, not the description.

The PR moves the pin e7796262e → e6c80014e and both the gitlink and registry/architectures.json carry the same sha. I had that
commit locally, so the claim was checkable: e6c80014e is "hrx: a fused HIP matcher can claim the node it is rooted at (#332)" and it
adds exactly hip_declare_tensor_claim / hip_tensor_claimed in hip/hip-capabilities.{h,cpp} plus the consult in ggml-hrx.cpp.

What makes the mechanism sound, checked in the implementation:

  1. Claims are predicates, not stored tensor pointers. hip_tensor_claimed(op) iterates the registered predicates and calls them on
    the node being asked about. That kills the failure mode this design class usually has — a claim cached against a ggml_tensor * and
    going stale when the tensor is freed and the address reused. Nothing here is keyed on identity over time.
  2. The read path is lock-free and bounded. A fixed table of 16 atomics with a count, written once while the registries are built
    under the existing function-local static, deduplicated by predicate identity (the same register_* runs once per architecture). The
    read is a count load plus at most 16 atomic loads, and zero when no add-on is present.
  3. "No registration, no change" is structural, not promised: with zero predicates the loop body never runs and supports_op falls
    through to its normal answer. The #if GGML_HRX_HIP_TENSOR_CLAIM gate defaults to 1 and is always defined by the header, so an older
    fork still builds and -Wundef stays quiet.
  4. The test pins the false case as well as the true one — REQUIRE(!hip_tensor_claimed(named)) and
    REQUIRE(!ggml_backend_supports_op(backend, named)) before the declaration, then the same two inverted after. A claim hook is worth
    exactly the negative case, and it is asserted.

One note, not a blocker: table overflow is handled with GGML_ASSERT(n < kMaxTensorClaims && "raise kMaxTensorClaims"). That fires
inside registry construction, i.e. a library abort on a 17th predicate. Given the table is private to our add-on and 16 is generous, an
assert is defensible; a static_assert-style compile-time check is not possible here, but returning early with a log line would fail one
matcher instead of the process. Your call — the message at least names the fix.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT at e538cd6: READY

This is the pin I predicted one bump ago, and it carries both halves of #332 — verified in the submodule's clone, which is the lesson from my last pin review.

  • e6c80014e exists and is on origin/1bit/hrx-vulkan-patched.
  • Forward, not a rewind: e7796262e (the pin #2312 merged) is an ancestor. I checked this inside the llama.cpp clone this time — asking the engine repo answers "not an ancestor" for a commit it does not hold, which is a false negative that reads exactly like the danger the check exists for.
  • By content, which is what actually decides: the pinned hip-capabilities.h contains HipTensorClaim / declare_tensor_claim (2 matches) and the HIP_TENSOR_CLAIM env gate (4 matches). So the claim registry and the switch that gates its consult both arrive, which is more than the registry alone — the gate is what makes the mechanism bisectable on a box where a fused claim misbehaves.
  • registry/architectures.json changes the pin line.

Sequencing, so nobody has to reconstruct it later: #2312 was pinned at e7796262e and merged; this is the next link. The registry lands with the gate, so #332 needs no further bump. What is not here is the consumer side — the QSA gather claim lives in gpu-kernels#69, which is a different repo and lands separately; this pin makes it possible, not live.

One note for whoever watches the pair: a claim is only honest if a dispatch takes it (the invariant #332's header states — "a claimed node no dispatch takes fails the whole graph"). The pinned tree is the capability; the dispatch that honours it is a kernel PR. Two repos, one property.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant