Skip to content

qwen4_exp: serve NVIDIA's Qwen3.8-Flash-Next FP4-Local on its native formats (one GB10) - #195

Merged
AzeezIsh merged 26 commits into
mainfrom
feat/fp4-local-flash-next
Oct 21, 2026
Merged

AzeezIsh merged 26 commits into
mainfrom
feat/fp4-local-flash-next

Conversation

@AzeezIsh

Copy link
Copy Markdown
Contributor

Summary

Serve NVIDIA's nvidia/Qwen3.8-Flash-Next-FP4-Local (ModelOpt QAD, ~96 GiB) on one GB10, with each tensor family running on the format the checkpoint ships: no widening copies and no 8→4-bit requant. The full record (formats, verification, memory analysis, benchmarks, and the open speed gap) is in docs/checkpoints/qwen3.8-flash-next-fp4-local.md.

  • Loading.
    • CSF-compressed NVFP4 expert scales are decoded losslessly at load. The output is byte-identical to vLLM's reference decoder.
    • Shards under tensors/ are routed.
    • U32 tensors load.
    • Host-decoded tensors stay deferred, so their compressed originals are never resident.
  • Native MXFP8 for the GDN in_proj_qkv/z/out_proj, attention q/k/v/o, the shared expert and the vision attention.
    • New mxfp8_proj.cu: a 1-row GEMV and a batched GEMV whose rows are bit-identical to it, plus an exact dequant feeding a BF16 GEMM for prefill.
    • The shared expert uses a generalized separate-shared-expert seam (SeparateSharedExpert::{Bf16,Mxfp8}) and is applied in every MoE arm. The previous BF16 → NVFP4 requant of an 8-bit shared expert is gone.
    • The MTP shared expert now stays BF16, as shipped.
  • F32 norms end to end. The hc and PLE norm kernel arguments are now const float*. BF16 packs are widened exactly.
  • Native vision. MXFP8 and NVFP4 weights are uploaded as shipped and expanded per GEMM; the result is bit-identical to the old expansion.
  • PLE n-gram table. It stays disk-backed, with hot rows in a pinned RAM cache, and is decoded on the GPU (batched_embed_nvfp4, bit-identical to the host decoder). Rows and their block scales share one slot-parallel cache. ATLAS_PLE_CACHE_GB sizes the cache, the bookkeeping scales to tens of millions of slots, and a periodic hit-rate line is logged. Full residency was measured not to fit on one Spark: the table's payload entropy is 3.966 of 4 bits, so it doesn't compress (doc §4).
  • MTP: experts stored as per-expert NVFP4 are now loaded.

Measured (sparky, GB10; single runs unless noted; run ids in the doc)

  • BFCL golden ST-995: 87.44 overall / 87.32 normalized, n=995 (run-1792340347555552942). A same-binary NVFP4 control is still queued, so this PR makes no claim versus the NVFP4 pack.
  • Long context: passcode recall 12/12 at 32K–245K prompt tokens.
  • Gates: SSM-state-poisoning, cross-contamination and video pass. Vision fails 1 integrity probe; it fails identically on the NVFP4 pack and on the pre-change binary.
  • Speed, Atlas harness:
    • decode-floor 33.0 tok/s with MTP 3 drafts and exact verify.
    • 4-user aggregate 36.9 tok/s, 8-user 42.3 tok/s.
  • Speed vs NVIDIA's vLLM recipe, same box and same streaming client:
    • Short chat: 25.6 vs 41.5 tok/s.
    • ~18K-token agent turn: 22.9 vs 43.6 tok/s, with first token in 15.4 s vs 9.2 s.
    • Atlas is behind; the ranked plan to close the gap is in doc §8.

Notes for reviewers

  • Shared paths touched:
    • spark-storage n-gram cache: new scale source; u32 row ids; end_batch is O(pins).
    • The MoE separate-shared-expert seam: a rename plus the new enum.
    • The hc/PLE kernel signatures, including the strix-hip mirror, which was updated but not compiled on HIP here.
  • After every commit, the NVFP4 pack answered a fixed 4-request control byte-identically.

Test plan

  • Unit and mock-GPU tests for the codec, loader, n-gram cache, MoE separate shared expert and vision weights
  • GPU microtests: CSF vs the vLLM reference; MXFP8 / NVFP4 PLE / vision dequant vs the host codec, bit-identical
  • NVFP4 pack byte-identical on the 4-request control after every commit
  • FP4-Local: coherence, tool call, needle, BFCL ST-995, long-context recall, state-poisoning, contamination, video
  • Live EXACT_VERIFY_CHECK / BATCH_FAST_CHECK: 0 rows differ across MTP depth 3 and C=4 batching
  • cargo fmt --check, workspace clippy (ATLAS_SKIP_BUILD=1), license headers, typos, file-size cap at the rebased HEAD
  • Rebased HEAD: built, booted, and FP4-Local's 4-request control compared against the measured binary
  • HIP / strix build of the mirrored hc kernels
  • Same-binary NVFP4 BFCL control (queued)

AzeezIsh and others added 14 commits October 18, 2026 15:34
CSF-compressed NVFP4 block scales (lossless, per the layout vLLM's
csf_scales.py specifies), exact MXFP8 -> BF16 expansion, and NVFP4 rows
-> BF16 for row-gathered tables. Pure arithmetic, no env lookups in the
hot loops.

