Skip to content

llama : qwen35/qwen35moe GDN graph - 2-D output projection, in-place recurrent state, masked h_nextn fix - #57

Open
SimonTeixidor wants to merge 2 commits into
halo-box:masterfrom
SimonTeixidor:pr/qwen35-gdn-graph
Open

SimonTeixidor wants to merge 2 commits into
halo-box:masterfrom
SimonTeixidor:pr/qwen35-gdn-graph

Conversation

@SimonTeixidor

@SimonTeixidor SimonTeixidor commented Sep 14, 2026 •

Copy link
Copy Markdown

Branch pr/qwen35-gdn-graph (c976b39c4), 3 commits on halo/master 69946438a.

Overview

  1. b009b7972 Flatten the GDN output projection input to 2-D: one ssm_out matmul over n_seq_tokens*n_seqs columns instead of n_seqs matmuls that each read the whole weight. Same columns, same order; only n_seqs > 1 changes.
  2. e3f31cf6d Run gated_delta_net in place on the recurrent state cache (qwen35, qwen35moe). build_rs gains allow_inplace: with an identity sequence-copy mapping the state input is a view of the cache rows, not a get_rows copy; s_copy_main_identity joins the graph-reuse key. Off while rollback snapshots are kept (n_rs_seq > 0). Adds a seq_cp/graph-reuse check to test-recurrent-state-rollback (qwen35, qwen35moe), against no reuse, n_rs_seq = 1 and one sequence per ubatch.

Measurements

Device:     HP ZBook Ultra G1a 14 inch Mobile Workstation, AMD RYZEN AI MAX+ PRO 395 w/ Radeon 8060S (gfx1151)
Memory:     64 GB LPDDR5X unified (62 GiB visible to Linux; speed not read, no root)
Power:      AC, ACPI platform_profile=balanced, amdgpu power_dpm_force_performance_level=auto (GPU clocks not pinned)
BIOS:       UMA carve-out 512 MiB VRAM, GTT 60 GiB
Kernel:     7.1.13-200.fc44.x86_64, cmdline amdgpu.gttsize=61440 ttm.pages_limit=15728640 amd_iommu=off
Backend:    ROCm 10.0.0 + patched HIP runtime 7.16.26353-c4b77ac5cb (pwilkin rocm-systems, retained PM4), image localhost/strix-halo:stackv2-2d7825b37 (df09292858be); env HSA_OVERRIDE_GFX_VERSION=11.5.1 GGML_HIP_ENABLE_UNIFIED_MEMORY=1 DEBUG_HIP_GRAPH_PM4=1; HIP_LAUNCH_BLOCKING unset
Build:      localhost/strix-halo:builder (f1db10cdcd29), CMAKE_BUILD_TYPE=Release GGML_HIP=ON GPU_TARGETS=gfx1151 GGML_HIP_GRAPHS=ON GGML_HIP_MMQ_MFMA=ON GGML_HIP_NO_VMM=ON GGML_CUDA_FA=ON LLAMA_BUILD_TESTS/EXAMPLES/TOOLS=ON, -j 20
Baseline:   654803517 (halo/master), built and run in this session
Change:     c976b39c4 (pr/qwen35-gdn-graph)
Model:      Qwen3.8-27B-UD-IQ4_XS.gguf (qwen35, sha256 40fac4050e94...); Qwen3.6-35B-A3B-UD-Q6_K_XL.gguf (qwen35moe, sha256 35fce994cd36...)

Stock: 654803517, same flags, image and session. Mirrored blocks, n = 4 per build.

model test stock t/s (sd, n) PR t/s (sd, n) gain % 95 % CI %
Qwen3.8-27B batched decode, npl 4 31.32 (0.68, 4) 33.35 (0.47, 4) +6.45 +3.07 .. +9.83
Qwen3.8-27B batched decode, npl 6 35.26 (0.67, 4) 38.28 (0.50, 4) +8.55 +5.50 .. +11.61
Qwen3.8-27B batched decode, npl 8 37.70 (0.77, 4) 40.89 (0.57, 4) +8.44 +5.16 .. +11.72
Qwen3.6-35B-A3B batched decode, npl 6 107.59 (3.27, 4) 121.79 (0.83, 4) +13.20 +8.20 .. +18.19
Qwen3.6-35B-A3B tg128 @ d32000 46.41 (0.30, 4) 47.47 (0.21, 4) +2.28 +1.25 .. +3.31

Batched decode at npl 1-2 and every other pp2048 / tg128 cell (depths 0-64000) are unresolved.

