Merge branch 'main' into perf/minimax-h3-lossless-hot-path
This commit is contained in:
143 files changed
+8587
-2165
No files matched your search
@@ -1,6 +1,7 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
import json
|
||||
from multiprocessing.reduction import ForkingPickler
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock
|
||||
@@ -24,12 +25,343 @@ def test_pipeline_import_registry_and_component_discovery():
|
||||
"pipeline_minimax_h3",
|
||||
"MiniMaxH3Pipeline",
|
||||
)
|
||||
assert _DIFFUSION_MODELS["MiniMaxH3ModularPipeline"] == _DIFFUSION_MODELS["MiniMaxH3Pipeline"]
|
||||
assert _DIFFUSION_POST_PROCESS_FUNCS["MiniMaxH3Pipeline"] == "get_minimax_h3_post_process_func"
|
||||
assert MiniMaxH3Pipeline._dit_modules == ["transformer"]
|
||||
assert (
|
||||
_DIFFUSION_POST_PROCESS_FUNCS["MiniMaxH3ModularPipeline"] == _DIFFUSION_POST_PROCESS_FUNCS["MiniMaxH3Pipeline"]
|
||||
)
|
||||
assert MiniMaxH3Pipeline._dit_modules == ["transformer", "transformers_ref"]
|
||||
assert MiniMaxH3Pipeline._encoder_modules == ["text_encoder"]
|
||||
assert MiniMaxH3Pipeline._vae_modules == ["video_vae", "audio_vae"]
|
||||
|
||||
|
||||
def _write_partition_index(path, *, partition, tasks):
|
||||
path.mkdir(parents=True)
|
||||
(path / "model_index.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"_class_name": "MiniMaxH3Pipeline",
|
||||
"_diffusers_version": "0.32.2",
|
||||
"_minimax_h3": {
|
||||
"partition": partition,
|
||||
"tasks": tasks,
|
||||
"sigma_shift_scales": {"video": 12.0, "audio": 3.0},
|
||||
},
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def test_modular_diffusers_index_is_resolved_generically(tmp_path):
|
||||
from vllm_omni.diffusion.data import OmniDiffusionConfig, resolve_model_class_name
|
||||
from vllm_omni.diffusion.utils.hf_utils import is_diffusion_model
|
||||
from vllm_omni.entrypoints.utils import resolve_model_config_path
|
||||
|
||||
(tmp_path / "modular_model_index.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"_class_name": "MiniMaxH3ModularPipeline",
|
||||
"_diffusers_version": "0.36.0.dev0",
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert is_diffusion_model(str(tmp_path))
|
||||
assert resolve_model_class_name(str(tmp_path)) == "MiniMaxH3ModularPipeline"
|
||||
assert resolve_model_config_path(str(tmp_path)) is None
|
||||
|
||||
config = OmniDiffusionConfig(model=str(tmp_path))
|
||||
config.enrich_config()
|
||||
assert config.model_class_name == "MiniMaxH3ModularPipeline"
|
||||
assert config.supports_multimodal_inputs
|
||||
assert config.max_multimodal_image_inputs == 9
|
||||
assert config.supports_mixed_reference_inputs
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("task_type", "partition"),
|
||||
[
|
||||
(None, "combined"),
|
||||
("auto", "combined"),
|
||||
("t2va", "fl2va"),
|
||||
("fl2va", "fl2va"),
|
||||
("ref2va", "ref2va"),
|
||||
],
|
||||
)
|
||||
def test_startup_task_selects_weight_partition(task_type, partition):
|
||||
from vllm_omni.diffusion.models.minimax_h3.pipeline_minimax_h3 import _minimax_h3_partition_for_task
|
||||
|
||||
assert _minimax_h3_partition_for_task(task_type) == partition
|
||||
|
||||
|
||||
def test_startup_auto_task_uses_explicit_local_partition(tmp_path):
|
||||
from vllm_omni.diffusion.models.minimax_h3.pipeline_minimax_h3 import _minimax_h3_partition_for_task
|
||||
|
||||
for directory, partition, tasks in (
|
||||
("FL2VA", "fl2va", ["t2va", "fl2va"]),
|
||||
("Ref2VA", "ref2va", ["ref2va"]),
|
||||
):
|
||||
model_path = tmp_path / directory
|
||||
_write_partition_index(model_path, partition=partition, tasks=tasks)
|
||||
assert _minimax_h3_partition_for_task(None, str(model_path)) == partition
|
||||
assert _minimax_h3_partition_for_task("auto", str(model_path)) == partition
|
||||
assert _minimax_h3_partition_for_task("combined", str(model_path)) == "combined"
|
||||
|
||||
|
||||
def test_startup_task_rejects_unsupported_value():
|
||||
from vllm_omni.diffusion.models.minimax_h3.pipeline_minimax_h3 import _minimax_h3_partition_for_task
|
||||
|
||||
with pytest.raises(ValueError, match="task_type must be one of"):
|
||||
_minimax_h3_partition_for_task("unsupported")
|
||||
|
||||
|
||||
def test_combined_task_inference_and_transformer_routing():
|
||||
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
|
||||
|
||||
pipeline = object.__new__(MiniMaxH3Pipeline)
|
||||
torch.nn.Module.__init__(pipeline)
|
||||
pipeline.partition = "combined"
|
||||
pipeline.supported_tasks = frozenset({"t2va", "fl2va", "ref2va"})
|
||||
pipeline.transformer = torch.nn.Identity()
|
||||
pipeline.transformers_ref = torch.nn.Linear(1, 1)
|
||||
assert pipeline._resolve_task(None, {}) == "t2va"
|
||||
assert pipeline._resolve_task(None, {"image": object()}) == "fl2va"
|
||||
assert pipeline._resolve_task(None, {"audio": object()}) == "ref2va"
|
||||
assert pipeline._resolve_task(None, {"video": object()}) == "ref2va"
|
||||
pipeline.partition = "ref2va"
|
||||
pipeline.supported_tasks = frozenset({"ref2va"})
|
||||
assert pipeline._resolve_task(None, {"image": object()}) == "ref2va"
|
||||
|
||||
pipeline.partition = "combined"
|
||||
pipeline.supported_tasks = frozenset({"t2va", "fl2va", "ref2va"})
|
||||
assert pipeline._transformer_for_task("fl2va") is pipeline.transformer
|
||||
assert pipeline._transformer_for_task("ref2va") is pipeline.transformers_ref
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"task_type",
|
||||
"expected_partition",
|
||||
"expected_dits",
|
||||
"component_partition",
|
||||
"source_partitions",
|
||||
"expected_tasks",
|
||||
),
|
||||
[
|
||||
(None, "combined", 2, "FL2VA", ["FL2VA", "Ref2VA"], {"t2va", "fl2va", "ref2va"}),
|
||||
("fl2va", "fl2va", 1, "FL2VA", ["FL2VA"], {"t2va", "fl2va"}),
|
||||
("ref2va", "ref2va", 1, "Ref2VA", ["Ref2VA"], {"ref2va"}),
|
||||
],
|
||||
)
|
||||
def test_pipeline_loads_task_selected_dits_and_shared_components_once(
|
||||
monkeypatch,
|
||||
tmp_path,
|
||||
task_type,
|
||||
expected_partition,
|
||||
expected_dits,
|
||||
component_partition,
|
||||
source_partitions,
|
||||
expected_tasks,
|
||||
):
|
||||
from vllm_omni.diffusion.data import (
|
||||
DiffusionParallelConfig,
|
||||
OmniDiffusionConfig,
|
||||
)
|
||||
from vllm_omni.diffusion.models.minimax_h3 import (
|
||||
pipeline_minimax_h3 as pipeline_module,
|
||||
)
|
||||
|
||||
_write_partition_index(
|
||||
tmp_path / "FL2VA",
|
||||
partition="fl2va",
|
||||
tasks=["t2va", "fl2va"],
|
||||
)
|
||||
_write_partition_index(
|
||||
tmp_path / "Ref2VA",
|
||||
partition="ref2va",
|
||||
tasks=["ref2va"],
|
||||
)
|
||||
for partition_name in ("FL2VA", "Ref2VA"):
|
||||
for component in (
|
||||
"transformer",
|
||||
"tokenizer",
|
||||
"processor",
|
||||
"text_encoder",
|
||||
"video_vae",
|
||||
"audio_vae",
|
||||
):
|
||||
(tmp_path / partition_name / component).mkdir()
|
||||
|
||||
created = {"dit": [], "text_encoder": [], "video_vae": [], "audio_vae": []}
|
||||
|
||||
class FakeModule(torch.nn.Module):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__()
|
||||
|
||||
class FakeDiT(FakeModule):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__()
|
||||
created["dit"].append((args, kwargs))
|
||||
|
||||
def component_factory(name):
|
||||
def create(path, *args, **kwargs):
|
||||
created[name].append(str(path))
|
||||
return FakeModule()
|
||||
|
||||
return create
|
||||
|
||||
tokenizer_calls = []
|
||||
processor_calls = []
|
||||
tokenizer_cls = Mock(spec=pipeline_module.Qwen2TokenizerFast)
|
||||
tokenizer_cls.from_pretrained.side_effect = lambda *args, **kwargs: (
|
||||
tokenizer_calls.append((args, kwargs)) or object()
|
||||
)
|
||||
processor_cls = Mock(spec=pipeline_module.Qwen3VLProcessor)
|
||||
processor_cls.from_pretrained.side_effect = lambda *args, **kwargs: (
|
||||
processor_calls.append((args, kwargs)) or object()
|
||||
)
|
||||
monkeypatch.setattr(pipeline_module, "MiniMaxH3DiTModel", FakeDiT)
|
||||
monkeypatch.setattr(
|
||||
pipeline_module,
|
||||
"MiniMaxH3Qwen3VLEncoder",
|
||||
component_factory("text_encoder"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
pipeline_module,
|
||||
"MiniMaxH3VideoVAE",
|
||||
component_factory("video_vae"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
pipeline_module,
|
||||
"MiniMaxH3AudioVAE",
|
||||
component_factory("audio_vae"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
pipeline_module,
|
||||
"Qwen2TokenizerFast",
|
||||
tokenizer_cls,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
pipeline_module,
|
||||
"Qwen3VLProcessor",
|
||||
processor_cls,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
pipeline_module,
|
||||
"_dit_rank_world",
|
||||
lambda: (None, 0, 1),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
pipeline_module,
|
||||
"get_local_device",
|
||||
lambda: torch.device("cpu"),
|
||||
)
|
||||
download_calls = []
|
||||
|
||||
def fake_download(**kwargs):
|
||||
download_calls.append(kwargs)
|
||||
return str(tmp_path)
|
||||
|
||||
monkeypatch.setattr(
|
||||
pipeline_module,
|
||||
"download_weights_from_hf_specific",
|
||||
fake_download,
|
||||
)
|
||||
|
||||
od_config = OmniDiffusionConfig(
|
||||
model="MiniMaxAI/MiniMax-H3",
|
||||
revision=None,
|
||||
task_type=task_type,
|
||||
quantization_config=None,
|
||||
parallel_config=DiffusionParallelConfig(
|
||||
cfg_parallel_size=1,
|
||||
text_encoder_tp_size=1,
|
||||
),
|
||||
)
|
||||
pipeline = pipeline_module.MiniMaxH3Pipeline(od_config=od_config)
|
||||
|
||||
assert pipeline.partition == expected_partition
|
||||
assert pipeline.supported_tasks == expected_tasks
|
||||
assert len(created["dit"]) == expected_dits
|
||||
component_path = tmp_path / component_partition
|
||||
assert created["text_encoder"] == [str(component_path / "text_encoder")]
|
||||
assert created["video_vae"] == [str(component_path / "video_vae")]
|
||||
assert created["audio_vae"] == [str(component_path / "audio_vae")]
|
||||
assert len(tokenizer_calls) == 1
|
||||
assert len(processor_calls) == 1
|
||||
expected_dit_modules = ["transformer", "transformers_ref"] if expected_dits == 2 else ["transformer"]
|
||||
assert pipeline._dit_modules == expected_dit_modules
|
||||
assert hasattr(pipeline, "transformers_ref") is (expected_dits == 2)
|
||||
if expected_partition == "ref2va":
|
||||
assert pipeline._transformer_for_task("ref2va") is pipeline.transformer
|
||||
assert [source.model_or_path for source in pipeline.weights_sources] == [
|
||||
str(tmp_path / partition_name) for partition_name in source_partitions
|
||||
]
|
||||
expected_patterns = (
|
||||
pipeline_module.MINIMAX_H3_DOWNLOAD_PATTERNS
|
||||
if expected_partition == "combined"
|
||||
else pipeline_module.MINIMAX_H3_TASK_DOWNLOAD_PATTERNS[expected_partition]
|
||||
)
|
||||
assert download_calls == [
|
||||
{
|
||||
"model_name_or_path": "MiniMaxAI/MiniMax-H3",
|
||||
"cache_dir": None,
|
||||
"allow_patterns": expected_patterns,
|
||||
"revision": None,
|
||||
"require_all": True,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_combined_weight_loader_routes_each_contiguous_partition():
|
||||
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
|
||||
|
||||
class FakeTransformer(torch.nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.loaded = []
|
||||
self.post_load_calls = 0
|
||||
|
||||
def load_weights(self, weights):
|
||||
self.loaded = [name for name, _ in weights]
|
||||
return set(self.loaded)
|
||||
|
||||
def post_load_weights(self):
|
||||
self.post_load_calls += 1
|
||||
|
||||
pipeline = object.__new__(MiniMaxH3Pipeline)
|
||||
torch.nn.Module.__init__(pipeline)
|
||||
pipeline.transformer = FakeTransformer()
|
||||
pipeline.transformers_ref = FakeTransformer()
|
||||
pipeline.text_encoder = torch.nn.Identity()
|
||||
pipeline.video_vae = torch.nn.Identity()
|
||||
pipeline.audio_vae = torch.nn.Identity()
|
||||
loaded = pipeline.load_weights(
|
||||
iter(
|
||||
[
|
||||
("transformer.a", torch.ones(1)),
|
||||
("transformer.b", torch.ones(1)),
|
||||
("transformers_ref.a", torch.ones(1)),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
assert pipeline.transformer.loaded == ["a", "b"]
|
||||
assert pipeline.transformers_ref.loaded == ["a"]
|
||||
assert pipeline.transformer.post_load_calls == 1
|
||||
assert pipeline.transformers_ref.post_load_calls == 1
|
||||
assert loaded == {"transformer.a", "transformer.b", "transformers_ref.a"}
|
||||
|
||||
|
||||
def test_dlo_offload_plan_includes_token_refiner():
|
||||
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
|
||||
|
||||
assert MiniMaxH3Pipeline._offload_plan.offload_submodules == {"token_refiner": "blocks"}
|
||||
assert MiniMaxH3Pipeline._offload_plan.resident_dit_paths == frozenset({"transformer"})
|
||||
|
||||
|
||||
def test_joint_postprocess_is_multiprocessing_picklable():
|
||||
from vllm_omni.diffusion.models.minimax_h3 import (
|
||||
get_minimax_h3_post_process_func,
|
||||
@@ -87,6 +419,87 @@ def test_shifted_sigma_schedule_matches_reference_values():
|
||||
)
|
||||
|
||||
|
||||
def test_cudnn_packed_attention_uses_python_length_without_padding_mask():
|
||||
from vllm_omni.diffusion.models.minimax_h3.minimax_h3_transformer import (
|
||||
MiniMaxH3Attention,
|
||||
)
|
||||
|
||||
class FakeBackend:
|
||||
supports_prefix_kv_slicing = True
|
||||
|
||||
class FakeAttention(torch.nn.Module):
|
||||
attn_backend = FakeBackend
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.metadata = None
|
||||
|
||||
def forward(self, query, key, value, metadata):
|
||||
self.metadata = metadata
|
||||
return query
|
||||
|
||||
attention = object.__new__(MiniMaxH3Attention)
|
||||
torch.nn.Module.__init__(attention)
|
||||
attention.attention = FakeAttention()
|
||||
# Model-side Ulysses shards rows before Attention gathers them again.
|
||||
# The Python packed_total must therefore remain global even though q is
|
||||
# local here.
|
||||
q = torch.randn(4, 2, 4)
|
||||
cu_seqlens = torch.tensor([0, 5, 8], dtype=torch.int32)
|
||||
|
||||
output = attention._run_packed_attention(
|
||||
q,
|
||||
q,
|
||||
q,
|
||||
cu_seqlens=cu_seqlens,
|
||||
max_seqlen=5,
|
||||
packed_total=8,
|
||||
)
|
||||
|
||||
assert output.shape == q.shape
|
||||
assert attention.attention.metadata.attn_mask is None
|
||||
assert attention.attention.metadata.extra["valid_kv_length"] == 5
|
||||
|
||||
|
||||
def test_packed_attention_keeps_padding_mask_for_other_backends():
|
||||
from vllm_omni.diffusion.models.minimax_h3.minimax_h3_transformer import (
|
||||
MiniMaxH3Attention,
|
||||
)
|
||||
|
||||
class FakeBackend:
|
||||
supports_prefix_kv_slicing = False
|
||||
|
||||
class FakeAttention(torch.nn.Module):
|
||||
attn_backend = FakeBackend
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.metadata = None
|
||||
|
||||
def forward(self, query, key, value, metadata):
|
||||
self.metadata = metadata
|
||||
return query
|
||||
|
||||
attention = object.__new__(MiniMaxH3Attention)
|
||||
torch.nn.Module.__init__(attention)
|
||||
attention.attention = FakeAttention()
|
||||
q = torch.randn(8, 2, 4)
|
||||
|
||||
attention._run_packed_attention(
|
||||
q,
|
||||
q,
|
||||
q,
|
||||
cu_seqlens=torch.tensor([0, 5, 8], dtype=torch.int32),
|
||||
max_seqlen=5,
|
||||
packed_total=8,
|
||||
)
|
||||
|
||||
assert torch.equal(
|
||||
attention.attention.metadata.attn_mask,
|
||||
torch.tensor([[True, True, True, True, True, False, False, False]]),
|
||||
)
|
||||
|
||||
|
||||
def test_reference_image_resize_contract():
|
||||
from PIL import Image
|
||||
|
||||
@@ -293,6 +706,142 @@ def test_layerwise_offload_releases_text_encoder():
|
||||
pipeline.text_encoder.offload_to_cpu.assert_called_once_with()
|
||||
|
||||
|
||||
def test_distributed_layerwise_offload_releases_text_encoder():
|
||||
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
|
||||
|
||||
pipeline = object.__new__(MiniMaxH3Pipeline)
|
||||
torch.nn.Module.__init__(pipeline)
|
||||
pipeline.od_config = SimpleNamespace(
|
||||
enable_cpu_offload=False,
|
||||
enable_layerwise_offload=False,
|
||||
enable_distributed_layerwise_offload=True,
|
||||
)
|
||||
pipeline.text_encoder = Mock()
|
||||
expected = torch.ones(2, 3)
|
||||
pipeline.text_encoder.encode_ids.return_value = expected
|
||||
|
||||
actual = pipeline._encode_text_hidden(torch.tensor([1, 2]), {})
|
||||
|
||||
assert actual is expected
|
||||
pipeline.text_encoder.load_to_device.assert_called_once_with()
|
||||
pipeline.text_encoder.offload_to_cpu.assert_called_once_with()
|
||||
|
||||
|
||||
def test_distributed_layerwise_offload_stages_vae_component():
|
||||
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
|
||||
|
||||
pipeline = object.__new__(MiniMaxH3Pipeline)
|
||||
torch.nn.Module.__init__(pipeline)
|
||||
pipeline.od_config = SimpleNamespace(
|
||||
enable_layerwise_offload=False,
|
||||
enable_distributed_layerwise_offload=True,
|
||||
)
|
||||
component = Mock()
|
||||
|
||||
with pipeline._component_on_device(component):
|
||||
component.load_to_device.assert_called_once_with()
|
||||
component.offload_to_cpu.assert_not_called()
|
||||
|
||||
component.offload_to_cpu.assert_called_once_with()
|
||||
|
||||
|
||||
def test_distributed_layerwise_resident_blocks_are_stage_scoped():
|
||||
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
|
||||
|
||||
pipeline = object.__new__(MiniMaxH3Pipeline)
|
||||
torch.nn.Module.__init__(pipeline)
|
||||
controller = Mock()
|
||||
pipeline._dlo_residency_controller = controller
|
||||
|
||||
with pipeline._resident_dit_layers_on_device():
|
||||
controller.load_resident_layers.assert_called_once_with()
|
||||
controller.offload_resident_layers.assert_not_called()
|
||||
|
||||
controller.offload_resident_layers.assert_called_once_with()
|
||||
|
||||
|
||||
def test_distributed_layerwise_resident_blocks_release_on_failure():
|
||||
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
|
||||
|
||||
pipeline = object.__new__(MiniMaxH3Pipeline)
|
||||
torch.nn.Module.__init__(pipeline)
|
||||
controller = Mock()
|
||||
pipeline._dlo_residency_controller = controller
|
||||
|
||||
with pytest.raises(RuntimeError, match="denoise failed"):
|
||||
with pipeline._resident_dit_layers_on_device():
|
||||
raise RuntimeError("denoise failed")
|
||||
|
||||
controller.load_resident_layers.assert_called_once_with()
|
||||
controller.offload_resident_layers.assert_called_once_with()
|
||||
|
||||
|
||||
def test_distributed_layerwise_resident_blocks_can_be_skipped():
|
||||
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
|
||||
|
||||
pipeline = object.__new__(MiniMaxH3Pipeline)
|
||||
torch.nn.Module.__init__(pipeline)
|
||||
controller = Mock()
|
||||
pipeline._dlo_residency_controller = controller
|
||||
|
||||
with pipeline._resident_dit_layers_on_device(enabled=False):
|
||||
pass
|
||||
|
||||
controller.load_resident_layers.assert_not_called()
|
||||
controller.offload_resident_layers.assert_not_called()
|
||||
|
||||
|
||||
def test_encoder_layerwise_offload_keeps_tp_blocks_rank_local(monkeypatch):
|
||||
from vllm_omni.diffusion.models.minimax_h3.encoder import (
|
||||
MiniMaxH3Qwen3VLEncoder,
|
||||
)
|
||||
|
||||
class Stack(torch.nn.Module):
|
||||
def __init__(self, count):
|
||||
super().__init__()
|
||||
self.blocks = torch.nn.ModuleList([torch.nn.Linear(2, 2) for _ in range(count)])
|
||||
|
||||
class TextStack(torch.nn.Module):
|
||||
def __init__(self, count):
|
||||
super().__init__()
|
||||
self.layers = torch.nn.ModuleList([torch.nn.Linear(2, 2) for _ in range(count)])
|
||||
|
||||
hooks = []
|
||||
|
||||
def fake_apply(block, next_block, device, stream, pin_memory):
|
||||
hook = SimpleNamespace(
|
||||
block=block,
|
||||
next_block=next_block,
|
||||
device=device,
|
||||
stream=stream,
|
||||
pin_memory=pin_memory,
|
||||
_prev_hook=None,
|
||||
offload_layer=Mock(),
|
||||
)
|
||||
hooks.append(hook)
|
||||
return hook
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.diffusion.offloader.layerwise_backend.apply_block_hook",
|
||||
fake_apply,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.platforms.current_omni_platform.Stream",
|
||||
Mock(return_value="copy-stream"),
|
||||
)
|
||||
encoder = object.__new__(MiniMaxH3Qwen3VLEncoder)
|
||||
torch.nn.Module.__init__(encoder)
|
||||
encoder.device_target = torch.device("cpu")
|
||||
encoder.vision = Stack(2)
|
||||
encoder.text_model = TextStack(3)
|
||||
|
||||
encoder.enable_omni_layerwise_offload(pin_memory=False)
|
||||
|
||||
assert len(hooks) == 5
|
||||
assert all(hook._prev_hook is not None for hook in hooks)
|
||||
assert all(hook.device == torch.device("cpu") for hook in hooks)
|
||||
|
||||
|
||||
def test_video_vae_keeps_reference_fp32_weights(monkeypatch):
|
||||
from vllm_omni.diffusion.models.minimax_h3 import vae as vae_module
|
||||
|
||||
@@ -324,6 +873,82 @@ def test_video_vae_keeps_reference_fp32_weights(monkeypatch):
|
||||
assert next(video_vae.parameters()).dtype == torch.float32
|
||||
|
||||
|
||||
def test_video_vae_can_load_on_cpu_for_staged_gpu_residency(monkeypatch):
|
||||
from vllm_omni.diffusion.models.minimax_h3 import vae as vae_module
|
||||
|
||||
class FakeRemote(torch.nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.model = torch.nn.Linear(1, 1)
|
||||
|
||||
monkeypatch.setattr(
|
||||
vae_module,
|
||||
"_load_component_config",
|
||||
lambda _path: {
|
||||
"latent_channels": 1,
|
||||
"latents_mean": [0.0],
|
||||
"latents_std": [1.0],
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
vae_module,
|
||||
"_load_remote_component",
|
||||
lambda _path, _config: FakeRemote(),
|
||||
)
|
||||
|
||||
video_vae = vae_module.MiniMaxH3VideoVAE(
|
||||
"unused",
|
||||
device=torch.device("meta"),
|
||||
load_device=torch.device("cpu"),
|
||||
)
|
||||
|
||||
assert next(video_vae.parameters()).device.type == "cpu"
|
||||
assert video_vae._device_target.type == "meta"
|
||||
|
||||
|
||||
def test_video_vae_uses_snapshot_stager_for_accelerator_target(monkeypatch):
|
||||
from vllm_omni.diffusion.models.minimax_h3 import vae as vae_module
|
||||
|
||||
class FakeRemote(torch.nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.model = torch.nn.Linear(1, 1)
|
||||
|
||||
stager = Mock()
|
||||
stager_cls = Mock(return_value=stager)
|
||||
monkeypatch.setattr(
|
||||
vae_module,
|
||||
"_load_component_config",
|
||||
lambda _path: {
|
||||
"latent_channels": 1,
|
||||
"latents_mean": [0.0],
|
||||
"latents_std": [1.0],
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
vae_module,
|
||||
"_load_remote_component",
|
||||
lambda _path, _config: FakeRemote(),
|
||||
)
|
||||
monkeypatch.setattr(vae_module, "PinnedModuleStager", stager_cls)
|
||||
|
||||
video_vae = vae_module.MiniMaxH3VideoVAE(
|
||||
"unused",
|
||||
device=torch.device("cuda"),
|
||||
load_device=torch.device("cpu"),
|
||||
)
|
||||
video_vae.load_to_device()
|
||||
video_vae.offload_to_cpu()
|
||||
|
||||
stager_cls.assert_called_once_with(
|
||||
video_vae.remote,
|
||||
torch.device("cuda"),
|
||||
pin_memory=True,
|
||||
)
|
||||
stager.load.assert_called_once_with()
|
||||
stager.offload.assert_called_once_with()
|
||||
|
||||
|
||||
def test_video_vae_encode_uses_configured_parallel_tiling():
|
||||
from vllm_omni.diffusion.models.minimax_h3.vae import (
|
||||
MiniMaxH3VideoVAE,
|
||||
|
||||
@@ -61,6 +61,33 @@ def test_packed_attention_is_a_regional_compile_boundary():
|
||||
assert getattr(MiniMaxH3Attention._run_packed_attention, "_torchdynamo_disable", False)
|
||||
|
||||
|
||||
def test_h3_fused_rope_matches_reference_and_preserves_unrotated_dims():
|
||||
from vllm_omni.diffusion.layers.rope import RotaryEmbedding
|
||||
from vllm_omni.diffusion.models.minimax_h3.minimax_h3_transformer import (
|
||||
MiniMaxH3Attention,
|
||||
)
|
||||
|
||||
attention = object.__new__(MiniMaxH3Attention)
|
||||
nn.Module.__init__(attention)
|
||||
attention.rope = RotaryEmbedding(is_neox_style=True, half_head_dim=False)
|
||||
attention.rope._forward_method = attention.rope.forward_native
|
||||
|
||||
x = torch.randn(11, 3, 128, dtype=torch.bfloat16)
|
||||
freqs_half = torch.randn(11, 48)
|
||||
freqs = torch.cat((freqs_half, freqs_half), dim=-1)
|
||||
actual = attention._apply_rope(x, freqs)
|
||||
|
||||
cos = torch.cos(freqs).to(x.dtype).unsqueeze(1)
|
||||
sin = torch.sin(freqs).to(x.dtype).unsqueeze(1)
|
||||
x_rot = x[..., :96]
|
||||
x1, x2 = x_rot.chunk(2, dim=-1)
|
||||
expected_rot = x_rot * cos + torch.cat((-x2, x1), dim=-1) * sin
|
||||
expected = torch.cat((expected_rot, x[..., 96:]), dim=-1)
|
||||
|
||||
torch.testing.assert_close(actual, expected, atol=0, rtol=0)
|
||||
torch.testing.assert_close(actual[..., 96:], x[..., 96:], atol=0, rtol=0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("tp_size", "message"),
|
||||
[
|
||||
|
||||
@@ -135,6 +135,7 @@ def _generate_joint_output(omni, config: QualityTestConfig):
|
||||
num_frames=config.num_frames,
|
||||
guidance_scale=config.guidance_scale,
|
||||
sigmas=config.sigmas,
|
||||
extra_args={"aspect_ratio": "16:9"},
|
||||
),
|
||||
)
|
||||
main_process_peak_mem = torch.accelerator.max_memory_allocated() / (1024**3)
|
||||
|
||||
Reference in new issue
Block a user