Checked on nvidia/Qwen3.8-Flash-Next-FP4-Local with examples/fp4local_decode:
CSF bytes identical to a numpy port of the vLLM reference decoder on four
expert tensors (81-762 exceptions each); dequantized experts cos 0.994,
MXFP8 projections cos 0.9996 and PLE rows cos 0.995 against the NVFP4 pack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4aUGgtiZX94pLr63HXQzn
…ssed tensors resident

- U32 maps to a raw 4-byte container (CSF exception streams); the fast
  loader's header now defers to the shared dtype table instead of a copy.
- The fast loader defers CSF streams and the weight/scale pair of every
  module in host_decode_modules, so the build expands them from disk.
- is_ngram_table also takes the per-shard NVFP4 block scales of the PLE
  table, which are row data like the weight.
- An index that names shards under a directory (tensors/model-*.safetensors)
  routed zero tensors to every shard; match shard paths, not file names.
- DeferredTensor::read_bytes / WeightStore::undefer for build-time consumers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4aUGgtiZX94pLr63HXQzn
The pack differs from the NVFP4 one only in how it stores tensors:
- weight_map::modelopt_local decodes the 73,728 CSF expert scales into
  plain E4M3, expands 438 MXFP8 projections and 56 vision NVFP4
  projections to BF16, and rounds the 100 F32 norms (hc_norm, PLE norms)
  to the BF16 the kernels read. Left F32, those norms produced NaN logits:
  every request answered with one empty token.
- The PLE table is NVFP4: a second row cache serves the block-scale rows
  and the gather decodes both on the host, the trellis path's contract.

Verified on a GB10 (sparky): boots in 65 s, coherent chat, correct tool
call, needle passes at 4.4k prompt tokens, 16.9 tok/s decode without MTP
(the NVFP4 pack measures 16.14 without it). MTP experts are per-expert
NVFP4 here and are not wired yet, so --speculative is not supported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4aUGgtiZX94pLr63HXQzn
nvidia/Qwen3.8-Flash-Next-FP4-Local quantizes mtp.layers.0.mlp.experts as
W4A16 NVFP4 with plain block scales, the trunk experts' own format, so a
PerExpertNvfp4 layout loads them through quantized_any as shipped. The
load log now names the detected layout instead of always claiming a
stacked BF16 requantization. mtp.rs's tests move to mtp_tests.rs.

Verified on a GB10: --speculative --num-drafts 1 boots, K2 acceptance
73-75%, decode 18.9-19.8 tok/s (16.9 without), outputs coherent and the
tool call unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4aUGgtiZX94pLr63HXQzn
…FP8 bytes

nvidia/Qwen3.8-Flash-Next-FP4-Local ships the GDN in_proj_qkv, in_proj_z and
out_proj (36 layers, 2.08 GB) as MXFP8: E4M3 plus one E8M0 scale per 32
inputs, QAD-trained that way. They were expanded to 4.15 GB of BF16 at load
and read by dense_gemv_bf16 — 44% of a decode token's GPU time.

- kernels: mxfp8_proj.cu — mxfp8_proj_gemv (1 row), mxfp8_proj_batchm (2..8
  rows, one weight pass, every row bit-identical to the 1-row kernel) and
  mxfp8_proj_dequant (exact BF16 expansion for prefill).
- ops::mxfp8_proj picks by row count; prefill expands one projection at a
  time into a single 84 MB scratch shared by all GDN layers, then bf16_gemm.
- materialize uploads native_mxfp8() modules byte for byte;
  ATLAS_FP4LOCAL_EXPAND_MXFP8=1 restores the BF16 expansion for A/B.
- qwen4_exp::gdn_mx8 builds the layer on those bytes; Qwen3SsmLayer::mx8 is
  the only copy of both projections and is checked first at every dispatch
  site (serial decode, batched verify, multi-seq decode, prefill).

Measured on GB10 (sparky):
- microtest: dequant byte-identical to modelopt_codec::mxfp8_to_bf16 over
  42M values; GEMV vs f64 rel_l2 1.7e-3; batchm M=1..8 0 mismatches;
  230 GB/s, 1.89x dense_gemv_bf16 on [16384, 2560]; M=8 costs 1.09x M=1.
- serve: pre-KV 97.6 -> 95.0 GB; decode with --speculative --num-drafts 1
  18.9-19.8 -> 24.2-28.6 tok/s; coherence prompts and tool call correct.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4aUGgtiZX94pLr63HXQzn
FP4-Local ships the 12 full-attention layers' q (with its interleaved
gate rows), k, v and o as MXFP8. The generic builder expanded them to BF16
and then requantized BF16 -> NVFP4 for decode: a second, lossy quantization
of weights NVIDIA's QAD trained in MXFP8, plus a BF16 copy for prefill.

- QuantWeight::Mxfp8(Mxfp8Proj); Mxfp8Proj moves to weight_map so the
  variant carries its geometry. quant_gemv/quant_gemm refuse it by name.
- Qwen3AttentionLayer::set_mx8_projections installs q/k/v/o as the only
  copies (BF16 slots NULL, no NVFP4/FP8 twin) with the kernels and the
  shared prefill scratch; try_mx8 is checked first at every projection
  site: serial decode Q (gated + ungated) / K,V / O, the multi-sequence and
  verify path (qkv_mx8.rs: one weight pass per 8 rows, then the per-row
  scatter, as qkv_fp8.rs does) and its O, and the three prefill
  projections through try_unpacked_prefill (MXFP8, then packed Q2_0).