Correctness:

  • test-backend-ops test -b ROCm0: GATED_DELTA_NET 50/50, SSM_CONV 45/45. No ggml code changes.
  • Model output byte-identical to stock on every check. 27B: final logits and token ids 5/5, KLD-base files 4/4 at 1 sequence and 2/2 at 4 sequences per ubatch (exercises commit 1), greedy decode with top-10 log-probs 5/5. 35B: 3/3, 2/2, 2/2, 3/3.
Raw results

Prompt processing: llama-bench -m MODEL -p 2048 -d 0,12000,32000,64000 -b 1024 -ub 1024 -n 0 -r 2 -ngl 99 -fa on -ctk f16 -ctv f16 --load-mode none -o jsonl. Token generation: same with -p 0 -n 128 -d 0,32000.

Qwen3.8-27B, prompt processing:

invocation test ub t/s sd n mean GPU power/temp
A-1-base pp2048 1024 285.38 0.37 2 46.2W/71C
A-1-base pp2048 @ d12000 1024 250.03 5.93 2 46.2W/71C
A-1-base pp2048 @ d32000 1024 211.80 0.28 2 46.2W/71C
A-1-base pp2048 @ d64000 1024 166.23 5.45 2 46.2W/71C
A-3-gdn pp2048 1024 274.35 0.99 2 45.6W/70C
A-3-gdn pp2048 @ d12000 1024 243.23 4.64 2 45.6W/70C
A-3-gdn pp2048 @ d32000 1024 203.35 7.83 2 45.6W/70C
A-3-gdn pp2048 @ d64000 1024 162.94 8.72 2 45.6W/70C
B-2-gdn pp2048 1024 274.50 0.39 2 45.6W/70C
B-2-gdn pp2048 @ d12000 1024 242.91 6.26 2 45.6W/70C
B-2-gdn pp2048 @ d32000 1024 209.75 0.38 2 45.6W/70C
B-2-gdn pp2048 @ d64000 1024 162.81 9.36 2 45.6W/70C
B-4-base pp2048 1024 276.30 0.06 2 45.7W/70C
B-4-base pp2048 @ d12000 1024 244.44 4.55 2 45.7W/70C
B-4-base pp2048 @ d32000 1024 210.22 0.66 2 45.7W/70C
B-4-base pp2048 @ d64000 1024 162.60 7.64 2 45.7W/70C
test ub stock t/s (n, sd) build candidate t/s (n, sd) gain % 95 % CI % Welch t resolved
pp2048 1024 280.84 (4, 5.25) gdn 274.43 (4, 0.62) -2.28 -5.28 .. +0.71 -2.43 no
pp2048 @ d12000 1024 247.24 (4, 5.39) gdn 243.07 (4, 4.50) -1.69 -5.34 .. +1.97 -1.19 no
pp2048 @ d32000 1024 211.01 (4, 1.00) gdn 206.55 (4, 5.84) -2.11 -6.58 .. +2.36 -1.50 no
pp2048 @ d64000 1024 164.41 (4, 5.81) gdn 162.88 (4, 7.39) -0.93 -8.28 .. +6.42 -0.33 no

Qwen3.6-35B-A3B, prompt processing:

invocation test ub t/s sd n mean GPU power/temp
A-1-base pp2048 1024 1106.23 21.09 2 45.5W/70C
A-1-base pp2048 @ d12000 1024 1004.04 0.95 2 45.5W/70C
A-1-base pp2048 @ d32000 1024 696.37 4.26 2 45.5W/70C
A-1-base pp2048 @ d64000 1024 570.34 2.29 2 45.5W/70C
A-2-gdn pp2048 1024 1214.24 16.20 2 45.3W/70C
A-2-gdn pp2048 @ d12000 1024 995.35 3.07 2 45.3W/70C
A-2-gdn pp2048 @ d32000 1024 769.95 2.60 2 45.3W/70C
A-2-gdn pp2048 @ d64000 1024 561.66 2.58 2 45.3W/70C
B-1-gdn pp2048 1024 1065.40 38.67 2 45.2W/70C
B-1-gdn pp2048 @ d12000 1024 997.12 10.21 2 45.2W/70C
B-1-gdn pp2048 @ d32000 1024 754.83 36.57 2 45.2W/70C
B-1-gdn pp2048 @ d64000 1024 565.88 5.24 2 45.2W/70C
B-2-base pp2048 1024 1113.39 18.60 2 45.2W/69C
B-2-base pp2048 @ d12000 1024 1000.29 1.31 2 45.2W/69C
B-2-base pp2048 @ d32000 1024 780.76 1.25 2 45.2W/69C
B-2-base pp2048 @ d64000 1024 539.62 44.11 2 45.2W/69C
test ub stock t/s (n, sd) build candidate t/s (n, sd) gain % 95 % CI % Welch t resolved
pp2048 1024 1109.81 (4, 16.75) gdn 1139.82 (4, 89.27) +2.70 -10.32 .. +15.73 +0.66 no
pp2048 @ d12000 1024 1002.17 (4, 2.36) gdn 996.23 (4, 6.24) -0.59 -1.65 .. +0.47 -1.78 no
pp2048 @ d32000 1024 738.57 (4, 48.79) gdn 762.39 (4, 22.90) +3.23 -6.90 .. +13.35 +0.88 no
pp2048 @ d64000 1024 554.98 (4, 31.06) gdn 563.77 (4, 4.16) +1.58 -7.40 .. +10.57 +0.56 no

