diff --git a/.buildkite/scripts/docker_login_ecr_public.sh b/.buildkite/scripts/docker_login_ecr_public.sh new file mode 100755 index 000000000..51c5e1a5d --- /dev/null +++ b/.buildkite/scripts/docker_login_ecr_public.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# Helper function to safely login to ECR Public with per-job config isolation +# Uses DOCKER_CONFIG environment variable to prevent race conditions +# +# This script prevents the "device or resource busy" error by giving each +# Buildkite job its own isolated Docker config directory. +# +# Usage: +# source docker_login_ecr_public.sh && safe_docker_login_ecr_public + +set -euo pipefail + +# Configuration +ECR_REGISTRY="public.ecr.aws" + +setup_isolated_docker_config() { + # Use BUILDKITE_JOB_ID for job-specific isolation + # Fallback to PID if running outside Buildkite + local job_id="${BUILDKITE_JOB_ID:-$$}" + + # Set Docker config to job-specific directory + export DOCKER_CONFIG="/tmp/docker-config-${job_id}" + + # Create directory if it doesn't exist + mkdir -p "$DOCKER_CONFIG" + + echo "[docker-config] Using isolated Docker config: $DOCKER_CONFIG" +} + +check_docker_auth() { + # Check if already authenticated to the given registry + # Returns 0 if authenticated, 1 if not + local registry="$1" + + # Check if credentials exist in the isolated config + if [[ -f "$DOCKER_CONFIG/config.json" ]]; then + # Check if registry is present in config + if grep -q "$registry" "$DOCKER_CONFIG/config.json" 2>/dev/null; then + return 0 + fi + fi + + return 1 +} + +safe_docker_login_ecr_public() { + # Setup isolated config first + setup_isolated_docker_config + + local registry="$ECR_REGISTRY" + + # Check if already authenticated (within this job) + if check_docker_auth "$registry"; then + echo "[docker-login] Already authenticated to $registry in this job" + return 0 + fi + + # Perform login to isolated config directory + echo "[docker-login] Logging in to $ECR_REGISTRY (isolated config)..." + if aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin "$ECR_REGISTRY"; then + echo "[docker-login] Login successful (config: $DOCKER_CONFIG)" + return 0 + else + local exit_code=$? + echo "[docker-login] ERROR: Login failed with exit code $exit_code" >&2 + return $exit_code + fi +} + +# Execute if run as script (not sourced) +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + safe_docker_login_ecr_public +fi diff --git a/.buildkite/scripts/hardware_ci/run-amd-test.sh b/.buildkite/scripts/hardware_ci/run-amd-test.sh index a291f1b8c..5f7440a80 100755 --- a/.buildkite/scripts/hardware_ci/run-amd-test.sh +++ b/.buildkite/scripts/hardware_ci/run-amd-test.sh @@ -66,6 +66,14 @@ done echo "--- Pulling container" image_name="public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:${BUILDKITE_COMMIT}-rocm-omni" container_name="rocm_${BUILDKITE_COMMIT}_$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 10; echo)" + +# Install AWS CLI to authenticate to ECR Public Gallery to get higher rate limit for pulling images +sudo apt-get update && sudo apt-get install -y awscli +# Use safe docker login helper to prevent race conditions +source "$(dirname "${BASH_SOURCE[0]}")/../docker_login_ecr_public.sh" +safe_docker_login_ecr_public +# Pull the container from ECR Public Gallery + docker pull "${image_name}" remove_docker_container() { diff --git a/.buildkite/test-amd.yaml b/.buildkite/test-amd.yaml index 57008e59f..bcece8c49 100644 --- a/.buildkite/test-amd.yaml +++ b/.buildkite/test-amd.yaml @@ -1,10 +1,10 @@ steps: - label: "Diffusion Model Test" - timeout_in_minutes: 15 + timeout_in_minutes: 20 agent_pool: mi325_2 depends_on: amd-build - mirror_hardwares: [amdexperimental, amdproduction, amdtentative] + mirror_hardwares: [amdproduction] grade: Blocking commands: - export GPU_ARCHS=gfx942 @@ -20,7 +20,7 @@ steps: timeout_in_minutes: 15 agent_pool: mi325_1 depends_on: amd-build - mirror_hardwares: [amdexperimental, amdproduction, amdtentative] + mirror_hardwares: [amdproduction] grade: Blocking commands: - export GPU_ARCHS=gfx942 @@ -34,11 +34,33 @@ steps: - export VLLM_ROCM_USE_AITER_RMSNORM=0 - pytest -s -v tests/e2e/offline_inference/test_cache_dit.py tests/e2e/offline_inference/test_teacache.py +- label: "Diffusion Parallelism Test" + timeout_in_minutes: 15 + agent_pool: mi325_2 + depends_on: amd-build + mirror_hardwares: [amdproduction] + grade: Blocking + commands: + - export MIOPEN_DEBUG_CONV_DIRECT=0 + - export MIOPEN_DEBUG_CONV_GEMM=0 + - pytest -s -v tests/e2e/offline_inference/test_sequence_parallel.py + +- label: "Diffusion GPU Worker Test" + timeout_in_minutes: 20 + agent_pool: mi325_2 + depends_on: amd-build + mirror_hardwares: [amdproduction] + grade: Blocking + commands: + - export MIOPEN_DEBUG_CONV_DIRECT=0 + - export MIOPEN_DEBUG_CONV_GEMM=0 + - pytest -s -v tests/diffusion/test_gpu_worker.py + - label: "Omni Model Test Qwen2-5-Omni" timeout_in_minutes: 15 agent_pool: mi325_2 depends_on: amd-build - mirror_hardwares: [amdexperimental, amdproduction, amdtentative] + mirror_hardwares: [amdproduction] grade: Blocking commands: - export GPU_ARCHS=gfx942 @@ -51,3 +73,34 @@ steps: - export VLLM_ROCM_USE_AITER_LINEAR=0 - export VLLM_ROCM_USE_AITER_RMSNORM=0 - pytest -s -v tests/e2e/offline_inference/test_qwen2_5_omni.py + +- label: "Omni Model Test Qwen3-Omni" + timeout_in_minutes: 15 + agent_pool: mi325_2 + depends_on: amd-build + mirror_hardwares: [amdproduction] + grade: Blocking + commands: + - export VLLM_LOGGING_LEVEL=DEBUG + - export VLLM_WORKER_MULTIPROC_METHOD=spawn + - export MIOPEN_DEBUG_CONV_DIRECT=0 + - export MIOPEN_DEBUG_CONV_GEMM=0 + - pytest -s -v tests/e2e/offline_inference/test_qwen3_omni.py tests/e2e/online_serving/test_qwen3_omni.py + +- label: "Diffusion Image Edit Test" + timeout_in_minutes: 15 + agent_pool: mi325_1 + depends_on: amd-build + mirror_hardwares: [amdproduction] + grade: Blocking + commands: + - export GPU_ARCHS=gfx942 + - export VLLM_LOGGING_LEVEL=DEBUG + - export VLLM_WORKER_MULTIPROC_METHOD=spawn + - export MIOPEN_DEBUG_CONV_DIRECT=0 + - export MIOPEN_DEBUG_CONV_GEMM=0 + - export VLLM_ROCM_USE_AITER=1 + - export VLLM_ROCM_USE_AITER_MHA=1 + - export VLLM_ROCM_USE_AITER_LINEAR=0 + - export VLLM_ROCM_USE_AITER_RMSNORM=0 + - pytest -s -v tests/e2e/online_serving/test_i2i_multi_image_input.py diff --git a/.buildkite/test-template-amd-omni.j2 b/.buildkite/test-template-amd-omni.j2 index c407f74d7..4d98e9295 100644 --- a/.buildkite/test-template-amd-omni.j2 +++ b/.buildkite/test-template-amd-omni.j2 @@ -13,7 +13,7 @@ depends_on: ~ soft_fail: false commands: - - "aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/q9t5s3a7" + - "source .buildkite/scripts/docker_login_ecr_public.sh && safe_docker_login_ecr_public" - "docker build -f docker/Dockerfile.rocm -t {{ docker_image_amd }} --progress plain ." - "docker push {{ docker_image_amd }}" key: "amd-build" diff --git a/tests/e2e/offline_inference/stage_configs/rocm/qwen3_omni_ci.yaml b/tests/e2e/offline_inference/stage_configs/rocm/qwen3_omni_ci.yaml new file mode 100644 index 000000000..5e3be865e --- /dev/null +++ b/tests/e2e/offline_inference/stage_configs/rocm/qwen3_omni_ci.yaml @@ -0,0 +1,98 @@ +# Stage config for running Qwen3-Omni-MoE with 3-stage architecture +# Stage 0: Thinker (multimodal understanding + text generation) +# Stage 1: Talker (text embeddings → 16-layer RVQ codec codes) +# Stage 2: Code2Wav (8-layer RVQ codes → audio waveform) + +# The following config has been verified on 2x H100-80G GPUs. +stage_args: + - stage_id: 0 + runtime: + devices: "0,1" + max_batch_size: 1 + engine_args: + model_stage: thinker + model_arch: Qwen3OmniMoeForConditionalGeneration + worker_cls: vllm_omni.worker.gpu_ar_worker.GPUARWorker + scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler + gpu_memory_utilization: 0.6 + enforce_eager: false + trust_remote_code: true + engine_output_type: latent # Output hidden states for talker + distributed_executor_backend: "mp" + enable_prefix_caching: false + hf_config_name: thinker_config + tensor_parallel_size: 2 + load_format: dummy + 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 + detokenize: True + repetition_penalty: 1.05 + + - stage_id: 1 + runtime: + devices: "1" + max_batch_size: 1 + engine_args: + model_stage: talker + model_arch: Qwen3OmniMoeForConditionalGeneration + worker_cls: vllm_omni.worker.gpu_ar_worker.GPUARWorker + scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler + gpu_memory_utilization: 0.3 + enforce_eager: true + trust_remote_code: true + engine_output_type: latent # Output codec codes for code2wav + # tensor_parallel_size: 2 + enable_prefix_caching: false + distributed_executor_backend: "mp" + hf_config_name: talker_config + load_format: dummy + 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: 100 + seed: 42 + detokenize: False + repetition_penalty: 1.05 + stop_token_ids: [2150] + + - stage_id: 2 + runtime: + devices: "0" + max_batch_size: 1 + engine_args: + model_stage: code2wav + model_arch: Qwen3OmniMoeForConditionalGeneration + worker_cls: vllm_omni.worker.gpu_generation_worker.GPUGenerationWorker + 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.1 + distributed_executor_backend: "mp" + max_num_batched_tokens: 1000000 + hf_config_name: thinker_config + load_format: dummy + 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: 200 + seed: 42 + detokenize: True + repetition_penalty: 1.1 diff --git a/tests/e2e/offline_inference/test_qwen3_omni.py b/tests/e2e/offline_inference/test_qwen3_omni.py index 9dc3d614a..71eac2a68 100644 --- a/tests/e2e/offline_inference/test_qwen3_omni.py +++ b/tests/e2e/offline_inference/test_qwen3_omni.py @@ -10,14 +10,20 @@ from pathlib import Path import pytest from vllm.assets.video import VideoAsset +from vllm_omni.utils import is_rocm + from .conftest import OmniRunner os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn" models = ["Qwen/Qwen3-Omni-30B-A3B-Instruct"] -# CI stage config for 2xH100-80G GPUs -stage_configs = [str(Path(__file__).parent / "stage_configs" / "qwen3_omni_ci.yaml")] +# CI stage config for 2xH100-80G GPUs or AMD GPU MI325 +if is_rocm(): + # ROCm stage config optimized for MI325 GPU + stage_configs = [str(Path(__file__).parent / "stage_configs" / "rocm" / "qwen3_omni_ci.yaml")] +else: + stage_configs = [str(Path(__file__).parent / "stage_configs" / "qwen3_omni_ci.yaml")] # Create parameter combinations for model and stage config test_params = [(model, stage_config) for model in models for stage_config in stage_configs] diff --git a/tests/e2e/online_serving/stage_configs/rocm/qwen3_omni_ci.yaml b/tests/e2e/online_serving/stage_configs/rocm/qwen3_omni_ci.yaml new file mode 100644 index 000000000..7420e2745 --- /dev/null +++ b/tests/e2e/online_serving/stage_configs/rocm/qwen3_omni_ci.yaml @@ -0,0 +1,98 @@ +# Stage config for running Qwen3-Omni-MoE with 3-stage architecture +# Stage 0: Thinker (multimodal understanding + text generation) +# Stage 1: Talker (text embeddings → 16-layer RVQ codec codes) +# Stage 2: Code2Wav (8-layer RVQ codes → audio waveform) + +# The following config has been verified on 2x H100-80G GPUs. +stage_args: + - stage_id: 0 + runtime: + devices: "0,1" + max_batch_size: 5 + engine_args: + model_stage: thinker + model_arch: Qwen3OmniMoeForConditionalGeneration + worker_cls: vllm_omni.worker.gpu_ar_worker.GPUARWorker + scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler + gpu_memory_utilization: 0.6 + enforce_eager: false + trust_remote_code: true + engine_output_type: latent # Output hidden states for talker + distributed_executor_backend: "mp" + enable_prefix_caching: false + hf_config_name: thinker_config + tensor_parallel_size: 2 + load_format: dummy + 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 + detokenize: True + repetition_penalty: 1.05 + + - stage_id: 1 + runtime: + devices: "1" + max_batch_size: 5 + engine_args: + model_stage: talker + model_arch: Qwen3OmniMoeForConditionalGeneration + worker_cls: vllm_omni.worker.gpu_ar_worker.GPUARWorker + scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler + gpu_memory_utilization: 0.3 + enforce_eager: true + trust_remote_code: true + engine_output_type: latent # Output codec codes for code2wav + # tensor_parallel_size: 2 + enable_prefix_caching: false + distributed_executor_backend: "mp" + hf_config_name: talker_config + load_format: dummy + 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: 100 + seed: 42 + detokenize: False + repetition_penalty: 1.05 + stop_token_ids: [2150] + + - stage_id: 2 + runtime: + devices: "0" + max_batch_size: 1 + engine_args: + model_stage: code2wav + model_arch: Qwen3OmniMoeForConditionalGeneration + worker_cls: vllm_omni.worker.gpu_generation_worker.GPUGenerationWorker + 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.1 + distributed_executor_backend: "mp" + max_num_batched_tokens: 1000000 + hf_config_name: thinker_config + load_format: dummy + 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: 200 + seed: 42 + detokenize: True + repetition_penalty: 1.1 diff --git a/tests/e2e/online_serving/test_qwen3_omni.py b/tests/e2e/online_serving/test_qwen3_omni.py index 5d54c00c0..b06d58ea1 100644 --- a/tests/e2e/online_serving/test_qwen3_omni.py +++ b/tests/e2e/online_serving/test_qwen3_omni.py @@ -20,12 +20,18 @@ import pytest from vllm.assets.video import VideoAsset from vllm.utils import get_open_port +from vllm_omni.utils import is_rocm + os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn" models = ["Qwen/Qwen3-Omni-30B-A3B-Instruct"] -# CI stage config for 2*H100-80G GPUs -stage_configs = [str(Path(__file__).parent / "stage_configs" / "qwen3_omni_ci.yaml")] +# CI stage config for 2xH100-80G GPUs or AMD GPU MI325 +if is_rocm(): + # ROCm stage config optimized for MI325 GPU + stage_configs = [str(Path(__file__).parent / "stage_configs" / "rocm" / "qwen3_omni_ci.yaml")] +else: + stage_configs = [str(Path(__file__).parent / "stage_configs" / "qwen3_omni_ci.yaml")] # Create parameter combinations for model and stage config test_params = [(model, stage_config) for model in models for stage_config in stage_configs]