- ops::mxfp8_proj: up to 32 rows run as 8-row GEMV chunks (bit-identical
  to single-token decode per row), more rows expand + bf16_gemm.
- qwen4_exp::attn_mx8 builds the layer; mx8_common holds the checked
  weight/scale lookup and one scratch sized for the largest projection of
  either layer kind. native_mxfp8() now covers self_attn.{q,k,v,o}_proj.

Measured on GB10 (sparky), --speculative --num-drafts 1: pre-KV 95.0 ->
93.4 GB; decode 24.5-26.3 tok/s (the MXFP8 q/k/v/o read 0.6 GB a token vs
0.3 GB for the lossy NVFP4 requant); coherence prompts, tool call, and the
~4.4k-token passcode recall all correct.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4aUGgtiZX94pLr63HXQzn
The FP4-Local pack ships the 100 hyper-connection hc_norm and PLE
norm_conv/norm_key/norm_query weights as F32 (the only release that does).
modelopt_local rounded them to BF16 because every consumer kernel was
declared __nv_bfloat16* — measured lossy on all 100 tensors.

The kernels already compute (1 + w) in FP32, so change the norm-weight
arguments to const float* (hyper_connection.cu: hc_pre, hc_head,
hc_pre_stage, hc_pre_stage_bf16, hc_post_stage_bf16, hc_pre_stage_vec,
hc_post_stage_vec; ple.cu: ple_gate; strix-hip mirror), load every site's
norm through dense_keep_f32 — F32 native for FP4-Local, exact (bits << 16)
widening for the NVFP4 pack — and delete the BF16 rounding. NVFP4 stays
bit-identical; FP4-Local keeps its full precision.
The FP4-Local checkpoint ships every main-layer mlp.shared_expert
{gate,up,down}_proj as MXFP8 (E4M3 + E8M0 scales). The loader used to
expand them to BF16 and requantize them to NVFP4 for the fused routed
kernels -- an 8-bit -> 4-bit loss on the one expert every token goes
through.

Generalize the Laguna BF16 seam into a separate shared-expert enum
(Bf16 | Mxfp8): fused routed kernels run with a NULL shared expert and
the native chain (mxfp8_proj + silu_mul) fills shared_out afterward.
Gate and up are row-concatenated into one [2*inter, h] projection so
the <=32-row bands take one pass plus fused_silu_mul; larger prefills
use the unfused trio. Covers decode, MTP K=2 verify, C>1 batched decode
(forward_rows, qwen4exp_fast, atomic_c4) and prefill. The MTP block's
BF16 shared expert is installed through the BF16 arm.

Tracked weight memory: +0.15 GB vs the NVFP4 shared copy.
gate_proj and up_proj are now views into the halves of the [2*inter, h]
gate|up weight (and scale) buffer the fused small-row arm already needed;
the store originals are reclaimed at load. Tracked weight memory drops
~0.16 GB. Also drop a dead local in helpers_shared.
FP4-Local's PLE table ships as packed E2M1 rows + E4M3 block scales on
NVMe. Every gather used to copy both to the host, decode to BF16 there,
upload, and synchronize the stream. Now the block-scale rows fault into
the same cache as the weight rows (new segmented scale source on
NgramRowCache) and a new batched_embed_nvfp4 kernel decodes them in
place -- bit-identical to the host oracle, no per-gather synchronize,
no copy_slot.

Bookkeeping scales to tens of millions of slots: u32 row ids, O(pins)
end_batch, and ATLAS_PLE_CACHE_GB sizes the arena (slot count stays an
env override, the span-derived floor still applies). Adds a 30 s
residency/hit-rate/fault-time line plus a shutdown summary.
FP4-Local ships the ViT's attn.qkv/attn.proj as MXFP8 and the MLP/merger
linears as NVFP4; materialize used to host-expand all of them to BF16.
They now upload as shipped (NVFP4 tensors stay deferred, uploaded
verbatim by the vision loader; visual MXFP8 joins the native list) and
VitWeight expands them into an encoder-owned BF16 scratch per GEMM --
mxfp8_proj_dequant and dequant_nvfp4_to_bf16, both bit-identical to the
host codec. Every other checkpoint keeps raw BF16 weights on the same
call path. Tracked weight memory drops ~0.5 GB.
…e open speed gap

Adds docs/checkpoints/qwen3.8-flash-next-fp4-local.md: what the checkpoint
ships, what Atlas now runs natively, how exactness was proven, why the n-gram
table stays disk-backed, the serve configuration that was benchmarked, the
results with their run ids, a same-box comparison against NVIDIA's vLLM recipe,
and the ranked list of what is left. The four scripts under
bench/qwen4_exp/fp4local/ are the non-in-tree harnesses those results used.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
CI's fmt job runs the latest stable rustfmt (1.10), which lays out several
of this branch's new expressions differently from the pinned 1.93 toolchain
they were formatted with. Formatting only.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@AzeezIsh

Copy link
Copy Markdown
Contributor Author

Overnight results (same build and settings as the doc, sparky GB10)

Same-binary quality A/B (only the checkpoint differs): BFCL golden ST-995, no overrides, single run each.

