Repository navigation
Conversation
DeepseekV4TopKRouter and DeepseekV4HashRouter computed their logits in the activation dtype, so with dtype=torch.bfloat16 the scores, the bias addition, the top-k and the routing weights all ran in bf16. Cast the router matmul to float32 and run it with autocast disabled, as DeepseekV3TopkRouter and Ernie4_5_MoeTopKRouter do.
Contributor
|
Thank you for your contribution 🤗! CI Security Gate — waiting for maintainer approvalCI is approved automatically only for authors who already have a contribution merged in this repository. A maintainer will review this PR and approve CI to run on it. |
Contributor
|
[For maintainers] Suggested jobs to run (before merge) run-slow: deepseek_v4 |
Member
|
cc @ArthurZucker since you added V4! I checked this out and I think they're correct that upstream uses |
This branch has not been deployed
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.
What does this PR do?
What's wrong
DeepseekV4TopKRouterandDeepseekV4HashRoutercomputelogits = F.linear(flat, self.weight)in the dtype of their inputs. Withdtype=torch.bfloat16, almost everything is bf16:post_attention_layernormcasts its output back to the activation dtype.gate.weightis bf16. The checkpoint stores it in bf16, and it is not in_keep_in_fp32_modules_strict.e_score_correction_biasis float32.So the logits, the
sqrtsoftplusscores and the normalised routing weights are all bf16.DeepSeek's reference
Gate.forward(inference/model.pyin deepseek-ai/DeepSeek-V4-Flash) computesscores = linear(x.float(), self.weight.float())and keeps the scores and weights in float32. This library'sDeepseekV3TopkRouterdoes the same, withF.linear(hidden_states.type(torch.float32), self.weight.type(torch.float32)).Near 1, consecutive bf16 values are 2⁻⁷ ≈ 0.0078 apart. The scores of competing experts can be closer than that. The float32 bias is then added to rounded scores, so the top-k depends on the rounding, not only on the router.
Reproduction
On main (95bafdc):
With this PR:
Impact
I measured this on DeepSeek-V4-Flash's layer-5 router:
layers.5.ffn.gate.weight(bf16),layers.5.ffn.gate.bias(float32) andlayers.5.ffn_norm.weight.DeepseekV4TopKRouter, with the dtypes that adtype=torch.bfloat16load gives.ffn_normweight, cast to bf16. I did not run the full model on text, so real hidden states may give a different rate.Gateformula in float32.torch.autocast("cpu", dtype=torch.bfloat16)torch.autocast("cpu", dtype=torch.bfloat16)The remaining 3.42e-7 is float32 rounding. The router takes
topk(..., sorted=False), so it adds up the six weights in a different order from the reference.The hash layers (the first 3 in V4-Flash) pick their experts from
tid2eid, so only their weights are affected.Fix
F.linear(flat.type(torch.float32), self.weight.type(torch.float32)), asDeepseekV3TopkRouterdoes. The scores, the bias addition, the top-k and the normalisation then run in float32, and the routers return float32 logits and weights.maybe_autocast(device_type=device_type, enabled=False), as inErnie4_5_MoeTopKRouter.torch.autocast(dtype=torch.bfloat16)runs the matmul in bf16 again. Trainer, through Accelerate, turns that autocast on withbf16=True, outside DeepSpeed.maybe_autocastpatched to a no-op), the new test still fails on its autocast case, withtorch.bfloat16 != torch.float32.maybe_autocastis anullcontext.modular_deepseek_v4.py, andmodeling_deepseek_v4.pyis regenerated.The routers now hand float32
top_k_weightsto the experts. DeepSeek-V3's router already does, so all experts backends take them:DeepseekV4Experts.forward),grouped_mmandbatched_mmmultiply the bf16 expert output by the weights. That promotes to float32, and the result is cast to the hidden dtype (.to(final.dtype)/.to(hidden_states.dtype)).deepgemm, the finegrained-FP8 forwards andsonicmoefirst cast the weights to the activation dtype (sorted_weights.to(out.dtype),sample_weights.to(proj_out.dtype),top_k_weights.to(hidden_states.dtype)). For them only the final multiply stays in bf16. Expert selection and normalisation are still float32.deepgemm_megamoecopy_s the weights into its buffer.With
output_router_logits=True, the recorded router logits are now float32 instead of bf16.load_balancing_loss_funcalready returned a float32 aux loss. I checked a tiny bf16 V4 with labels andoutput_router_logits=True, on main and with this PR:Tests
I added
DeepseekV4ModelTest::test_router_computes_in_float32:torch.autocast(torch_device, dtype=torch.bfloat16).Results (CPU, macOS arm64, Python 3.13.12, torch 2.13.0):
AssertionError: torch.bfloat16 != torch.float32. With this PR, it passes.test_modeling_deepseek_v4.pygives 122 passed, 195 skipped and 13 failed.Tensor parallelism is not supported on MPS devices.utils/checkers.pypasses withruff_check, ruff_format, init_isort, sort_auto_mappings, noisy_comments, modular_conversion, copies. I did not runmake typing(tyis not installed).deepseek_v4file has changed. On 8090b6a, the full test file and the checks give the same results.I also ran the test file on Linux (Python 3.13.3, torch 2.13.0+cu130), on 8090b6a with this PR's test file and on this PR's head:
torch.autocast("cuda", dtype=torch.bfloat16): 2.44% (801 / 32,768). The logits are bf16 and the weights float32.Related
Duplicate search (read-only, 2026-10-27,
huggingface/transformers):gh search issues --include-prs "DeepseekV4TopKRouter"gh search issues --include-prs "DeepseekV4HashRouter"gh search issues --include-prs "deepseek v4 router float32"/"… bf16"/"… fp32"gh search issues --include-prs "deepseek v4 routing precision"/"deepseek v4 gate float"/"deepseek v4 expert selection"gh search issues --include-prs "deepseek_v4 router"gh pr list --state open --search "deepseek_v4"gh pr list --state open --search "DeepSeek-V4"gh pr list --state open --search "deepseek v4 router"For each of the 27 distinct open PRs, I listed the changed files (
gh pr view --json files). For the PRs that touchdeepseek_v4files, I read the diff (gh pr diff):flat→hidden_states,logits→router_logits). It keepsF.linear(hidden_states, self.weight), so the bf16 router remains. It is a textual conflict only: whichever lands second needs a small rebase.modular_deepseek_v4.py, but not the routers.Fp8Config) #49777: the config.test_modeling_deepseek_v4.py.maybe_autocast(..., enabled=False)wrapper to the routers that already cast to float32. That is 25 models, including DeepSeek-V3. It changes nodeepseek_v4file: V4 didn't cast, so it was not in that PR.Code Agent Policy
The Transformers repo is currently being overwhelmed by a large number of PRs and issue comments written by code agents. These often are low-quality, or fix extremely minor issues that occur rarely or never in practice. As a result, we're instituting a rule that first-time contributors should not use code agents to submit PRs or issues. We'd also ask autonomous "OpenClaw"-like agents not to open any PRs or issues.
Issues/PRs from first-time contributors that violate this rule will probably just be closed without review, and we might block you, especially if you open more than one or appear to be deliberately ignoring this. We especially do not want new contributors to jump in on random issues to contribute an agent-written fix. This creates lots of noise for reviewers and other users and will almost certainly get you blocked.
For more information, please read
CONTRIBUTING.md.The code, the tests and this description were drafted with an AI coding agent (Claude Code), so the box above is not checked.
Before submitting
Who can review?
Anyone in the community is free to review the PR once the tests have passed. Feel free to tag members/contributors who may be interested in your PR.