This repository was archived by the owner on Oct 6, 2026. It is now read-only.
Repository navigation
W4A8: read MXFP4-CSF expert scales inline in the compact kernels - #479
Merged
voipmonitor merged 7 commits intoOct 4, 2026
Merged
Conversation
Compact (intermediate size 64 mod 128) W4A8 experts prepared from MXFP4-CSF checkpoints keep their E8M0 scales as inline storage (mxfp4_csf_inline): per native scale tile one 96-byte block of selector bits and up to seven exception records, 128 row bases per row block, and raw copies of the rare tiles with more exceptions. The compact micro FC1/FC2 and split M16 FC1/FC2 kernels copy each pipeline stage's block with cp.async and every thread rebuilds its four scale words in registers, so no routed-expert scale expansion runs before the MoE. B12X_W4A8_CSF_INLINE=0 at preparation keeps the per-call expansion; N256-repacked geometries are unchanged. Outputs are bit-identical to the expansion and to uncompressed scales (synthetic sweeps; DeepSeek-V4.1-Flash layers 0/20/38/39, TP4 ranks 0 and 3). With the flag off the phase 1, phase 2 and micro projection cubins are byte-identical to the base revision. DeepSeek-V4.1-Flash TP4 MoE layer on one RTX PRO 6000 (real layer 0, median of CUDA graph replays): 8 tokens 107.9 us vs 118.6 us with the expansion (111.9 us uncompressed), 64 tokens 587.5 vs 645.8 (603.1), 128 tokens 783.9 vs 871.0 (813.2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vLLM loads weights with expandable segments and max_split_size_mb:20. The inline storage was allocated after the encoder's large intermediates (per-row histograms, gathered planes, records), so each plane's storage kept pages of their freed blocks mapped. empty_cache could not release them: DeepSeek-V4.1 TP4 stranded 1.01 GiB per GPU (0.04 GiB with the per-call expansion) and started with 11.96M KV tokens instead of 13.13M. Encoding and verification now run in a private torch.cuda.MemPool and only the storage is copied out. Stranded memory drops to 0.06 GiB and the KV cache to 12.84M tokens; the remaining 2.2% below the expansion path is the inline storage itself (0.33 GiB per GPU). test_build_does_not_strand_allocator_pages builds eight DS4.1 TP4 planes in a subprocess with vLLM's allocator settings: 115 MiB stranded before, 15 MiB now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebuilding scale words in every pipeline stage pays off while compact W4A8 calls are latency bound, not once they are compute bound. On a DeepSeek-V4.1 TP4 MoE layer (real layers 20 and 39), inline reads beat the per-call expansion by 7 to 10% up to 1024 tokens but lost 2.5 to 6.3% at 2048 and 4096 tokens, which made 8K prefill slower than with the expansion. Plans above W4A8_CSF_INLINE_MAX_TOKENS (1536, B12X_W4A8_CSF_INLINE_MAX_TOKENS) now compile the native dynamic kernel. Before such a call, a Triton kernel expands every expert's native compact scale words from the inline storage into the canonical scratch (61 us for both projections of a layer), so no CSF batch is kept beside the inline storage. The compile-time and run-time choice both use the plan's token capacity. Same layer, inline against the per-call expansion: 8 tokens -9%, 128 -10%, 1024 -7.5%, 1536 -3.5% (layer 39 +0.7%), 2048 and 4096 equal. All arms bit-identical. Tests: the expansion reproduces the native plane on every inline geometry; with the limit lowered to 16, plans of 17 and 512 tokens replay under CUDA graphs with a poisoned scratch, receive the native scales and match native outputs bit for bit. CSF and inline suites: 207 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Serving resolves Triton programs without lowering them, so a plan above the inline limit could not prepare: "no-compilation phase encountered an uncached Triton program: _expand_tiles" (DeepSeek-V4.1 TP4, m4096 plan). Fused-MoE preparation now compiles each inline plane geometry's expansion (compile_inline_scale_expansion, a geometry-only payload) for dynamic plans above the limit and attaches it to the plan's programs; the launch goes through compile_plan.launch_triton like the other serving Triton programs. test_expansion_launches_the_program_preparation_compiles runs the compile job, then the launch under no_compilation() with an empty Triton cache; it fails without the job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 4, 2026
Merged
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Summary
DeepSeek-V4.1-Flash on the lossless MXFP4-CSF checkpoint (TP4, MXFP4 routed experts, MXFP8 activations,
w4a8_mx) ranexperts.mxfp4_csf.decode(...)before every MoE call: it expanded the routed experts' E8M0 scales into the shared nativeSFB scratch, and the W4A8 kernels then read that scratch. In serving this expansion was 2.73% of GPU kernel time at C16
(DSpark K7) and CSF decode was 4.3% (C8) / 1.7% (C16) slower per step than the uncompressed checkpoint.
This PR makes the compact W4A8 kernels read the compressed scales inline. No scale expansion runs before the MoE for
compact geometries, and outputs are bit-identical to both the expansion path and uncompressed scales.
Plans above
W4A8_CSF_INLINE_MAX_TOKENS(1536, envB12X_W4A8_CSF_INLINE_MAX_TOKENS) are compute bound: they expand everyexpert's scales once from the inline storage (a Triton kernel, 61 us per layer) and run the native kernels.
Serving (DeepSeek-V4.1-Flash TP4, DSpark K7, 4x RTX PRO 6000 Max-Q, lossless CSF checkpoint)
Beta code (b12x
d4b73caf, vLLMff292a9c63) with this branch;llm_decode_benchdecode at context 0 (two 30 s runs), standalone prefill(two runs). Same day and GPUs for all three arms.
B12X_W4A8_CSF_INLINE=0)Decode +3.6% (C8, C16) and +4.1% (C32) over the expansion, at or above the uncompressed checkpoint; prefill unchanged. Greedy smoke
output is coherent; server-level greedy text differs between restarts for every arm (also uncompressed vs expansion), so it is not an
identity check here. The kernel-level checks below are.
Fixes found by the serving run
max_split_size_mb:20; the inline storage, allocated after the encoder's large intermediates, kept their freed pages mapped(
torch.cuda.memory_reserved() - memory_allocated()after load: 1.01 GiB vs 0.04 GiB). Encoding and verification now run in a privatetorch.cuda.MemPooland only the storage is copied out (0.06 GiB; KV 12.79-12.84M). A subprocess test with vLLM's allocator settingsreproduces it: 115 MiB stranded over eight DS4.1 planes before, 15 MiB after.
hence the 1536-token limit above. With it, layer 20 against the expansion: 8 tokens -9.0%, 128 -10.1%, 1024 -7.5%, 1536 -3.5%,
2048 and 4096 0.0%; layer 39: -9.1%, -9.9%, -6.9%, +0.7%, +0.5%, 0.0%. Bit-identical throughout.
(
compile_inline_scale_expansion, a geometry-only payload) for dynamic plans above the limit; the launch goes throughcompile_plan.launch_triton. Without this the m4096 plan failed to prepare (no-compilation phase encountered an uncached Triton program: _expand_tiles).Design
b12x/_lib/quant/mxfp4_csf_inline.py(new):(the scratch already holds every expert's native scales at that point):
K64 tail): 64 bytes of selector nibbles (one bit per scale:
baseorbase + 1), a header word and up to 7exception records (slot, column, byte);
Rows are stored in consumer slot order (
slot = 32 warp + 4 quad + nt), so every MMA thread reads its four rowbases with one 32-bit load per task and its four selector nibbles with one shared 32-bit load per stage.
Bases are chosen per row from the native bytes (most bytes equal to base or base + 1), independently of the
checkpoint's unsliced row bases. Preparation verifies that the storage decodes to the native plane byte for byte.
csf_inlineflag; every existing path is unchanged when it is off):cp.asyncin the same commit group as A/B (replacing the512-byte SFB copy, or the byte-wise synchronous SFB loads of phase 2);
base * 0x01010101 + spread(nibble), then patches exception records in registers (or, for a raw tile, loads itsfour native words); K-tail tiles clear the two padding columns as the native stage does. No extra barrier.
W4A8CompactMicroProjectionKernel), compact micro FC2 (W4A8MaterializedPhase2Kernel,direct routes), and the split-materialized M16 FC1/FC2 (
W4A8MaterializedPhase1Kernel/W4A8MaterializedPhase2Kernel).These are all kernels that compact (N % 128 == 64) W4A8 runs, so compact CSF experts no longer keep the expansion
decoder at all.
MoEWeightPreparationPlan.w4a8_csf_inline(in the weight payload, so compile jobs and program keysdistinguish it),
B12XFP4ExpertWeights.mxfp4_csf_inline,w4a8_csf_inlinecompile key / launch-policy bit for thedynamic kernel and
csf_inlinekey for the compact micro launch.b12x_moe_fp4skips the decode and passes the inlinestorage in every SFB operand slot, so no launch argument references the caller's expansion scratch.
B12X_W4A8_CSF_INLINE=0at preparation restores the per-call expansion. N % 128 == 0 geometries (N256 repacked)keep the expansion path unchanged.
Measurements
benchmarks/benchmark_w4a8_csf_inline.py: one DS4.1 TP4 MoE layer (E=384, H=5120, N=576 per rank, top-6, MXFP8activations), three arms sharing weights, scales and routes, planned/prepared/graph-captured outside timing; median of
60 interleaved replays of a 20-call CUDA graph, per call. All arms bit-identical (checked by the benchmark).
RTX PRO 6000 Blackwell Workstation (600 W,
GPU-a2f726b2-60db-8102-abef-abf76c457fe0), imageghcr.io/local-inference-lab/vllm:karmic-kraken-beta-20261004-be47852127fafda5(CUTLASS DSL 4.7.1), dynamic clocks.Inline is faster than uncompressed scales because a stage reads 96 instead of 512 scale bytes per tile.
Scale storage of one layer and rank: native 101.2 MiB, inline 23.4 MiB (layer 0) to 27.3 MiB (layer 39).
Command (real layers):
python benchmarks/benchmark_w4a8_csf_inline.py --checkpoint <DeepSeek-V4.1-Flash-MXFP4-CSF> --layer {0,20,39} --tokens 8,64,128 --rounds 60.Tests
Added with the fixes:
test_build_does_not_strand_allocator_pages,test_expansion_reproduces_native_bytes(all inline geometries),test_expansion_launches_the_program_preparation_compiles(empty Triton cache,no_compilation(); fails without the compile job),test_inline_plans_above_the_limit_expand_the_inline_storage(limit lowered to 16; 17 and 512 tokens under CUDA graph replay with apoisoned scratch). Final run of the CSF/inline/x4t/NVFP4-CSF suites plus
test_fused_moe_variant_selection.pyandtest_prepared_plan_compile.py: 252 passed, 1 failed (test_mhc_runtime_projection_splits_share_code_across_prepared_plans,pre-existing on the base).
All runs on one RTX PRO 6000 (GPU 14/15) in the image above,
PYTHONPATH=<worktree> python -m pytest -q -p no:cacheprovider ....tests/quantization/test_mxfp4_csf_inline.py: storage reproduces native compact planes (DS4.1 W13 w13/w31 withN64 group tails, DS4.1 W2 with K64 tails, Kimi TP16, 3584x6) with sparse and dense exceptions, clamped and zero rows,
raw tiles; a probe kernel stages every tile like the compact kernels (thread offsets 0 and 32) and its rebuilt words
equal the native staged words exactly.
tests/moe/test_mxfp4_csf.py:test_inline_scales_match_native_and_expansion_under_poisoned_replay: native vs per-call expansion(
B12X_W4A8_CSF_INLINE=0) vs inline, H/N = 5120/576 (DS4.1 TP4), 1024/320 and 512/256 (N128: expansion kept),tokens 1, 8, 17, 64, 512 (micro and split M16 dynamic), w13 and w31 layouts, int32/int64 routes, CUDA graph capture
and three replays with mutated inputs, the shared expansion scratch filled with 0xFF (NaN E8M0) before every replay
and still 0xFF afterwards, zero replay allocations, outputs equal bit for bit. Fixture scales put exceptions in most
tiles and dense rows that overflow the records (raw tiles).
test_inline_capacity_plan_reuses_its_launches_for_live_counts: capacities 8 (micro) and 512 (dynamic), livecounts 1, 3, capacity/2 + 1 and capacity after
session.freeze()(frozen kernel resolution), bit-exact.test_inline_requires_compact_split_kernels.dfe61efcvs this branch (same command, files:tests/moe/test_mxfp4_csf.py,tests/quantization/test_mxfp4_csf.py,tests/quantization/test_x4t_scales.py,tests/moe/test_w4a8_mx_tp_moe.py,tests/moe/test_w4a8_tp_moe.py,tests/moe/test_w4a8_dynamic_kernel.py,tests/moe/test_nvfp4_csf.py,tests/moe/test_nvfp4_csf_dispatch.py,tests/moe/test_fused_moe_variant_selection.py,tests/preparation/test_moe_corpus.py,tests/preparation/test_tuning_predicates.py,tests/preparation/test_prepared_plan_compile.py,tests/preparation/test_precision_choices.py, plus the newtests/quantization/test_mxfp4_csf_inline.py):pre-existing: 40 W4A8 TP-MoE tests call stale helpers (
plan_tp_moe_execution()/build_tp_moe_fp4_binding()without
decode_config, non-canonical experts) andtest_mhc_runtime_projection_splits_share_code_across_prepared_plans.d4b73caf(NVFP4-CSF: expand_scales() for ahead-of-call scale expansion #477):tests/quantization/test_mxfp4_csf_inline.py,tests/moe/test_mxfp4_csf.py,tests/quantization/test_mxfp4_csf.py,tests/quantization/test_x4t_scales.pyand
tests/moe/test_nvfp4_csf.py: 185 passed.validation/csf/check_mxfp4_csf_inline.py, vLLMread_mxfp4_csf_layer):DeepSeek-V4.1-Flash-MXFP4-CSF layers 0, 20, 38, 39, TP4 ranks 0 and 3: inline storage decodes to the native planes
exactly (raw tiles: 0 to 7367 per plane), and native / expansion / inline MoE outputs are bit-identical at 1, 8
(micro), 64 and 128 (dynamic) tokens under graph replay with the poisoned scratch.
byte-identical to the base revision. Flag on, no spills or local memory: registers phase 1 168 -> 228, phase 2
102 -> 126 (direct 100 -> 120), micro projection 84 -> 124; the persistent grids stay at two CTAs per SM.
Risks and open items
per-call expansion; inline staging there needs the monolithic dynamic kernel's repacked SFB consumers.
TP4 layer, 0.33 GiB per GPU in total). The caller's shared expansion scratch is filled during preparation and serves
the calls above the inline limit (about 101 MiB per rank).
are reused.
AI assistance (Claude Code) was used for the implementation, tests, measurements and this description.
🤖 Generated with Claude Code