Repository navigation
🚨 TP: give embeddings their own style, shard input embeddings by default, and shard widths that don't divide by the TP size - #49570
Conversation
… styles `ColwiseParallel` and `RowwiseParallel` no longer special-case `nn.Embedding`; they now require a linear layer. Embeddings get `EmbeddingRowwiseParallel` (`embedding_rowwise`, vocab-parallel) and `EmbeddingColwiseParallel` (`embedding_colwise`, hidden-dim parallel, used by qwen4_exp whose ngram table loads as vocab chunks). Doge's MoE embeddings move to `embedding_rowwise`, and gemma4's vision plan targets the inner `.linear` of `Gemma4ClippableLinear`, which holds the weights. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he TP size `DTensor.from_local` assumes even shards, so colwise outputs and rowwise inputs are wrapped with the full shape taken from the module's `out_features` / `in_features`. The colwise divisibility check goes away, and the TP tester keeps the tiny vocab of 99 so `lm_head` exercises the uneven split end to end. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`embedding_rowwise` and `embedding_colwise` become `EmbeddingParallel(shard_dim=0)` and `EmbeddingParallel(shard_dim=1)` instead of two classes, and a hidden-sharded embedding no longer needs a hidden size divisible by the TP size: its local output is wrapped with `_from_local_last_dim_shard`, like the linear layers'. The helper always shards the last dim, so it drops its placement argument. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ranks The strided split of a packed weight (an MoE experts' fused gate_up_proj) can't hold uneven slices, so fail up front with a clear error instead of building a misshapen DTensor. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The packed-weight divisibility check was unneeded: DTensor's strided split (with `src_data_rank=None`, as transformers distributes) and the checkpoint loader both give each rank matching ceil-sized slices of every packed chunk, so an uneven MoE `gate_up_proj` works. Verified with a TP forward and backward of a tiny Qwen3-MoE whose `moe_intermediate_size=36` splits 8/8/8/8/4 over 5 ranks. The TP mixin's packed-gradient slicing now uses the same ceil-sized shards. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
I added a cost section to discuss whether we should warn the user, bcz it might make sense to drop some tok ids from the vocab for faster inference/training. |
Every TP plan now vocab-shards the model's input embeddings: 89 plans with a plain `nn.Embedding`, the scaled-embedding family (Gemma*, VaultGemma, MiniCPM3, NeoMME, T5Gemma*), Qwen4-Exp's `embed_tokens`, and the models whose only plan was a colwise `lm_head` (AFMoE, DBRX, GPT-OSS, HunYuan, LFM2, PhiMoE). Replicated, the embedding cost `vocab x hidden` on every rank, and a tied `lm_head` sharded on the vocabulary did not match the embedding weight it shares. `test_tp_plan_shards_input_embeddings` keeps every model with a TP plan sharding its embeddings. The models that now get TP tests for the first time skip what can't pass: PhiMoE's backward (it routes by Gumbel sampling in training) and DBRX's tied forward (it doesn't support tying). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
| """ | ||
|
|
||
| model_type = "afmoe" | ||
| base_model_tp_plan = {"embed_tokens": "embedding_rowwise"} |
There was a problem hiding this comment.
no need to edit every tp plan, we have it by default in configuration_utils.py
if getattr(self, "tie_word_embeddings", False) and self.base_model_tp_plan is not None:
self.base_model_tp_plan = {
**self.base_model_tp_plan,
"embed_tokens": "embedding_rowwise",
}
There was a problem hiding this comment.
can i drop the tie_word embedding condition ? it's not necessary for sharding the embedding right ? actually checkpoints that aren't tied benefit the most bcz the tied ones might already have the lm_head sharded.
There was a problem hiding this comment.
also maybe better self.base_model_tp_plan = {"embed_tokens": "embedding_rowwise", **self.base_model_tp_plan} is better to let the model's tp plan take precedence (no model overrides it for now tho)
There was a problem hiding this comment.
okay there's a problem in this, setting self.base_model_tp_plan changes it from a class attr to instance attr, changing the nature of the attr and breaking some tests where we patch the class attr.
There was a problem hiding this comment.
adding it in the config to models that don't necessarily tie weights also broke some vlms that do have embed_tokens in the text decoder, but don't have it in the vision encoder so test_tp_plan_matches_params complained.
There was a problem hiding this comment.
i moved it to the distributed mixin
# Shard the input embeddings on the vocabulary unless the plan already names them.
if self._tp_plan and isinstance(getattr(self, "embed_tokens", None), torch.nn.Embedding):
self._tp_plan = {"embed_tokens": "embedding_rowwise", **self._tp_plan}
which you might not like 🥲 lmk what u think
There was a problem hiding this comment.
it would be best inside resolve_parallel_plans once I clean this PR #49527
Drop the `tie_word_embeddings` gate on the config-level `embedding_rowwise` default (explicit plan entries win), remove the per-config `embed_tokens` entries, and leave the lm_head-only models out of the embedding rollout; `test_tp_plan_shards_input_embeddings` now only checks decoder plans. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The config-level default added `embed_tokens` to every plan, including vision towers and models whose embedding has another name, which `test_tp_plan_matches_params` rejects. The model now adds `embed_tokens: embedding_rowwise` only when its base model has an `nn.Embedding` of that name; composite models pick it up from their text sub-model's plan. The tied-embeddings config default is left as on main for backward compatibility. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`init_parallel_plans` now adds the same `embed_tokens` entry, tied or not, so the config-level one only duplicated it (and overrode a plan's own `embed_tokens` style). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ArthurZucker
left a comment
There was a problem hiding this comment.
BTW this IS breaking we need a small alert! And prob ping @hmellor
| # Shard the input embeddings on the vocabulary unless the plan already names them. | ||
| if self._tp_plan and isinstance(getattr(self, "embed_tokens", None), torch.nn.Embedding): |
There was a problem hiding this comment.
arf yeah would be last resort!
I expect us to move more default plan for defaults so finding a solution is welcome 👁️
There was a problem hiding this comment.
the explicit solution is to name it in every plan 😆 see conv in #49570 (comment)
| return output | ||
|
|
||
|
|
||
| class EmbeddingParallel(TensorParallelLayer): |
There was a problem hiding this comment.
❤️ exactly what we needed for al ong time ty!
| "encoder.layers.*.self_attn.q_proj": "colwise", | ||
| "encoder.layers.*.self_attn.k_proj": "colwise", | ||
| "encoder.layers.*.self_attn.v_proj": "colwise", | ||
| "encoder.layers.*.self_attn.q_proj.linear": "colwise", |
There was a problem hiding this comment.
yep 🥲 q_proj is a Gemma4ClippableLinear wrapper; the actual nn.Linear and weight live at q_proj.linear
There was a problem hiding this comment.
was not deeply involved in gemma4 but wow indeedd.. super weird 😢
There was a problem hiding this comment.
yeah I am surprised it even worked before
There was a problem hiding this comment.
we will be able to catch those errors/regression soon with the distributed CI dashboard
There was a problem hiding this comment.
yeah I am surprised it even worked before
yeh normally we have a test that checks for plans matching params/modules, will check how it got past it. the encoder.layers.*.self_attn.q_proj is a module but now with the distinct linear/embedding colwise/rowwise plans we can check each plan is used with the class it's meant for for example.
|
@ArthurZucker I added a warning with what's breaking + the 🚨 |
|
Thanks for the ping, I don't think this will cause me problems in vLLM. I surgically replace all |
ArthurZucker
left a comment
There was a problem hiding this comment.
small nit as the func is not really readable / understandable but ok
| "encoder.layers.*.self_attn.q_proj": "colwise", | ||
| "encoder.layers.*.self_attn.k_proj": "colwise", | ||
| "encoder.layers.*.self_attn.v_proj": "colwise", | ||
| "encoder.layers.*.self_attn.q_proj.linear": "colwise", |
There was a problem hiding this comment.
yeah I am surprised it even worked before
|
[For maintainers] Suggested jobs to run (before merge) run-slow: doge, gemma4, inkling, muse_glimmer, nemotron_h, qwen4_exp |
CI recapDashboard: View test results in Grafana |
Main gives embeddings their own TP style (#49570), shards input embeddings over the vocabulary by default, and lets colwise widths split unevenly. Quantized tables have no vocab-parallel lookup, so the quantizer turns the default `embedding_rowwise` into `embedding_colwise`; `EmbeddingParallel` rejects an explicit vocab shard and keeps the whole-scale-group check. A per-expert slice is any list entry with fewer dims than its parameter (per-expert 0-dim scales into an (E, n) param), not only one fewer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Just FYI, this broke some TP tests in PEFT. The fix seems to be simple enough though: huggingface/peft#4050. If one of the TP experts (@3outeille or @michaelbenayoun) could confirm that this is valid, it would be great. Not sure if we need updates for LoRA embedding layers too. |
…sformers backend transformers PR #49570 introduced embedding_rowwise/embedding_colwise TP styles; the pinned SGLang commit predates this and raises ValueError for unknown styles, causing both model-loading test 3 and transformers-backend to crash (disguised as exit 137 via SIGQUIT propagation, not OOM). Patch _normalize_tp_plan via regex to wrap _normalize_tp_style in try/except and skip unknown styles. For TP=1 CPU CI the module simply runs unreplaced, which is correct. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The SGLang transformers backend does not yet support the embedding_rowwise TP style introduced in transformers PR #49570. Rather than patching SGLang at CI runtime, leave a TODO pointing to the right fix location in SGLang. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
What does this PR do?
Gives embeddings their own tensor-parallel style, shards every model's input embeddings by default, and lets layers shard widths that don't divide by the TP size.
ColwiseParallelandRowwiseParallelnow only handle linear layers.Warning
Breaking: custom TP plans must use
embedding_rowwise(vocabulary sharding) orembedding_colwise(hidden-dimension sharding) for embeddings. Linear styles (colwise*/rowwise*) now raise on embeddings. Eligible models also shardembed_tokensby default, including untied models; explicit plan entries take precedence.Embeddings get their own style
ColwiseParallelandRowwiseParallelused to branch onisinstance(module, nn.Embedding)in their parameter sharding, input handling and validation. Those branches move to a dedicatedEmbeddingParallel(shard_dim), which keeps the token ids replicated:embedding_rowwiseEmbeddingParallel(shard_dim=0)lm_head)embedding_colwise(new)EmbeddingParallel(shard_dim=1)ngram_embedding)ColwiseParallelandRowwiseParallelare now linear-only: applying them to a module withoutout_features/in_featuresraises a clear error instead of silently taking the embedding path.Plans that targeted embeddings with linear styles move to the embedding ones:
mlp.down_embed/mlp.up_embed→embedding_rowwise(wasrowwise_split_input).ple.ple_embedding.ngram_embedding→embedding_colwise(wascolwise_gather_output)..linearinside the clippable-linear wrappers, which have noin_features/out_featuresthemselves.Input embeddings are sharded by default
Almost every model replicated its input embeddings while sharding its
lm_head. A replicated embedding costsvocab x hiddenon every rank (~1 GiB for Llama-3-8B in bf16, ~2 GiB for Llama-3-70B), and a tied model's vocab-shardedlm_headdoesn't match the replicated embedding it shares a weight with.The config used to add
"embed_tokens": "embedding_rowwise"only for tied models. That default moves toinit_parallel_plans, without thetie_word_embeddingscondition: a base model with a TP plan and annn.Embeddingnamedembed_tokensshards it on the vocabulary, unless its plan already names it. Untied models (most large checkpoints) benefit the most. Composite models (VLMs, encoder-decoders) pick the entry up from their text sub-model's plan, e.g.model.language_model.embed_tokens. Vision towers and models without anembed_tokensembedding (GPT-NeoX'sembed_in) get nothing, so no plan carries a key that matches no module.No model plan is edited for this, and Inkling's and MuseGlimmer's explicit entries are removed as redundant. Models whose only plan is a class-level
lm_headentry (AFMoE, DBRX, GPT-OSS, HunYuan, LFM2, PhiMoE) are unchanged: they need full plans first, in a follow-up.Divisibility
Real vocab sizes often don't divide by the TP size (GPT-2's 50257, Whisper's 51865, GotOcr2's), and
colwise_gather_outputused to reject them outright.distribute_tensoralready splits a parameter unevenly (the last rank holds a shorter slice), and DTensor ops track the real shape. The one gap was the local fast path (inference and quantized kernels):DTensor.from_localassumes every rank holds an equal slice. A smallTensorParallelLayer._from_local_last_dim_shardhelper wraps the local tensor with its real width, taken from the module'sout_features/in_features(orembedding_dim).colwise*,rowwise*embedding_rowwise,embedding_colwisepacked_colwise,packed_rowwise(MoE experts' fusedgate_up_proj)num_expertsmust divide byep_size)Cost. Divisible sizes run exactly as before. An uneven width only costs anything where it is gathered (a
colwise_gather_outputlm_head,embedding_colwise), where DTensor pads and unpads around the all-gather; layers that reduce a full-sizePartial(attention, MLP, packed experts, vocab-parallel embeddings) are unaffected. Gathering bf16 logits over 2 GPUs (NCCL), vocab 128257 vs 128256:generateonly gathers the last token's logits, so that's about 0.15 ms per decode step.Tests
test_tp_plan_shards_input_embeddingsin the TP mixin: a static check that a model whose TP plan covers more than thelm_headshards itsnn.Embeddinginput embeddings. Withtest_tp_plan_matches_params, it passes for every model (258 passed).vocab_size=99exercises the unevenlm_headin every TP test. The backward test slices the reference gradients (plain and packed) with ceil-sized shards to match.mainalready hits with its explicitembedding_rowwiseentry (Inkling isn't in the CI allowlist).