Qwen3.8-27B, token generation:

invocation test ub t/s sd n mean GPU power/temp
A-1-base tg128 1024 12.97 0.07 2 45.4W/70C
A-1-base tg128 @ d32000 1024 11.00 0.42 2 45.4W/70C
A-3-gdn tg128 1024 13.03 0.05 2 45.2W/69C
A-3-gdn tg128 @ d32000 1024 11.35 0.04 2 45.2W/69C
B-2-gdn tg128 1024 13.01 0.13 2 45.0W/70C
B-2-gdn tg128 @ d32000 1024 11.33 0.04 2 45.0W/70C
B-4-base tg128 1024 12.91 0.05 2 45.1W/70C
B-4-base tg128 @ d32000 1024 11.25 0.04 2 45.1W/70C
test ub stock t/s (n, sd) build candidate t/s (n, sd) gain % 95 % CI % Welch t resolved
tg128 1024 12.94 (4, 0.06) gdn 13.02 (4, 0.08) +0.61 -0.39 .. +1.61 +1.58 no
tg128 @ d32000 1024 11.13 (4, 0.28) gdn 11.34 (4, 0.03) +1.94 -2.13 .. +6.00 +1.52 no

Qwen3.6-35B-A3B, token generation:

invocation test ub t/s sd n mean GPU power/temp
A-1-base tg128 1024 53.83 0.78 2 44.0W/68C
A-1-base tg128 @ d32000 1024 46.28 0.39 2 44.0W/68C
A-2-gdn tg128 1024 52.53 0.50 2 44.3W/69C
A-2-gdn tg128 @ d32000 1024 47.36 0.24 2 44.3W/69C
B-1-gdn tg128 1024 55.61 0.80 2 44.7W/69C
B-1-gdn tg128 @ d32000 1024 47.59 0.17 2 44.7W/69C
B-2-base tg128 1024 53.33 1.70 2 44.2W/69C
B-2-base tg128 @ d32000 1024 46.55 0.24 2 44.2W/69C
test ub stock t/s (n, sd) build candidate t/s (n, sd) gain % 95 % CI % Welch t resolved
tg128 1024 53.58 (4, 1.12) gdn 54.07 (4, 1.86) +0.92 -4.69 .. +6.53 +0.45 no
tg128 @ d32000 1024 46.41 (4, 0.30) gdn 47.47 (4, 0.21) +2.28 +1.25 .. +3.31 +5.69 yes

Batched decode: llama-batched-bench -c 8192 -b 4096 -ub 512 -npp 512 -ntg 128 -npl ..., decode throughput from speed_tg, one sample per invocation.

Qwen3.8-27B, -npl 1,2,4,6,8 (blocks A/C: stock, this PR, matmul PR; B/D reversed):

