Files
vllm-omni/tests/e2e/features/rlhf_test/test_verl_omni_e2e.py
T

218 lines
7.4 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""E2E test that follows the EXACT same flow as
``verl-omni/tests/workers/rollout/rollout_vllm/test_vllm_omni_generate.py``
but inlines all the ``verl`` / ``verl_omni`` symbols the flow depends on
(stripped down to only what the diffusion-mode test actually exercises).
Flow (identical to the verl-omni test):
1. Build two ``OmegaConf`` configs (``DiffusionRolloutConfig`` /
``DiffusionModelConfig`` shape).
2. ``ServerCls = ray.remote(vLLMOmniHttpServerLocal)`` then
``ServerCls.options(...).remote(config=..., model_config=...,
rollout_mode=RolloutMode.STANDALONE, workers=[], replica_rank=0,
node_rank=0, gpus_per_node=1, nnodes=1, cuda_visible_devices="0")``.
3. ``ray.get(server.launch_server.remote())``.
4. ``server.generate.remote(prompt_ids=..., sampling_params=..., request_id=...)``
and assert the returned ``DiffusionOutput``.
The Ray actor lives in ``tests.e2e.features.helpers.verl_omni_server`` so
workers can import it after directory migration (pytest test modules are not
importable as ``test_verl_omni_e2e`` in Ray child processes).
Usage:
pytest tests/e2e/features/rlhf_test/test_verl_omni_e2e.py -v -s
"""
from __future__ import annotations
import os
from functools import lru_cache
from uuid import uuid4
import pytest
import ray
import torch
from huggingface_hub import snapshot_download
from omegaconf import OmegaConf
from transformers import AutoTokenizer
from tests.e2e.features.helpers.verl_omni_server import (
DiffusionOutput,
RolloutMode,
normalize_token_ids,
vLLMOmniHttpServerLocal,
)
from tests.helpers.mark import hardware_test
MODEL = "tiny-random/Qwen-Image"
TOKENIZER_MODEL = "Qwen/Qwen2-1.5B-Instruct"
_MIN_PROMPT_TOKENS = 35
def _resolve_model_path(repo_id: str) -> str:
if os.path.isdir(repo_id):
return repo_id
return snapshot_download(repo_id=repo_id)
@lru_cache(maxsize=1)
def _get_tokenizer():
return AutoTokenizer.from_pretrained(TOKENIZER_MODEL, trust_remote_code=True)
def _tokenize_prompt(text: str) -> list[int]:
tokenizer = _get_tokenizer()
messages = [{"role": "user", "content": text}]
token_ids = normalize_token_ids(tokenizer.apply_chat_template(messages, tokenize=True, add_generation_prompt=False))
assert len(token_ids) > _MIN_PROMPT_TOKENS, (
f"Prompt too short ({len(token_ids)} tokens, need >{_MIN_PROMPT_TOKENS}). "
"The pipeline drops the first 34 chat-template prefix tokens; "
"use a longer prompt so content tokens remain after the drop."
)
return token_ids
@pytest.fixture
def init_server():
"""Create and launch a vLLMOmniHttpServerLocal Ray actor with Qwen-Image."""
model_path = _resolve_model_path(MODEL)
ray.init(
runtime_env={
"env_vars": {
"TOKENIZERS_PARALLELISM": "true",
"NCCL_DEBUG": "WARN",
"VLLM_LOGGING_LEVEL": "INFO",
}
},
ignore_reinit_error=True,
)
rollout_cfg = OmegaConf.create(
{
"name": "vllm_omni",
"mode": "async",
"tensor_model_parallel_size": 1,
"data_parallel_size": 1,
"pipeline_model_parallel_size": 1,
"gpu_memory_utilization": 0.8,
"max_num_batched_tokens": 8192,
# QwenImagePipeline (and the logprob custom subclass) only supports
# serial request execution; max_num_seqs>1 enables request-level batching.
"max_num_seqs": 1,
"max_model_len": 1058,
"dtype": "bfloat16",
"load_format": "auto",
"enforce_eager": True,
"enable_chunked_prefill": False,
"enable_prefix_caching": False,
"enable_sleep_mode": False,
"free_cache_engine": True,
"disable_log_stats": True,
"logprobs_mode": "processed_logprobs",
"scheduling_policy": "fcfs",
"seed": 0,
"n": 4,
"pipeline": {
"height": 512,
"width": 512,
"num_inference_steps": 10,
},
}
)
model_cfg = OmegaConf.create(
{
"path": str(model_path),
"local_path": str(model_path),
"tokenizer_path": TOKENIZER_MODEL,
"trust_remote_code": True,
"load_tokenizer": True,
}
)
ServerCls = ray.remote(vLLMOmniHttpServerLocal)
server = ServerCls.options(
runtime_env={
"env_vars": {
"RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES": "1",
"RAY_EXPERIMENTAL_NOSET_ASCEND_RT_VISIBLE_DEVICES": "1",
"NCCL_CUMEM_ENABLE": "0",
}
},
max_concurrency=10,
num_gpus=1,
).remote(
config=rollout_cfg,
model_config=model_cfg,
rollout_mode=RolloutMode.STANDALONE,
workers=[],
replica_rank=0,
node_rank=0,
gpus_per_node=1,
nnodes=1,
cuda_visible_devices="0",
)
ray.get(server.launch_server.remote())
yield server
ray.shutdown()
@pytest.mark.core_model
@pytest.mark.diffusion
@hardware_test(res={"cuda": "L4"}, num_cards=1)
def test_generate(init_server):
"""Concurrent generate() calls covering basic output, logprobs, and multi-request correctness."""
server = init_server
prompts = [
"a beautiful sunset over the ocean with vibrant orange and purple clouds "
"reflecting on the calm water surface near a rocky coastline",
"a fluffy orange cat sitting on a wooden windowsill looking outside at "
"a garden full of colorful flowers on a bright sunny afternoon",
"a majestic mountain landscape covered with fresh white snow under a "
"clear blue sky with pine trees in the foreground and a frozen lake",
"a futuristic city at night with neon lights glowing on tall glass "
"skyscrapers and flying vehicles soaring between the buildings",
]
refs = []
for i, prompt in enumerate(prompts):
rid = f"test_{i}_{uuid4().hex[:8]}"
ref = server.generate.remote(
prompt_ids=_tokenize_prompt(prompt),
sampling_params={
"num_inference_steps": 10,
"true_cfg_scale": 4.0,
"height": 512,
"width": 512,
"logprobs": i == 0,
},
request_id=rid,
)
refs.append(ref)
results = ray.get(refs, timeout=600)
for i, output in enumerate(results):
assert isinstance(output, DiffusionOutput), f"Request {i}: expected DiffusionOutput"
img = output.diffusion_output
assert isinstance(img, torch.Tensor) and img.ndim == 3, (
f"Request {i}: expected CHW torch.Tensor, got {type(img).__name__} ndim={getattr(img, 'ndim', None)}"
)
c, h, w = img.shape
assert c == 3, f"Request {i}: expected 3 channels (CHW), got {c}"
assert h > 0 and w > 0, f"Request {i}: image dimensions must be positive"
assert output.stop_reason in ("completed", "aborted", None), (
f"Request {i}: unexpected stop_reason {output.stop_reason!r}"
)
assert 0.0 <= float(img[0, 0, 0]) <= 1.0, f"Request {i}: pixel values must be in [0, 1]"
print(f"All {len(prompts)} concurrent requests returned valid DiffusionOutput")