checkpoint overall normalized run
FP4-Local 87.44 87.32 run-1792340347555552942
NVFP4 pack 86.23 85.64 run-1792378038850732185

This is an observation from one run per arm, not a significance claim.

Re-runs with the adopted ATLAS_QWEN4EXP_HC_FAST (v2 profile):

  • decode-floor 33.0 tok/s (run-1792378753368339758)
  • quick-speed 512/512: 34.7 tok/s
  • SSM state poisoning: 12/12 invariant
  • long-context passcode recall: 12/12 at 32K–245K, the same as v1

End to end:

  • Image built from the measured commit: azeezish/atlas-gb10:a78d2fb (3.0 GB, revision label a78d2fb). It's saved offline on sparky and has not been pushed.
  • sparkrun served it with the rendered recipe, and coherence and needle recall passed.
  • Open item: at temperature 0 the image's answers are all correct but are not byte-identical to the bare-metal single-target binary. Suspects are the multi-target ('*') build's kernel selection and the in-container template path. Not yet root-caused.

Next on this branch (in progress): make FP4-Local loading format-driven. The per-module formats will come from quantization_config.quantized_layers instead of qwen4_exp module-name lists. CSF decoding will dispatch on weight_scale_encoding, and an unknown encoding will fail with an error naming it. The gate is byte-identical FP4-Local and NVFP4 outputs.

Replace the qwen4_exp-shaped name lists with ModeloptQuantPlan: the
checkpoint's quantized_layers map (config.json or hf_quant_config.json)
yields a per-module ModuleFormat { algo, group_size, scale_encoding },
accepting both group_size and weight_block_size spellings and the
FP8_PB_WO tile form. Which modules host-expand and which stay native now
come from the plan's algo intersected with a per-model NativeConsumers
registry; CSF streams decode only for weight_scale_encoding == "csf",
any other encoding fails the load naming it. A model with no registered
consumers gets the exact-but-unoptimized BF16 expansion with no names
needed. FP4-Local loads byte-identical to the name-driven path.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@AzeezIsh

Copy link
Copy Markdown
Contributor Author

Pushed b2c031e: format-driven FP4-Local loading

FP4-Local handling is now keyed on what the checkpoint declares, not on qwen4_exp module names.

  • ModeloptQuantPlan (weight_map/modelopt_plan.rs) reads quantization_config.quantized_layers, from config.json or hf_quant_config.json, into a per-module {algo, group_size, weight_scale_encoding}.
    • algo is one of NVFP4, W4A16_NVFP4, MXFP8, FP8 block [bn,bk], or other.
    • Both group_size and weight_block_size [1,32] are accepted.
  • CSF scales decode only for weight_scale_encoding == "csf". Any other encoding (e.g. csf_32x2_v1) fails at load with an error naming the module and the encoding. Its layout is not guessed.
  • Per-model native-consumer registry: a model declares which module roles it runs natively, per format; qwen4_exp declares exactly the previous set. Every other declared module gets the exact BF16 expansion. So a new FP4-Local checkpoint loads correctly (unoptimized) without name lists, and is then optimized by registering consumers.

Gates (sparky):

  • The FP4-Local boot log is unchanged: decoded 73728 CSF scale tensors, kept 354 MXFP8 projections native, expanded 84 MXFP8 and 0 NVFP4 projections to BF16, kept 100 F32 norms native.
  • The 4-request output control is byte-identical to the previous build, on both FP4-Local and the NVFP4 pack.
  • Checks pass: cargo +stable fmt --check, workspace clippy, license headers, typos, the file-size cap, and the lib tests. The new tests cover Flash-Next- and fixture-shaped maps, refusing an unknown encoding, registry coverage, and unregistered-format expansion.

Note: nvidia's only FP4-Local releases are Flash-Next and GLM-5.3-Flash; no Qwen3.8-27B FP4-Local exists. GLM-5.3-Flash FP4-Local enablement is owned by someone else; this change only makes its declared formats parse and fail loudly where a codec is missing.

…FP4-Local record

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@maplepaladin73

Copy link
Copy Markdown
Contributor

Happy to test this at TP=EP=2 if that's useful. Our GB10 pair has served this same checkpoint every day since 10-16, using Enntity's SparkQwen FP4-Local build. That gives a same-box reference for both output and speed.

What we'd run on a build of this branch at TP=2:

If TP=2 isn't meant to work on this branch yet, say so and we'll wait. Otherwise, is there a head commit you'd like us to pin?

…t ceiling

The default K-vs-batch ladder pinned every n<=4 verify at 3 drafts, so
qwen4_exp with ATLAS_QWEN4EXP_MTP_DEPTH=4..7 (K=5..8, exact-lane) still
speculated only 3 deep. The rung exists to step depth down with
concurrency, not to cap below the ceiling -- the n<=4 rung now passes
num_drafts (already min(--num-drafts, verify_max_drafts)) through, while
the 8/16/32 rungs are unchanged. Models capped at 3 see no change.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@AzeezIsh

Copy link
Copy Markdown
Contributor Author

Speed step S1 (sparky, current single-user profile + ATLAS_PLE_CACHE_GB=2, MTP 3 drafts unless stated)

