[Model][MiniMax-H3] Multi-GPU serving fixes and layer-wise offload component selection (#7047)

Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com>
This commit is contained in:
heyuanliu-intel authored and GitHub committed 2026-09-14 10:02:30 -07:00
1 parent 2f1a18e69f
commit 40f8e94400
21 files changed
+1857 -60

No files matched your search

@@ -459,6 +459,7 @@ def test_combined_task_inference_and_transformer_routing():
("fl2va", "fl2va", 1, "FL2VA", ["FL2VA"], {"t2va", "fl2va"}, False),
],
)
@pytest.mark.parametrize("trust_remote_code", [False, True])
def test_pipeline_loads_task_selected_components_with_encoder_ownership(
monkeypatch,
tmp_path,
@@ -469,6 +470,7 @@ def test_pipeline_loads_task_selected_components_with_encoder_ownership(
source_partitions,
expected_tasks,
load_text_encoder,
trust_remote_code,
):
from vllm_omni.diffusion.data import (
DiffusionParallelConfig,
@@ -500,6 +502,7 @@ def test_pipeline_loads_task_selected_components_with_encoder_ownership(
(tmp_path / partition_name / component).mkdir()
created: dict[str, list[Any]] = {"dit": [], "text_encoder": [], "video_vae": [], "audio_vae": []}
created_kwargs: dict[str, list[Any]] = {"text_encoder": [], "video_vae": [], "audio_vae": []}
class FakeModule(torch.nn.Module):
def __init__(self, *args, **kwargs):
@@ -513,6 +516,7 @@ def test_pipeline_loads_task_selected_components_with_encoder_ownership(
def component_factory(name):
def create(path, *args, **kwargs):
created[name].append(str(path))
created_kwargs[name].append(kwargs)
return FakeModule()
return create
@@ -589,6 +593,7 @@ def test_pipeline_loads_task_selected_components_with_encoder_ownership(
cfg_parallel_size=1,
text_encoder_tp_size=1,
),
trust_remote_code=trust_remote_code,
)
pipeline = pipeline_module.MiniMaxH3Pipeline(od_config=od_config)
@@ -598,6 +603,13 @@ def test_pipeline_loads_task_selected_components_with_encoder_ownership(
component_path = tmp_path / component_partition
assert created["video_vae"] == [str(component_path / "video_vae")]
assert created["audio_vae"] == [str(component_path / "audio_vae")]
# The checkpoint VAEs execute the modeling code shipped with the weights, so
# the pipeline must hand them the engine's trust_remote_code decision. Both
# VAEs are created for every parametrization, so this stays unconditional.
for component in ("video_vae", "audio_vae"):
assert [kwargs["trust_remote_code"] for kwargs in created_kwargs[component]] == [trust_remote_code]
# The tokenizer/processor "loaded once" assertions are asserted per
# load_text_encoder branch below, matching upstream's own expectations.
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)
@@ -1122,6 +1134,64 @@ def test_packed_attention_keeps_padding_mask_for_other_backends():
)
def test_ring_packed_attention_publishes_prefix_length_without_global_mask():
attention = _fake_packed_attention("CUDNN_ATTN")
attention.attention.use_ring = True
q = torch.randn(4, 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,
)
metadata = attention.attention.metadata
assert metadata.attn_mask is None
assert metadata.extra["valid_kv_length"] == 5
def test_ring_packed_attention_keeps_mask_when_runtime_sp_is_inactive(monkeypatch):
from vllm_omni.diffusion.models.minimax_h3 import minimax_h3_transformer
class InactiveContext:
sp_active = False
attention = _fake_packed_attention("CUDNN_ATTN")
attention.attention.use_ring = True
monkeypatch.setattr(
minimax_h3_transformer,
"is_forward_context_available",
lambda: True,
raising=False,
)
monkeypatch.setattr(
minimax_h3_transformer,
"get_forward_context",
InactiveContext,
raising=False,
)
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,
)
metadata = attention.attention.metadata
assert torch.equal(
metadata.attn_mask,
torch.tensor([[True, True, True, True, True, False, False, False]]),
)
assert metadata.extra["npu_attn_varlen"] is True
def _fake_packed_attention(
backend_name: str,
*,
@@ -1143,6 +1213,10 @@ def _fake_packed_attention(
def supports_multi_doc_packed_varlen(cls) -> bool:
return supports_multi_doc
@classmethod
def supports_packed_mask_free(cls) -> bool:
return False
class FakeAttention(torch.nn.Module):
attn_backend = FakeBackend
@@ -1712,7 +1786,7 @@ def test_video_vae_keeps_reference_fp32_weights(monkeypatch):
monkeypatch.setattr(
vae_module,
"_load_remote_component",
lambda _path, _config: FakeRemote(),
lambda _path, _config, *, trust_remote_code: FakeRemote(),
)
video_vae = vae_module.MiniMaxH3VideoVAE(
@@ -1834,7 +1908,7 @@ def test_video_vae_can_load_on_cpu_for_staged_gpu_residency(monkeypatch):
monkeypatch.setattr(
vae_module,
"_load_remote_component",
lambda _path, _config: FakeRemote(),
lambda _path, _config, *, trust_remote_code: FakeRemote(),
)
video_vae = vae_module.MiniMaxH3VideoVAE(
@@ -1869,7 +1943,7 @@ def test_video_vae_uses_snapshot_stager_for_accelerator_target(monkeypatch):
monkeypatch.setattr(
vae_module,
"_load_remote_component",
lambda _path, _config: FakeRemote(),
lambda _path, _config, *, trust_remote_code: FakeRemote(),
)
monkeypatch.setattr(vae_module, "PinnedModuleStager", stager_cls)
@@ -1890,6 +1964,123 @@ def test_video_vae_uses_snapshot_stager_for_accelerator_target(monkeypatch):
stager.offload.assert_called_once_with()
def test_remote_component_refuses_untrusted_checkpoint_code(monkeypatch):
from vllm_omni.diffusion.models.minimax_h3 import vae as vae_module
loaded = []
def spy_get_class(class_reference, component_path):
loaded.append((class_reference, component_path))
raise AssertionError("checkpoint code must not be resolved when untrusted")
monkeypatch.setattr(vae_module, "get_class_from_dynamic_module", spy_get_class)
with pytest.raises(ValueError, match="--trust-remote-code"):
vae_module._load_remote_component(
"/checkpoints/video_vae",
{"auto_map": {"AutoModel": "modeling_vae.VideoVAE"}},
trust_remote_code=False,
)
assert loaded == []
def test_remote_component_resolves_checkpoint_code_when_trusted(monkeypatch):
from vllm_omni.diffusion.models.minimax_h3 import vae as vae_module
class FakeComponent(torch.nn.Module):
@classmethod
def from_pretrained(cls, component_path):
module = cls()
module.loaded_from = component_path
return module
loaded = []
def spy_get_class(class_reference, component_path):
loaded.append((class_reference, component_path))
return FakeComponent
monkeypatch.setattr(vae_module, "get_class_from_dynamic_module", spy_get_class)
module = vae_module._load_remote_component(
"/checkpoints/video_vae",
{"auto_map": {"AutoModel": "modeling_vae.VideoVAE"}},
trust_remote_code=True,
)
# Negative control: the trusted path stays byte-for-byte the pre-gate call.
assert loaded == [("modeling_vae.VideoVAE", "/checkpoints/video_vae")]
assert module.loaded_from == "/checkpoints/video_vae"
@pytest.mark.parametrize("vae_cls_name", ["MiniMaxH3VideoVAE", "MiniMaxH3AudioVAE"])
@pytest.mark.parametrize("trusted", [True, False])
def test_vae_forwards_trust_remote_code_to_loader(monkeypatch, vae_cls_name, trusted):
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)
forwarded = []
def spy_load(_component_path, _config, *, trust_remote_code):
forwarded.append(trust_remote_code)
return FakeRemote()
monkeypatch.setattr(
vae_module,
"_load_component_config",
lambda _path: {
"latent_channels": 1,
"latents_mean": [0.0],
"latents_std": [1.0],
"sample_rate": 32000,
},
)
monkeypatch.setattr(vae_module, "_load_remote_component", spy_load)
vae_cls = getattr(vae_module, vae_cls_name)
vae_cls(
"unused",
device=torch.device("cpu"),
trust_remote_code=trusted,
)
assert forwarded == [trusted]
@pytest.mark.parametrize("vae_cls_name", ["MiniMaxH3VideoVAE", "MiniMaxH3AudioVAE"])
def test_vae_defaults_to_refusing_checkpoint_code(monkeypatch, vae_cls_name):
from vllm_omni.diffusion.models.minimax_h3 import vae as vae_module
forwarded = []
def spy_load(_component_path, _config, *, trust_remote_code):
forwarded.append(trust_remote_code)
raise AssertionError("unreachable")
monkeypatch.setattr(
vae_module,
"_load_component_config",
lambda _path: {
"latent_channels": 1,
"latents_mean": [0.0],
"latents_std": [1.0],
"sample_rate": 32000,
},
)
monkeypatch.setattr(vae_module, "_load_remote_component", spy_load)
vae_cls = getattr(vae_module, vae_cls_name)
with pytest.raises(AssertionError, match="unreachable"):
vae_cls("unused", device=torch.device("cpu"))
assert forwarded == [False]
def test_video_vae_encode_uses_configured_parallel_tiling():
from vllm_omni.diffusion.models.minimax_h3.vae import (
MiniMaxH3VideoVAE,
@@ -2889,3 +3080,112 @@ def test_encoder_load_places_every_source_in_its_own_rows(tmp_path):
}
for source, rows in slices.items():
assert torch.equal(rows, torch.full_like(rows, _SOURCE_FILL[source])), source
def _pad_seq_len_batch(pad_seq_len):
"""A t2va request that pins the packed length through ``extra_args``."""
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.diffusion.worker.request_batch import DiffusionRequestBatch
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
extra_args = {"task": "t2va", "aspect_ratio": "16:9"}
if pad_seq_len is not None:
extra_args["pad_seq_len"] = pad_seq_len
sampling = OmniDiffusionSamplingParams(
quality="lossless",
width=1344,
height=768,
fps=24,
num_frames=124,
num_inference_steps=None,
extra_args=extra_args,
)
return DiffusionRequestBatch(
[
OmniDiffusionRequest(
prompt="pinned packed length",
sampling_params=sampling,
request_id="pad-seq-len",
)
]
)
@pytest.mark.parametrize("pinned, expected", [(None, None), (54080, 54080)])
def test_pad_seq_len_reaches_diffuse_from_extra_args(pinned, expected):
"""The request field must arrive at ``diffuse``, not stop at validation."""
diffuse_calls: list[dict[str, Any]] = []
pipeline = _distilled_pipeline(diffuse_calls, {"fl2va": None, "ref2va": None})
pipeline.forward(_pad_seq_len_batch(pinned))
assert [call["pad_seq_len"] for call in diffuse_calls] == [expected]
@pytest.mark.parametrize("task", ["t2va", "ref2va"])
def test_pad_seq_len_reaches_the_packer_for_every_task_family(task, monkeypatch):
"""``diffuse`` must hand the pin to whichever packer the task uses."""
import vllm_omni.diffusion.models.minimax_h3.pipeline_minimax_h3 as module
class _CapturedError(Exception):
pass
recorded = {}
def _capture(**kwargs):
recorded.update(kwargs)
raise _CapturedError
monkeypatch.setattr(module, "minimax_h3_packed_sequence", _capture)
monkeypatch.setattr(module, "minimax_h3_packed_sequence_ref2va_blocks", _capture)
pipeline = _distilled_pipeline([], {"fl2va": None, "ref2va": None})
pipeline._initial_noise = lambda **kwargs: (torch.zeros(1), torch.zeros(1))
with pytest.raises(_CapturedError):
pipeline._build_denoise_inputs(
task=task,
text_embeddings=torch.ones(7, 2),
text_tags=torch.ones(7, dtype=torch.long),
seed=0,
latent_t=2,
latent_h=4,
latent_w=6,
audio_t=3,
num_frames=5,
num_steps=2,
video_shift=12.0,
audio_shift=3.0,
base_schedule=None,
visual_condition=None,
visual_condition_shape=(1, 4, 6),
audio_condition=None,
ref_audio_t=1,
pad_seq_len=192,
)
assert recorded["seq_len"] == 192
@pytest.mark.parametrize("value", [0, -64, 100, "128", True, 1.5, (1 << 20) + 64])
def test_pad_seq_len_request_validation_rejects_bad_values(value):
from vllm_omni.diffusion.models.minimax_h3.pipeline_minimax_h3 import (
_resolve_pad_seq_len,
)
from vllm_omni.errors import OmniClientError
with pytest.raises(OmniClientError):
_resolve_pad_seq_len(value)
def test_pad_seq_len_request_validation_accepts_aligned_values():
from vllm_omni.diffusion.models.minimax_h3.packed_sequence import (
MINIMAX_H3_MAX_PAD_SEQ_LEN,
)
from vllm_omni.diffusion.models.minimax_h3.pipeline_minimax_h3 import (
_resolve_pad_seq_len,
)
assert _resolve_pad_seq_len(None) is None
assert _resolve_pad_seq_len(54080) == 54080
assert _resolve_pad_seq_len(MINIMAX_H3_MAX_PAD_SEQ_LEN) == MINIMAX_H3_MAX_PAD_SEQ_LEN
@@ -1,7 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from contextlib import contextmanager
import weakref
from contextlib import contextmanager, nullcontext
from types import SimpleNamespace
from typing import Any
from unittest.mock import Mock
@@ -76,6 +77,247 @@ def test_requested_model_offload_without_backend_uses_resident_components():
pipeline.text_encoder.encode_ids.assert_called_once()
def test_decoded_output_is_unchanged_without_active_model_offload():
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
pipeline = object.__new__(MiniMaxH3Pipeline)
torch.nn.Module.__init__(pipeline)
pipeline._model_cpu_offload_modules = []
output = Mock()
assert pipeline._offload_model_cpu_stage_output(output) is output
output.cpu.assert_not_called()
def test_reply_rank_copies_decoded_output_to_host(monkeypatch):
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
pipeline = object.__new__(MiniMaxH3Pipeline)
torch.nn.Module.__init__(pipeline)
pipeline._model_cpu_offload_modules = [Mock()]
monkeypatch.setattr(pipeline, "_is_output_owner_rank", lambda: True)
output = Mock()
host_output = Mock()
output.cpu.return_value = host_output
assert pipeline._offload_model_cpu_stage_output(output) is host_output
output.cpu.assert_called_once_with()
def test_non_reply_rank_drops_decoded_output_storage(monkeypatch):
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
pipeline = object.__new__(MiniMaxH3Pipeline)
torch.nn.Module.__init__(pipeline)
pipeline._model_cpu_offload_modules = [Mock()]
monkeypatch.setattr(pipeline, "_is_output_owner_rank", lambda: False)
output = torch.ones((2, 3), dtype=torch.float16)
placeholder = pipeline._offload_model_cpu_stage_output(output)
assert placeholder.device.type == "meta"
assert placeholder.shape == output.shape
assert placeholder.dtype == output.dtype
def test_decode_releases_only_audio_before_return(monkeypatch):
"""Video is released by the callers of ``decode`` -- after quantization."""
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
from vllm_omni.diffusion.models.minimax_h3 import pipeline_minimax_h3 as module
video = torch.ones((1, 1, 4, 4))
audio = torch.ones((1, 8))
pipeline = SimpleNamespace(
device=torch.device("cpu"),
video_vae=Mock(decode_latent=Mock(return_value=video)),
audio_vae=Mock(decode_latent=Mock(return_value=audio)),
_component_on_device=lambda component: nullcontext(),
_offload_model_cpu_stage_output=Mock(side_effect=lambda value: value),
)
monkeypatch.setattr(
module.current_omni_platform,
"create_autocast_context",
lambda **kwargs: nullcontext(),
)
actual_video, actual_audio = MiniMaxH3Pipeline.decode(
pipeline,
torch.zeros(1),
torch.zeros(1),
height=2,
width=3,
)
assert actual_video.shape[-2:] == (2, 3)
assert actual_audio is audio
calls = pipeline._offload_model_cpu_stage_output.call_args_list
assert len(calls) == 1
assert calls[0].args[0] is audio
def _recording_release(events, releases):
"""Stand-in for ``_offload_model_cpu_stage_output`` that records what it got."""
def release(value):
events.append("release")
releases.append(value)
return value
return release
def test_forward_releases_video_after_quantization_and_before_next_seed(monkeypatch):
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
# MiniMaxH3VideoVAE.decode_latent ends in frames.float(), so this is what
# the release hook would see if it ran before the quantizer.
decoded_dtype = torch.float32
events: list[str] = []
releases: list[torch.Tensor] = []
def diffuse(**kwargs):
events.append(f"diffuse:{kwargs['seed']}")
return torch.zeros(1), torch.zeros(1)
def decode(video_latent, audio_latent, *, height, width):
events.append("decode")
return (
torch.zeros((1, 3, 2, height, width), dtype=decoded_dtype),
torch.zeros((1, 8)),
)
# ``forward`` indexes ``preencode_mp4`` (#7018): this test drives the
# tensor-decode branch, which is the one the release hook wraps.
context = {"num_outputs": 2, "seed": 41, "height": 4, "width": 4, "preencode_mp4": False}
pipeline = SimpleNamespace(
od_config=SimpleNamespace(),
_extract_prompt=lambda raw: ("a prompt", {}),
_extract_text_conditioning=lambda raw: None,
_extract_prepared_reference_videos=lambda raw: None,
_prepare_request_inputs=lambda **kwargs: context,
_denoise_kwargs=lambda ctx: {},
diffuse=diffuse,
decode=decode,
_offload_model_cpu_stage_output=_recording_release(events, releases),
)
request = SimpleNamespace(prompts=["a prompt"], sampling_params=SimpleNamespace())
output = MiniMaxH3Pipeline.forward(pipeline, request)
# The release must see the quantized frames, not the decoded floats.
assert len(releases) == 2
assert [tensor.dtype for tensor in releases] == [torch.uint8, torch.uint8]
assert decoded_dtype is not torch.uint8
# ... and it must still happen before the next seed reloads the DiT.
assert events == ["diffuse:41", "decode", "release", "diffuse:42", "decode", "release"]
assert output.output[0].dtype is torch.uint8
def test_forward_drops_the_decoded_video_before_the_next_seed_diffuses(monkeypatch):
"""Releasing the copy is not enough if a local still pins the original.
``_offload_model_cpu_stage_output`` hands back a *new* tensor, so the
decoded frames stay alive for as long as anything still references them.
The whole point of this branch is that they are gone before the next seed
reloads the DiT, and ``diffuse`` is where that reload happens.
"""
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
decoded_refs: list[weakref.ref] = []
alive_at_diffuse: list[int] = []
def diffuse(**kwargs):
alive_at_diffuse.append(sum(1 for ref in decoded_refs if ref() is not None))
return torch.zeros(1), torch.zeros(1)
def decode(video_latent, audio_latent, *, height, width):
video = torch.zeros((1, 3, 2, height, width), dtype=torch.float32)
decoded_refs.append(weakref.ref(video))
return video, torch.zeros((1, 8))
# ``forward`` indexes ``preencode_mp4`` (#7018): this test drives the
# tensor-decode branch, which is the one the release hook wraps.
context = {"num_outputs": 2, "seed": 41, "height": 4, "width": 4, "preencode_mp4": False}
pipeline = SimpleNamespace(
od_config=SimpleNamespace(),
_extract_prompt=lambda raw: ("a prompt", {}),
_extract_text_conditioning=lambda raw: None,
_extract_prepared_reference_videos=lambda raw: None,
_prepare_request_inputs=lambda **kwargs: context,
_denoise_kwargs=lambda ctx: {},
diffuse=diffuse,
decode=decode,
_offload_model_cpu_stage_output=lambda value: value.clone(),
)
request = SimpleNamespace(prompts=["a prompt"], sampling_params=SimpleNamespace())
MiniMaxH3Pipeline.forward(pipeline, request)
assert len(decoded_refs) == 2
# Seed 1's diffuse sees nothing decoded yet; seed 2's must not see seed 1's
# decoded frames still resident.
assert alive_at_diffuse == [0, 0]
def test_post_decode_releases_video_after_quantization(monkeypatch):
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
from vllm_omni.diffusion.models.minimax_h3 import pipeline_minimax_h3 as module
events: list[str] = []
releases: list[torch.Tensor] = []
def decode(video_latent, audio_latent, *, height, width):
events.append("decode")
return (
torch.zeros((1, 3, 2, height, width), dtype=torch.float16),
torch.zeros((1, 8)),
)
pipeline = SimpleNamespace(
od_config=SimpleNamespace(),
_unpack_denoised_rows=lambda *args, **kwargs: (torch.zeros(1), torch.zeros(1)),
decode=decode,
_offload_model_cpu_stage_output=_recording_release(events, releases),
)
state = SimpleNamespace(
latents=torch.zeros(1),
extra={
module._STEP_BRANCH: object(),
module._STEP_AUDIO_ROWS: torch.zeros(1),
module._STEP_SHAPE: {
"latent_t": 1,
"latent_h": 1,
"latent_w": 1,
"audio_t": 1,
"height": 4,
"width": 4,
},
},
)
output = MiniMaxH3Pipeline.post_decode(pipeline, state)
assert events == ["decode", "release"]
assert len(releases) == 1
assert releases[0].dtype is torch.uint8
assert output.output[0] is releases[0]
@pytest.mark.parametrize(
("available", "initialized", "rank", "expected"),
[(False, False, 7, True), (True, False, 7, True), (True, True, 0, True), (True, True, 1, False)],
)
def test_output_owner_rank_matches_executor_contract(monkeypatch, available, initialized, rank, expected):
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
from vllm_omni.diffusion.models.minimax_h3 import pipeline_minimax_h3 as module
monkeypatch.setattr(module.dist, "is_available", lambda: available)
monkeypatch.setattr(module.dist, "is_initialized", lambda: initialized)
monkeypatch.setattr(module.dist, "get_rank", lambda: rank)
assert MiniMaxH3Pipeline._is_output_owner_rank() is expected
def test_h3_model_cpu_offload_registers_direct_vae_stages(monkeypatch):
from vllm_omni.diffusion.models.minimax_h3 import MiniMaxH3Pipeline
from vllm_omni.diffusion.models.minimax_h3 import pipeline_minimax_h3 as module
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import pytest
import torch
@@ -190,3 +190,90 @@ def test_condition_noise_accepts_a_reference_video_longer_than_the_target():
noise_aug=0.5,
)
assert result.shape == rows.shape
def test_explicit_seq_len_pins_one_shape_across_prompt_lengths():
"""Prompts of different token counts must land on the same packed length.
Without the pin they fall into different 64-row buckets, and each bucket is
a packed shape the compiled transformer has not seen before.
"""
from vllm_omni.diffusion.models.minimax_h3.packed_sequence import (
minimax_h3_packed_sequence,
)
common = dict(
latent_t=2,
latent_h=4,
latent_w=6,
audio_t=3,
include_keyframe_cond=False,
)
# used = text_len + 18 rows: 22 -> bucket 64; 118 -> bucket 128.
short = minimax_h3_packed_sequence(text_len=4, **common)
long = minimax_h3_packed_sequence(text_len=100, **common)
assert int(short["seq_len"]) == 64
assert int(long["seq_len"]) == 128
short_pinned = minimax_h3_packed_sequence(text_len=4, seq_len=192, **common)
long_pinned = minimax_h3_packed_sequence(text_len=100, seq_len=192, **common)
assert int(short_pinned["seq_len"]) == int(long_pinned["seq_len"]) == 192
# The pin only adds padding rows; the used prefix is untouched.
assert int(short_pinned["cu_seqlens"][1]) == int(short["cu_seqlens"][1]) == 22
assert int(long_pinned["cu_seqlens"][1]) == int(long["cu_seqlens"][1]) == 118
assert short_pinned["img_pos"].tolist() == short["img_pos"].tolist()
def test_the_ref2va_packer_pins_one_shape_across_prompt_lengths_too():
"""Both packers share one resolver, so both honour the pin."""
from vllm_omni.diffusion.models.minimax_h3.packed_sequence import (
minimax_h3_packed_sequence_ref2va_blocks,
)
common = dict(
latent_t=2,
latent_h=4,
latent_w=6,
audio_t=3,
ref_blocks=[{"kind": "audio", "ref_audio_t": 1}],
)
short = minimax_h3_packed_sequence_ref2va_blocks(text_len=4, **common)
long = minimax_h3_packed_sequence_ref2va_blocks(text_len=100, **common)
assert int(short["seq_len"]) != int(long["seq_len"])
short_pinned = minimax_h3_packed_sequence_ref2va_blocks(text_len=4, seq_len=192, **common)
long_pinned = minimax_h3_packed_sequence_ref2va_blocks(text_len=100, seq_len=192, **common)
assert int(short_pinned["seq_len"]) == int(long_pinned["seq_len"]) == 192
assert int(short_pinned["cu_seqlens"][1]) == int(short["cu_seqlens"][1])
assert int(long_pinned["cu_seqlens"][1]) == int(long["cu_seqlens"][1])
def test_explicit_seq_len_below_used_rows_is_a_client_error():
"""Both packers reject an under-sized pin as a 4xx, not a server error."""
from vllm_omni.diffusion.models.minimax_h3.packed_sequence import (
minimax_h3_packed_sequence,
minimax_h3_packed_sequence_ref2va_blocks,
)
from vllm_omni.errors import OmniClientError
with pytest.raises(OmniClientError, match="used rows"):
minimax_h3_packed_sequence(
text_len=4,
latent_t=2,
latent_h=4,
latent_w=6,
audio_t=3,
include_keyframe_cond=False,
seq_len=8,
)
with pytest.raises(OmniClientError, match="used rows"):
minimax_h3_packed_sequence_ref2va_blocks(
text_len=4,
latent_t=2,
latent_h=4,
latent_w=6,
audio_t=3,
ref_blocks=[{"kind": "audio", "ref_audio_t": 1}],
seq_len=8,
)
@@ -0,0 +1,473 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""CPU tests for the pinned tiled-VAE gather buffer in the MiniMax-H3 video VAE.
The checkpoint's ``_all_gather_tiled_results`` allocates its gather output on
every call, so with a per-request ``empty_cache()`` the collective lands on a
new device address every request and XCCL's per-address registrations pile up.
On XPU the adapter replaces that method with an equal-shape gather into a
buffer it keeps alive. These tests pin the properties that make it a fix
rather than a rewrite: the address is stable, the tiles come back
bit-identical and in the same order, the caller owns what it gets back, and no
other device is touched.
"""
import logging
import sys
import types
from typing import Any
import pytest
import torch
import torch.distributed as dist
from vllm_omni.diffusion.models.minimax_h3 import vae as vae_module
from vllm_omni.diffusion.models.minimax_h3.vae import MiniMaxH3VideoVAE
pytestmark = [pytest.mark.core_model, pytest.mark.cpu, pytest.mark.diffusion]
TILE = (1, 3, 2, 4, 4)
class _CheckpointGatherSentinel:
"""Stands in for the checkpoint's own bound method.
Identity is the point: a test asserts this exact object is still installed
on the devices the adapter must leave alone.
"""
def __call__(self, tasks, num_tiles): # pragma: no cover - never invoked
raise AssertionError("the checkpoint gather must not run in these tests")
class _FakeCheckpointModel:
def __init__(self):
self._all_gather_tiled_results = _CheckpointGatherSentinel()
def _local_tiles(all_tiles, num_tiles, rank, sp_size):
"""One rank's round-robin share, as ``klvae.py:275`` computes it."""
return [all_tiles[index] for index in range(rank, num_tiles, sp_size)]
def _reference_gather(all_tiles, num_tiles, sp_size):
"""The checkpoint's variable-shape list gather, reimplemented verbatim.
Written from the control flow of ``_all_gather_tiled_results`` in the
checkpoint's ``klvae.py`` (lines 252-273) -- stack each rank's share, gather
the list, walk it back into global tile order -- so that it is an
independent oracle rather than a restatement of the code under test.
"""
gathered = [torch.stack(_local_tiles(all_tiles, num_tiles, rank, sp_size), dim=0) for rank in range(sp_size)]
results = [None] * num_tiles
for rank, rank_tensors in enumerate(gathered):
for k in range(rank_tensors.shape[0]):
global_index = k * sp_size + rank
if global_index >= num_tiles:
break
results[global_index] = rank_tensors[k]
return results
def _peer_stacks(all_tiles, num_tiles, sp_size):
"""What every rank hands to the collective, padded the way the adapter pads."""
max_tasks = -(-num_tiles // sp_size)
stacks = []
for rank in range(sp_size):
share = _local_tiles(all_tiles, num_tiles, rank, sp_size)
stacked = share[0].new_zeros((max_tasks, *share[0].shape))
stacked[: len(share)] = torch.stack(share, dim=0)
stacks.append(stacked)
return stacks
def _tiles(num_tiles, seed=0, dtype=torch.float32):
generator = torch.Generator().manual_seed(seed)
return [torch.rand(TILE, generator=generator).to(dtype) for _ in range(num_tiles)]
def _workspace_bytes(num_tiles, sp_size, dtype=torch.float32):
"""Bytes one gather needs: ``sp_size`` copies of the padded local stack."""
max_tasks = -(-num_tiles // sp_size)
element_size = torch.empty(0, dtype=dtype).element_size()
numel = 1
for dim in TILE:
numel *= dim
return sp_size * max_tasks * numel * element_size
@pytest.fixture
def fake_collective(monkeypatch):
"""Replace the collective with a single-process stand-in.
``all_gather_into_tensor`` is the only distributed call on this path, so
filling the output buffer from a caller-supplied peer list exercises the
real padding, keying, addressing and unpacking code without a process
group.
"""
calls: list[dict[str, Any]] = []
def fake_all_gather_into_tensor(output, input_tensor, group=None, async_op=False):
peers = calls[-1]["peers"]
assert output.shape == (len(peers), *input_tensor.shape)
calls[-1]["sent"] = input_tensor.clone()
calls[-1]["buf_ptr"] = output.data_ptr()
for rank, peer in enumerate(peers):
output[rank].copy_(peer)
monkeypatch.setattr(dist, "all_gather_into_tensor", fake_all_gather_into_tensor)
return calls
class _Records(logging.Handler):
"""vllm's logger sets ``propagate=False``, so caplog never sees these."""
def __init__(self):
super().__init__(level=logging.DEBUG)
self.messages = []
self.records = []
def emit(self, record):
self.messages.append(record.getMessage())
self.records.append((record.levelno, record.getMessage()))
@pytest.fixture
def gather_log():
handler = _Records()
logger = vae_module.logger
previous = logger.level
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
try:
yield handler
finally:
logger.removeHandler(handler)
logger.setLevel(previous)
def _vae(sp_size, sp_rank=0):
"""An adapter instance with the override installed, no checkpoint needed."""
state = {
"sp_size": sp_size,
"sp_rank": sp_rank,
"sp_enabled": sp_size > 1,
"sp_process_group": object(),
}
module = types.ModuleType("fake_gather_ckpt.parallel")
module.get_parallel_state = lambda: state # type: ignore[attr-defined]
sys.modules.setdefault("fake_gather_ckpt", types.ModuleType("fake_gather_ckpt"))
sys.modules["fake_gather_ckpt.parallel"] = module
vae = object.__new__(MiniMaxH3VideoVAE)
vae.remote = type("Remote", (), {"__module__": "fake_gather_ckpt.klvae"})()
vae.model = _FakeCheckpointModel()
vae._device_target = torch.device("xpu")
vae._tile_gather_workspace = None
vae._tile_gather_stats = {"hits": 0, "allocs": 0, "workspace_bytes": 0}
vae._checkpoint_tile_gather = None
vae._install_persistent_tile_gather()
return vae
def _run(vae, calls, all_tiles, num_tiles, sp_size, sp_rank):
calls.append({"peers": _peer_stacks(all_tiles, num_tiles, sp_size)})
return vae.model._all_gather_tiled_results(_local_tiles(all_tiles, num_tiles, sp_rank, sp_size), num_tiles)
# --------------------------------------------------------------------------
# The address is stable across calls. This is what the accumulation is about:
# drop the workspace and every call registers a new receive address. And the
# workspace is one grow-only block, so varying request geometries cannot keep
# adding resident buffers the way a per-geometry cache would.
# --------------------------------------------------------------------------
def test_the_landing_buffer_address_is_stable_across_calls(fake_collective):
vae = _vae(4)
pointers = []
for request in range(3):
_run(vae, fake_collective, _tiles(8, seed=request), 8, 4, 0)
pointers.append(fake_collective[-1]["buf_ptr"])
assert len(set(pointers)) == 1, f"gather buffer moved across calls: {pointers}"
assert vae._tile_gather_stats == {
"hits": 2,
"allocs": 1,
"workspace_bytes": _workspace_bytes(8, 4),
}
assert vae._tile_gather_workspace.numel() == _workspace_bytes(8, 4)
assert vae._tile_gather_workspace.dtype is torch.uint8
def test_distinct_shapes_share_the_one_workspace(fake_collective):
"""One decode gathers several temporal clips, and the head/tail clips differ."""
vae = _vae(4)
_run(vae, fake_collective, _tiles(8), 8, 4, 0)
first = fake_collective[-1]["buf_ptr"]
_run(vae, fake_collective, _tiles(12), 12, 4, 0)
second = fake_collective[-1]["buf_ptr"]
_run(vae, fake_collective, _tiles(8, seed=5), 8, 4, 0)
third = fake_collective[-1]["buf_ptr"]
# The larger geometry did not fit, so the block was replaced -- not added to.
assert second == third
assert vae._tile_gather_stats["allocs"] == 2
assert vae._tile_gather_stats["hits"] == 1
assert vae._tile_gather_stats["workspace_bytes"] == _workspace_bytes(12, 4)
# ``first`` is deliberately not compared: the replaced block is freed, so
# the allocator is free to hand its address back for the larger one.
assert isinstance(first, int)
def test_workspace_is_bounded_across_geometries(fake_collective):
"""Alternating geometries must not each retain a buffer of their own."""
sp_size = 4
large, small, largest = 12, 8, 20
vae = _vae(sp_size)
pointers = []
for round_index in range(3):
for num_tiles in (large, small):
_run(vae, fake_collective, _tiles(num_tiles, seed=round_index), num_tiles, sp_size, 0)
pointers.append(fake_collective[-1]["buf_ptr"])
assert len(pointers) == 6
assert len(set(pointers)) == 1, f"workspace moved across geometries: {pointers}"
assert vae._tile_gather_stats["allocs"] == 1
assert vae._tile_gather_stats["workspace_bytes"] == _workspace_bytes(large, sp_size)
assert vae._tile_gather_workspace.numel() == _workspace_bytes(large, sp_size)
# A geometry that does not fit grows the single block; it does not add one.
_run(vae, fake_collective, _tiles(largest), largest, sp_size, 0)
grown = fake_collective[-1]["buf_ptr"]
assert vae._tile_gather_stats["allocs"] == 2
assert vae._tile_gather_stats["workspace_bytes"] == _workspace_bytes(largest, sp_size)
assert vae._tile_gather_workspace.numel() == _workspace_bytes(largest, sp_size)
# ...and the geometries seen before it now fit, so nothing reallocates again.
for num_tiles in (large, small):
_run(vae, fake_collective, _tiles(num_tiles, seed=7), num_tiles, sp_size, 0)
assert fake_collective[-1]["buf_ptr"] == grown
assert vae._tile_gather_stats["allocs"] == 2
assert vae._tile_gather_stats["workspace_bytes"] == _workspace_bytes(largest, sp_size)
def test_workspace_grows_only_upward(fake_collective):
"""Resident bytes track the largest geometry seen, not the sum of them."""
sp_size = 4
small, large = 8, 12
vae = _vae(sp_size)
_run(vae, fake_collective, _tiles(small), small, sp_size, 0)
assert vae._tile_gather_stats["workspace_bytes"] == _workspace_bytes(small, sp_size)
_run(vae, fake_collective, _tiles(large), large, sp_size, 0)
assert vae._tile_gather_stats["allocs"] == 2
assert vae._tile_gather_stats["hits"] == 0
assert vae._tile_gather_stats["workspace_bytes"] == _workspace_bytes(large, sp_size)
assert vae._tile_gather_stats["workspace_bytes"] < _workspace_bytes(small, sp_size) + _workspace_bytes(
large, sp_size
)
assert vae._tile_gather_workspace.numel() == _workspace_bytes(large, sp_size)
def test_mixed_dtype_shares_one_workspace(fake_collective):
"""The workspace is bytes, so a narrower dtype reuses it instead of adding one."""
sp_size, num_tiles = 4, 8
vae = _vae(sp_size)
fp32_bytes = _workspace_bytes(num_tiles, sp_size, dtype=torch.float32)
pointers = []
for round_index in range(2):
for dtype in (torch.float32, torch.bfloat16):
tiles = _tiles(num_tiles, seed=round_index, dtype=dtype)
produced = _run(vae, fake_collective, tiles, num_tiles, sp_size, 0)
pointers.append(fake_collective[-1]["buf_ptr"])
assert all(tile.dtype is dtype for tile in produced)
for index, tile in enumerate(produced):
assert torch.equal(tile, tiles[index])
assert len(set(pointers)) == 1, f"workspace moved across dtypes: {pointers}"
assert vae._tile_gather_stats["allocs"] == 1
assert vae._tile_gather_stats["hits"] == 3
assert vae._tile_gather_stats["workspace_bytes"] == fp32_bytes
assert vae._tile_gather_workspace.numel() == fp32_bytes
# --------------------------------------------------------------------------
# Equivalence with the checkpoint's own gather, padded branch included. A
# reordered tile grid is silent at runtime, so order is asserted as well.
# --------------------------------------------------------------------------
@pytest.mark.parametrize("num_tiles", [5, 8, 9, 28])
@pytest.mark.parametrize("sp_rank", [0, 3])
def test_gathered_tiles_match_the_checkpoint_gather(fake_collective, num_tiles, sp_rank):
sp_size = 4
all_tiles = _tiles(num_tiles, seed=num_tiles)
vae = _vae(sp_size, sp_rank=sp_rank)
produced = _run(vae, fake_collective, all_tiles, num_tiles, sp_size, sp_rank)
expected = _reference_gather(all_tiles, num_tiles, sp_size)
assert len(produced) == num_tiles
assert all(tile is not None for tile in produced)
for index, (got, want) in enumerate(zip(produced, expected)):
assert torch.equal(got, want), f"tile {index} differs from the checkpoint gather"
# ...and it is the tile that belongs at that index, not a permutation that
# happens to agree with an equally permuted oracle.
for index, tile in enumerate(produced):
assert torch.equal(tile, all_tiles[index])
def test_the_padded_branch_is_actually_exercised(fake_collective):
"""9 tiles over 4 ranks: ranks 1-3 are one short, so the padding runs."""
sp_size, num_tiles = 4, 9
all_tiles = _tiles(num_tiles, seed=9)
assert len(_local_tiles(all_tiles, num_tiles, 1, sp_size)) < -(-num_tiles // sp_size)
vae = _vae(sp_size, sp_rank=1)
produced = _run(vae, fake_collective, all_tiles, num_tiles, sp_size, 1)
sent = fake_collective[-1]["sent"]
assert sent.shape[0] == 3
assert torch.equal(sent[2], torch.zeros_like(sent[2]))
for index, tile in enumerate(produced):
assert torch.equal(tile, all_tiles[index])
def test_an_empty_local_share_still_raises(fake_collective):
"""A rank with no tiles fails loudly, exactly as the checkpoint does."""
vae = _vae(4)
with pytest.raises(ValueError, match="empty tasks on sp rank"):
vae.model._all_gather_tiled_results([], 8)
def test_more_tiles_than_round_robin_allows_is_refused(fake_collective):
"""The unpacking arithmetic is only valid for the checkpoint's ownership."""
vae = _vae(4)
with pytest.raises(ValueError, match="round-robin"):
vae.model._all_gather_tiled_results(_tiles(5), 8)
# --------------------------------------------------------------------------
# Ownership: the buffer is overwritten by the next gather, so returned tiles
# must not alias it.
# --------------------------------------------------------------------------
def test_returned_tiles_do_not_alias_the_pinned_buffer(fake_collective):
sp_size, num_tiles = 4, 8
first_tiles = _tiles(num_tiles, seed=1)
vae = _vae(sp_size)
produced = _run(vae, fake_collective, first_tiles, num_tiles, sp_size, 0)
buffer_storage = vae._tile_gather_workspace.untyped_storage().data_ptr()
for tile in produced:
assert tile.untyped_storage().data_ptr() != buffer_storage
# The next gather reuses that memory; the first call's tiles must survive it.
_run(vae, fake_collective, _tiles(num_tiles, seed=2), num_tiles, sp_size, 0)
for index, tile in enumerate(produced):
assert torch.equal(tile, first_tiles[index])
# --------------------------------------------------------------------------
# Non-XPU behaviour is unchanged: the checkpoint's own method stays installed.
# --------------------------------------------------------------------------
def _construct(monkeypatch, device, model):
"""Run the real ``__init__`` against a stub checkpoint component."""
class _Remote(torch.nn.Module):
def __init__(self):
super().__init__()
self.weight = torch.nn.Parameter(torch.zeros(1))
self.model = model
monkeypatch.setattr(
vae_module,
"_load_component_config",
lambda path: {"latent_channels": 1, "latents_mean": [0.0], "latents_std": [1.0]},
)
# **kwargs: the loader gained a trust_remote_code argument at some point,
# and this stub does not care either way.
monkeypatch.setattr(vae_module, "_load_remote_component", lambda path, config, **kwargs: _Remote())
# The adapter only stores this handle; resolving it for real needs an
# accelerator, which a CPU test runner does not have.
monkeypatch.setattr(torch, "get_device_module", lambda *args, **kwargs: types.SimpleNamespace())
return MiniMaxH3VideoVAE("unused", device=torch.device(device))
@pytest.mark.parametrize("device", ["cpu", "meta"])
def test_non_xpu_devices_keep_the_checkpoint_gather(monkeypatch, device):
model = _FakeCheckpointModel()
original = model._all_gather_tiled_results
vae = _construct(monkeypatch, device, model)
assert vae._tile_gather_reuse_enabled() is False
assert vae.model._all_gather_tiled_results is original
assert vae._checkpoint_tile_gather is None
@pytest.mark.parametrize(
("device", "enabled"),
[("xpu", True), ("cpu", False), ("cuda", False), ("meta", False)],
)
def test_only_xpu_asks_for_the_pinned_buffer(device, enabled):
vae = object.__new__(MiniMaxH3VideoVAE)
vae._device_target = torch.device(device)
assert vae._tile_gather_reuse_enabled() is enabled
def test_construction_installs_the_override_when_the_device_asks_for_it(monkeypatch):
"""Close the loop: ``__init__`` must consult the predicate, not ignore it."""
monkeypatch.setattr(MiniMaxH3VideoVAE, "_tile_gather_reuse_enabled", lambda self: True)
model = _FakeCheckpointModel()
original = model._all_gather_tiled_results
vae = _construct(monkeypatch, "cpu", model)
assert vae.model._all_gather_tiled_results is not original
assert vae._checkpoint_tile_gather is original
# --------------------------------------------------------------------------
# The decision-point receipt carries quantities, not just presence: a line
# that only says "installed" cannot tell reuse from reallocation.
# --------------------------------------------------------------------------
def test_the_receipt_line_reports_hits_allocs_and_the_address(fake_collective, gather_log):
vae = _vae(4)
install = [
(level, line) for level, line in gather_log.records if "persistent tile gather installed device=xpu" in line
]
# The one-shot install line is what an operator needs at info level.
assert install and all(level == logging.INFO for level, _ in install)
gather_log.messages.clear()
gather_log.records.clear()
_run(vae, fake_collective, _tiles(8), 8, 4, 0)
first_ptr = fake_collective[-1]["buf_ptr"]
_run(vae, fake_collective, _tiles(8, seed=3), 8, 4, 0)
records = [(level, line) for level, line in gather_log.records if "[H3_VAE_GATHER] reuse=" in line]
# The per-gather line fires once per decoder tile batch, so it is debug.
assert all(level == logging.DEBUG for level, _ in records)
lines = [line for _, line in records]
assert len(lines) == 2
assert "reuse=alloc" in lines[0] and "hits=0 allocs=1" in lines[0]
assert "reuse=hit" in lines[1] and "hits=1 allocs=1" in lines[1]
# Resident bytes are reported, not just the bytes this one gather needed.
needed = _workspace_bytes(8, 4) / (1024.0 * 1024.0)
for line in lines:
assert f"need_mib={needed:.2f}" in line
assert f"workspace_mib={needed:.2f}" in line
assert f"buf_ptr=0x{first_ptr:x}" in lines[0]
assert f"buf_ptr=0x{first_ptr:x}" in lines[1]
@@ -198,7 +198,7 @@ def test_video_vae_installs_exact_optimizations(monkeypatch):
monkeypatch.setattr(
vae_module,
"_load_remote_component",
lambda _path, _config: FakeRemote(),
lambda _path, _config, **_kwargs: FakeRemote(),
)
install = Mock(return_value=True)
monkeypatch.setattr(vae_module, "install_h3_vae_optimizations", install)