Files
vLLM/tests/distributed/test_weight_transfer_m2n.py
6b6a6c5c98 [RL] Support per-parameter source layouts for NCCL M2N (#60934)
Signed-off-by: pkousha <pkousha@nvidia.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
Co-authored-by: aoshen02 <aoshen@inferact.ai>
2026-10-16 21:41:17 -04:00

1796 lines
62 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Unit tests for the NCCL M2N weight transfer backend.
These cover how a transfer is *described* — layout encoding, whole-plan
validation, and the vLLM-owned source contract. The transfer itself needs the
M2N runtime and multiple GPUs, so it is exercised separately.
"""
import logging
from contextlib import contextmanager
from functools import wraps
from types import MethodType, SimpleNamespace
from unittest.mock import Mock, call
import pybase64 as base64
import pytest
import ray
import torch
from vllm.config.weight_transfer import WeightTransferConfig
from vllm.distributed.weight_transfer import (
WeightTransferEngineFactory,
WeightTransferTrainerFactory,
)
from vllm.distributed.weight_transfer.m2n_common import (
M2N_WIRE_SCHEMA_VERSION,
REPLICATE,
REPLICATED,
M2NLayout,
M2NMesh,
M2NParamMeta,
M2NWireParam,
check_complete_plan_agreement,
check_data_plane_agreement,
check_plan_limits,
check_runtime_ready_agreement,
check_source_plan_agreement,
create_m2n_communicator,
expected_local_shape,
resolve_layout,
source_plan_digest,
)
from vllm.distributed.weight_transfer.m2n_engine import (
M2NWeightTransferEngine,
M2NWeightTransferInitInfo,
M2NWeightTransferUpdateInfo,
)
from vllm.distributed.weight_transfer.m2n_layout import (
M2NDestination,
resolve_parameter_destinations,
)
from vllm.distributed.weight_transfer.m2n_source import (
M2NManifestEntry,
M2NWeightSource,
ManifestM2NWeightSource,
mesh_from_tensor,
placements_from_tensor,
)
from vllm.distributed.weight_transfer.m2n_trainer import (
M2NTrainerInitInfo,
M2NTrainerWeightTransferEngine,
)
from vllm.distributed.weight_transfer.nccl_common import (
stateless_init_metadata_group,
)
from vllm.model_executor.layers.linear import (
ColumnParallelLinear,
RowParallelLinear,
)
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
from vllm.platforms import current_platform
from vllm.utils.network_utils import get_open_port
@pytest.mark.parametrize(
"engine_type,group_attribute",
[
(M2NWeightTransferEngine, "model_update_group"),
(M2NTrainerWeightTransferEngine, "group"),
],
)
def test_shutdown_releases_resources_once(monkeypatch, engine_type, group_attribute):
engine = object.__new__(engine_type)
handle = Mock()
engine._handle = handle
group = Mock()
setattr(engine, group_attribute, group)
synchronize = Mock()
monkeypatch.setattr(torch.accelerator, "synchronize", synchronize)
if engine_type is M2NWeightTransferEngine:
engine._parameter_destinations = [Mock()]
else:
engine._dst_placements = [REPLICATED]
engine._metadata_group = Mock()
engine.shutdown()
engine.shutdown()
synchronize.assert_called_once_with()
handle.destroy.assert_called_once_with()
group.abort.assert_called_once_with()
group.destroy.assert_not_called()
assert engine._handle is None
assert getattr(engine, group_attribute) is None
if engine_type is M2NWeightTransferEngine:
assert engine._parameter_destinations == []
else:
assert engine._dst_placements == []
assert engine._metadata_group is None
@pytest.mark.parametrize(
"engine_type,group_attribute",
[
(M2NWeightTransferEngine, "model_update_group"),
(M2NTrainerWeightTransferEngine, "group"),
],
)
@pytest.mark.parametrize("failure", ["synchronize", "handle"])
def test_shutdown_attempts_all_cleanup_after_failure(
monkeypatch, engine_type, group_attribute, failure
):
engine = object.__new__(engine_type)
handle = Mock()
group = Mock()
engine._handle = handle
setattr(engine, group_attribute, group)
if engine_type is M2NTrainerWeightTransferEngine:
engine._metadata_group = Mock()
synchronize = Mock()
if failure == "synchronize":
synchronize.side_effect = RuntimeError("synchronize failed")
else:
handle.destroy.side_effect = RuntimeError("handle destroy failed")
monkeypatch.setattr(torch.accelerator, "synchronize", synchronize)
with pytest.raises(RuntimeError, match=failure):
engine.shutdown()
synchronize.assert_called_once_with()
handle.destroy.assert_called_once_with()
group.abort.assert_called_once_with()
assert engine._handle is None
assert getattr(engine, group_attribute) is None
if engine_type is M2NTrainerWeightTransferEngine:
assert engine._metadata_group is None
engine.shutdown()
synchronize.assert_called_once_with()
handle.destroy.assert_called_once_with()
group.abort.assert_called_once_with()
class _BufferLike:
"""Minimal nccl4py buffer protocol without torch-only methods."""
def __init__(self, dtype=torch.float32, shape=(4,)):
self.dtype = dtype
self.shape = shape
def data_ptr(self):
return 1234
class _AllocatorAwareBuffer(_BufferLike):
def __init__(self):
super().__init__()
self.record_stream = Mock()
class _FakeDistTensor:
def __init__(self, buffer, local_shape=None, dtype=None, **kwargs):
del kwargs
self.local_shape = (
tuple(local_shape) if local_shape is not None else tuple(buffer.shape)
)
self.dtype = dtype if dtype is not None else buffer.dtype
def _fake_m2n():
return SimpleNamespace(
DistTensor=_FakeDistTensor,
Mesh=lambda dims, start_rank=0: (tuple(dims), start_rank),
Replicate=lambda: REPLICATE,
Shard=lambda dim: dim,
reshard=Mock(),
)
class TestLayout:
def test_replicated_splits_nothing(self):
"""A replicated tensor imposes no divisibility constraint. The shape
dims are deliberately coprime with the mesh size: a layout that actually
split the tensor would reject them."""
mesh = M2NMesh((2, 2), start_rank=1)
resolved_mesh, placements = resolve_layout(mesh, REPLICATED)
assert resolved_mesh.dims == (4, 1)
assert placements == (REPLICATE, 0)
def test_replicated_keeps_the_same_ranks(self):
"""Replication re-factors the mesh to get a size-1 axis for its no-op
shard. That is only sound if it still covers exactly the same GPUs."""
mesh = M2NMesh((2, 3), start_rank=4)
resolved_mesh, _ = resolve_layout(mesh, REPLICATED)
assert resolved_mesh.size == mesh.size
assert resolved_mesh.start_rank == mesh.start_rank
def test_sharded_keeps_its_own_factorization(self):
"""Rank order decides who owns which shard, so a sharded tensor must
not be re-factored the way a replicated one is."""
mesh = M2NMesh((2, 3), start_rank=4)
resolved_mesh, placements = resolve_layout(mesh, (REPLICATE, 0))
assert resolved_mesh == mesh
assert placements == (REPLICATE, 0)
@pytest.mark.parametrize(
("placements", "match"),
[((REPLICATE, 1), "tensor dim 1"), ((REPLICATE, 0), "divide evenly")],
)
def test_layout_validation_always_returns_shape_or_raises(self, placements, match):
meta = M2NParamMeta(
"w",
torch.float32,
(5,),
M2NLayout(M2NMesh((1, 2), 0), placements),
)
with pytest.raises(ValueError, match=match):
expected_local_shape(meta)
def test_cross_sharded_fanout_over_capacity_is_rejected(self):
src = (M2NMesh((1, 2), 0), (REPLICATE, 0))
dst = (M2NMesh((1, 65), 2), (REPLICATE, 1))
with pytest.raises(ValueError, match=r"parameter 'w'.*MAX_TARGETS=64"):
check_plan_limits(src, dst, "w", (2, 65))
class TestSourceLayout:
def test_plain_tensor_is_replicated_across_trainer_ranks(self):
"""A tensor with no DTensor metadata is the same on every trainer rank,
so the source describes it as replicated over all of them."""
assert placements_from_tensor(torch.zeros(4)) is REPLICATED
assert mesh_from_tensor(torch.zeros(4), 4) == M2NMesh((4, 1), 0)
def test_manifest_preserves_order_and_materializes_lazily(self):
calls: list[int] = []
def provider(index, value):
def get_tensor():
calls.append(index)
return value
return get_tensor
source = ManifestM2NWeightSource(
[
M2NManifestEntry(
"second",
torch.float32,
(4,),
M2NLayout(M2NMesh((1, 1), 0), REPLICATED),
provider(0, torch.ones(4)),
),
M2NManifestEntry(
"first",
torch.float32,
(2,),
M2NLayout(M2NMesh((1, 1), 0), REPLICATED),
provider(1, torch.full((2,), 2.0)),
),
]
)
assert [meta.name for meta in source.metadata()] == ["second", "first"]
assert calls == []
assert [name for name, _ in source] == ["second", "first"]
assert calls == [0, 1]
def test_trainer_boundary_rejects_duplicate_manifest_names(self):
entry = M2NManifestEntry(
"w",
torch.float32,
(4,),
M2NLayout(M2NMesh((1, 1), 0), REPLICATED),
lambda: torch.zeros(4),
)
source = ManifestM2NWeightSource([entry, entry])
engine = M2NTrainerWeightTransferEngine(
client=Mock(), source=source, is_sender=False
)
with pytest.raises(ValueError, match="duplicate parameter 'w'"):
engine._prepare_source_plan(source, num_trainer_ranks=1)
@pytest.mark.parametrize(
("dtype", "shape"),
[(torch.complex64, (4,)), (torch.float32, (2, 2, 2, 2))],
)
def test_transport_capabilities_rejected_during_initialization(self, dtype, shape):
source = ManifestM2NWeightSource(
[
M2NManifestEntry(
"w",
dtype,
shape,
M2NLayout(M2NMesh((1, 1), 0), REPLICATED),
lambda: _BufferLike(dtype, shape),
)
]
)
engine = M2NTrainerWeightTransferEngine(
client=Mock(), source=source, is_sender=False
)
with pytest.raises(ValueError, match=r"parameter 'w'.*(dtype|rank)"):
engine._prepare_source_plan(source, num_trainer_ranks=1)
@pytest.mark.parametrize("placements", [(REPLICATE, 1), (REPLICATE, 0)])
def test_layout_geometry_rejected_during_initialization(self, placements):
source = ManifestM2NWeightSource(
[
M2NManifestEntry(
"w",
torch.float32,
(5,),
M2NLayout(M2NMesh((1, 2), 0), placements),
lambda: _BufferLike(shape=(5,)),
)
]
)
engine = M2NTrainerWeightTransferEngine(
client=Mock(), source=source, is_sender=False
)
with pytest.raises(ValueError, match=r"parameter 'w'.*source layout"):
engine._prepare_source_plan(source, num_trainer_ranks=2)
def test_raw_pointer_invalid_layout_rejected_during_initialization(self):
source = ManifestM2NWeightSource(
[
M2NManifestEntry(
"pointer",
torch.float32,
(5,),
M2NLayout(M2NMesh((1, 2), 0), (REPLICATE, 0)),
lambda: 1234,
)
]
)
engine = M2NTrainerWeightTransferEngine(
client=Mock(), source=source, is_sender=False
)
with pytest.raises(ValueError, match=r"parameter 'pointer'.*divide evenly"):
engine._prepare_source_plan(source, num_trainer_ranks=2)
class TestPreflightAgreement:
def test_source_error_is_reported_before_data_plane(self):
group = Mock(world_size=2)
group.all_gather_obj.return_value = [
{
"phase": "source",
"digest": None,
"declared_digest": None,
"error": "ValueError: bad local manifest",
},
{
"phase": "source",
"digest": None,
"declared_digest": None,
"error": "ValueError: bad local manifest",
},
]
with pytest.raises(RuntimeError, match="bad local manifest"):
check_source_plan_agreement(
group, [], local_error="ValueError: bad local manifest"
)
def test_data_plane_identity_must_match(self):
group = Mock(world_size=2)
def gather(envelope):
peer = dict(envelope)
peer["mode"] = "uid"
peer["uid_digest"] = "different"
return [envelope, peer]
group.all_gather_obj.side_effect = gather
with pytest.raises(RuntimeError, match="data-plane identity disagrees"):
check_data_plane_agreement(group, None)
def test_runtime_error_is_shared_before_nccl4py(self):
group = Mock(world_size=2)
group.all_gather_obj.return_value = [
{"phase": "runtime_ready", "error": None},
{"phase": "runtime_ready", "error": "ImportError: no nccl_m2n"},
]
with pytest.raises(RuntimeError, match="no nccl_m2n"):
check_runtime_ready_agreement(group, None)
def test_complete_plan_error_is_shared_before_first_reshard(self):
group = Mock(world_size=2)
group.all_gather_obj.return_value = [
{"phase": "complete_plan", "digest": "same", "error": None},
{
"phase": "complete_plan",
"digest": None,
"error": "ValueError: bad destination",
},
]
with pytest.raises(RuntimeError, match="bad destination"):
check_complete_plan_agreement(
group,
"source",
M2NMesh((1, 1), 1),
[REPLICATED],
)
def test_complete_plan_digest_must_match(self):
group = Mock(world_size=2)
def gather(envelope):
peer = dict(envelope)
peer["digest"] = "different"
return [envelope, peer]
group.all_gather_obj.side_effect = gather
with pytest.raises(RuntimeError, match="complete-plan digest disagrees"):
check_complete_plan_agreement(
group,
"source",
M2NMesh((1, 1), 1),
[REPLICATED],
)
class TestWireTypes:
@staticmethod
def _param(
name="w",
dtype_name="bfloat16",
shape=(16, 16),
src_mesh_dims=(1, 1),
src_placements=None,
):
return M2NWireParam(
name=name,
dtype_name=dtype_name,
shape=shape,
src_mesh_dims=src_mesh_dims,
src_placements=src_placements,
)
def _init_info(self, **overrides):
param = self._param()
fields = dict(
schema_version=M2N_WIRE_SCHEMA_VERSION,
master_address="127.0.0.1",
master_port=1234,
rank_offset=1,
world_size=3,
dst_mesh_dims=[2, 1],
source_digest=source_plan_digest([param]),
params=[param.to_dict()],
)
fields.update(overrides)
if "params" in overrides and "source_digest" not in overrides:
params = [M2NWireParam.from_dict(value) for value in fields["params"]]
fields["source_digest"] = source_plan_digest(params)
return M2NWeightTransferInitInfo(**fields)
def test_accepts_a_consistent_plan(self):
assert [param.name for param in self._init_info().parse_wire_params()] == ["w"]
def test_m2n_communicator_uses_nccl4py_uid_under_unpinned_env(self, monkeypatch):
nccl = Mock()
group = Mock(rank=1, world_size=3)
unique_id = object()
communicator = object()
nccl.UniqueId.from_bytes.return_value = unique_id
active = False
@contextmanager
def unpinned():
nonlocal active
active = True
try:
yield
finally:
active = False
def init(**kwargs):
assert active
assert kwargs == {"nranks": 3, "rank": 1, "unique_id": unique_id}
return communicator
nccl.Communicator.init.side_effect = init
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_common.unpinned_nccl_env",
unpinned,
)
result = create_m2n_communicator(nccl, group, b"shared-id")
assert result is communicator
nccl.UniqueId.from_bytes.assert_called_once_with(b"shared-id")
nccl.Communicator.init.assert_called_once_with(
nranks=3, rank=1, unique_id=unique_id
)
group.broadcast_obj.assert_not_called()
@pytest.mark.parametrize(
"mutation",
[
lambda value: value.pop("shape"),
lambda value: value.update(extra="field"),
lambda value: value.update(src_mesh_dims=[1]),
lambda value: value.update(src_placements=[0, 1]),
],
)
def test_malformed_wire_parameter_rejected(self, mutation):
value = self._param().to_dict()
mutation(value)
with pytest.raises((TypeError, ValueError)):
M2NWireParam.from_dict(value)
def test_source_digest_authenticates_ordered_plan(self):
params = [self._param("a"), self._param("b")]
info = self._init_info(
params=[param.to_dict() for param in params],
source_digest=source_plan_digest(list(reversed(params))),
)
with pytest.raises(ValueError, match="source digest"):
info.parse_wire_params()
@pytest.fixture
def worker_init_env(self, monkeypatch):
metadata_group = Mock(rank=1, world_size=3)
metadata_group.all_gather_obj.side_effect = lambda envelope: [envelope] * 3
init_metadata = Mock(return_value=metadata_group)
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_engine.worker_init_metadata_group",
init_metadata,
)
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_engine.import_m2n", Mock()
)
handle = Mock()
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_engine.prepare_m2n_local_runtime",
Mock(return_value=(Mock(), 0, handle)),
)
communicator = Mock()
make_communicator = Mock(return_value=communicator)
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_engine.create_m2n_communicator",
make_communicator,
)
publish = Mock(return_value=[])
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_engine."
"publish_destination_placements",
publish,
)
vllm_config = SimpleNamespace(
parallel_config=Mock(pipeline_parallel_size=1),
model_config=SimpleNamespace(quantization=None),
)
engine = M2NWeightTransferEngine(
WeightTransferConfig(backend="nccl_m2n"),
vllm_config,
torch.device("cuda:0"),
torch.nn.Module(),
)
return SimpleNamespace(
engine=engine,
metadata_group=metadata_group,
init_metadata=init_metadata,
handle=handle,
communicator=communicator,
make_communicator=make_communicator,
publish=publish,
)
def test_trainer_disagreement_is_rejected_before_nccl4py(self, worker_init_env):
group = worker_init_env.metadata_group
def gather(envelope):
peer = dict(envelope)
peer["digest"] = "different"
return [envelope, peer, envelope]
group.all_gather_obj.side_effect = gather
with pytest.raises(RuntimeError, match="source digest disagrees"):
worker_init_env.engine.init_transfer_engine(self._init_info())
worker_init_env.make_communicator.assert_not_called()
def test_worker_rejects_active_reinit_but_allows_init_after_shutdown(
self, monkeypatch, worker_init_env
):
engine = worker_init_env.engine
old_handle = Mock()
old_group = Mock()
engine._handle = old_handle
engine.model_update_group = old_group
with pytest.raises(RuntimeError, match="already initialized"):
engine.init_transfer_engine(self._init_info(params=[]))
worker_init_env.init_metadata.assert_not_called()
monkeypatch.setattr(torch.accelerator, "synchronize", Mock())
engine.shutdown()
engine.init_transfer_engine(self._init_info(params=[]))
assert engine._handle is worker_init_env.handle
assert engine.model_update_group is worker_init_env.communicator
def test_partial_worker_initialization_releases_transport_resources(
self, worker_init_env
):
worker_init_env.publish.side_effect = RuntimeError("late init failure")
with pytest.raises(RuntimeError, match="late init failure"):
worker_init_env.engine.init_transfer_engine(self._init_info(params=[]))
worker_init_env.communicator.abort.assert_called_once_with()
worker_init_env.communicator.destroy.assert_not_called()
worker_init_env.handle.destroy.assert_called_once_with()
assert worker_init_env.engine.model_update_group is None
assert worker_init_env.engine._handle is None
def test_destination_mesh_must_cover_the_workers(self):
"""The trainer declares the inference mesh, so one that does not cover
the workers is a config error — and it has to fail the init RPC, since
a mismatched mesh would otherwise surface as a hung collective."""
with pytest.raises(ValueError, match="dst_mesh_dims"):
self._init_info(dst_mesh_dims=[3, 1]) # 3 != the 2 workers
def test_world_must_hold_a_trainer_and_a_worker(self):
with pytest.raises(ValueError, match="rank_offset"):
self._init_info(rank_offset=3, world_size=3)
@pytest.mark.parametrize("dtype_name", ["not_a_dtype", "Tensor"])
def test_invalid_dtype_name_names_parameter(self, dtype_name):
engine = object.__new__(M2NWeightTransferEngine)
param = self._param(dtype_name=dtype_name)
with pytest.raises(ValueError, match=r"parameter 'w'.*dtype"):
engine._prepare_source_plan((param,), num_trainer_ranks=1)
def _init_32_to_4_plan(self, monkeypatch, destination):
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_engine."
"resolve_parameter_destinations",
lambda *args, **kwargs: [destination],
)
engine = object.__new__(M2NWeightTransferEngine)
engine.parallel_config = Mock(pipeline_parallel_size=1)
engine.model_config = Mock(quantization=None)
engine.model = torch.nn.Module()
param = self._param(
shape=(32, 8),
src_mesh_dims=(1, 32),
src_placements=(REPLICATE, 0),
)
engine._prepare_source_plan((param,), num_trainer_ranks=32)
engine._prepare_destination_plan(
self._init_info(
rank_offset=32,
world_size=36,
dst_mesh_dims=[1, 4],
params=[param.to_dict()],
)
)
def test_sharded_destination_within_capacity_initializes(self, monkeypatch):
destination = M2NDestination("w", (REPLICATE, 0), torch.empty(8, 8))
self._init_32_to_4_plan(monkeypatch, destination)
def test_cross_sharded_destination_over_capacity_rejected_at_init(
self, monkeypatch
):
destination = M2NDestination("w", (REPLICATE, 1), torch.empty(32, 2))
with pytest.raises(ValueError, match=r"parameter 'w'.*MAX_SOURCES=16"):
self._init_32_to_4_plan(monkeypatch, destination)
def test_replicated_destination_over_capacity_rejected_at_init(self, monkeypatch):
destination = M2NDestination("w", REPLICATED, None)
with pytest.raises(ValueError, match=r"parameter 'w'.*MAX_SOURCES=16"):
self._init_32_to_4_plan(monkeypatch, destination)
def test_invalid_destination_placement_rejected_at_init(self, monkeypatch):
destination = M2NDestination("w", (REPLICATE, 2), torch.empty(8, 8))
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_engine."
"resolve_parameter_destinations",
lambda *args, **kwargs: [destination],
)
engine = object.__new__(M2NWeightTransferEngine)
engine.parallel_config = Mock(pipeline_parallel_size=1)
engine.model_config = Mock(quantization=None)
engine.model = torch.nn.Module()
param = self._param(shape=(32, 8))
engine._prepare_source_plan((param,), num_trainer_ranks=1)
with pytest.raises(
ValueError, match=r"parameter 'w'.*destination layout.*tensor dim 2"
):
engine._prepare_destination_plan(
self._init_info(
rank_offset=1,
world_size=5,
dst_mesh_dims=[1, 4],
params=[param.to_dict()],
)
)
def test_later_invalid_parameter_rejects_plan_before_any_reshard(self, monkeypatch):
destinations = [
M2NDestination("valid", (REPLICATE, 0), torch.empty(8, 8)),
M2NDestination("invalid", (REPLICATE, 1), torch.empty(32, 2)),
]
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_engine."
"resolve_parameter_destinations",
lambda *args, **kwargs: destinations,
)
engine = object.__new__(M2NWeightTransferEngine)
engine.parallel_config = Mock(pipeline_parallel_size=1)
engine.model_config = Mock(quantization=None)
engine.model = torch.nn.Module()
params = (
self._param(
name="valid",
shape=(32, 8),
src_mesh_dims=(8, 4),
src_placements=(REPLICATE, 0),
),
self._param(
name="invalid",
shape=(32, 8),
src_mesh_dims=(1, 32),
src_placements=(REPLICATE, 0),
),
)
engine._prepare_source_plan(params, num_trainer_ranks=32)
engine._reshard = Mock()
with pytest.raises(ValueError, match=r"parameter 'invalid'.*MAX_SOURCES=16"):
engine._prepare_destination_plan(
self._init_info(
rank_offset=32,
world_size=36,
dst_mesh_dims=[1, 4],
params=[param.to_dict() for param in params],
)
)
engine._reshard.assert_not_called()
def test_update_preflights_all_names_before_reshard(self):
engine = object.__new__(M2NWeightTransferEngine)
engine._handle = object()
engine.model_update_group = object()
engine._index = {"valid": 0}
engine._metas = [
M2NParamMeta(
"valid",
torch.float32,
(4,),
M2NLayout(M2NMesh((1, 1), 0), REPLICATED),
)
]
engine._reshard = Mock()
with pytest.raises(ValueError, match=r"parameter 'unknown'"):
engine.receive_weights(
M2NWeightTransferUpdateInfo(names=["valid", "unknown"])
)
engine._reshard.assert_not_called()
def test_fallback_tensors_share_one_load_weights_invocation(self, monkeypatch):
class CoupledModel:
def __init__(self):
self.calls = 0
self.loaded = []
def load_weights(self, weights):
self.calls += 1
received = list(weights)
if [name for name, _ in received] == ["pair.weight", "pair.scale"]:
self.loaded = [(name, tensor.clone()) for name, tensor in received]
model = CoupledModel()
direct = torch.zeros(1)
engine = object.__new__(M2NWeightTransferEngine)
engine._handle = object()
engine.model_update_group = object()
engine.device = torch.device("cpu")
engine.model = model
engine._index = {"pair.weight": 0, "direct": 1, "pair.scale": 2}
engine._metas = [
M2NParamMeta(
name,
torch.float32,
(1,),
M2NLayout(M2NMesh((1, 1), 0), REPLICATED),
)
for name in engine._index
]
engine._parameter_destinations = [
M2NDestination("pair.weight", REPLICATED, None),
M2NDestination("direct", REPLICATED, direct),
M2NDestination("pair.scale", REPLICATED, None),
]
reshard_order = []
def reshard(comm, stream, meta, placements, buffer):
reshard_order.append(meta.name)
buffer.fill_(len(reshard_order))
engine._reshard = reshard
stream = Mock()
monkeypatch.setattr(torch.cuda, "current_stream", lambda: stream)
engine.receive_weights(
M2NWeightTransferUpdateInfo(names=["pair.weight", "direct", "pair.scale"])
)
assert model.calls == 1
assert [name for name, _ in model.loaded] == ["pair.weight", "pair.scale"]
assert [tensor.item() for _, tensor in model.loaded] == [1, 3]
assert direct.item() == 2
assert reshard_order == ["pair.weight", "direct", "pair.scale"]
assert stream.synchronize.call_count == 2
def test_trainer_rank_must_be_a_trainer_rank(self):
"""A trainer rank must fall within the trainer portion of the group."""
with pytest.raises(ValueError, match="num_trainer_ranks"):
M2NTrainerInitInfo(
master_address="127.0.0.1",
master_port=1234,
world_size=4,
num_trainer_ranks=2,
rank=2,
)
def test_trainer_destination_mesh_must_cover_the_workers(self):
"""The destination mesh must include every inference worker."""
with pytest.raises(ValueError, match="dst_mesh_dims"):
M2NTrainerInitInfo(
master_address="127.0.0.1",
master_port=1234,
world_size=6,
num_trainer_ranks=2,
dst_mesh_dims=(3, 1), # 3 != the 4 inference workers
rank=0,
)
def test_destination_mesh_defaults_to_flat(self):
"""A replicated destination does not care how the mesh is factored, so
callers that do not shard it need not supply one."""
info = M2NTrainerInitInfo(
master_address="127.0.0.1",
master_port=1234,
world_size=6,
num_trainer_ranks=2,
rank=0,
)
assert info.destination_mesh_dims == (4, 1)
def test_sender_is_trainer_rank_zero(self):
"""Trainer rank 0 drives the inference control plane."""
info = M2NTrainerInitInfo(
master_address="127.0.0.1", master_port=1234, world_size=4, rank=0
)
assert info.is_sender
def test_metadata_group_receives_reserved_socket(monkeypatch):
reservation = Mock()
create = Mock(return_value=Mock())
monkeypatch.setattr("vllm.distributed.utils.StatelessProcessGroup.create", create)
stateless_init_metadata_group(
"127.0.0.1",
1234,
rank=0,
world_size=2,
listen_socket=reservation,
)
create.assert_called_once_with(
host="127.0.0.1",
port=1234,
rank=0,
world_size=2,
listen_socket=reservation,
)
class _StaticM2NSource(M2NWeightSource):
def __init__(self, metadata, values):
self._metadata = metadata
self._values = values
def metadata(self):
return list(self._metadata)
def __iter__(self):
return iter(self._values)
class TestTrainerSourceContract:
@staticmethod
def _meta(name="weight"):
return M2NParamMeta(
name,
torch.float32,
(4,),
M2NLayout(M2NMesh((1, 1), 0), REPLICATED),
)
@staticmethod
def _engine(source):
engine = M2NTrainerWeightTransferEngine(
client=Mock(), source=source, is_sender=False
)
engine._m2n = _fake_m2n()
engine._handle = object()
engine._metas = source.metadata()
engine._dst_mesh = M2NMesh((1, 1), 1)
engine._dst_placements = [REPLICATED] * len(engine._metas)
engine.group = Mock(comm=1)
return engine
def test_partial_trainer_initialization_releases_transport_resources(
self, monkeypatch
):
metadata_group = Mock(rank=0, world_size=2)
metadata_group.all_gather_obj.side_effect = lambda envelope: [envelope] * 2
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_trainer."
"stateless_init_metadata_group",
Mock(return_value=metadata_group),
)
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_trainer.import_m2n", Mock()
)
handle = Mock()
nccl = Mock()
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_trainer.prepare_m2n_local_runtime",
Mock(return_value=(nccl, 0, handle)),
)
communicator = Mock()
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_trainer.create_m2n_communicator",
Mock(return_value=communicator),
)
monkeypatch.setattr(
"vllm.distributed.weight_transfer.m2n_trainer."
"publish_destination_placements",
Mock(return_value=[]),
)
source = _StaticM2NSource([], [])
client = Mock()
client.init_weight_transfer_engine.side_effect = RuntimeError(
"worker init failed"
)
engine = M2NTrainerWeightTransferEngine(
client=client, source=source, is_sender=True
)
init_info = M2NTrainerInitInfo(
rank=0,
master_address="127.0.0.1",
master_port=1234,
world_size=2,
)
with pytest.raises(RuntimeError, match="worker init failed"):
engine._initialize(init_info, source)
communicator.abort.assert_called_once_with()
communicator.destroy.assert_not_called()
handle.destroy.assert_called_once_with()
assert engine.group is None
assert engine._handle is None
def test_non_sender_observes_rank_zero_finish_failure(self):
outcomes: dict[int, dict[str, object]] = {}
sender_group = Mock()
sender_group.send_obj.side_effect = lambda outcome, rank: outcomes.__setitem__(
rank, outcome
)
sender_group.recv_obj.return_value = {"phase": "round_outcome_consumed"}
source = _StaticM2NSource([], [])
sender = self._engine(source)
sender.is_sender = True
sender._metadata_group = sender_group
sender._num_trainer_ranks = 2
sender._send = Mock()
sender.client = Mock()
sender.client.finish_weight_update.side_effect = RuntimeError("finish failed")
with pytest.raises(RuntimeError, match="finish failed"):
sender.send_weights()
sender._send.assert_called_once_with()
sender_group.recv_obj.assert_called_once_with(1)
receiver_group = Mock()
receiver_group.recv_obj.side_effect = lambda _source_rank: outcomes.pop(1)
receiver = self._engine(source)
receiver._metadata_group = receiver_group
receiver._num_trainer_ranks = 2
receiver._send = Mock()
with pytest.raises(
RuntimeError,
match=(
"worker update failed on trainer rank zero: RuntimeError: finish failed"
),
):
receiver.send_weights()
receiver._send.assert_called_once_with()
receiver_group.recv_obj.assert_called_once_with(0)
receiver_group.send_obj.assert_called_once_with(
{"phase": "round_outcome_consumed"}, 0
)
assert not outcomes
def test_sender_rejects_invalid_round_outcome_acknowledgement(self):
source = _StaticM2NSource([], [])
sender = self._engine(source)
sender.is_sender = True
sender._metadata_group = Mock()
sender._metadata_group.recv_obj.return_value = {"phase": "wrong"}
sender._num_trainer_ranks = 2
sender._send = Mock()
sender.client = Mock()
with pytest.raises(RuntimeError, match="invalid round-outcome acknowledgement"):
sender.send_weights()
def test_non_sender_consumes_outcome_after_local_failure(self):
source = _StaticM2NSource([], [])
receiver = self._engine(source)
receiver._send = Mock(side_effect=[ValueError("local send failed"), None])
receiver._metadata_group = Mock()
receiver._metadata_group.recv_obj.side_effect = [
{"ok": False, "error": "RuntimeError: first round failed"},
{"ok": True, "error": None},
]
with pytest.raises(ValueError, match="local send failed"):
receiver.send_weights()
receiver.send_weights()
assert receiver._send.call_count == 2
assert receiver._metadata_group.recv_obj.call_count == 2
assert receiver._metadata_group.send_obj.call_args_list == [
call({"phase": "round_outcome_consumed"}, 0),
call({"phase": "round_outcome_consumed"}, 0),
]
@pytest.mark.parametrize(
("case", "match"),
[
("missing", "first missing: 'weight'"),
("extra", "first extra"),
("reordered", "yielded 'other'.*declared 'weight'"),
],
)
def test_source_cardinality_and_order(self, monkeypatch, case, match):
buffer = _BufferLike()
values = {
"missing": [],
"extra": [("weight", buffer), ("extra", buffer)],
"reordered": [("other", buffer)],
}[case]
source = _StaticM2NSource([self._meta()], values)
engine = self._engine(source)
monkeypatch.setattr(torch.cuda, "current_stream", Mock())
monkeypatch.setattr(torch.accelerator, "synchronize", Mock())
monkeypatch.setattr(torch.accelerator, "current_device_index", lambda: 0)
with pytest.raises(RuntimeError, match=match):
engine._send()
@pytest.mark.parametrize(
("case", "match"), [("dtype", "dtype"), ("shape", "local shape")]
)
def test_materialized_buffer_matches_declared_metadata(
self, monkeypatch, case, match
):
buffer = _BufferLike()
if case == "dtype":
buffer.dtype = torch.float16
else:
buffer.shape = (2,)
source = _StaticM2NSource([self._meta()], [("weight", buffer)])
engine = self._engine(source)
monkeypatch.setattr(torch.cuda, "current_stream", Mock())
monkeypatch.setattr(torch.accelerator, "synchronize", Mock())
monkeypatch.setattr(torch.accelerator, "current_device_index", lambda: 0)
with pytest.raises(ValueError, match=match):
engine._send()
engine._m2n.reshard.assert_not_called()
def test_m2n_compatible_buffer_needs_no_torch_methods(self, monkeypatch):
buffer = _BufferLike()
source = _StaticM2NSource([self._meta()], [("weight", buffer)])
engine = self._engine(source)
stream = Mock()
monkeypatch.setattr(torch.cuda, "current_stream", lambda: stream)
monkeypatch.setattr(torch.accelerator, "synchronize", Mock())
monkeypatch.setattr(torch.accelerator, "current_device_index", lambda: 0)
engine._send()
assert engine._m2n.reshard.call_args.args[0] is buffer
stream.synchronize.assert_called_once_with()
def test_allocator_aware_buffer_records_send_stream(self, monkeypatch):
buffer = _AllocatorAwareBuffer()
source = _StaticM2NSource([self._meta()], [("weight", buffer)])
engine = self._engine(source)
stream = Mock()
monkeypatch.setattr(torch.cuda, "current_stream", lambda: stream)
monkeypatch.setattr(torch.accelerator, "synchronize", Mock())
monkeypatch.setattr(torch.accelerator, "current_device_index", lambda: 0)
engine._send()
buffer.record_stream.assert_called_once_with(stream)
def test_raw_pointer_uses_declared_shape_and_dtype(self, monkeypatch):
pointer = 1234
source = _StaticM2NSource([self._meta()], [("weight", pointer)])
engine = self._engine(source)
monkeypatch.setattr(torch.cuda, "current_stream", Mock())
monkeypatch.setattr(torch.accelerator, "synchronize", Mock())
monkeypatch.setattr(torch.accelerator, "current_device_index", lambda: 0)
engine._send()
call = engine._m2n.reshard.call_args
assert call.args[0] == pointer
assert call.kwargs["src_local_shape"] == (4,)
assert call.kwargs["src_dtype"] == torch.float32
class TestRegistration:
def test_both_registries_expose_the_backend(self):
"""Both worker and trainer factories register nccl_m2n."""
assert "nccl_m2n" in WeightTransferEngineFactory._registry
assert "nccl_m2n" in WeightTransferTrainerFactory._registry
def test_init_info_dispatches_to_the_backend(self):
"""Trainer init info selects the nccl_m2n factory entry."""
assert M2NTrainerInitInfo.backend == "nccl_m2n"
# ---------------------------------------------------------------------------
# End-to-end transfer
#
# Transport-only, in the style of the NCCL/sparse tests in
# test_weight_transfer.py: two Ray tasks with one GPU each drive the two engines
# directly, so no HTTP server or LLM instance is involved. The worker is not an
# RPC endpoint here, so the trainer engine gets a no-op control-plane client --
# the NCCL rendezvous and the reshard itself are the real thing.
# ---------------------------------------------------------------------------
SHAPE = [64, 32]
DTYPE = "float32"
def _init_ray() -> None:
if ray.is_initialized():
return
ray.init(
ignore_reinit_error=True,
runtime_env={
"env_vars": {
"RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES": "1",
"RAY_EXPERIMENTAL_NOSET_HIP_VISIBLE_DEVICES": "1",
"RAY_EXPERIMENTAL_NOSET_ROCR_VISIBLE_DEVICES": "1",
}
},
)
def _assigned_device() -> "torch.device":
gpu_ids = ray.get_gpu_ids()
device = torch.device(f"cuda:{int(gpu_ids[0])}" if gpu_ids else "cuda:0")
current_platform.set_device(device)
return device
@ray.remote(num_gpus=1)
def _m2n_trainer_send(
master_address: str,
master_port: int,
world_size: int,
nccl_unique_id_b64: str | None = None,
trainer_rank: int = 0,
mixed_layout: bool = False,
) -> bool:
"""Send either the basic or mixed-layout manifest through the real engine."""
device = _assigned_device()
from vllm.distributed.weight_transfer import WeightTransferTrainerFactory
from vllm.distributed.weight_transfer.m2n_source import DTensorModuleSource
from vllm.distributed.weight_transfer.m2n_trainer import M2NTrainerInitInfo
class NoopClient:
def init_weight_transfer_engine(self, init_info):
pass
def start_weight_update(self):
pass
def update_weights(self, update_info):
pass
def finish_weight_update(self, weight_version=None):
pass
class Tiny(torch.nn.Module):
def __init__(self):
super().__init__()
self.weight = torch.nn.Parameter(
torch.arange(
SHAPE[0] * SHAPE[1], dtype=torch.float32, device=device
).reshape(SHAPE)
)
num_trainer_ranks = 2 if mixed_layout else 1
if mixed_layout:
row_full = torch.arange(16, dtype=torch.float32, device=device).reshape(4, 4)
column_full = row_full + 100
row_local = row_full.chunk(2, dim=0)[trainer_rank].contiguous()
column_local = column_full.chunk(2, dim=1)[trainer_rank].contiguous()
source = ManifestM2NWeightSource(
[
M2NManifestEntry(
"row_sharded",
torch.float32,
(4, 4),
M2NLayout(M2NMesh((1, 2), 0), (REPLICATE, 0)),
lambda: row_local,
),
M2NManifestEntry(
"column_sharded",
torch.float32,
(4, 4),
M2NLayout(M2NMesh((2, 1), 0), (1, REPLICATE)),
lambda: column_local,
),
]
)
dst_mesh_dims = (2, 1)
else:
source = DTensorModuleSource(Tiny(), num_trainer_ranks=1)
dst_mesh_dims = (1, world_size - 1)
engine = WeightTransferTrainerFactory.trainer_init(
init_info=M2NTrainerInitInfo(
master_address=master_address,
master_port=master_port,
nccl_unique_id_b64=nccl_unique_id_b64,
world_size=world_size,
num_trainer_ranks=num_trainer_ranks,
dst_mesh_dims=dst_mesh_dims,
rank=trainer_rank,
),
client=NoopClient(),
source=source,
)
engine.send_weights()
torch.accelerator.synchronize()
engine.shutdown()
return True
@ray.remote(num_gpus=1)
def _m2n_worker_receive(
master_address: str,
master_port: int,
world_size: int,
worker_rank: int = 0,
nccl_unique_id_b64: str | None = None,
mixed_layout: bool = False,
) -> dict:
"""Receive either the basic or mixed-layout manifest through the engine."""
from unittest.mock import MagicMock
device = _assigned_device()
from vllm.config.parallel import ParallelConfig
from vllm.config.weight_transfer import WeightTransferConfig
from vllm.distributed.weight_transfer.m2n_engine import (
M2NWeightTransferEngine,
M2NWeightTransferInitInfo,
M2NWeightTransferUpdateInfo,
)
class Recorder(torch.nn.Module):
def __init__(self):
super().__init__()
self.received: list[tuple[str, torch.Tensor]] = []
self.received_by_name: dict[str, torch.Tensor] = {}
if not mixed_layout and world_size > 2:
self.weight = torch.nn.Parameter(
torch.zeros(
SHAPE[0] // (world_size - 1),
SHAPE[1],
dtype=torch.float32,
device=device,
)
)
self.weight.output_dim = 0
self.weight.weight_loader = MethodType(
ColumnParallelLinear.weight_loader,
Mock(tp_size=world_size - 1),
)
def load_weights(self, weights):
for name, tensor in weights:
if mixed_layout:
self.received_by_name[name] = tensor.clone()
else:
self.received.append((name, tensor.clone()))
num_trainer_ranks = 2 if mixed_layout else 1
num_workers = world_size - num_trainer_ranks
parallel_config = MagicMock(spec=ParallelConfig)
parallel_config.rank = worker_rank
parallel_config.world_size = num_workers
parallel_config.data_parallel_rank = 0
parallel_config.data_parallel_index = 0
parallel_config.tensor_parallel_size = num_workers
parallel_config.pipeline_parallel_size = 1
vllm_config = MagicMock()
vllm_config.parallel_config = parallel_config
vllm_config.model_config = MagicMock()
vllm_config.model_config.quantization = None
recorder = Recorder()
engine = M2NWeightTransferEngine(
WeightTransferConfig(backend="nccl_m2n"), vllm_config, device, recorder
)
if mixed_layout:
params = [
M2NWireParam("row_sharded", "float32", (4, 4), (1, 2), (REPLICATE, 0)),
M2NWireParam("column_sharded", "float32", (4, 4), (2, 1), (1, REPLICATE)),
]
names = [param.name for param in params]
dst_mesh_dims = [2, 1]
else:
params = [M2NWireParam("weight", DTYPE, tuple(SHAPE), (1, 1), REPLICATED)]
names = ["weight"]
dst_mesh_dims = [1, num_workers]
engine.init_transfer_engine(
M2NWeightTransferInitInfo(
schema_version=M2N_WIRE_SCHEMA_VERSION,
master_address=master_address,
master_port=master_port,
nccl_unique_id_b64=nccl_unique_id_b64,
rank_offset=num_trainer_ranks,
world_size=world_size,
dst_mesh_dims=dst_mesh_dims,
source_digest=source_plan_digest(params),
params=[param.to_dict() for param in params],
)
)
engine.start_weight_update()
engine.receive_weights(M2NWeightTransferUpdateInfo(names=names))
engine.finish_weight_update()
torch.accelerator.synchronize()
if mixed_layout:
result = {
name: tensor.cpu().tolist()
for name, tensor in recorder.received_by_name.items()
}
engine.shutdown()
return result
full = torch.arange(
SHAPE[0] * SHAPE[1], dtype=torch.float32, device=device
).reshape(SHAPE)
direct = num_workers > 1
name: str | None
got: torch.Tensor | None
if direct:
name, got = "weight", recorder.weight
expected = full.chunk(num_workers, dim=0)[worker_rank]
else:
name, got = recorder.received[0] if recorder.received else (None, None)
expected = full
result = {
"count": len(recorder.received),
"name": name,
"shape": list(got.shape) if got is not None else None,
"exact": bool(torch.equal(got, expected)) if got is not None else False,
"direct": direct,
}
engine.shutdown()
return result
@pytest.mark.skipif(
torch.accelerator.device_count() < 2,
reason="Need at least 2 GPUs: one trainer rank and one inference worker.",
)
@pytest.mark.parametrize("data_plane", ["tcp", "uid"])
def test_m2n_weight_transfer_between_processes(data_plane):
"""A parameter survives a real reshard from a trainer process to a worker.
This is the only test here that moves bytes: it builds both engines, joins
one NCCL communicator across two processes, and reshards. Everything else
in this file checks how a transfer is *described*.
"""
pytest.importorskip("nccl.m2n", reason="nccl_m2n backend needs the m2n runtime")
_init_ray()
master_address = "127.0.0.1"
master_port = get_open_port()
world_size = 2 # 1 trainer + 1 inference worker
nccl_unique_id_b64 = None
if data_plane == "uid":
import nccl.core as nccl
nccl_unique_id_b64 = base64.b64encode(
bytes(nccl.get_unique_id().as_bytes)
).decode()
worker = _m2n_worker_receive.remote(
master_address,
master_port,
world_size,
nccl_unique_id_b64=nccl_unique_id_b64,
)
trainer = _m2n_trainer_send.remote(
master_address,
master_port,
world_size,
nccl_unique_id_b64,
)
trainer_ok, result = ray.get([trainer, worker], timeout=300)
assert trainer_ok, "trainer engine did not complete"
assert result["count"] == 1, f"expected one parameter, got {result['count']}"
assert result["name"] == "weight"
assert result["shape"] == SHAPE
assert result["exact"], "resharded tensor does not match what the trainer sent"
@pytest.mark.skipif(
torch.accelerator.device_count() < 3,
reason="Need at least 3 GPUs: one trainer rank and two inference workers.",
)
def test_m2n_weight_transfer_to_tp2_shards():
"""Two inference workers receive their own TP shard in live model storage."""
pytest.importorskip("nccl.m2n", reason="nccl_m2n backend needs the m2n runtime")
_init_ray()
master_address = "127.0.0.1"
master_port = get_open_port()
world_size = 3
workers = [
_m2n_worker_receive.remote(master_address, master_port, world_size, rank)
for rank in range(2)
]
trainer = _m2n_trainer_send.remote(master_address, master_port, world_size)
trainer_ok, *results = ray.get([trainer, *workers], timeout=300)
assert trainer_ok, "trainer engine did not complete"
for rank, result in enumerate(results):
assert result["count"] == 0, f"worker {rank} unexpectedly called load_weights"
assert result["name"] == "weight"
assert result["shape"] == [SHAPE[0] // 2, SHAPE[1]]
assert result["direct"]
assert result["exact"], f"worker {rank} received the wrong shard"
@pytest.mark.skipif(
torch.accelerator.device_count() < 4,
reason="Need 4 GPUs: two trainer ranks and two inference workers.",
)
def test_m2n_transfer_with_per_parameter_source_meshes():
"""One collective sequence accepts heterogeneous source factorizations."""
pytest.importorskip("nccl.m2n", reason="nccl_m2n backend needs the m2n runtime")
_init_ray()
master_address = "127.0.0.1"
master_port = get_open_port()
trainers = [
_m2n_trainer_send.remote(
master_address,
master_port,
4,
trainer_rank=rank,
mixed_layout=True,
)
for rank in range(2)
]
workers = [
_m2n_worker_receive.remote(
master_address,
master_port,
4,
worker_rank=rank,
mixed_layout=True,
)
for rank in range(2)
]
results = ray.get(trainers + workers, timeout=300)
trainer_results, worker_results = results[:2], results[2:]
assert trainer_results == [True, True]
expected_row = torch.arange(16, dtype=torch.float32).reshape(4, 4).tolist()
expected_column = (
torch.arange(16, dtype=torch.float32).reshape(4, 4) + 100
).tolist()
for result in worker_results:
assert result == {
"row_sharded": expected_row,
"column_sharded": expected_column,
}
class _Model(torch.nn.Module):
"""Stand-in for a TP=2 shard of a tiny model.
`column` is sharded on dim 0 and `row` on dim 1 (the two shapes a TP linear
layer produces), `norm` is replicated, and `fused` has no checkpoint-name
counterpart — the shape a fused parameter presents to the resolver.
"""
def __init__(self) -> None:
super().__init__()
self.column = torch.nn.Parameter(torch.zeros(8, 16))
self.column.output_dim = 0
self.column.weight_loader = MethodType(
ColumnParallelLinear.weight_loader, Mock(tp_size=2)
)
self.row = torch.nn.Parameter(torch.zeros(16, 8))
self.row.input_dim = 1
self.row.weight_loader = MethodType(
RowParallelLinear.weight_loader, Mock(tp_size=2)
)
self.norm = torch.nn.Parameter(torch.zeros(16))
self.norm.weight_loader = default_weight_loader
self.fused = torch.nn.Parameter(torch.zeros(24, 16))
class _MixedModel(torch.nn.Module):
def __init__(self) -> None:
super().__init__()
self.proj = torch.nn.Module()
self.proj.weight = torch.nn.Parameter(torch.zeros(16, 8))
self.proj.weight.input_dim = 1
self.proj.weight.weight_loader = MethodType(
RowParallelLinear.weight_loader, Mock(tp_size=2)
)
self.proj.bias = torch.nn.Parameter(torch.zeros(16))
def _resolve(names, dtypes, shapes, **kwargs):
defaults = dict(num_workers=2, shard_axis_size=2, allow_direct=True)
defaults.update(kwargs)
return resolve_parameter_destinations(_Model(), names, dtypes, shapes, **defaults)
class TestDestinationResolution:
@pytest.mark.parametrize(
"name,expected_dim",
[("column", 0), ("row", 1)],
)
def test_sharded_parameter_resolves_to_its_tp_dim(self, name, expected_dim):
[destination] = _resolve([name], [torch.float32], [(16, 16)])
assert destination.direct
assert destination.placements == (REPLICATE, expected_dim)
def test_replicated_parameter_needs_no_placement(self):
"""A parameter every rank holds in full is REPLICATED, so it works
whatever way the inference mesh happens to be factored."""
[destination] = _resolve(["norm"], [torch.float32], [(16,)])
assert destination.direct
assert destination.placements is REPLICATED
def test_direct_destination_is_the_live_parameter(self):
model = _Model()
[destination] = resolve_parameter_destinations(
model,
["column"],
[torch.float32],
[(16, 16)],
num_workers=2,
shard_axis_size=2,
allow_direct=True,
)
assert destination.tensor.data_ptr() == model.column.data_ptr()
def test_known_transforming_model_submodule_falls_back(self):
class GPT2Model(_Model):
pass
GPT2Model.__module__ = "vllm.model_executor.models.gpt2"
model = torch.nn.Module()
model.transforming = GPT2Model()
[destination] = resolve_parameter_destinations(
model,
["transforming.row"],
[torch.float32],
[(16, 16)],
num_workers=2,
shard_axis_size=2,
allow_direct=True,
)
assert not destination.direct
assert destination.placements is REPLICATED
def test_unknown_name_falls_back(self):
"""Fused parameters reach the worker under checkpoint names that do not
exist in the model; those must take the full-tensor path."""
[destination] = _resolve(["mlp.gate_proj.weight"], [torch.float32], [(16, 16)])
assert not destination.direct
assert destination.placements is REPLICATED
def test_fallback_parameter_demotes_direct_sibling_in_same_module(self):
"""Layerwise finalization must not overwrite a directly loaded weight
when a sibling bias takes the fallback path."""
destinations = resolve_parameter_destinations(
_MixedModel(),
["proj.weight", "proj.bias"],
[torch.float32, torch.float32],
[(16, 16), (16,)],
num_workers=2,
shard_axis_size=2,
allow_direct=True,
)
assert not any(destination.direct for destination in destinations)
def test_unknown_loader_falls_back_despite_matching_name_and_shape(self):
model = _Model()
model.norm.weight_loader = lambda param, weight: param.data.copy_(weight)
[destination] = resolve_parameter_destinations(
model,
["norm"],
[torch.float32],
[(16,)],
num_workers=2,
shard_axis_size=2,
allow_direct=True,
)
assert not destination.direct
def test_missing_loader_is_not_assumed_to_be_a_plain_copy(self):
model = _Model()
del model.norm.weight_loader
[destination] = resolve_parameter_destinations(
model,
["norm"],
[torch.float32],
[(16,)],
num_workers=2,
shard_axis_size=2,
allow_direct=True,
)
assert not destination.direct
def test_wrapped_known_loader_falls_back(self):
model = _Model()
@wraps(default_weight_loader)
def wrapped_loader(param, weight):
default_weight_loader(param, weight)
model.norm.weight_loader = wrapped_loader
[destination] = resolve_parameter_destinations(
model,
["norm"],
[torch.float32],
[(16,)],
num_workers=2,
shard_axis_size=2,
allow_direct=True,
)
assert not destination.direct
def test_replicated_loader_with_transform_metadata_falls_back(self):
model = _Model()
model.norm.is_transposed = True
[destination] = resolve_parameter_destinations(
model,
["norm"],
[torch.float32],
[(16,)],
num_workers=2,
shard_axis_size=2,
allow_direct=True,
)
assert not destination.direct
def test_packed_dim_zero_falls_back(self):
model = _Model()
model.column.packed_dim = 0
[destination] = resolve_parameter_destinations(
model,
["column"],
[torch.float32],
[(16, 16)],
num_workers=2,
shard_axis_size=2,
allow_direct=True,
)
assert not destination.direct
def test_loader_declared_dim_wins_over_shape_guessing(self):
model = _Model()
model.column.output_dim = 1
[destination] = resolve_parameter_destinations(
model,
["column"],
[torch.float32],
[(16, 16)],
num_workers=2,
shard_axis_size=2,
allow_direct=True,
)
assert not destination.direct
def test_shape_the_tp_factor_cannot_explain_falls_back(self):
[destination] = _resolve(["fused"], [torch.float32], [(16, 16)])
assert not destination.direct
def test_dtype_mismatch_falls_back(self):
"""A parameter stored in a different dtype than the wire dtype means a
quantized or otherwise transformed layout — never write into it."""
[destination] = _resolve(["column"], [torch.bfloat16], [(16, 16)])
assert not destination.direct
def test_allow_direct_false_forces_every_parameter_to_fall_back(self):
destinations = _resolve(
["column", "norm"],
[torch.float32] * 2,
[(16, 16), (16,)],
allow_direct=False,
)
assert not any(d.direct for d in destinations)
def test_plan_reports_byte_coverage(self, caplog_vllm):
"""A small direct parameter must not hide a large fallback tensor."""
with caplog_vllm.at_level(
logging.INFO,
logger="vllm.distributed.weight_transfer.m2n_layout",
):
_resolve(
["norm", "missing_fused_weight"],
[torch.float32, torch.bfloat16],
[(16,), (1024, 1024)],
)
assert (
"1/2 parameters resharded directly into the model, 1 via "
"full-tensor fallback; direct byte coverage: 64/2097216 bytes "
"(0.0%)" in caplog_vllm.text
)