config exactness CHECK decode-floor (accept len) short chat decode / first token ~17K agent turn decode / first token
control — 33.1 (3.29) 25.5 / 0.38 s 22.9 / 15.8 s
MOE_UNITS=1 0 rows differ (64 steps / 256 rows) 33.0 (3.29) 25.5 / 0.38 s 22.8 / 15.5 s
MOE_TC=1 different numerics, so every draft was rejected inconclusive (raw runs ~22) 21.4 / 0.38 s 20.6 / 15.4 s
depth 4 (after cap fix) 0 rows differ (64 / 256) 32.3 (3.29) 24.9 / 0.38 s 23.0 / 15.3 s
depth 5 (after cap fix) — 31.4 (3.29) 24.3 / 0.38 s 23.0 / 15.8 s
  • Pushed 171f532: the default MTP ladder's first rung held single-sequence speculation at 3 drafts, even when ATLAS_QWEN4EXP_MTP_DEPTH allowed more. It now passes the model's ceiling through. The rungs for higher concurrency are unchanged.
  • Deeper drafts don't pay on this workload. Accept length stays at 3.29, and the extra verify rows cost time, so the profile stays at 3 drafts.
  • Neither existing MoE lever helps at C=1. MOE_UNITS is a wash. MOE_TC breaks speculative exactness.
  • The gap to vLLM is in the routed-MoE decode kernels, which run at ~53% of bandwidth. That's the next step (S2).

AzeezIsh and others added 2 commits October 19, 2026 17:01
The single-row gate_up and silu_down kernels issue one byte-wide load
per thread per k step and sit at ~100 GB/s under a cold expert set
(~45 GB of per-layer pointer tables, 216 GB/s ceiling). Both impls now
double-buffer a 64-row packed tile plus its scale rows through shared
memory via cooperative 16 B loads, then accumulate each thread's own
output column in the identical FP32 order — byte-identical outputs,
1.1-1.26x on the cold bench, +3.6% decode-floor. The _e8m0 (GS=32)
variants keep the scalar impl.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
On a cold ~17K prefill the fault wall is ~450K O_DIRECT row reads; at
64 in-flight preads the pool reaches the GB10 NVMe's ~66K IOPS random-4K
ceiling (fio-measured), cutting the fault wall ~20% and agent TTFT ~6%
(15.4s -> 14.5s). Same rows, same bytes, same slot/pin semantics --
only deeper queue depth. ATLAS_PLE_FAULT_THREADS still overrides.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@AzeezIsh

Copy link
Copy Markdown
Contributor Author

Speed steps S2 and S4 (sparky, single-user profile, same-session A/Bs)

S2: routed-MoE decode (pushed 40a01d3). The cause was cold-data streaming, not memory layout. Each decode step reads about 9 MB of experts that are not in L2, out of roughly 1 GB.

  • A cold benchmark (48 layers × 512 separately allocated experts, fresh layer and random top-10 each pass) reproduces the in-server rate: 97 GB/s, against 160–300 GB/s when the data is warm.
  • A pure 16 B-coalesced read of the same bytes reaches 139 GB/s. That is the ceiling.
  • moe_expert_gate_up_shared_t / silu_down_shared_t now stage double-buffered 64-row tiles through shared memory. Each output keeps the same FP32 order.
  • Gates:
    • byte-identical in the microtest (NULL and NVFP4 shared expert);
    • FP4-Local and NVFP4 controls identical;
    • EXACT_VERIFY_CHECK 0 of 256 rows differ.
  • Results:
    • decode-floor 33.1 → 34.3 tok/s;
    • ab_stream: short chat 25.5 → 26.4 tok/s, ~17K agent turn 22.9 → 23.7 tok/s.
  • Rejected variants: wider 4-outputs-per-thread loads (slower, register pressure) and larger blocks (no change).

S4: prefill / first token (pushed c24ea26). In a profile of the ~17K agent turn, about 7 s of the ~15 s first-token time is the GPU waiting on first-touch n-gram (PLE) rows read from NVMe: about 455K misses per prompt.

  • Prefetching each chunk's rows earlier didn't help. The faults already overlapped compute.
  • The bound is the drive. fio random 4K reads reach ~17K IOPS at queue depth 1 and ~65K at QD 64.
  • The fault pool had 32 workers. 64 workers reach the drive's ~66K IOPS, also measured in place.
  • Results:
    • agent first token ~15.4 → 14.46 s;
    • n-gram fault wall 10.3 → 8.0 s.
  • Next: the drive's logical block size is 512 B and rows are ~90 B. If smaller aligned O_DIRECT reads raise IOPS, the fault path will use the file's actual DIO alignment instead of 4 KiB.

Remaining prefill costs from the same profile: routed-MoE batched GEMMs 25% of busy time, QSA attention 18%, mHC 15%, projections 11%, GDN 11%.

Cold faulting reads two IOPS-bound O_DIRECT reads per miss: the packed
row and its E4M3 block-scale row (~2.03 reads/miss measured cold). With
the flag on, attach_segmented_block_scales reads every shard's scale
tensor into one flat host arena in shard order at boot (~3.2 GB for the
128-shard NVFP4 table, loaded shard-parallel); fetch_row then memcpys
arena[id * stride..] using the same (shard, local) split as scale_loc,
leaving packed rows on disk. ~1.02 reads/miss, fault wall 7.5s->5.3s and
agent TTFT 14.4->13.7s on a cold 17K turn. Flag off is byte-identical;
Segments/files are built the same either way.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@AzeezIsh

Copy link
Copy Markdown
Contributor Author

