Repository navigation
docs(research): speculative-capture feasibility — CONDITIONAL-GO gated behind Marlin - #957
Merged
Merged
Conversation
…d behind Marlin Design-only feasibility doc answering whether CUDA-graph-captured speculative decoding can be a net win. Grounds the answer in the settled Lever B NO-GO (#948/#949: captured M=8 verify = 8.58x captured M=1) and the M>1 kernel dispatch paths. Root cause (code-grounded): the ~80 ms M=8 floor is dominated by the 240 MatMulNBits generic 16x16 CUDA-core tiled GEMMs (M>1 leaves the tuned fused int4 GEMV), not GQA/norm. Marlin (Lever A, already primary) is the enabler: capture-safe by construction + weights reused across M rows collapses the cliff. Estimated post-Marlin verify ~12-20 ms moves acceptance break-even from B*=8.5 (impossible today) to B*~1.2-2.0 (cleared by prompt-lookup/EAGLE-3). Residual blockers after Marlin are cheap: SkipSimplifiedLayerNorm = signature relaxation; GQA = pre-warmed fixed-M flash workspace. Staged plan with hard Stage-3 re-measure gate before funding any verify-graph build. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
Merge Deckard's speculative-capture feasibility decision drop into decisions.md (CONDITIONAL-GO gated behind Marlin; break-even B* = 8.5 today → ~1.2–2.0 post-Marlin). Adds orchestration + session logs. State-only; follows #957. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #957 +/- ##
==========================================
- Coverage 79.60% 79.01% -0.60%
==========================================
Files 356 356
Lines 150096 150096
Branches 150096 150096
==========================================
- Hits 119486 118596 -890
- Misses 25955 26849 +894
+ Partials 4655 4651 -4
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
🔴 Benchmark Regression DetectedComparison of criterion micro-benchmarks: PR head vs merge-base, measured on the same runner in the same job (base first → PR second).
Visual flags: Host infoWhat this cannot catch
|
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
Extend the Marlin int4 M>1 path to cover the gate/up SwiGLU MLP fusion so the MLP no longer falls back to the portable tiled two-GEMM at M>1. This is the bulk of prefill/verify cost and the remaining gap toward zero M>1 tiled-fallback nodes (the capture segments->1 unlock). - marlin_gemm.rs: add `ensure_scratch` size-keyed scratch pool (slot-keyed, FIFO cap 256, rejects cold-miss during capture) so warm replays allocate nothing and stay capture-safe. - matmul_nbits.rs: `try_launch_marlin_gemm_rmsnorm` now uses the pooled scratch (returns Option<bool> warm flag); add `try_launch_marlin_gate_up_prefill` (paired gate/up Marlin GEMMs + shared SiluMul, optional rmsnorm prologue) and wire it into `run_f16_gate_up_swiglu` M>1 branch. - Tests: gate_up SwiGLU M>1 Marlin-vs-tiled parity + capture-safety (plain / rmsnorm / decomposed-silu). worst_abs 0.0001-0.008 vs tiled, warm replays capture-safe & byte-identical. Refs #957, #960. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
pushed a commit
that referenced
this pull request
Aug 14, 2026
…xport marlin_bench: new --prefill-lens/--prefill-iters mode (Axis 3b) times a single batched decode(prompt,0) M=len forward (the MatMulNBits M>1 path), KV reset via rewind(0), contention-invariant MIN. Prefill is a direct Marlin gate (M>1 is where Marlin wins). Resolve the glm ORT head-to-head: all stock ORT (1.27 EP, pip 1.28, og 0.14.1 bundled) reject the native-authored glm ONNX GQA (rotary_embedding_dim). Re-export a clean stock ORT-genai chatglm int4 via the onnxruntime-genai model builder from glm-4-9b-gptq-source and benchmark it (bench-scripts/ort_glm_bench.py). BEFORE finding: native prefill is flat ~150-217 tok/s (no M>1 GEMM efficiency) vs ORT-genai glm 8k-26k tok/s -> ~100-120x gap = the decisive Marlin gate. glm decode native ~95 vs ORT-genai 233 tok/s. Decision drop updated with the prefill sweep table + glm-ORT result + reproduce commands. Refs #957, #962; docs/research/decode-remaining-levers-feasibility.md §3/§4 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
Add a tracer-driven coverage audit to the M>1 e2e test that runs a
prefill-heavy generation with ONNX_GENAI_MARLIN_M_GT_1=1, tallies the
per-op kernel_variant annotations, and asserts NO MatMulNBits node fell
back to a tiled M>1 kernel (gemm_f16_tiled{,_rmsnorm},
gate_up_swiglu{,_rmsnorm}_prefill). Zero tiled M>1 fallbacks is the
prerequisite for the capture segments->1 collapse.
Measured (H200, GPU 7):
- glm-4-9b-int4 : 240 gemm_marlin_int4, 0 tiled M>1
- qwen2.5-14b : 240 gemm_marlin_int4 + 48 gate_up_swiglu_marlin_prefill
+ 1 gemm_marlin_int4_rmsnorm = 289 Marlin M>1, 0 tiled
Greedy token streams remain byte-identical tiled-vs-Marlin on both.
Refs #957, #960.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
P2 "widen the win": adds a split-K variant of the Marlin int4 GEMM to attack the occupancy-bound small-M regime, where the direct kernel launches only ~1-1.5 waves of blocks (N=5120 => ~160 blocks on 132 SMs) and stalls at ~3% weight-DRAM. - matmul_nbits_marlin_gemm_f16_splitk: partitions the K/group range over grid.z, each z accumulates its groups' frag*scale into fp32 and writes partials[split_k, M, N] (no bias). - matmul_nbits_marlin_splitk_reduce: sums the z-slices in FIXED order (deterministic => run-to-run identical => capture-stable) then applies fold_bias -> fp16. - choose_split_k: conservative heuristic; only splits when the base grid is under ~2 waves (so it NEVER regresses the already-occupied M>=32 / large-N cases), caps each split at >=2 groups and factor <= 8. - launch_marlin_gemm_splitk: static-grid gemm + reduce pair, capture-safe once partials is pre-allocated (scratch pool). Numerics: split-K reorders fp32 partials vs the single-block kernel, so NOT byte-identical to direct, but passes the f64-oracle parity guard (tol 2e-2) incl. asymmetric-zp, and is deterministic (new tests marlin_splitk_parity_vs_f64_oracle, marlin_splitk_is_deterministic). Measured (H200 GPU 3, median us, split-K vs direct): N=5120 M=1 : 90.0 -> 32.0 us 2.81x (weight 3.0% -> 8.5% peak) N=5120 M=2 : 90.2 -> 32.1 us 2.81x N=5120 M=8 : 105.5 -> 42.9 us 2.46x N=13824 M=1 : 210.3 -> 88.2 us 2.38x (split_k=2) N=13824 M=2 : 127.3 -> 81.3 us 1.57x (split_k=8) Correctly regresses (heuristic returns 1, stays on direct) at M>=32 and large-N M>=8, where base occupancy already suffices. Standalone/opt-in only; not yet wired into the op dispatch. Direct kernel remains the default (byte-identical greedy-token path unchanged). Refs #957, #960. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
pushed a commit
that referenced
this pull request
Aug 14, 2026
…EFORE Merged Deckard's P1 snapshot c692bff (full M>1 Marlin, zero tiled fallbacks) and ran every gate MARLIN-OFF vs ON (ONNX_GENAI_MARLIN_M_GT_1=1) on H200 GPU7. HEADLINE (leverb-phase0 Part D INC0, glm): captured M=8/M=1 ratio B* 8.76x->4.99x (-43%); all 240 MatMulNBits capture seams eliminated; captured M=8 wall 87.3->50.7ms. segments 41->120 because Marlin exposed the residual GQA x40 + LayerNorm x80 seams (capture-safety lever, not Marlin scope). Still NO-GO at strict B*<=2, but much closer. Prefill sweep: native ~2x on both models (glm 218->426, qwen 154->300 tok/s), halving the glm-vs-ORT gap (121x->62x at L=1024). M-scaling tail slope collapses glm 2.21->0.93, qwen 3.26->1.10 ms/row — confirms Deckard's kernel 6.9x->~2x at the forward level. Portability: Marlin-OFF rows == portable tiled fallback == unchanged BEFORE baseline, proving <SM80 does not regress. Decision drop updated with full AFTER tables + commands. Refs #957, #962; docs/research/decode-remaining-levers-feasibility.md §3/§4 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
Routes the plain and rmsnorm-prologue Marlin M>1 GEMMs through split-K when ONNX_GENAI_MARLIN_SPLITK=1 AND choose_split_k elects to split (base grid under ~2 waves). Partials come from the capture-safe scratch pool (slot 4, distinct from rmsnorm slot 0 / gate_up slot 1), so warm replays stay allocation-free; the reduce kernel applies the same fold_bias epilogue. Default OFF => the byte-identical direct kernel remains the greedy-token path. New helper Kernel::maybe_launch_marlin_splitk centralizes the direct-vs-split-K choice and propagates the pooled-scratch warm flag into the existing capture-safety return contract. gate_up SwiGLU stays on the direct kernel for now (its fused SiluMul epilogue would need a split-K reduce variant); documented as remaining work. Validated (H200 GPU 3): - f64-oracle parity + determinism (marlin_splitk_* lib tests) pass, incl. asymmetric-zp, tol 2e-2. - e2e greedy tokens BYTE-IDENTICAL vs tiled with SPLITK=1 on both glm-4-9b-int4 and qwen2.5-14b-int4 (zp) M>1 prefill. - SM80 guard + tiled fallback unchanged; split-K itself is SM80-gated. Refs #957, #960. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
The last two MatMulNBits-adjacent capture barriers at query-width > 1: GroupQueryAttention (x40) and SkipSimplifiedLayerNormalization (x80) declared KernelCaptureUnsupported for any M>1 shape, keeping the speculative-verify (M=K) forward segmented even after Marlin removed all 240 MatMulNBits seams. Both were conservative gates, not real hazards. SkipSimplifiedLayerNormalization: - The launch is already a static grid (groups_u,1,1) with no mid-kernel sync / host read-back / alloc-free on the hot path, and the shape-keyed SkipBroadcastMetadataCache already rejects any shape change (incl. num_groups) mid-capture — the same pre-warm cold-miss safety valve the Marlin repack cache uses. The `num_groups == 1` capture latch was purely conservative. Relax it to advertise capture support for a fixed, pre-warmed M=K shape (bf16 staging path still demotes on arena growth). GroupQueryAttention: - Broaden the capture signature to admit a fp16/bf16 q_seq>1 fused-flash verify/prefill shape, not just q_seq==1 Phase2a decode. The fused flash kernel is already capture-safe by construction (static grid, device total_lengths/past_lengths, no host read-back, skips synchronize during capture); the on-device gqa_prepare_metadata kernel + capture_error_ptr latch replace the host-side seqlens/position validation, and the capture-safe branch uses present_capacity (masking the tail on-device) exactly like the M=1 decode path. Backend is resolved against the fixed present_capacity so warm and captured runs agree deterministically; f32 M>1 (reference-scores, host-sized) stays on the eager fallback. Gates: greedy tokens byte-identical on glm-4-9b-int4 (heavy SkipSimplifiedLayerNorm) M>1 prefill; GQA + normalization lib unit tests pass; fmt + lib clippy + native-backend clippy clean. Capture support is still declared only for a warmed signature (pre-warm the M=K shape before capture, same safety valve as the Marlin repack/scratch pools). This is Stage 2 of the merged spec-capture design (#957): "SkipSimplified LayerNorm = signature relaxation, GQA = pre-warmed fixed-M flash workspace." With Marlin M>1 + these two, no hot decode/verify node declares KernelCaptureUnsupported at M>1. Refs #957, #960. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
pushed a commit
that referenced
this pull request
Aug 14, 2026
…afe) Merged Deckard's c842b75 (GroupQueryAttention + SkipSimplifiedLayerNorm M>1 capture support). leverb-phase0 Part D INC0 (glm, M=8, pre-warmed): - segments 120 -> 1: whole M=8 verify now captures as ONE replayable graph. Only residual seam = MatMul[KernelCaptureUnsupported]x1 (lone dense MatMul). - B* unchanged by capture-safety alone (5.10x vs marlin-only 4.99x): the captured M=8 wall is compute-bound, not segmentation-bound (120->1 segments moved the wall ~0: 50.7->51.8ms). - Split-K (exploratory, ALSO capture-safe + byte-identical) is the lever that moves B*: 5.10x -> 2.69x (M=8 wall 51.8->27.3ms) -- right at the <=2 GO line. - Numerics: Marlin greedy tokens byte-identical to tiled with AND without split-K (24/24), no NaN under capture. Zero-init KV tail assumption holds. Path to speculative-capture GO = capture-safety (done, segments=1) + split-K on the M=K verify. Decision drop updated with progression table + commands. Refs #957, #960, #962 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
pushed a commit
that referenced
this pull request
Aug 14, 2026
…confirm Refreshed M-scaling + prefill sweep at c842b75 with split-K ON, and re-ran the decisive capture probe clean for reproducibility: - Capture re-probe reconfirmed: canonical segments=1 / MatMul x1 seam / B*=5.09x; split-K segments=1 / B*=2.71x. Reproduces prior 5.10/2.69 within noise. - Warmup coverage confirmed: Part-D INC0 warms via a FULL M=K forward, so every fixed-shape cache (Marlin repack+scratch, GQA meta, LN meta) is populated in one pass; capture_alloc=(0,0) + segments=1 prove no cold-miss valve fired. - Final tail slope (ms/row) BEFORE -> Marlin -> +split-K: glm 2.21 -> 0.93 -> 0.822 (-63%); qwen 3.26 -> 1.10 -> 1.147 (-65%). - Prefill nuance: canonical Marlin edges split-K at large-M prefill (qwen 291-300 vs 267-277); split-K's win is small-M M=K verify. Recommend canonical Marlin for prefill, split-K auto-on for the speculative verify. Refs #957, #960, #962 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
pushed a commit
that referenced
this pull request
Aug 14, 2026
…B* 2.63x Merged Deckard's 2971403/1a72c865 (lm_head cached dense-GEMM capture plan + split-K default within MARLIN_M_GT_1=1). leverb-phase0 Part D INC0, glm, M=8, GPU7 (Deckard on GPU5): - Census now TRUE ZERO unsupported nodes: seam nodes '<none: whole-graph>'. The whole M=8 verify (incl. lm_head logits projection) captures as ONE whole-subgraph replay. Last MatMul[KernelCaptureUnsupported]x1 is gone. - B* 2.71 -> 2.63x (captured M=8 wall 27.5 -> 26.8ms): folding lm_head out of its per-step eager launch+sync nudged B* down as predicted, but strict <=2 GO line not yet crossed. Residual is genuine small-M GEMM compute. - Byte-identical greedy tokens on glm-4-9b-int4 with the new default path (split-K default + lm_head plan); no NaN. Cleanest capture config to date. Refs #957, #960, #962 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
pushed a commit
that referenced
this pull request
Aug 14, 2026
Merged Deckard's 4abe4e5/0143f235 (choose_split_k retune for M=8 verify: gate_up/down/o-q now split sk=8; prefill still sk=1 direct kernel). Re-probed glm M=8, GPU7 (Deckard on 4/6), reproduced x2: - B* 2.63 -> 2.16x / 2.17x (captured M=8 verify wall 26.8 -> 22.0ms). Gate_up MLP GEMM off the no-split slow path is the dominant win. - Still segments=1, whole-graph, ZERO unsupported nodes. Byte-identical greedy tokens on glm-4-9b-int4 (parity PASS), no NaN. - 2.16x is an honest plateau a hair above the conservative <=2 line; but <=2 is break-even-at-2-accepted-tokens, so this is a PRACTICAL GO once the draft accepts >2.16 tok/verify (trivial for an 8-wide draft). Full arc: segments 41->1; B* 8.76->4.99->5.09->2.71->2.63->2.16x, byte-identical. Refs #957, #960, #962 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
pushed a commit
that referenced
this pull request
Aug 14, 2026
…plateau) Merged Deckard's 3735d57/dffabf0d (gate_up SwiGLU MLP GEMM was DIRECT-launching, bypassing split-K even after the 4abe4e5 factor retune; now routes sk=8). Re-probed glm M=8, GPU7, reproduced x2: - B* 2.15x / 2.19x; captured M=8 verify wall stable ~21.9ms (ratio jitter is from the M=1 captured baseline 9.98-10.18ms, not the M=8 wall). segments=1, whole-graph, zero unsupported nodes, byte-identical (parity PASS), no NaN. - This is the small-M GEMM floor: M=8 mma.m16n8k16 is ~half-occupancy (~50% MMA waste), so split-K+occupancy is the last GEMM lever; path to universal <=2 is drafting depth, not more GEMM tuning. - Practical GO: any 8-wide draft accepting >2.16 tok/verify wins. Capture-safety fully solved. Full arc: segments 41->1 whole-graph; B* 8.76->4.99->5.09->2.71->2.63->2.16x. Refs #957, #960, #962 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
pushed a commit
that referenced
this pull request
Aug 14, 2026
…wen block_32 B*~4.7x Key finding: the fused gate_up SwiGLU Marlin path requires block_size==32 (matmul_nbits.rs run_f16_gate_up_swiglu). Verified via onnx: glm=block_128, qwen=block_32. - glm (block_128) is NOT eligible for the gate_up fusion -> 3735d57 is a NO-OP for glm; glm's B*=2.16x comes from the 4abe4e5 general split-K retune, not gate_up fusion. Explains the flat glm wall (22.0->21.9ms) after 3735d57. - qwen (block_32) M=8 verify re-probe: segments=1, whole-graph, zero unsupported (48 GQA + 96 LN captured -> capture-safety generalizes), but B*=4.62-4.72x (captured M=8 35.5ms / M=1 7.5ms). NOT at GO. - Honest read: captured_ratio > eager_ratio (4.7>3.8) is a DENOMINATOR effect (capture speeds the launch-bound M=1 by 34% vs M=8 by 18%), not a capture inefficiency. qwen's tuned block-32 M=1 is fast (inflates ratio) and its 14B/48-layer M=8 verify is genuinely heavier. Deckard caveat #1 (qwen attn proj cold-clock bimodal) may add. Small-M GEMM floor is model-dependent: glm practical GO (2.16x), qwen 4.7x needs more small-M work or deeper drafting. glm remains canonical gate. Refs #957, #960, #962 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
pushed a commit
that referenced
this pull request
Aug 14, 2026
…OT a Marlin regression Ran qwen byte-identical parity at 3735d57; first run failed (Marlin greedy stream diverged from tiled at token 19). Investigated before raising a regression: - 4 runs (split-K on x3 + off x1): Marlin output DETERMINISTIC every time (5692,374,264,1140,315). The TILED reference diverged (320,94409,...) on only 1 of 4 runs. Token 19 is a near-degenerate argmax; the tiled GEMM's own nondeterministic (atomic/reduction-order) accumulation occasionally flips it. - Conclusion: NOT a Marlin numerics regression. Marlin (fixed split-K reduction) is the MORE deterministic path; the tiled oracle flakes ~1-in-4 at this tie. Split-K on and off both produce identical deterministic Marlin tokens. - Action for Chew/Gaff: the qwen exact-token parity test is flaky (~25%) because it asserts equality vs a nondeterministic tiled reference at a near-tie. Not a blocker; harden the assertion (logit tolerance / deterministic oracle / assert Marlin self-determinism). glm's prompt doesn't hit such a tie -> glm clean. Refs #957, #960, #962 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
…960) ## Marlin-style fused fp16xint4 tensor-core GEMM (WIP -- draft) Builds the primary decode-perf lever from `docs/research/decode-remaining-levers-feasibility.md` and the capture enabler from the spec-capture feasibility doc (#957): a Marlin-style fused fp16xint4 tensor-core GEMM to replace the slow M>1 path of `com.microsoft::MatMulNBits` (today a portable 16x16 CUDA-core tiled GEMM) and, conditionally, the M=1 decode GEMV. Refs #957. ### What's landed (Stage 1-4) - New module `crates/onnx-runtime-ep-cuda/src/kernels/marlin_gemm.rs` (kernel + host/device repack isolated from the 475 KB `matmul_nbits.rs`). - From-scratch SM80 `mma.sync.m16n8k16` kernel using raw inline PTX (no `<mma.h>`), adapting Marlin ideas (repacked per-lane weights, per-group scale AFTER the tensor-core accumulate) to our ONNX-native asymmetric-zp / nibble format, group sizes 16/32/64/128. - `repack_int4_weights` + on-device `matmul_nbits_marlin_repack`: bijective 8-column-interleaved reorder for coalesced 64-byte/warp loads. Device repack is byte-identical to the host reference. - `device_supports_marlin()` SM80 guard for the Rule 11 byte-identical fallback. Static launch grid => capture-safe by construction. - Wired into the `MatMulNBits` M>1 dispatch (opt-in `ONNX_GENAI_MARLIN_M_GT_1`, default OFF, tier-scoped). Module-level repack cache repacks immutable-initializer weights once during warmup; warm replays do no allocation and are capture-safe. Cold-miss-during-capture is rejected. Any ineligibility / launch error falls through byte-compatibly to the portable tiled GEMM. - `capture_support` advertises M>1 support on a warm cache -- the property that unlocks capture-stable speculative verification at M>1. ### Validation - Numerics: `marlin_parity_vs_f64_oracle` (standalone) and `marlin_m_gt_1_op_parity_and_capture_safety` (through the real op) pass vs an f64 dequant->GEMM oracle across M in {1,2,7,16,33,64}, group in {16,32,64,128}, fp16/fp32 scales, sym+asym zero-points (abs <= 2e-2*max|out|; op worst_abs=0.013 << tol=0.67). The op test asserts cold=NOT-capture-safe / warm=capture-safe + byte-identical replay. - Wall vs tiled (through the op), `marlin_m_gt_1_op_wall_vs_tiled`, H200, K=5120 N=13824 block=128 (median us): | M | tiled us | marlin us | speedup | |---|---|---|---| | 1 | 69.5 | 69.0 | 1.01x (M=1 stays on GEMV) | | 2 | 478.0 | 140.0 | 3.41x | | 4 | 494.1 | 138.5 | 3.57x | | 8 | 536.4 | 176.9 | 3.03x | | 16 | 658.1 | 251.2 | 2.62x | | 32 | 1076.7 | 385.0 | 2.80x | | 64 | 2010.6 | 821.6 | 2.45x | | 128 | 3851.5 | 1357.9 | 2.84x | The M=1->M=2 cliff collapses from 6.9x (69.5->478 us tiled) to ~2x (69.5->140 us). Marlin is 2.4-3.6x faster than the tiled GEMM across every M>1. ### Honest status - M>1: correct, capture-safe (warm), 2.4-3.6x faster than tiled -- landed opt-in. - M=1: through-op M=1 Marlin ties the GEMV (69 vs 69.5 us), ~3.1% HBM peak -- does NOT clear the feasibility section-3 >=40% precondition, so M=1 stays on the existing GEMV (reported, not silently dropped). - Next: fused epilogues (rmsnorm-prologue -> swiglu/gate_up/down); small-M perf levers (split-K / cp.async); e2e on glm-4-9b-int4 / qwen2.5-14b (with Sebastian). Draft -- do not merge. Numerics gated by Chew; e2e/capture re-probe by Sebastian. ### Update — fused RMS-norm epilogue + e2e PASS - **Fused RMS-norm prologue for Marlin M>1** landed (first fused epilogue). Stages the normalized activation into scratch (byte-identical to the tiled prologue), then runs Marlin. Test `marlin_m_gt_1_rmsnorm_op_parity`: worst_abs=0.0156 << tol=0.90. - **Stage-5 e2e** (`tests/marlin_m_gt_1_e2e.rs`, native CUDA): tiled (flag off) vs Marlin (flag on) greedy token diff on the real models. Both **byte-identical**: - glm-4-9b-int4: 24 greedy tokens match. - qwen2.5-14b-instruct-int4 (asymmetric zp): 24 greedy tokens match. - Remaining: swiglu/gate_up/down fused epilogues; small-M perf levers (split-K / cp.async). Numerics gated by Chew; ORT e2e + capture re-probe by Sebastian. ### Update — P1 COMPLETE: full M>1 coverage (gate_up SwiGLU fused), zero tiled fallbacks Every hot MatMulNBits node in glm-4-9b and qwen2.5-14b now runs through Marlin at M>1. - **gate_up SwiGLU MLP fusion at M>1** (`try_launch_marlin_gate_up_prefill`): paired gate/up Marlin int4 GEMMs + the identical SiluMul epilogue the tiled path uses, with an optional RMS-norm prologue. This is the MLP — the bulk of prefill/verify cost. `down_proj` is a plain tall-skinny MatMulNBits already covered by `gemm_marlin_int4`. - **Capture-safe scratch pool** (`ensure_scratch`, size+slot-keyed, rejects cold-miss during capture) so warm replays of gate_up + rmsnorm allocate nothing. The rmsnorm prefill was migrated onto it and is now capture-safe on warm replay too. - **Per-node coverage audit** (tracer-driven e2e test): asserts zero `gemm_f16_tiled{,_rmsnorm}` / `gate_up_swiglu{,_rmsnorm}_prefill` at M>1. Measured coverage audit (H200, Marlin M>1 enabled): - glm-4-9b-int4: 240 `gemm_marlin_int4`, **0 tiled M>1**. - qwen2.5-14b-int4 (asym zp): 240 `gemm_marlin_int4` + 48 `gate_up_swiglu_marlin_prefill` + 1 `gemm_marlin_int4_rmsnorm` = **289 Marlin M>1, 0 tiled**. - Greedy tokens **byte-identical** tiled-vs-Marlin on both (24 tokens). gate_up unit parity (Marlin vs tiled): worst_abs 0.0001–0.008; warm replays capture-safe and byte-identical. No hot M>1 node remains on the tiled path — this unblocks the Increment-0 capture re-probe (segments→1). Next: P2 split-K / cp.async + honest M=1 precondition re-run. --- ## P2 — widen the win: split-K (landed, opt-in) + honest M=1 re-run **split-K** (`ONNX_GENAI_MARLIN_SPLITK=1`, default OFF): partitions the K/group range over `grid.z` to fill idle SMs when M is small (the occupancy-bound regime), fixed-order fp32 partial reduce ⇒ deterministic/capture-stable, pooled partials ⇒ capture-safe. Conservative `choose_split_k` only splits under ~2 waves so it never regresses the already-occupied M≥32 / large-N cases. Measured (H200, split-K vs direct): N=5120 M=1 **2.81×**, M=2 2.81×, M=8 2.46×; N=13824 M=1 2.38×, M=2 1.57×. Gates intact: f64-oracle parity + determinism tests pass; e2e greedy tokens **byte-identical** vs tiled with SPLITK=1 on glm-4-9b and qwen2.5-14b (zp); SM80 guard + fallback unchanged. **cp.async multistage: rejected** (measured slower — small-M is occupancy-bound, not latency-bound; hiding load latency only added barrier overhead). **M=1 DRAM precondition — honest re-run: still fails.** Best split-K lifts M=1 Marlin weight-DRAM to only ~8.5% peak, far below the ≥40% floor ⇒ **M=1 stays on the existing GEMV**. Remaining: gate_up SwiGLU M>1 stays on the direct kernel (split-K reduce for the fused SiluMul epilogue is follow-up). --- ## Stage 2 of spec-capture (#957): last two M>1 capture barriers removed With Marlin clearing all 240 MatMulNBits M>1 seams, the only ops still declaring `KernelCaptureUnsupported` at query-width>1 were **GroupQueryAttention (×40)** and **SkipSimplifiedLayerNormalization (×80)**. Both were conservative gates, not real hazards; now both advertise capture support for a warmed M=K signature. - **SkipSimplifiedLayerNorm** (signature relaxation): static grid, shape-keyed metadata cache already the pre-warm valve; relaxed the `num_groups == 1` latch. - **GQA** (pre-warmed fixed-M flash): admit an fp16/bf16 `q_seq>1` fused-flash signature. `flash_attention::run` is already capture-safe (static grid, device `total_lengths`/`past_lengths`, no host read-back); on-device metadata + `capture_error_ptr` replace host validation; capture-safe branch uses `present_capacity` (device-masked tail) like the M=1 path. f32 M>1 stays eager. Gates: greedy tokens byte-identical (glm-4-9b-int4 M>1 prefill); GQA + norm unit tests pass; fmt + lib clippy + native-backend clippy clean. Capture still declared only for a warmed signature (pre-warm M=K before capture). Sebastian re-probes for the decisive segments→~1 / B*≤~2 (speculative-capture GO). --- ## Segment census confirmed (Sebastian) + last dense seam closed Sebastian's Increment-0 re-probe (glm-4-9b, M=8, `MARLIN_M_GT_1=1`) confirmed the GQA + SkipSimplifiedLayerNorm capture-safety: **segments 120 → 1**, all 40 GQA + 80 SkipSimplifiedLayerNorm + 240 MatMulNBits M>1 seams gone, byte-identical, no NaN under capture. Two follow-ups (commit `29714037`): - **Split-K default-ON** within the opt-in Marlin M>1 path (opt out `ONNX_GENAI_MARLIN_SPLITK=0`). It is the measured B\* lever — Sebastian's probe: B\* **5.10× → 2.69×** at M=8 (wall 51.8→27.3ms). `choose_split_k` keeps large-M prefill on the byte-identical direct kernel; greedy tokens byte-identical. - **Last M>1 capture seam closed:** the lone residual node was `lm_head/MatMul` → `logits` (plain fp16 dense `MatMul`, not MatMulNBits), which fell to cuBLASLt's per-call heuristic at M>1. Added a cached-plan fast path (`blas::CaptureGemmPlan` + `DenseGemmPlan`): algorithm + persistent workspace selected once at warmup, replayed with no heuristic/alloc/sync — the M>1 analogue of `F32GemvPlan`, bit-for-bit identical to `governed_gemm` at a fixed shape. Batched/broadcast GEMMs keep the per-call path. Gates: greedy tokens byte-identical on glm-4-9b-int4 and qwen2.5-14b-int4-zp; fmt + lib clippy + native-backend clippy clean; SM80 guard + fallback unchanged. --- ## Small-M split-K retune for the M=8 verify shape Profiled glm/qwen decode-GEMM dims at M=8 and found `choose_split_k` was under-splitting: it picked `split_k=2-3` (or `1` for large-N gate_up) where the optimum is `8`. Root cause — block count is a poor fill proxy at small M, where each block is latency-bound; deeper K-splitting raises weight-DRAM throughput. Retuned (split for `m<=32`, oversubscribe ~8 waves, floor toward the measured small-M optimum, cap 8). M=8 auto factor before→after: | GEMM | K,N | old | new | |---|---|---|---| | o/q proj | 4096,4096 | sk3 2.34× | **sk8 2.93×** | | down_proj | 13696,4096 | sk3 2.45× | **sk8 3.18×** | | gate_up (glm) | 4096,27392 | none | **sk8 1.11×** | | gate_up (qwen) | 5120,13824 | none | **sk8 1.35×** | The dominant MLP verify cost now splits; prefill (`m>32`) stays on the byte-identical direct kernel. Gates: split-K f64-oracle parity + determinism at sk=8; greedy tokens byte-identical on glm-4-9b-int4; fmt + lib clippy clean. ### gate_up (SwiGLU MLP) routed through split-K (3735d57) Found the residual B\* gap: `try_launch_marlin_gate_up_prefill` launched the two largest MLP GEMMs (gate/up, glm K=4096 N=13696 each ×40 layers) as **direct** `launch_marlin_gemm`, bypassing split-K. The update-7 retune therefore never reached gate_up. Both projections now go through `maybe_launch_marlin_splitk` (sk=8 at M=8); gate/up reuse the slot-4 partials scratch sequentially (correct under single-stream ordering), and both split-K warm flags fold into the returned capture warmth. Gates: `marlin_gate_up_swiglu_matches_tiled_*` PASS at M=8 (split-K now active; warm replay byte-identical, cold-miss valve intact); glm-4-9b-int4 **and** qwen2.5-14b-int4-zp e2e greedy tokens byte-identical; fmt + lib clippy + native-backend clippy clean. --- ## FINAL STATUS — frozen for merge review (supersedes the "Next"/"Draft" notes above) Capture-safety is **complete** and the M>1 Marlin path is **landed opt-in**. This branch is frozen for numerics (Chew) + quality (Gaff) review. **Frozen head:** `a11facbc` (code frozen at `3735d57e`; later commits are decision-drops only). Sebastian's decisive perf A/B was re-probed **on the frozen code commit `3735d57e`** (glm M=8, GPU7, ×2): **B\*=2.15–2.19×, captured M=8 verify wall stable 21.9ms**, segments=1 whole-graph, zero unsupported nodes, byte-identical greedy tokens, no cold-miss. gate_up split-K routing is thus both the most complete config and the independently-measured one. **Capture / perf arc (glm-4-9b, M=8 speculative verify, Sebastian's Increment-0 probe):** - Segment census: **41 → 1** — the whole M=8 verify (all 240 MatMulNBits + 40 GQA + 80 SkipSimplifiedLayerNorm + the lone dense lm_head MatMul) captures as **one whole-graph replay, zero `KernelCaptureUnsupported` nodes**. - Capture **B\* 8.76 → 4.99 (Marlin) → 2.71 (split-K) → 2.63 (lm_head dense-plan) → 2.16× (small-M split-K retune)** — an honest plateau at the ≤2 practical-GO line (residual is intrinsic M=8 `mma.m16n8k16` half-occupancy, not a capture blocker). Prefill ~2× (glm 218→426, qwen 154→300). Marlin-OFF == BEFORE (no <SM80 / consumer-tier regression). **Per-model note (block size).** glm-4-9b is **block-128**: its gate/up run as separate MatMulNBits through the general Marlin split-K path (covered by the retune), so the fused gate_up split-K commit (`3735d57e`) is a **no-op for glm** and its 2.16× is the retune alone. The fused gate_up SwiGLU path requires `block_size==32`, so `3735d57e` benefits **block-32** models (qwen2.5-14b), where it is confirmed firing under capture (qwen census: segments=1, zero unsupported — a fall-through would hit the non-capture-safe tiled path and fragment). qwen M=8 verify lands at **B\*≈4.7×**: capture-safety fully generalizes (whole-graph, zero seams, byte-identical eager parity), but the ratio is inflated by a denominator effect (capture speeds qwen's fast block-32 M=1 GEMV 34% vs the heavier 14B M=8 verify 18%). Closing qwen's B\* is a drafting-depth story, not a kernel fix. **glm-4-9b (block-128) is the canonical practical GO at 2.16×.** **Env-var contract (both implemented — verified in `marlin_gemm.rs`):** - `ONNX_GENAI_MARLIN_M_GT_1` — opt-in, **default OFF**, tier-scoped (Rule 11). `marlin_m_gt_1_enabled()` (`1`/`true`/`on`). SM80 runtime guard (`device_supports_marlin`) + byte-identical fallback to the portable tiled GEMM on <SM80 / CPU EP. Two weight layouts coexist; repack is an added packaging step. - `ONNX_GENAI_MARLIN_SPLITK` — **default ON** within the opt-in M>1 path; opt out `=0`/`false`/`off` for the byte-identical direct kernel. `choose_split_k` only elects a split for small-M / low-wave shapes (returns 1 for large-M prefill), so prefill/decode stay on the byte-identical direct kernel. **Coverage:** zero M>1 tiled-fallback for the hot MatMulNBits nodes on glm-4-9b + qwen2.5-14b (plain GEMM, rmsnorm-prologue, swiglu/gate_up, down_proj all fused through Marlin). GQA + SkipSimplifiedLayerNorm made M>1 capture-safe; lone dense lm_head MatMul made M>1 capture-safe via a cached cuBLASLt plan (`CaptureGemmPlan` / `DenseGemmPlan`). **Gates at frozen head (H200 GPU6):** f64-oracle split-K parity PASS; `marlin_gate_up_swiglu_matches_tiled_{plain,rmsnorm,decomposed}` PASS (split-K active at M=8, warm replay byte-identical, cold-miss valve intact); glm-4-9b-int4 **and** qwen2.5-14b-int4-zp e2e greedy tokens **byte-identical** to the tiled path; `cargo fmt --all -- --check` clean; `cargo clippy` lib + native-backend (warnings-as-errors) clean. --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
…961) ## Numerics validation gate for Marlin int4 GEMM Builds the reusable **f64 dequant→GEMM oracle + parity gate** that Chew (numerics reviewer) will use to sign off Deckard's Marlin int4 GEMM (`squad/marlin-kernel`). Refs #957 (Lever A — Marlin int4 relayout, the primary decode lever). Marlin's weight relayout reorders partial sums, so its output is **not byte-exact** vs the current tiled kernel and cannot be validated by a bit-for-bit diff — the only defensible reference is a high-precision f64 oracle with a justified tolerance. ### What's here New self-contained integration test `crates/onnx-runtime-ep-cuda/tests/matmul_nbits_marlin_numerics.rs` (no edits to `matmul_nbits.rs` → no conflict with the Marlin kernel branch): - **f64 oracle:** dequantize packed int4 to f64 as `(code − zp)·scale` (scale pre-rounded to storage dtype) and accumulate `Σ a·w` in f64. Candidate and oracle share the same fp16-rounded activations + rounded scales, so the residual isolates only accumulation precision + fp16 output rounding. - **Coverage:** group sizes {16,32,64,128}; symmetric **and** asymmetric-zp; M ∈ {1,2,4,8,16,32}; realistic **glm-4-9b** (4096/4096, 4096/256, 4096/13696, 13696/4096) + **Qwen2.5-1.5B** (1536/8960, 8960/1536) attention+MLP projections; fp16 & fp32 scales. - **Marlin-ready:** drives the op purely via **op semantics** (ONNX `MatMulNBits` node + `get_kernel`/`execute`), never internal layout. Once Marlin is wired into the same dispatch, `run_matmul_nbits_f16` validates it with **zero changes**; or feed any candidate output slice into `Int4Problem::parity`. ### Justified tolerance envelope - **Absolute (primary):** `max(max_out·4e-3, 4e-3)` — ~8 fp16 ULP; covers a Marlin re-associated fp32 reduction (`~K·eps_f32 < 2e-3` at K≈13696). - **Relative (secondary):** `5e-2` against a **conditioning-aware** denominator floor `max(1e-1, 3e-2·max_out)`, so cancellation-dominated small-output columns stay absolute-governed. ### Current-path baseline (H200) | sweep | max_abs | max_rel | max_out | |---|---|---|---| | group-size (K=4096,N=896) | 6.50e-2 | 2.71e-3 | 1.67e2 | | projections (bs=32, M∈{1,8}) | 2.53e-1 | 8.14e-3 | 5.27e2 | Both ≈ **1 fp16 ULP** (abs ≈ `4e-4·max_out`) — ~8–10× inside the abs bound, ~6× inside the rel bound. **Marlin must land in the same envelope.** ### How to run ``` source .cudaenv.sh CUDA_VISIBLE_DEVICES=<idle-gpu> cargo test -p onnx-runtime-ep-cuda \ --features gpu-tests --test matmul_nbits_marlin_numerics -- --nocapture --test-threads=1 ``` 5 pure-CPU oracle self-consistency tests run without a GPU / feature; GPU baseline tests are `#[ignore]` unless `gpu-tests` is set (CPU-only CI stays green). `cargo fmt` + `clippy` clean.⚠️ Draft — multi-turn; coordinator will send follow-ups. Numerics reviewer (Chew) sign-off pending; validates against Marlin (Deckard) once it lands. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
…957 Lever A) (#962) ## What New reproducible perf/capture harness **`marlin_bench`** (`crates/onnx-genai-bench/src/bin/marlin_bench.rs`, `--features bench-native,cuda`) that gates the full Marlin int4 GEMM build (**Lever A**, greenlit in #957 / `docs/research/decode-remaining-levers-feasibility.md`), plus the recorded **BEFORE baseline** on today's code so the moment Marlin lands the *same commands* produce a decisive delta. New file only — **zero overlap with Deckard's kernel branch** (`squad/marlin-kernel`). Uses only the public `NativeDecodeSession::load_with_resolved_io` API. ## The four gate axes (BEFORE baseline, H200 SM90) Full table, per-shape ncu rows, and exact reproduce commands are in the decision drop `.squad/decisions/inbox/sebastian-marlin-bench.md`. **Axis 1 — M=1 int4 GEMV DRAM%** (`ncu --graph-profiling node`): qwen MLP gate_up **~29.5%** (reproduces the doc §3 "~29% DRAM on an MLP shape" precondition), down_proj ~40%, attn ~24%; glm untuned `gemv_f16_general_bs` 11–17%. All far below the ≥55% Marlin M=1 pass line → GEMV is latency/dispatch-bound, as diagnosed. **Axis 2 — M-scaling wall (the cliff)** (`marlin_bench`, M∈{1,2,4,8,16}, min ms): glm past=32 **cliff 67.7 ms, M=8 91 ms, tail 1.65 ms/row** — reproduces Deckard's Increment-0 (~67 ms / ~87 ms / ~1.85). glm past=512 cliff 88.7 ms; qwen 120.9 ms. **Axis 3 — e2e tok/s native vs ORT** (`profile_native --steady`): qwen native **127.8** vs ORT **89.2 tok/s** (**1.43×**); glm native **98.6 tok/s** (matches the 96.4 doc reference). ORT can't load the native-authored glm ONNX (`GroupQueryAttention` `rotary_embedding_dim`) — foundry-local ORT-genai head-to-head is a follow-up. **Axis 4 — capture-safety re-probe** (existing `leverb_phase0` test, re-run): **segments=41**, captured M=8/M=1 = **8.77×**, NO-GO for Lever B today. Wired to re-run against Marlin (target: segments→1, ratio→~1). Its Part-C eager sweep independently reproduces Axis 2's cliff (67.2 ms) / tail (1.88 ms/row) — two harnesses agree. ## Measurement discipline / portability - Shared 8× H200 box: GPU pinned to a verified-idle high index, `nvidia-smi` re-checked before runs; harness reports median + p10/p90/max so contention is visible; **min is the contention-invariant estimator** used in the table. - **Rule 11:** every number is the current split-K/`general_bs` fallback layout (pre-Marlin), so re-running the identical harness proves <SM80 does not regress. `marlin_bench` prints device `compute_cap`. ## Validation `cargo fmt --all -- --check` clean · `cargo clippy` clean · `marlin_bench` + `profile_native` build under `bench-native,(bench-ort,)cuda`. Refs #957 · `docs/research/decode-remaining-levers-feasibility.md`⚠️ Draft: this is the BEFORE baseline + harness. Multi-turn — AFTER numbers land once Marlin (Deckard) merges. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
Scribe consolidation for the Marlin int4 GEMM program landing (#960/#961/#962). - Merge 5 decision inbox drops (deckard/pris/sebastian kernel+bench+numerics + chew/gaff reviews) into one consolidated Marlin program entry; corrected per-model attribution (glm block-128 → gate_up fusion no-op; 2.16× was the general split-K retune). - Archive resolved #957/#948/#949 spec-capture + Lever B entries to decisions-archive/2026-08.md (decisions.md 45,991 → 42,104 B, under the 50 KB gate). - Orchestration logs (deckard/sebastian/pris/chew/gaff), session log, cross-agent history updates; summarized sebastian history (>8 chronicle threshold). State-only change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 14, 2026
…wen 2×2 (#975) Follow-up to the Marlin int4 M>1 capture arc (kernel #960, bench #962, numerics gate #961, flaky-parity hardening #974). Refs #957 and `docs/research/decode-remaining-levers-feasibility.md`. ## What Deckard's final ask before Chew's #960 numerics review: an explicit **qwen captured-M=8 vs eager-M=8 token-equality** data point to fill the last cell of the captured/eager × glm/qwen 2×2. The shipping speculative path uses the *eager* verify path (`native_decode/backend.rs`), and the existing leverb INC0 probe writes a constant token (timing-only), so no existing harness answered this. This adds a **test-only** parity variant that feeds real tokens and compares captured-replay vs eager warm-forward per-row argmax **and** raw logits bytes. - `native_decode/cuda.rs`: `leverb_increment0_token_parity_attempt` (gated `#[cfg(all(test, feature="cuda"))]`). - `native_decode/leverb_phase0_probe.rs`: **Part E** prints per-row eager vs capture argmax + `logits_byte_identical` + PASS/FAIL. ## Result — full 2×2 GREEN, captured == eager **byte-identical** on both models | Model (block) | M | argmax_match | byte_identical | segments | B* | |---|---|---|---|---|---| | qwen2.5-14b (block-32) | 8 | ✅ | ✅ | 1 | 4.45× | | qwen2.5-14b | 1 | ✅ | ✅ | 1 | — | | glm-4-9b (block-128, canonical) | 8 | ✅ | ✅ | 1 | 2.19× | | glm-4-9b | 1 | ✅ | ✅ | 1 | — | B* refresh matches the frozen plateau (glm ≈2.16×, qwen ≈4.45×). No shipping path touched; fmt + engine clippy clean. Reproduce commands in `.squad/decisions/inbox/sebastian-captured-eager-parity.md`.⚠️ Bench/validation follow-up — a squad member (Chew/Deckard) should confirm before merge. Co-authored-by: Sebastian <sebastian@squad.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 15, 2026
…e decode Add the CUDA-graph-captured verify path (WP4 of #957): a persistent padded [1, width] token/position/logits binding set and a warm->arm->capture->replay state machine (DecodeCudaState::verify_capture + decode_verify_captured), modelled on run_one_token and the Increment-0 capture probe. Graceful eager fallback on NotCapturable keeps correctness independent of capture. Session surface: set_retain_graph_on_rewind / is_cuda / reset_captured_verify; rewind(0) tears down the retained verify graph before freeing its buffers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced Aug 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Speculative decoding under CUDA-graph capture — feasibility (DESIGN-ONLY)
Answers Justin's question — "想办法把 speculative decoding 的 capture 做好?有办法吗?" — with a concrete, honest staged path. No kernels touched; adds one research doc + one decision drop.
Verdict: CONDITIONAL-GO, and the single condition is Marlin (Lever A) — which we are already building as the primary decode lever.
MatMulNBitsgeneric 16×16 CUDA-core tiled GEMMs — M>1 leaves the tuned fused int4 GEMV (matmul_nbits.rs:4803-4869). GQA (efficient flash) and norm (same kernel) live in the cheap 1.85 ms/row tail.num_groups==1signature relaxation (normalization.rs:2306); GQA = pre-warmed fixed-M flash workspace (group_query_attention.rs:3099). Not kernel rewrites.Staged plan with hard gates
docs/research/decode-remaining-levers-feasibility.md) + a new "capture-safe at fixed M=K" contract item.Context
docs/research/decode-remaining-levers-feasibility.md.