Skip to content

Commit e237d7e

Browse files
committed
Enhance AutoTP logging for lm_head diagnostics and improve HuggingFace tp_plan extraction logic
1 parent 5ee2faf commit e237d7e

5 files changed

Lines changed: 170 additions & 31 deletions

File tree

deepspeed/module_inject/auto_tp.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from .fusedqkv_utils import require_tp_fused_qkvw
1717
from deepspeed.module_inject.tp_shard import get_shard_size, get_shard_size_list
1818
from deepspeed.utils import groups
19+
from deepspeed.utils.logging import print_dist
1920
from deepspeed.module_inject.layers import is_autotp_training_mode
2021
from .autotp_config import TPLayerSpec, AutoTPConfig, PartitionType
2122

@@ -425,6 +426,17 @@ def _replace_with_config(self, child, name):
425426
model_type = self._get_model_type()
426427
spec = self.partition_config.find_matching_spec(param_name, model_type)
427428

429+
if any(part in ("lm_head", "embed_out") for part in name.split('.')):
430+
spec_details = None
431+
if spec is not None:
432+
spec_details = {
433+
"patterns": spec.patterns,
434+
"partition_type": spec.partition_type.value,
435+
"gather_output": spec.gather_output,
436+
}
437+
print_dist(
438+
f"AutoTP lm_head spec match: parameter={param_name!r}; matched_spec={spec_details!r}", ranks=[0])
439+
428440
if spec is None:
429441
# No matching spec found
430442
if self.partition_config.strict_mode:
@@ -478,6 +490,8 @@ def _create_column_parallel_layer(self, module, spec: TPLayerSpec, name: str):
478490
partition_dim=spec.get_partition_dim(),
479491
name=name,
480492
)
493+
if spec.gather_output:
494+
print_dist(f"AutoTP: replacing '{name}' with LinearLayer(gather_output=True)", ranks=[0])
481495
return LinearLayer(module, self.mp_group, name=name, gather_output=spec.gather_output)
482496

483497
def _validate_gathered_column_ties(self):

deepspeed/runtime/engine.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@
135135
from ..git_version_info import version
136136

137137
from deepspeed.profiling.flops_profiler.profiler import FlopsProfiler
138-
from deepspeed.utils.logging import print_json_dist, print_configuration, set_log_level_from_string
138+
from deepspeed.utils.logging import print_dist, print_json_dist, print_configuration, set_log_level_from_string
139139

140140
from deepspeed.accelerator import get_accelerator
141141

@@ -703,6 +703,43 @@ def _apply_autotp_partitioning(self, model, tp_config):
703703
if hasattr(tp_config, "get_partition_config_object"):
704704
partition_config = tp_config.get_partition_config_object()
705705

