Skip to content

qwen35 : in-place recurrent state rows for plain decode (GGML_GDN_ROWS_PLAIN=1) - #276

Open
jasontitus wants to merge 2 commits into
PrismML-Eng:prismfrom
jasontitus:downstream/qwen35-gdn-rows-plain
Open

jasontitus wants to merge 2 commits into
PrismML-Eng:prismfrom
jasontitus:downstream/qwen35-gdn-rows-plain

Conversation

@jasontitus

Copy link
Copy Markdown

Overview

The fused gated delta net has a rows mode that reads and writes each sequence's state rows in the recurrent cache in place. Today it is used only when n_rs_seq > 0 (speculative contexts with snapshots). Plain decode gathers every layer's state, runs the recurrence, and copies the state back; on the Bonsai 2 models that is 10-14% of a decode token on an M5 Max.

With GGML_GDN_ROWS_PLAIN=1, plain decode takes the rows path too, with one snapshot slot (K = 1). Output is bitwise identical. Off by default.

  • Only when no extra cells are relocated (n_rs == n_seqs): the relocation in build_rs_cache_view runs before the GDN read and, after a cell reorder, could overwrite a row another sequence reads (the gathered path reads first). Graph reuse compares the s_copy_extra size, so such a batch rebuilds and takes the gathered path.
  • GGML_GDN_ROWS_PLAIN_MAX_TOKENS=N limits it to at most N tokens per sequence: on an A19 the in-place recurrence op is ~18% slower at 512-token prefill while decode gains. Contexts with n_rs_seq > 0 keep rows mode at every width, as today.
  • delta-net-base.cpp accepts rows mode without snapshots (the assert that required n_rs_seq > 0 becomes the keep condition).

Related: #207 does an in-place GDN update for the CPU backend with a new op. This PR only reuses the existing rows path (Metal) for plain decode; the two touch the same lines in qwen35.cpp and delta-net-base.cpp.

Additional information

M5 Max, Ternary Bonsai 2 27B PTQ1_0, this branch, switch off vs on, three A-B-B-A quartets with 8 s cooldowns (tok/s; llama-server rates are aggregate over the requests, 128 greedy tokens):

Case Off On Speedup [quartet range]
tg128 (llama-bench) 38.95 41.96 1.076 [1.068-1.091]
pp512 (llama-bench) 722.8 755.9 1.046 [1.042-1.052]
llama-server, 1 request 37.49 40.13 1.070 [1.065-1.073]
llama-server, 2 requests 18.64 19.25 1.033 [1.021-1.040]

Generated tokens identical in every pair. Every logit bitwise equal off vs on for 96 single-token decodes plus 12 batches of 4 on PTQ1_0, Q1_0 and PQ2_0 Bonsai models.

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: YES. Claude (Anthropic) helped with the implementation, testing, measurements and this description; I reviewed the change and own it.

jasontitus and others added 2 commits September 25, 2026 16:41
…S_PLAIN=1)

The rows path of the gated delta net (the fused op reads and writes the per-sequence state rows of the cache)
is used today only when n_rs_seq > 0 (speculative contexts with snapshots). Plain decode instead gathers each
layer's state, runs the recurrence, and copies it back: 10-14% of a decode token on the Bonsai models (M5 Max
per-op profile). GGML_GDN_ROWS_PLAIN=1 takes the rows path for plain decode too, with one snapshot slot (K = 1);
the output is bitwise identical.

- only when no extra cells are relocated (n_rs == n_seqs): the relocation in build_rs_cache_view runs before
  the GDN read and, after a cell reorder, could overwrite a row another sequence reads (the gathered path reads
  first); graph reuse compares the s_copy_extra size, so such a batch rebuilds and takes the gathered path
- GGML_GDN_ROWS_PLAIN_MAX_TOKENS caps it by tokens per sequence (on an A19 the in-place recurrence is ~18%
  slower at 512-token prefill); contexts with n_rs_seq > 0 keep rows mode at every width
- off by default; read per context

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHqq1nTe46u7euASncLsee
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHqq1nTe46u7euASncLsee
@github-actions github-actions Bot added the model label Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant