Notes from an independent NVFP4 port of Step-5-Preview
We integrated Step 5 Preview into our own C++/CUDA inference engine (no PyTorch
at runtime) and converted the FP8 release in place to NVFP4: 606 GB of
safetensors → a 317 GB tree, with the 30 976 routed experts streamed from host
mmap through a device-slot LRU. The dense graph pins at 8.6 GiB; with a
~1 500-slot expert cache the process sits at roughly 23 GiB of VRAM on one
RTX 5090. Thanks for shipping the standalone inference/ implementation — the
packed-DSA reference made the hybrid schedule and the router arithmetic
unambiguous.
What worked well
- Greedy determinism is solid: the framed control prompt answers "Paris" in the
reasoning block, and three runs produced byte-identical final logits
(logits_hash = 63b0f8b2fcf56d8aover all 128 896 logits), including across a
full model reload. - The native tool wire is unambiguous:
<tool_call>/<function=NAME>/<parameter=KEY>bodies parse cleanly. A real-weight round-trip generated the
call after the</think>marker and parsed to{"city":"Paris"}. - The router recipe is well specified: sigmoid scores, a selection-only bias,
renormalised(score − bias), then× 3.0, with the gated shared expert
added in FP32. - The tokenizer bridge reproduces the checkpoint tokenizer exactly (7/7
encode+decode cases, including the digit/CJK splits and the o200k
punctuation-run pattern).
Three checkpoint facts that cost debugging time
The RMSNorm is zero-centred:
x / rms(x) * (1 + w). The stored weights
are centred on zero, so treating them as a conventional RMSNorm scale
multiplies every activation by roughly zero. Shapes and dtypes do not object,
and the first symptom is fluent-looking but empty output (in our case, a
stream of newlines). A note in the model card would save any new integrator
the afternoon it cost us.moe_layer_listis 3..90, but layer 91 is a dense-MLP full-attention
layer. Reading the config as "dense prefix then MoE to the end" makes the
last layer look for 352 experts that do not exist. The mixed layout also
means an expert store cannot assume a contiguous dense suffix; we bound the
store at layer 91 and keep the flat indexlayer × experts + e.Layers 88–90 ship BF16 experts. They are listed in
quantization_config.modules_to_not_convertand have noweight_scale_inv. A converter that keys on "experts are FP8" silently stops
at layer 88; it must accept both an FP8 block-scaled stack and a BF16 stack.
Things that were easy to get subtly wrong
- The expert projections are fused stacks
[352, 1536, 4096]/[352, 4096, 1536], not per-expert tensors; the converter has to split them
and re-quantise each expert. - The RoPE span is per layer and the table is halved (pair
(d, d + span/2),
NEOX): 192 dims at theta 1e4 on the sliding layers, 64 dims at theta 1e7 on
the full-attention layers. Therope_scalingllama3 block hasfactor = 1.0
and is the identity. - The per-head attention gate (
self_attn.g_proj, one logit per head) is
applied beforeo_proj. - The final RMSNorm narrows the FP32 residual to BF16 first; that rounding
boundary is part of the reference arithmetic. - The router gate is BF16 on disk but must be evaluated in FP32.
What we did not run
The DSA indexer is stored in the converted tree but not executed. At or belowtopk × region_block_size = 4096 tokens every region is selected, so the dense
causal path is the reference path; beyond that our engine refuses the request
rather than silently producing a different answer. Implementing the FP8 region
summaries, the radix top-k with its zero-group tie-break and the region gather
is the remaining work for the 1M-token claim.
One data point on speed
Our correctness-first path (token-by-token prefill/decode, a router readback per
MoE layer, no CUDA graphs, no prefetch stream) reaches 1.82 t/s at 2 048
generated tokens (TTFT ≈ 14.5 s) on the RTX 5090, with the 6.6 GB/token
expert stream as the bottleneck. That is a floor, not a verdict on the model's
throughput — it mostly measures our host-to-device staging.
Overall: a well-specified model to port, and the three checkpoint facts above
are the only things that cost real debugging time.