From bc73546c065844136b16e3a8e799cc0bd36bc2ca Mon Sep 17 00:00:00 2001 From: TJian Date: Fri, 14 Aug 2026 15:38:47 +0800 Subject: [PATCH] [ROCm] [CI] Migrate CI to mi300x for v0.27.x (#5886) Signed-off-by: tjtanaa Signed-off-by: TJian Co-authored-by: Hongsheng Liu --- .buildkite/amd/scripts/run-amd-test.sh | 232 +++++++----------- .buildkite/amd/test-amd-merge.yml | 79 +++--- .buildkite/amd/test-amd-ready.yml | 81 +++--- .buildkite/amd/test-template-amd-omni.j2 | 112 ++++++++- docker/Dockerfile.rocm | 11 +- pyproject.toml | 1 + .../ar_diffusion/test_paged_attention.py | 14 +- tests/diffusion/attention/test_flash_attn.py | 13 +- .../distributed/test_sequence_parallel.py | 12 +- .../lora/test_images_generations_lora.py | 16 ++ tests/engine/test_duplex_import_boundary.py | 18 ++ .../openai_api/test_decode_audio_url.py | 3 +- .../ar_diffusion/kv_cache/paged_attention.py | 44 ++++ 13 files changed, 403 insertions(+), 233 deletions(-) diff --git a/.buildkite/amd/scripts/run-amd-test.sh b/.buildkite/amd/scripts/run-amd-test.sh index c49614e70..c1c80c495 100755 --- a/.buildkite/amd/scripts/run-amd-test.sh +++ b/.buildkite/amd/scripts/run-amd-test.sh @@ -1,153 +1,103 @@ #!/bin/bash -# vllm-omni customized version -# Based on: vllm/.buildkite/scripts/hardware_ci/run-amd-test.sh -# Last synced: 2025-12-15 -# Modifications: docker image name for vllm-omni -# This script runs test inside the corresponding ROCm docker container. -set -o pipefail +# Run vLLM-Omni ROCm tests directly in the MI300 Kubernetes pod. The pod's +# container image is selected by test-template-amd-omni.j2; MI300 has no DinD. +set -euo pipefail -# Export Python path -export PYTHONPATH=".." +: "${PYTHONFAULTHANDLER:=1}" +: "${HF_HOME:=/home/buildkite-agent/huggingface}" +: "${HF_HUB_DOWNLOAD_TIMEOUT:=300}" +: "${HF_HUB_ETAG_TIMEOUT:=60}" +: "${MIOPEN_DEBUG_CONV_DIRECT:=0}" +: "${MIOPEN_DEBUG_CONV_GEMM:=0}" +: "${VLLM_ROCM_USE_AITER:=0}" +export PYTHONFAULTHANDLER HF_HOME HF_HUB_DOWNLOAD_TIMEOUT HF_HUB_ETAG_TIMEOUT +export MIOPEN_DEBUG_CONV_DIRECT MIOPEN_DEBUG_CONV_GEMM VLLM_ROCM_USE_AITER +export PYTORCH_ROCM_ARCH="" +export PYTHONPATH="${PYTHONPATH:-..}" + +if [[ "${VLLM_CI_DOCKER_DISABLED:-0}" != "1" ]]; then + echo "Error: MI300 CI must run natively with Docker disabled." >&2 + exit 1 +fi + +if [[ -n "${TEST_COMMANDS:-}" ]]; then + commands="${TEST_COMMANDS}" +else + commands="$*" +fi +if [[ -z "${commands}" ]]; then + echo "Error: No test commands provided." >&2 + exit 1 +fi + +echo "--- Native in-pod ROCm CI" + +job_id="${BUILDKITE_JOB_ID:-${BUILDKITE_PARALLEL_JOB:-local}}" +job_id="${job_id//[^A-Za-z0-9_.-]/_}" +native_cache_root="/tmp/vllm-omni-native-${job_id}" +TMPDIR="${native_cache_root}/tmp" +TORCHINDUCTOR_CACHE_DIR="${native_cache_root}/torchinductor" +TRITON_CACHE_DIR="${native_cache_root}/triton" +VLLM_CACHE_ROOT="${native_cache_root}/vllm" +XDG_CACHE_HOME="${native_cache_root}/xdg" +HF_DATASETS_CACHE="${native_cache_root}/huggingface/datasets" +export TMPDIR TORCHINDUCTOR_CACHE_DIR TRITON_CACHE_DIR VLLM_CACHE_ROOT +export XDG_CACHE_HOME HF_DATASETS_CACHE +mkdir -p "${TMPDIR}" "${TORCHINDUCTOR_CACHE_DIR}" "${TRITON_CACHE_DIR}" \ + "${VLLM_CACHE_ROOT}" "${XDG_CACHE_HOME}" "${HF_HOME}" \ + "${HF_DATASETS_CACHE}" + +if [[ "${VLLM_CI_REQUIRE_PERSISTENT_HF_CACHE:-0}" == "1" ]]; then + if ! command -v findmnt >/dev/null 2>&1; then + echo "Error: findmnt is required to verify the Hugging Face cache mount." >&2 + exit 1 + fi + hf_mount=$(findmnt -n -T "${HF_HOME}" -o TARGET 2>/dev/null || true) + if [[ -z "${hf_mount}" || "${hf_mount}" == "/" ]]; then + echo "Error: MI300 CI requires a persistent volume at or above ${HF_HOME}." >&2 + exit 1 + fi +fi -# Print ROCm version -echo "--- ROCm info" rocminfo -# cleanup older docker images -cleanup_docker() { - # Get Docker's root directory - docker_root=$(docker info -f '{{.DockerRootDir}}') - if [ -z "$docker_root" ]; then - echo "Failed to determine Docker root directory." - exit 1 - fi - echo "Docker root directory: $docker_root" - # Check disk usage of the filesystem where Docker's root directory is located - disk_usage=$(df "$docker_root" | tail -1 | awk '{print $5}' | sed 's/%//') - # Define the threshold - threshold=70 - if [ "$disk_usage" -gt "$threshold" ]; then - echo "Disk usage is above $threshold%. Cleaning up Docker images and volumes..." - # Remove dangling images (those that are not tagged and not used by any container) - docker image prune -f - # Remove unused volumes / force the system prune for old images as well. - docker volume prune -f && docker system prune --force --filter "until=72h" --all - echo "Docker images and volumes cleanup completed." - else - echo "Disk usage is below $threshold%. No cleanup needed." - fi -} +expected_gpus="${VLLM_CI_EXPECTED_GPU_COUNT:-1}" +python3 - <<'PY' +import os -# Call the cleanup docker function -cleanup_docker +import torch -echo "--- Pulling container" -## Temporary change to use AMD Docker Hub to store the vllm-omni image -# to bypass the rate limit issue with ECR Public Gallery. -# Images are now stored in a separate repository for vllm-omni, instead of vllm-ci. -# TODO: @tjtanaa point back to ECR Public Gallery -# once the amd agents are configured to use ECR Public Gallery. -# image_name="public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:${BUILDKITE_COMMIT}-rocm-omni" -image_name="rocm/vllm-omni:${BUILDKITE_COMMIT}" -container_name="rocm_${BUILDKITE_COMMIT}_$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 10; echo)" +expected = int(os.environ.get("VLLM_CI_EXPECTED_GPU_COUNT", "1")) +assert torch.version.hip, "PyTorch is not a ROCm build" +assert torch.cuda.is_available(), "ROCm GPU is not available to PyTorch" +actual = torch.cuda.device_count() +assert actual == expected, f"Expected {expected} ROCm GPU(s), found {actual}" +PY -# TODO: @tjtanaa uncomment this once the amd agents are configured to use ECR Public Gallery. -# # 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]}")/../../common/scripts/docker_login_ecr_public.sh" -# safe_docker_login_ecr_public - -## Pull the container from AMD Docker Hub - -docker pull "${image_name}" - -remove_docker_container() { - docker ps -aq --filter "name=${container_name}" | xargs -r docker rm -f || true - docker image rm -f "${image_name}" || true -} -trap remove_docker_container EXIT - -echo "--- Running container" - -HF_CACHE="$(realpath ~)/huggingface" -mkdir -p "${HF_CACHE}" -HF_MOUNT="/root/.cache/huggingface" - -if [[ -n "${TEST_COMMAND:-}" ]]; then - commands="$TEST_COMMAND" -else - commands="$@" -fi -echo "Commands:$commands" - -PARALLEL_JOB_COUNT=8 -MYPYTHONPATH=".." - -# Test that we're launching on the machine that has -# proper access to GPUs -render_gid=$(getent group render | cut -d: -f3) -if [[ -z "$render_gid" ]]; then - echo "Error: 'render' group not found. This is required for GPU access." >&2 - exit 1 -fi - -# check if the command contains shard flag, we will run all shards in parallel because the host have 8 GPUs. -# TODO: @tjtanaa reenable to run VLLM_ROCM_USE_AITER=1 when AITER is shipped with prebuilt kernels. -if [[ $commands == *"--shard-id="* ]]; then - # assign job count as the number of shards used - commands=$(echo "$commands" | sed -E "s/--num-shards[[:blank:]]*=[[:blank:]]*[0-9]*/--num-shards=${PARALLEL_JOB_COUNT} /g" | sed 's/ \\ / /g') - for GPU in $(seq 0 $(($PARALLEL_JOB_COUNT-1))); do - # assign shard-id for each shard - commands_gpu=$(echo "$commands" | sed -E "s/--shard-id[[:blank:]]*=[[:blank:]]*[0-9]*/--shard-id=${GPU} /g" | sed 's/ \\ / /g') - echo "Shard ${GPU} commands:$commands_gpu" - echo "Render devices: $BUILDKITE_AGENT_META_DATA_RENDER_DEVICES" - docker run \ - --device /dev/kfd $BUILDKITE_AGENT_META_DATA_RENDER_DEVICES \ - --network=host \ - --shm-size=16gb \ - --group-add "$render_gid" \ - -e MIOPEN_DEBUG_CONV_DIRECT=0 \ - -e MIOPEN_DEBUG_CONV_GEMM=0 \ - -e VLLM_ROCM_USE_AITER=0 \ - -e HIP_VISIBLE_DEVICES="${GPU}" \ - -e HF_TOKEN \ - -v "${HF_CACHE}:${HF_MOUNT}" \ - -e "HF_HOME=${HF_MOUNT}" \ - -e "PYTHONPATH=${MYPYTHONPATH}" \ - --name "${container_name}_${GPU}" \ - "${image_name}" \ - /bin/bash -c "${commands_gpu}" \ - |& while read -r line; do echo ">>Shard $GPU: $line"; done & - PIDS+=($!) - done - #wait for all processes to finish and collect exit codes - for pid in "${PIDS[@]}"; do - wait "${pid}" - STATUS+=($?) - done - for st in "${STATUS[@]}"; do - if [[ ${st} -ne 0 ]]; then - echo "One of the processes failed with $st" - exit "${st}" +echo "Commands:${commands}" +if /bin/bash -o pipefail -c ' +set -E +test_status=0 +trap '\'' + command_status=$? + # Keep running subsequent commands, but retain a failure for the final exit. + # Prefer a test failure over pytest "no tests collected" when both occur. + if (( test_status == 0 || (test_status == 5 && command_status != 5) )); then + test_status=${command_status} fi - done +'\'' ERR +eval "$1" +exit "${test_status}" +' _ "${commands}"; then + exit 0 else - echo "Render devices: $BUILDKITE_AGENT_META_DATA_RENDER_DEVICES" - docker run \ - --device /dev/kfd $BUILDKITE_AGENT_META_DATA_RENDER_DEVICES \ - --network=host \ - --shm-size=16gb \ - --group-add "$render_gid" \ - -e MIOPEN_DEBUG_CONV_DIRECT=0 \ - -e MIOPEN_DEBUG_CONV_GEMM=0 \ - -e VLLM_ROCM_USE_AITER=0 \ - -e HF_TOKEN \ - -v "${HF_CACHE}:${HF_MOUNT}" \ - -e "HF_HOME=${HF_MOUNT}" \ - -e "PYTHONPATH=${MYPYTHONPATH}" \ - --name "${container_name}" \ - "${image_name}" \ - /bin/bash -c "${commands}" + exit_code=$? fi + +if [[ ${exit_code} -eq 5 && "${VLLM_CI_ALLOW_NO_TESTS:-0}" == "1" ]]; then + echo "Pytest collected no tests; VLLM_CI_ALLOW_NO_TESTS=1, treating exit code 5 as success." + exit 0 +fi + +exit "${exit_code}" diff --git a/.buildkite/amd/test-amd-merge.yml b/.buildkite/amd/test-amd-merge.yml index 4e80d1968..fbe3b0c0f 100644 --- a/.buildkite/amd/test-amd-merge.yml +++ b/.buildkite/amd/test-amd-merge.yml @@ -7,54 +7,64 @@ steps: - group: ":card_index_dividers: Simple Test" steps: - - label: "Simple · Diffusion & Model Executor Test (1/2)" - agent_pool: mi325_1 + - label: "Simple · Model Executor Test · Shard %N/%t" + agent_pool: mi300_1 + parallelism: 2 depends_on: amd-build - timeout_in_minutes: 70 mirror_hardwares: [amdproduction] grade: Blocking commands: - export VLLM_ROCM_USE_AITER=0 # ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM # that is not supported on CPU - # CPU tests on ROCm gpu machines are very slow, so we give them more time. - - "timeout 60m pytest -sv tests/diffusion -m 'core_model and cpu' --ignore=tests/diffusion/cache/test_teacache_extractors.py --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml" + - "pytest -sv tests/model_executor -m 'core_model and cpu' --num-shards=$$BUILDKITE_PARALLEL_JOB_COUNT --shard-id=$$BUILDKITE_PARALLEL_JOB --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml:coverage-model-executor-$$BUILDKITE_PARALLEL_JOB.xml" - - label: "Simple · Diffusion & Model Executor Test (2/2)" - agent_pool: mi325_1 + - label: "Simple · Diffusion Test · Shard %N/%t" + agent_pool: mi300_1 + parallelism: 2 depends_on: amd-build - timeout_in_minutes: 70 mirror_hardwares: [amdproduction] grade: Blocking commands: - export VLLM_ROCM_USE_AITER=0 - # CPU tests on ROCm gpu machines are very slow, so we give them more time. - - "timeout 60m pytest -sv tests/model_executor -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml" + # ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM + # that is not supported on CPU + - "pytest -sv tests/diffusion -m 'core_model and cpu' --ignore=tests/diffusion/cache/test_teacache_extractors.py --num-shards=$$BUILDKITE_PARALLEL_JOB_COUNT --shard-id=$$BUILDKITE_PARALLEL_JOB --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml:coverage-diffusion-$$BUILDKITE_PARALLEL_JOB.xml" + + - label: "Simple · Engine&Entrypoints Test" + agent_pool: mi300_1 + depends_on: amd-build + mirror_hardwares: [amdproduction] + grade: Blocking + commands: + - export VLLM_ROCM_USE_AITER=0 + - "pytest -sv tests/entrypoints tests/engine -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml" - label: "Simple · Other Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build - timeout_in_minutes: 50 mirror_hardwares: [amdproduction] grade: Blocking commands: - export VLLM_ROCM_USE_AITER=0 # ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM # that is not supported on CPU - - "timeout 40m pytest -sv tests/diffusion tests/model_executor -m 'core_model and cpu' --ignore=tests/diffusion/cache/test_teacache_extractors.py --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml" + - "pytest -sv tests/ -m 'core_model and cpu' --ignore=tests/diffusion --ignore=tests/model_executor --ignore=tests/entrypoints --ignore=tests/engine --ignore=tests/diffusion/cache/test_teacache_extractors.py --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml" - group: ":card_index_dividers: Diffusion Test" steps: - label: "Diffusion · LoRA Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking + artifact_paths: + - lora-server.log commands: - - timeout 20m pytest -s -v tests/diffusion/lora/test_images_generations_lora.py + - pytest -sv tests/diffusion/lora/ -m 'advanced_model and cuda' --run-level "advanced_model" 2>&1 | tee lora-server.log - label: "Diffusion Model CPU offloading Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -62,7 +72,7 @@ steps: - export VLLM_LOGGING_LEVEL=DEBUG - export VLLM_WORKER_MULTIPROC_METHOD=spawn - | - timeout 20m bash -c ' + bash -c ' set +e pytest -s -v tests/diffusion/offloader/test_diffusion_cpu_offload.py EXIT1=\$? @@ -72,7 +82,7 @@ steps: ' - label: "Diffusion Sequence Parallelism Test (Need 4 GPUs)" - agent_pool: mi325_4 + agent_pool: mi300_4 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -84,7 +94,7 @@ steps: # merge-only tests - label: "Diffusion Tensor Parallelism Test" - agent_pool: mi325_2 + agent_pool: mi300_2 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -92,7 +102,7 @@ steps: - pytest -s -v tests/diffusion/distributed/test_tensor_parallel.py - label: "Diffusion Model Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -100,7 +110,7 @@ steps: - timeout 30m pytest -s -v tests/e2e/offline_inference/test_z_image.py - label: "Diffusion GPU Worker Test" - agent_pool: mi325_2 + agent_pool: mi300_2 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -108,7 +118,7 @@ steps: - timeout 20m pytest -s -v tests/diffusion/test_diffusion_worker.py - label: "Diffusion · Wan22 Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -121,7 +131,7 @@ steps: ## ISSUE depends on `diffusers` package: https://github.com/huggingface/diffusers/issues/13274 # - label: "Audio Generation Model Test" -# agent_pool: mi325_1 +# agent_pool: mi300_1 # depends_on: amd-build # mirror_hardwares: [amdproduction] # grade: Blocking @@ -132,7 +142,7 @@ steps: # - timeout 20m pytest -s -v tests/e2e/offline_inference/test_stable_audio_expansion.py -m "advanced_model and diffusion and L4" --run-level advanced_model - label: "Engine Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -147,7 +157,7 @@ steps: - group: ":card_index_dividers: E2E Test" steps: - label: "Omni Model Test Qwen2-5-Omni" - agent_pool: mi325_4 + agent_pool: mi300_4 depends_on: amd-build mirror_hardwares: [amdproduction] commands: @@ -155,7 +165,7 @@ steps: - pytest -s -v tests/e2e/*/test_qwen2_5_omni_expansion.py --run-level "core_model" - label: "Omni Model Test Qwen3-Omni" - agent_pool: mi325_2 + agent_pool: mi300_2 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -163,22 +173,23 @@ steps: - export VLLM_WORKER_MULTIPROC_METHOD=spawn - pytest -s -v tests/e2e/offline_inference/test_qwen3_omni.py tests/e2e/online_serving/test_qwen3_omni.py -m 'advanced_model and cuda' --run-level 'advanced_model' - - label: "Qwen3-TTS CustomVoice E2E Test" - agent_pool: mi325_1 + - label: "Qwen3-TTS CustomVoice E2E Test · Shard %N/%t" + agent_pool: mi300_1 + parallelism: 2 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking commands: - | - timeout 50m bash -c ' + bash -c ' export VLLM_LOGGING_LEVEL=DEBUG export VLLM_WORKER_MULTIPROC_METHOD=spawn export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1" - pytest -s -v tests/e2e/online_serving/test_qwen3_tts_customvoice.py tests/e2e/offline_inference/test_qwen3_tts_customvoice.py -m "advanced_model" --run-level "advanced_model" + pytest -s -v tests/e2e/online_serving/test_qwen3_tts_customvoice.py tests/e2e/offline_inference/test_qwen3_tts_customvoice.py -m "advanced_model" --run-level "advanced_model" --num-shards=$$BUILDKITE_PARALLEL_JOB_COUNT --shard-id=$$BUILDKITE_PARALLEL_JOB ' - label: "Qwen3-TTS Base E2E Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -196,7 +207,7 @@ steps: # Need to debug before reneable numerical changes across large PRs # # split Bagel Model Test with H100 (Real Weights) into three tests # - label: "Bagel Text2Img Model Test (1/3)" -# agent_pool: mi325_1 +# agent_pool: mi300_1 # depends_on: amd-build # mirror_hardwares: [amdproduction] # grade: Blocking @@ -208,7 +219,7 @@ steps: # - timeout 30m pytest -s -v tests/e2e/offline_inference/test_bagel.py -m "advanced_model" --run-level "advanced_model" -k "shared_memory" -k "rocm" # - label: "Bagel Img2Img Model Test (2/3)" -# agent_pool: mi325_1 +# agent_pool: mi300_1 # depends_on: amd-build # mirror_hardwares: [amdproduction] # grade: Blocking @@ -220,7 +231,7 @@ steps: # - timeout 30m pytest -s -v tests/e2e/offline_inference/test_bagel.py -m "advanced_model" --run-level "advanced_model" -k "rocm" # - label: "Bagel Online Serving Test (3/3)" -# agent_pool: mi325_1 +# agent_pool: mi300_1 # depends_on: amd-build # mirror_hardwares: [amdproduction] # grade: Blocking diff --git a/.buildkite/amd/test-amd-ready.yml b/.buildkite/amd/test-amd-ready.yml index 8f99ce909..b8fca2f88 100644 --- a/.buildkite/amd/test-amd-ready.yml +++ b/.buildkite/amd/test-amd-ready.yml @@ -7,8 +7,9 @@ steps: - group: ":card_index_dividers: Simple Test" steps: - - label: "Simple · Diffusion & Model Executor Test" - agent_pool: mi325_1 + - label: "Simple · Model Executor Test · Shard %N/%t" + agent_pool: mi300_1 + parallelism: 2 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -16,10 +17,30 @@ steps: - export VLLM_ROCM_USE_AITER=0 # ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM # that is not supported on CPU - - "timeout 40m pytest -sv tests/diffusion tests/model_executor -m 'core_model and cpu' --ignore=tests/diffusion/cache/test_teacache_extractors.py --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml" + - "pytest -sv tests/model_executor -m 'core_model and cpu' --num-shards=$$BUILDKITE_PARALLEL_JOB_COUNT --shard-id=$$BUILDKITE_PARALLEL_JOB --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml:coverage-model-executor-$$BUILDKITE_PARALLEL_JOB.xml" + + - label: "Simple · Diffusion Test" + agent_pool: mi300_1 + depends_on: amd-build + mirror_hardwares: [amdproduction] + grade: Blocking + commands: + - export VLLM_ROCM_USE_AITER=0 + # ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM + # that is not supported on CPU + - "pytest -sv tests/diffusion -m 'core_model and cpu' --ignore=tests/diffusion/cache/test_teacache_extractors.py --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml" + + - label: "Simple · Engine&Entrypoints Test" + agent_pool: mi300_1 + depends_on: amd-build + mirror_hardwares: [amdproduction] + grade: Blocking + commands: + - export VLLM_ROCM_USE_AITER=0 + - "pytest -sv tests/entrypoints tests/engine -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml" - label: "Simple · Other Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -27,19 +48,19 @@ steps: - export VLLM_ROCM_USE_AITER=0 # ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM # that is not supported on CPU - - "timeout 40m pytest -sv tests/diffusion tests/model_executor -m 'core_model and cpu' --ignore=tests/diffusion/cache/test_teacache_extractors.py --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml" + - "pytest -sv tests/ -m 'core_model and cpu' --ignore=tests/diffusion --ignore=tests/model_executor --ignore=tests/entrypoints --ignore=tests/engine --ignore=tests/diffusion/cache/test_teacache_extractors.py --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml" - label: "Custom Pipeline Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking commands: - - timeout 20m pytest -s -v tests/e2e/features/custom_pipeline/ -m "core_model" + - pytest -s -v tests/e2e/features/custom_pipeline/ -m "core_model" - label: "Voxtral TTS CUDA Unit Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -49,33 +70,25 @@ steps: - group: ":card_index_dividers: Diffusion Test" steps: - label: "Diffusion · Batch Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking commands: - - timeout 20m pytest -sv tests/diffusion/batching -m 'core_model and cuda' --run-level "core_model" + - pytest -sv tests/diffusion/batching -m 'core_model and cuda' --run-level "core_model" - label: "Diffusion · Offloader Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking commands: - export VLLM_LOGGING_LEVEL=DEBUG - export VLLM_WORKER_MULTIPROC_METHOD=spawn - - | - timeout 20m bash -c ' - set +e - pytest -s -v tests/diffusion/offloader/test_diffusion_cpu_offload.py - EXIT1=\$? - pytest -s -v tests/diffusion/offloader/test_diffusion_layerwise_offload.py - EXIT2=\$? - exit \$((EXIT1 | EXIT2)) - ' + - pytest -s -v tests/diffusion/offloader -m "core_model and cuda" --run-level "core_model" - label: "Diffusion · Cache Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -83,10 +96,10 @@ steps: - export GPU_ARCHS=gfx942 - export VLLM_LOGGING_LEVEL=DEBUG - export VLLM_WORKER_MULTIPROC_METHOD=spawn - - timeout 15m pytest -s -v -m "core_model and cache and diffusion and not distributed_cuda and L4" + - pytest -sv tests/diffusion/cache -m 'core_model and cuda' --run-level "core_model" - label: "Diffusion Sequence Parallelism Test" - agent_pool: mi325_2 + agent_pool: mi300_2 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -96,7 +109,7 @@ steps: - timeout 20m pytest -s -v tests/diffusion/distributed/test_sequence_parallel.py -m core_model - label: "Diffusion GPU Worker Test" - agent_pool: mi325_2 + agent_pool: mi300_2 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -104,7 +117,7 @@ steps: - timeout 20m pytest -s -v tests/diffusion/test_diffusion_worker.py - label: "Diffusion · Model Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -112,7 +125,7 @@ steps: - timeout 15m pytest -sv tests/diffusion/models/ -m 'core_model and cuda' --run-level "core_model" - label: "Diffusion · Wan22 Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -126,7 +139,7 @@ steps: ## ISSUE depends on `diffusers` package: https://github.com/huggingface/diffusers/issues/13274 # - label: "Audio Generation Model Test" -# agent_pool: mi325_1 +# agent_pool: mi300_1 # depends_on: amd-build # mirror_hardwares: [amdproduction] # grade: Blocking @@ -137,7 +150,7 @@ steps: # - timeout 20m pytest -s -v tests/e2e/offline_inference/test_stable_audio_expansion.py -m "advanced_model and diffusion and L4" --run-level advanced_model - label: "Engine Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -149,7 +162,7 @@ steps: ' # - label: "Omni Model Test Qwen3-Omni" -# agent_pool: mi325_2 +# agent_pool: mi300_2 # depends_on: amd-build # mirror_hardwares: [amdproduction] # grade: Blocking @@ -162,7 +175,7 @@ steps: - group: ":card_index_dividers: E2E Test" steps: - label: "Qwen3-TTS E2E Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -173,7 +186,7 @@ steps: - timeout 30m pytest -s -v tests/e2e/online_serving/test_qwen3_tts_customvoice.py -m "core_model" --run-level "core_model" - label: "CosyVoice3-TTS E2E Test" - agent_pool: mi325_1 + agent_pool: mi300_1 depends_on: amd-build mirror_hardwares: [amdproduction] grade: Blocking @@ -186,7 +199,7 @@ steps: # TODO: Bagel test on ROCm is very unstable. @tjtanaa # Need to debug before reneable numerical changes across large PRs # - label: "Bagel Text2Img Model Test" -# agent_pool: mi325_1 +# agent_pool: mi300_1 # depends_on: amd-build # mirror_hardwares: [amdproduction] # grade: Blocking @@ -198,7 +211,7 @@ steps: # - timeout 30m pytest -s -v tests/e2e/offline_inference/test_bagel.py -m "core_model" --run-level "core_model" -k "rocm" # - label: "Bagel Img2Img Model Test" -# agent_pool: mi325_1 +# agent_pool: mi300_1 # depends_on: amd-build # mirror_hardwares: [amdproduction] # grade: Blocking @@ -210,7 +223,7 @@ steps: # - timeout 30m pytest -s -v tests/e2e/offline_inference/test_bagel.py -m "core_model" --run-level "core_model" -k "rocm" # - label: "Bagel Online Serving Test" -# agent_pool: mi325_1 +# agent_pool: mi300_1 # depends_on: amd-build # mirror_hardwares: [amdproduction] # grade: Blocking diff --git a/.buildkite/amd/test-template-amd-omni.j2 b/.buildkite/amd/test-template-amd-omni.j2 index 2f1a9aed3..e2042b96a 100644 --- a/.buildkite/amd/test-template-amd-omni.j2 +++ b/.buildkite/amd/test-template-amd-omni.j2 @@ -1,11 +1,65 @@ {# vllm-omni customized version Based on: https://github.com/vllm-project/ci-infra/blob/main/buildkite/test-template-amd.j2 - Last synced: 2025-12-15 - Modifications: Removed unused CUDA/NVIDIA logic, keeping only AMD tests + Last synced: 2026-08-04 + Modifications: Use the shared MI300 queues and Omni command-passing contract #} {% set docker_image_amd = "rocm/vllm-omni:$BUILDKITE_COMMIT" %} {% set default_working_dir = "/app/vllm-omni" %} +{# MI300 runs tests natively in Kubernetes; Docker-in-Docker is unavailable. #} +{% macro amd_native_k8s_plugin(indent, step) -%} +{{ indent }}plugins: +{{ indent }} - kubernetes: +{{ indent }} podSpecPatch: +{{ indent }} automountServiceAccountToken: false +{{ indent }} securityContext: +{{ indent }} seccompProfile: +{{ indent }} type: RuntimeDefault +{{ indent }} imagePullSecrets: +{{ indent }} - name: docker-config +{{ indent }} containers: +{{ indent }} - name: container-0 +{{ indent }} image: {{ docker_image_amd }} +{{ indent }} imagePullPolicy: Always +{{ indent }} securityContext: +{{ indent }} allowPrivilegeEscalation: false +{{ indent }} capabilities: +{{ indent }} add: +{{ indent }} - IPC_LOCK +{{ indent }} resources: +{{ indent }} limits: +{{ indent }} amd.com/gpu: "{{ step.agent_pool[-1:] }}" +{{ indent }} requests: +{{ indent }} amd.com/gpu: "{{ step.agent_pool[-1:] }}" +{{ indent }} volumeMounts: +{{ indent }} - name: devshm +{{ indent }} mountPath: /dev/shm +{{ indent }} env: +{{ indent }} - name: AMD_CI_RUNTIME +{{ indent }} value: native +{{ indent }} - name: NATIVE_CI +{{ indent }} value: "true" +{{ indent }} - name: VLLM_CI_DOCKER_DISABLED +{{ indent }} value: "1" +{{ indent }} - name: VLLM_CI_EXPECTED_GPU_COUNT +{{ indent }} value: "{{ step.agent_pool[-1:] }}" +{{ indent }} - name: HF_HOME +{{ indent }} value: /home/buildkite-agent/huggingface +{{ indent }} - name: MIOPEN_DEBUG_CONV_DIRECT +{{ indent }} value: "0" +{{ indent }} - name: MIOPEN_DEBUG_CONV_GEMM +{{ indent }} value: "0" +{{ indent }} - name: VLLM_ROCM_USE_AITER +{{ indent }} value: "0" +{{ indent }} - name: PYTORCH_ROCM_ARCH +{{ indent }} value: "" +{{ indent }} volumes: +{{ indent }} - name: devshm +{{ indent }} emptyDir: +{{ indent }} medium: Memory +{{ indent }} sizeLimit: 16Gi +{%- endmacro %} + {% if env %} env: {% for name in env %} @@ -53,19 +107,31 @@ steps: {% if step.agent_pool %} queue: amd_{{ step.agent_pool }} {% else %} - queue: amd_mi325_1 + queue: amd_mi300_1 {% endif %} +{{ amd_native_k8s_plugin(" ", step) }} {% set cmd_body = (step.command or (step.commands | join("\n"))) | trim %} {% set indented_cmd = cmd_body | replace("\n", "\n ") %} command: bash .buildkite/amd/scripts/run-amd-test.sh env: - DOCKER_BUILDKIT: "1" + AMD_CI_RUNTIME: "native" + NATIVE_CI: "true" + VLLM_CI_DOCKER_DISABLED: "1" + VLLM_CI_EXPECTED_GPU_COUNT: "{{ step.agent_pool[-1:] }}" + VLLM_CI_REQUIRE_PERSISTENT_HF_CACHE: "1" + HF_HOME: "/home/buildkite-agent/huggingface" + HF_HUB_DOWNLOAD_TIMEOUT: "300" + HF_HUB_ETAG_TIMEOUT: "60" + MIOPEN_DEBUG_CONV_DIRECT: "0" + MIOPEN_DEBUG_CONV_GEMM: "0" + VLLM_ROCM_USE_AITER: "0" + PYTORCH_ROCM_ARCH: "" {% if step.env %} {% for name in step.env %} {{ name }}: "{{ step.env[name] }}" {% endfor %} {% endif %} - TEST_COMMAND: |- + TEST_COMMANDS: |- (command rocm-smi || true) && cd {{ (step.working_dir or default_working_dir) | safe }} {% if env %} {% for name in env %} @@ -82,6 +148,15 @@ steps: {% endif %} {{ indented_cmd | safe }} priority: 100 +{% if step.parallelism %} + parallelism: {{ step.parallelism }} +{% endif %} +{% if step.artifact_paths %} + artifact_paths: +{% for path in step.artifact_paths %} + - "{{ path }}" +{% endfor %} +{% endif %} {% if step.timeout_in_minutes %} timeout_in_minutes: {{ step.timeout_in_minutes }} {% endif %} @@ -100,19 +175,31 @@ steps: {% if step.agent_pool %} queue: amd_{{ step.agent_pool }} {% else %} - queue: amd_mi325_1 + queue: amd_mi300_1 {% endif %} +{{ amd_native_k8s_plugin(" ", step) }} {% set cmd_body = (step.command or (step.commands | join("\n"))) | trim %} {% set indented_cmd = cmd_body | replace("\n", "\n ") %} command: bash .buildkite/amd/scripts/run-amd-test.sh env: - DOCKER_BUILDKIT: "1" + AMD_CI_RUNTIME: "native" + NATIVE_CI: "true" + VLLM_CI_DOCKER_DISABLED: "1" + VLLM_CI_EXPECTED_GPU_COUNT: "{{ step.agent_pool[-1:] }}" + VLLM_CI_REQUIRE_PERSISTENT_HF_CACHE: "1" + HF_HOME: "/home/buildkite-agent/huggingface" + HF_HUB_DOWNLOAD_TIMEOUT: "300" + HF_HUB_ETAG_TIMEOUT: "60" + MIOPEN_DEBUG_CONV_DIRECT: "0" + MIOPEN_DEBUG_CONV_GEMM: "0" + VLLM_ROCM_USE_AITER: "0" + PYTORCH_ROCM_ARCH: "" {% if step.env %} {% for name in step.env %} {{ name }}: "{{ step.env[name] }}" {% endfor %} {% endif %} - TEST_COMMAND: |- + TEST_COMMANDS: |- (command rocm-smi || true) && cd {{ (step.working_dir or default_working_dir) | safe }} {% if env %} {% for name in env %} @@ -129,6 +216,15 @@ steps: {% endif %} {{ indented_cmd | safe }} priority: 100 +{% if step.parallelism %} + parallelism: {{ step.parallelism }} +{% endif %} +{% if step.artifact_paths %} + artifact_paths: +{% for path in step.artifact_paths %} + - "{{ path }}" +{% endfor %} +{% endif %} {% if step.timeout_in_minutes %} timeout_in_minutes: {{ step.timeout_in_minutes }} {% endif %} diff --git a/docker/Dockerfile.rocm b/docker/Dockerfile.rocm index 6b5a12309..4283efc77 100644 --- a/docker/Dockerfile.rocm +++ b/docker/Dockerfile.rocm @@ -1,4 +1,4 @@ -ARG BASE_IMAGE=vllm/vllm-openai-rocm:v0.26.0 +ARG BASE_IMAGE=vllm/vllm-openai-rocm:v0.27.0 FROM ${BASE_IMAGE} AS base # Declare a variable to know if we want to use the nightly build or the stable build. @@ -10,7 +10,7 @@ FROM ${BASE_IMAGE} AS base # we should swap over to use stable release ASAP. # We should avoid relying on custom commits. ARG USE_NIGHTLY_BUILD=0 -ARG VLLM_VERSION_OR_COMMIT_HASH=v0.26.0 +ARG VLLM_VERSION_OR_COMMIT_HASH=v0.27.0 ARG ARG_PYTORCH_ROCM_ARCH ENV PYTORCH_ROCM_ARCH=${ARG_PYTORCH_ROCM_ARCH:-${PYTORCH_ROCM_ARCH}} @@ -79,9 +79,16 @@ RUN printf '%s\n' \ ' os._exit(_exit_code)' \ > ${COMMON_WORKDIR}/vllm-omni/conftest.py +# Some optional dependencies pull in the CPU ONNX Runtime distribution. The +# CPU and ROCm wheels own the same import path and cannot coexist, so remove +# the CPU wheel and reinstall the ROCm wheel after dependency resolution. RUN cd ${COMMON_WORKDIR}/vllm-omni && \ uv pip install --python "$(python3 -c 'import sys; print(sys.executable)')" \ --no-cache-dir ".[dev]" --no-build-isolation && \ + python3 -m pip uninstall -y onnxruntime && \ + python3 -m pip install --no-cache-dir --force-reinstall --no-deps \ + "onnxruntime-rocm>=1.22.2" && \ + python3 -c "import importlib.metadata as m; import onnxruntime as ort; assert 'ROCMExecutionProvider' in ort.get_available_providers(), ort.get_available_providers(); assert m.version('onnxruntime-rocm'); assert not any(d.metadata['Name'] == 'onnxruntime' for d in m.distributions())" && \ python3 -c "from torchcodec.decoders import VideoDecoder" RUN ln -sf /usr/bin/python3 /usr/bin/python diff --git a/pyproject.toml b/pyproject.toml index 48273fd20..160c6bc2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ dev = [ "pytest-asyncio==1.4.0", "pytest-cov==7.1.0", "pytest-mock==3.15.1", + "pytest-shard==0.1.2", "websockets==16.1", "pytest-subtests==0.15.0", "datasets==3.6.0", diff --git a/tests/diffusion/ar_diffusion/test_paged_attention.py b/tests/diffusion/ar_diffusion/test_paged_attention.py index 0b66c01a0..27135874f 100644 --- a/tests/diffusion/ar_diffusion/test_paged_attention.py +++ b/tests/diffusion/ar_diffusion/test_paged_attention.py @@ -60,9 +60,18 @@ def _dense_attention(query: torch.Tensor, key: torch.Tensor, value: torch.Tensor return torch.einsum("bhqk,bkhd->bqhd", probs, value) -def _cuda_flash_attn_usable() -> bool: +def _gpu_flash_attn_usable() -> bool: if not torch.cuda.is_available(): return False + if torch.version.hip is not None: + for module in ("aiter", "flash_attn"): + try: + imported = __import__(module, fromlist=["flash_attn_varlen_func"]) + if getattr(imported, "flash_attn_varlen_func", None) is not None: + return True + except ImportError: + pass + return False try: spec = find_spec("vllm.vllm_flash_attn") if spec is None or spec.origin is None: @@ -227,12 +236,11 @@ def test_paged_attention_matches_dense_reference_cpu(history_chunks, action_len, assert st.adapter(POS).completed_chunks == before + (1 if commit_current else 0) -@pytest.mark.skipif(not _cuda_flash_attn_usable(), reason="usable CUDA FlashAttention is required") +@pytest.mark.skipif(not _gpu_flash_attn_usable(), reason="usable GPU FlashAttention is required") @pytest.mark.parametrize("history_chunks", [1, 3]) @pytest.mark.parametrize("action_len", [0, 3]) @pytest.mark.parametrize("commit_current", [False, True]) def test_paged_attention_matches_dense_reference_gpu(history_chunks, action_len, commit_current): - pytest.importorskip("vllm.vllm_flash_attn") torch.manual_seed(0) device = torch.device("cuda") dtype = torch.float16 diff --git a/tests/diffusion/attention/test_flash_attn.py b/tests/diffusion/attention/test_flash_attn.py index e3f5355bf..5924beabf 100644 --- a/tests/diffusion/attention/test_flash_attn.py +++ b/tests/diffusion/attention/test_flash_attn.py @@ -265,10 +265,15 @@ def test_fa_vs_sdpa(): print(f"Max absolute difference (valid region): {max_diff:.6f}") print(f"Mean absolute difference (valid region): {mean_diff:.6f}") - # Assert that outputs are close - # Using higher tolerance for bfloat16 and different implementations - assert max_diff < 0.01, f"Max difference {max_diff} exceeds threshold 0.01" - assert mean_diff < 0.001, f"Mean difference {mean_diff} exceeds threshold 0.001" + # AITER's ROCm BF16 kernel is deterministic, but differs from ROCm SDPA by + # about 1.3% relative error. An absolute-only bound misclassifies that + # expected rounding difference for larger output values (for example, + # -2.65625 vs -2.6875). Keep the tighter historical bounds elsewhere. + if current_omni_platform.is_rocm(): + torch.testing.assert_close(output_fa_valid, output_sdpa_valid, rtol=1e-2, atol=1.5e-2) + else: + assert max_diff < 0.01, f"Max difference {max_diff} exceeds threshold 0.01" + assert mean_diff < 0.001, f"Mean difference {mean_diff} exceeds threshold 0.001" print("✓ Case 2 PASSED: FA and SDPA outputs are very close!") diff --git a/tests/diffusion/distributed/test_sequence_parallel.py b/tests/diffusion/distributed/test_sequence_parallel.py index d43d18c49..f083e1d26 100644 --- a/tests/diffusion/distributed/test_sequence_parallel.py +++ b/tests/diffusion/distributed/test_sequence_parallel.py @@ -416,8 +416,8 @@ def test_sp_correctness_advanced(model_name: str): @pytest.mark.skipif( - not (current_omni_platform.is_cuda() or current_omni_platform.is_xpu()), - reason="Only tested on CUDA and XPU", + not (current_omni_platform.is_cuda() or current_omni_platform.is_rocm() or current_omni_platform.is_xpu()), + reason="Only tested on CUDA, ROCM and XPU", ) @pytest.mark.diffusion @pytest.mark.parallel @@ -447,8 +447,8 @@ def test_local_sp_padding_mask(monkeypatch: pytest.MonkeyPatch) -> None: @pytest.mark.skipif( - not (current_omni_platform.is_cuda() or current_omni_platform.is_xpu()), - reason="Only tested on CUDA and XPU", + not (current_omni_platform.is_cuda() or current_omni_platform.is_rocm() or current_omni_platform.is_xpu()), + reason="Only tested on CUDA, ROCM and XPU", ) @pytest.mark.diffusion @pytest.mark.parallel @@ -468,8 +468,8 @@ def test_local_sp_padding_mask_no_padding(monkeypatch: pytest.MonkeyPatch) -> No @pytest.mark.skipif( - not (current_omni_platform.is_cuda() or current_omni_platform.is_xpu()), - reason="Only tested on CUDA and XPU", + not (current_omni_platform.is_cuda() or current_omni_platform.is_rocm() or current_omni_platform.is_xpu()), + reason="Only tested on CUDA, ROCM and XPU", ) @pytest.mark.diffusion @pytest.mark.parallel diff --git a/tests/diffusion/lora/test_images_generations_lora.py b/tests/diffusion/lora/test_images_generations_lora.py index dbdf59647..a01bcdae3 100644 --- a/tests/diffusion/lora/test_images_generations_lora.py +++ b/tests/diffusion/lora/test_images_generations_lora.py @@ -24,11 +24,20 @@ from safetensors.torch import save_file from tests.helpers.mark import hardware_test from tests.helpers.runtime import OmniServer, OmniServerParams +from vllm_omni.platforms import current_omni_platform os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn" MODEL = "Tongyi-MAI/Z-Image-Turbo" +# ROCm's default FLASH_ATTN diffusion backend resolves to AITER. On a cold CI +# worker, JIT-building its MHA kernel can take longer than the server readiness +# timeout. This test validates LoRA switching rather than attention-backend or +# compiled-execution performance, so use the eager SDPA path on ROCm only. +ROCM_SERVER_ARGS = ( + ["--enforce-eager", "--diffusion-attention-backend", "TORCH_SDPA"] if current_omni_platform.is_rocm() else [] +) + PROMPT = "a photo of a cat sitting on a laptop keyboard" SIZE = "256x256" @@ -42,7 +51,14 @@ server_params = [ server_args=[ "--num-gpus", "1", + *ROCM_SERVER_ARGS, ], + # A cold-cache Z-Image load can take slightly more than the + # orchestrator's 900s default on CI (weights alone have been + # observed at ~690s and full orchestrator readiness at ~906s). + # Match the large-model offline runner's startup allowance without + # weakening timeout detection for every online-serving test. + init_timeout=1800, ), id="zimage_turbo_lora", ), diff --git a/tests/engine/test_duplex_import_boundary.py b/tests/engine/test_duplex_import_boundary.py index 6eae2e698..d04ed2e70 100644 --- a/tests/engine/test_duplex_import_boundary.py +++ b/tests/engine/test_duplex_import_boundary.py @@ -3,6 +3,7 @@ from __future__ import annotations +import os import subprocess import sys from pathlib import Path @@ -14,9 +15,18 @@ REPO_ROOT = Path(__file__).resolve().parents[2] def _assert_isolated_import_succeeds(script: str) -> None: + # These tests validate Python import/package boundaries and do not use a + # GPU. Other tests in the combined entrypoints/engine suite may temporarily + # configure only one of the CUDA/ROCm visibility variables. Do not let an + # unrelated, inconsistent device-selection environment prevent the child + # interpreter from reaching the boundary assertion. + env = os.environ.copy() + env.pop("CUDA_VISIBLE_DEVICES", None) + env.pop("HIP_VISIBLE_DEVICES", None) result = subprocess.run( [sys.executable, "-c", script], cwd=REPO_ROOT, + env=env, capture_output=True, text=True, timeout=60, @@ -25,6 +35,14 @@ def _assert_isolated_import_succeeds(script: str) -> None: assert result.returncode == 0, result.stderr +def test_isolated_import_ignores_conflicting_gpu_visibility(monkeypatch: pytest.MonkeyPatch) -> None: + """Import-boundary checks must not depend on GPU state leaked by prior tests.""" + monkeypatch.setenv("HIP_VISIBLE_DEVICES", "0") + monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "0,1") + + _assert_isolated_import_succeeds("import vllm_omni.outputs") + + def test_stable_engine_imports_do_not_load_experimental_duplex() -> None: _assert_isolated_import_succeeds(""" import sys diff --git a/tests/entrypoints/openai_api/test_decode_audio_url.py b/tests/entrypoints/openai_api/test_decode_audio_url.py index 2a5c5be47..5dff3b052 100644 --- a/tests/entrypoints/openai_api/test_decode_audio_url.py +++ b/tests/entrypoints/openai_api/test_decode_audio_url.py @@ -5,6 +5,7 @@ import asyncio import base64 import os +import tempfile import httpx import pytest @@ -101,7 +102,7 @@ class TestDecodeAudioUrlSuffixSanitization: try: # ".." contains non-alnum dots, so falls back to .wav assert path.endswith(".wav") - assert os.path.dirname(path) == "/tmp" + assert os.path.dirname(path) == tempfile.gettempdir() finally: _cleanup(path) diff --git a/vllm_omni/experimental/ar_diffusion/kv_cache/paged_attention.py b/vllm_omni/experimental/ar_diffusion/kv_cache/paged_attention.py index bcd06b63a..f16ecc9e9 100644 --- a/vllm_omni/experimental/ar_diffusion/kv_cache/paged_attention.py +++ b/vllm_omni/experimental/ar_diffusion/kv_cache/paged_attention.py @@ -365,6 +365,22 @@ def _resolve_fa_version(head_size: int) -> int: return version +def _rocm_flash_attn_varlen_func(): + """Resolve a ROCm varlen kernel, preferring AITER when available. + + The caller gathers paged KV into packed tensors before invoking this + function, avoiding AITER releases whose ``block_table`` kernel is broken. + """ + try: + from aiter import flash_attn_varlen_func + + return flash_attn_varlen_func + except ImportError: + from flash_attn import flash_attn_varlen_func + + return flash_attn_varlen_func + + def ar_diffusion_paged_attention( query: torch.Tensor, key_cache: torch.Tensor, @@ -401,6 +417,34 @@ def ar_diffusion_paged_attention( softmax_scale, causal=causal, ) + elif torch.version.hip is not None: + # vllm.vllm_flash_attn contains CUDA-only extensions. ROCm's AITER and + # upstream flash-attn expose the standard cu_seqlens_k API instead of + # vLLM's seqused_k/fa_version API. The ROCm flash-attn paged kernel also + # requires 128-token blocks, while AR-Diffusion uses frame-aligned + # 16-token blocks, so gather the visible blocks on-device first. + flash_attn_varlen_func = _rocm_flash_attn_varlen_func() + cu_seqlens_k = torch.cat([seq_lens.new_zeros(1), torch.cumsum(seq_lens, dim=0, dtype=torch.int32)]) + positions = torch.arange(int(max_seq_len), device=query_flat.device) + logical_blocks = torch.div(positions, key_cache.shape[1], rounding_mode="floor") + offsets = positions % key_cache.shape[1] + physical_blocks = block_table[:, logical_blocks].long() + gathered_k = key_cache[physical_blocks, offsets] + gathered_v = value_cache[physical_blocks, offsets] + valid = positions.unsqueeze(0) < seq_lens.unsqueeze(1) + packed_k = gathered_k[valid] + packed_v = gathered_v[valid] + out = flash_attn_varlen_func( + q=query_flat, + k=packed_k, + v=packed_v, + cu_seqlens_q=query_start_loc, + cu_seqlens_k=cu_seqlens_k, + max_seqlen_q=int(max_query_len), + max_seqlen_k=int(max_seq_len), + softmax_scale=float(softmax_scale), + causal=causal, + ) else: from vllm.vllm_flash_attn import flash_attn_varlen_func