diff --git a/.buildkite/scripts/hardware_ci/run-xpu-test.sh b/.buildkite/scripts/hardware_ci/run-xpu-test.sh new file mode 100755 index 000000000..e1d464ef0 --- /dev/null +++ b/.buildkite/scripts/hardware_ci/run-xpu-test.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# This script build the XPU docker image and run the offline inference inside the container. +set -ex + +omni_source_dir=$(git rev-parse --show-toplevel) + +base_image_name="xpu/vllm-omni-ci-base:${VLLM_VERSION:?VLLM_VERSION must be set}" +image_name="xpu/vllm-omni-ci:${BUILDKITE_COMMIT:?BUILDKITE_COMMIT must be set}" +container_name="xpu_${BUILDKITE_COMMIT}_$( + tr -dc A-Za-z0-9 None: """ @@ -87,7 +90,7 @@ def test_mix_to_audio(omni_runner, omni_runner_handler) -> None: @pytest.mark.core_model @pytest.mark.omni -@hardware_test(res={"cuda": "L4", "rocm": "MI325"}, num_cards={"cuda": 4, "rocm": 2}) +@hardware_test(res={"cuda": "L4", "rocm": "MI325", "xpu": "B60"}, num_cards={"cuda": 4, "rocm": 2, "xpu": 3}) @pytest.mark.parametrize("omni_runner", test_params, indirect=True) def test_text_to_text(omni_runner, omni_runner_handler) -> None: """ diff --git a/tests/e2e/offline_inference/test_qwen3_omni.py b/tests/e2e/offline_inference/test_qwen3_omni.py index 8c57becef..01be0486f 100644 --- a/tests/e2e/offline_inference/test_qwen3_omni.py +++ b/tests/e2e/offline_inference/test_qwen3_omni.py @@ -40,6 +40,8 @@ def get_cuda_graph_config(): if current_omni_platform.is_rocm(): # ROCm stage config optimized for MI325 GPU stage_configs = [str(Path(__file__).parent.parent / "stage_configs" / "rocm" / "qwen3_omni_ci.yaml")] +elif current_omni_platform.is_xpu(): + stage_configs = [str(Path(__file__).parent.parent / "stage_configs" / "xpu" / "qwen3_omni_ci.yaml")] else: stage_configs = [get_cuda_graph_config()] diff --git a/tests/e2e/offline_inference/test_stable_audio_model.py b/tests/e2e/offline_inference/test_stable_audio_model.py index 2acfe5097..c36f06493 100644 --- a/tests/e2e/offline_inference/test_stable_audio_model.py +++ b/tests/e2e/offline_inference/test_stable_audio_model.py @@ -23,7 +23,7 @@ models = ["linyueqian/stable_audio_random"] @pytest.mark.core_model @pytest.mark.diffusion -@hardware_test(res={"cuda": "L4"}) +@hardware_test(res={"cuda": "L4", "xpu": "B60"}) @pytest.mark.parametrize("model_name", models) def test_stable_audio_model(model_name: str): m = Omni(model=model_name) diff --git a/tests/e2e/offline_inference/test_t2i_model.py b/tests/e2e/offline_inference/test_t2i_model.py index 33960f560..0cb0dafeb 100644 --- a/tests/e2e/offline_inference/test_t2i_model.py +++ b/tests/e2e/offline_inference/test_t2i_model.py @@ -36,7 +36,7 @@ elif current_omni_platform.is_rocm(): @pytest.mark.core_model @pytest.mark.advanced_model @pytest.mark.diffusion -@hardware_test(res={"cuda": "L4", "rocm": "MI325"}, num_cards={"cuda": 1, "rocm": 2}) +@hardware_test(res={"cuda": "L4", "rocm": "MI325", "xpu": "B60"}, num_cards={"cuda": 1, "rocm": 2, "xpu": 2}) @pytest.mark.parametrize("model_name", models) def test_diffusion_model(model_name: str, run_level): if run_level == "core_model" and model_name != "riverclouds/qwen_image_random": diff --git a/tests/e2e/offline_inference/test_teacache.py b/tests/e2e/offline_inference/test_teacache.py index 96254f5b6..f2d110125 100644 --- a/tests/e2e/offline_inference/test_teacache.py +++ b/tests/e2e/offline_inference/test_teacache.py @@ -36,7 +36,7 @@ models = ["riverclouds/qwen_image_random"] @pytest.mark.core_model @pytest.mark.diffusion @pytest.mark.cache -@hardware_test(res={"cuda": "L4", "rocm": "MI325"}) +@hardware_test(res={"cuda": "L4", "rocm": "MI325", "xpu": "B60"}) @pytest.mark.parametrize("model_name", models) def test_teacache(model_name: str): """Test TeaCache backend with diffusion model.""" diff --git a/tests/e2e/offline_inference/test_zimage_parallelism.py b/tests/e2e/offline_inference/test_zimage_parallelism.py index d5b5e39e5..b11176070 100644 --- a/tests/e2e/offline_inference/test_zimage_parallelism.py +++ b/tests/e2e/offline_inference/test_zimage_parallelism.py @@ -160,7 +160,7 @@ def _run_zimage_generate( # @pytest.mark.advanced_model # @pytest.mark.diffusion # @pytest.mark.parallel -# @hardware_test(res={"cuda": "L4", "rocm": "MI325"}, num_cards={"cuda": 4, "rocm": 2}) +# @hardware_test(res={"cuda": "L4", "rocm": "MI325", "xpu": "B60"}, num_cards={"cuda": 4, "rocm": 2, "xpu": 2}) # def test_zimage_tensor_parallel_tp2(tmp_path: Path): # if current_omni_platform.is_npu() or current_omni_platform.is_rocm(): # pytest.skip("Z-Image TP e2e test is only supported on CUDA for now.") diff --git a/tests/e2e/online_serving/test_images_generations_lora.py b/tests/e2e/online_serving/test_images_generations_lora.py index ac5d81ce0..a1f1d1450 100644 --- a/tests/e2e/online_serving/test_images_generations_lora.py +++ b/tests/e2e/online_serving/test_images_generations_lora.py @@ -147,7 +147,7 @@ def _basic_payload() -> dict: @pytest.mark.advanced_model @pytest.mark.diffusion -@hardware_test(res={"cuda": "L4", "rocm": "MI325"}) +@hardware_test(res={"cuda": "L4", "rocm": "MI325", "xpu": "B60"}) def test_images_generations_per_request_lora_switching(omni_server: OmniServer, tmp_path: Path) -> None: # Base generation. base_img = _post_images(omni_server, _basic_payload()) diff --git a/tests/e2e/online_serving/test_qwen3_omni.py b/tests/e2e/online_serving/test_qwen3_omni.py index 9227928f3..ef4d40198 100644 --- a/tests/e2e/online_serving/test_qwen3_omni.py +++ b/tests/e2e/online_serving/test_qwen3_omni.py @@ -48,6 +48,8 @@ def get_chunk_config(): if current_omni_platform.is_rocm(): # ROCm stage config optimized for MI325 GPU stage_configs = [str(Path(__file__).parent.parent / "stage_configs" / "rocm" / "qwen3_omni_ci.yaml")] +elif current_omni_platform.is_xpu(): + stage_configs = [str(Path(__file__).parent.parent / "stage_configs" / "xpu" / "qwen3_omni_ci.yaml")] else: stage_configs = [get_chunk_config()] diff --git a/tests/e2e/online_serving/test_qwen3_omni_expansion.py b/tests/e2e/online_serving/test_qwen3_omni_expansion.py index a28039888..6fb6a069e 100644 --- a/tests/e2e/online_serving/test_qwen3_omni_expansion.py +++ b/tests/e2e/online_serving/test_qwen3_omni_expansion.py @@ -6,6 +6,8 @@ E2E Online tests for Qwen3-Omni model. import os +from vllm_omni.platforms import current_omni_platform + os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn" from pathlib import Path @@ -50,6 +52,10 @@ def get_chunk_config(default_path): # CI stage config for 2*H100-80G GPUs default_path = str(Path(__file__).parent.parent / "stage_configs" / "qwen3_omni_ci.yaml") stage_configs = [default_path, get_chunk_config(default_path)] + +if current_omni_platform.is_xpu(): + stage_configs = [str(Path(__file__).parent.parent / "stage_configs" / "xpu" / "qwen3_omni_ci.yaml")] + # Create parameter combinations for model and stage config test_params = [ OmniServerParams(model=model, stage_config_path=stage_config) for model in models for stage_config in stage_configs diff --git a/tests/e2e/stage_configs/xpu/qwen2_5_omni_ci.yaml b/tests/e2e/stage_configs/xpu/qwen2_5_omni_ci.yaml new file mode 100644 index 000000000..de7e9d901 --- /dev/null +++ b/tests/e2e/stage_configs/xpu/qwen2_5_omni_ci.yaml @@ -0,0 +1,110 @@ +# stage config for running qwen2.5-omni with architecture of OmniLLM. + +# The following config is verified with 2 * Intel Arc Pro B60 XPU. +stage_args: + - stage_id: 0 + stage_type: llm # Use llm stage type to launch OmniLLM + runtime: + process: true # Run this stage in a separate process + devices: "0" # Visible devices for this stage + max_batch_size: 1 + engine_args: + model_stage: thinker + model_arch: Qwen2_5OmniForConditionalGeneration + worker_type: ar + scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler + max_model_len: 16384 + max_num_batched_tokens: 16384 + max_num_seqs: 1 + gpu_memory_utilization: 0.9 # thinker weight is around 16.74GB for Qwen2.5-Omni-7B + skip_mm_profiling: true + enforce_eager: true + trust_remote_code: true + engine_output_type: latent + enable_prefix_caching: false + mm_processor_cache_gb: 0 + is_comprehension: true + final_output: true + final_output_type: text + default_sampling_params: + temperature: 0.0 + top_p: 1.0 + top_k: -1 + max_tokens: 128 + seed: 42 + detokenize: True + repetition_penalty: 1.1 + - stage_id: 1 + stage_type: llm # Use llm stage type to launch OmniLLM + runtime: + process: true + devices: "1" + max_batch_size: 1 + engine_args: + model_stage: talker + model_arch: Qwen2_5OmniForConditionalGeneration + worker_type: ar + scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler + max_model_len: 16384 + max_num_batched_tokens: 16384 + max_num_seqs: 1 + gpu_memory_utilization: 0.5 # talker weight is 6.03GB for Qwen2.5-Omni-7B + skip_mm_profiling: true + enforce_eager: true + trust_remote_code: true + enable_prefix_caching: false + engine_output_type: latent + engine_input_source: [0] + custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen2_5_omni.thinker2talker + default_sampling_params: + temperature: 0.9 + top_p: 0.8 + top_k: 40 + max_tokens: 4096 + seed: 42 + detokenize: True + repetition_penalty: 1.05 + stop_token_ids: [8294] + + - stage_id: 2 + stage_type: llm # Use llm stage type to launch OmniLLM + runtime: + process: true + devices: "2" + max_batch_size: 1 + engine_args: + model_stage: code2wav + model_arch: Qwen2_5OmniForConditionalGeneration + worker_type: generation + scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler + gpu_memory_utilization: 0.3 # code2wav weight is around 1.46GB for Qwen2.5-Omni-7B + enforce_eager: true + trust_remote_code: true + enable_prefix_caching: false + engine_output_type: audio + engine_input_source: [1] + final_output: true + final_output_type: audio + default_sampling_params: + temperature: 0.0 + top_p: 1.0 + top_k: -1 + max_tokens: 2048 + seed: 42 + detokenize: True + repetition_penalty: 1.1 + +# Top-level runtime config (concise): default windows and stage edges +runtime: + enabled: true + defaults: + window_size: -1 # Simplified: trigger downstream only after full upstream completion + max_inflight: 1 # Simplified: process serially within each stage + + edges: + - from: 0 # thinker → talker: trigger only after receiving full input (-1) + to: 1 + window_size: -1 + - from: 1 # talker → code2wav: trigger only after receiving full input (-1) + to: 2 + window_size: -1 diff --git a/tests/e2e/stage_configs/xpu/qwen3_omni_ci.yaml b/tests/e2e/stage_configs/xpu/qwen3_omni_ci.yaml new file mode 100644 index 000000000..b362a8667 --- /dev/null +++ b/tests/e2e/stage_configs/xpu/qwen3_omni_ci.yaml @@ -0,0 +1,109 @@ +# Stage config for running Qwen3-Omni-MoE with 3-stage architecture +# Stage 0: Thinker (multimodal understanding + text generation) +# Stage 1: Talker (text embeddings → 8-layer RVQ codec codes) +# Stage 2: Code2Wav (8-layer RVQ codes → audio waveform) + +# The following config is verified with 8 * Intel Arc Pro B60 XPU. +stage_args: +- stage_id: 0 + stage_type: llm # Use llm stage type to launch OmniLLM + runtime: + devices: "0,1,2,3" + max_batch_size: 1 + engine_args: + model_stage: thinker + model_arch: Qwen3OmniMoeForConditionalGeneration + worker_type: ar + scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler + gpu_memory_utilization: 0.85 # thinker weight is around 61.08GB for Qwen3-Omni-30B-A3B-Instruct + skip_mm_profiling: true + enforce_eager: true + trust_remote_code: true + engine_output_type: latent # Output hidden states for talker + distributed_executor_backend: "mp" + max_num_batched_tokens: 4096 + max_model_len: 4096 + enable_prefix_caching: false + hf_config_name: thinker_config + tensor_parallel_size: 4 + max_cudagraph_capture_size: 0 + final_output: true + final_output_type: text + is_comprehension: true + default_sampling_params: + temperature: 0.4 + top_p: 0.9 + top_k: 1 + max_tokens: 100 + seed: 42 + ignore_eos: False + detokenize: True + repetition_penalty: 1.05 + +- stage_id: 1 + stage_type: llm # Use llm stage type to launch OmniLLM + runtime: + devices: "4" + max_batch_size: 1 + engine_args: + model_stage: talker + model_arch: Qwen3OmniMoeForConditionalGeneration + worker_type: ar + scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler + gpu_memory_utilization: 0.6 # talker weight is around 8.5GB for Qwen3-Omni-30B-A3B-Instruct + skip_mm_profiling: true + enforce_eager: true + trust_remote_code: true + engine_output_type: latent # Output codec codes for code2wav + enable_prefix_caching: false + max_num_batched_tokens: 4096 + max_model_len: 4096 + distributed_executor_backend: "mp" + hf_config_name: talker_config + max_cudagraph_capture_size: 0 + engine_input_source: [0] + custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker + # final_output: true + # final_output_type: text + default_sampling_params: + temperature: 0.9 + top_k: 50 + max_tokens: 4096 + seed: 42 + detokenize: False + repetition_penalty: 1.05 + stop_token_ids: [2150] + +- stage_id: 2 + stage_type: llm # Use llm stage type to launch OmniLLM + runtime: + devices: "5" + max_batch_size: 1 + engine_args: + model_stage: code2wav + model_arch: Qwen3OmniMoeForConditionalGeneration + worker_type: generation + scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler + enforce_eager: true + trust_remote_code: true + enable_prefix_caching: false + engine_output_type: audio # Final output: audio waveform + gpu_memory_utilization: 0.3 # code2wav weight is around 0.4GB for Qwen3-Omni-30B-A3B-Instruct + skip_mm_profiling: true + distributed_executor_backend: "mp" + max_num_batched_tokens: 100000 + hf_config_name: thinker_config + async_scheduling: false + max_cudagraph_capture_size: 0 + engine_input_source: [1] + custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav + final_output: true + final_output_type: audio + default_sampling_params: + temperature: 0.0 + top_p: 1.0 + top_k: -1 + max_tokens: 2000 + seed: 42 + detokenize: True + repetition_penalty: 1.1 diff --git a/tests/examples/online_serving/test_qwen2_5_omni.py b/tests/examples/online_serving/test_qwen2_5_omni.py index 58b011f19..6d6ebd318 100644 --- a/tests/examples/online_serving/test_qwen2_5_omni.py +++ b/tests/examples/online_serving/test_qwen2_5_omni.py @@ -4,6 +4,8 @@ Example online tests for Qwen2.5-Omni-7B model. import os +from vllm_omni.platforms import current_omni_platform + os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn" import re @@ -20,6 +22,11 @@ models = ["Qwen/Qwen2.5-Omni-7B"] stage_configs = [str(Path(__file__).parent.parent.parent / "e2e" / "stage_configs" / "qwen2_5_omni_ci.yaml")] +if current_omni_platform.is_xpu(): + stage_configs = [ + str(Path(__file__).parent.parent.parent / "e2e" / "stage_configs" / "xpu" / "qwen2_5_omni_ci.yaml") + ] + example_dir = str(Path(__file__).parent.parent.parent.parent / "examples" / "online_serving" / "qwen2_5_omni") # Create parameter combinations for model and stage config test_params = [ diff --git a/tests/examples/online_serving/test_qwen3_omni.py b/tests/examples/online_serving/test_qwen3_omni.py index 568f1f9b4..abe5322dd 100644 --- a/tests/examples/online_serving/test_qwen3_omni.py +++ b/tests/examples/online_serving/test_qwen3_omni.py @@ -4,6 +4,8 @@ Example Online tests for Qwen3-Omni model. import os +from vllm_omni.platforms import current_omni_platform + os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn" import re @@ -20,6 +22,10 @@ models = ["Qwen/Qwen3-Omni-30B-A3B-Instruct"] stage_configs = [str(Path(__file__).parent.parent.parent / "e2e" / "stage_configs" / "qwen3_omni_ci.yaml")] +if current_omni_platform.is_xpu(): + stage_configs = [str(Path(__file__).parent.parent.parent / "e2e" / "stage_configs" / "xpu" / "qwen3_omni_ci.yaml")] + + example_dir = str(Path(__file__).parent.parent.parent.parent / "examples" / "online_serving" / "qwen3_omni") # Create parameter combinations for model and stage config test_params = [ diff --git a/tests/utils.py b/tests/utils.py index e8a9d5c05..72fc6639a 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -402,13 +402,41 @@ def rocm_marks(*, res: str, num_cards: int): return marks + [test_distributed] +def xpu_marks(*, res: str, num_cards: int): + """ + Get a collection of pytest marks to apply for `@xpu_test`. + + Args: + res: Resource type, e.g., "B60". + num_cards: Number of GPU cards required. + + Returns: + List of pytest marks to apply. + """ + test_platform_detail = pytest.mark.xpu + + if res == "B60": + test_resource = pytest.mark.B60 + else: + raise ValueError(f"Invalid XPU resource type: {res}. Supported: B60") + + marks = [test_resource, test_platform_detail] + + if num_cards == 1: + return marks + else: + test_distributed = pytest.mark.distributed_rocm(num_cards=num_cards) + # TODO: add XPU support for `skipif_xpu` marker + return marks + [test_distributed] + + def gpu_marks(*, res: str, num_cards: int): """ Get a collection of pytest marks to apply for `@gpu_test`. Platform is automatically determined based on resource type. Args: - res: Resource type, e.g., "L4", "H100" for CUDA, or "MI325" for ROCm. + res: Resource type, e.g., "L4", "H100" for CUDA, or "MI325" for ROCm, or "B60" for XPU. num_cards: Number of GPU cards required. Returns: @@ -419,6 +447,8 @@ def gpu_marks(*, res: str, num_cards: int): return [test_platform] + cuda_marks(res=res, num_cards=num_cards) if res == "MI325": return [test_platform] + rocm_marks(res=res, num_cards=num_cards) + if res == "B60": + return [test_platform] + xpu_marks(res=res, num_cards=num_cards) raise ValueError(f"Invalid resource type: {res}. Supported: L4, H100, MI325") @@ -446,13 +476,13 @@ def npu_marks(*, res: str, num_cards: int): def hardware_marks(*, res: dict[str, str], num_cards: int | dict[str, int] = 1): """ Get a collection of pytest marks to apply for `@hardware_test`, - including CUDA, ROCm, and NPU, + including CUDA, ROCm, XPU, and NPU, based on the specified platforms and resources. """ # Validate platforms # Don't validate platform details in this decorator for platform, _ in res.items(): - if platform not in ("cuda", "rocm", "npu"): + if platform not in ("cuda", "rocm", "xpu", "npu"): raise ValueError(f"Unsupported platform: {platform}") # Normalize num_cards @@ -473,7 +503,7 @@ def hardware_marks(*, res: dict[str, str], num_cards: int | dict[str, int] = 1): all_marks: list[pytest.MarkDecorator] = [] for platform, resource in res.items(): cards = num_cards_dict[platform] - if platform == "cuda" or platform == "rocm": + if platform == "cuda" or platform == "rocm" or platform == "xpu": marks = gpu_marks(res=resource, num_cards=cards) elif platform == "npu": marks = npu_marks(res=resource, num_cards=cards)