Skip to content

Commit 7154a00

Browse files
zupengwangdelock
andauthored
Fix AutoTP metadata updates for unsharded modules (#8299)
## Summary - defer module metadata updates until recursive AutoTP replacement has completed - update logical dimensions only when a subtree contains parameters carrying AutoTP TP metadata - preserve metadata for replicated vision modules and high-dimensional weights while retaining existing sharded Linear behavior - add regression coverage for PatchEmbed-style Conv3d wrappers, unsharded attention subtrees, and genuinely sharded parameters Fixes #8285. ## Problem `update_mp_params()` previously ran before Linear replacement and scaled logical attributes for every traversed module. In multimodal models this changed replicated vision metadata, such as Qwen2-VL PatchEmbed `embed_dim`, even though the corresponding Conv3d weight was not partitioned. The metadata then disagreed with the full weight shape and broke vision forward. ## Validation Current master baseline `e70b90352e93e07862c45ebb946d75b813f025a2` reproduces on 2-GPU NCCL with `patch_embed.embed_dim=60` while `proj.out_channels=120`. - CPU targeted: `7 passed, 20 deselected` - 2-process Gloo metadata check: unsharded `num_heads=16`, sharded `hidden_size=6` on both ranks - 2-GPU custom-pattern suite: `27 passed` - 4-GPU AutoTP/ZeRO adjacent suite: `33 passed, 2 skipped, 24 deselected` - synthetic training on 1/2/3/5 RTX 3090 GPUs: forward, backward, and optimizer step all complete with zero cross-rank output, PatchEmbed gradient, and PatchEmbed parameter difference - 5-GPU synthetic, 3 runs with 3 warmup and 20 measured steps each: `5.008 ms` mean step latency, `409,216 tokens/s`, `28,170,752` peak allocated bytes, `52,428,800` peak reserved bytes - Qwen2-VL-2B AutoTP=2 vision forward: PatchEmbed `1280 -> 1280`, vision heads `16 -> 16`, language q_proj `[1536,1536] -> [768,1536]`, zero cross-rank output difference - changed-file pre-commit hooks, `DS_BUILD_OPS=0 python setup.py check`, `compileall`, and `git diff --check` pass --------- Signed-off-by: Wang Zupeng <zupenwang@gmail.com> Co-authored-by: Ma, Guokai <guokai.ma@gmail.com>
1 parent eba5d27 commit 7154a00

2 files changed

Lines changed: 85 additions & 9 deletions

File tree

deepspeed/module_inject/auto_tp.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -625,12 +625,17 @@ def hook(grad):
625625
def update_mp_params(self, child, name=None):
626626
if getattr(child, "replaced", False) == True:
627627
return
628+
if not any(hasattr(param, DS_AUTOTP_UC_META) for param in child.parameters()):
629+
setattr(child, "replaced", True)
630+
return
628631
tp_index = dist.get_rank(group=self.mp_group) if self.mp_group is not None else 0
629-
# Fused-expert containers (Mixtral/Llama4/Qwen-MoE style) hold their weights as 3D
630-
# parameters that AutoTP does not shard, so their dimension attributes must stay whole.
631-
# Halving e.g. Llama4TextExperts.hidden_size while its weights keep the full size breaks
632-
# the experts' batched matmul.
633-
if any(param.dim() >= 3 for param in child.parameters(recurse=False)):
632+
# AutoTP does not shard high-dimensional weights, so their dimension attributes must stay whole.
633+
# Some wrappers, such as Qwen2-VL PatchEmbed, keep that weight in a direct child module.
634+
has_unsharded_high_dimensional_param = any(param.dim() >= 3 for param in child.parameters(recurse=False))
635+
if not has_unsharded_high_dimensional_param:
636+
has_unsharded_high_dimensional_param = any(param.dim() >= 3 for module in child.children()
637+
for param in module.parameters(recurse=False))
638+
if has_unsharded_high_dimensional_param:
634639
setattr(child, "replaced", True)
635640
return
636641
param_list = [
@@ -683,8 +688,8 @@ def _replace_autoep_shared_experts(self, autoep_layer, autoep_name):
683688
key = next(lp for lp in self.linear_policies if isinstance(child, lp))
684689
setattr(autoep_layer, child_name, self.linear_policies[key](child, full_name, self.conv_linear_layer))
685690
else:
686-
self.update_mp_params(child, full_name)
687691
self._replace_module(child, full_name, "")
692+
self.update_mp_params(child, full_name)
688693

689694
def _replace_module(self, r_module, prev_name='', prev_class_name=''):
690695
if prev_name == '' and prev_class_name == '':
@@ -732,8 +737,8 @@ def _replace_module(self, r_module, prev_name='', prev_class_name=''):
732737
if new_child is not None:
733738
setattr(r_module, name, new_child)
734739
else:
735-
self.update_mp_params(child, full_name)
736740
self._replace_module(child, name, class_name)
741+
self.update_mp_params(child, full_name)
737742
# Traditional path: use linear_policies for type-based routing
738743
elif child.__class__ in self.linear_policies:
739744
setattr(r_module, name, self.linear_policies[child.__class__](child, prev_name + '.' + name,
@@ -750,8 +755,10 @@ def _replace_module(self, r_module, prev_name='', prev_class_name=''):
750755
setattr(r_module, name, self.linear_policies[key](child, prev_name + '.' + name,
751756
self.conv_linear_layer))
752757
else:
753-
self.update_mp_params(child, name)
754758
self._replace_module(child, name, class_name)
759+
# Descendants have now been replaced and carry universal-checkpoint TP metadata.
760+
# Keep logical dimensions whole when no parameter in this subtree was actually sharded.
761+
self.update_mp_params(child, name)
755762
return r_module
756763

757764
@staticmethod

tests/unit/model_parallelism/test_autotp_custom_patterns.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
from deepspeed.module_inject.layers import (GateUpPack_LinearLayer, LinearAllreduce, LinearLayer,
1717
SubParamLinearAllreduce, SubParamLinearLayer, fused_LinearLayer)
1818
from deepspeed.module_inject.layers import collect_autotp_universal_checkpoint_info
19-
from deepspeed.checkpoint.constants import PARAMETER_WITH_ROW_PARALLELISM_PATTERNS, TP_REPLICATED_PARAMETER_PATTERNS
19+
from deepspeed.checkpoint.constants import (DS_AUTOTP_UC_META, PARAMETER_WITH_ROW_PARALLELISM_PATTERNS,
20+
TP_REPLICATED_PARAMETER_PATTERNS)
2021
from deepspeed.module_inject.autotp_config import AutoTPConfig
2122
from deepspeed.module_inject.tp_shard import get_shard_size, get_shard_size_list, set_num_kv_heads
2223
from deepspeed.module_inject.auto_tp import AutoTP
@@ -496,6 +497,8 @@ def test_update_mp_params_uses_group_local_rank(monkeypatch):
496497
autotp.mp_group = tp_group
497498
autotp.mp_size = 2
498499
child = nn.Module()
500+
child.proj = nn.Linear(1, 1, bias=False)
501+
setattr(child.proj.weight, DS_AUTOTP_UC_META, {})
499502
child.num_heads = 12
500503

501504
monkeypatch.setattr(dist, "get_rank", lambda group=None: 1 if group is tp_group else 0)
@@ -515,6 +518,8 @@ def test_update_mp_params_shards_attributes_like_their_weights(monkeypatch):
515518
autotp.mp_group = tp_group
516519
autotp.mp_size = 2
517520
child = nn.Module()
521+
child.proj = nn.Linear(1, 1, bias=False)
522+
setattr(child.proj.weight, DS_AUTOTP_UC_META, {})
518523
child.hidden_size = 12
519524

520525
monkeypatch.setattr(dist, "get_rank", lambda group=None: 1 if group is tp_group else 0)
@@ -529,6 +534,70 @@ def test_update_mp_params_shards_attributes_like_their_weights(monkeypatch):
529534
assert child.hidden_size == get_shard_size(12, 2, "model.layers.0.mlp", rank=1)
530535

531536

537+
def test_update_mp_params_preserves_unsharded_high_dimensional_modules(monkeypatch):
538+
tp_group = object()
539+
autotp = object.__new__(AutoTP)
540+
autotp.mp_group = tp_group
541+
autotp.mp_size = 2
542+
child = nn.Module()
543+
child.embed_dim = 12
544+
child.proj = nn.Conv3d(3, child.embed_dim, kernel_size=1)
545+
546+
monkeypatch.setattr(dist, "get_rank", lambda group=None: 1 if group is tp_group else 0)
547+
set_num_kv_heads(3)
548+
try:
549+
autotp.update_mp_params(child, "model.visual.patch_embed")
550+
finally:
551+
set_num_kv_heads(None)
552+
553+
assert child.embed_dim == 12
554+
555+
556+
def test_replace_module_preserves_metadata_for_unsharded_subtree(monkeypatch):
557+
tp_group = object()
558+
autotp = object.__new__(AutoTP)
559+
autotp.mp_group = tp_group
560+
autotp.mp_size = 2
561+
autotp.partition_config = None
562+
autotp.state_dict = None
563+
autotp.linear_policies = {}
564+
autotp.prefix = ""
565+
visual = nn.Module()
566+
visual.attn = nn.Module()
567+
visual.attn.num_heads = 16
568+
visual.attn.qkv = nn.Linear(12, 36)
569+
visual.attn.proj = nn.Linear(12, 12)
570+
571+
monkeypatch.setattr(dist, "get_rank", lambda group=None: 1 if group is tp_group else 0)
572+
set_num_kv_heads(None)
573+
try:
574+
autotp._replace_module(visual, "model.visual")
575+
finally:
576+
set_num_kv_heads(None)
577+
578+
assert visual.attn.num_heads == 16
579+
580+
581+
def test_update_mp_params_follows_actual_tp_parameter_metadata(monkeypatch):
582+
tp_group = object()
583+
autotp = object.__new__(AutoTP)
584+
autotp.mp_group = tp_group
585+
autotp.mp_size = 2
586+
child = nn.Module()
587+
child.hidden_size = 12
588+
child.proj = nn.Linear(12, 12)
589+
setattr(child.proj.weight, DS_AUTOTP_UC_META, {})
590+
591+
monkeypatch.setattr(dist, "get_rank", lambda group=None: 1 if group is tp_group else 0)
592+
set_num_kv_heads(None)
593+
try:
594+
autotp.update_mp_params(child, "model.layers.0.mlp")
595+
finally:
596+
set_num_kv_heads(None)
597+
598+
assert child.hidden_size == 6
599+
600+
532601
def test_sliced_embedding_publishes_row_partition_metadata(monkeypatch):
533602
tp_group = object()
534603
autotp = object.__new__(AutoTP)

0 commit comments

Comments
 (0)