Files
vllm-omni/tests/e2e/accuracy/test_hunyuan_image3_pixel_accuracy.py
T
2026-08-03 06:31:50 +00:00

299 lines
9.6 KiB
Python

from __future__ import annotations
import base64
import copy
import io
import logging
import os
import tempfile
from pathlib import Path
import pytest
import requests
import yaml
from PIL import Image
from tests.e2e.accuracy.helpers import assert_images_pixel_close, assert_similarity, model_output_dir
from tests.helpers.mark import hardware_test
from tests.helpers.media import get_asset_path
from tests.helpers.runtime import OmniServer
pytestmark = [pytest.mark.full_model, pytest.mark.diffusion]
logger = logging.getLogger(__name__)
MODEL_NAME = "tencent/HunyuanImage-3.0-Instruct"
SEED = 42
NUM_INFERENCE_STEPS = 50
GUIDANCE_SCALE = 2.5
HEIGHT = 1024
WIDTH = 1024
PROMPT = "A brown and white dog is running on the grass."
MEAN_THRESHOLD = 3e-2
P99_THRESHOLD = 3e-1
SSIM_THRESHOLD = 0.97
PSNR_THRESHOLD_CUDA = 30.0
PSNR_THRESHOLD_NPU = 26.0
def _psnr_threshold() -> float:
from vllm_omni.platforms import current_omni_platform
return PSNR_THRESHOLD_NPU if current_omni_platform.is_npu() else PSNR_THRESHOLD_CUDA
_REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent
def _baseline_path() -> Path:
from vllm_omni.platforms import current_omni_platform
suffix = "_npu" if current_omni_platform.is_npu() else ""
path = get_asset_path(f"hunyuan/hunyuan_baseline{suffix}.png")
assert path.exists(), f"Baseline image not found at {path}"
return path
_OFFLINE_SCRIPT = _REPO_ROOT / "examples" / "offline_inference" / "hunyuan_image3" / "end2end.py"
# DiT-only deploy config with trust_remote_code (based on hunyuan_image3_dit.yaml).
_DEPLOY_CONFIG = {
"pipeline": "hunyuan_image3_dit",
"async_chunk": False,
"trust_remote_code": True,
"stages": [
{
"stage_id": 0,
"max_num_seqs": 1,
"gpu_memory_utilization": 0.9,
"enforce_eager": True,
"trust_remote_code": True,
"devices": "0,1,2,3", # set dynamically by _write_deploy_config
"vae_use_slicing": False,
"moe_backend": "flashinfer_cutlass",
"vae_use_tiling": False,
"parallel_config": {
"pipeline_parallel_size": 1,
"data_parallel_size": 1,
"tensor_parallel_size": 1,
"enable_expert_parallel": True,
"sequence_parallel_size": 1,
"ulysses_degree": 1,
"ring_degree": 1,
"allgather_degree": 4,
"cfg_parallel_size": 1,
"vae_patch_parallel_size": 1,
"use_hsdp": False,
"hsdp_shard_size": -1,
"hsdp_replicate_size": 1,
},
"default_sampling_params": {
"seed": SEED,
},
},
],
"platforms": {
"npu": {
"stages": [
{
"stage_id": 0,
"gpu_memory_utilization": 0.65,
"devices": "0,1,2,3",
"moe_backend": "auto",
"max_num_batched_tokens": 32768,
"parallel_config": {
"tensor_parallel_size": 4,
"enable_expert_parallel": True,
},
},
],
},
},
}
def _model_name() -> str:
return os.environ.get("HUNYUAN_IMAGE3_MODEL", MODEL_NAME)
def _devices() -> str:
return os.environ.get("HUNYUAN_IMAGE3_DEVICES", "0,1,2,3")
def _write_deploy_config(path: Path) -> None:
config = copy.deepcopy(_DEPLOY_CONFIG)
devices = _devices()
num_devices = len(devices.split(","))
config["stages"][0]["devices"] = devices
parallel_config = config["stages"][0]["parallel_config"]
if parallel_config.get("allgather_degree", 1) > 1:
parallel_config["allgather_degree"] = num_devices
elif parallel_config.get("tensor_parallel_size", 1) > 1:
parallel_config["tensor_parallel_size"] = num_devices
sequence_parallel_size = (
parallel_config.get("allgather_degree", 1)
if parallel_config.get("allgather_degree", 1) > 1
else parallel_config.get("ulysses_degree", 1) * parallel_config.get("ring_degree", 1)
)
configured_world_size = (
parallel_config.get("pipeline_parallel_size", 1)
* parallel_config.get("data_parallel_size", 1)
* parallel_config.get("tensor_parallel_size", 1)
* sequence_parallel_size
* parallel_config.get("cfg_parallel_size", 1)
)
assert configured_world_size == num_devices, (
f"Configured parallel world size ({configured_world_size}) does not match "
f"the number of devices ({num_devices}): {parallel_config}"
)
parallel_config["sequence_parallel_size"] = sequence_parallel_size
config_text = yaml.dump(config, default_flow_style=False, sort_keys=False)
path.write_text(config_text)
logger.info(
"HunyuanImage3 pixel accuracy deploy config:\n"
" path=%s\n"
" CUDA_VISIBLE_DEVICES=%s\n"
" devices=%s\n"
" tensor_parallel_size=%s\n"
" allgather_degree=%s\n%s",
path,
os.environ.get("CUDA_VISIBLE_DEVICES", "<unset>"),
devices,
parallel_config.get("tensor_parallel_size", 1),
parallel_config.get("allgather_degree", 1),
config_text,
)
def _run_vllm_omni_hunyuan_image3_online(*, model: str, deploy_config: str, output_path: Path) -> Image.Image:
server_args = [
"--deploy-config",
deploy_config,
"--stage-init-timeout",
"300",
"--init-timeout",
"900",
"--enforce-eager",
"--trust-remote-code",
]
with OmniServer(model, server_args, use_omni=True) as omni_server:
response = requests.post(
f"http://{omni_server.host}:{omni_server.port}/v1/images/generations",
json={
"model": omni_server.model,
"prompt": PROMPT,
"size": f"{WIDTH}x{HEIGHT}",
"n": 1,
"response_format": "b64_json",
"num_inference_steps": NUM_INFERENCE_STEPS,
"guidance_scale": GUIDANCE_SCALE,
"seed": SEED,
"bot_task": "none",
"use_system_prompt": "en_unified",
},
timeout=600,
)
response.raise_for_status()
payload = response.json()
assert len(payload["data"]) == 1
image_bytes = base64.b64decode(payload["data"][0]["b64_json"])
image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
image.load()
image.save(output_path)
return image
def _run_vllm_omni_hunyuan_image3_offline(*, model: str, deploy_config: str, output_path: Path) -> Image.Image:
import subprocess
import sys
output_dir = str(output_path.parent)
subprocess.run(
[
sys.executable,
str(_OFFLINE_SCRIPT),
"--modality",
"text2img",
"--deploy-config",
deploy_config,
"--prompts",
PROMPT,
"--output",
output_dir,
"--steps",
str(NUM_INFERENCE_STEPS),
"--guidance-scale",
str(GUIDANCE_SCALE),
"--seed",
str(SEED),
"--height",
str(HEIGHT),
"--width",
str(WIDTH),
"--bot-task",
"none",
"--sys-type",
"en_unified",
"--model",
model,
"--enforce-eager",
],
check=True,
)
images = sorted(Path(output_dir).glob("output_*.png"))
assert images, f"No output image found in {output_dir}"
image = Image.open(images[0]).convert("RGB")
image.load()
image.save(output_path)
return image
def _assert_against_baseline(image: Image.Image, label: str) -> None:
baseline_path = _baseline_path()
baseline_image = Image.open(baseline_path).convert("RGB")
assert_images_pixel_close(
model_name=f"{MODEL_NAME} ({label} vs baseline)",
vllm_image=image,
diffusers_image=baseline_image,
mean_threshold=MEAN_THRESHOLD,
p99_threshold=P99_THRESHOLD,
)
assert_similarity(
model_name=f"{MODEL_NAME} ({label} vs baseline)",
vllm_image=image,
diffusers_image=baseline_image,
ssim_threshold=SSIM_THRESHOLD,
psnr_threshold=_psnr_threshold(),
)
@hardware_test(res={"cuda": "H100", "npu": "A3"}, num_cards=4)
def test_hunyuan_image3_pixel_accuracy_online(accuracy_artifact_root: Path) -> None:
model = _model_name()
output_dir = model_output_dir(accuracy_artifact_root, MODEL_NAME)
with tempfile.TemporaryDirectory() as tmpdir:
deploy_config_path = Path(tmpdir) / "deploy.yaml"
_write_deploy_config(deploy_config_path)
image = _run_vllm_omni_hunyuan_image3_online(
model=model, deploy_config=str(deploy_config_path), output_path=output_dir / "vllm_omni_online.png"
)
_assert_against_baseline(image, "online")
@hardware_test(res={"cuda": "H100", "npu": "A3"}, num_cards=4)
def test_hunyuan_image3_pixel_accuracy_offline(accuracy_artifact_root: Path) -> None:
model = _model_name()
output_dir = model_output_dir(accuracy_artifact_root, MODEL_NAME)
with tempfile.TemporaryDirectory() as tmpdir:
deploy_config_path = Path(tmpdir) / "deploy.yaml"
_write_deploy_config(deploy_config_path)
image = _run_vllm_omni_hunyuan_image3_offline(
model=model, deploy_config=str(deploy_config_path), output_path=output_dir / "vllm_omni_offline.png"
)
_assert_against_baseline(image, "offline")