Signed-off-by: zwhzzz0821 <2831474076@qq.com> Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
161 lines
6.0 KiB
Python
161 lines
6.0 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
"""
|
|
Expanded end-to-end test for SenseNova-U1 in offline mode.
|
|
|
|
This test file primarily covers end-to-end tests for LoRA support.
|
|
|
|
Validates that LoRA adapters are correctly loaded, applied with controllable
|
|
scale, and cleanly deactivated. Uses a synthetic rank-4 adapter targeting the
|
|
first decoder layer's gen-path (``_mot_gen``) output projection, so the
|
|
perturbation is exercised during the denoising loop.
|
|
|
|
Assertions:
|
|
(a) LoRA at scale=1.0 visibly changes the output (diff > 0.5)
|
|
(b) scale=2.0 produces a different delta than scale=1.0 (scale sensitivity)
|
|
(c) The delta is bounded (not corrupted)
|
|
(d) Deactivating LoRA exactly restores the baseline (diff == 0)
|
|
|
|
Observed on H100 (seed=42, 8 steps, 512x512): diff_1x~62.2, diff_2x~76.9,
|
|
diff_restored=0.0. Thresholds match test_bagel_expansion.py and hold with margin.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
|
|
|
import numpy as np
|
|
import pytest
|
|
import torch
|
|
from PIL import Image
|
|
from safetensors.torch import save_file
|
|
|
|
from tests.helpers.mark import hardware_test
|
|
from tests.helpers.runtime import OmniRunner
|
|
from vllm_omni.entrypoints.omni import Omni
|
|
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
|
from vllm_omni.lora.request import LoRARequest
|
|
from vllm_omni.lora.utils import stable_lora_int_id
|
|
|
|
MODEL = "SenseNova/SenseNova-U1-8B-MoT"
|
|
DEFAULT_PROMPT = "a photo of a cat sitting on a laptop keyboard"
|
|
SIZE = (512, 512) # (width, height); small for fast E2E
|
|
|
|
# SenseNova-U1-8B-MoT hidden_size (Qwen3-based). o_proj is square (hidden x hidden)
|
|
# regardless of GQA, so this is the robust LoRA target. Verify against config.json.
|
|
_LORA_DIM = 4096
|
|
_LORA_MODULE = "language_model.model.layers.0.self_attn.o_proj_mot_gen"
|
|
_LORA_RANK = 4
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers (reused from test_sensenova_u1_text2img.py patterns)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _build_sampling_params(
|
|
lora_request: LoRARequest | None = None,
|
|
lora_scale: float = 1.0,
|
|
) -> OmniDiffusionSamplingParams:
|
|
return OmniDiffusionSamplingParams(
|
|
height=SIZE[1],
|
|
width=SIZE[0],
|
|
seed=42,
|
|
num_inference_steps=8,
|
|
extra_args={
|
|
"cfg_scale": 4.0,
|
|
"cfg_norm": "none",
|
|
"timestep_shift": 3.0,
|
|
"cfg_interval": (0.0, 1.0),
|
|
"batch_size": 1,
|
|
"think": False,
|
|
"t_eps": 0.02,
|
|
},
|
|
lora_request=lora_request,
|
|
lora_scale=lora_scale,
|
|
)
|
|
|
|
|
|
def _extract_generated_image(omni_outputs: list) -> Image.Image | None:
|
|
for req_output in omni_outputs:
|
|
if images := getattr(req_output, "images", None):
|
|
return images[0]
|
|
return None
|
|
|
|
|
|
def _generate(omni: Omni, lora_request: LoRARequest | None = None, lora_scale: float = 1.0) -> Image.Image:
|
|
outputs = list(
|
|
omni.generate(
|
|
prompts={"prompt": DEFAULT_PROMPT, "modalities": ["image"]},
|
|
sampling_params_list=_build_sampling_params(lora_request, lora_scale),
|
|
)
|
|
)
|
|
img = _extract_generated_image(outputs)
|
|
assert img is not None, "No image generated"
|
|
return img
|
|
|
|
|
|
def _make_file_lora_request(adapter_dir: Path) -> LoRARequest:
|
|
"""Write synthetic adapter to disk and return a file-backed LoRARequest."""
|
|
adapter_dir.mkdir(parents=True, exist_ok=True)
|
|
gen = torch.Generator().manual_seed(42)
|
|
lora_a = torch.randn((_LORA_RANK, _LORA_DIM), dtype=torch.float32, generator=gen) * 0.1
|
|
lora_b = torch.randn((_LORA_DIM, _LORA_RANK), dtype=torch.float32, generator=gen) * 0.5
|
|
save_file(
|
|
{
|
|
f"base_model.model.{_LORA_MODULE}.lora_A.weight": lora_a,
|
|
f"base_model.model.{_LORA_MODULE}.lora_B.weight": lora_b,
|
|
},
|
|
str(adapter_dir / "adapter_model.safetensors"),
|
|
)
|
|
(adapter_dir / "adapter_config.json").write_text(
|
|
json.dumps({"r": _LORA_RANK, "lora_alpha": _LORA_RANK, "target_modules": [_LORA_MODULE]}),
|
|
encoding="utf-8",
|
|
)
|
|
lora_dir = str(adapter_dir)
|
|
return LoRARequest(lora_name="test_file", lora_int_id=stable_lora_int_id(lora_dir), lora_path=lora_dir)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.slow
|
|
@pytest.mark.diffusion
|
|
@hardware_test(res={"cuda": "H100"})
|
|
def test_sensenova_u1_lora_scale_and_deactivation(tmp_path) -> None:
|
|
"""Validate LoRA effect, scale sensitivity, bounded perturbation, and clean deactivation."""
|
|
lora_request = _make_file_lora_request(tmp_path / "sensenova_lora")
|
|
|
|
with OmniRunner(MODEL) as runner:
|
|
omni = runner.omni
|
|
baseline = _generate(omni)
|
|
img_1x = _generate(omni, lora_request, lora_scale=1.0)
|
|
img_2x = _generate(omni, lora_request, lora_scale=2.0)
|
|
restored = _generate(omni)
|
|
|
|
baseline_arr = np.array(baseline, dtype=np.int16)
|
|
diff_1x = np.abs(baseline_arr - np.array(img_1x, dtype=np.int16)).mean()
|
|
diff_2x = np.abs(baseline_arr - np.array(img_2x, dtype=np.int16)).mean()
|
|
diff_restored = np.abs(baseline_arr - np.array(restored, dtype=np.int16)).mean()
|
|
|
|
# (a) Adapter has visible effect at both scales
|
|
assert diff_1x > 0.5, f"LoRA scale=1.0 had no visible effect: diff={diff_1x}"
|
|
assert diff_2x > 0.5, f"LoRA scale=2.0 had no visible effect: diff={diff_2x}"
|
|
|
|
# (b) Different scales produce different outputs
|
|
assert not np.isclose(diff_1x, diff_2x, atol=1.0), (
|
|
f"LoRA scale has no effect: diff_1x={diff_1x:.2f}, diff_2x={diff_2x:.2f}"
|
|
)
|
|
|
|
# (c) Output is not corrupted
|
|
assert diff_1x < 80, f"LoRA output looks corrupted: diff_1x={diff_1x}"
|
|
assert diff_2x < 120, f"LoRA output looks corrupted: diff_2x={diff_2x}"
|
|
|
|
# (d) Deactivation fully restores base model
|
|
assert diff_restored == 0.0, f"Base model not restored after LoRA deactivation: diff={diff_restored}"
|