npl stock decode t/s (sd, n) build candidate decode t/s (sd, n) gain % 95 % CI % Welch t
1 13.05 (0.16, 4) gdn 13.14 (0.12, 4) +0.71 -1.29 .. +2.70 +0.91
2 21.45 (0.55, 4) gdn 22.11 (0.30, 4) +3.05 -0.99 .. +7.09 +2.09
4 31.32 (0.68, 4) gdn 33.35 (0.47, 4) +6.45 +3.07 .. +9.83 +4.91
6 35.26 (0.67, 4) gdn 38.28 (0.50, 4) +8.55 +5.50 .. +11.61 +7.19
8 37.70 (0.77, 4) gdn 40.89 (0.57, 4) +8.44 +5.16 .. +11.72 +6.62
invocation npl S_PP t/s S_TG t/s
A-1-base 1 220.80 13.29
A-1-base 2 285.97 22.22
A-1-base 4 276.18 32.28
A-1-base 6 277.49 36.25
A-1-base 8 280.78 38.77
A-2-gdn 1 219.29 13.31
A-2-gdn 2 281.76 22.52
A-2-gdn 4 277.92 34.04
A-2-gdn 6 277.18 38.93
A-2-gdn 8 279.17 41.62
B-2-gdn 1 207.95 13.14
B-2-gdn 2 277.97 22.05
B-2-gdn 4 268.41 33.17
B-2-gdn 6 272.73 38.36
B-2-gdn 8 276.16 41.06
B-3-base 1 193.60 12.97
B-3-base 2 276.93 21.13
B-3-base 4 268.11 30.79
B-3-base 6 268.39 34.81
B-3-base 8 267.88 37.14
C-1-base 1 238.48 13.04
C-1-base 2 278.03 21.48
C-1-base 4 274.82 31.32
C-1-base 6 271.13 34.90
C-1-base 8 274.60 37.77
C-2-gdn 1 188.55 13.09
C-2-gdn 2 278.51 22.04
C-2-gdn 4 269.46 33.18
C-2-gdn 6 272.35 38.10
C-2-gdn 8 276.03 40.45
D-2-gdn 1 217.64 13.04
D-2-gdn 2 276.18 21.81
D-2-gdn 4 264.45 32.99
D-2-gdn 6 271.37 37.73
D-2-gdn 8 271.90 40.41
D-3-base 1 241.11 12.92
D-3-base 2 274.05 20.98
D-3-base 4 270.83 30.91
D-3-base 6 267.23 35.09
D-3-base 8 266.42 37.13

Qwen3.6-35B-A3B, -npl 1,6 (A/C stock first, B/D PR first):

npl stock decode t/s (sd, n) build candidate decode t/s (sd, n) gain % 95 % CI % Welch t
1 48.47 (4.58, 4) gdn 50.66 (2.11, 4) +4.52 -9.92 .. +18.96 +0.87
6 107.59 (3.27, 4) gdn 121.79 (0.83, 4) +13.20 +8.20 .. +18.19 +8.41
invocation npl S_PP t/s S_TG t/s
A-1-base 1 879.33 50.35
A-1-base 6 950.70 104.18
A-2-gdn 1 841.01 48.20
A-2-gdn 6 1039.88 121.32
B-1-gdn 1 691.08 50.59
B-1-gdn 6 1058.27 121.00
B-2-base 1 895.19 53.14
B-2-base 6 996.92 107.71
C-1-base 1 858.80 48.02
C-1-base 6 1057.48 111.98
C-2-gdn 1 804.85 53.37
C-2-gdn 6 1071.24 121.96
D-1-gdn 1 919.82 50.50
D-1-gdn 6 905.65 122.89
D-2-base 1 835.53 42.37
D-2-base 6 942.52 106.49

Correctness: llama-debug --save-logits -b 16384 -ub 512 (prompt token ids and complete final logits, float32 x 248320); llama-perplexity --kl-divergence-base (all scored positions; log-probs quantised to 16 bits within 16 nats of the max, so identity is strong but not complete); llama-server greedy /completion, 256 tokens (128 on the 13480-token prompt), n_probs 10. Same -b/-ub, context, KV types and FA on both builds. The single-sequence checks cannot see commit 1 (n_seqs = 1 makes the reshape a no-op); the -b 2048 -ub 2048 -c 512 run gives n_seqs = 4.

check Qwen3.8-27B Qwen3.6-35B-A3B
llama-debug --save-logits, -b 16384 -ub 512: prompt token ids + complete final logits (float32 x 248320) 5/5 identical (4 corpora + 13480-token prompt) 3/3 identical (prose, code, 13480-token prompt)
llama-perplexity -c 512 -b/-ub 512 --chunks 4, KLD-base files (all scored positions) 4/4 identical; PPL prose 5.4246, code 1.9202, structured 2.4814, numeric 1.9349 2/2 identical; PPL prose 6.9016, code 2.1808
same with -b/-ub 2048 (n_seq = 4, 4 sequences per ubatch, exercises commit 1) 2/2 identical; PPL prose 5.4312, code 1.9198 2/2 identical; PPL prose 6.9016, code 2.1808
llama-server greedy /completion, 256 tokens (128 on the long prompt), n_probs 10 5/5 identical ids and top-10 log-probs at every step 3/3 identical

On the 35B the 4-sequence KLD-base files are also byte-identical to the 1-sequence ones on both builds; on the 27B they differ (PPL 5.4312 vs 5.4246), as batch-width kernel choice predicts.