Speed step S4f (pushed 5e18f77): opt-in resident n-gram block scales

Measured cold (dropped page cache, fresh boot), each first-touch n-gram miss cost 2.03 device reads: the 80 B packed row plus its 10 B E4M3 scale row. The drive is limited by reads per second, not bytes: fio gives the same ~62–69K IOPS at 512 B, 1 KiB and 4 KiB. So halving reads is the lever.

ATLAS_PLE_SCALES_RESIDENT=1 reads all 128 shards' scale rows (3.2 GB) into one host arena at boot, in parallel, one reader per shard. A fault then reads only the packed row and copies the scale bytes from the arena.

  • The arena is indexed by the same (shard, local) mapping the file path uses: one shared helper.
  • A missing or short shard is a boot error.
  • No kernel or gather change. With the flag off, the old path is unchanged.
  • New regression test covers shard 0, a shard boundary and the last row.

Cold A/B, single-user profile + ATLAS_PLE_CACHE_GB=2, ~17K agent turn:

flag off flag on
first token (median) 14.43 s 13.69 s
device reads 910,689 456,797
reads per miss 2.03 1.02
n-gram fault wall 7.5 s 5.3 s
decode, agent / short 23.6 / 26.1 tok/s 23.5 / 26.0 tok/s
tracked memory ~93 GB 96.2 GB (+3.2)

The 4-request controls are byte-identical, flag off vs on, on FP4-Local and the NVFP4 pack. The flag stays opt-in: its 3.2 GB comes out of the KV budget, which matters for the 262K profile.

AzeezIsh and others added 2 commits October 19, 2026 20:53
The 171f532 shallow rung passes the model's draft ceiling through at
n<=4, so slot 0 legitimately sizes at the full K-1 when num_drafts=7. The
assertion encoded the retired cap.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…stfmt

The format-driven load commit removed `native_mxfp8`; the module doc now
links `native_consumers`. Includes cargo +stable fmt output.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@AzeezIsh

Copy link
Copy Markdown
Contributor Author

