Skip to content

qwen35moe follow-ups: VRAM cap + quality mode, MTP for F16 draft tensors, chunked GDN, 256/8 route-resident - #1820

Closed
atomicmilkshake wants to merge 28 commits into
Niko1221:mainfrom
atomicmilkshake:qwen36-upstream
Closed

atomicmilkshake wants to merge 28 commits into
Niko1221:mainfrom
atomicmilkshake:qwen36-upstream

Conversation

@atomicmilkshake

@atomicmilkshake atomicmilkshake commented Oct 10, 2026 •

Copy link
Copy Markdown

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):

  • MTP for third-party single-file GGUFs whose draft block is stored as F16;
  • an opt-in VRAM fraction cap (--vram-frac) and a quality cap mode (--vram-cap-mode quality)
    that keeps every expert miss on the same GPU kernel as a hit;
  • opt-in chunked DeltaNet prefill for qwen35moe;
  • route-resident routing generalized to 256-expert / top-8 models (the route-resident: one warp per token in the swap kernel #1737 warp-shuffle idea,
    merged with our 256/8 implementation);
  • the determinism fixes the auto expert cache needed on Windows/WDDM;
  • setup-family and SYCL follow-ups.

Base: #1402 head 48534f3. The first commits in the diff are #1402's; the 16 commits listed below are
this 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 and
    BF16/F32 for routers, so a single-file GGUF whose MTP block keeps ffn_gate_inp.weight at F16 refused to
    start. 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 the
    loader supports it.

Opt-in VRAM cap + quality mode

  • Add opt-in total VRAM fraction cap through cache reserves — --vram-frac F sizes the expert cache so
    the 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 quality routes every expert miss
    through 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 records
    in the frozen budget, re-checks the floor on the serve path and after cudaGraphInstantiate, and logs
    free 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

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 between
    identical runs) and refuses cleanly instead of stalling under low headroom.
  • Add Huihui abliterated Qwen3.6 setup family — the family in setup.py with 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 touch
    the 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):

  • Build: VS 18 / Ninja / Release / sm_86, exit 0.
  • Full ctest: 96 passed, 1 skipped (expert_multi_test), 1 failed (ple_parity, missing Flash-Next
    Q2_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.
  • Capped quality + MTP smoke on the Huihui Q4_K file: exit 0, output token ids identical to the validated
    line (64/64), --expert-cache 3200 capped 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, sha d58e8ded;
it also contains the five upstream PRs we adopted locally — #1700/#1660/#1659/#1696/#1720 — which this PR
deliberately excludes):

  • Cap + quality: 20/20 token-identical vs uncapped, 6,759 vs 9,397 MiB peak.
  • MTP: 62.5 vs 26.0 tok/s (2.4×), 21/25 accepted; server path 71.1 tok/s.
  • Full stack (cap + quality + MTP + chunked + route-resident): 10/10 arms exit 0, whole-card peaks
    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).
  • Determinism: byte-identical outputs across repeated runs at a fixed cache geometry; the bucketed
    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.
  • Backends: CUDA built and exercised; HIP and SYCL were not built (no toolchain on this machine). The
    SYCL changes are verified by inspection only, which does not meet AGENTS.md's build-every-touched-backend
    rule; an Intel/HIP machine should build them before this is merged.

What this leaves alone

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 a
    signed 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 and
    underflow-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 — the
    floor was checked only before the first prompt chunk; a long prefill, a fully reused prompt, and the
    --pipeline-windows decode loop could step past it. Prefill now has a per-chunk guard, serve re-checks
    at decode admission, all five pl_launch sites are guarded (the initial A, the speculative B, the
    promoted 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

  • One machine: Windows, RTX 3080, CUDA. No AMD/Intel or Linux numbers here.
  • The F16 draft conversion is bit-checked and end-to-end on the Huihui GGUF; no other F16-draft model was
    available.
  • Quality mode requires a native expert pack; on this machine it cannot run the 46.8 GiB Flash-Next arena
    because the whole arena cannot be pinned (a host limit, unrelated to these paths).
  • The determinism fixes target the Windows/WDDM auto-cache path; Linux behavior was not re-measured here.

aflin and others added 28 commits October 7, 2026 09:13
… 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.
@aly8246

aly8246 commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

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 STRATA_ROUTE_TAIL_SKIP at its new default of 7, this
kernel is worth +9.7% decode / -13.1% window (median of 5 paired ABBA rounds, 20 engine restarts, the
learned expert profile restored before every leg; the two builds differ by only these 7,680 bytes of kernel
code, and the greedy answers came out identical). The tail skip shortens the window, which makes the swap a
larger share of it, so the two do not cancel. For scale: +6.6% on 0.1.41, and 0.1.41 -> 0.1.42 by itself was
+11.4% decode.

A parity test, if the parameterized kernel does not have one yet. src/kernels/route_resident_parity.cu
in #1737 is 240 random cases against a scalar reference of the semantics - a serial walk with the same
comparisons, not a copy of the kernel it replaced - checking ids and the four counters bit for bit and the
weights within 8 ulp (worst seen: 6), with a sentinel that fails a no-swap case that touches the weights.

git cherry-pick 488eecba from that PR is the whole thing: the test file is new and the CMakeLists hunk is
8 lines. It keeps the authorship, which is nicer than a trailer if you want the provenance in the log - but
your commit message already names #1737, so take it as is, adapt it, or ignore it, all fine.

@atomicmilkshake

Copy link
Copy Markdown
Author

Extracted the 256/8 route-resident generalization and parameterized parity test into a clean, standalone single-commit PR on top of \main: #2321.

@Niko1221

Copy link
Copy Markdown
Owner

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:

  1. Rebase on main if it no longer applies, and re-run your checks on 0.2.0-beta.
  2. Start the title with its area: [Engine], [GUI], [TUI], [Benchmark], [Docs] or [Other].
  3. Fill in the pull request template: the problem and result, and the validation (what you ran, on which revision, what you saw).
  4. Comment /reopen. A check runs and either reopens the pull request or lists what is still missing.

The rules are in CONTRIBUTING.md.

@Niko1221 Niko1221 closed this Oct 19, 2026
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.

4 participants