Merge branch 'main' into lance-model-support
This commit is contained in:
224 files changed
+19202
-7026
No files matched your search
@@ -23,18 +23,31 @@ from tests.e2e.accuracy.helpers import (
|
||||
download_images,
|
||||
model_output_dir,
|
||||
)
|
||||
from tests.helpers.runtime import OmniRunner
|
||||
from tests.helpers.mark import hardware_marks
|
||||
from vllm_omni.diffusion.models.hunyuan_image3.prompt_utils import build_prompt_tokens, resolve_stop_token_ids
|
||||
|
||||
os.environ["DIFFUSION_ATTENTION_BACKEND"] = "TORCH_SDPA"
|
||||
|
||||
pytestmark = [pytest.mark.full_model, pytest.mark.diffusion]
|
||||
pytestmark = [pytest.mark.local_model, pytest.mark.diffusion, *hardware_marks(res={"cuda": "H100"}, num_cards=8)]
|
||||
|
||||
# ============================================================================
|
||||
# Configurable Parameters
|
||||
# ============================================================================
|
||||
AR_DEVICES = "0,1"
|
||||
DIT_DEVICES = "2,3"
|
||||
# Comma-separated logical CUDA device ids per stage: split visible GPUs (0..n-1), first half -> AR, second -> DiT.
|
||||
|
||||
|
||||
def _default_ar_dit_devices() -> tuple[str, str]:
|
||||
"""First floor(n/2) logical devices -> AR, rest -> DiT. ``device_count`` respects ``CUDA_VISIBLE_DEVICES``."""
|
||||
n = torch.accelerator.device_count()
|
||||
if n < 2:
|
||||
return "0,1", "2,3"
|
||||
split = n // 2
|
||||
ar = ",".join(str(i) for i in range(split))
|
||||
dit = ",".join(str(i) for i in range(split, n))
|
||||
return ar, dit
|
||||
|
||||
|
||||
AR_DEVICES, DIT_DEVICES = _default_ar_dit_devices()
|
||||
MODEL_NAME = "tencent/HunyuanImage-3.0-Instruct"
|
||||
NUM_INFERENCE_STEPS = 50
|
||||
GUIDANCE_SCALE = 2.5
|
||||
@@ -75,7 +88,7 @@ _BASE_CONFIG = {
|
||||
"scheduler_cls": "vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler",
|
||||
"gpu_memory_utilization": 0.95, "enforce_eager": True, "trust_remote_code": True,
|
||||
"engine_output_type": "latent", "enable_prefix_caching": False,
|
||||
"max_num_batched_tokens": 32768, "tensor_parallel_size": 2, "pipeline_parallel_size": 1,
|
||||
"max_num_batched_tokens": 32768, "tensor_parallel_size": AR_TP_SIZE, "pipeline_parallel_size": 1,
|
||||
"hf_overrides": {"rope_parameters": {"mrope_section": [0, 32, 32], "rope_type": "default"}},
|
||||
},
|
||||
"is_comprehension": False, "final_output": True, "final_output_type": "text",
|
||||
@@ -91,7 +104,7 @@ _BASE_CONFIG = {
|
||||
"engine_args": {
|
||||
"model_stage": "dit", "model_arch": "HunyuanImage3ForCausalMM",
|
||||
"enforce_eager": True, "trust_remote_code": True, "distributed_executor_backend": "mp",
|
||||
"parallel_config": {"tensor_parallel_size": 2, "enable_expert_parallel": True},
|
||||
"parallel_config": {"tensor_parallel_size": DIT_TP_SIZE, "enable_expert_parallel": True},
|
||||
},
|
||||
"engine_input_source": [0],
|
||||
"custom_process_input_func": "vllm_omni.model_executor.stage_input_processors.hunyuan_image3.ar2diffusion",
|
||||
@@ -134,12 +147,13 @@ def _make_config(enable_kv_reuse: bool, path: Path) -> None:
|
||||
def _run_offline(stage_configs_path: str, output_path: Path) -> tuple[Image.Image, str, float]:
|
||||
from transformers import AutoTokenizer
|
||||
|
||||
from tests.helpers.runtime import OmniRunner
|
||||
from vllm_omni.inputs.data import OmniDiffusionSamplingParams, OmniPromptType
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
|
||||
build_kwargs: dict = {"task": "it2i", "bot_task": "think_recaption", "sys_type": "en_unified", "num_images": 2}
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
|
||||
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)
|
||||
result = build_prompt_tokens(
|
||||
PROMPT,
|
||||
tokenizer,
|
||||
@@ -149,7 +163,7 @@ def _run_offline(stage_configs_path: str, output_path: Path) -> tuple[Image.Imag
|
||||
system_prompt_type = result.system_prompt_type
|
||||
|
||||
ar_stop_token_ids = resolve_stop_token_ids(task="it2i", bot_task="think_recaption", tokenizer=tokenizer)
|
||||
with OmniRunner(MODEL_NAME, stage_configs_path=stage_configs_path) as runner:
|
||||
with OmniRunner(MODEL_PATH, stage_configs_path=stage_configs_path) as runner:
|
||||
params_list = list(runner.omni.default_sampling_params_list)
|
||||
for sp in params_list:
|
||||
if isinstance(sp, OmniDiffusionSamplingParams):
|
||||
@@ -206,7 +220,10 @@ def _run_offline(stage_configs_path: str, output_path: Path) -> tuple[Image.Imag
|
||||
return image, cot_text, elapsed
|
||||
|
||||
|
||||
@pytest.mark.skipif(torch.accelerator.device_count() < 4, reason="Needs 4+ GPUs (2 AR + 2 DiT)")
|
||||
@pytest.mark.skipif(
|
||||
torch.accelerator.device_count() < AR_TP_SIZE + DIT_TP_SIZE,
|
||||
reason=f"Needs {AR_TP_SIZE + DIT_TP_SIZE}+ GPUs ({AR_TP_SIZE} AR + {DIT_TP_SIZE} DiT)",
|
||||
)
|
||||
def test_image_to_image_alignment(accuracy_artifact_root: Path, accuracy_assets_root: Path) -> None:
|
||||
if importlib.util.find_spec("FlagEmbedding") is None:
|
||||
raise ImportError("Missing dependency: FlagEmbedding\nInstall with: pip install FlagEmbedding")
|
||||
|
||||
@@ -70,7 +70,7 @@ def test_text_to_text(omni_runner, omni_runner_handler):
|
||||
"modalities": ["text"],
|
||||
}
|
||||
response = omni_runner_handler.send_omni_request(request_config)
|
||||
assert response.success, f"Request failed: {response.error_message}"
|
||||
assert response.success, "Request failed"
|
||||
assert response.text_content and len(response.text_content.strip()) > 0
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ def test_audio_to_text(omni_runner, omni_runner_handler):
|
||||
"modalities": ["text"],
|
||||
}
|
||||
response = omni_runner_handler.send_omni_request(request_config)
|
||||
assert response.success, f"Request failed: {response.error_message}"
|
||||
assert response.success, "Request failed"
|
||||
assert response.text_content and len(response.text_content.strip()) > 0
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ def test_image_to_text(omni_runner, omni_runner_handler):
|
||||
"modalities": ["text"],
|
||||
}
|
||||
response = omni_runner_handler.send_omni_request(request_config)
|
||||
assert response.success, f"Request failed: {response.error_message}"
|
||||
assert response.success, "Request failed"
|
||||
assert response.text_content and len(response.text_content.strip()) > 0
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ def test_video_to_text(omni_runner, omni_runner_handler):
|
||||
"modalities": ["text"],
|
||||
}
|
||||
response = omni_runner_handler.send_omni_request(request_config)
|
||||
assert response.success, f"Request failed: {response.error_message}"
|
||||
assert response.success, "Request failed"
|
||||
assert response.text_content and len(response.text_content.strip()) > 0
|
||||
|
||||
|
||||
@@ -162,4 +162,4 @@ def test_mix_to_audio(omni_runner, omni_runner_handler):
|
||||
"modalities": ["audio"],
|
||||
}
|
||||
response = omni_runner_handler.send_omni_request(request_config)
|
||||
assert response.success, f"Request failed: {response.error_message}"
|
||||
assert response.success, "Request failed"
|
||||
@@ -87,7 +87,7 @@ def test_text_to_text(omni_runner, omni_runner_handler):
|
||||
"modalities": ["text"],
|
||||
}
|
||||
response = omni_runner_handler.send_request(request_config)
|
||||
assert response.success, f"Request failed: {response.error_message}"
|
||||
assert response.success, "Request failed"
|
||||
assert response.text_content and len(response.text_content.strip()) > 0
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ def test_audio_to_text(omni_runner, omni_runner_handler):
|
||||
"modalities": ["text"],
|
||||
}
|
||||
response = omni_runner_handler.send_request(request_config)
|
||||
assert response.success, f"Request failed: {response.error_message}"
|
||||
assert response.success, "Request failed"
|
||||
assert response.text_content and len(response.text_content.strip()) > 0
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ def test_image_to_text(omni_runner, omni_runner_handler):
|
||||
"modalities": ["text"],
|
||||
}
|
||||
response = omni_runner_handler.send_request(request_config)
|
||||
assert response.success, f"Request failed: {response.error_message}"
|
||||
assert response.success, "Request failed"
|
||||
assert response.text_content and len(response.text_content.strip()) > 0
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ def test_video_to_text(omni_runner, omni_runner_handler):
|
||||
"modalities": ["text"],
|
||||
}
|
||||
response = omni_runner_handler.send_request(request_config)
|
||||
assert response.success, f"Request failed: {response.error_message}"
|
||||
assert response.success, "Request failed"
|
||||
assert response.text_content and len(response.text_content.strip()) > 0
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ def test_video_to_audio(omni_runner, omni_runner_handler):
|
||||
"modalities": ["audio"],
|
||||
}
|
||||
response = omni_runner_handler.send_request(request_config)
|
||||
assert response.success, f"Request failed: {response.error_message}"
|
||||
assert response.success, "Request failed"
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@@ -204,4 +204,4 @@ def test_mix_to_audio(omni_runner, omni_runner_handler):
|
||||
"modalities": ["audio"],
|
||||
}
|
||||
response = omni_runner_handler.send_request(request_config)
|
||||
assert response.success, f"Request failed: {response.error_message}"
|
||||
assert response.success, "Request failed"
|
||||
@@ -1,99 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
"""E2E test for VoxCPM offline inference."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from tests.helpers.mark import hardware_test
|
||||
from tests.helpers.runtime import OmniRunner
|
||||
from tests.helpers.stage_config import get_deploy_config_path
|
||||
|
||||
VOXCPM_MODEL = "OpenBMB/VoxCPM1.5"
|
||||
STAGE_CONFIG = get_deploy_config_path("voxcpm.yaml")
|
||||
SAMPLE_RATE = 24000
|
||||
|
||||
# (model, stage_config_path) for ``@pytest.mark.parametrize("omni_runner", ..., indirect=True)``
|
||||
_OMNI_RUNNER_PARAM = (VOXCPM_MODEL, STAGE_CONFIG)
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.full_model,
|
||||
pytest.mark.tts,
|
||||
pytest.mark.parametrize("omni_runner", [_OMNI_RUNNER_PARAM], indirect=True),
|
||||
]
|
||||
|
||||
|
||||
def _build_prompt(text: str) -> dict[str, Any]:
|
||||
return {
|
||||
"prompt_token_ids": [1],
|
||||
"additional_information": {
|
||||
"text": [text],
|
||||
"cfg_value": [2.0],
|
||||
"inference_timesteps": [10],
|
||||
"min_len": [2],
|
||||
"max_new_tokens": [1024],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _extract_audio_tensor(multimodal_output: dict[str, Any]) -> torch.Tensor:
|
||||
audio = multimodal_output.get("audio", multimodal_output.get("model_outputs"))
|
||||
assert audio is not None, f"No audio output found, keys={list(multimodal_output.keys())}"
|
||||
|
||||
if isinstance(audio, list):
|
||||
parts: list[torch.Tensor] = []
|
||||
for item in audio:
|
||||
if item is None:
|
||||
continue
|
||||
tensor = torch.as_tensor(item)
|
||||
if tensor.numel() == 0:
|
||||
continue
|
||||
parts.append(tensor.float().cpu().reshape(-1))
|
||||
return torch.cat(parts, dim=-1) if parts else torch.zeros((0,), dtype=torch.float32)
|
||||
|
||||
return torch.as_tensor(audio).float().cpu().reshape(-1)
|
||||
|
||||
|
||||
def _extract_final_multimodal_output(outputs) -> dict[str, Any]:
|
||||
for item in reversed(outputs):
|
||||
request_output = getattr(item, "request_output", None)
|
||||
if request_output is not None:
|
||||
multimodal_output = getattr(request_output, "multimodal_output", None)
|
||||
if isinstance(multimodal_output, dict):
|
||||
return multimodal_output
|
||||
completions = getattr(request_output, "outputs", None) or []
|
||||
for completion in completions:
|
||||
multimodal_output = getattr(completion, "multimodal_output", None)
|
||||
if isinstance(multimodal_output, dict):
|
||||
return multimodal_output
|
||||
|
||||
multimodal_output = getattr(item, "multimodal_output", None)
|
||||
if isinstance(multimodal_output, dict):
|
||||
return multimodal_output
|
||||
|
||||
raise AssertionError("No multimodal audio output found in VoxCPM generate results")
|
||||
|
||||
|
||||
@hardware_test(res={"cuda": "L4"}, num_cards=1)
|
||||
def test_voxcpm_zero_shot_001(omni_runner: OmniRunner) -> None:
|
||||
outputs = list(omni_runner.omni.generate(_build_prompt("Hello, this is a VoxCPM offline inference test.")))
|
||||
|
||||
assert outputs, "No outputs returned"
|
||||
|
||||
multimodal_output = _extract_final_multimodal_output(outputs)
|
||||
audio = _extract_audio_tensor(multimodal_output)
|
||||
assert audio.numel() > SAMPLE_RATE // 2, f"Audio too short: {audio.numel()} samples"
|
||||
|
||||
duration_s = audio.shape[0] / SAMPLE_RATE
|
||||
assert 0.5 < duration_s < 30.0, f"Audio duration out of range: {duration_s:.2f}s"
|
||||
|
||||
peak = float(torch.max(torch.abs(audio)).item()) if audio.numel() > 0 else 0.0
|
||||
assert peak > 0.01, "Generated audio appears to be silence"
|
||||
|
||||
audio_np = audio.numpy()
|
||||
rms = float(np.sqrt(np.mean(np.square(audio_np)))) if audio_np.size else 0.0
|
||||
assert rms > 1e-4, "Generated audio RMS too low"
|
||||
@@ -0,0 +1,311 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""E2E tests for Wan2.2 AutoRound W4A16 quantized inference.
|
||||
|
||||
These tests cover I2V (image-to-video) and T2V (text-to-video) generation
|
||||
with quantized weights.
|
||||
|
||||
Requirements:
|
||||
- CUDA GPU (H100 or equivalent, ~36 GiB for quantized model)
|
||||
- The quantized model checkpoint (Intel/Wan2.2-I2V-A14B-Diffusers-int4-AutoRound,
|
||||
Intel/Wan2.2-T2V-A14B-Diffusers-int4-AutoRound)
|
||||
"""
|
||||
|
||||
import gc
|
||||
import os as _os
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
import torch
|
||||
from PIL import Image
|
||||
|
||||
from tests.helpers.env import DeviceMemoryMonitor
|
||||
from tests.helpers.mark import hardware_test
|
||||
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
|
||||
_os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
|
||||
QUANTIZED_MODEL_I2V = "Intel/Wan2.2-I2V-A14B-Diffusers-int4-AutoRound"
|
||||
BASELINE_MODEL_I2V = "Wan-AI/Wan2.2-I2V-A14B-Diffusers"
|
||||
QUANTIZED_MODEL_T2V = "Intel/Wan2.2-T2V-A14B-Diffusers-int4-AutoRound"
|
||||
BASELINE_MODEL_T2V = "Wan-AI/Wan2.2-T2V-A14B-Diffusers"
|
||||
|
||||
QUANTIZED_MODEL_I2V = _os.environ.get("WAN22_I2V_AUTOROUND_MODEL", QUANTIZED_MODEL_I2V)
|
||||
BASELINE_MODEL_I2V = _os.environ.get("WAN22_I2V_BASELINE_MODEL", BASELINE_MODEL_I2V)
|
||||
QUANTIZED_MODEL_T2V = _os.environ.get("WAN22_T2V_AUTOROUND_MODEL", QUANTIZED_MODEL_T2V)
|
||||
BASELINE_MODEL_T2V = _os.environ.get("WAN22_T2V_BASELINE_MODEL", BASELINE_MODEL_T2V)
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.full_model,
|
||||
pytest.mark.diffusion,
|
||||
]
|
||||
|
||||
# Small resolution to keep GPU memory & time manageable
|
||||
HEIGHT = 480
|
||||
WIDTH = 640
|
||||
NUM_FRAMES = 5 # must satisfy num_frames % 4 == 1 for Wan2.2
|
||||
NUM_STEPS = 2 # minimal for smoke-test
|
||||
|
||||
# Parametrise: (model, stage_config_path=None, extra_omni_kwargs)
|
||||
# When stage_config_path is None, the engine auto-resolves from the model's own config.
|
||||
quant_i2v_params = [(QUANTIZED_MODEL_I2V, None, {"enforce_eager": True})]
|
||||
baseline_i2v_params = [(BASELINE_MODEL_I2V, None, {"enforce_eager": True})]
|
||||
quant_t2v_params = [(QUANTIZED_MODEL_T2V, None, {"enforce_eager": True})]
|
||||
baseline_t2v_params = [(BASELINE_MODEL_T2V, None, {"enforce_eager": True})]
|
||||
|
||||
# Module-level storage for peak memory results across tests
|
||||
_memory_results: dict[str, float] = {}
|
||||
|
||||
|
||||
def _sampling_params_i2v() -> OmniDiffusionSamplingParams:
|
||||
"""Create sampling params for I2V generation."""
|
||||
return OmniDiffusionSamplingParams(
|
||||
height=HEIGHT,
|
||||
width=WIDTH,
|
||||
num_frames=NUM_FRAMES,
|
||||
num_inference_steps=NUM_STEPS,
|
||||
guidance_scale=5.0,
|
||||
guidance_scale_2=6.0,
|
||||
boundary_ratio=0.875,
|
||||
generator=torch.Generator(device=current_omni_platform.device_type).manual_seed(42),
|
||||
)
|
||||
|
||||
|
||||
def _sampling_params_t2v() -> OmniDiffusionSamplingParams:
|
||||
"""Create sampling params for T2V generation."""
|
||||
return OmniDiffusionSamplingParams(
|
||||
height=HEIGHT,
|
||||
width=WIDTH,
|
||||
num_frames=NUM_FRAMES,
|
||||
num_inference_steps=NUM_STEPS,
|
||||
guidance_scale=4.0,
|
||||
generator=torch.Generator(device=current_omni_platform.device_type).manual_seed(42),
|
||||
)
|
||||
|
||||
|
||||
def _create_test_image(width: int = WIDTH, height: int = HEIGHT) -> Image.Image:
|
||||
"""Create a deterministic test image for I2V tests."""
|
||||
rng = np.random.RandomState(42)
|
||||
arr = rng.randint(0, 256, (height, width, 3), dtype=np.uint8)
|
||||
return Image.fromarray(arr)
|
||||
|
||||
|
||||
def _generate_i2v_video(omni_runner_handler, prompt: str = "A cat sitting on a table, smooth motion") -> tuple:
|
||||
"""Generate one I2V video, return (frames, peak_memory_mb)."""
|
||||
gc.collect()
|
||||
current_omni_platform.empty_cache()
|
||||
device_index = current_omni_platform.current_device()
|
||||
current_omni_platform.reset_peak_memory_stats()
|
||||
monitor = DeviceMemoryMonitor(device_index=device_index, interval=0.02)
|
||||
monitor.start()
|
||||
|
||||
image = _create_test_image()
|
||||
response = omni_runner_handler.send_diffusion_request(
|
||||
{
|
||||
"prompt": prompt,
|
||||
"images": image,
|
||||
"sampling_params": _sampling_params_i2v(),
|
||||
},
|
||||
)
|
||||
|
||||
peak = monitor.peak_used_mb
|
||||
monitor.stop()
|
||||
|
||||
assert response.success, f"Request failed: {response.error_message}"
|
||||
assert response.images is not None and len(response.images) > 0, "Expected image output"
|
||||
frames = response.images[0]
|
||||
|
||||
gc.collect()
|
||||
current_omni_platform.empty_cache()
|
||||
|
||||
return frames, peak
|
||||
|
||||
|
||||
def _generate_t2v_video(omni_runner_handler, prompt: str = "A cat sitting on a table") -> tuple:
|
||||
"""Generate one T2V video, return (frames, peak_memory_mb)."""
|
||||
gc.collect()
|
||||
current_omni_platform.empty_cache()
|
||||
device_index = current_omni_platform.current_device()
|
||||
current_omni_platform.reset_peak_memory_stats()
|
||||
monitor = DeviceMemoryMonitor(device_index=device_index, interval=0.02)
|
||||
monitor.start()
|
||||
|
||||
response = omni_runner_handler.send_diffusion_request(
|
||||
{
|
||||
"prompt": prompt,
|
||||
"sampling_params": _sampling_params_t2v(),
|
||||
},
|
||||
)
|
||||
|
||||
peak = monitor.peak_used_mb
|
||||
monitor.stop()
|
||||
|
||||
assert response.success, f"Request failed: {response.error_message}"
|
||||
assert response.images is not None and len(response.images) > 0, "Expected image output"
|
||||
frames = response.images[0]
|
||||
|
||||
gc.collect()
|
||||
current_omni_platform.empty_cache()
|
||||
|
||||
return frames, peak
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Test: I2V quantized model generates valid video
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
@hardware_test(res={"cuda": "H100"})
|
||||
@pytest.mark.parametrize("omni_runner", quant_i2v_params, indirect=True)
|
||||
def test_wan22_i2v_autoround_w4a16_generates_video(omni_runner, omni_runner_handler):
|
||||
"""Load the W4A16 quantized Wan2.2 I2V model and verify it produces a valid video."""
|
||||
frames, _ = _generate_i2v_video(omni_runner_handler)
|
||||
|
||||
assert frames is not None, "Expected video frames output"
|
||||
assert hasattr(frames, "shape"), "Expected frames to have a shape attribute"
|
||||
|
||||
# frames shape: (batch, num_frames, height, width, channels)
|
||||
assert frames.shape[1] == NUM_FRAMES, f"Expected {NUM_FRAMES} frames, got {frames.shape[1]}"
|
||||
assert frames.shape[2] == HEIGHT, f"Expected height {HEIGHT}, got {frames.shape[2]}"
|
||||
assert frames.shape[3] == WIDTH, f"Expected width {WIDTH}, got {frames.shape[3]}"
|
||||
|
||||
# Sanity: video should not be blank (frames are [0, 1] floats)
|
||||
arr = np.asarray(frames)
|
||||
assert arr.std() > 0.01, "Generated video appears blank (std ≈ 0)"
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Test: T2V quantized model generates valid video
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
@hardware_test(res={"cuda": "H100"})
|
||||
@pytest.mark.parametrize("omni_runner", quant_t2v_params, indirect=True)
|
||||
def test_wan22_t2v_autoround_w4a16_generates_video(omni_runner, omni_runner_handler):
|
||||
"""Load the W4A16 quantized Wan2.2 T2V model and verify it produces a valid video."""
|
||||
frames, _ = _generate_t2v_video(omni_runner_handler)
|
||||
|
||||
assert frames is not None, "Expected video frames output"
|
||||
assert hasattr(frames, "shape"), "Expected frames to have a shape attribute"
|
||||
|
||||
assert frames.shape[1] == NUM_FRAMES, f"Expected {NUM_FRAMES} frames, got {frames.shape[1]}"
|
||||
assert frames.shape[2] == HEIGHT, f"Expected height {HEIGHT}, got {frames.shape[2]}"
|
||||
assert frames.shape[3] == WIDTH, f"Expected width {WIDTH}, got {frames.shape[3]}"
|
||||
|
||||
arr = np.asarray(frames)
|
||||
assert arr.std() > 0.01, "Generated video appears blank (std ≈ 0)"
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Test: I2V quantized peak memory
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
@hardware_test(res={"cuda": "H100"})
|
||||
@pytest.mark.parametrize("omni_runner", quant_i2v_params, indirect=True)
|
||||
def test_wan22_i2v_autoround_w4a16_quant_peak(omni_runner, omni_runner_handler):
|
||||
"""Measure peak GPU memory of W4A16 quantized I2V model."""
|
||||
frames, peak = _generate_i2v_video(omni_runner_handler)
|
||||
|
||||
assert frames is not None, "Expected video frames output"
|
||||
_memory_results["quant_i2v"] = peak
|
||||
print(f"\nQuantized I2V (W4A16) peak memory: {peak:.0f} MB")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Test: I2V baseline peak memory
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
@hardware_test(res={"cuda": "H100"})
|
||||
@pytest.mark.parametrize("omni_runner", baseline_i2v_params, indirect=True)
|
||||
def test_wan22_i2v_autoround_w4a16_baseline_peak(omni_runner, omni_runner_handler):
|
||||
"""Measure peak GPU memory of BF16 baseline I2V model."""
|
||||
frames, peak = _generate_i2v_video(omni_runner_handler)
|
||||
|
||||
assert frames is not None, "Expected video frames output"
|
||||
_memory_results["baseline_i2v"] = peak
|
||||
print(f"\nBaseline I2V (BF16) peak memory: {peak:.0f} MB")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Test: I2V memory savings
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
@hardware_test(res={"cuda": "H100"})
|
||||
def test_wan22_i2v_autoround_w4a16_memory_savings():
|
||||
"""Assert quantized I2V model uses meaningfully less memory than BF16 baseline."""
|
||||
quant_peak = _memory_results["quant_i2v"]
|
||||
baseline_peak = _memory_results["baseline_i2v"]
|
||||
|
||||
savings = baseline_peak - quant_peak
|
||||
print(f"\nQuantized I2V (W4A16) peak memory: {quant_peak:.0f} MB")
|
||||
print(f"Baseline I2V (BF16) peak memory: {baseline_peak:.0f} MB")
|
||||
print(f"Savings: {savings:.0f} MB")
|
||||
|
||||
# Wan2.2 I2V A14B transformer is ~28 GB in BF16; W4A16 should save ~20 GB.
|
||||
# Use a conservative threshold to account for activations and overhead.
|
||||
min_savings_mb = 5000
|
||||
assert quant_peak + min_savings_mb < baseline_peak, (
|
||||
f"Quantized model ({quant_peak:.0f} MB) should use at least "
|
||||
f"{min_savings_mb} MB less than baseline ({baseline_peak:.0f} MB)"
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Test: T2V quantized peak memory
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
@hardware_test(res={"cuda": "H100"})
|
||||
@pytest.mark.parametrize("omni_runner", quant_t2v_params, indirect=True)
|
||||
def test_wan22_t2v_autoround_w4a16_quant_peak(omni_runner, omni_runner_handler):
|
||||
"""Measure peak GPU memory of W4A16 quantized T2V model."""
|
||||
frames, peak = _generate_t2v_video(omni_runner_handler)
|
||||
|
||||
assert frames is not None, "Expected video frames output"
|
||||
_memory_results["quant_t2v"] = peak
|
||||
print(f"\nQuantized T2V (W4A16) peak memory: {peak:.0f} MB")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Test: T2V baseline peak memory
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
@hardware_test(res={"cuda": "H100"})
|
||||
@pytest.mark.parametrize("omni_runner", baseline_t2v_params, indirect=True)
|
||||
def test_wan22_t2v_autoround_w4a16_baseline_peak(omni_runner, omni_runner_handler):
|
||||
"""Measure peak GPU memory of BF16 baseline T2V model."""
|
||||
frames, peak = _generate_t2v_video(omni_runner_handler)
|
||||
|
||||
assert frames is not None, "Expected video frames output"
|
||||
_memory_results["baseline_t2v"] = peak
|
||||
print(f"\nBaseline T2V (BF16) peak memory: {peak:.0f} MB")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Test: T2V memory savings
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
@hardware_test(res={"cuda": "H100"})
|
||||
def test_wan22_t2v_autoround_w4a16_memory_savings():
|
||||
"""Assert quantized T2V model uses meaningfully less memory than BF16 baseline."""
|
||||
quant_peak = _memory_results["quant_t2v"]
|
||||
baseline_peak = _memory_results["baseline_t2v"]
|
||||
|
||||
savings = baseline_peak - quant_peak
|
||||
print(f"\nQuantized T2V (W4A16) peak memory: {quant_peak:.0f} MB")
|
||||
print(f"Baseline T2V (BF16) peak memory: {baseline_peak:.0f} MB")
|
||||
print(f"Savings: {savings:.0f} MB")
|
||||
|
||||
# Wan2.2 T2V A14B transformer is ~28 GB in BF16; W4A16 should save ~20 GB.
|
||||
# Use a conservative threshold to account for activations and overhead.
|
||||
min_savings_mb = 5000
|
||||
assert quant_peak + min_savings_mb < baseline_peak, (
|
||||
f"Quantized model ({quant_peak:.0f} MB) should use at least "
|
||||
f"{min_savings_mb} MB less than baseline ({baseline_peak:.0f} MB)"
|
||||
)
|
||||
@@ -105,16 +105,3 @@ class TestOmniVoiceVoiceCloning:
|
||||
"min_audio_bytes": _DEFAULT_MIN_AUDIO_BYTES,
|
||||
}
|
||||
openai_client.send_audio_speech_request(request_config)
|
||||
|
||||
@hardware_test(res={"cuda": "L4"}, num_cards=1)
|
||||
def test_voice_clone_invalid_ref_audio_format(self, omni_server, openai_client) -> None:
|
||||
"""Test that invalid ref_audio format returns a clear error."""
|
||||
request_config = {
|
||||
"model": omni_server.model,
|
||||
"input": get_prompt("text"),
|
||||
"ref_audio": "not_a_valid_uri",
|
||||
"response_format": "wav",
|
||||
"timeout": 180.0,
|
||||
"status_code": (400, 422),
|
||||
}
|
||||
openai_client.send_audio_speech_request(request_config)
|
||||
@@ -91,20 +91,3 @@ class TestVoxtralTTSFixedVoice:
|
||||
"timeout": 120.0,
|
||||
}
|
||||
)
|
||||
|
||||
@pytest.mark.advanced_model
|
||||
@pytest.mark.tts
|
||||
@hardware_test(res={"cuda": "H100"}, num_cards=1)
|
||||
def test_speech_invalid_voice_rejected(self, omni_server, openai_client) -> None:
|
||||
"""Request with a non-existent voice should be rejected (HTTP 200 with JSON or 4xx, depending on server)."""
|
||||
openai_client.send_audio_speech_request(
|
||||
{
|
||||
"model": omni_server.model,
|
||||
"input": "This voice does not exist.",
|
||||
"voice": "nonexistent_voice_xyz",
|
||||
"language": "English",
|
||||
"response_format": "wav",
|
||||
"timeout": 120.0,
|
||||
"err_message": "Invalid speaker",
|
||||
}
|
||||
)
|
||||
Reference in new issue
Block a user