Gate campaign status: preparing the run on sparky surfaced a regression in this branch. vision_encoder/enc_impl/init.rs looks up mxfp8_proj_dequant unconditionally, so vision models on every other kernel target (e.g. the gate's qwen3.6-35b-a3b-fp8-bf16head) fail the boot audit. Fix in progress: resolve the ViT dequant kernels only when the loaded vision weights are MXFP8/NVFP4. I'm also auditing the branch's other new kernel lookups for the same pattern. After the fix lands, the 10-gate campaign will run at the new head.

…ghts

BF16-only vision trees no longer register mxfp8_proj_dequant or
dequant_nvfp4_to_bf16 with the boot audit — the FP4-Local native-format
work had made every non-flash-next vision model fail boot. A checkpoint
that does carry MXFP8/NVFP4 projections fails hard naming the kernel and
weight if the kernel is missing.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@maplepaladin73

Copy link
Copy Markdown
Contributor

@AzeezIsh, as you asked: how this branch and Enntity's FP4-Local path (Enntity/atlas @ 92dd5435, what SparkQwen ships) differ, against this branch @ d773b218, read from both trees. Our GB10 pair has served the Enntity path at TP=EP=2 since 10-16.

They solve opposite halves:

  • Enntity's path is TP=EP=2 only. Every SparkQwen profile runs on two Sparks, and their LIMITATIONS still lists a single-GPU run as owed.
  • This branch is one GB10 only. The MXFP8 attention and GDN builders refuse tp_world_size > 1.
piece Enntity #195
CSF expert scales lossless decode at upload lossless, byte-identical to vLLM's decoder
GDN / attention decode checkpoint MXFP8 bytes, sliced per rank (mxfp8_rows_twin / mxfp8_cols_twin) checkpoint MXFP8 bytes, mxfp8_proj.cu
GDN / attention prefill MXFP8 W8A8 cuBLASLt on the same bytes exact dequant → BF16 GEMM
shared expert dequant → requantized to NVFP4 (lossy; documented as policy because the fused MoE reads NVFP4) native MXFP8 (SeparateSharedExpert)
F32 norms (hc + PLE) rounded to BF16 F32 end to end
PLE table one-time packed 28.8 GB copy on NVMe, row cache read from the checkpoint, pinned-RAM hot cache, GPU decode
vision expanded to BF16 uploaded as shipped, expanded per GEMM
format detection qwen4_exp tensor probes ModeloptQuantPlan (model-independent)

Why they differ, as far as we can tell: Enntity built for two Sparks and for speed (rank-sliced twins, W8A8 prefill). This branch built for fidelity to the checkpoint on one box (native shared expert, F32 norms, no extra PLE copy). Neither has the other's half.

The plan you suggested, as we'd carry it out with @data-angel:

  1. On Enntity: bring this branch's native shared expert and F32 norms to the TP=2 path, and add a TP=1 run.
  2. Port to main as one path: Enntity's rank-sliced decode twins and W8A8 prefill, plus this branch's shared-expert seam, F32 norms, PLE cache, vision and ModeloptQuantPlan.
  3. Test at TP=2 on our pair against today's SparkQwen build. TP=1 would be tested on a single GB10.

We opened Enntity/sparkqwen#13 to ask Jason about step 1. If this matches what you have in mind, we start there once he answers.

@AzeezIsh

Copy link
Copy Markdown
Contributor Author

@maplepaladin73 thanks, this is a useful map. One update to the premise: the TP=2 half of this branch exists as #198, stacked on this PR. It serves FP4-Local on two GB10s at TP=EP=2 and keeps this branch's fidelity choices. So step 1 of the plan is largely done on the Atlas-Inf side. Same rows as your table, with #198 at TP=2:

piece Enntity (TP=2) #198 (TP=2, on #195)
CSF expert scales lossless decode at upload lossless, byte-identical to vLLM's decoder (from #195)
GDN / attention decode MXFP8 bytes sliced per rank MXFP8 bytes sliced per rank: q/k/v and GDN segments by rows; o_proj / out_proj by columns on whole 32-input scale groups. Served by mxfp8_proj.cu: one weight pass for 2..=32 rows, each row bit-identical to the 1-row kernel
GDN / attention prefill MXFP8 W8A8 cuBLASLt exact dequant → BF16 GEMM (no activation quantization)
shared expert requantized to NVFP4 native MXFP8, replicated, added once after the EP all-reduce; the TC v3 MoE units now run without a fused shared expert
router mlp.gate not checked BF16 as shipped. Base Atlas requantized it to NVFP4; that was the last lossy conversion on our output path
F32 norms (hc + PLE) rounded to BF16 F32 end to end
PLE table packed 28.8 GB copy on NVMe, row cache resident on device on both ranks (ATLAS_PLE_RESIDENT=1): loaded from the checkpoint shards at boot, no NVMe reads while serving, startup parity across ranks
vision, format detection as in your table inherited from #195

Measured on one GB10 pair, rc16 TP2 profile, single runs unless noted (fingerprints in the PR):

  • Speed: decode-floor 84.6 tok/s (3 runs); short chat 63.0 tok/s; ~18K agent turn 43.1 tok/s with a 5.8 s first token; total at 1 / 4 / 8 users 64.5 / 146.0 / 205.7 tok/s.
  • Quality: BFCL ST-995 87.44 / 87.45 (n=995).
  • Exactness: speculative output == serial greedy output 8/8 at C1 and C8; the resident table matches the NVMe-cached path bit for bit.

We haven't run the Enntity / SparkQwen build on this pair, so there's no head-to-head yet.

What the merged path would take from each side, as I see it:

Happy to run the SparkQwen build on our pair for a same-box comparison if that helps step 3.

….93.1

Formatting only. CI's fmt job runs the rust-toolchain.toml pin (1.93.1),
which these two spots from the resident-scale arena did not match.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@AzeezIsh

Copy link
Copy Markdown
Contributor Author

FP4-Local agentic soak (2.5 h shape) passed at d773b21. Head is now 5ddfabb (formatting only)

agentic-webserver, 50 iterations: run-1792475013132909319 (sparky, GB10)

  • 50/50 webserver_ok, 50/50 followed_directions. The Flash-Next bounds in kernels/gb10/qwen3.8-flash-next/BENCH.toml are 50 and ≥49.
  • 11.94 s/turn, Σwall 4,502 s, 0 failed runs. Single run, so treat the speed as an observation; the counts are the gate's absolute bounds.
  • Fingerprint:
    • Build d773b218e, serving nvidia/Qwen3.8-Flash-Next-FP4-Local@b9ce18a6, port 8890.
    • Serve flags: --max-num-seqs 1 --max-batch-size 1 --max-seq-len 32768 --max-prefill-tokens 8192 --kv-cache-dtype bf16 --gpu-memory-utilization 0.9 --enable-prefix-caching --speculative --num-drafts 3, reasoning_effort low.
    • Env: ATLAS_QWEN4EXP_{EXACT_VERIFY,DECODE_FUSE,BATCH_FAST,BATCH_SMALL,HC_FAST}=1, MTP_DEPTH=3, ATLAS_PLE_CACHE_GB=2.
    • Harness params: iterations=50 max_turns=40 max_tokens=8192 wall_budget_s=100000.

5ddfabb6e: rustfmt under the pinned 1.93.1 for two spots in the resident PLE-scale code. This is what CI's cargo fmt --check flagged at d773b218. Whitespace only.

In progress on sparky:

  1. BFCL golden ST-995 at 5ddfabb6e (binary sha256 8294e6b8aec5bcae), no param overrides, same serve config as the soak. The earlier ST-995 (87.44 overall / 87.32 normalized) was on a78d2fb; this re-run puts the score on the current head.

  2. main (e29809db, this PR's base) vs head (5ddfabb6e) on two gates that failed on other models in the 2026-10-20 gate campaign:

    • vision-fidelity on Qwen/Qwen3.6-35B-A3B-FP8: multi-choice-image failed ("the later choices are not seeing it"). Geometry and the other probes passed. Main's last record (d5472821c8) passed.
    • concurrency-sweep on nvidia/Qwen3.8-27B-NVFP4: 59.5 aggregate tok/s at C=16 vs the 72.0 floor.

    Same binaries and invocation for both arms; vision runs once per arm, concurrency twice per arm, alternating. Until these finish, I'm not claiming either failure is pre-existing.

The kernel shadow structure failure is the strix-hip Nemotron override that #192 removes; it is red on main as well.

@AzeezIsh

Copy link
Copy Markdown
Contributor Author

ST-995 at head, plus main vs head on the two gates that failed on other models

BFCL golden ST-995 at 5ddfabb6e: run-1792488042130537521, 995 samples, 87.44 overall / 87.32 normalized.

  • Identical to the earlier a78d2fb record.
  • Binary sha256 8294e6b8aec5bcae, no param overrides, same serve config as the agentic soak (fingerprint in the previous comment).

main e29809db (this PR's base, binary 98d17fab2a3ae5cc) vs head 5ddfabb6e (8294e6b8aec5bcae). Same box, same --pull-request-gate invocation:

gate (subject) main head
vision-fidelity (Qwen3.6-35B-A3B-FP8) FAIL: multi-choice-image, choice [1] not seeing the image FAIL: identical
concurrency-sweep (nvidia/Qwen3.8-27B-NVFP4), run 1 PASS: C8 54.4 / C16 76.6 FAIL: C8 46.9 < 50.5
concurrency-sweep, run 2 FAIL: C8 47.1 < 50.5 FAIL: C8 47.0 < 50.5
  • Vision: fails identically on main, so it predates this PR.
  • Concurrency: not yet determined. Main failed once at the same values the head shows. Three more alternating pairs, with a cooldown before each run, are running now.

video-fidelity on nvidia/Qwen3.8-27B-NVFP4 (its own BENCH.toml entry and recipe), tested as a non-Unsloth replacement subject:

  • 5 of 5 runs so far, main and head alike: 12/13.
  • mixed-media reads [red, blue, yellow] for [red, green, blue, yellow].
  • So this subject fails on main as well.

…entic-webserver records

NVIDIA's FP4-Local checkpoint now has its own non-default, measured entries
next to the vendor pack's, so its evidence lives in the benchmark SSOT
rather than only in PR comments: the golden ST-995 draw (87.44 / 87.32 in
run-1792340347555552942 at a78d2fb and run-1792488042130537521 at
5ddfabb) and the 50-iteration agentic soak (50/50 + 50/50,
run-1792475013132909319 at d773b21). Bars follow the pack entry's rules:
measured less the inherited 0.4 MTP floor for BFCL, absolute measured
counts for the soak, speed deliberately ungated. BENCH.toml is outside the
closure hash, so no gate record is invalidated.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@AzeezIsh

Copy link
Copy Markdown
Contributor Author

Concurrency clears: with the MTP gate out of the picture, head matches main

Why the gate runs were bimodal (main as well): the MTP throughput gate.

  • Every slow cell in the six runs whose server logs I read has MTP gate: switching Mtp -> Serial at its start, and no fast cell has one.
  • After a batch-width regime change, the gate takes a one-step window as the new speculative baseline. When the batch jumps 1→N, that step is the bootstrap: one token per sequence, plus graph captures.
  • Example from main, run-1792544951131605812: current 33.5 tok/s vs other 56.4, while verify was delivering ~70.
  • Whether the batch jumps 1→N or 2/4→N is a timing race, which is why both builds flip between ~54 and ~47 at C=8.
  • The fix is a separate PR against main, since the defect is on main.

Controlled A/B, gate bypassed (ATLAS_MTP_GATE_FORCE=1, mtp_gate=force confirmed in every serve log, 0 switches):

  • concurrency-sweep --pull-request-gate, nvidia/Qwen3.8-27B-NVFP4, its own recipe.
  • Alternating head/main, 3 pairs, with a 180 s cooldown before each run. sparky, 2026-10-21 06:40–07:18 UTC.
  • head 5ddfabb6e (sha256 8294e6b8), main e29809db9 (sha256 98d17fab).
run C=1 C=4 C=8 C=16 verdict
head #1 run-1792565083507394981 24.4 44.2 57.5 79.9 PASS
main #1 run-1792565490959661118 24.3 44.8 56.8 79.6 PASS
head #2 run-1792565897142336884 24.3 44.6 58.5 79.7 PASS
main #2 run-1792566303636630239 24.3 44.1 58.0 79.4 PASS
head #3 run-1792566710152889056 24.3 43.6 57.9 79.9 PASS
main #3 run-1792567117659399254 24.3 43.6 56.8 79.6 PASS
  • Aggregate tok/s against floors of 16.2 / 33.5 / 50.5 / 72.0.
  • C=8 means: head 57.97, main 57.20. C=16: 79.83 vs 79.53. No cell differs by more than the run-to-run spread within either arm.
  • Observed under this fingerprint, 3 repeats per arm. Read it as "head matches main", not as a speedup.

Also on the branch (15839966a, config only): kernels/gb10/qwen3.8-flash-next/BENCH.toml now declares FP4-Local's own measured, non-default entries.

  • bfcl-subset: ST-995 87.44 / 87.32, from run-1792340347555552942 (a78d2fb) and run-1792488042130537521 (5ddfabb). Floors are 87.04 / 86.92.
  • agentic-webserver: 50/50 + 50/50, from run-1792475013132909319 (d773b21).
  • BENCH.toml is outside the closure hash, so no record is invalidated. The atlas-plugin gate tests pass with the new entries (225/225).
  • The recipe they name, qwen3.8/qwen3.8-flash-next-fp4-local, goes to sparkrun-recipes.

The remaining red checks:

Per the merge condition agreed tonight, merging.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@AzeezIsh
AzeezIsh merged commit 6b09b40 into main Oct 21, 2026
20 of 23 checks passed
AzeezIsh added a commit that referenced this pull request Oct 27, 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.

2 participants