Repository navigation
qwen35moe follow-ups: VRAM cap + quality mode, MTP for F16 draft tensors, chunked GDN, 256/8 route-resident - #1820
atomicmilkshake wants to merge 28 commits into
Conversation
… model for 8-12 GB cards The engine reads general.architecture from the GGUF and runs qwen35moe beside qwen4exp (Flash-Next) on the same code path, switching on the model's geometry: one residual stream instead of hyper-connections, dense attention (no indexer), no PLE, a silu(z) DeltaNet output gate, 8 of 256 experts 2048/512 wide. Flash- Next's compile-time sizes stay as capacities; kernels get template instances (8 or 12 query heads, 32/48 DeltaNet heads, 8/10 experts), so its code is unchanged: the same tokens and speed as before (Coder, 3 prompts + 8K, A/B). - decode (verify windows), batched prompt path (NVIDIA sm_75+; elsewhere the prompt is read through the decode windows), MTP draft layer read from the model file's blk.40, server, checkpoints, low-RAM mode - native Q2_K/Q3_K/Q6_K expert formats (Unsloth's mixes); iq_parity covers Q2_K and Q4_0 - greedy tokens match llama.cpp b11438 (UD-IQ4_XS: 3 prompts + 8K exactly) - RTX 4070 Ti 12 GB, 32K, MTP: UD-IQ4_XS 105 tok/s chat, 4,460 tok/s 8K read; 8 GB emulated 75 / 2,669; llama.cpp on the same PC 51 / 708 - small cards: the expert cache keeps the dense attention (grows with the context; 256K failed on every card before) and a 500 MiB floor out of the cache; a cache too small to work stops the start with what is short. UD-IQ3_S starts from 4.17 GB free at 32K, 3.9 GB at 8K + --draft-vocab en - data/expert-profile-qwen36.bin: ranked from 24 prompts' routing (--expert-profile-save, 64-slot cache); 12 GB writes +7-11% against an unranked one setup: families qwen36 (unsloth/Qwen3.6-35B-A3B-MTP-GGUF UD-IQ4_XS, UD-IQ3_S) and ornith (bartowski/Ornith-1.5-35B-A3B-GGUF IQ4_XS, IQ3_XXS), pinned revisions and SHA-256; suggested where no Flash-Next size fits the RAM; on a card under 5.5 GB the smallest size and an 8K context are recommended. The ready-made engines predate qwen35moe, so setup looks for it in the engine and compiles one when it is missing. MCP recommend() follows the same rules. Tests: tools/test_setup_qwen36.py, tools/test_setup_ornith.py. Docs: docs/QWEN36.md (setup, what fits, small cards, speed, llama.cpp, Ornith, limits), MODELS.md, README. Tested on Linux (Debian 12, kernel 6.1) with an NVIDIA RTX 4070 Ti 12 GB (driver 580), CUDA 13.0, Ryzen 9 5950X, 62 GB RAM. Not run on AMD or Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts, resolved for both models: - setup.py: the Flash-Next draft-layer fetch (now with STRATA_SOURCE) stays in the branch the 35B-A3B families skip (their draft layer is in the model file) - verify.cpp: STRATA_DF_BRANCH's query branch only with the indexer (its side 1 is the indexer query); qwen35moe takes the plain path - qsa_decode_attn.cu: the pre-sm_75/HIP kernel takes both template parameters (query heads per K/V head G; NC_/DPP_ for gfx103x) - pool.cpp: the native phases' rows from the format (n_ff/n_embd), tasks from phase_tasks (--pool-tasks) - prefill.cpp: the deferred shared expert and the CPU share's buffers with the model's sizes; the CPU share (opt-in) is off for one-stream models (unmeasured) - generate.cpp: the dense-attention log line and the APU warning both kept Also: QWEN36_ENGINE = 0.1.40.2 (this tree's version, setup's four-number versions); docs/QWEN36.md names 0.1.40.2 among the engines without qwen35moe, and says the llama.cpp match is exact with a fixed cache (a near-tie can move with the cache's contents). ModelScope serves the four 35B-A3B files with the pinned SHA-256. Checked after the merge (RTX 4070 Ti, Linux, CUDA 13.0): Flash-Next Coder tokens identical to upstream's own build (3 prompts + 8K); Qwen3.6 UD-IQ4_XS and Ornith IQ4_XS tokens identical to the pre-merge engine at the same cache sizes; speed unchanged; small-card floor and 256K as before; iq_parity and native_grouped_parity 0 failures; setup/MCP tests pass except test_setup_engine_hash.py's two, which fail the same way on upstream/main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts, resolved for both models: - prefill.cpp: main's routing debug hook (STRATA_DBG_FEAT, opt-in) and CPU-share arms only for Flash-Next (one-stream models keep the CPU share off); the peer-device inputs, sums and combine with the model's sizes - kernels.cu / kernels.hpp: the opt-in chunked GDN recurrence (STRATA_GDN_CHUNKED) only for Flash-Next's 48 value heads, VPK kept for it; the peer-device kernels beside the templated moe_combine - mtp.cpp: formatting only Also: - generate.cpp: a qwen35moe model refuses --peer-device, --layer-split and --expert-cache-device1..3 at the start (their code carries Flash-Next's shapes) - sycl/src/prefill/kernels.dp.cpp: the prompt kernels take the shared header's new size parameters (they were separate overloads: an ambiguous call); any size but Flash-Next's stops with a message - the SYCL engine has Flash-Next's kernels only - QWEN36_ENGINE = 0.1.41 (this tree); docs/QWEN36.md names 0.1.41 among the engines without qwen35moe Built: CUDA (sm_89, with tests), HIP (gfx1100, gfx1201, gfx1030, gfx1151; and STRATA_HIP_GFX906 for gfx906) with ROCm 7.10 (TheRock wheels), SYCL (the strata-sycl-dev image, every target). Run on an RTX 4070 Ti only. Checked (RTX 4070 Ti, Linux, CUDA 13.0): Qwen3.6 UD-IQ4_XS = llama.cpp b11438 on 3 prompts + 8K (cache 3,000 slots); Ornith IQ4_XS as before; Qwen3.6 speed, small-card floor (4.15 GB free starts) and 256K as before; iq_parity and native_grouped_parity 0 failures; setup/MCP tests pass except test_setup_engine_hash.py's two (they fail the same way on main). Flash-Next Coder: prompts 1 and 2 identical to main's own build, prompt 0 alternates between the same two outputs in both; the 8K prompt varies from run to run in main's own build too (fixed cache, no adaptive swaps, no PCIe share, with and without MTP: 3 runs, 3 outputs), and the merged build's outputs are among main's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 16-bit K/V (no --kv, as setup writes at 8K) stopped at the first append with "qsa: kv_append: geometry is not set up": the size check required the indexer's sizes, which a model without one leaves at 0. Dense attention no longer needs them; Flash-Next keeps the same check. - strata serve on a card without the batched prompt path for the model (qwen35moe below sm_75, or HIP) failed with "prefill: geometry differs from the artifact's". It now says so at start and reads every prompt through the decode windows, as one-shot generate already did. - setup asks before the download on Pascal / Volta cards, as it does on AMD. Tested on Linux + RTX 4070 Ti + CUDA 13: 16-bit K/V at 8K gives llama.cpp's tokens on 4 prompts; the fallback with the sm_75 check raised in a scratch build (server: a 9,802-token prompt in 113 s, sensible answers). Not run on a real Pascal or AMD card. Flash-Next Coder tokens = main's; HIP and SYCL build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ense attention) A qwen35moe model attends to every cell, so with KV streaming (--kv-resident, which setup writes from a 64K context) a selection past the resident cells names more blocks than the VRAM slots hold. The resolve kept its misses in a list of n_slots entries and wrote past its end; the blocks it could not place stayed claimed (page -2) for good, and the attention masked every non-resident cell. From 32K tokens on, the model lost about 40% of its context: on a 52.7K-token agentic transcript KL against llama.cpp was 0.57-1.16 (int8 and fp16), with wrong tool calls. - kv_stream.cu: the resolve claims at most n_slots misses; the rest go back to page -1 and count as read from RAM. ctl[3] (overflow) now means "some blocks were read in place", and the end-of-request line says so. - qsa_decode_attn.cu: a selected cell whose block is not resident is read from the layer's host copy in place (QsaAttnPools::host_*, set by qsa_attn_pools for a streamed layer) instead of masked. Flash-Next's indexer selections always fit the slots, so its path never takes it. - kv_stream_parity: a dense selection of 10,000 blocks over 4,828 slots, twice: bitwise the resident pool's output, nothing left claimed, overflow set (int8, fp16, q4_0, k8v4). Checked: KAT-Coder i-quality-v2 on an RTX 4070 Ti 12 GB, 64K context, against llama.cpp on the same file at 12.8K-52.7K tokens: KL 0.0006-0.009, top-1 98-100%, with --kv-resident 32768 in int8 and fp16, as without streaming. Flash-Next IQ3_XXS with --kv-resident 32768 on a 52.7K prompt: the same 143 tokens as the engine before this change (which itself differs from run to run at token 88). Built: CUDA, HIP (gfx1100, gfx1201, gfx1030, gfx1151), gfx906, SYCL. Speed: past the resident cells every token reads the rest over PCIe. 52.7K-token prompt, 64K context, no desktop on the card: 27.0 tok/s streamed vs 59.6 with the KV cache in VRAM (12 GB); 23.3 vs 47.0 (8 GB emulated). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…om 128K, asked otherwise Streamed, a qwen35moe model reads the KV cache past 32K tokens from RAM for every token. Measured with KAT-Coder i-quality-v2 (8-bit KV) on an RTX 4070 Ti 12 GB with no desktop, whole and with 4 GB held: - 52.7K-token prompt, 64K context: 59.6 / 47.0 tok/s with the KV cache in VRAM, 27.0 / 23.3 streamed; - 2K-token prompt: as fast or faster streamed (more expert slots); - 7.9 GB free: 128K and 256K fail at the first prompt unless streamed; 10 GB free: both run. So for qwen36 / ornith / their fine-tunes from a 64K context (not WSL): - under 9 GB of VRAM free (nvidia-smi memory.free, so a desktop counts) from 128K: streaming is turned on, and setup says why, what it costs and that --context 65536 avoids it; --kv-streaming off is kept with a warning that the first prompt fails; - otherwise setup explains the trade-off and asks, recommending no (--yes: no); - both say how to change it later (the config's args, or --kv-streaming on|off). --kv-streaming on|off answers it as before. Flash-Next is unchanged and never asked. Tests: tools/test_setup_qwen36.py KvStreaming (needed vs asked, free VRAM not the card's size, the option, Flash-Next) and FreeVram. test_setup_engine_hash fails as before this change (2 failures). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GGUF draft-layer loader took F32 for the layer norms and BF16/F32 for the routers, so a third-party single-file GGUF whose MTP block keeps ffn_gate_inp.weight / ffn_gate_inp_shexp.weight at F16 refused to start with "mtp: blk.40.ffn_gate_inp.weight is neither BF16 nor F32" (the Huihui Qwen3.6 Q4_K files ship them as F16). - norms: F16 widens to F32 exactly (every half has an exact float image). - routers: F16 -> F32 (exact) -> BF16 (round to nearest even), the rounding the F32 branch has always used; the consumers (bf16_gemv_fp32_mmvf, shared_expert) take BF16 only. - The BF16 (memcpy) and F32 paths are unchanged; the F32 branch now calls the same expression through f32_bits_to_bf16. - The two new helpers were checked over all 65536 half bit patterns against an independent reference: 0 mismatches for F16->F32 and for F16->BF16. For the target file the conversion is bit-exact: 0 of 526336 router weights change.
Keep qwen4exp's default width 10 and STRATA_LOOKAHEAD_K overrides unchanged. Log the selected width only for qwen35moe. CUDA sm_86 built; runtime reports width 8. Fixed cache budget 3000 (3215 actual slots), 5/513-token prompts: both 32-token greedy completions match 44fdbbd's binary. Single-pair decode 32.55/35.52 -> 32.24/35.17 tok/s; no speed claim. Initial auto-cache runs were confounded by different resident sets.
Template prep/scan, scratch and launch attributes for 32 value heads and 8192 conv channels; keep the existing SiLU output norm and all opt-in/hardware thresholds. All 65 pre-existing prefill kernel instruction streams remain byte-identical on sm_86, including Flash-Next's chunked kernels. Runtime on RTX 3080 + Ryzen 5800X3D: flag 1 falls back (68 SMs); flag 2 executes the new 79360-byte shared-memory instance. Flag 0/1/2 all produce the pre-change binary's identical 32 greedy IDs on a 513-token prompt. Recurrence timeline rounds to 9/8/10 ms; no speedup claimed on this card. HIP/SYCL not built: compilers not on PATH.
Advertise the F16 norms/router loader with an engine-owned marker; setup only points --mtp at the same GGUF when that marker is present. Stock PR engines keep MTP off with no fallback download. Test both config paths and default-family behavior. Correct MTP/determinism notes, state the Huihui Apache-2.0 base and repo, and document the deliberate menu renumbering. Leave the verified KV predicate and its generic diagnostic unchanged.
Mock the Windows HIP engine/card path as well as CUDA so mocked setup runs cannot reach a real download or device probe. Compare the IQ3_S native path with pathlib instead of POSIX separators. Fix two pre-existing Windows-only failures exposed by the integration regression run; no runtime code changes.
20f4d4b generalised validate() for a model without an indexer in the CUDA kernel but left sycl/src/kernels/cuda/qsa.dp.cpp with the old indexer-size requirements. Mirror the same two predicates so the SYCL build accepts qwen35moe's dense geometry too.
Quality routes every native expert miss through the grouped GPU kernel, grows and budgets its PCIe staging, disables prefill CPU sharing, and refuses CPU fallback. It also supplies an uncapped same-contract reference. Fast remains the default. Capped startup sizing freezes a pre-touch budget with one WDDM haircut and fails rather than resizing after touch or allocation failures. Uncapped fast sizing and arithmetic stay unchanged. Add CPU cap, planner and CLI regression tests; GPU parity/peak/speed validation is queued, not run.
Parameterize the route-resident API and kernel body by expert count and top-k, retaining the original 512/10 CUDA entry point. Add a 256/8 specialization, geometry-aware tail ranks, and host argument/span checks before widening the verifier guard. Keep the feature off by default.
The post-touch free-VRAM read is not a size: the same allocation reports 0 MiB or a few hundred MiB, and a 0 reading gave back 1 GiB twice. A large auto cache now subtracts 1 GiB from the pre-touch budget once.
Cut the shortfall plus 64 MiB, rounded up to 256 MiB, instead of a flat 1 GiB. Keep that size only when post-touch free VRAM clears the reserve within 64 MiB. One correction is allowed; a second miss exits 1. qwen4exp stays on the measured-shortfall loop.
--vram-frac / --vram-cap-mode and a capping STRATA_VRAM_FRAC now exit 2 with 'the VRAM cap is not implemented in the SYCL build' instead of falling through to 'unknown argument' (flags) or being silently ignored (env). No cap port; CUDA/shared files untouched. Compile-UNVERIFIED (no SYCL toolchain).
PR Niko1221#1737 replaced the serial expert scan with a warp shuffle, hardcoded to 512 experts and top 10. That reduction now lives in the parameterized kernel. Both widths are multiples of the warp, a strictly greater logit wins, and the smallest index breaks ties. The qwen4exp top-10 router is unchanged.
Chunked-GDN scratch, MTP prefill records, and an explicit verify-graph estimate are charged in the frozen pre-touch cache budget when those features are on. Serve checks the floor before the first prompt chunk and before each verify window, and cudaGraphInstantiate drops the new graph if free memory is under the floor. Telemetry reports free MiB at READY, first capture, first chunked-GDN launch, and first checkpoint.
…ck + refusal The 30 MiB x (2*T+1) verify-graph estimate over-booked (~4x the measured 12-16 MiB per window on the RTX 3080) and double-counted the existing cap_late_bytes graph/hit-scratch slack. Booking only the chunked-GDN scratch and MTP prefill records keeps the safety in the post-instantiate refusal while restoring ~150 cache slots.
The F16 -> BF16 rounded addition carried a NaN whose low mantissa bits were all ones into infinity (0x7c01 -> 0x7f80) or into a signed zero (0x7fff -> 0x8000). The shared f32_bits_to_bf16 now special-cases NaN and keeps a nonzero BF16 mantissa; infinity and every finite value still take the nearest-even path, and the F32 router path (which used the same expression inline) gets the same fix. Found by an adversarial review of this branch.
The gate only refused a successfully parsed 0 < F < 1, so 0, NaN, an empty value, trailing junk and underflow-to-zero were silently ignored while the CUDA build errors on them. Now a malformed value exits 2 with the same wording; an unset value or exactly 1 keeps the cap off as before.
…before pipelined windows The cap-floor work checked the floor only before the first prompt chunk, so a long prefill could cross it in a later chunk (the first-use chunked-GDN scratch and the later kernels had no check), a fully reused prompt skipped the prompt checks entirely before decode, and the --pipeline-windows decode loop called pl_launch() without any guard. - Prefill gains an optional per-chunk admission guard, called at the top of every m.T-sized chunk; the capped path sets it to the floor check (one-shot and serve). - Serve reads the floor before every prompt segment and again at decode admission (after REUSED), which covers the fully-reused-prompt case. - Every pl_launch() on the pipelined prompt and decode paths is preceded by the same floor check. All refuse with the existing "cap not relaxed" wording.
…scratch Follow-up to the floor-enforcement commit after a re-review. The speculative B launch and the promoted A's stage-1 launch had no floor check, so a successful speculative chain could run on without one. The first-use chunked-GDN scratch allocated without checking the floor (it only logged after the launch). All five pl_launch sites are now preceded by the check, and gdn_chunk_scratch checks the floor before cudaMalloc: it prints the cap line and returns null (default recurrence) rather than allocating under the floor. The scratch bytes are already booked in the frozen cache budget.
A re-review noted the check passed while free >= floor even when the pending allocation itself would cross it. vram_floor_allow now takes the bytes about to be taken (default 0, so every existing caller is unchanged) and the chunked-GDN scratch passes its size. Also corrects the scratch comment: a null scratch becomes cudaErrorMemoryAllocation, not NotSupported.
…e; router renorm clamp Review follow-ups on top of 650cf81 (whole-project review): - mtp.cpp: the MTP prefill records (prepare_prefill and the grown chunk buffer) now charge their bytes through vram_floor_allow before cudaMalloc, so short headroom refuses instead of allocating under the floor and keeping the buffer when a later graph check fails. - verify.cpp: capture_commit and the batch commit path now upload + sync + re-check the floor after instantiate, as the window capture already did; the exec is destroyed on failure. - generate.cpp: check_vram_cap takes an optional needed_bytes and prints a 'strata vram: REFUSED' line, matching vram_floor_allow. - sycl/src/program/generate.cpp: the CLI fraction now wins over STRATA_VRAM_FRAC as on CUDA; --vram-frac 1 clears a capping environment; --vram-cap-mode fast is accepted (quality still refused). - native_router.cu: the route-resident swap renorm uses the same 2**-14 lower clamp as the default router, so a row of very small logits cannot divide by a zero/denormal sum.
|
The 256/8 generalization is exactly what this wanted. Two things that may save you work. The win survives the new defaults. On 0.1.42 with A parity test, if the parameterized kernel does not have one yet.
|
|
Extracted the 256/8 route-resident generalization and parameterized parity test into a clean, standalone single-commit PR on top of \main: #2321. |
|
Thanks for the pull request. We're resetting the tracker for 0.2.0-beta. There are more than 1,300 open issues and pull requests, too many for us to review properly, so we're closing everything that isn't being worked on right now. Closed here does not mean rejected. To reopen it takes about two minutes:
The rules are in CONTRIBUTING.md. |
What this is
A stacked PR on top of #1402 (qwen35moe support). It contains the qwen35moe follow-up work we built
and measured while validating that PR on one machine (RTX 3080 10 GB, Windows 11, CUDA 13.2, sm_86):
--vram-frac) and a quality cap mode (--vram-cap-mode quality)that keeps every expert miss on the same GPU kernel as a hit;
merged with our 256/8 implementation);
Base: #1402 head
48534f3. The first commits in the diff are #1402's; the 16 commits listed below arethis PR's content. The five upstream PRs we also adopted locally (#1700, #1660, #1659, #1696, #1720) are
deliberately not included — they are separate PRs and should land on their own.
The repo's guide is one change per pull request. This is a stack of related qwen35moe follow-ups; I am
happy to split it into separate PRs (MTP-F16; cap+quality; chunked GDN; route-resident 256/8) if that is
easier to review. The MTP F16 commit is the smallest and probably the most valuable on its own.
Commits
MTP for third-party GGUFs
MTP: accept F16 draft tensors in a single-file GGUF— the draft-layer loader took F32 for norms andBF16/F32 for routers, so a single-file GGUF whose MTP block keeps
ffn_gate_inp.weightat F16 refused tostart. F16→F32 is exact; F16→BF16 uses the same round-to-nearest-even as the F32 path. The finite cases
check out over all 65,536 half bit patterns; NaN needed a later fix (the first checker skipped NaNs, and
the rounded addition carried them into infinity/signed zero — see the follow-up commit below). Measured on
the Huihui Q4_K file: 62.5 tok/s vs 26.0 without MTP, 21/25 drafts accepted.
Gate Huihui MTP on the F16 draft loader capability— setup only offers MTP for that family when theloader supports it.
Opt-in VRAM cap + quality mode
Add opt-in total VRAM fraction cap through cache reserves—--vram-frac Fsizes the expert cache sothe engine's own use stays under F of the card; a floor is kept free and the plan is frozen pre-touch
(post-touch readings only accept or refuse; no resize).
Add opt-in uniform-GPU VRAM cap quality mode—--vram-cap-mode qualityroutes every expert missthrough the grouped GPU kernel, no CPU fallback. Measured 20/20 token-identical outputs vs the
uncapped run, 6,759 vs 9,397 MiB peak.
Book late VRAM and refuse serve requests that cross the floor+cap booking: keep the named late allocations, leave graphs to the slack + refusal— books the chunked-GDN scratch and MTP prefill recordsin the frozen budget, re-checks the floor on the serve path and after
cudaGraphInstantiate, and logsfree memory at READY / first capture / first chunked launch / first checkpoint. Validated: free
3,511–3,577 MiB against the 2,048 MiB floor, no refusals, prefix reuse intact.
qwen35moe prefill and routing
Port opt-in chunked DeltaNet recurrence to qwen35moe— opt-in (STRATA_GDN_CHUNKED), parity-checkedagainst the default recurrence.
Use qwen35moe active expert count for router lookahead— width 8 instead of qwen4exp's 10.Port opt-in route-resident routing to 256-expert top-8 models— the 256/8 counterpart of route-resident: one warp per token in the swap kernel #1737.route-resident: one warp per token for the 512/10 and 256/8 swap— merges route-resident: one warp per token in the swap kernel #1737's warp-shuffle swapwith the 256/8 implementation into one templated kernel (
route_resident_impl<NE,K>). This commit canreplace route-resident: one warp per token in the swap kernel #1737 if preferred: it contains its approach for 512/10 and adds 256/8.
Determinism, setup, SYCL
Make expert-cache auto use one fixed 1 GiB haircut+Bucket the qwen35moe auto-cache haircut and check the reserve— fixes the auto-cache non-reproducibility on Windows/WDDM (2,974 vs 3,494 slots betweenidentical runs) and refuses cleanly instead of stalling under low headroom.
Add Huihui abliterated Qwen3.6 setup family— the family insetup.pywith tests.Keep the shared Qwen36 setup test harness offline on Windows.qsa: mirror the dense-attention validate() fix into the SYCL twin— the CUDA fix in Qwen3.6-35B-A3B and Ornith-1.5-35B-A3B (qwen35moe): a second, smaller model for 8-12 GB cards #1402 did not touchthe SYCL twin.
SYCL generate: explicitly refuse the CUDA-only VRAM cap flags— fail closed instead of ignoring them.Validation (RTX 3080 10 GB, Windows 11, CUDA 13.2, sm_86)
Measured on this exact branch (stacked on
48534f3):ctest: 96 passed, 1 skipped (expert_multi_test), 1 failed (ple_parity, missing Flash-NextQ2_0 fixture, not present on this machine). The two cap tests are
vram_cap_test(169 CPU checks)and
vram_cap_quality_test(2,529 CPU checks); the other suites are the repo's own.line (64/64),
--expert-cache 3200capped to 1430 slots, free 3,513 MiB after the first capture.Measured on the integrated line this work sits in (
95f205d+ the cap-floor fix, shad58e8ded;it also contains the five upstream PRs we adopted locally — #1700/#1660/#1659/#1696/#1720 — which this PR
deliberately excludes):
7,269–7,371 MiB under the 8,192 ceiling; server prefix reuse 1,005/1,012 with TTFT 1.89 s → 0.098 s;
chunked kernel launches confirmed under Nsight Systems; HumanEval 39/40 at 2,048 and 39/40 at 4,096
(no truncations; the single miss is HumanEval/132's wrong implementation).
haircut reduces but cannot eliminate sensitivity when the pre-touch reading crosses a bucket boundary, so
a run can still open a different slot count. Cache size does not change the output tokens in quality mode
(20/20 identical), but the slot count is not guaranteed bit-identical run to run. Clean refusal (no stall)
when the reserve cannot be met.
SYCL changes are verified by inspection only, which does not meet
AGENTS.md's build-every-touched-backendrule; an Intel/HIP machine should build them before this is merged.
What this leaves alone
change the qwen35moe fast-mode auto-cache path: the bucketed haircut activates under WDDM for eligible
native non-indexed models even without
--vram-frac, and the router lookahead uses the model's activeexpert count (8 instead of 10). qwen4exp keeps its measured-shortfall loop; no other default path changes.
measured-shortfall loop for qwen4exp.
templated 512/10 + 256/8 commit here).
Follow-up fixes from an adversarial review (2026-10-10)
Three review findings were fixed on top of the stack:
MTP F16: keep NaN NaN in the F16/BF16 conversion— the rounded addition carried NaN into infinity or asigned zero; NaN now keeps a nonzero BF16 mantissa.
SYCL: refuse a malformed STRATA_VRAM_FRAC like the CUDA build does— 0, NaN, empty, trailing junk andunderflow-to-zero were silently ignored; they now exit 2 as on CUDA.
VRAM cap: check the floor at every chunk, at serve decode admission, before pipelined windows— thefloor was checked only before the first prompt chunk; a long prefill, a fully reused prompt, and the
--pipeline-windowsdecode loop could step past it. Prefill now has a per-chunk guard, serve re-checksat decode admission, all five
pl_launchsites are guarded (the initial A, the speculative B, thepromoted A's stage-1 launch, and both prompt-pipeline launches), and the first-use chunked-GDN scratch
allocation checks the floor before taking its bytes.
Still open from that review (not fixed here): the stack does not meet the repo's one-change-per-PR rule,
and HIP/SYCL builds are not run.
Known limits
available.
because the whole arena cannot be pinned (a host limit, unrelated to these paths).