Do not mark replicated weights as tensor-model-parallel - #715
Open
wenchenvincent wants to merge 2 commits into
Open
Do not mark replicated weights as tensor-model-parallel#715wenchenvincent wants to merge 2 commits into
wenchenvincent wants to merge 2 commits into
Conversation
ipanfilo
reviewed
Aug 26, 2026
| @@ -0,0 +1,296 @@ | |||
| # Replicated weights are counted `tp_size` times in the global gradient norm | |||
Collaborator
There was a problem hiding this comment.
While this is nice explanation, should fixed bug description be a part of docs?
Collaborator
Author
There was a problem hiding this comment.
Good point, thanks — dropped it.
The doc file is removed from the PR and the full explanation now lives in the PR description instead, so it does not become tree maintenance for a bug that will not exist after this change. The PR is now code-only: 3 files, +12 -4.
wenchenvincent
force-pushed
the
fix/replicated-weight-gradnorm
branch
from
August 31, 2026 02:49
f0c9f0e to
76ec87e
Compare
Linear.reset_parameters marked every weight is_parallel=True regardless of parallel_mode. For parallel_mode=None the weight is replicated on every TP rank, so downstream consumers that use the attribute to de-duplicate -- notably Megatron's param_is_not_tensor_parallel_duplicate(), which gates get_grads_for_norm() -- admit it to the global gradient norm once per rank instead of once. The norm is assembled as a sum of squares across ranks, so the contribution is added tp_size times. Inflation follows sqrt(1 + (TP-1)*f), where f is the replicated weights share of the true squared norm. Measured at TP=8 on MI355X: DeepSeek-V4-Flash 3.763 -> 2.472 (1.52x, f~0.19) DeepSeek-V3 12.228 -> 9.011 (1.36x, f~0.12) This is primarily a diagnostic bug: the reported norm is what practitioners use to judge training health, tune --clip-grad and compare against reference curves. The effect on weights is optimizer-dependent -- clipping is a global uniform rescale, which Adam largely absorbs and Muon absorbs exactly, while SGD sees it in full. Affects architectures that deliberately replicate weights carrying real gradient energy. Megatron MLA passes parallel_mode=duplicated for q_down_proj/kv_down_proj, so DeepSeek-V2/V3/V3.2-family models take this path. Restores TE own default: _MODEL_PARALLEL_ATTRIBUTE_DEFAULTS declares tensor_model_parallel=False for a non-parallel tensor. Introduced upstream in 0449033 (2023-02-10).
Both accept parallel_mode: Optional[str] = None and mark weights is_parallel=True unconditionally, exactly as Linear did (1 site in LayerNormLinear, 2 in GroupedLinear). LayerNormMLP is NOT affected: it has no parallel_mode parameter and is structurally column-then-row, so is_parallel=True is always correct there. The bias handling below each weight loop is already correct -- it branches on parallel_mode (row sets sequence_parallel, column marks the bias, None marks nothing). Only the weight marking ignored the mode. These two are latent rather than triggered: no Megatron wrapper instantiates them with parallel_mode=None today (TELayerNormColumnParallelLinear passes column; TEColumnParallelGroupedLinear/TERowParallelGroupedLinear pass column/row). They are reachable through TE public API, and fixing them keeps the three call sites consistent. Only the Linear fix is backed by measurement.
wenchenvincent
force-pushed
the
fix/replicated-weight-gradnorm
branch
from
August 31, 2026 02:49
76ec87e to
56a9e19
Compare
ipanfilo
approved these changes
Sep 2, 2026
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
transformer_engine.pytorch.Linearmarks every weight as tensor-model-parallel,including weights that are explicitly replicated (
parallel_mode=None, which is whatMegatron's
"duplicated"maps to). Megatron reads that attribute to decide whether aparameter enters the global gradient-norm sum once, or once per TP rank. Replicated
weights are therefore counted
tp_sizetimes and the reported gradient norm isinflated.
Measured on two MLA models at TP=8, each with a constant per-iteration ratio:
The DeepSeek-V3 run uses Megatron's
pretrain_gpt.pywith--multi-latent-attention --transformer-impl transformer_engineand no third-partymodel code, so the behaviour is reproducible with upstream components alone.
This is primarily a diagnostic bug. The gradient norm is the number practitioners
use to judge training health, tune
--clip-grad, detect instability, and compareagainst reference curves — and it is wrong by 52% here. Its effect on the weights
is much smaller, and depends entirely on the optimizer (see
Impact on training).
Mechanism
transformer_engine/pytorch/module/linear.py,Linear.reset_parameters:parallel_modeselects the dim but never whether to mark at all, so a non-parallellinear is still labelled
tensor_model_parallel=True.Megatron consumes it in
param_is_not_tensor_parallel_duplicate():which gates
get_grads_for_norm(). The norm is then assembled as a sum of squaresacross ranks, so a replicated weight's contribution is added
tp_sizetimes:Two details confirm the intent:
_MODEL_PARALLEL_ATTRIBUTE_DEFAULTSdeclares{'tensor_model_parallel': False, 'partition_dim': -1, 'partition_stride': 1};the code overrides its own stated default for the non-parallel case.
Megatron does not correct it afterwards: the post-init loop in
megatron/core/extensions/transformer_engine.pysets onlyallreduceandsequence_parallel.Note there is no separate "duplicated" state. The attribute set is entirely TP-scoped
and TE's
Linearhas no data-parallel concept at all (sequence_parallel,tp_group,tp_size,parallel_mode). Replicated istensor_model_parallel=False.How large is it
where
fis the duplicated weights' share of the true squared norm — gradientenergy, not parameter count. The distinction matters: in DeepSeek-V4-Flash the
replicated projections are ~1.5% of parameters but hold ~19% of gradient energy,
because they see the full hidden state and every head's gradient flows back through
them.
Inverting the measured ratios gives
fdirectly, and both models land far above theirparameter share (~0.3%), confirming the effect tracks gradient energy rather than
parameter count:
So the same bug is invisible in most models and significant in MLA at large TP.
Which axis is affected
Only tensor parallelism.
--use-distributed-optimizereach rank'sget_parameters()returns only its shard, so a gradient enters the sum on exactly one DP rank. Without it, the norm group is the model-parallel group, excluding DP.param_is_not_tensor_parallel_duplicate()— the attribute above, and the only guard on this axis.Impact on training
Megatron's
--clip-gradis norm-based and global: one scalar for the whole model,applied uniformly.
This is a rescale, not a clip — direction and the relative weighting between
parameters are preserved exactly. Three properties together bound the damage:
relative-weighting error;
untouched;
It also only engages at all when the inflated norm crosses the threshold:
--clip-grad 0clip_coeff >= 1, gated off1/inflationDeepSeek-V4-Flash is in the third regime: true norm ~2.47 against
--clip-grad 1.0,so clipping fires every step, with coefficient
0.266instead of0.405— gradients34% smaller than intended.
What that does to the weights depends on the optimizer:
m -> c·m,v -> c²·v, soupdate = lr·m̂/(√v̂ + ε/c)— theccancels except throughε. Residue:cvaries per step so cancellation across accumulated moments is imperfect; plus the first steps before moments equilibrate.B = UΣVᵀ -> UVᵀ, discarding the singular values, and the update magnitude comes from a shape-dependentget_muon_scale_factor(size[0], size[1]). ScalingBbycleavesUVᵀunchanged.Megatron's default is Adam, so for most users the weight-level effect is small — but
the reported norm is wrong regardless, and an SGD configuration sees the full effect.
Which models are exposed
For a standard transformer every linear is column- or row-parallel, so
is_parallel=Trueis correct and the bug cannot trigger.
It requires an architecture that deliberately replicates weights carrying real
gradient energy. Multi-head Latent Attention is exactly that — the low-rank
down-projections are replicated because the KV latent is shared by every head (each TP
rank needs the whole latent), and because the down-projection feeds a column-parallel
up-projection that requires the complete input on every rank.
Megatron's own MLA does this explicitly
(
megatron/core/transformer/multi_latent_attention.py):Scope: any Megatron MLA model takes this path. DeepSeek-V3 has been measured
directly (see Evidence) using Megatron's own
pretrain_gpt.py, confirming thebehaviour is not specific to any downstream model implementation. DeepSeek-V2 and V3.2
share the same spec path and are expected to behave identically, though they have not
been run.
Evidence
DeepSeek-V4-Flash, 4 layers, TP=8, EP=8, PP=1, DP=1, bf16, fixed seed (deterministic,
identical across reruns). Two linear backends, to show the behaviour is not specific
to one implementation.
The constant ratio across iterations is the signature of a counting error rather than a
numerical one. The two backends differ slightly only because the runs have different
random initialisation.
Loss is unchanged over the same window — differences in the 4th–5th decimal with no
systematic drift — confirming the fix alters only the clipping scale, not the
mathematics:
DeepSeek-V3 on Megatron's own MLA
Megatron
pretrain_gpt.py,--multi-latent-attention --transformer-impl transformer_engine,6 layers (3 dense + 3 MoE, following V3's
first_k_dense_replace=3), hidden 7168,128 heads,
q_lora_rank=1536,kv_lora_rank=512, 256 experts, TP=8, EP=8, bf16.No third-party model code; the only variable is the one-line change to
Linear.reset_parameters.Loss is again unchanged (13.19588 vs 13.19578, 13.20511 vs 13.20663, ...).
History
Introduced in
044903374(2023-02-10, "QKV parameters unfused path fixes andoptimization" #66), which added weight marking to
transformer_engine/pytorch/module.py.Verified by bisect: the preceding commit
78b4e9339(2023-02-07) has no weight-markingcall.
The initial code drop (
996ea169c, 2022-09-27) marked only biases, which incolumn-parallel layers genuinely are sharded.
parallel_mode: Optional[str] = Nonewasalready reachable by
5612ba784(2022-10-04), so the non-parallel case existed when theunconditional marking landed.
Everything since is refactoring, not semantic change:
c6a4a4e08(2023-05-09) moved thecode into
module/linear.py; v1.3 moved it from__init__intoreset_parameters. Thebehaviour is unchanged in ~3.5 years and is present both in NVIDIA
mainand inROCm/TransformerEngine
dev.Fix
Mark only genuinely sharded weights:
This restores TE's own documented default.
The same unconditional marking is present in
LayerNormLinear(1 site) andGroupedLinear(2 sites), both of which also acceptparallel_mode: Optional[str] = None,and is fixed the same way.
LayerNormMLPis not affected: it has noparallel_modeparameter and is structurally column-then-row, so
is_parallel=Trueis always correctthere.
Note the bias handling directly below each weight loop is already correct — it branches
on
parallel_mode(row setssequence_parallel, column marks the bias,Nonemarksnothing). Only the weight marking ignores the mode.
No Megatron wrapper currently instantiates
LayerNormLinearorGroupedLinearwithparallel_mode=None(TELayerNormColumnParallelLinearpasses"column";TEColumnParallelGroupedLinear/TERowParallelGroupedLinearpass"column"/"row"),so those two are latent — reachable through TE's public API but not triggered by Megatron
today. Only the
Linearfix is backed by the measurements above.A downstream workaround needing no TE change is to clear the attribute after
construction, before the optimizer is built — the distributed optimizer copies TP
attributes onto its shards via
copy_tensor_model_parallel_attributes, so the clearedvalue propagates:
Caveats
ratios are specific to those configurations; they follow
sqrt(1 + (TP-1)f)and varywith model, layer count and TP size.
dense/MoE balance differs from production — real DeepSeek-V3 is 58 MoE + 3 dense,
which changes
fand hence the ratio. Production TP is typically higher than 8, wherethe scaling law predicts a larger factor.
optimizer-sensitivity table is derived from the update rules, not measured end to end;
a convergence comparison against a reference curve has not been run.
copy_tensor_model_parallel_attributes), not measured.