Skip to content

Commit 44f5ba1

Browse files
committed
Cover an uneven vocabulary head restoring at a different tp degree
101 rows over two ranks gives shards of 51 and 50, so the map has to carry the per-rank extents rather than assume an even split, and restoring at TP1 merges two unequal shards into one tensor. The fixture asserts the emitted map records that split before saving, so the test fails if conversion silently falls back to the vocabulary category. Adapted from @jinyouzhi's cross-tp vocab test in #8309, using the column partition already in tree so it carries no dependency on that PR. Signed-off-by: Achyuthan Sivasankar <achyuthan.sivasankar@gmail.com>
1 parent e385ba2 commit 44f5ba1

1 file changed

Lines changed: 86 additions & 0 deletions

File tree

tests/unit/checkpoint/test_autotp_uc_checkpoint.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,3 +1847,89 @@ def test_every_convertible_parameter_has_a_map(self):
18471847
assert not missing, (f"these parameters are placed by a name category but carry no affine map, "
18481848
f"so conversion still depends on the category: {sorted(missing)}")
18491849
engine.destroy()
1850+
1851+
1852+
def _uneven_vocab_engine(tp_size, load_universal=False):
1853+
"""A vocabulary head split 101 ways over `tp_size` ranks, so the shards are uneven."""
1854+
torch.manual_seed(42)
1855+
model = UnevenVocabLmHeadModel(12, 101)
1856+
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
1857+
config = {
1858+
"train_micro_batch_size_per_gpu": 1,
1859+
"zero_allow_untested_optimizer": True,
1860+
"zero_optimization": {
1861+
"stage": 1
1862+
},
1863+
"checkpoint": {
1864+
"load_universal": load_universal
1865+
},
1866+
}
1867+
if tp_size > 1:
1868+
config["tensor_parallel"] = {
1869+
"autotp_size": tp_size,
1870+
"partition_config": {
1871+
"use_default_specs":
1872+
False,
1873+
"layer_specs": [{
1874+
"patterns": [r".*lm_head\.weight$"],
1875+
"partition_type": "column",
1876+
"gather_output": True,
1877+
}],
1878+
},
1879+
}
1880+
engine, _, _, _ = deepspeed.initialize(model=model, optimizer=optimizer, config=config)
1881+
return engine
1882+
1883+
1884+
class uneven_vocab_checkpoint(DistributedFixture):
1885+
world_size = 2
1886+
1887+
def run(self, tmpdir):
1888+
from deepspeed.checkpoint.constants import AFFINE_MAP, AFFINE_MAP_PARAMS
1889+
from deepspeed.module_inject.layers import collect_autotp_universal_checkpoint_info
1890+
1891+
engine = _uneven_vocab_engine(self.world_size)
1892+
_train_steps(engine, hidden_dim=12)
1893+
1894+
# Pin that the checkpoint below is converted through the map rather than the
1895+
# vocabulary category, and that the map carries the real 51/50 split.
1896+
maps = collect_autotp_universal_checkpoint_info(engine.module)[AFFINE_MAP][AFFINE_MAP_PARAMS]
1897+
head = maps[r"^lm_head\.weight$"]
1898+
assert [head["ranks"][rank]["shard_shape"][0] for rank in sorted(head["ranks"])] == [51, 50]
1899+
1900+
tp_group = groups.get_tensor_model_parallel_group()
1901+
weight = _all_gather_cat_dim0(engine.module.lm_head.weight.detach(), tp_group).cpu()
1902+
bias = _all_gather_cat_dim0(engine.module.lm_head.bias.detach().view(-1, 1), tp_group).view(-1).cpu()
1903+
if dist.get_rank() == 0:
1904+
torch.save({"weight": weight, "bias": bias}, os.path.join(tmpdir, "uneven_vocab_reference.pt"))
1905+
1906+
_save_and_convert(engine, tmpdir)
1907+
engine.destroy()
1908+
1909+
1910+
@pytest.mark.parametrize("world_size", [1, 2], ids=["tp1", "tp2"])
1911+
class TestUnevenVocabCrossTpResume(DistributedTest):
1912+
"""A vocabulary head saved at TP2 must restore at a different TP degree.
1913+
1914+
101 rows over two ranks gives shards of 51 and 50, so the map has to carry the per-rank
1915+
extents rather than assume an even split. Restoring at TP1 then merges two unequal
1916+
shards into one tensor, which is where an even-split assumption would show up.
1917+
"""
1918+
1919+
def test_resume_from_tp2(self, uneven_vocab_checkpoint, tmpdir, world_size):
1920+
tp_size = dist.get_world_size()
1921+
engine = _uneven_vocab_engine(tp_size, load_universal=True)
1922+
engine.load_checkpoint(tmpdir, tag=UNIVERSAL_TAG, load_module_only=True)
1923+
1924+
reference = torch.load(os.path.join(tmpdir, "uneven_vocab_reference.pt"))
1925+
if tp_size > 1:
1926+
tp_group = groups.get_tensor_model_parallel_group()
1927+
weight = _all_gather_cat_dim0(engine.module.lm_head.weight.detach(), tp_group).cpu()
1928+
bias = _all_gather_cat_dim0(engine.module.lm_head.bias.detach().view(-1, 1), tp_group).view(-1).cpu()
1929+
else:
1930+
weight = engine.module.lm_head.weight.detach().cpu()
1931+
bias = engine.module.lm_head.bias.detach().cpu()
1932+
1933+
torch.testing.assert_close(weight, reference["weight"])
1934+
torch.testing.assert_close(bias, reference["bias"])
1935+
engine.destroy()

0 commit comments

Comments
 (0)