Skip to content

feat(qwen4exp): fit the context to the devices, prefix snapshots in system memory - #832

Merged
davide221 merged 3 commits into
mainfrom
feat/qwen4exp-context-auto
Oct 9, 2026
Merged

davide221 merged 3 commits into
mainfrom
feat/qwen4exp-context-auto

Conversation

@davide221

@davide221 davide221 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Qwen3.8-Flash-Next now gets the largest context the box holds, without a hand-picked --max-ctx, and its prefix cache works at that context with four requests at once.

What changes

  1. Prefix snapshots in system memory beside a discrete GPU. Snapshots and concurrent prefix checkpoints were copies in the target GPU's memory, so in split mode they competed with the KV caches on the R9700. At 131,072 tokens with four slots their allowance was 473 MB: most captures failed, and in one run a request came out different after a cross-slot restore. They now go to system memory through create_snapshot_backend, as the other models' snapshots do, and the server's resident limit bounds them. On the gfx1151 iGPU they stay in its memory, which is the system's, under the planner's allowance as before.
  2. --max-ctx auto. The server starts from the model's trained context (262,144 tokens) and the qwen4exp backend lowers it, in steps of 4096, to the largest at which every slot's cache, the resident graphs, a floor-sized prompt chunk and, with snapshots on the target, one full snapshot per slot fit. The server then serves that context and /props reports it. The chunk planner returns what it measured (Qwen4ExpChunkPlan); Qwen4ExpContextFit shrinks a miss by its shortfall, then interpolates between the largest fit and the smallest miss. An unpark keeps the fitted context. Other architectures refuse auto.
  3. Split serving keeps one prompt chunk's memory on the target. At 131,072 tokens with four slots, a long prompt ran the R9700 out of memory. The chunk planner did not count two kinds of growth. First, every slot kept its own split schedulers and so its own prompt-chunk buffers (about 1.9 GB each); the slots now share the first slot's. Second, the CUDA pool keeps every block it frees while a chunk's temporaries grow with its position; split prompt chunks now release the cached blocks, and a failed graph allocation retries once after releasing them.
  4. --profile qwen-next-r9700-strix uses --max-ctx auto; a number on the command line still wins.

Results

UD-Q4_K_XL, adaptive MTP, greedy, R9700 + Strix Halo unless noted. Every run starts in about 15 s.

Layout Context 4 short prompts together Same output alone vs together
Split, 4 at once, before (131,072 set by hand) 131,072 76.6 tok/s 3 of 4
Split, 4 at once, auto 131,072 78.2 tok/s 4 of 4
Split, 1 at once, auto 262,144 4 of 4
Strix Halo alone (192 GB), 4 at once, auto 262,144 48.8 tok/s 4 of 4

Split, 4 at once, prefix cache on:

  • Two ~6K prompts beside two short ones: outputs identical alone and together (4 of 4).
  • A resent 16K prompt prefills in 0.3 s instead of 10.4 s, with the same answer.
  • A 6K-token checkpoint restores from system memory in 106 ms.
  • Every capture is saved.
  • A 96K prompt beside three short requests completes in 72 s. The R9700 keeps at least 2.15 GB free through all of these runs; before the fix in (3) it fell to 0 and the server died.

Strix Halo alone, 262,144 tokens with four slots: a 159K prompt beside three short requests completes, and a resent 16K prompt prefills in 0.2 s.

Validation

  • test_qwen4exp_prefix_cache: CPU checks, then on the Strix Halo and in split mode (system-memory snapshots, hot experts across park/unpark).
  • test_qwen4exp_seq_engine: prefix store and prefill cuts with checkpoints in device memory and in system memory, the QSA boundary, and exact distinct concurrent streams.
  • test_server_unit: 650 of 650 pass, including the new launch-profile check.
  • test_qwen4exp_chunk: the context search.

The split's four-at-once ceiling is the R9700's memory: 262,144 tokens for four slots needs part of the KV cache on the Strix Halo, which is the next step.

🤖 Generated with Claude Code

davide221 and others added 2 commits October 9, 2026 17:03
Prefix snapshots and concurrent prefix checkpoints were copies in the
target's memory, so beside a GPU with its own memory (the R9700 in split
mode) their allowance competed with the KV caches. At 131072 tokens with
four slots it was 473 MB: captures failed and the prefix cache stopped
helping. They now go to system memory through create_snapshot_backend, as
the other models' snapshots do, and the server's resident limit bounds
them. On the gfx1151 iGPU they stay in its memory, which is the system's,
under the planner's allowance as before.

Split mode, four slots, 131072 tokens: every capture saved, a 6K-token
checkpoint restores in 106 ms, a resent 16K prompt prefills in 0.3 s
instead of 10.4 s, and outputs match alone and together (4 of 4 short
prompts, 4 of 4 with two 6K prompts).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… devices

`--max-ctx auto` starts from the model's trained context (262,144 tokens)
and the qwen4exp backend lowers it, in steps of 4096, to the largest at
which every slot's cache, the resident graphs, a floor-sized prompt chunk
and, with snapshots on the target, one full snapshot per slot fit. The
server serves that context (/props n_ctx). The chunk planner reports what
it measured (Qwen4ExpChunkPlan); Qwen4ExpContextFit shrinks a miss by its
shortfall, then interpolates between the largest fit and the smallest
miss. An unpark keeps the fitted context. The qwen-next-r9700-strix
profile uses it.

Measured, starting in 15 s each:

| Layout | Fitted context | 4 short prompts together vs alone |
| --- | --- | --- |
| R9700 + Strix Halo, 4 at once | 131,072 | identical, 78.2 tok/s together |
| R9700 + Strix Halo, 1 at once | 262,144 | identical |
| Strix Halo alone (192 GB), 4 at once | 262,144 | identical, 48.8 tok/s together |

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

@davide221 davide221 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Code review

No issues found. Checked for bugs and logic errors in the changed code.

Reviewed 82397fd2 · 0 candidate issues checked, kept at ≥70% confidence · add the code-review label to review again

@davide221
davide221 marked this pull request as draft October 9, 2026 15:54
…arget

At 131072 tokens with four slots, the split ran out of R9700 memory on a
long prompt: a 96K prompt beside three short requests, and also a 96K
prompt alone. The chunk planner did not count two kinds of growth:

- Every concurrency slot had its own split schedulers, so each slot that
  ran a prompt kept a prompt chunk's buffers (about 1.9 GB each). The slots
  now share the first slot's schedulers. One forward runs at a time, which
  is what the planner counts.
- The target's CUDA pool keeps every block it frees, and a prompt chunk's
  temporaries grow with its position. During a prompt alone, free memory
  fell by about 270 MB per chunk until the allocation failed. Split prompt
  chunks now release the pool's cached blocks. A failed graph allocation
  retries once after releasing them.

Split mode, four slots, 131072 tokens: a 96K prompt beside three short
requests completes. The R9700 keeps at least 2.15 GB free through the
short, long and prefix-cache runs; before, it fell to 0. Outputs match
alone and together, and a 16K prompt prefills in 9.6 s, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@davide221
davide221 marked this pull request as ready for review October 9, 2026 16:20
@davide221
davide221 merged commit 49c6b01 into main Oct 9, 2026
10 checks passed
@davide221
davide221 deleted the feat/qwen4exp-context-auto branch October 12, 2026 07:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant