fix(deepseek-v4): use vanilla RMSNorm weight semantics#315
Merged
SeedSource merged 4 commits intoJul 14, 2026
Conversation
DeepSeek-V4 ships HF-vanilla RMSNorm weights (`out = x * w / rms`), including
values far from 1. Atlas applied the offset-from-1 storage convention to them:
the loader pre-subtracted 1.0 and stored `bf16(w - 1)` so the offset-from-1
`rms_norm` kernel (`out = x * (1 + w) / rms`) would recover `1 + (w - 1) = w`.
That round-trip is exact only when `w` is near 1. DeepSeek-V4's norm weights are
around 0.03, so it stored about -0.97, and BF16's rounding error there becomes a
large RELATIVE error on the weight once 1 is added back (catastrophic
cancellation). Measured across all 249 norm tensors in the checkpoint:
attn_norm 3.44 % mean relative error, 16.4 % max
q_norm 3.52 % mean, 19.3 % max
kv_norm 4.94 % mean, 100 % max, 23 sign flips (layers.4 is near zero)
compressor.norm 55.9 % mean, 100 % max, 423 sign flips
ffn_norm 0.49 % mean
final_norm 0.34 % mean, 31.6 % max
Weights that straddle zero are not merely degraded: `bf16(w - 1)` rounds to
exactly -1.0, so `1 + (-1.0)` collapses the weight to zero, destroying sign and
magnitude together.
This loads V4's norm weights exactly and dispatches the vanilla RMSNorm kernel,
which already exists in-tree (`kernels/gb10/common/rms_norm_vanilla.cu`) and is
already used by the DFlash drafter for precisely this reason.
Scope:
- guarded by an explicit model predicate (`model_type == "deepseek_v4"`), not
inferred from weight statistics
- every V4 checkpoint norm weight loads exactly (`dense_auto`); every launch
consuming one dispatches `rms_norm_vanilla` (attn_norm, ffn_norm, q_norm,
kv_norm, compressor.norm, final norm, MTP norms)
- the UNWEIGHTED q_b_norm normalize keeps the offset kernel and its zero-filled
weight buffer (1 + 0 = 1). Pointing it at the vanilla kernel would multiply
by zero
- V4 without the mHC highway now fails at load: the fused residual+norm kernels
are offset-only with no vanilla twin, and would silently apply (1 + w)
- `dense_minus_one` is removed. V4 was its only caller
- no MoE, routing, attention, or other model family is touched. Models that
genuinely store offsets (Qwen3-Next, init 0) keep `dense` + the offset kernel
Verification:
- unit tests (`norm_convention_tests`): the offset round-trip is bit-exact for
weights >= 0.63 (which is why every other family was unaffected), >1 % wrong
for real attn_norm values, ~20 % wrong at q_norm's tail, and collapses a small
negative compressor weight to zero
- device test (`examples/rmsnorm_vanilla_microtest.rs`): the production kernels
against an F64 host formula, 120 cells (6 weight regimes x 4 hidden dims x
5 seeds), 0 failures. The offset path and the unweighted normalize are both
bit-exact unchanged
This is a correctness fix. It is NOT a resolution of the remaining DeepSeek-V4
parity gap: on a frozen 8-question eval the score moved 12/40 -> 15/40, but the
loop degeneration remained in 8/8 questions, so the dominant failure is unchanged
and still under investigation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RxQjRmdoT6qfWofRnmqfw
The `typos` linter flags `tru` as a misspelling of `true`/`thru`. It was an abbreviation for the F64 ground-truth vector; spell it out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RxQjRmdoT6qfWofRnmqfw
…est (typos CI) The previous rename used a word boundary, which does not split on an underscore, so `pred_tru` survived. Spell it out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RxQjRmdoT6qfWofRnmqfw
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.
Summary
DeepSeek-V4 ships vanilla RMSNorm weights (
out = x * w / rms), including values far from 1. Atlas was applying the offset-from-1 storage convention to them: the loader pre-subtracted 1.0 and storedbf16(w - 1), so the offset-from-1rms_normkernel (out = x * (1 + w) / rms) would recover1 + (w - 1) = w.That round-trip is exact only when
wis near 1. V4's norm weights are ≈ 0.03, so it stored ≈ −0.97, and BF16's rounding error there becomes a large relative error on the weight once 1 is added back — catastrophic cancellation. For weights that straddle zero it is worse than degradation:bf16(w - 1)rounds to exactly-1.0, so1 + (-1.0)collapses the weight to zero, destroying sign and magnitude together.This PR loads V4's norm weights exactly and dispatches the vanilla RMSNorm kernel —
kernels/gb10/common/rms_norm_vanilla.cu, which already exists in-tree and is already used by the DFlash drafter for exactly this reason. V4 was simply wired to the wrong kernel.Important
This is a correctness fix. It is NOT a resolution of the remaining DeepSeek-V4 parity gap.
On our frozen 8-question eval the score moved 12/40 → 15/40, but loop degeneration remained in 8/8 questions and every answer still finished on
lengthrather than a clean stop. The dominant failure mode is unchanged and still under investigation. Please do not read this PR as "V4 is fixed."Measured — all 249 norm tensors in the checkpoint
attn_normq_normkv_normffn_normcompressor.normfinal_normThe fix is exactly lossless: the checkpoint is already BF16, so
bf16(w) == wand the new error is0.00e+00on every tensor.Why this went unnoticed
The offset round-trip is bit-exact when
w ≳ 0.63— measured on-device, zero error across the near-one (0.63–0.99) and large (1.0–3.83) regimes. That describes every other model family Atlas serves, all of which store genuine offsets (initialised to 0) or weights near 1. DeepSeek-V4 is the first checkpoint whose norm weights sit near zero. A unit test pins this property in both directions so it cannot regress.Scope
ships_vanilla_norm_weights(config) := config.model_type == "deepseek_v4". Not inferred from weight statistics.dense_auto); every launch consuming one dispatchesrms_norm_vanilla—attn_norm,ffn_norm,q_norm,kv_norm,compressor.norm, the final norm, and the MTP norms.q_b_normnormalize is deliberately left on the offset kernel. It is driven by a zero-filled weight buffer (1 + 0 = 1), so pointing it at the vanilla kernel would multiply the Q path by zero. Only the norms that consume a checkpoint weight are switched.residual_add_rms_norm/rms_norm_residualpaths are offset-only, have no vanilla twin in-tree, and would silently applyx * (1 + w)to an exactly-loaded weight. Better to refuse than to serve wrong numbers.dense_minus_oneis removed — V4 was its only caller. The offset loader existed solely for this model.dense+ the offset kernel and are bit-exact unchanged.Tests
Unit (
norm_convention_tests, runs in CI — no GPU needed):deepseek_v4takes the vanilla path;qwen3_next/qwen3_5_moe/deepseek_v3/llama/ … all keep offset-from-1attn_normvalues, ~20 % wrong atq_norm's tailDevice (
crates/spark-model/examples/rmsnorm_vanilla_microtest.rs) — the production kernels against an F64 host formula:q_b_normnormalize is bit-exact unchangedq_norm/attn_norm/ straddle-zero; new = 0 on all threeCI gates run locally, all green:
cargo fmt --all --check,cargo clippy --workspace --tests,cargo test --workspace(0 failures).Validation
Served EP=2 on 2× DGX Spark (GB10, SM121), native-MXFP4 DeepSeek-V4-Flash, dual-rail RoCE. Baseline and fixed images were built from the same Dockerfile and launcher, differing only in this change, with the
sparkbinary sha256 verified inside the running container on both nodes.🤖 Generated with Claude Code
https://claude.ai/code/session_015RxQjRmdoT6qfWofRnmqfw