Skip to content

bump llama.cpp to 1703bfeaa: a gated-delta-net layer behind a type cast no longer fails the graph (fixes #1925) - #2134

Merged
bong-water-water-bong merged 1 commit into
mainfrom
1bit/1925-pin
Nov 11, 2026
Merged

bong-water-water-bong merged 1 commit into
mainfrom
1bit/1925-pin

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

Fixes #1925

Moves the llama.cpp pin 7a1f2b81c -> 1703bfeaa, one fork commit (1bit-MONSTER/llama.cpp#302), and documents it in docs/hrx.md.

What it fixes

Qwen3.8-Flash-Next, the first 512-token batch past 2,048 tokens of context with the sparse path's mask chain on the device:

graph_compute: unsupported HRX node 160: L2_NORM output=201:f32[128,16,512,1]<-L2_NORM inputs=[200:f32[128,16,512,1]<-VIEW] consumers=[174:GATED_DELTA_NET]

The fused dispatch matched that node; the graph was walked in the wrong order. The scheduler's dependency walk counted an in-place CPY's own output as an input it had to wait for, so the CPY of a type cast never became ready, the walk stopped at it, and everything after it was taken in index order, where the L2_NORM comes before the projections its pattern reads. #302 stops counting a node's own output and makes the fused matcher keep a match whose layout alias was already passed. The full trail is on the issue.

Measured (gfx1151, 2026-11-11)

The fork commit differs from the old pin in two source files. Device runs used an add-on build with exactly those two objects replaced ("new") against the same build unchanged ("old"); every run under its own gpu-hold ticket.

The failing case (Flash-Next UD-IQ3_XXS, -c 19456 -np 1, one 18,782-token request, mask chain on the device; ~/lb/aval/clhrxkern-1925-fix2-20261111T055051Z):

old new
unsupported HRX node 1 (node 160) 0
fused gated-delta-net roots accepted 183 of 184, then the graph fails 390 of 390
prefill stops at 2,048 tokens all 18,778 tokens

A model outside the sparse path (Qwen3.5-4B Q4_K_M, same layer type), because the walk changes for every graph that contains a cast:

3 pairs old/new, 2,758-token prompt, 128 tokens generated: 128 of 128 tokens identical in each pair
dispatch registrations used (GGML_HRX_LOG_DISPATCH=1): 33 old, 33 new, same names, same order
llama-bench -p 512 -n 64 -r 5, alternated      prefill tok/s   decode tok/s
  old   3516.0   57.4
  new   3041.6   58.3
  old   3109.0   58.1
  new   3212.7   58.2

Decode does not move. Prefill is within the spread of the two old arms.

Host: tests/test-hrx-gdn-passed-alias.cpp (7 cases, fails before the change with the model's two messages), hrx-backend-test host 66 passed, 0 failed.

Not shown

That the answer at 18.8k is the same with the mask chain on the device as on the CPU. Three of four long requests in these runs ended in all-NaN logits on the first sampled token, on the old library and the new, mask chain on or off. That is #1536, which now gates #679's 18.8k row in place of this issue.

Checks

$ python3 tools/check_pins.py origin/main
ok   third_party/llama.cpp: 7a1f2b81c -> 1703bfeaa is ahead
$ python3 tools/registry_build.py --check-pins
registry/architectures.json records the committed pins

The fork branch is one commit further (b6f65a273, another lane's); this PR pins the commit that was measured.

🤖 Generated with Claude Code

…st no longer fails the graph (fixes #1925)

One fork commit past 7a1f2b81c:
- 1703bfeaa (#302): the scheduler's dependency walk no longer counts an in-place CPY's own output as a pending
  input (a ggml_cast stalled the walk and everything after it was taken in index order, where the L2_NORM root of
  the fused gated delta net comes before the projections it reads); the fused matcher keeps a match whose layout
  alias was already passed.

Measured on gfx1151 with the two changed objects replaced in an add-on build: Flash-Next UD-IQ3_XXS 18,782-token
request, unsupported HRX node 1 -> 0, 390 of 390 fused roots accepted, the whole prefill runs. Qwen3.5-4B old vs
new library: 128 of 128 tokens identical in three pairs, the same 33 dispatch registrations, decode 57.4/58.1 vs
58.3/58.2 tok/s. Host: test-hrx-gdn-passed-alias 7 cases, hrx-backend-test 66/66.

Not shown: the 18.8k answer with the mask chain on the device against the CPU; those requests end in #1536's
all-NaN logits on the old and the new library alike.

registry/architectures.json: the recorded source pin only. docs/hrx.md: the #1925 section.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

ready for review at 7b419e2 (pin 7a1f2b81c -> 1703bfeaa, docs/hrx.md section, registry line; device and host rows in the body)

@github-actions

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis ✅

1925 - Fully compliant

Compliant requirements:

  • Fix the fused GATED_DELTA_NET matcher leaving its own L2_NORM input uncovered when the norm's view is strided
  • Enable the sparse path with mask kernels on the device to work correctly
  • Measure the fix on hardware with the specified model and context length
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Documentation Clarity

The documentation update in docs/hrx.md provides a good explanation of the issue and fix, but it could be more precise about the specific conditions under which the bug manifests. Specifically, it mentions that the issue occurs with "a long prompt" and "the sparse attention path's mask chain" but doesn't clearly state that the problem is specifically tied to the order in which nodes are processed during graph walking, particularly when a type cast (in-place copy) names its own output as an input.

### Fixed: a gated-delta-net layer behind a type cast failed the whole graph ([engine#1925](https://github.com/1bit-MONSTER/engine/issues/1925))

On Qwen3.8-Flash-Next, a long prompt failed at the first 512-token batch past 2,048 tokens of context when the
sparse attention path's mask chain ran on the device:

```
graph_compute: unsupported HRX node 160: L2_NORM output=f32[128,16,512,1]<-L2_NORM inputs=[f32[128,16,512,1]<-VIEW] consumers=[174:GATED_DELTA_NET]
```

The fused gated-delta-net dispatch did match that node. The fault was the order in which the graph was walked.
The scheduler walks a graph in dependency order and takes the matmuls first, which is what puts a layer's gate
and beta projections ahead of the `L2_NORM` its fused pattern starts from. A type cast is an in-place copy that
names its own output as an input. The walk counted that as a dependency, so the copy waited for itself, the walk
stopped there, and every node after it was taken in index order. In index order the `L2_NORM` comes before the
projections, and the pattern was refused because it would have read a projection that had not run. The sparse
path's mask chain contains such casts, so the failure needed both a long context and that chain on the device.

The walk no longer counts a node's own output as a dependency (llama.cpp #302). The fused matcher also keeps a
match when a node of its pattern that only renames storage was already passed.

Measured on gfx1151 (2026-11-11, Flash-Next UD-IQ3_XXS, `-c 19456 -np 1`, one 18,782-token request, a build of
the fork with only the two changed objects replaced): `unsupported HRX node` 1 before, 0 after; all 390 fused
gated-delta-net roots accepted; the whole prefill runs. `tests/test-hrx-gdn-passed-alias.cpp` plans the layer
behind a cast on the host and requires the same dispatches as without it. Qwen3.5-4B, which has the same layer
type and no sparse path, gives the same tokens on the old and the new library (128 of 128 in three alternated pairs), uses the same 33
dispatch registrations in the same order, and runs at the same rate (`llama-bench -p 512 -n 64 -r 5`, old, new,
old, new: decode 57.4, 58.3, 58.1, 58.2 tok/s; prefill 3,516, 3,042, 3,109, 3,213).

Not shown by this: that the answer at 18.8k is the same with the mask chain on the device as with it on the
CPU. Three of the four long requests in these runs, on the old library and the new, ended in all-NaN logits on
the first sampled token, which is [engine#1536](https://github.com/1bit-MONSTER/engine/issues/1536).

@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 7b419e2: READY

Reviewed head 7b419e2 (fixes #1925, release-blocker). A pin bump: third_party/llama.cpp 7a1f2b81c → 1703bfeaa, plus registry/architectures.json and docs/hrx.md.

The pin is the reviewed fork fix, and only that

  • 1703bfeaa is the squash-merge of llama.cpp#302 (merged=true, merge_commit_sha 1703bfeaa) onto the fork's 1bit/hrx-vulkan-patched. #302 had READY verdicts at 5742970 and at its final head cf4f36d.
  • git log 7a1f2b81c..1703bfeaa is that one commit. Its diff is the two source files the body names (dispatch-gated-delta-net.cpp +20/−1, graph-traversal.cpp +10) plus the new host test tests/test-hrx-gdn-passed-alias.cpp and its CMake row. Nothing else rides along.
  • registry/architectures.json's "llama.cpp (hrx)" line moves to the same 1703bfeaaaae16686e0ea5fd22b18b6fa2163fa1, so the registry and the gitlink agree.

Checked

  • git merge-tree onto current main: no conflicts.
  • CI at this head: build, hrx_build, CodeQL, "hrx-system gitlink agrees with the hold file" and "pinned loom-jit.cpp compiles against the pinned hrx-system" all succeed.
  • docs/hrx.md's new section matches the fix as #302 describes it (the in-place CPY no longer counts its own output as an input; a match whose layout alias was already passed is kept).

Not re-run by me: the device rows (Flash-Next 18,782-token request, 0 unsupported nodes; Qwen3.5-4B 128/128 identical tokens in three old/new pairs) and the fork's host test. I am taking those from the body and #302's reviews.

For the release lane, not blocking this PR: the fork has moved one commit past this pin. Its head b6f65a273 is llama.cpp#303 (merged), the #1962 draft-head logits fix (item 4), and 1703bfeaa is its direct parent. So this bump brings #1925's fix to engine main but not #303's. #1962 needs one more bump, or this one could go straight to b6f65a273. Either way, the #1962 row should not be read as landed from the fork merge alone.

@bong-water-water-bong
bong-water-water-bong merged commit 6f68403 into main Nov 11, 2026
12 checks passed
@bong-water-water-bong
bong-water-water-bong deleted the 1bit/1925-pin branch November 11, 2026 07:04
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

VERDICT: READY 7b419e2

Checked the two things a pin bump can get wrong, and the one claim in the docs that matters most to the release.

1. The bump points at the commit it credits, not an arbitrary tip. 1703bfeaa exists in the fork and its own subject is exactly the fix the docs section
describes:

1703bfeaa hrx: an in-place CPY no longer stalls the scheduler's walk, so the fused GATED_DELTA_NET is reached in order
           (engine #1925) (#302)

So registry/architectures.json and third_party/llama.cpp both move to the squash of #302 — a half-bump (registry updated, submodule not, or the reverse) is a real
failure mode here and this is not it. tools/registry_build.py --check-pins → records the committed pins, rc=0.

2. The docs section is honest about its own boundary, which is the unusual part and the reason I read it closely. It states the measured fix (1 unsupported HRX node before, 0 after; 390 fused GDN roots accepted; the whole 18,782-token prefill runs on gfx1151 — this box's GPU family), and then says explicitly:

"Not shown by this: that the answer at 18.8k is the same with the mask chain on the device as with it on the CPU. Three of the four long requests in these runs, on
the old library and the new, ended in all-NaN logits on the first sampled token, which is engine#1536."

That is the correct framing and it is load-bearing for the release: this bump fixes a graph-refusal (#1925), it does not fix #1536, and it says so instead of letting a
green "prefill runs" read as "long-context answer is correct". #1536 and #1346 are separate blocker rows; nothing here should be taken as closing either.

I also verified the mechanism against the code rather than the prose: the named test tests/test-hrx-gdn-passed-alias.cpp is in the pin, and the two-part fix matches
the description (the walk stops counting an in-place copy's own output as a dependency, and the matcher keeps a match when a storage-renaming node of its pattern was
already passed).

One suggestion, not a blocker: the docs line "with only the two changed objects replaced" makes the measurement reproducible only by whoever did it. A sentence naming
the build directory or the command would let a second person repeat the 18.8k observation that #1536 needs — my own long-context harness is in ~/lb/ and the row is
still waiting for exactly that.

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.

hrx: the fused GATED_DELTA_NET matcher leaves its own L2_NORM input uncovered when the norm's view is strided (blocks #679's structural fix)

1 participant