Requirements

  • I have read and agree with the contributing guidelines
  • This change is justified by measurements on Strix Halo (gfx1151)
  • AI usage disclosure: AGENT-AUTHORED. Claude (Claude Code) wrote the commits and this description and ran the measurements; the owner reviews.
  • What was NOT verified:
    • Measured on 654803517. The branch was then rebased onto 69946438a, whose 6 new commits touch only ggml/rocmfpx, one Vulkan shader and tests/CMakeLists.txt; not re-measured.
    • qwen3next, qwen4exp (commit 1): not run, no model on disk.
    • Vulkan (GDN_STATE_CPY, mentioned in a comment): not built; ROCm only.
    • In-place state with seq_cp between steps: no dedicated test; --parallel > 1 server decoding not run.
    • MTP masked multi-row decode (commit 3): no path here exercises it.
    • Speculative decoding / llama-benchy: not run (MTP drafting uses n_rs_seq > 0, where in-place state is off).
    • Full test-backend-ops suite: not run (no ggml change).
    • One session, 4 samples per build per cell (-r 2 over two mirrored blocks, not one -r 4).

https://claude.ai/code/session_01MkuKwgPNgpAg7EbxKR2iWC

build_layer_attn_linear reshapes the gated-norm result to
[d, n_seq_tokens, n_seqs] before the ssm_out projection and then flattens the
projection result to [n_embd, n_seq_tokens*n_seqs] on the very next line. The
3-D shape therefore carries no information past the mul_mat, but it does decide
how the mul_mat is dispatched: src1 with ne12 = n_seqs is a batch of n_seqs
matmuls of ne11 = n_seq_tokens columns each, and every one of them re-reads the
whole ssm_out weight. Reshaping to [d, n_seq_tokens*n_seqs] instead gives a
single matmul of ne11 = n_seq_tokens*n_seqs columns over one weight read.

Mathematically identical: mul_mat is per-column, attn_out_norm is contiguous
(the 3-D reshape already required that), and the columns are in the same order
in both layouts, so the flattened call computes the same columns in the same
order. The reshape_2d after the projection becomes shape-preserving.

At n_seqs = 6 this is the difference between a batched call and six unbatched
ones on every backend, over 48 of these projections per decode step on the 27B
models. Note this only helps n_seqs > 1: one sequence with n_seq_tokens
speculative tokens already arrives as a single ne11 = n_seq_tokens matmul.

Only qwen35 and qwen35moe are changed, the architectures this was tested on.

Assisted-by: Claude Opus 5
…e cache

build_rs gains an allow_inplace option: when the sequence-copy mapping is the
identity, the state input is a view of the cache slot rather than a get_rows
copy. qwen35 and qwen35moe both opt in, since they build the same Gated DeltaNet
graph through llm_build_delta_net_base.

s_copy_main_identity joins the graph-reuse key: a seq_cp between steps changes
whether the mapping is the identity, so a graph built for one must not be reused
for the other.

test-recurrent-state-rollback gains a check that decodes two sequences in shared
ubatches while seq_cp swaps them, forks one and rolls it back, with graph reuse
on. The logits are compared with a context without graph reuse (bit-exact), one
with n_rs_seq = 1 and one that decodes one sequence per ubatch. It uses fresh
random weights at scale 1: at the 0.01 scale of the generated models the
recurrent branch does not change the logits, so a wrong state would not be seen.
The check runs for qwen35 and qwen35moe, and the test now also runs on the
generated qwen35moe model.

Assisted-by: Claude Opus 5
Claude-Session: https://claude.ai/code/session_01MkuKwgPNgpAg7EbxKR2iWC
@github-actions github-actions Bot added the model label Sep 14, 2026

@dzannotti dzannotti left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Validation: a Release HIP gfx1151 build completed.

Benchmark reproduced on the PR's Qwen3.6-35B-A3B target: tg128 at depth 32000 averaged 48.92 t/s on current master and 49.95 t/s on this PR across six alternating samples per arm: +2.11%. This closely matches the reported +2.28% result.

Correctness/test request: add a regression that interleaves two sequences with seq_cp/rollback and graph reuse, alongside the multi-row MTP case. Cover every changed architecture rather than only the reported Qwen variants.

Scope: this mixes a general GDN graph optimization across four architectures, a recurrent-state change, and an MTP correctness fix. It is not Strix-specific; split it and route the generic pieces through halo-box/llama.cpp/upstream.

comment generated by my clanker Codex

@SimonTeixidor

Copy link
Copy Markdown
Author

I reduced the scope of this one to the two model architectures that I have been optimizing a lot.

I'm trying to push this upstream as well, first step is here: ggml-org#28265.

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.

2 participants