Skip to content

Commit bce0adf

Browse files
Achyuthan-Sdelock
andauthored
Carry the affine scale on the replicated map, not the split (#8477)
Follow-up to #8385. The scale move answering @delock's question was pushed after the merge queue had already snapshotted the branch, so it did not land with the rest. The layouts that pre-divide a value hold it whole on every rank: Yuan's o_proj and the last conv layer both replicate the bias divided by the world size, so the all-reduced sum adds it exactly once. The weight beside them is what gets split, and it is unscaled — so no in-tree layout scales a split, and the split constructors no longer take the argument. Adds a test covering the replicated case. Signed-off-by: Achyuthan Sivasankar <achyuthan.sivasankar@gmail.com> Co-authored-by: Ma, Guokai <guokai.ma@gmail.com>
1 parent 080eb6b commit bce0adf

2 files changed

Lines changed: 28 additions & 5 deletions

File tree

deepspeed/checkpoint/affine.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -356,11 +356,16 @@ def _row_major_strides(shape):
356356
return tuple(strides)
357357

358358

359-
def replicated_map(shape, tp_degree):
359+
def replicated_map(shape, tp_degree, scale=1.0):
360360
"""Every rank holds the whole parameter.
361361
362362
One piece, named by every rank, which is what lets a converter read it from whichever
363363
rank is cheapest rather than from a designated owner.
364+
365+
``scale`` belongs here rather than on a split, because the layouts that pre-divide a
366+
value hold it whole on every rank: a row-parallel layer replicates its bias divided by
367+
the world size so that summing the all-reduced outputs adds it exactly once. The weight
368+
beside it is split and unscaled.
364369
"""
365370
shape = tuple(shape)
366371
strides = _row_major_strides(shape)
@@ -371,7 +376,8 @@ def replicated_map(shape, tp_degree):
371376
source_strides=strides,
372377
dest_offset=0,
373378
dest_strides=strides,
374-
locations=ranks)
379+
locations=ranks,
380+
scale=scale)
375381
]
376382
return ParamAffineMap(logical_shape=shape,
377383
shard_shapes={rank: shape
@@ -380,7 +386,7 @@ def replicated_map(shape, tp_degree):
380386
for rank in ranks})
381387

382388

383-
def contiguous_split_map(shape, per_rank_sizes, partition_dim, scale=1.0):
389+
def contiguous_split_map(shape, per_rank_sizes, partition_dim):
384390
"""Each rank holds one contiguous block along ``partition_dim``.
385391
386392
Covers row-parallel and column-parallel layers alike: they differ only in which axis
@@ -402,8 +408,7 @@ def contiguous_split_map(shape, per_rank_sizes, partition_dim, scale=1.0):
402408
source_strides=source_strides,
403409
dest_offset=0,
404410
dest_strides=_row_major_strides(shard_shape),
405-
locations=[rank],
406-
scale=scale)
411+
locations=[rank])
407412
]
408413
start += size
409414
return ParamAffineMap(logical_shape=shape, shard_shapes=shard_shapes, pieces_by_rank=pieces_by_rank)

tests/unit/checkpoint/test_affine_shard_map.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,21 @@ def test_unscaled_optimizer_state_still_converts():
567567
shards = {rank: torch.ones(4, dtype=torch.float64) for rank in range(2)}
568568
for scale_power in (1, -1, -2):
569569
assert torch.equal(affine_map.rebuild(shards, scale_power), torch.ones(4, dtype=torch.float64))
570+
571+
572+
def test_replicated_map_carries_the_scale():
573+
"""The layouts that pre-divide a value replicate it whole, so the scale belongs here.
574+
575+
A row-parallel layer divides its bias by the world size and gives every rank the whole
576+
thing, so summing the all-reduced outputs adds the bias once. The weight beside it is
577+
split and unscaled, which is why the split constructors take no scale.
578+
"""
579+
world_size = 4
580+
full_bias = torch.randn(5, dtype=torch.float64)
581+
affine_map = replicated_map((5, ), world_size, scale=1.0 / world_size)
582+
affine_map.validate_coverage()
583+
584+
shards = {rank: affine_map.extract(full_bias, rank) for rank in range(world_size)}
585+
assert torch.equal(shards[0], full_bias / world_size)
586+
assert torch.equal(affine_map.rebuild(shards), full_bias)
587+
assert affine_map.pieces_by_rank[0][0].locations == frozenset(range(world_size))

0 commit comments

Comments
 (0)