Files
wehub-resource-sync 59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:31 +08:00

617 lines
21 KiB
Python

import pytest
from tokenspeed.runtime.distributed.mapping import (
AttentionLayerMapping,
DenseLayerMapping,
Mapping,
MappingBase,
MoeLayerMapping,
_make_parallelism_group,
_make_parallelism_rank,
_resolve_parallelism_sizes,
)
# =============================================================================
# _resolve_parallelism_sizes
# =============================================================================
class TestResolveParallelismSizes:
def test_all_provided(self):
assert _resolve_parallelism_sizes(8, 4, 2) == (4, 2)
def test_all_provided_three_dims(self):
assert _resolve_parallelism_sizes(24, 2, 3, 4) == (2, 3, 4)
def test_infer_last(self):
assert _resolve_parallelism_sizes(8, 4, None) == (4, 2)
def test_infer_first(self):
assert _resolve_parallelism_sizes(8, None, 2) == (4, 2)
def test_infer_middle(self):
assert _resolve_parallelism_sizes(24, 2, None, 4) == (2, 3, 4)
def test_all_none_two_dims(self):
# First None gets world_size, rest get 1
assert _resolve_parallelism_sizes(8, None, None) == (8, 1)
def test_all_none_three_dims(self):
assert _resolve_parallelism_sizes(8, None, None, None) == (8, 1, 1)
def test_world_size_one(self):
assert _resolve_parallelism_sizes(1, None, None) == (1, 1)
assert _resolve_parallelism_sizes(1, 1, 1) == (1, 1)
def test_product_mismatch_raises(self):
with pytest.raises(AssertionError):
_resolve_parallelism_sizes(8, 4, 4)
def test_indivisible_raises(self):
with pytest.raises(AssertionError):
_resolve_parallelism_sizes(8, 3, None)
def test_provided_exceeds_world_size_raises(self):
with pytest.raises(AssertionError):
_resolve_parallelism_sizes(8, 16, None)
def test_zero_size_raises(self):
with pytest.raises(AssertionError):
_resolve_parallelism_sizes(8, 0, None)
# =============================================================================
# _make_parallelism_rank
# =============================================================================
class TestMakeParallelismRank:
def test_basic(self):
# rank=5 in [tp=4, dp=2]: tp_rank=1, dp_rank=1
assert _make_parallelism_rank(5, size=4, stride=1) == 1
assert _make_parallelism_rank(5, size=2, stride=4) == 1
def test_wraps_with_global_rank(self):
# rank=10, tp=4: tp_rank = 10 % 4 = 2
assert _make_parallelism_rank(10, size=4, stride=1) == 2
# dp_rank = (10 // 4) % 2 = 0
assert _make_parallelism_rank(10, size=2, stride=4) == 0
def test_consistent_with_group(self):
"""rank should be at position rank_in_dim within its group."""
for rank in range(24):
for size, stride in [(2, 1), (3, 2), (4, 6)]:
r = _make_parallelism_rank(rank, size, stride)
g = _make_parallelism_group(rank, size, stride)
assert g[r] == rank
# =============================================================================
# _make_parallelism_group
# =============================================================================
class TestMakeParallelismGroup:
def test_stride_1(self):
# TP group for rank 5 in [tp=4, dp=2]
assert _make_parallelism_group(5, size=4, stride=1) == (4, 5, 6, 7)
def test_stride_gt_1(self):
# DP group for rank 5 in [tp=4, dp=2]
assert _make_parallelism_group(5, size=2, stride=4) == (1, 5)
def test_size_1(self):
assert _make_parallelism_group(3, size=1, stride=1) == (3,)
def test_rank_0(self):
assert _make_parallelism_group(0, size=4, stride=1) == (0, 1, 2, 3)
def test_rank_always_in_group(self):
for rank in range(24):
for size, stride in [(2, 1), (3, 2), (4, 6)]:
group = _make_parallelism_group(rank, size, stride)
assert rank in group
# =============================================================================
# MappingBase
# =============================================================================
class TestMappingBase:
def test_immediate_rank(self):
m = MappingBase(rank=3, world_size=8)
assert m.rank == 3
assert m.world_size == 8
def test_deferred_rank(self):
m = MappingBase(world_size=8)
m.rank = 5
assert m.rank == 5
def test_access_before_set_raises(self):
m = MappingBase(world_size=8)
with pytest.raises(AssertionError, match="rank is not initialized"):
_ = m.rank
def test_set_once_raises(self):
m = MappingBase(rank=3, world_size=8)
with pytest.raises(AssertionError, match="rank is already initialized"):
m.rank = 5
def test_deferred_set_once_raises(self):
m = MappingBase(world_size=8)
m.rank = 3
with pytest.raises(AssertionError, match="rank is already initialized"):
m.rank = 5
def test_negative_rank_raises(self):
with pytest.raises(AssertionError):
MappingBase(rank=-1, world_size=8)
def test_deferred_negative_rank_raises(self):
m = MappingBase(world_size=8)
with pytest.raises(AssertionError):
m.rank = -1
def test_zero_world_size_raises(self):
with pytest.raises(AssertionError):
MappingBase(world_size=0)
# =============================================================================
# DenseLayerMapping
# =============================================================================
class TestDenseLayerMapping:
def test_tp_only(self):
m = DenseLayerMapping(rank=3, world_size=8, tp_size=8)
assert m.tp_size == 8
assert m.dp_size == 1
assert m.tp_rank == 3
assert m.dp_rank == 0
assert m.tp_group == tuple(range(8))
assert m.dp_group == (3,)
def test_dp_only(self):
m = DenseLayerMapping(rank=3, world_size=8, tp_size=1)
assert m.tp_size == 1
assert m.dp_size == 8
assert m.tp_rank == 0
assert m.dp_rank == 3
assert m.tp_group == (3,)
assert m.dp_group == tuple(range(8))
def test_combined(self):
# ws=8, tp=4, dp=2: ranks [0..3] are dp0, [4..7] are dp1
m = DenseLayerMapping(rank=5, world_size=8, tp_size=4)
assert m.tp_size == 4
assert m.dp_size == 2
assert m.tp_rank == 1
assert m.dp_rank == 1
assert m.tp_group == (4, 5, 6, 7)
assert m.dp_group == (1, 5)
def test_infer_dp(self):
m = DenseLayerMapping(rank=0, world_size=8, tp_size=4)
assert m.dp_size == 2
def test_infer_tp(self):
m = DenseLayerMapping(rank=0, world_size=8, dp_size=2)
assert m.tp_size == 4
def test_deferred_rank(self):
m = DenseLayerMapping(world_size=8, tp_size=4)
assert m.tp_size == 4
assert m.dp_size == 2
m.rank = 5
assert m.tp_rank == 1
assert m.dp_rank == 1
assert m.tp_group == (4, 5, 6, 7)
assert m.dp_group == (1, 5)
def test_deferred_rank_access_before_set_raises(self):
m = DenseLayerMapping(world_size=8, tp_size=4)
with pytest.raises(AssertionError):
_ = m.tp_rank
def test_groups_partition_world(self):
"""Every rank's TP group should partition the world into dp_size groups,
and every rank's DP group should partition the world into tp_size groups."""
ws = 8
tp = 4
mappings = [
DenseLayerMapping(rank=r, world_size=ws, tp_size=tp) for r in range(ws)
]
tp_groups = set()
dp_groups = set()
for m in mappings:
tp_groups.add(tuple(m.tp_group))
dp_groups.add(tuple(m.dp_group))
# Should have dp_size distinct TP groups and tp_size distinct DP groups
assert len(tp_groups) == 2 # dp_size
assert len(dp_groups) == 4 # tp_size
# TP groups should be disjoint and cover all ranks
all_tp_ranks = sorted(r for g in tp_groups for r in g)
assert all_tp_ranks == list(range(ws))
# DP groups should be disjoint and cover all ranks
all_dp_ranks = sorted(r for g in dp_groups for r in g)
assert all_dp_ranks == list(range(ws))
def test_global_rank_beyond_world_size(self):
"""Layer mappings accept global ranks beyond the local mapping size."""
m = DenseLayerMapping(rank=10, world_size=8, tp_size=4)
assert m.tp_rank == 2 # 10 % 4
assert m.dp_rank == 0 # (10 // 4) % 2
assert m.tp_group == (8, 9, 10, 11)
assert m.dp_group == (10, 14)
def test_invalid_rank_raises(self):
with pytest.raises(AssertionError):
DenseLayerMapping(rank=-1, world_size=8, tp_size=4)
def test_invalid_world_size_raises(self):
with pytest.raises(AssertionError):
DenseLayerMapping(rank=0, world_size=0, tp_size=1)
# =============================================================================
# AttentionLayerMapping
# =============================================================================
class TestAttentionLayerMapping:
def test_tp_only(self):
m = AttentionLayerMapping(rank=2, world_size=8, tp_size=8, cp_size=1, dp_size=1)
assert m.tp_rank == 2
assert m.cp_rank == 0
assert m.dp_rank == 0
assert m.tp_group == tuple(range(8))
assert m.cp_group == (2,)
assert m.dp_group == (2,)
def test_tp_cp(self):
# ws=8, tp=2, cp=4, dp=1
# rank layout: rank = dp_rank*(tp*cp) + cp_rank*tp + tp_rank
m = AttentionLayerMapping(rank=5, world_size=8, tp_size=2, cp_size=4, dp_size=1)
assert m.tp_rank == 1 # 5 % 2
assert m.cp_rank == 2 # (5 // 2) % 4
assert m.dp_rank == 0 # 5 // 8
assert m.tp_group == (4, 5)
assert m.cp_group == (1, 3, 5, 7)
def test_tp_cp_dp(self):
# ws=16, tp=2, cp=2, dp=4
m = AttentionLayerMapping(
rank=7, world_size=16, tp_size=2, cp_size=2, dp_size=4
)
assert m.tp_rank == 1 # 7 % 2
assert m.cp_rank == 1 # (7 // 2) % 2
assert m.dp_rank == 1 # 7 // 4
assert m.tp_group == (6, 7)
assert m.cp_group == (5, 7)
assert m.dp_group == (3, 7, 11, 15)
def test_infer_cp(self):
m = AttentionLayerMapping(rank=0, world_size=16, tp_size=2, dp_size=4)
assert m.cp_size == 2
def test_infer_dp(self):
m = AttentionLayerMapping(rank=0, world_size=16, tp_size=2, cp_size=4)
assert m.dp_size == 2
def test_deferred_rank(self):
m = AttentionLayerMapping(world_size=16, tp_size=2, cp_size=2, dp_size=4)
assert m.tp_size == 2
assert m.cp_size == 2
assert m.dp_size == 4
m.rank = 7
assert m.tp_rank == 1
assert m.cp_rank == 1
assert m.dp_rank == 1
assert m.tp_group == (6, 7)
assert m.cp_group == (5, 7)
assert m.dp_group == (3, 7, 11, 15)
def test_cp_size_1_matches_dense(self):
"""With cp_size=1, AttentionLayerMapping should produce the same
tp/dp ranks and groups as DenseLayerMapping."""
ws = 8
tp = 4
for r in range(ws):
attn = AttentionLayerMapping(rank=r, world_size=ws, tp_size=tp, cp_size=1)
dense = DenseLayerMapping(rank=r, world_size=ws, tp_size=tp)
assert attn.tp_rank == dense.tp_rank
assert attn.dp_rank == dense.dp_rank
assert attn.tp_group == dense.tp_group
assert attn.dp_group == dense.dp_group
def test_groups_partition_world(self):
"""All three group types should partition the world correctly."""
ws = 24
tp, cp, dp = 2, 3, 4
mappings = [
AttentionLayerMapping(
rank=r, world_size=ws, tp_size=tp, cp_size=cp, dp_size=dp
)
for r in range(ws)
]
for attr, expected_count in [
("tp_group", ws // tp),
("cp_group", ws // cp),
("dp_group", ws // dp),
]:
groups = set()
for m in mappings:
groups.add(tuple(getattr(m, attr)))
assert (
len(groups) == expected_count
), f"{attr}: expected {expected_count} groups, got {len(groups)}"
all_ranks = sorted(r for g in groups for r in g)
assert all_ranks == list(range(ws)), f"{attr}: groups don't cover all ranks"
def test_all_ranks_consistent(self):
"""For every rank, rank == dp_rank * (tp*cp) + cp_rank * tp + tp_rank."""
ws = 24
tp, cp, dp = 2, 3, 4
for r in range(ws):
m = AttentionLayerMapping(
rank=r, world_size=ws, tp_size=tp, cp_size=cp, dp_size=dp
)
reconstructed = m.dp_rank * (tp * cp) + m.cp_rank * tp + m.tp_rank
assert reconstructed == r, f"rank={r}: reconstructed={reconstructed}"
def test_invalid_product_raises(self):
with pytest.raises(AssertionError):
AttentionLayerMapping(
rank=0, world_size=16, tp_size=2, cp_size=3, dp_size=4
)
# =============================================================================
# MoeLayerMapping
# =============================================================================
class TestMoeLayerMapping:
def test_tp_only(self):
m = MoeLayerMapping(rank=3, world_size=8, tp_size=8, ep_size=1, dp_size=1)
assert m.tp_rank == 3
assert m.ep_rank == 0
assert m.dp_rank == 0
assert m.tp_group == tuple(range(8))
assert m.ep_group == (3,)
assert m.dp_group == (3,)
def test_ep_only(self):
m = MoeLayerMapping(rank=3, world_size=8, tp_size=1, ep_size=8, dp_size=1)
assert m.tp_rank == 0
assert m.ep_rank == 3
assert m.dp_rank == 0
assert m.tp_group == (3,)
assert m.ep_group == tuple(range(8))
assert m.dp_group == (3,)
def test_tp_ep(self):
# ws=8, tp=2, ep=4, dp=1
m = MoeLayerMapping(rank=5, world_size=8, tp_size=2, ep_size=4, dp_size=1)
assert m.tp_rank == 1 # 5 % 2
assert m.ep_rank == 2 # (5 // 2) % 4
assert m.dp_rank == 0 # 5 // 8
assert m.tp_group == (4, 5)
assert m.ep_group == (1, 3, 5, 7)
def test_tp_ep_dp(self):
# ws=16, tp=2, ep=2, dp=4
m = MoeLayerMapping(rank=7, world_size=16, tp_size=2, ep_size=2, dp_size=4)
assert m.tp_rank == 1 # 7 % 2
assert m.ep_rank == 1 # (7 // 2) % 2
assert m.dp_rank == 1 # 7 // 4
assert m.tp_group == (6, 7)
assert m.ep_group == (5, 7)
assert m.dp_group == (3, 7, 11, 15)
def test_infer_ep(self):
m = MoeLayerMapping(rank=0, world_size=16, tp_size=2, dp_size=4)
assert m.ep_size == 2
def test_infer_dp(self):
m = MoeLayerMapping(rank=0, world_size=16, tp_size=2, ep_size=4)
assert m.dp_size == 2
def test_deferred_rank(self):
m = MoeLayerMapping(world_size=16, tp_size=2, ep_size=2, dp_size=4)
assert m.tp_size == 2
assert m.ep_size == 2
assert m.dp_size == 4
m.rank = 7
assert m.tp_rank == 1
assert m.ep_rank == 1
assert m.dp_rank == 1
assert m.tp_group == (6, 7)
assert m.ep_group == (5, 7)
assert m.dp_group == (3, 7, 11, 15)
def test_ep_size_1_matches_dense(self):
"""With ep_size=1, MoeLayerMapping should produce the same
tp/dp ranks and groups as DenseLayerMapping."""
ws = 8
tp = 4
for r in range(ws):
moe = MoeLayerMapping(rank=r, world_size=ws, tp_size=tp, ep_size=1)
dense = DenseLayerMapping(rank=r, world_size=ws, tp_size=tp)
assert moe.tp_rank == dense.tp_rank
assert moe.dp_rank == dense.dp_rank
assert moe.tp_group == dense.tp_group
assert moe.dp_group == dense.dp_group
def test_structure_mirrors_attention(self):
"""MoeLayerMapping(tp, ep, dp) should have the same rank/group structure
as AttentionLayerMapping(tp, cp, dp) when sizes match, since both are
3-dim inner-to-outer layouts."""
ws = 24
tp, middle, dp = 2, 3, 4
for r in range(ws):
attn = AttentionLayerMapping(
rank=r, world_size=ws, tp_size=tp, cp_size=middle, dp_size=dp
)
moe = MoeLayerMapping(
rank=r, world_size=ws, tp_size=tp, ep_size=middle, dp_size=dp
)
assert attn.tp_rank == moe.tp_rank
assert attn.cp_rank == moe.ep_rank
assert attn.dp_rank == moe.dp_rank
assert attn.tp_group == moe.tp_group
assert attn.cp_group == moe.ep_group
assert attn.dp_group == moe.dp_group
def test_groups_partition_world(self):
"""All three group types should partition the world correctly."""
ws = 24
tp, ep, dp = 2, 3, 4
mappings = [
MoeLayerMapping(rank=r, world_size=ws, tp_size=tp, ep_size=ep, dp_size=dp)
for r in range(ws)
]
for attr, expected_count in [
("tp_group", ws // tp),
("ep_group", ws // ep),
("dp_group", ws // dp),
]:
groups = set()
for m in mappings:
groups.add(tuple(getattr(m, attr)))
assert (
len(groups) == expected_count
), f"{attr}: expected {expected_count} groups, got {len(groups)}"
all_ranks = sorted(r for g in groups for r in g)
assert all_ranks == list(range(ws)), f"{attr}: groups don't cover all ranks"
def test_all_ranks_consistent(self):
"""For every rank, rank == dp_rank * (tp*ep) + ep_rank * tp + tp_rank."""
ws = 24
tp, ep, dp = 2, 3, 4
for r in range(ws):
m = MoeLayerMapping(
rank=r, world_size=ws, tp_size=tp, ep_size=ep, dp_size=dp
)
reconstructed = m.dp_rank * (tp * ep) + m.ep_rank * tp + m.tp_rank
assert reconstructed == r, f"rank={r}: reconstructed={reconstructed}"
def test_invalid_product_raises(self):
with pytest.raises(AssertionError):
MoeLayerMapping(rank=0, world_size=16, tp_size=2, ep_size=3, dp_size=4)
# =============================================================================
# Mapping (global)
# =============================================================================
class TestMapping:
def test_parallel_groups(self):
m = Mapping(
rank=3,
world_size=8,
attn_tp_size=4,
dense_tp_size=8,
moe_tp_size=2,
moe_ep_size=4,
)
assert m.attn.tp_rank == 3
assert m.dense.tp_rank == 3
assert m.moe.ep_rank == 1
def test_layer_mappings_use_global_rank(self):
"""Layer mappings should use the global rank so groups contain global ranks."""
m = Mapping(rank=10, world_size=16, dense_tp_size=4)
# rank=10, world_size=16, dense tp=4, dp=4
assert m.dense.tp_group == (8, 9, 10, 11)
assert m.dense.dp_group == (2, 6, 10, 14)
# All group members should be valid global ranks
assert all(0 <= r < 16 for r in m.dense.tp_group)
assert all(0 <= r < 16 for r in m.dense.dp_group)
def test_nprocs_per_node_and_nnodes(self):
m = Mapping(rank=0, world_size=16, nprocs_per_node=8, nnodes=2)
assert m.nprocs_per_node == 8
assert m.nnodes == 2
def test_nprocs_per_node_infer_nnodes(self):
m = Mapping(rank=0, world_size=16, nprocs_per_node=8)
assert m.nprocs_per_node == 8
assert m.nnodes == 2
def test_nnodes_infer_nprocs(self):
m = Mapping(rank=0, world_size=16, nnodes=4)
assert m.nprocs_per_node == 4
assert m.nnodes == 4
def test_nprocs_default_single_node(self):
m = Mapping(rank=0, world_size=8)
assert m.nprocs_per_node == 8
assert m.nnodes == 1
def test_nprocs_nnodes_mismatch_raises(self):
with pytest.raises(AssertionError):
Mapping(rank=0, world_size=16, nprocs_per_node=3, nnodes=2)
def test_deferred_rank(self):
"""Create Mapping without rank, set it later, verify propagation."""
m = Mapping(
world_size=8,
attn_tp_size=4,
dense_tp_size=8,
moe_tp_size=2,
moe_ep_size=4,
)
# Sizes resolved eagerly
assert m.attn.tp_size == 4
assert m.dense.tp_size == 8
assert m.moe.tp_size == 2
assert m.moe.ep_size == 4
# Set rank later
m.rank = 3
assert m.rank == 3
# Propagated to sub-mappings
assert m.attn.rank == 3
assert m.dense.rank == 3
assert m.moe.rank == 3
# Derived ranks work
assert m.attn.tp_rank == 3
assert m.dense.tp_rank == 3
assert m.moe.ep_rank == 1
def test_deferred_rank_access_before_set_raises(self):
m = Mapping(world_size=8, dense_tp_size=4)
with pytest.raises(AssertionError, match="rank is not initialized"):
_ = m.rank
with pytest.raises(AssertionError, match="rank is not initialized"):
_ = m.dense.tp_rank
def test_deferred_rank_set_once_raises(self):
m = Mapping(world_size=8, dense_tp_size=4)
m.rank = 3
with pytest.raises(AssertionError, match="rank is already initialized"):
m.rank = 5
if __name__ == "__main__":
pytest.main([__file__, "-v"])