706+
model_config = getattr(model, "config", None)
707+
base_tp_plan = getattr(model_config, "base_model_tp_plan", None) if model_config is not None else None
708+
class_tp_plan = getattr(type(model), "_tp_plan", None)
709+
runtime_tp_plan = getattr(model, "__dict__", {}).get("_tp_plan")
710+
from deepspeed.runtime.tensor_parallel.config import _get_hf_tp_plan
711+
hf_tp_plan = _get_hf_tp_plan(model)
712+
713+
def lm_head_entries(tp_plan):
714+
if not isinstance(tp_plan, dict):
715+
return {}
716+
return {
717+
pattern: style
718+
for pattern, style in tp_plan.items()
719+
if any(part in ("lm_head", "embed_out") for part in pattern.split('.'))
720+
}
721+
722+
lm_head_modules = [
723+
name for name, _ in model.named_modules()
724+
if name and any(part in ("lm_head", "embed_out") for part in name.split('.'))
725+
]
726+
selected_route = "custom partition_config" if partition_config is not None else "HuggingFace tp_plan or AutoTP"
727+
model_class = f"{type(model).__module__}.{type(model).__qualname__}"
728+
print_dist(
729+
f"AutoTP tp_plan diagnostics: model_class={model_class}; route={selected_route}; "
730+
f"base_model_tp_plan={base_tp_plan!r}; type(model)._tp_plan={class_tp_plan!r}; "
731+
f"instance_tp_plan={runtime_tp_plan!r}; "
732+
f"effective_tp_plan={hf_tp_plan!r}",
733+
ranks=[0],
734+
)
735+
print_dist(
736+
f"AutoTP lm_head diagnostics: modules={lm_head_modules!r}; "
737+
f"base_entries={lm_head_entries(base_tp_plan)!r}; class_entries={lm_head_entries(class_tp_plan)!r}; "
738+
f"runtime_entries={lm_head_entries(runtime_tp_plan)!r}; "
739+
f"effective_entries={lm_head_entries(hf_tp_plan)!r}",
740+
ranks=[0],
741+
)
742+
706743
if partition_config is not None:
707744
autotp = AutoTP(module=model,
708745
all_reduce_linears=(),
@@ -723,19 +760,22 @@ def _apply_autotp_partitioning(self, model, tp_config):
723760
setattr(model, "ds_autotp_parsed", True)
724761
return
725762

726-
model_config = getattr(model, "config", None)
727763
from deepspeed.module_inject import replace_transformer_layer
728-
729-
from deepspeed.runtime.tensor_parallel.config import _get_hf_tp_plan
730-
731-
hf_tp_plan = _get_hf_tp_plan(model)
732764
if hf_tp_plan:
733765
from deepspeed.module_inject.tp_plan_converter import TPPlanConverter
734766
from deepspeed.module_inject.autotp_config import AutoTPConfig
735767

736768
layer_specs = TPPlanConverter.convert(hf_tp_plan)
737769
if layer_specs is not None:
738-
logger.info(f"Using HuggingFace tp_plan with {len(layer_specs)} layer specifications")
770+
gathered_output_patterns = [
771+
pattern for pattern, style in hf_tp_plan.items()
772+
if style.lower() in ("colwise_rep", "colwise_gather_output")
773+
]
774+
print_dist(
775+
f"Using HuggingFace tp_plan with {len(layer_specs)} layer specifications; "
776+
f"gathered column output patterns={gathered_output_patterns}",
777+
ranks=[0],
778+
)
739779
tp_plan_config = AutoTPConfig(tp_size=tp_size, layer_specs=layer_specs)
740780
autotp = AutoTP(
741781
module=model,
@@ -752,6 +792,14 @@ def _apply_autotp_partitioning(self, model, tp_config):
752792
autotp._replace_module(model)
753793
setattr(model, "ds_autotp_parsed", True)
754794
return
795+
print_dist(
796+
f"AutoTP: effective HuggingFace tp_plan could not be converted; falling back to heuristic AutoTP. "
797+
f"styles={sorted(set(hf_tp_plan.values()))!r}",
798+
ranks=[0],
799+
)
800+
else:
801+
print_dist("AutoTP: no effective HuggingFace tp_plan was found; falling back to heuristic AutoTP.",
802+
ranks=[0])
755803

756804
parser_dict = AutoTP.tp_parser(model)
757805
for client_module, injection_policy in parser_dict:

deepspeed/runtime/tensor_parallel/config.py

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -149,28 +149,26 @@ def get_tensor_parallel_config(ds_config):
149149
def _get_hf_tp_plan(model):
150150
"""Extract tp_plan from HuggingFace model.
151151
152-
Merge unique runtime entries into base_model_tp_plan. HuggingFace often adds
153-
duplicate runtime entries with a 'model.' prefix, but model-level entries such
154-
as lm_head may only exist in the runtime plan.
152+
Merge plans from the model config, model class, and runtime instance.
153+
HuggingFace may replace the instance plan with an expanded base-model plan,
154+
while model-level entries such as lm_head remain only on the model class.
155155
"""
156156
config = getattr(model, 'config', None)
157157
base_plan = getattr(config, 'base_model_tp_plan', None) if config else None
158-
runtime_plan = getattr(model, '_tp_plan', None)
159-
160-
if base_plan:
161-
if not runtime_plan:
162-
return base_plan
163-
164-
merged_plan = dict(base_plan)
165-
base_patterns = set(base_plan)
166-
for pattern, style in runtime_plan.items():
167-
unprefixed_pattern = pattern[len('model.'):] if pattern.startswith('model.') else pattern
168-
if pattern in merged_plan or unprefixed_pattern in base_patterns:
158+
class_plan = getattr(type(model), '_tp_plan', None)
159+
instance_dict = getattr(model, '__dict__', {})
160+
runtime_plan = instance_dict.get('_tp_plan')
161+
162+
merged_plan = {}
163+
canonical_patterns = set()
164+
for plan in (base_plan, class_plan, runtime_plan):
165+
if not isinstance(plan, dict):
166+
continue
167+
for pattern, style in plan.items():
168+
canonical_pattern = pattern[len('model.'):] if pattern.startswith('model.') else pattern
169+
if pattern.startswith('model.') and canonical_pattern in canonical_patterns:
169170
continue
170171
merged_plan[pattern] = style
171-
return merged_plan
172-
173-
if runtime_plan:
174-
return runtime_plan
172+
canonical_patterns.add(canonical_pattern)
175173

176-
return None
174+
return merged_plan or None

tests/unit/model_parallelism/test_tp_plan_real_models.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import deepspeed.comm as dist
99
import deepspeed
1010
from deepspeed.accelerator import get_accelerator
11+
from deepspeed.module_inject.layers import LinearLayer
12+
from deepspeed.runtime.tensor_parallel.config import _get_hf_tp_plan
1113
from deepspeed.utils import groups
1214
from unit.common import DistributedTest
1315

@@ -23,26 +25,30 @@ class TestTPPlanRealHFModels(DistributedTest):
2325
world_size = 2
2426

2527
def test_qwen2_tp_plan_with_zero2(self):
26-
"""Test Qwen2 model + tp_plan + ZeRO2"""
28+
"""Test an untied Qwen2 LM head with gathered column output and ZeRO2."""
2729
skip_on_device()
2830

2931
try:
30-
from transformers import AutoModelForCausalLM, AutoConfig
32+
from transformers import AutoModelForCausalLM, Qwen2Config
3133
except ImportError:
3234
pytest.skip("transformers not installed")
3335

34-
# Create small Qwen2 config
35-
config = AutoConfig.from_pretrained(
36-
"Qwen/Qwen2-7B",
36+
# Construct locally so the test does not download model configuration or weights.
37+
config = Qwen2Config(
3738
vocab_size=1000,
3839
hidden_size=128,
3940
intermediate_size=256,
4041
num_hidden_layers=2,
4142
num_attention_heads=4,
4243
num_key_value_heads=4,
44+
tie_word_embeddings=False,
4345
)
4446

4547
model = AutoModelForCausalLM.from_config(config)
48+
assert model.lm_head.weight is not model.model.embed_tokens.weight
49+
50+
tp_plan = _get_hf_tp_plan(model)
51+
assert tp_plan["lm_head"] in ("colwise_rep", "colwise_gather_output")
4652

4753
ds_config = {
4854
"train_micro_batch_size_per_gpu": 1,
@@ -67,6 +73,10 @@ def test_qwen2_tp_plan_with_zero2(self):
6773
engine, _, _, _ = deepspeed.initialize(model=model, model_parameters=model.parameters(), config=ds_config)
6874

6975
assert engine.autotp_size() == 2
76+
assert isinstance(model.lm_head, LinearLayer)
77+
assert model.lm_head.gather_output
78+
assert model.lm_head.weight.shape == (500, 128)
79+
assert model.model.embed_tokens.weight.shape == (1000, 128)
7080

7181
# Train for a few steps
7282
for _ in range(3):
@@ -77,11 +87,48 @@ def test_qwen2_tp_plan_with_zero2(self):
7787
group=groups.get_tensor_model_parallel_group(),
7888
)
7989
outputs = engine(input_ids, labels=input_ids)
90+
assert outputs.logits.shape == (1, 16, 1000)
8091
engine.backward(outputs.loss)
8192
engine.step()
8293

8394
assert not torch.isnan(outputs.loss)
8495

96+
def test_qwen2_tied_lm_head_is_rejected(self):
97+
"""Test that the gathered LM-head plan rejects an actual Qwen2 Parameter tie."""
98+
skip_on_device()
99+
100+
try:
101+
from transformers import AutoModelForCausalLM, Qwen2Config
102+
except ImportError:
103+
pytest.skip("transformers not installed")
104+
105+
config = Qwen2Config(
106+
vocab_size=1000,
107+
hidden_size=128,
108+
intermediate_size=256,
109+
num_hidden_layers=1,
110+
num_attention_heads=4,
111+
num_key_value_heads=4,
112+
tie_word_embeddings=True,
113+
)
114+
model = AutoModelForCausalLM.from_config(config)
115+
assert model.lm_head.weight is model.model.embed_tokens.weight
116+
117+
ds_config = {
118+
"train_micro_batch_size_per_gpu": 1,
119+
"tensor_parallel": {
120+
"autotp_size": 2
121+
},
122+
"zero_optimization": {
123+
"stage": 0
124+
},
125+
}
126+
127+
with pytest.raises(NotImplementedError, match="coupled vocabulary-parallel embedding"):
128+
deepspeed.initialize(model=model, model_parameters=model.parameters(), config=ds_config)
129+
130+
assert model.lm_head.weight is model.model.embed_tokens.weight
131+
85132
def test_custom_model_with_custom_tp_plan(self):
86133
"""Test custom model + custom tp_plan"""
87134
skip_on_device()

tests/unit/runtime/test_tp_plan_extraction.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,35 @@ def __init__(self, config):
129129
"layers.*.self_attn.q_proj": "colwise",
130130
"lm_head": "colwise_gather_output",
131131
}
132+
133+
def test_class_lm_head_plan_survives_runtime_base_plan_override(self):
134+
135+
class MockHFConfig:
136+
base_model_tp_plan = {"layers.*.self_attn.q_proj": "colwise"}
137+
138+
class MockHFModel:
139+
_tp_plan = {"lm_head": "colwise_gather_output"}
140+
141+
def __init__(self, config):
142+
self.config = config
143+
self._tp_plan = {
144+
"layers.*.self_attn.q_proj": "colwise",
145+
"model.layers.*.self_attn.q_proj": "colwise",
146+
}
147+
148+
model = MockHFModel(MockHFConfig())
149+
assert model._tp_plan != type(model)._tp_plan
150+
151+
tp_plan = _get_hf_tp_plan(model)
152+
153+
assert tp_plan == {
154+
"layers.*.self_attn.q_proj": "colwise",
155+
"lm_head": "colwise_gather_output",
156+
}
157+
158+
def test_extract_class_only_tp_plan(self):
159+
160+
class MockHFModel:
161+
_tp_plan = {"lm_head": "colwise_rep"}
162+
163+
assert _get_hf_tp_plan(MockHFModel()) == {"lm_head": "colwise_rep"}

0 commit comments

Comments
 (0)