Files
vllm-omni/tests/diffusion/models/sana_wm/test_cam_reduce_scatter.py
T
BruceLoveDecimaland汪志鹏 adf6d0c1ea [Model] Add SANA-WM support(Stage-1 only) (#4061)
Signed-off-by: BruceLoveDecimal <liuqihao970610@gmail.com>
Signed-off-by: 汪志鹏 <wangzhipeng628@gmail.com>
Signed-off-by: liuqihao <liuqihao970610@gmail.com>
Co-authored-by: 汪志鹏 <wangzhipeng628@gmail.com>
2026-08-18 23:57:55 -06:00

130 lines
5.0 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Equivalence tests for SANA-WM's camera-branch reduce-scatter.
``out_proj_cam`` is row-parallel, so every rank produces a partial sum over the
full hidden width, while the GDN stream it is added to is TP-local. Instead of
all-reducing and then discarding all but one slice, the branch reduce-scatters
straight to the local shard. These tests pin the shard each rank ends up with,
including the bias slice, on both code paths:
* the process-group path, which drives ``torch.distributed.reduce_scatter_tensor``
directly because vLLM-Omni's diffusion ``GroupCoordinator`` does not implement
``reduce_scatter``;
* the all-reduce-and-slice fallback taken when the coordinator exposes no
``device_group``.
CPU-only: the collectives and the rank/world-size lookups are mocked, so a
single process can simulate an arbitrary TP degree.
"""
from types import SimpleNamespace
from unittest.mock import patch
import pytest
import torch
from vllm_omni.diffusion.models.sana_wm.sana_wm_transformer import SanaWmSelfAttention
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
_MODULE = "vllm_omni.diffusion.models.sana_wm.sana_wm_transformer"
HIDDEN = 24
def _host(bias: torch.Tensor | None) -> SimpleNamespace:
"""A stand-in ``self`` exposing only what the method reads."""
return SimpleNamespace(out_proj_cam=SimpleNamespace(bias=bias))
def _fake_reduce_scatter_tensor(rank: int, tp_size: int):
"""Model ``reduce_scatter_tensor``: split dim 0 and keep chunk ``rank``.
The test drives a single process, so the other ranks' partials are already
folded into the input; scattering is all this has to reproduce.
"""
def _impl(output: torch.Tensor, input_: torch.Tensor, group=None) -> None:
output.copy_(input_.chunk(tp_size, dim=0)[rank])
return _impl
def _tp_group(with_device_group: bool):
return SimpleNamespace(device_group=object() if with_device_group else None)
@pytest.mark.parametrize("tp_size", [2, 4])
@pytest.mark.parametrize("rank", [0, 1])
def test_process_group_path_matches_all_reduce_then_slice(tp_size: int, rank: int) -> None:
"""The scattered shard equals the slice the all-reduce path would take."""
local_width = HIDDEN // tp_size
torch.manual_seed(0)
contrib = torch.randn(2, 3, HIDDEN)
bias = torch.randn(HIDDEN)
reference = torch.zeros(2, 3, local_width)
with (
patch(f"{_MODULE}.get_tensor_model_parallel_world_size", return_value=tp_size),
patch(f"{_MODULE}.get_tensor_model_parallel_rank", return_value=rank),
patch(f"{_MODULE}.get_tp_group", return_value=_tp_group(True)),
patch("torch.distributed.reduce_scatter_tensor", side_effect=_fake_reduce_scatter_tensor(rank, tp_size)),
):
out = SanaWmSelfAttention._reduce_scatter_cam_contrib(_host(bias), contrib, reference)
start = rank * local_width
expected = (contrib + bias)[..., start : start + local_width]
assert out.shape == (2, 3, local_width)
torch.testing.assert_close(out, expected)
@pytest.mark.parametrize("rank", [0, 1])
def test_fallback_path_matches_the_process_group_path(rank: int) -> None:
"""Without a device_group the method all-reduces and slices, same result."""
tp_size = 2
local_width = HIDDEN // tp_size
torch.manual_seed(1)
contrib = torch.randn(2, 3, HIDDEN)
bias = torch.randn(HIDDEN)
reference = torch.zeros(2, 3, local_width)
with (
patch(f"{_MODULE}.get_tensor_model_parallel_world_size", return_value=tp_size),
patch(f"{_MODULE}.get_tensor_model_parallel_rank", return_value=rank),
patch(f"{_MODULE}.get_tp_group", return_value=_tp_group(False)),
patch(f"{_MODULE}.tensor_model_parallel_all_reduce", side_effect=lambda t: t),
):
out = SanaWmSelfAttention._reduce_scatter_cam_contrib(_host(bias), contrib, reference)
start = rank * local_width
torch.testing.assert_close(out, (contrib + bias)[..., start : start + local_width])
def test_tp1_adds_bias_without_a_collective() -> None:
contrib = torch.randn(2, 3, HIDDEN)
bias = torch.randn(HIDDEN)
with (
patch(f"{_MODULE}.get_tensor_model_parallel_world_size", return_value=1),
patch("torch.distributed.reduce_scatter_tensor") as collective,
):
out = SanaWmSelfAttention._reduce_scatter_cam_contrib(_host(bias), contrib, contrib)
assert collective.call_count == 0
torch.testing.assert_close(out, contrib + bias)
def test_rejects_a_width_that_does_not_shard() -> None:
"""A local width that is not 1/tp of the contribution is a wiring bug."""
contrib = torch.randn(1, 2, HIDDEN)
reference = torch.zeros(1, 2, HIDDEN // 3) # tp=2 would need HIDDEN // 2
with (
patch(f"{_MODULE}.get_tensor_model_parallel_world_size", return_value=2),
patch("torch.distributed.reduce_scatter_tensor"),
pytest.raises(ValueError, match="camera contribution width mismatch"),
):
SanaWmSelfAttention._reduce_scatter_cam_contrib(_host(None), contrib, reference)