vllm-project--vllm-omni
243 行
8.3 KiB
Python
243 行
8.3 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
"""Unit tests for LayerwiseOffloadHook and LayerWiseOffloadBackend utilities."""
|
|
|
|
import gc
|
|
import os
|
|
import socket
|
|
from contextlib import contextmanager
|
|
|
|
import pytest
|
|
import torch
|
|
import torch.distributed as dist
|
|
from torch import nn
|
|
from torch.distributed.tensor import DeviceMesh, DTensor, Replicate
|
|
|
|
import vllm_omni.diffusion.offloader.layerwise_backend as layerwise_backend_module
|
|
from vllm_omni.diffusion.offloader.layerwise_backend import LayerWiseOffloadBackend, LayerwiseOffloadHook
|
|
from vllm_omni.platforms import current_omni_platform
|
|
|
|
pytestmark = [pytest.mark.diffusion, pytest.mark.cpu, pytest.mark.core_model]
|
|
|
|
|
|
class DummyStream:
|
|
def wait_stream(self, _stream) -> None:
|
|
return None
|
|
|
|
def wait_event(self, _event) -> None:
|
|
return None
|
|
|
|
|
|
class DummyEvent:
|
|
def record(self, _stream) -> None:
|
|
return None
|
|
|
|
|
|
@contextmanager
|
|
def dummy_stream(_stream):
|
|
yield None
|
|
|
|
|
|
def _find_free_port() -> int:
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
s.bind(("127.0.0.1", 0))
|
|
return int(s.getsockname()[1])
|
|
|
|
|
|
def _set_dist_env(*, rank: int, world_size: int, master_port: int) -> None:
|
|
os.environ["RANK"] = str(rank)
|
|
os.environ["LOCAL_RANK"] = str(rank)
|
|
os.environ["WORLD_SIZE"] = str(world_size)
|
|
os.environ["MASTER_ADDR"] = "127.0.0.1"
|
|
os.environ["MASTER_PORT"] = str(master_port)
|
|
|
|
|
|
def _cleanup_distributed() -> None:
|
|
if dist.is_initialized():
|
|
dist.destroy_process_group()
|
|
|
|
for key in ["MASTER_ADDR", "MASTER_PORT", "RANK", "WORLD_SIZE", "LOCAL_RANK"]:
|
|
os.environ.pop(key, None)
|
|
|
|
gc.collect()
|
|
if current_omni_platform.is_available():
|
|
current_omni_platform.empty_cache()
|
|
current_omni_platform.synchronize()
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def dist_group():
|
|
master_port = _find_free_port()
|
|
_set_dist_env(rank=0, world_size=1, master_port=master_port)
|
|
|
|
dist.init_process_group("gloo", rank=0, world_size=1)
|
|
try:
|
|
yield
|
|
finally:
|
|
_cleanup_distributed()
|
|
|
|
|
|
@pytest.fixture
|
|
def patched_offload_runtime(mocker):
|
|
mocker.patch.object(layerwise_backend_module.current_omni_platform, "Stream", DummyStream)
|
|
mocker.patch.object(layerwise_backend_module.current_omni_platform, "Event", DummyEvent)
|
|
mocker.patch.object(layerwise_backend_module.current_omni_platform, "current_stream", lambda: DummyStream())
|
|
mocker.patch.object(layerwise_backend_module.current_omni_platform, "stream", dummy_stream)
|
|
|
|
|
|
class TinyBlock(nn.Module):
|
|
def __init__(self, values: torch.Tensor):
|
|
super().__init__()
|
|
mesh = DeviceMesh("cpu", [0])
|
|
dtensor = DTensor.from_local(values, mesh, [Replicate()])
|
|
self.weight = nn.Parameter(dtensor)
|
|
|
|
|
|
def _make_values(start: float) -> torch.Tensor:
|
|
return torch.arange(start, start + 4, dtype=torch.float32)
|
|
|
|
|
|
class TestLayerwiseOffloadHook:
|
|
def test_dtensor_wrapper_is_preserved_across_prefetch_and_offload(self, dist_group, patched_offload_runtime):
|
|
current_block = TinyBlock(_make_values(1.0))
|
|
next_block = TinyBlock(_make_values(10.0))
|
|
|
|
hook = LayerwiseOffloadHook(
|
|
next_block=next_block,
|
|
device=torch.device("cpu"),
|
|
stream=DummyStream(),
|
|
pin_memory=False,
|
|
)
|
|
|
|
hook.initialize_hook(current_block)
|
|
|
|
assert isinstance(next_block.weight, DTensor)
|
|
assert next_block.weight.to_local().is_meta
|
|
assert next_block.weight.to_local().shape == torch.Size([4])
|
|
assert hook.dtype_metadata[next_block.weight.dtype][0]["shape"] == torch.Size([4])
|
|
|
|
hook.prefetch_layer(non_blocking=False)
|
|
assert isinstance(next_block.weight, DTensor)
|
|
assert torch.equal(next_block.weight.to_local(), _make_values(10.0))
|
|
assert next_block.weight.to_local().shape == torch.Size([4])
|
|
|
|
hook.offload_layer()
|
|
assert isinstance(current_block.weight, DTensor)
|
|
assert current_block.weight.to_local().is_meta
|
|
assert current_block.weight.to_local().shape == torch.Size([4])
|
|
assert not hook.is_materialized
|
|
|
|
|
|
class _DummyBlock(nn.Module):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.weight = nn.Parameter(torch.randn(10, 10))
|
|
|
|
|
|
class _SingleBlockModel(nn.Module):
|
|
_layerwise_offload_blocks_attrs = ["blocks"]
|
|
|
|
def __init__(self, num_blocks: int = 3):
|
|
super().__init__()
|
|
self.blocks = nn.ModuleList([_DummyBlock() for _ in range(num_blocks)])
|
|
|
|
|
|
class _MultiBlockModel(nn.Module):
|
|
_layerwise_offload_blocks_attrs = ["transformer_blocks", "single_transformer_blocks"]
|
|
|
|
def __init__(self, num_transformer: int = 2, num_single: int = 2):
|
|
super().__init__()
|
|
self.transformer_blocks = nn.ModuleList([_DummyBlock() for _ in range(num_transformer)])
|
|
self.single_transformer_blocks = nn.ModuleList([_DummyBlock() for _ in range(num_single)])
|
|
|
|
|
|
class _EmptyBlocksModel(nn.Module):
|
|
_layerwise_offload_blocks_attrs = ["blocks"]
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.blocks = nn.ModuleList([])
|
|
|
|
|
|
class _InvalidAttrModel(nn.Module):
|
|
_layerwise_offload_blocks_attrs = ["nonexistent_blocks", "blocks"]
|
|
|
|
def __init__(self, num_blocks: int = 2):
|
|
super().__init__()
|
|
self.blocks = nn.ModuleList([_DummyBlock() for _ in range(num_blocks)])
|
|
|
|
|
|
class _DeprecatedSingleAttrModel(nn.Module):
|
|
_layerwise_offload_blocks_attr = "blocks"
|
|
|
|
def __init__(self, num_blocks: int = 2):
|
|
super().__init__()
|
|
self.blocks = nn.ModuleList([_DummyBlock() for _ in range(num_blocks)])
|
|
|
|
|
|
class _NoAttrsModel(nn.Module):
|
|
def __init__(self, num_blocks: int = 2):
|
|
super().__init__()
|
|
self.blocks = nn.ModuleList([_DummyBlock() for _ in range(num_blocks)])
|
|
|
|
|
|
class TestGetBlocksFromDit:
|
|
def test_get_blocks_from_dit_single_block_attr(self):
|
|
model = _SingleBlockModel(num_blocks=3)
|
|
attr_names, blocks = LayerWiseOffloadBackend.get_blocks_from_dit(model)
|
|
assert attr_names == ["blocks"]
|
|
assert len(blocks) == 3
|
|
assert all(isinstance(b, _DummyBlock) for b in blocks)
|
|
|
|
def test_get_blocks_from_dit_multi_block_attrs(self):
|
|
model = _MultiBlockModel(num_transformer=2, num_single=3)
|
|
attr_names, blocks = LayerWiseOffloadBackend.get_blocks_from_dit(model)
|
|
assert set(attr_names) == {"transformer_blocks", "single_transformer_blocks"}
|
|
assert len(blocks) == 5
|
|
assert all(isinstance(b, _DummyBlock) for b in blocks)
|
|
|
|
def test_get_blocks_from_dit_empty_blocks(self):
|
|
model = _EmptyBlocksModel()
|
|
attr_names, blocks = LayerWiseOffloadBackend.get_blocks_from_dit(model)
|
|
assert attr_names == []
|
|
assert blocks == []
|
|
|
|
def test_get_blocks_from_dit_invalid_attr_name(self):
|
|
model = _InvalidAttrModel(num_blocks=2)
|
|
with pytest.raises(
|
|
AttributeError,
|
|
match="Attribute 'nonexistent_blocks' declared in _layerwise_offload_blocks_attrs does not exist",
|
|
):
|
|
LayerWiseOffloadBackend.get_blocks_from_dit(model)
|
|
|
|
def test_get_blocks_from_dit_no_attrs_defined(self):
|
|
model = _NoAttrsModel(num_blocks=3)
|
|
attr_names, blocks = LayerWiseOffloadBackend.get_blocks_from_dit(model)
|
|
assert attr_names == []
|
|
assert blocks == []
|
|
|
|
def test_get_blocks_from_dit_deprecated_single_attr(self):
|
|
model = _DeprecatedSingleAttrModel(num_blocks=2)
|
|
attr_names, blocks = LayerWiseOffloadBackend.get_blocks_from_dit(model)
|
|
assert attr_names == ["blocks"]
|
|
assert len(blocks) == 2
|
|
|
|
|
|
class TestGetBlocksAttrNames:
|
|
def test_get_blocks_attr_names_new_format(self):
|
|
model = _MultiBlockModel()
|
|
attrs = LayerWiseOffloadBackend.get_blocks_attr_names(model)
|
|
assert attrs == ["transformer_blocks", "single_transformer_blocks"]
|
|
|
|
def test_get_blocks_attr_names_no_attrs(self):
|
|
model = _NoAttrsModel()
|
|
attrs = LayerWiseOffloadBackend.get_blocks_attr_names(model)
|
|
assert attrs == []
|
|
|
|
def test_set_blocks_attr_names(self):
|
|
model = _NoAttrsModel()
|
|
LayerWiseOffloadBackend.set_blocks_attr_names(model, ["new_blocks"])
|
|
assert hasattr(model.__class__, "_layerwise_offload_blocks_attrs")
|
|
assert model.__class__._layerwise_offload_blocks_attrs == ["new_blocks"]
|