Compare commits

..
Author SHA1 Message Date
Hongsheng Liu b112c7fdee docs: restore BitsAndBytes navigation
Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com>
2026-08-12 00:10:38 +08:00
Hongsheng Liu e63ae881e7 docs: remove GGUF from active navigation
Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com>
2026-08-11 17:22:19 +08:00
Hongsheng Liu 2787f7f5f6 docs: clarify attention design coverage
Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com>
2026-08-11 17:07:49 +08:00
Hongsheng Liu 3cc05bf1c1 docs: address feature taxonomy review
Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com>
2026-08-11 16:55:39 +08:00
Hongsheng Liu 5a33440362 docs: align User Guide feature taxonomy
Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com>
2026-08-11 16:16:46 +08:00
2138 changed files with 59710 additions and 285882 deletions

No files matched your search

+65 -124
View File
@@ -1,7 +1,4 @@
#!/bin/bash #!/bin/bash
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
# vllm-omni customized version # vllm-omni customized version
# Based on: https://github.com/vllm-project/ci-infra/blob/main/buildkite/bootstrap-amd.sh # Based on: https://github.com/vllm-project/ci-infra/blob/main/buildkite/bootstrap-amd.sh
# Last synced: 2025-12-15 # Last synced: 2025-12-15
@@ -9,13 +6,8 @@
set -euo pipefail set -euo pipefail
# The bootstrap runs from the repository root; ShellCheck does not follow
# sourced files unless invoked with -x.
# shellcheck disable=SC1091
source .buildkite/common/scripts/resolve_skip_ci.sh source .buildkite/common/scripts/resolve_skip_ci.sh
AMD_SUITE_SELECTOR=".buildkite/amd/scripts/select_test_suites.py"
if [[ -z "${RUN_ALL:-}" ]]; then if [[ -z "${RUN_ALL:-}" ]]; then
RUN_ALL=0 RUN_ALL=0
fi fi
@@ -32,20 +24,31 @@ if [[ -z "${AMD_MIRROR_HW:-}" ]]; then
AMD_MIRROR_HW="amdproduction" AMD_MIRROR_HW="amdproduction"
fi fi
PR_LABELS=""
# Explicit debug suites do not depend on PR labels.
if [[ "${BUILDKITE_PULL_REQUEST:-false}" != "false" && -z "${DEBUG_TEST_YAML:-}" ]]; then
if ! PR_LABELS=$(curl -fsSL \
"https://api.github.com/repos/vllm-project/vllm-omni/pulls/$BUILDKITE_PULL_REQUEST" \
| jq -r '.labels[].name'); then
echo "ERROR: Could not read PR labels; refusing to select an incorrect AMD test tier." >&2
exit 1
fi
fi
fail_fast() { fail_fast() {
if [[ "${BUILDKITE_PULL_REQUEST:-false}" != "false" ]]; then DISABLE_LABEL="ci-no-fail-fast"
echo true # If BUILDKITE_PULL_REQUEST != "false", then we check the PR labels using curl and jq
if [ "$BUILDKITE_PULL_REQUEST" != "false" ]; then
PR_LABELS=$(curl -s "https://api.github.com/repos/vllm-project/vllm-omni/pulls/$BUILDKITE_PULL_REQUEST" | jq -r '.labels[].name')
if [[ $PR_LABELS == *"$DISABLE_LABEL"* ]]; then
echo false
else
echo true
fi
else
echo false # not a PR or BUILDKITE_PULL_REQUEST not set
fi
}
check_run_all_label() {
RUN_ALL_LABEL="ready-run-all-tests"
# If BUILDKITE_PULL_REQUEST != "false", then we check the PR labels using curl and jq
if [ "$BUILDKITE_PULL_REQUEST" != "false" ]; then
PR_LABELS=$(curl -s "https://api.github.com/repos/vllm-project/vllm-omni/pulls/$BUILDKITE_PULL_REQUEST" | jq -r '.labels[].name')
if [[ $PR_LABELS == *"$RUN_ALL_LABEL"* ]]; then
echo true
else
echo false
fi
else else
echo false # not a PR or BUILDKITE_PULL_REQUEST not set echo false # not a PR or BUILDKITE_PULL_REQUEST not set
fi fi
@@ -55,64 +58,11 @@ if [[ -z "${COV_ENABLED:-}" ]]; then
COV_ENABLED=0 COV_ENABLED=0
fi fi
resolve_test_specs() {
local selected_specs
if ! selected_specs=$(python3 "$AMD_SUITE_SELECTOR" \
--branch "$BUILDKITE_BRANCH" \
--labels "$PR_LABELS" \
--debug-test-yaml "${DEBUG_TEST_YAML:-}"); then
exit 1
fi
TEST_SPECS=()
while IFS= read -r suite_spec; do
[[ -z "$suite_spec" ]] && continue
TEST_SPECS+=("$suite_spec")
done <<< "$selected_specs"
}
filter_test_specs_by_skip_ci() {
local suite_spec level decision
local skip_all=0
local -a runnable_specs=()
for suite_spec in "${TEST_SPECS[@]}"; do
case "$suite_spec" in
READY_TESTS:*) level="l2" ;;
MERGE_TESTS:*) level="l3" ;;
*)
echo "ERROR: unknown AMD test suite spec '$suite_spec'" >&2
exit 1
;;
esac
if decision=$(python3 "$SKIP_CI_PY" gate amd "$level"); then
echo "Skipping AMD $level suite due to $decision decision."
[[ "$decision" == "skip-all" ]] && skip_all=1
else
runnable_specs+=("$suite_spec")
fi
done
TEST_SPECS=("${runnable_specs[@]}")
if [[ ${#TEST_SPECS[@]} -eq 0 ]]; then
if [[ $skip_all -eq 1 ]]; then
buildkite-agent annotate \
":memo: CI skipped — docs or pytest skip-mark changes only" \
--style "info" 2>/dev/null || true
fi
echo "No AMD L2/L3 suites remain after skip-ci filtering."
exit 0
fi
}
upload_pipeline() { upload_pipeline() {
echo "Uploading pipeline..." echo "Uploading pipeline..."
# Install minijinja # Install minijinja
ls .buildkite || buildkite-agent annotate --style error 'Please merge upstream main branch for buildkite CI' ls .buildkite || buildkite-agent annotate --style error 'Please merge upstream main branch for buildkite CI'
curl -sSfL https://github.com/mitsuhiko/minijinja/releases/download/2.3.1/minijinja-cli-installer.sh | sh curl -sSfL https://github.com/mitsuhiko/minijinja/releases/download/2.3.1/minijinja-cli-installer.sh | sh
# Installed by the minijinja bootstrap above and only present on the CI agent.
# shellcheck disable=SC1091
source /var/lib/buildkite-agent/.cargo/env source /var/lib/buildkite-agent/.cargo/env
if [[ $BUILDKITE_PIPELINE_SLUG == "fastcheck" ]]; then if [[ $BUILDKITE_PIPELINE_SLUG == "fastcheck" ]]; then
@@ -126,7 +76,7 @@ upload_pipeline() {
# (WIP) Use pipeline generator instead of jinja template # (WIP) Use pipeline generator instead of jinja template
if [ -e ".buildkite/amd/pipeline_generator/pipeline_generator.py" ]; then if [ -e ".buildkite/amd/pipeline_generator/pipeline_generator.py" ]; then
python -m pip install click pydantic python -m pip install click pydantic
python .buildkite/amd/pipeline_generator/pipeline_generator.py --run_all="$RUN_ALL" --list_file_diff="$LIST_FILE_DIFF" --nightly="$NIGHTLY" --mirror_hw="$AMD_MIRROR_HW" python .buildkite/amd/pipeline_generator/pipeline_generator.py --run_all=$RUN_ALL --list_file_diff="$LIST_FILE_DIFF" --nightly="$NIGHTLY" --mirror_hw="$AMD_MIRROR_HW"
buildkite-agent pipeline upload .buildkite/amd/pipeline.yaml buildkite-agent pipeline upload .buildkite/amd/pipeline.yaml
exit 0 exit 0
fi fi
@@ -139,46 +89,28 @@ upload_pipeline() {
cd .buildkite/amd cd .buildkite/amd
# Multiple label-selected or debug-selected suites share one image build. # Select test definition file: merge suite for main, ready suite for PRs.
if [[ ${#TEST_SPECS[@]} -eq 1 ]]; then # For debugging, DEBUG_TEST_YAML can override the selection — accepts
TEST_YAML="${TEST_SPECS[0]#*:}" # "merge" or "ready" (case-insensitive).
if [[ -n "${DEBUG_TEST_YAML:-}" ]]; then
case "${DEBUG_TEST_YAML,,}" in
merge)
TEST_YAML="test-amd-merge.yml"
;;
ready)
TEST_YAML="test-amd-ready.yml"
;;
*)
echo "ERROR: DEBUG_TEST_YAML must be 'merge' or 'ready', got '$DEBUG_TEST_YAML'" >&2
exit 1
;;
esac
echo "DEBUG_TEST_YAML override: using $TEST_YAML"
elif [[ $BUILDKITE_BRANCH == "main" ]]; then
TEST_YAML="test-amd-merge.yml"
else else
TEST_YAML=$(mktemp "${TMPDIR:-/tmp}/amd-selected-tests.XXXXXX.yml") TEST_YAML="test-amd-ready.yml"
python - "$TEST_YAML" "${TEST_SPECS[@]}" <<'PY'
import sys
import yaml
output_path, *suite_specs = sys.argv[1:]
combined = {"env": {}, "steps": []}
for suite_spec in suite_specs:
group_name, input_path = suite_spec.split(":", 1)
with open(input_path, encoding="utf-8") as test_file:
suite = yaml.safe_load(test_file)
for name, value in (suite.get("env") or {}).items():
previous = combined["env"].get(name, value)
if previous != value:
raise ValueError(
f"Conflicting environment value for {name}: {previous!r} != {value!r}"
)
combined["env"][name] = value
suite_steps = []
for entry in suite.get("steps") or []:
if "group" in entry:
suite_steps.extend(entry.get("steps") or [])
else:
suite_steps.append(entry)
combined["steps"].append({"group": group_name, "steps": suite_steps})
with open(output_path, "w", encoding="utf-8") as output_file:
yaml.safe_dump(combined, output_file, sort_keys=False)
PY
fi fi
echo "AMD test suites: ${TEST_SPECS[*]}"
( (
set -x set -x
@@ -198,20 +130,19 @@ PY
> pipeline.yaml > pipeline.yaml
) )
cat pipeline.yaml cat pipeline.yaml
if [[ "$TEST_YAML" == "${TMPDIR:-/tmp}/amd-selected-tests."*.yml ]]; then
rm -f -- "$TEST_YAML"
fi
buildkite-agent artifact upload pipeline.yaml buildkite-agent artifact upload pipeline.yaml
buildkite-agent pipeline upload pipeline.yaml buildkite-agent pipeline upload pipeline.yaml
exit 0 exit 0
} }
get_diff() { get_diff() {
git diff --name-only --diff-filter=ACMDR "$(git merge-base origin/main HEAD)" $(git add .)
echo $(git diff --name-only --diff-filter=ACMDR $(git merge-base origin/main HEAD))
} }
get_diff_main() { get_diff_main() {
git diff --name-only --diff-filter=ACMDR HEAD~1 $(git add .)
echo $(git diff --name-only --diff-filter=ACMDR HEAD~1)
} }
file_diff=$(get_diff) file_diff=$(get_diff)
@@ -219,10 +150,12 @@ if [[ $BUILDKITE_BRANCH == "main" ]]; then
file_diff=$(get_diff_main) file_diff=$(get_diff_main)
fi fi
# Resolve PR tier labels before skip-ci so ready and merge suites can be # Early exit: unified skip-ci (docs / skip marks) and CI-yaml-only level targeting.
# filtered independently for CI-YAML-only changes. if [[ $BUILDKITE_BRANCH == "main" ]]; then
resolve_test_specs gate_bootstrap_ci amd l3
filter_test_specs_by_skip_ci else
gate_bootstrap_ci amd l2
fi
patterns=( patterns=(
"docker/Dockerfile" "docker/Dockerfile"
@@ -248,7 +181,7 @@ for file in $file_diff; do
# First check if file matches any pattern # First check if file matches any pattern
matches_pattern=0 matches_pattern=0
for pattern in "${patterns[@]}"; do for pattern in "${patterns[@]}"; do
if [[ $file == "$pattern"* ]] || [[ $file == "$pattern" ]]; then if [[ $file == $pattern* ]] || [[ $file == $pattern ]]; then
matches_pattern=1 matches_pattern=1
break break
fi fi
@@ -258,7 +191,7 @@ for file in $file_diff; do
if [[ $matches_pattern -eq 1 ]]; then if [[ $matches_pattern -eq 1 ]]; then
matches_ignore=0 matches_ignore=0
for ignore in "${ignore_patterns[@]}"; do for ignore in "${ignore_patterns[@]}"; do
if [[ $file == "$ignore"* ]] || [[ $file == "$ignore" ]]; then if [[ $file == $ignore* ]] || [[ $file == $ignore ]]; then
matches_ignore=1 matches_ignore=1
break break
fi fi
@@ -272,6 +205,14 @@ for file in $file_diff; do
fi fi
done done
# Check for ready-run-all-tests label
LABEL_RUN_ALL=$(check_run_all_label)
if [[ $LABEL_RUN_ALL == true ]]; then
RUN_ALL=1
NIGHTLY=1
echo "Found 'ready-run-all-tests' label. Running all tests including optional tests."
fi
# Decide whether to use precompiled wheels # Decide whether to use precompiled wheels
# Relies on existing patterns array as a basis. # Relies on existing patterns array as a basis.
if [[ -n "${VLLM_USE_PRECOMPILED:-}" ]]; then if [[ -n "${VLLM_USE_PRECOMPILED:-}" ]]; then
+140 -90
View File
@@ -1,103 +1,153 @@
#!/bin/bash #!/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
# Run vLLM-Omni ROCm tests directly in the MI300 Kubernetes pod. The pod's # This script runs test inside the corresponding ROCm docker container.
# container image is selected by test-template-amd-omni.j2; MI300 has no DinD. set -o pipefail
set -euo pipefail
: "${PYTHONFAULTHANDLER:=1}" # Export Python path
: "${HF_HOME:=/home/buildkite-agent/huggingface}" export PYTHONPATH=".."
: "${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 rocminfo
expected_gpus="${VLLM_CI_EXPECTED_GPU_COUNT:-1}" # cleanup older docker images
python3 - <<'PY' cleanup_docker() {
import os # 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
}
import torch # Call the cleanup docker function
cleanup_docker
expected = int(os.environ.get("VLLM_CI_EXPECTED_GPU_COUNT", "1")) echo "--- Pulling container"
assert torch.version.hip, "PyTorch is not a ROCm build" ## Temporary change to use AMD Docker Hub to store the vllm-omni image
assert torch.cuda.is_available(), "ROCm GPU is not available to PyTorch" # to bypass the rate limit issue with ECR Public Gallery.
actual = torch.cuda.device_count() # Images are now stored in a separate repository for vllm-omni, instead of vllm-ci.
assert actual == expected, f"Expected {expected} ROCm GPU(s), found {actual}" # TODO: @tjtanaa point back to ECR Public Gallery
PY # 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)"
echo "Commands:${commands}" # TODO: @tjtanaa uncomment this once the amd agents are configured to use ECR Public Gallery.
if /bin/bash -o pipefail -c ' # # Install AWS CLI to authenticate to ECR Public Gallery to get higher rate limit for pulling images
set -E # sudo apt-get update && sudo apt-get install -y awscli
test_status=0 ## Use safe docker login helper to prevent race conditions
trap '\'' # source "$(dirname "${BASH_SOURCE[0]}")/../../common/scripts/docker_login_ecr_public.sh"
command_status=$? # safe_docker_login_ecr_public
# Keep running subsequent commands, but retain a failure for the final exit.
# Prefer a test failure over pytest "no tests collected" when both occur. ## Pull the container from AMD Docker Hub
if (( test_status == 0 || (test_status == 5 && command_status != 5) )); then
test_status=${command_status} docker pull "${image_name}"
fi
'\'' ERR remove_docker_container() {
eval "$1" docker ps -aq --filter "name=${container_name}" | xargs -r docker rm -f || true
exit "${test_status}" docker image rm -f "${image_name}" || true
' _ "${commands}"; then }
exit 0 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 else
exit_code=$? 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 fi
if [[ ${exit_code} -eq 5 && "${VLLM_CI_ALLOW_NO_TESTS:-0}" == "1" ]]; then # check if the command contains shard flag, we will run all shards in parallel because the host have 8 GPUs.
echo "Pytest collected no tests; VLLM_CI_ALLOW_NO_TESTS=1, treating exit code 5 as success." # TODO: @tjtanaa reenable to run VLLM_ROCM_USE_AITER=1 when AITER is shipped with prebuilt kernels.
exit 0 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}"
fi
done
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}"
fi fi
exit "${exit_code}"
@@ -1,93 +0,0 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Select AMD L2/L3 suites after Buildkite has created an AMD build.
This repository-side selector does not decide which GitHub label events start
the external ``vllm-omni-amd-ci`` pipeline. Its Buildkite PR build condition
must admit the relevant label event before this code can inspect the labels.
"""
from __future__ import annotations
import argparse
import sys
from collections.abc import Iterable
SUITE_SPECS = {
"ready": "READY_TESTS:test-amd-ready.yml",
"merge": "MERGE_TESTS:test-amd-merge.yml",
}
def _parse_debug_suites(value: str) -> tuple[str, ...]:
suites = tuple(item.strip().lower() for item in value.split(",") if item.strip())
if not suites:
raise ValueError("DEBUG_TEST_YAML did not contain a test suite")
seen: set[str] = set()
for suite in suites:
if suite not in SUITE_SPECS:
raise ValueError(f"DEBUG_TEST_YAML entries must be 'merge' or 'ready', got {suite!r}")
if suite in seen:
raise ValueError(f"duplicate DEBUG_TEST_YAML suite {suite!r}")
seen.add(suite)
return suites
def select_amd_test_suites(
*,
branch: str,
labels: Iterable[str],
debug_test_yaml: str = "",
) -> tuple[str, ...]:
"""Return ordered suite names while preserving legacy unlabeled PRs."""
if debug_test_yaml:
return _parse_debug_suites(debug_test_yaml)
if branch == "main":
return ("merge",)
label_set = {label.strip() for label in labels if label.strip()}
selected = tuple(suite for label, suite in (("ready", "ready"), ("merge-test", "merge")) if label in label_set)
# AMD historically uploaded L2 for every PR build. Keep that behavior for
# triggers such as amd-test while adding tier selection for ready/merge-test.
return selected or ("ready",)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--branch", required=True)
parser.add_argument("--labels", default="")
parser.add_argument("--debug-test-yaml", default="")
args = parser.parse_args()
labels = tuple(args.labels.splitlines())
try:
suites = select_amd_test_suites(
branch=args.branch,
labels=labels,
debug_test_yaml=args.debug_test_yaml,
)
except ValueError as exc:
parser.error(str(exc))
label_set = {label.strip() for label in labels if label.strip()}
if not args.debug_test_yaml and args.branch != "main" and not ({"ready", "merge-test"} & label_set):
print(
"No AMD L2/L3 tier label found; preserving the legacy ready-suite fallback.",
file=sys.stderr,
)
if "nightly-test" in label_set:
print(
"AMD nightly-test has no suite yet and does not affect L2/L3 selection.",
file=sys.stderr,
)
for suite in suites:
print(SUITE_SPECS[suite])
return 0
if __name__ == "__main__":
raise SystemExit(main())
+53 -104
View File
@@ -7,113 +7,84 @@ steps:
- group: ":card_index_dividers: Simple Test" - group: ":card_index_dividers: Simple Test"
steps: steps:
- label: "Simple · Model Executor Test · Shard %N/%t" - label: "Simple · Diffusion & Model Executor Test (1/2)"
agent_pool: mi300_1 agent_pool: mi325_1
parallelism: 2
depends_on: amd-build depends_on: amd-build
timeout_in_minutes: 70
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
commands: commands:
- export VLLM_ROCM_USE_AITER=0 - export VLLM_ROCM_USE_AITER=0
# ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM # ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM
# that is not supported on CPU # that is not supported on CPU
- "pytest -sv tests/model_executor -m 'core_model and cpu' --num-shards=$$BUILDKITE_PARALLEL_JOB_COUNT --shard-id=$$BUILDKITE_PARALLEL_JOB" # 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"
- label: "Simple · Diffusion Test · Shard %N/%t" - label: "Simple · Diffusion & Model Executor Test (2/2)"
agent_pool: mi300_1 agent_pool: mi325_1
parallelism: 4
depends_on: amd-build depends_on: amd-build
timeout_in_minutes: 70
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
commands: commands:
- export VLLM_ROCM_USE_AITER=0 - export VLLM_ROCM_USE_AITER=0
# ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM # CPU tests on ROCm gpu machines are very slow, so we give them more time.
# that is not supported on CPU - "timeout 60m pytest -sv tests/model_executor -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
- "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"
- 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'"
- label: "Simple · Other Test" - label: "Simple · Other Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
timeout_in_minutes: 50
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
commands: commands:
- export VLLM_ROCM_USE_AITER=0 - export VLLM_ROCM_USE_AITER=0
# ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM # ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM
# that is not supported on CPU # that is not supported on CPU
- "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" - "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"
- group: ":card_index_dividers: Diffusion Test" - group: ":card_index_dividers: Diffusion Test"
steps: steps:
- label: "Diffusion · LoRA Test" - label: "Diffusion · LoRA Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
artifact_paths:
- lora-server.log
commands: commands:
- pytest -sv tests/diffusion/lora/ -m 'advanced_model and cuda' --run-level "advanced_model" 2>&1 | tee lora-server.log - timeout 20m pytest -s -v tests/diffusion/lora/test_images_generations_lora.py
- label: "Diffusion Model CPU offloading Test" - label: "Diffusion Model CPU offloading Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
commands: commands:
- export VLLM_LOGGING_LEVEL=DEBUG - export VLLM_LOGGING_LEVEL=DEBUG
- export VLLM_WORKER_MULTIPROC_METHOD=spawn - export VLLM_WORKER_MULTIPROC_METHOD=spawn
- pytest -s -v tests/diffusion/offloader/test_diffusion_cpu_offload.py - |
- >- timeout 20m bash -c '
pytest -s -v tests/diffusion/offloader/test_diffusion_layerwise_offload.py set +e
--deselect='tests/diffusion/offloader/test_diffusion_layerwise_offload.py::test_layerwise_offload_diffusion_model[stabilityai/stable-audio-open-1.0]' 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))
'
# TODO: Diagnose unstable Stable Audio layerwise-offload memory savings on ROCm. - label: "Diffusion Sequence Parallelism Test (Need 4 GPUs)"
- label: "Diffusion Stable Audio Layerwise Offload Test (quarantined)" agent_pool: mi325_4
agent_pool: mi300_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: NonBlocking
commands:
- export VLLM_LOGGING_LEVEL=DEBUG
- export VLLM_WORKER_MULTIPROC_METHOD=spawn
- >-
pytest -s -v
'tests/diffusion/offloader/test_diffusion_layerwise_offload.py::test_layerwise_offload_diffusion_model[stabilityai/stable-audio-open-1.0]'
- label: "Diffusion Sequence Parallelism Test (Need 4 GPUs) · Shard 1/2"
agent_pool: mi300_4
depends_on: amd-build
mirror_hardwares: [amdproduction]
timeout_in_minutes: 40
grade: Blocking grade: Blocking
commands: commands:
- export VLLM_LOGGING_LEVEL=DEBUG - export VLLM_LOGGING_LEVEL=DEBUG
- export VLLM_WORKER_MULTIPROC_METHOD=spawn - export VLLM_WORKER_MULTIPROC_METHOD=spawn
- pytest -s -v tests/diffusion/distributed/test_sequence_parallel.py - timeout 20m pytest -s -v tests/diffusion/distributed/test_sequence_parallel.py
- timeout 20m pytest -s -v tests/diffusion/distributed/test_ulysses_uaa_perf.py
- label: "Diffusion Sequence Parallelism Test (Need 4 GPUs) · Shard 2/2"
agent_pool: mi300_4
depends_on: amd-build
mirror_hardwares: [amdproduction]
timeout_in_minutes: 40
grade: Blocking
commands:
- export VLLM_LOGGING_LEVEL=DEBUG
- export VLLM_WORKER_MULTIPROC_METHOD=spawn
- pytest -s -v tests/diffusion/distributed/test_ulysses_uaa_perf.py
# merge-only tests # merge-only tests
- label: "Diffusion Tensor Parallelism Test" - label: "Diffusion Tensor Parallelism Test"
agent_pool: mi300_2 agent_pool: mi325_2
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -121,7 +92,7 @@ steps:
- pytest -s -v tests/diffusion/distributed/test_tensor_parallel.py - pytest -s -v tests/diffusion/distributed/test_tensor_parallel.py
- label: "Diffusion Model Test" - label: "Diffusion Model Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -129,7 +100,7 @@ steps:
- timeout 30m pytest -s -v tests/e2e/offline_inference/test_z_image.py - timeout 30m pytest -s -v tests/e2e/offline_inference/test_z_image.py
- label: "Diffusion GPU Worker Test" - label: "Diffusion GPU Worker Test"
agent_pool: mi300_2 agent_pool: mi325_2
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -137,7 +108,7 @@ steps:
- timeout 20m pytest -s -v tests/diffusion/test_diffusion_worker.py - timeout 20m pytest -s -v tests/diffusion/test_diffusion_worker.py
- label: "Diffusion · Wan22 Test" - label: "Diffusion · Wan22 Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -150,7 +121,7 @@ steps:
## ISSUE depends on `diffusers` package: https://github.com/huggingface/diffusers/issues/13274 ## ISSUE depends on `diffusers` package: https://github.com/huggingface/diffusers/issues/13274
# - label: "Audio Generation Model Test" # - label: "Audio Generation Model Test"
# agent_pool: mi300_1 # agent_pool: mi325_1
# depends_on: amd-build # depends_on: amd-build
# mirror_hardwares: [amdproduction] # mirror_hardwares: [amdproduction]
# grade: Blocking # grade: Blocking
@@ -161,7 +132,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 # - 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" - label: "Engine Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -176,7 +147,7 @@ steps:
- group: ":card_index_dividers: E2E Test" - group: ":card_index_dividers: E2E Test"
steps: steps:
- label: "Omni Model Test Qwen2-5-Omni" - label: "Omni Model Test Qwen2-5-Omni"
agent_pool: mi300_4 agent_pool: mi325_4
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
commands: commands:
@@ -184,70 +155,48 @@ steps:
- pytest -s -v tests/e2e/*/test_qwen2_5_omni_expansion.py --run-level "core_model" - pytest -s -v tests/e2e/*/test_qwen2_5_omni_expansion.py --run-level "core_model"
- label: "Omni Model Test Qwen3-Omni" - label: "Omni Model Test Qwen3-Omni"
agent_pool: mi300_2 agent_pool: mi325_2
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
commands: commands:
- export VLLM_WORKER_MULTIPROC_METHOD=spawn - 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 tests/e2e/offline_inference/test_qwen3_omni_colocate_async.py -m 'advanced_model and cuda' --run-level 'advanced_model' - 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 · Shard %N/%t" - label: "Qwen3-TTS CustomVoice E2E Test"
agent_pool: mi300_1 agent_pool: mi325_1
parallelism: 3
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
timeout_in_minutes: 80
commands: commands:
- | - |
timeout --signal=TERM --kill-after=2m 80m bash -c ' timeout 50m bash -c '
export VLLM_LOGGING_LEVEL=DEBUG export VLLM_LOGGING_LEVEL=DEBUG
export VLLM_WORKER_MULTIPROC_METHOD=spawn export VLLM_WORKER_MULTIPROC_METHOD=spawn
export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1" export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
TESTS=( 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"
"tests/e2e/online_serving/test_qwen3_tts_customvoice.py::test_default_cuda_graph_startup[async_chunk]"
"tests/e2e/online_serving/test_qwen3_tts_customvoice.py::test_text_to_audio_002[async_chunk]"
"tests/e2e/offline_inference/test_qwen3_tts_customvoice.py::test_text_to_audio_001[no_cuda_graph]"
)
TEST="$${TESTS[$$BUILDKITE_PARALLEL_JOB]}"
pytest -s -v "$$TEST" -m "advanced_model" --run-level "advanced_model"
'
# TODO: Restore this case to the blocking sharded step once its long-running
# Whisper validation on AMD CI is resolved.
- label: "Qwen3-TTS CustomVoice async_chunk Test (quarantined)"
agent_pool: mi300_1
depends_on: amd-build
mirror_hardwares: [amdproduction]
grade: NonBlocking
timeout_in_minutes: 80
commands:
- |
timeout --signal=TERM --kill-after=2m 75m 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::test_text_to_audio_001[async_chunk]" -m "advanced_model" --run-level "advanced_model"
' '
- label: "Qwen3-TTS Base E2E Test" - label: "Qwen3-TTS Base E2E Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
timeout_in_minutes: 50 timeout_in_minutes: 50
commands: commands:
- export VLLM_LOGGING_LEVEL=DEBUG - |
- export VLLM_WORKER_MULTIPROC_METHOD=spawn timeout 40m bash -c '
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1" export VLLM_LOGGING_LEVEL=DEBUG
- timeout 40m pytest -s -v tests/e2e/online_serving/test_qwen3_tts_base.py tests/e2e/offline_inference/test_qwen3_tts_base.py -m "advanced_model and cuda" --run-level "advanced_model" export VLLM_WORKER_MULTIPROC_METHOD=spawn
export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
pytest -s -v tests/e2e/online_serving/test_qwen3_tts_base.py tests/e2e/offline_inference/test_qwen3_tts_base.py -m 'advanced_model and cuda' --run-level 'advanced_model'
'
# TODO: Bagel test on ROCm is very unstable. @tjtanaa # TODO: Bagel test on ROCm is very unstable. @tjtanaa
# Need to debug before reneable numerical changes across large PRs # Need to debug before reneable numerical changes across large PRs
# # split Bagel Model Test with H100 (Real Weights) into three tests # # split Bagel Model Test with H100 (Real Weights) into three tests
# - label: "Bagel Text2Img Model Test (1/3)" # - label: "Bagel Text2Img Model Test (1/3)"
# agent_pool: mi300_1 # agent_pool: mi325_1
# depends_on: amd-build # depends_on: amd-build
# mirror_hardwares: [amdproduction] # mirror_hardwares: [amdproduction]
# grade: Blocking # grade: Blocking
@@ -259,7 +208,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" # - 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)" # - label: "Bagel Img2Img Model Test (2/3)"
# agent_pool: mi300_1 # agent_pool: mi325_1
# depends_on: amd-build # depends_on: amd-build
# mirror_hardwares: [amdproduction] # mirror_hardwares: [amdproduction]
# grade: Blocking # grade: Blocking
@@ -271,7 +220,7 @@ steps:
# - timeout 30m pytest -s -v tests/e2e/offline_inference/test_bagel.py -m "advanced_model" --run-level "advanced_model" -k "rocm" # - 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)" # - label: "Bagel Online Serving Test (3/3)"
# agent_pool: mi300_1 # agent_pool: mi325_1
# depends_on: amd-build # depends_on: amd-build
# mirror_hardwares: [amdproduction] # mirror_hardwares: [amdproduction]
# grade: Blocking # grade: Blocking
+39 -60
View File
@@ -7,9 +7,8 @@ steps:
- group: ":card_index_dividers: Simple Test" - group: ":card_index_dividers: Simple Test"
steps: steps:
- label: "Simple · Model Executor Test · Shard %N/%t" - label: "Simple · Diffusion & Model Executor Test"
agent_pool: mi300_1 agent_pool: mi325_1
parallelism: 2
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -17,30 +16,10 @@ steps:
- export VLLM_ROCM_USE_AITER=0 - export VLLM_ROCM_USE_AITER=0
# ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM # ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM
# that is not supported on CPU # that is not supported on CPU
- "pytest -sv tests/model_executor -m 'core_model and cpu' --num-shards=$$BUILDKITE_PARALLEL_JOB_COUNT --shard-id=$$BUILDKITE_PARALLEL_JOB" - "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"
- 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"
- 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'"
- label: "Simple · Other Test" - label: "Simple · Other Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -48,19 +27,19 @@ steps:
- export VLLM_ROCM_USE_AITER=0 - export VLLM_ROCM_USE_AITER=0
# ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM # ignore test_teacache_extractors.py because it use rocm gemm kernel from vLLM
# that is not supported on CPU # that is not supported on CPU
- "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" - "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"
- label: "Custom Pipeline Test" - label: "Custom Pipeline Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
commands: commands:
- pytest -s -v tests/e2e/features/custom_pipeline/ -m "core_model" - timeout 20m pytest -s -v tests/e2e/features/custom_pipeline/ -m "core_model"
- label: "Voxtral TTS CUDA Unit Test" - label: "Voxtral TTS CUDA Unit Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -70,25 +49,33 @@ steps:
- group: ":card_index_dividers: Diffusion Test" - group: ":card_index_dividers: Diffusion Test"
steps: steps:
- label: "Diffusion · Batch Test" - label: "Diffusion · Batch Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
commands: commands:
- pytest -sv tests/diffusion/batching -m 'core_model and cuda' --run-level "core_model" - timeout 20m pytest -sv tests/diffusion/batching -m 'core_model and cuda' --run-level "core_model"
- label: "Diffusion · Offloader Test" - label: "Diffusion · Offloader Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
commands: commands:
- export VLLM_LOGGING_LEVEL=DEBUG - export VLLM_LOGGING_LEVEL=DEBUG
- export VLLM_WORKER_MULTIPROC_METHOD=spawn - export VLLM_WORKER_MULTIPROC_METHOD=spawn
- pytest -s -v tests/diffusion/offloader -m "core_model and cuda" --run-level "core_model" - |
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))
'
- label: "Diffusion · Cache Test" - label: "Diffusion · Cache Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -96,10 +83,10 @@ steps:
- export GPU_ARCHS=gfx942 - export GPU_ARCHS=gfx942
- export VLLM_LOGGING_LEVEL=DEBUG - export VLLM_LOGGING_LEVEL=DEBUG
- export VLLM_WORKER_MULTIPROC_METHOD=spawn - export VLLM_WORKER_MULTIPROC_METHOD=spawn
- pytest -sv tests/diffusion/cache -m 'core_model and cuda' --run-level "core_model" - timeout 15m pytest -s -v -m "core_model and cache and diffusion and not distributed_cuda and L4"
- label: "Diffusion Sequence Parallelism Test" - label: "Diffusion Sequence Parallelism Test"
agent_pool: mi300_2 agent_pool: mi325_2
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -107,14 +94,9 @@ steps:
- export VLLM_LOGGING_LEVEL=DEBUG - export VLLM_LOGGING_LEVEL=DEBUG
- export VLLM_WORKER_MULTIPROC_METHOD=spawn - export VLLM_WORKER_MULTIPROC_METHOD=spawn
- timeout 20m pytest -s -v tests/diffusion/distributed/test_sequence_parallel.py -m core_model - timeout 20m pytest -s -v tests/diffusion/distributed/test_sequence_parallel.py -m core_model
- >-
timeout 20m pytest -s -v
tests/diffusion/models/ltx2/test_ltx2_transformer_ulysses.py
-m 'core_model and rocm and cards_2'
--run-level "core_model"
- label: "Diffusion GPU Worker Test" - label: "Diffusion GPU Worker Test"
agent_pool: mi300_2 agent_pool: mi325_2
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -122,21 +104,15 @@ steps:
- timeout 20m pytest -s -v tests/diffusion/test_diffusion_worker.py - timeout 20m pytest -s -v tests/diffusion/test_diffusion_worker.py
- label: "Diffusion · Model Test" - label: "Diffusion · Model Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
commands: commands:
# Preserve legacy one-GPU tests without cards_1 while excluding every - timeout 15m pytest -sv tests/diffusion/models/ -m 'core_model and cuda' --run-level "core_model"
# registered multi-GPU marker from this one-GPU job.
- >-
timeout 15m pytest -sv tests/diffusion/models/
-m 'core_model and cuda and not
(cards_2 or cards_3 or cards_4 or cards_5 or cards_6 or cards_7 or cards_8)'
--run-level "core_model"
- label: "Diffusion · Wan22 Test" - label: "Diffusion · Wan22 Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -150,7 +126,7 @@ steps:
## ISSUE depends on `diffusers` package: https://github.com/huggingface/diffusers/issues/13274 ## ISSUE depends on `diffusers` package: https://github.com/huggingface/diffusers/issues/13274
# - label: "Audio Generation Model Test" # - label: "Audio Generation Model Test"
# agent_pool: mi300_1 # agent_pool: mi325_1
# depends_on: amd-build # depends_on: amd-build
# mirror_hardwares: [amdproduction] # mirror_hardwares: [amdproduction]
# grade: Blocking # grade: Blocking
@@ -161,7 +137,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 # - 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" - label: "Engine Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -173,7 +149,7 @@ steps:
' '
# - label: "Omni Model Test Qwen3-Omni" # - label: "Omni Model Test Qwen3-Omni"
# agent_pool: mi300_2 # agent_pool: mi325_2
# depends_on: amd-build # depends_on: amd-build
# mirror_hardwares: [amdproduction] # mirror_hardwares: [amdproduction]
# grade: Blocking # grade: Blocking
@@ -186,7 +162,7 @@ steps:
- group: ":card_index_dividers: E2E Test" - group: ":card_index_dividers: E2E Test"
steps: steps:
- label: "Qwen3-TTS E2E Test" - label: "Qwen3-TTS E2E Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
@@ -197,17 +173,20 @@ steps:
- timeout 30m pytest -s -v tests/e2e/online_serving/test_qwen3_tts_customvoice.py -m "core_model" --run-level "core_model" - 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" - label: "CosyVoice3-TTS E2E Test"
agent_pool: mi300_1 agent_pool: mi325_1
depends_on: amd-build depends_on: amd-build
mirror_hardwares: [amdproduction] mirror_hardwares: [amdproduction]
grade: Blocking grade: Blocking
commands: commands:
- pytest -s -v tests/e2e/online_serving/test_cosyvoice3_tts_expansion.py -m "slow" --run-level "core_model" - |
timeout 20m bash -c '
pytest -s -v tests/e2e/online_serving/test_cosyvoice3_tts_expansion.py -m "slow" --run-level "core_model"
'
# TODO: Bagel test on ROCm is very unstable. @tjtanaa # TODO: Bagel test on ROCm is very unstable. @tjtanaa
# Need to debug before reneable numerical changes across large PRs # Need to debug before reneable numerical changes across large PRs
# - label: "Bagel Text2Img Model Test" # - label: "Bagel Text2Img Model Test"
# agent_pool: mi300_1 # agent_pool: mi325_1
# depends_on: amd-build # depends_on: amd-build
# mirror_hardwares: [amdproduction] # mirror_hardwares: [amdproduction]
# grade: Blocking # grade: Blocking
@@ -219,7 +198,7 @@ steps:
# - timeout 30m pytest -s -v tests/e2e/offline_inference/test_bagel.py -m "core_model" --run-level "core_model" -k "rocm" # - 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" # - label: "Bagel Img2Img Model Test"
# agent_pool: mi300_1 # agent_pool: mi325_1
# depends_on: amd-build # depends_on: amd-build
# mirror_hardwares: [amdproduction] # mirror_hardwares: [amdproduction]
# grade: Blocking # grade: Blocking
@@ -231,7 +210,7 @@ steps:
# - timeout 30m pytest -s -v tests/e2e/offline_inference/test_bagel.py -m "core_model" --run-level "core_model" -k "rocm" # - 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" # - label: "Bagel Online Serving Test"
# agent_pool: mi300_1 # agent_pool: mi325_1
# depends_on: amd-build # depends_on: amd-build
# mirror_hardwares: [amdproduction] # mirror_hardwares: [amdproduction]
# grade: Blocking # grade: Blocking
+8 -104
View File
@@ -1,65 +1,11 @@
{# vllm-omni customized version {# vllm-omni customized version
Based on: https://github.com/vllm-project/ci-infra/blob/main/buildkite/test-template-amd.j2 Based on: https://github.com/vllm-project/ci-infra/blob/main/buildkite/test-template-amd.j2
Last synced: 2026-08-04 Last synced: 2025-12-15
Modifications: Use the shared MI300 queues and Omni command-passing contract Modifications: Removed unused CUDA/NVIDIA logic, keeping only AMD tests
#} #}
{% set docker_image_amd = "rocm/vllm-omni:$BUILDKITE_COMMIT" %} {% set docker_image_amd = "rocm/vllm-omni:$BUILDKITE_COMMIT" %}
{% set default_working_dir = "/app/vllm-omni" %} {% 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 %} {% if env %}
env: env:
{% for name in env %} {% for name in env %}
@@ -107,31 +53,19 @@ steps:
{% if step.agent_pool %} {% if step.agent_pool %}
queue: amd_{{ step.agent_pool }} queue: amd_{{ step.agent_pool }}
{% else %} {% else %}
queue: amd_mi300_1 queue: amd_mi325_1
{% endif %} {% endif %}
{{ amd_native_k8s_plugin(" ", step) }}
{% set cmd_body = (step.command or (step.commands | join("\n"))) | trim %} {% set cmd_body = (step.command or (step.commands | join("\n"))) | trim %}
{% set indented_cmd = cmd_body | replace("\n", "\n ") %} {% set indented_cmd = cmd_body | replace("\n", "\n ") %}
command: bash .buildkite/amd/scripts/run-amd-test.sh command: bash .buildkite/amd/scripts/run-amd-test.sh
env: env:
AMD_CI_RUNTIME: "native" DOCKER_BUILDKIT: "1"
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 %} {% if step.env %}
{% for name in step.env %} {% for name in step.env %}
{{ name }}: "{{ step.env[name] }}" {{ name }}: "{{ step.env[name] }}"
{% endfor %} {% endfor %}
{% endif %} {% endif %}
TEST_COMMANDS: |- TEST_COMMAND: |-
(command rocm-smi || true) && cd {{ (step.working_dir or default_working_dir) | safe }} (command rocm-smi || true) && cd {{ (step.working_dir or default_working_dir) | safe }}
{% if env %} {% if env %}
{% for name in env %} {% for name in env %}
@@ -148,15 +82,6 @@ steps:
{% endif %} {% endif %}
{{ indented_cmd | safe }} {{ indented_cmd | safe }}
priority: 100 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 %} {% if step.timeout_in_minutes %}
timeout_in_minutes: {{ step.timeout_in_minutes }} timeout_in_minutes: {{ step.timeout_in_minutes }}
{% endif %} {% endif %}
@@ -175,31 +100,19 @@ steps:
{% if step.agent_pool %} {% if step.agent_pool %}
queue: amd_{{ step.agent_pool }} queue: amd_{{ step.agent_pool }}
{% else %} {% else %}
queue: amd_mi300_1 queue: amd_mi325_1
{% endif %} {% endif %}
{{ amd_native_k8s_plugin(" ", step) }}
{% set cmd_body = (step.command or (step.commands | join("\n"))) | trim %} {% set cmd_body = (step.command or (step.commands | join("\n"))) | trim %}
{% set indented_cmd = cmd_body | replace("\n", "\n ") %} {% set indented_cmd = cmd_body | replace("\n", "\n ") %}
command: bash .buildkite/amd/scripts/run-amd-test.sh command: bash .buildkite/amd/scripts/run-amd-test.sh
env: env:
AMD_CI_RUNTIME: "native" DOCKER_BUILDKIT: "1"
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 %} {% if step.env %}
{% for name in step.env %} {% for name in step.env %}
{{ name }}: "{{ step.env[name] }}" {{ name }}: "{{ step.env[name] }}"
{% endfor %} {% endfor %}
{% endif %} {% endif %}
TEST_COMMANDS: |- TEST_COMMAND: |-
(command rocm-smi || true) && cd {{ (step.working_dir or default_working_dir) | safe }} (command rocm-smi || true) && cd {{ (step.working_dir or default_working_dir) | safe }}
{% if env %} {% if env %}
{% for name in env %} {% for name in env %}
@@ -216,15 +129,6 @@ steps:
{% endif %} {% endif %}
{{ indented_cmd | safe }} {{ indented_cmd | safe }}
priority: 100 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 %} {% if step.timeout_in_minutes %}
timeout_in_minutes: {{ step.timeout_in_minutes }} timeout_in_minutes: {{ step.timeout_in_minutes }}
{% endif %} {% endif %}
+30 -493
View File
@@ -1,36 +1,14 @@
# Uploader-only hardware presets for CUDA / NPU test YAML. # Uploader-only hardware presets for CUDA / NPU test YAML.
# Referenced as ``mirror_hardwares: l4_1`` on a step; ``upload_pipeline.py`` expands to # Referenced as ``mirror_hardwares: l4_1`` on a step; ``upload_pipeline.py`` expands to
# ``agents`` (+ optional top-level ``image`` for NPU) + ``plugins`` (+ ``retry`` # ``agents`` (+ optional top-level ``image`` for NPU) + ``plugins``, then strips
# for ``l4_*``), then strips ``mirror_hardwares`` before Buildkite upload. # ``mirror_hardwares`` before Buildkite upload.
#
# Omit the key: compose {chip}_{n} from pytest -m SKU + cards_n (max of several
# positives; not cards_1 with no positive cards_* → that chip's highest existing preset).
# Unset MIRROR_HW matches H100 or L4 in -m (both → H100); no match skips the step.
# MIRROR_HW=b200 matches B200 in -m (else the step is skipped).
# MIRROR_HW must be empty or b200 (case-insensitive); unknown values fail the upload.
# CUDA preset strings (h100_4, l4_1) are omitted when MIRROR_HW=b200.
# Unset MIRROR_HW keeps string presets. NPU presets ignore MIRROR_HW.
# #
# CUDA naming: {chip}_{count} # CUDA naming: {chip}_{count}
# l4_1, l4_2, l4_3, l4_4, h100_1 … h100_4, b200_1 … b200_4 # l4_1, l4_4, h100_1 … h100_4
# NPU naming: {queue}_{resource_class} # NPU naming: {queue}_{resource_class}
# a2b3_npu_1 — ascend-a2b3, resource_class npu-1 (A2/B3 CI image) # a2b3_npu_1 — ascend-a2b3, resource_class npu-1 (A2/B3 CI image)
# a2b3_npu_4 — ascend-a2b3, resource_class npu-4 # a2b3_npu_4 — ascend-a2b3, resource_class npu-4
# a3_npu_2 — ascend-a3, resource_class npu-2 (A3 CI image tag …:$BUILDKITE_COMMIT-a3) # a3_npu_2 — ascend-a3, resource_class npu-2
# a5_npu_2 — ascend-a5, resource_class npu-2
# 310p_npu_1 — ascend-310p, resource_class npu-1
#
# B200 (b200-k8s): matches vLLM device ``b200-k8s`` from ci-infra
# (AgentQueue.B200_K8S + b200_plugin_template). Queue routes the K8s pool;
# podSpec uses hostNetwork + /raid|/mnt/shared (no H100-style nodeSelector).
#
# The ``l4_*`` presets run on the EKS ``l4-k8s`` queue (agent-stack-k8s),
# packed onto g6.12xlarge L4 nodes (``vllm.ci/gpu-pool=l4x4``). GPU / CPU /
# memory / shm requests scale with card count (1 GPU = 10 CPU / 40Gi / 24Gi shm).
# Retry covers lost agents / killed pods, not pytest or OOM (exit 1 / 137).
# The Kubernetes HF secret deliberately uses VLLM_CI_HF_TOKEN. Scheduled
# build-level HF_TOKEN values can override pod env entries with the same name;
# upload_pipeline.py restores the secret-backed value inside the job shell.
x-npu-k8s-plugins: &npu_k8s_plugins x-npu-k8s-plugins: &npu_k8s_plugins
- kubernetes: - kubernetes:
@@ -38,224 +16,37 @@ x-npu-k8s-plugins: &npu_k8s_plugins
imagePullSecrets: imagePullSecrets:
- name: swr-secret - name: swr-secret
# Infra-only retry. Omit exit 1 (pytest failed) and 137 (cgroup OOM / SIGKILL
# of the test process). ci-infra ``K8S_RETRY`` still retries exit 1.
x-l4-k8s-retry: &l4_k8s_retry
automatic:
- exit_status: -1
limit: 1
- exit_status: 128
limit: 1
- signal_reason: agent_stop
limit: 1
- signal_reason: agent_refused
limit: 1
mirror_hardwares: mirror_hardwares:
l4_1: l4_1:
agents: agents:
queue: l4-k8s queue: gpu_1_queue
retry: *l4_k8s_retry
plugins: plugins:
- kubernetes: - docker#v5.2.0:
metadata: image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
annotations: always-pull: true
cluster-autoscaler.kubernetes.io/safe-to-evict: "false" propagate-environment: true
karpenter.sh/do-not-disrupt: "true" shm-size: "8gb"
podSpec: environment:
priorityClassName: ci - HF_HOME=/fsx/hf_cache
serviceAccountName: buildkite-gpu-jobs - HF_TOKEN
tolerations: volumes:
- key: nvidia.com/gpu - /fsx/hf_cache:/fsx/hf_cache
operator: Exists
effect: NoSchedule
nodeSelector:
vllm.ci/gpu-pool: l4x4
containers:
- image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
resources:
limits:
nvidia.com/gpu: 1
requests:
nvidia.com/gpu: 1
cpu: "10"
memory: 40Gi
volumeMounts:
- name: devshm
mountPath: /dev/shm
- name: hf-cache
mountPath: /fsx/hf_cache
env:
- name: HF_HOME
value: /fsx/hf_cache
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
key: token
volumes:
- name: devshm
emptyDir:
medium: Memory
sizeLimit: 24Gi
- name: hf-cache
hostPath:
path: /fsx/hf_cache
type: DirectoryOrCreate
l4_2:
agents:
queue: l4-k8s
retry: *l4_k8s_retry
plugins:
- kubernetes:
metadata:
annotations:
cluster-autoscaler.kubernetes.io/safe-to-evict: "false"
karpenter.sh/do-not-disrupt: "true"
podSpec:
priorityClassName: ci
serviceAccountName: buildkite-gpu-jobs
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
nodeSelector:
vllm.ci/gpu-pool: l4x4
containers:
- image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
resources:
limits:
nvidia.com/gpu: 2
requests:
nvidia.com/gpu: 2
cpu: "20"
memory: 80Gi
volumeMounts:
- name: devshm
mountPath: /dev/shm
- name: hf-cache
mountPath: /fsx/hf_cache
env:
- name: HF_HOME
value: /fsx/hf_cache
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
key: token
volumes:
- name: devshm
emptyDir:
medium: Memory
sizeLimit: 48Gi
- name: hf-cache
hostPath:
path: /fsx/hf_cache
type: DirectoryOrCreate
l4_3:
agents:
queue: l4-k8s
retry: *l4_k8s_retry
plugins:
- kubernetes:
metadata:
annotations:
cluster-autoscaler.kubernetes.io/safe-to-evict: "false"
karpenter.sh/do-not-disrupt: "true"
podSpec:
priorityClassName: ci
serviceAccountName: buildkite-gpu-jobs
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
nodeSelector:
vllm.ci/gpu-pool: l4x4
containers:
- image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
resources:
limits:
nvidia.com/gpu: 3
requests:
nvidia.com/gpu: 3
cpu: "30"
memory: 120Gi
volumeMounts:
- name: devshm
mountPath: /dev/shm
- name: hf-cache
mountPath: /fsx/hf_cache
env:
- name: HF_HOME
value: /fsx/hf_cache
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
key: token
volumes:
- name: devshm
emptyDir:
medium: Memory
sizeLimit: 72Gi
- name: hf-cache
hostPath:
path: /fsx/hf_cache
type: DirectoryOrCreate
l4_4: l4_4:
agents: agents:
queue: l4-k8s queue: gpu_4_queue
retry: *l4_k8s_retry
plugins: plugins:
- kubernetes: - docker#v5.2.0:
metadata: image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
annotations: always-pull: true
cluster-autoscaler.kubernetes.io/safe-to-evict: "false" propagate-environment: true
karpenter.sh/do-not-disrupt: "true" shm-size: "8gb"
podSpec: environment:
priorityClassName: ci - HF_HOME=/fsx/hf_cache
serviceAccountName: buildkite-gpu-jobs - HF_TOKEN
tolerations: volumes:
- key: nvidia.com/gpu - /fsx/hf_cache:/fsx/hf_cache
operator: Exists
effect: NoSchedule
nodeSelector:
vllm.ci/gpu-pool: l4x4
containers:
- image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
resources:
limits:
nvidia.com/gpu: 4
requests:
nvidia.com/gpu: 4
cpu: "40"
memory: 160Gi
volumeMounts:
- name: devshm
mountPath: /dev/shm
- name: hf-cache
mountPath: /fsx/hf_cache
env:
- name: HF_HOME
value: /fsx/hf_cache
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
key: token
volumes:
- name: devshm
emptyDir:
medium: Memory
sizeLimit: 96Gi
- name: hf-cache
hostPath:
path: /fsx/hf_cache
type: DirectoryOrCreate
h100_1: h100_1:
agents: agents:
@@ -276,7 +67,7 @@ mirror_hardwares:
env: env:
- name: HF_HOME - name: HF_HOME
value: /root/.cache/huggingface value: /root/.cache/huggingface
- name: VLLM_CI_HF_TOKEN - name: HF_TOKEN
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: hf-token-secret name: hf-token-secret
@@ -311,7 +102,7 @@ mirror_hardwares:
env: env:
- name: HF_HOME - name: HF_HOME
value: /root/.cache/huggingface value: /root/.cache/huggingface
- name: VLLM_CI_HF_TOKEN - name: HF_TOKEN
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: hf-token-secret name: hf-token-secret
@@ -346,7 +137,7 @@ mirror_hardwares:
env: env:
- name: HF_HOME - name: HF_HOME
value: /root/.cache/huggingface value: /root/.cache/huggingface
- name: VLLM_CI_HF_TOKEN - name: HF_TOKEN
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: hf-token-secret name: hf-token-secret
@@ -381,7 +172,7 @@ mirror_hardwares:
env: env:
- name: HF_HOME - name: HF_HOME
value: /root/.cache/huggingface value: /root/.cache/huggingface
- name: VLLM_CI_HF_TOKEN - name: HF_TOKEN
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: hf-token-secret name: hf-token-secret
@@ -397,211 +188,6 @@ mirror_hardwares:
path: /mnt/hf-cache path: /mnt/hf-cache
type: DirectoryOrCreate type: DirectoryOrCreate
# vLLM device: b200-k8s → agents.queue b200-k8s + Buildkite K8s agent-stack podSpec
b200_1:
agents:
queue: b200-k8s
plugins:
- kubernetes:
podSpec:
runtimeClassName: nvidia
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
imagePullSecrets:
- name: k8s-ecr-login-renew-docker-secret
containers:
- image: 936637512419.dkr.ecr.us-west-2.amazonaws.com/vllm-ci-pull-through-cache/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
resources:
limits:
nvidia.com/gpu: 1
securityContext:
capabilities:
add:
- IPC_LOCK
- SYS_RESOURCE
volumeMounts:
- name: devshm
mountPath: /dev/shm
- name: raid
mountPath: /raid
- name: shared
mountPath: /mnt/shared
env:
- name: NCCL_CUMEM_HOST_ENABLE
value: "0"
- name: HF_HOME
value: /raid/hf_cache
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
key: token
volumes:
- name: devshm
emptyDir:
medium: Memory
- name: raid
hostPath:
path: /raid
type: DirectoryOrCreate
- name: shared
hostPath:
path: /mnt/shared
type: DirectoryOrCreate
b200_2:
agents:
queue: b200-k8s
plugins:
- kubernetes:
podSpec:
runtimeClassName: nvidia
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
imagePullSecrets:
- name: k8s-ecr-login-renew-docker-secret
containers:
- image: 936637512419.dkr.ecr.us-west-2.amazonaws.com/vllm-ci-pull-through-cache/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
resources:
limits:
nvidia.com/gpu: 2
securityContext:
capabilities:
add:
- IPC_LOCK
- SYS_RESOURCE
volumeMounts:
- name: devshm
mountPath: /dev/shm
- name: raid
mountPath: /raid
- name: shared
mountPath: /mnt/shared
env:
- name: NCCL_CUMEM_HOST_ENABLE
value: "0"
- name: HF_HOME
value: /raid/hf_cache
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
key: token
volumes:
- name: devshm
emptyDir:
medium: Memory
- name: raid
hostPath:
path: /raid
type: DirectoryOrCreate
- name: shared
hostPath:
path: /mnt/shared
type: DirectoryOrCreate
b200_3:
agents:
queue: b200-k8s
plugins:
- kubernetes:
podSpec:
runtimeClassName: nvidia
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
imagePullSecrets:
- name: k8s-ecr-login-renew-docker-secret
containers:
- image: 936637512419.dkr.ecr.us-west-2.amazonaws.com/vllm-ci-pull-through-cache/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
resources:
limits:
nvidia.com/gpu: 3
securityContext:
capabilities:
add:
- IPC_LOCK
- SYS_RESOURCE
volumeMounts:
- name: devshm
mountPath: /dev/shm
- name: raid
mountPath: /raid
- name: shared
mountPath: /mnt/shared
env:
- name: NCCL_CUMEM_HOST_ENABLE
value: "0"
- name: HF_HOME
value: /raid/hf_cache
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
key: token
volumes:
- name: devshm
emptyDir:
medium: Memory
- name: raid
hostPath:
path: /raid
type: DirectoryOrCreate
- name: shared
hostPath:
path: /mnt/shared
type: DirectoryOrCreate
b200_4:
agents:
queue: b200-k8s
plugins:
- kubernetes:
podSpec:
runtimeClassName: nvidia
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
imagePullSecrets:
- name: k8s-ecr-login-renew-docker-secret
containers:
- image: 936637512419.dkr.ecr.us-west-2.amazonaws.com/vllm-ci-pull-through-cache/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
resources:
limits:
nvidia.com/gpu: 4
securityContext:
capabilities:
add:
- IPC_LOCK
- SYS_RESOURCE
volumeMounts:
- name: devshm
mountPath: /dev/shm
- name: raid
mountPath: /raid
- name: shared
mountPath: /mnt/shared
env:
- name: NCCL_CUMEM_HOST_ENABLE
value: "0"
- name: HF_HOME
value: /raid/hf_cache
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
key: token
volumes:
- name: devshm
emptyDir:
medium: Memory
- name: raid
hostPath:
path: /raid
type: DirectoryOrCreate
- name: shared
hostPath:
path: /mnt/shared
type: DirectoryOrCreate
a2b3_npu_1: a2b3_npu_1:
agents: agents:
queue: "ascend-a2b3" queue: "ascend-a2b3"
@@ -651,52 +237,3 @@ mirror_hardwares:
resource_class: "npu-16" resource_class: "npu-16"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-a3" image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-a3"
plugins: *npu_k8s_plugins plugins: *npu_k8s_plugins
a5_npu_2:
agents:
queue: "ascend-a5"
resource_class: "npu-2"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-a5"
plugins: *npu_k8s_plugins
a5_npu_4:
agents:
queue: "ascend-a5"
resource_class: "npu-4"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-a5"
plugins: *npu_k8s_plugins
a5_npu_8:
agents:
queue: "ascend-a5"
resource_class: "npu-8"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-a5"
plugins: *npu_k8s_plugins
a5_npu_16:
agents:
queue: "ascend-a5"
resource_class: "npu-16"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-a5"
plugins: *npu_k8s_plugins
310p_npu_1:
agents:
queue: "ascend-310p"
resource_class: "npu-1"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-310p"
plugins: *npu_k8s_plugins
310p_npu_2:
agents:
queue: "ascend-310p"
resource_class: "npu-2"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-310p"
plugins: *npu_k8s_plugins
310p_npu_4:
agents:
queue: "ascend-310p"
resource_class: "npu-4"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-310p"
plugins: *npu_k8s_plugins
@@ -1,98 +0,0 @@
#!/usr/bin/env bash
# Run a model's E2E tests once per entry mode as separate coverage runs and upload
# the reports, so per-mode attribution survives (see vllm-project/vllm-omni#5332).
#
# Usage: run_cov_split.sh --model-id <id> [--offline <paths>] [--online <paths>] \
# -- <pytest args...>
#
# --model-id Names the artifacts: coverage-<id>-{offline,online}-<step id>.xml.gz.
# Use the model's directory name under vllm_omni/*/models/.
# --offline Pytest path(s) for the offline half. Repeatable.
# --online Pytest path(s) for the online half. Repeatable.
# -- Everything after this is forwarded verbatim to every pytest run,
# quoting preserved: the job's own -m expression, --run-level, and
# anything else it needs (e.g. --test-config-file).
#
# At least one of --offline/--online is required, so a job with only one mode runs
# just that half. Total runtime is bounded by the step's timeout_in_minutes.
#
# Coverage + artifact upload run only on scheduled weekly E2E sweeps
# (WEEKLY=1 and BUILDKITE_BRANCH=main — same gate as bootstrap --e2e).
# All other invocations (PR merge-test, regular main merge, local) run the
# same combined pytest as the job used before coverage was added.
#
# Exits non-zero if any half or the upload failed.
set -uo pipefail
MODEL_ID=""
OFFLINE=()
ONLINE=()
PYTEST_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
--model-id) MODEL_ID="$2"; shift 2 ;;
--offline) OFFLINE+=("$2"); shift 2 ;;
--online) ONLINE+=("$2"); shift 2 ;;
--) shift; PYTEST_ARGS=("$@"); break ;;
*) echo "run_cov_split.sh: unknown argument: $1" >&2; exit 2 ;;
esac
done
if [[ -z "${MODEL_ID}" ]]; then
echo "run_cov_split.sh: missing --model-id" >&2
exit 2
fi
if ((${#OFFLINE[@]} == 0 && ${#ONLINE[@]} == 0)); then
echo "run_cov_split.sh: need at least one of --offline / --online" >&2
exit 2
fi
_should_run_coverage() {
[[ "${WEEKLY:-}" == "1" && "${BUILDKITE_BRANCH:-}" == "main" ]]
}
if ! _should_run_coverage; then
PATHS=("${OFFLINE[@]}" "${ONLINE[@]}")
echo "--- pytest (no coverage): ${MODEL_ID}"
pytest -s -v "${PATHS[@]}" "${PYTEST_ARGS[@]}"
exit $?
fi
# Two steps can test the same model, and artifacts are scoped to the build (a
# nightly one carries the ready, merge and nightly tiers at once), so unsuffixed
# reports would share a path -- a glob download then races to rename onto one file.
# Not BUILDKITE_JOB_ID: the step id survives retries, so attempts collapse under
# Buildkite's latest-attempt lookup instead of accumulating. A parallel step would
# need BUILDKITE_PARALLEL_JOB as well; no pilot is one.
REPORT_SUFFIX="${BUILDKITE_STEP_ID:-local}"
OFFLINE_REPORT="coverage-${MODEL_ID}-offline-${REPORT_SUFFIX}.xml"
ONLINE_REPORT="coverage-${MODEL_ID}-online-${REPORT_SUFFIX}.xml"
run_half() {
local mode="$1"
shift
local report="coverage-${MODEL_ID}-${mode}-${REPORT_SUFFIX}.xml"
echo "--- coverage: ${MODEL_ID} ${mode}"
pytest -s -v "$@" "${PYTEST_ARGS[@]}" --cov=vllm_omni --cov-report="xml:${report}"
local status=$?
# Cobertura lists every statement in vllm_omni whether or not it ran, so the
# report is ~7MB no matter how little the job covers. The repetition is what
# dominates it, and gzip takes that to ~400KB.
gzip -9 -f "${report}" || status=1
return "${status}"
}
# Clear both modes, not just the ones being run: the upload glob matches both, so
# a report left by an earlier run would otherwise be published as if the mode it
# belongs to had run this time.
rm -f "${OFFLINE_REPORT}" "${ONLINE_REPORT}" "${OFFLINE_REPORT}.gz" "${ONLINE_REPORT}.gz"
EXIT=0
((${#OFFLINE[@]})) && { run_half offline "${OFFLINE[@]}" || EXIT=1; }
((${#ONLINE[@]})) && { run_half online "${ONLINE[@]}" || EXIT=1; }
buildkite-agent artifact upload "coverage-${MODEL_ID}-*-${REPORT_SUFFIX}.xml.gz" || EXIT=1
exit "${EXIT}"
+36 -3
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Shared Buildkite skip-ci and CI-YAML level targeting logic. """Shared Buildkite skip-ci and CI-YAML level targeting logic.
``resolve_ci_decision(changed_files, ...)`` buckets explicit file lists (tests, dry-run). ``resolve_ci_decision(changed_files, ...)`` buckets explicit file lists (tests, dry-run).
@@ -11,6 +10,12 @@ CLI (for bootstrap scripts; exit code is the signal):
python3 skip_ci.py gate <platform> <level> python3 skip_ci.py gate <platform> <level>
Exit 0 when bootstrap should stop (skip-all, or that L2/L3 target is off). Exit 0 when bootstrap should stop (skip-all, or that L2/L3 target is off).
Prints ``skip-all`` or ``skip-l23`` on stdout; logs the decision once. Prints ``skip-all`` or ``skip-l23`` on stdout; logs the decision once.
python3 skip_ci.py check-skip-all
Exit 0 when only docs/skip-mark changes → bootstrap skips entire CI upload.
python3 skip_ci.py check-skip-l2-l3 <platform> <level>
Exit 0 when that L2/L3 pipeline should be skipped (CI config YAML-only diffs).
python3 skip_ci.py print-annotate
Print decision message for Buildkite annotation.
""" """
from __future__ import annotations from __future__ import annotations
@@ -253,10 +258,11 @@ class CiDecision:
@dataclass(frozen=True) @dataclass(frozen=True)
class CiContext: class CiContext:
"""Skip-ci decision plus changed files used for pipeline filtering.""" """Skip-ci decision plus git diff inputs used to compute it."""
decision: CiDecision decision: CiDecision
changed_files: list[str] | None changed_files: list[str] | None
diff_range: str | None
def _log(message: str) -> None: def _log(message: str) -> None:
@@ -326,7 +332,7 @@ def resolve_ci_context_from_git() -> CiContext:
diff_range = _resolve_diff_range() diff_range = _resolve_diff_range()
changed_files = _changed_files_for_diff_range(diff_range) changed_files = _changed_files_for_diff_range(diff_range)
decision = resolve_ci_decision(changed_files, diff_range=diff_range) decision = resolve_ci_decision(changed_files, diff_range=diff_range)
return CiContext(decision=decision, changed_files=changed_files) return CiContext(decision=decision, changed_files=changed_files, diff_range=diff_range)
# --- Doc / skip-mark helpers --- # --- Doc / skip-mark helpers ---
@@ -565,6 +571,23 @@ def main() -> int:
gate_parser.add_argument("platform", help="cuda, amd, intel, or npu") gate_parser.add_argument("platform", help="cuda, amd, intel, or npu")
gate_parser.add_argument("level", choices=("l2", "l3"), help="test level") gate_parser.add_argument("level", choices=("l2", "l3"), help="test level")
subparsers.add_parser(
"check-skip-all",
help="exit 0 when docs/skip-mark-only diff should skip entire CI upload",
)
skip_parser = subparsers.add_parser(
"check-skip-l2-l3",
help="exit 0 when PLATFORM l2/l3 pipeline should be skipped (CI config YAML-only diffs)",
)
skip_parser.add_argument("platform", help="cuda, amd, intel, or npu")
skip_parser.add_argument("level", choices=("l2", "l3"), help="test level")
subparsers.add_parser(
"print-annotate",
help="print human-readable skip reason for Buildkite annotation",
)
args = parser.parse_args() args = parser.parse_args()
decision = resolve_ci_context_from_git().decision decision = resolve_ci_context_from_git().decision
@@ -577,6 +600,16 @@ def main() -> int:
return 0 return 0
return 1 return 1
if args.command == "check-skip-all":
return 0 if decision.skip_all else 1
if args.command == "check-skip-l2-l3":
return 0 if not decision.is_run(args.platform, args.level) else 1
if args.command == "print-annotate":
print(decision.message or "CI will run normally")
return 0
return 1 return 1
+89 -342
View File
@@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Render and optionally upload Buildkite pipeline YAML with diff-aware logic. """Render and optionally upload Buildkite pipeline YAML with diff-aware logic.
Bootstrap mode (``bootstrap-upload-steps.yml``): Bootstrap mode (``bootstrap-upload-steps.yml``):
@@ -12,19 +11,8 @@ Bootstrap mode (``bootstrap-upload-steps.yml``):
Test pipeline mode (e.g. test-merge.yml): Test pipeline mode (e.g. test-merge.yml):
- Drop steps whose ``source_file_dependencies`` do not match changed files. - Drop steps whose ``source_file_dependencies`` do not match changed files.
- Expand uploader-only ``mirror_hardwares`` into ``agents`` (+ optional ``image`` - Expand uploader-only ``mirror_hardwares: l4_1`` into ``agents`` (+ optional ``image``
for NPU) + ``plugins`` (see ci_mirror_hardwares.yml). for NPU) + ``plugins`` (see ci_mirror_hardwares.yml).
- Omit ``mirror_hardwares`` to compose ``{chip}_{n}`` from pytest ``-m`` SKU
markers plus ``cards_n`` (max of several positives; ``not cards_1`` with no
positive ``cards_*`` uses that chip's highest existing preset). An explicit
preset string (``h100_2``) is used as-is (marks are ignored). Names that
are not keys in ``ci_mirror_hardwares.yml`` fail the upload.
Inferred chip: unset ``MIRROR_HW`` matches ``H100`` or ``L4`` in ``-m``
(both → H100); no match skips the step. ``MIRROR_HW=b200`` matches
``B200`` in ``-m`` (otherwise skipped). ``-m`` is not rewritten.
``MIRROR_HW`` must be empty or ``b200``; unknown values fail the upload.
A CUDA preset string such as ``h100_4`` is omitted when ``MIRROR_HW=b200``.
Usage: Usage:
python3 upload_pipeline.py [--upload] [--all | --e2e] <pipeline.yml> python3 upload_pipeline.py [--upload] [--all | --e2e] <pipeline.yml>
@@ -36,13 +24,11 @@ from __future__ import annotations
import argparse import argparse
import copy import copy
import os
import re
import subprocess import subprocess
import sys import sys
from functools import lru_cache from functools import lru_cache
from pathlib import Path from pathlib import Path
from typing import Any, Literal from typing import Any
try: try:
import yaml import yaml
@@ -58,27 +44,11 @@ from skip_ci import (
resolve_ci_context_from_git, resolve_ci_context_from_git,
) )
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from tests.helpers.mark import ( # noqa: E402
get_skus_for_platform,
get_supported_card_counts,
)
# --- Constants --- # --- Constants ---
LOG = "upload_pipeline" LOG = "upload_pipeline"
BOOTSTRAP_STEPS_FILENAME = "bootstrap-upload-steps.yml" BOOTSTRAP_STEPS_FILENAME = "bootstrap-upload-steps.yml"
BOOTSTRAP_IMAGE_BUILD_KEYS = frozenset( BOOTSTRAP_IMAGE_BUILD_KEYS = frozenset({"image-build", "image-build-a2", "image-build-a3"})
{
"image-build",
"image-build-a2",
"image-build-a3",
"image-build-a5",
"image-build-310p",
}
)
BOOTSTRAP_UPLOAD_IF_KEYS = { BOOTSTRAP_UPLOAD_IF_KEYS = {
"upload-ready-pipeline": "ready", "upload-ready-pipeline": "ready",
"upload-merge-pipeline": "merge", "upload-merge-pipeline": "merge",
@@ -87,15 +57,12 @@ BOOTSTRAP_UPLOAD_IF_KEYS = {
} }
E2E_GROUP_MARKER = "E2E Test" E2E_GROUP_MARKER = "E2E Test"
CI_MIRROR_HARDWARES_PATH = ROOT / ".buildkite/common/ci_mirror_hardwares.yml" CI_MIRROR_HARDWARES_PATH = ROOT / ".buildkite/common/ci_mirror_hardwares.yml"
CUDA_HF_TOKEN_ENV = "VLLM_CI_HF_TOKEN"
CUDA_HF_TOKEN_EXPORT = f'if [ -n "$${{{CUDA_HF_TOKEN_ENV}:-}}" ]; then export HF_TOKEN="$${{{CUDA_HF_TOKEN_ENV}}}"; fi'
# Bootstrap Buildkite ``if`` expressions. CUDA_NIGHTLY_ONLY = (
# ``*_MAIN_IF``: main + env schedule. ``*_LABEL_IF``: PR label (and/or composed with MAIN). '(build.pull_request.labels includes "nightly-test") || (build.branch == "main" && build.env("NIGHTLY") == "1")'
# ``*_UPLOAD_IF``: full gate for uploading that child pipeline. )
NIGHTLY_MAIN_IF = 'build.branch == "main" && build.env("NIGHTLY") == "1"' NPU_NIGHTLY_ONLY = (
NIGHTLY_LABEL_IF = ( '(build.branch == "main" && build.env("NIGHTLY") == "1") || '
f"({NIGHTLY_MAIN_IF}) || "
'(build.branch != "main" && (' '(build.branch != "main" && ('
'build.pull_request.labels includes "nightly-test" || ' 'build.pull_request.labels includes "nightly-test" || '
'build.pull_request.labels includes "omni-test" || ' 'build.pull_request.labels includes "omni-test" || '
@@ -104,19 +71,6 @@ NIGHTLY_LABEL_IF = (
'build.pull_request.labels includes "diffusion-x2v-test"' 'build.pull_request.labels includes "diffusion-x2v-test"'
"))" "))"
) )
WEEKLY_E2E_IF = 'build.branch == "main" && build.env("WEEKLY") == "1"'
WEEKLY_MAIN_IF = 'build.branch == "main" && (build.env("WEEKLY") == "1" || build.env("NON_CRITICAL") == "1")'
WEEKLY_LABEL_IF = f'({WEEKLY_MAIN_IF}) || (build.branch != "main" && build.pull_request.labels includes "weekly-test")'
READY_LABEL_IF = 'build.branch != "main" && build.pull_request.labels includes "ready"'
MERGE_LABEL_IF = 'build.branch != "main" && build.pull_request.labels includes "merge-test"'
MERGE_MAIN_IF = (
'build.branch == "main" && build.env("NIGHTLY") != "1" && '
'build.env("WEEKLY") != "1" && build.env("NON_CRITICAL") != "1"'
)
READY_UPLOAD_IF = f"({WEEKLY_E2E_IF}) || ({READY_LABEL_IF})"
MERGE_UPLOAD_IF = f"({WEEKLY_E2E_IF}) || (({MERGE_MAIN_IF}) || ({MERGE_LABEL_IF}))"
BOOTSTRAP_DISABLED_IF = "false"
BOOTSTRAP_ENABLED_IF = "true"
# --- Logging --- # --- Logging ---
@@ -148,47 +102,74 @@ def _format_bootstrap_if(expr: str) -> str:
def _compute_bootstrap_if_exprs(*, decision, platform: str) -> dict[str, str]: def _compute_bootstrap_if_exprs(*, decision, platform: str) -> dict[str, str]:
disabled = "false"
nightly_main = 'build.branch == "main" && build.env("NIGHTLY") == "1"'
nightly_only = NPU_NIGHTLY_ONLY if platform == "npu" else CUDA_NIGHTLY_ONLY
if platform == "npu": if platform == "npu":
ready_upload = READY_LABEL_IF ready_pr = (
merge_upload = BOOTSTRAP_DISABLED_IF 'build.branch != "main" && ('
weekly_label_if = BOOTSTRAP_DISABLED_IF 'build.pull_request.labels includes "npu-test" || '
'build.pull_request.labels includes "ready"'
")"
)
nightly_label_if = nightly_only
weekly_label_if = disabled
merge_base = disabled
else: else:
ready_upload = READY_UPLOAD_IF ready_pr = 'build.branch != "main" && build.pull_request.labels includes "ready"'
merge_upload = MERGE_UPLOAD_IF merge_main = 'build.branch == "main" && build.env("NIGHTLY") != "1" && build.env("WEEKLY") != "1"'
weekly_label_if = WEEKLY_LABEL_IF merge_pr = 'build.branch != "main" && build.pull_request.labels includes "merge-test"'
nightly_label_if = (
'(build.branch == "main" && build.env("NIGHTLY") == "1") || '
'(build.branch != "main" && ('
'build.pull_request.labels includes "nightly-test" || '
'build.pull_request.labels includes "omni-test" || '
'build.pull_request.labels includes "tts-test" || '
'build.pull_request.labels includes "diffusion-x2iat-test" || '
'build.pull_request.labels includes "diffusion-x2v-test"'
"))"
)
weekly_label_if = (
'(build.branch == "main" && build.env("WEEKLY") == "1") || '
'(build.branch != "main" && build.pull_request.labels includes "weekly-test")'
)
merge_base = f"({nightly_main}) || (({merge_main}) || ({merge_pr}))"
ready_base = f"({nightly_main}) || ({ready_pr})"
weekly_main = 'build.branch == "main" && build.env("WEEKLY") == "1"'
if decision.skip_all: if decision.skip_all:
# Docs / skip-mark only: no PR-label escape hatch. Main scheduled # Docs / skip-mark only: no PR-label escape hatch. Main scheduled
# NIGHTLY=1 still runs L4; WEEKLY=1 / NON_CRITICAL=1 still run L5. # NIGHTLY=1 still runs L4 + L2/L3 (--e2e); WEEKLY=1 still runs L5.
# main+WEEKLY=1 also uploads L2/L3 (those steps then pass --e2e). image_expr = f"({nightly_main}) || ({weekly_main})" if platform == "cuda" else nightly_main
image_expr = f"({NIGHTLY_MAIN_IF}) || ({WEEKLY_MAIN_IF})" if platform == "cuda" else NIGHTLY_MAIN_IF ready_expr = nightly_main
ready_expr = WEEKLY_E2E_IF if platform == "cuda" else BOOTSTRAP_DISABLED_IF merge_expr = nightly_main if platform == "cuda" else disabled
merge_expr = WEEKLY_E2E_IF if platform == "cuda" else BOOTSTRAP_DISABLED_IF nightly_expr = nightly_main
nightly_expr = NIGHTLY_MAIN_IF weekly_expr = weekly_main if platform == "cuda" else disabled
weekly_expr = WEEKLY_MAIN_IF if platform == "cuda" else BOOTSTRAP_DISABLED_IF
elif decision.skip_l2_l3: elif decision.skip_l2_l3:
l2_enabled = decision.is_run("npu", "l2") if platform == "npu" else decision.is_run("cuda", "l2") l2_enabled = decision.is_run("npu", "l2") if platform == "npu" else decision.is_run("cuda", "l2")
l3_enabled = platform == "cuda" and decision.is_run("cuda", "l3") l3_enabled = platform == "cuda" and decision.is_run("cuda", "l3")
ready_expr = ready_upload if l2_enabled else BOOTSTRAP_DISABLED_IF ready_expr = ready_base if l2_enabled else disabled
merge_expr = merge_upload if l3_enabled else BOOTSTRAP_DISABLED_IF merge_expr = merge_base if l3_enabled else disabled
nightly_expr = NIGHTLY_LABEL_IF nightly_expr = nightly_label_if
weekly_expr = weekly_label_if if platform == "cuda" else BOOTSTRAP_DISABLED_IF weekly_expr = weekly_label_if if platform == "cuda" else disabled
image_parts = [f"({NIGHTLY_LABEL_IF})"] image_parts = [f"({nightly_label_if})"]
if platform == "cuda": if platform == "cuda":
image_parts.append(f"({weekly_label_if})") image_parts.append(f"({weekly_label_if})")
if l2_enabled: if l2_enabled:
image_parts.insert(0, f"({ready_upload})") image_parts.insert(0, f"({ready_base})")
if l3_enabled: if l3_enabled:
image_parts.insert(1 if l2_enabled else 0, f"({merge_upload})") image_parts.insert(1 if l2_enabled else 0, f"({merge_base})")
image_expr = " || ".join(image_parts) image_expr = " || ".join(image_parts)
else: else:
image_expr = BOOTSTRAP_ENABLED_IF image_expr = "true"
ready_expr = ready_upload ready_expr = ready_base
merge_expr = merge_upload if platform == "cuda" else BOOTSTRAP_DISABLED_IF merge_expr = merge_base if platform == "cuda" else disabled
nightly_expr = NIGHTLY_LABEL_IF nightly_expr = nightly_label_if
weekly_expr = weekly_label_if if platform == "cuda" else BOOTSTRAP_DISABLED_IF weekly_expr = weekly_label_if if platform == "cuda" else disabled
return { return {
"image": _format_bootstrap_if(image_expr), "image": _format_bootstrap_if(image_expr),
@@ -264,268 +245,32 @@ def _load_mirror_hardwares() -> dict[str, dict[str, Any]]:
return presets return presets
@lru_cache(maxsize=1) def _expand_mirror_hardwares(step: dict[str, Any]) -> dict[str, Any]:
def _cuda_mirror_chips() -> tuple[str, ...]: """Replace uploader-only ``mirror_hardwares`` with preset fields from ci_mirror_hardwares.yml."""
"""Lowercase CUDA SKUs that have at least one ``{chip}_{n}`` preset. hardware = step.get("mirror_hardwares")
if hardware is None:
return step
Longer tokens first so ``h100`` is not matched as a prefix of ``h200``. if not isinstance(hardware, str) or not hardware.strip():
"""
skus = get_skus_for_platform("cuda")
known = _load_mirror_hardwares()
chips = {
sku.lower() for sku in skus if any(name == sku.lower() or name.startswith(f"{sku.lower()}_") for name in known)
}
return tuple(sorted(chips, key=lambda chip: (-len(chip), chip)))
def _read_cards_marks(expr: str) -> tuple[set[int], bool]:
"""Parse registered ``cards_n`` / ``not cards_n`` from a pytest ``-m`` expr.
Returns positive card counts and whether any ``not cards_*`` is present.
"""
counts = get_supported_card_counts()
alt = "|".join(str(n) for n in sorted(counts, key=lambda n: (-len(str(n)), -n))) if counts else "(?!)"
positives: set[int] = set()
has_not_cards = False
for match in re.finditer(rf"\b(?:(not)\s+)?cards_({alt})\b", expr):
if match.group(1):
has_not_cards = True
else:
positives.add(int(match.group(2)))
return positives, has_not_cards
def _read_hardware_marks(expr: str) -> set[str]:
"""Return lowercase positive CUDA SKUs that have a mirror preset.
``not H100`` is ignored. SKUs with no ``{chip}_*`` preset (e.g. H200) are dropped.
"""
skus = sorted(get_skus_for_platform("cuda"), key=lambda s: (-len(s), s))
if not skus:
return set()
alt = "|".join(re.escape(sku) for sku in skus)
mirror = set(_cuda_mirror_chips())
chips: set[str] = set()
for match in re.finditer(rf"\b(?:(not)\s+)?({alt})\b", expr):
if match.group(1):
continue
chip = match.group(2).lower()
if chip in mirror:
chips.add(chip)
return chips
# CI policy: not pytest-mark registry.
_SUPPORTED_MIRROR_HW_SELECTORS = frozenset({"b200"})
# Unset MIRROR_HW: match H100 then L4 in -m (skip if neither).
_DEFAULT_INFER_CHIPS = ("h100", "l4")
_PYTEST_MARKER_ARG = re.compile(r"-m\s+(?:\"([^\"]*)\"|'([^']*)'|(\S+))")
_CARDS_CHIP_MAX: Literal["max"] = "max"
def _get_mirror_hw_selector() -> str:
"""Return lowercase ``MIRROR_HW``, or empty to keep string presets / marker chips.
A set value must be ``b200`` (case-insensitive). Unknown values fail closed
so a typo cannot silently drop the CUDA pipeline.
"""
selector = os.environ.get("MIRROR_HW", "").strip().lower()
if not selector:
return ""
if selector not in _SUPPORTED_MIRROR_HW_SELECTORS:
allowed = " or ".join(repr(s) for s in sorted(_SUPPORTED_MIRROR_HW_SELECTORS))
raise ValueError(f"unsupported MIRROR_HW={selector!r}; expected empty or {allowed}")
return selector
def _read_pytest_marks(commands: Any) -> tuple[set[str], int | Literal["max"] | None]:
"""Read pytest ``-m`` from step commands: CUDA SKU chips and cards count.
*chips* are lowercase SKUs from positive markers (``not H100`` is ignored).
*cards* is the max positive ``cards_n``, ``"max"`` when only ``not cards_*``
is present, or ``None``.
"""
chunks: list[str] = []
def _collect(value: Any) -> None:
if value is None:
return
if isinstance(value, str):
chunks.append(value)
elif isinstance(value, list):
for part in value:
_collect(part)
else:
chunks.append(str(value))
_collect(commands)
text = "\n".join(chunks)
chips: set[str] = set()
positives: set[int] = set()
has_not_cards = False
for match in _PYTEST_MARKER_ARG.finditer(text):
expr = match.group(1) or match.group(2) or match.group(3) or ""
chips |= _read_hardware_marks(expr)
card_counts, not_cards = _read_cards_marks(expr)
positives.update(card_counts)
has_not_cards = has_not_cards or not_cards
if positives:
cards: int | Literal["max"] | None = max(positives)
elif has_not_cards:
cards = _CARDS_CHIP_MAX
else:
cards = None
return chips, cards
def _compose_mirror_hardware_name(
chips: set[str],
cards: int | Literal["max"] | None,
*,
step_label: str,
) -> str | None:
"""Assemble ``{chip}_{n}`` from pytest ``-m`` chips/cards, or None to skip.
Unset ``MIRROR_HW``: ``H100`` if in ``-m``, else ``L4``, else skip.
Set ``MIRROR_HW``: that chip if in ``-m``, else skip.
``cards`` is a registered ``cards_n``, ``"max"`` for that chip's highest
existing preset, or None (error after a chip matched — no ``cards_*``).
"""
selector = _get_mirror_hw_selector()
if selector:
chip = selector if selector in chips else None
skip_reason = f"MIRROR_HW={selector!r} not in -m SKUs {sorted(chips)}"
else:
chip = next((c for c in _DEFAULT_INFER_CHIPS if c in chips), None)
skip_reason = f"no H100 or L4 in -m SKUs {sorted(chips)}"
if chip is None:
_log(f"skip {step_label}: {skip_reason}")
return None
if cards is None:
raise ValueError( raise ValueError(
f"step {step_label!r} has a pytest -m SKU marker but no cards_* " f"mirror_hardwares must be a non-empty string in step {_get_step_label(step)!r}",
f"(or not cards_*); add cards_n or set mirror_hardwares to force a preset",
) )
count_source = "not cards_*" if cards == _CARDS_CHIP_MAX else f"cards_{cards}" preset = _load_mirror_hardwares().get(hardware)
count = None if cards == _CARDS_CHIP_MAX else cards
registered = get_supported_card_counts()
if count is not None and count not in registered:
supported = ", ".join(f"cards_{n}" for n in sorted(registered)) or "(none)"
raise ValueError(
f"{count_source} in step {step_label!r} is not a registered cards_* mark; supported: {supported}",
)
known = _load_mirror_hardwares()
if count is None:
available = [n for n in sorted(registered) if f"{chip}_{n}" in known]
if not available:
raise ValueError(
f"{count_source} in step {step_label!r} has no CUDA count presets for {chip}",
)
count = max(available)
chosen = f"{chip}_{count}"
if chosen not in known:
raise ValueError(
f"{count_source} in step {step_label!r} has no preset {chosen!r}",
)
_log(f"{step_label}: {count_source} → {chosen!r}")
return chosen
def _resolve_mirror_hardware_name(hardware: Any, *, step_label: str) -> str | None:
"""Map ``mirror_hardwares`` to a ``ci_mirror_hardwares.yml`` preset, or None to skip.
The value is a preset name (``h100_2``). Unknown names fail later when the
registry is loaded. CUDA names are omitted when ``MIRROR_HW`` names a
different chip; NPU strings ignore ``MIRROR_HW``.
"""
if isinstance(hardware, bool):
raise ValueError(f"mirror_hardwares must not be a boolean in step {step_label!r}")
if isinstance(hardware, str):
name = hardware.strip()
elif isinstance(hardware, int):
name = str(hardware)
else:
raise ValueError(
f"mirror_hardwares must be a preset string in step {step_label!r}",
)
if not name:
raise ValueError(f"mirror_hardwares must be a non-empty string in step {step_label!r}")
selector = _get_mirror_hw_selector()
token = name.lower()
chip = next(
(c for c in _cuda_mirror_chips() if token == c or token.startswith(f"{c}_")),
None,
)
if selector and chip is not None and chip != selector:
_log(
f"skip {step_label}: preset {name!r} is {chip} hardware; MIRROR_HW={selector!r}",
)
return None
return name
def _expand_mirror_hardwares(step: dict[str, Any]) -> dict[str, Any] | None:
"""Replace ``mirror_hardwares`` (or infer it from ``-m``) with preset agents/plugins.
Explicit preset string: used as-is; pytest marks are ignored.
Omitted key: match H100/L4 (or ``MIRROR_HW``) in ``-m``, then compose
``{chip}_{n}`` from ``cards_n``. No match skips the step. Steps with no
SKU/cards and no ``mirror_hardwares`` are left unchanged (CPU jobs).
Returns None when the current ``MIRROR_HW`` selector does not apply.
"""
step_label = _get_step_label(step)
has_pool = step.get("agents") is not None or step.get("plugins") is not None or step.get("image") is not None
if "mirror_hardwares" not in step:
if has_pool:
return step
chips, cards = _read_pytest_marks(step.get("commands"))
if not chips and cards is None:
return step
preset_name = _compose_mirror_hardware_name(chips, cards, step_label=step_label)
else:
if has_pool:
raise ValueError(
f"step {step_label!r} sets mirror_hardwares together with agents/plugins/image; "
f"use mirror_hardwares only",
)
preset_name = _resolve_mirror_hardware_name(
step.get("mirror_hardwares"),
step_label=step_label,
)
if preset_name is None:
return None
preset = _load_mirror_hardwares().get(preset_name)
if preset is None: if preset is None:
known = ", ".join(sorted(_load_mirror_hardwares())) known = ", ".join(sorted(_load_mirror_hardwares()))
raise ValueError( raise ValueError(
f"unknown mirror_hardwares {preset_name!r} in step {step_label!r}; known: {known}", f"unknown mirror_hardwares {hardware!r} in step {_get_step_label(step)!r}; known: {known}",
)
if step.get("agents") is not None or step.get("plugins") is not None or step.get("image") is not None:
raise ValueError(
f"step {_get_step_label(step)!r} sets mirror_hardwares together with agents/plugins/image; "
"use mirror_hardwares only",
) )
expanded = copy.deepcopy(preset) expanded = copy.deepcopy(preset)
merged = {key: value for key, value in step.items() if key != "mirror_hardwares"} | expanded return {key: value for key, value in step.items() if key != "mirror_hardwares"} | expanded
# A scheduled build's HF_TOKEN may override a pod env entry with the same
# name. The preset injects the Kubernetes secret under a collision-proof
# alias; restore the conventional name after Buildkite has merged env.
if any(preset_name == chip or preset_name.startswith(f"{chip}_") for chip in _cuda_mirror_chips()):
commands = merged.get("commands")
if isinstance(commands, list):
merged["commands"] = [CUDA_HF_TOKEN_EXPORT, *commands]
elif commands is None:
merged["commands"] = [CUDA_HF_TOKEN_EXPORT]
else:
merged["commands"] = [CUDA_HF_TOKEN_EXPORT, commands]
# Preset retry (K8S_RETRY on l4_*) must not clobber a step that opted out.
if "retry" in step:
merged["retry"] = step["retry"]
return merged
def _match_source_file(changed_files: list[str], prefixes: list[str]) -> bool: def _match_source_file(changed_files: list[str], prefixes: list[str]) -> bool:
@@ -564,7 +309,7 @@ def _process_test_steps(
nested = step.get("steps") nested = step.get("steps")
if nested is not None: if nested is not None:
kept_nested = _process_test_steps(nested, changed_files) kept_nested = _process_test_steps(nested, changed_files)
if not kept_nested: if changed_files is not None and not kept_nested:
_log(f"omit empty group {_get_step_label(step)!r}") _log(f"omit empty group {_get_step_label(step)!r}")
continue continue
new_step = {key: value for key, value in step.items() if key != "source_file_dependencies"} new_step = {key: value for key, value in step.items() if key != "source_file_dependencies"}
@@ -572,11 +317,14 @@ def _process_test_steps(
processed.append(new_step) processed.append(new_step)
continue continue
leaf = {key: value for key, value in step.items() if key != "source_file_dependencies"} if deps is not None:
expanded = _expand_mirror_hardwares(leaf) processed.append(
if expanded is None: _expand_mirror_hardwares(
continue {key: value for key, value in step.items() if key != "source_file_dependencies"},
processed.append(expanded) ),
)
else:
processed.append(_expand_mirror_hardwares(step))
return processed return processed
@@ -602,10 +350,6 @@ def _render_test_pipeline(
e2e_only: bool = False, e2e_only: bool = False,
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Filter steps by PR diff and strip uploader-only ``source_file_dependencies`` metadata.""" """Filter steps by PR diff and strip uploader-only ``source_file_dependencies`` metadata."""
# Validate MIRROR_HW once up front. Per-step skip must not run for typos
# (e.g. b20o), or a pipeline can shrink to leftover CPU-only steps and
# still upload successfully.
_get_mirror_hw_selector()
steps = doc.get("steps") steps = doc.get("steps")
if not isinstance(steps, list): if not isinstance(steps, list):
return doc return doc
@@ -635,7 +379,10 @@ def _render_pipeline(
text = path.read_text(encoding="utf-8") text = path.read_text(encoding="utf-8")
ctx = resolve_ci_context_from_git() ctx = resolve_ci_context_from_git()
changed_files = None if force_all or e2e_only else ctx.changed_files if force_all or e2e_only:
changed_files = None
else:
changed_files = ctx.changed_files
doc = yaml.safe_load(text) doc = yaml.safe_load(text)
if not isinstance(doc, dict): if not isinstance(doc, dict):
+5 -5
View File
@@ -8,28 +8,28 @@ steps:
agents: agents:
queue: "cpu_queue_premerge" queue: "cpu_queue_premerge"
# L2 Test — PR ready label (diff-aware) or main+WEEKLY=1 (--e2e, full E2E group) # L2 Test — PR ready label (diff-aware) or main+NIGHTLY=1 (--e2e, full E2E group)
- label: "Upload Ready Pipeline" - label: "Upload Ready Pipeline"
depends_on: image-build depends_on: image-build
key: upload-ready-pipeline key: upload-ready-pipeline
commands: commands:
- | - |
EXTRA="" EXTRA=""
if [ "${WEEKLY:-}" = "1" ] && [ "${BUILDKITE_BRANCH}" = "main" ]; then if [ "${NIGHTLY:-}" = "1" ] && [ "${BUILDKITE_BRANCH}" = "main" ]; then
EXTRA="--e2e" EXTRA="--e2e"
fi fi
python3 .buildkite/common/scripts/upload_pipeline.py --upload $$EXTRA .buildkite/cuda/test-ready.yml python3 .buildkite/common/scripts/upload_pipeline.py --upload $$EXTRA .buildkite/cuda/test-ready.yml
agents: agents:
queue: "cpu_queue_premerge" queue: "cpu_queue_premerge"
# L3 Test — PR merge-test label (diff-aware) or main+WEEKLY=1 (--e2e, full E2E group) # L3 Test — PR merge-test label (diff-aware) or main+NIGHTLY=1 (--e2e, full E2E group)
- label: "Upload Merge Pipeline" - label: "Upload Merge Pipeline"
depends_on: image-build depends_on: image-build
key: upload-merge-pipeline key: upload-merge-pipeline
commands: commands:
- | - |
EXTRA="" EXTRA=""
if [ "${WEEKLY:-}" = "1" ] && [ "${BUILDKITE_BRANCH}" = "main" ]; then if [ "${NIGHTLY:-}" = "1" ] && [ "${BUILDKITE_BRANCH}" = "main" ]; then
EXTRA="--e2e" EXTRA="--e2e"
fi fi
python3 .buildkite/common/scripts/upload_pipeline.py --upload $$EXTRA .buildkite/cuda/test-merge.yml python3 .buildkite/common/scripts/upload_pipeline.py --upload $$EXTRA .buildkite/cuda/test-merge.yml
@@ -45,7 +45,7 @@ steps:
agents: agents:
queue: "cpu_queue_premerge" queue: "cpu_queue_premerge"
# L5 Test — main+WEEKLY=1 / NON_CRITICAL=1 (scheduled), or PR with weekly-test label # L5 Test — main+WEEKLY=1 (scheduled), or PR with weekly-test label
- label: "Upload Weekly Pipeline" - label: "Upload Weekly Pipeline"
depends_on: image-build depends_on: image-build
key: upload-weekly-pipeline key: upload-weekly-pipeline
+92 -130
View File
@@ -1,8 +1,6 @@
# CUDA CI L3 (`advanced_model` e2e) plus L1 CPU Simple Test (`core_model and cpu`).
# Uploaded by Upload Merge Pipeline: PR label `merge-test`, or post-merge on main.
env: env:
VLLM_WORKER_MULTIPROC_METHOD: spawn VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_USE_FLASHINFER_MOE_FP16: 0
HF_HUB_DOWNLOAD_TIMEOUT: 300 HF_HUB_DOWNLOAD_TIMEOUT: 300
HF_HUB_ETAG_TIMEOUT: 60 HF_HUB_ETAG_TIMEOUT: 60
@@ -11,27 +9,23 @@ steps:
depends_on: upload-merge-pipeline depends_on: upload-merge-pipeline
steps: steps:
- label: "Simple · Model Executor Test" - label: "Simple · Model Executor Test"
timeout_in_minutes: 20
commands: commands:
- "pytest -sv tests/model_executor -m 'core_model and cpu'" - "timeout 20m pytest -sv tests/model_executor -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- label: "Simple · Diffusion Test" - label: "Simple · Diffusion Test"
timeout_in_minutes: 20
commands: commands:
- "pytest -sv tests/diffusion -m 'core_model and cpu'" - "timeout 20m pytest -sv tests/diffusion -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- label: "Simple · Engine&Entrypoints Test" - label: "Simple · Engine&Entrypoints Test"
timeout_in_minutes: 20
commands: commands:
- "pytest -sv tests/entrypoints tests/engine -m 'core_model and cpu'" - "timeout 20m pytest -sv tests/entrypoints tests/engine -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- label: "Simple · Other Test" - label: "Simple · Other Test"
timeout_in_minutes: 20
commands: commands:
- "pytest -sv tests/ -m 'core_model and cpu' --ignore=tests/diffusion --ignore=tests/model_executor --ignore=tests/entrypoints --ignore=tests/engine" - "timeout 20m pytest -sv tests/ -m 'core_model and cpu' --ignore=tests/diffusion --ignore=tests/model_executor --ignore=tests/entrypoints --ignore=tests/engine --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- group: ":card_index_dividers: Diffusion Test" - group: ":card_index_dividers: Diffusion Test"
@@ -79,99 +73,73 @@ steps:
steps: steps:
- label: "TTS · Qwen3-TTS CustomVoice Test" - label: "TTS · Qwen3-TTS CustomVoice Test"
source_file_dependencies: source_file_dependencies:
- tests/e2e/features/prefill_phase/
- vllm_omni/worker/gpu_model_runner.py
- tests/helpers/media.py
- tests/e2e/online_serving/test_qwen3_tts_customvoice.py - tests/e2e/online_serving/test_qwen3_tts_customvoice.py
- tests/e2e/offline_inference/test_qwen3_tts_customvoice.py - tests/e2e/offline_inference/test_qwen3_tts_customvoice.py
- vllm_omni/model_executor/models/qwen3_tts/ - vllm_omni/model_executor/models/qwen3_tts/
- vllm_omni/model_executor/models/common/qwen3_code_predictor.py
- vllm_omni/model_executor/models/common/snake_activation.py
- vllm_omni/model_executor/models/common/alias_free_activation.py
- vllm_omni/model_executor/stage_input_processors/qwen3_tts.py - vllm_omni/model_executor/stage_input_processors/qwen3_tts.py
- vllm_omni/deploy/qwen3_tts.yaml - vllm_omni/deploy/qwen3_tts.yaml
- vllm_omni/entrypoints/openai/serving_speech.py
- vllm_omni/entrypoints/openai/tts_adapters/
- vllm_omni/engine/stage_pool.py
commands: commands:
- | - |
timeout 40m bash -c " timeout 40m bash -c "
export VLLM_LOGGING_LEVEL=DEBUG export VLLM_LOGGING_LEVEL=DEBUG
export VLLM_ALLOW_LONG_MAX_MODEL_LEN=1 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 tests/e2e/features/prefill_phase/test_chunked_prefill_tail.py -m 'advanced_model and cuda' --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 and cuda' --run-level 'advanced_model'
" "
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- label: "TTS · Qwen3-TTS Base Test" - label: "TTS · Qwen3-TTS Base Test"
source_file_dependencies: source_file_dependencies:
- tests/helpers/media.py
- tests/e2e/online_serving/test_qwen3_tts_base.py - tests/e2e/online_serving/test_qwen3_tts_base.py
- tests/e2e/offline_inference/test_qwen3_tts_base.py - tests/e2e/offline_inference/test_qwen3_tts_base.py
- vllm_omni/model_executor/models/qwen3_tts/ - vllm_omni/model_executor/models/qwen3_tts/
- vllm_omni/model_executor/models/common/qwen3_code_predictor.py
- vllm_omni/model_executor/models/common/snake_activation.py
- vllm_omni/model_executor/models/common/alias_free_activation.py
- vllm_omni/model_executor/stage_input_processors/qwen3_tts.py - vllm_omni/model_executor/stage_input_processors/qwen3_tts.py
- vllm_omni/deploy/qwen3_tts.yaml - vllm_omni/deploy/qwen3_tts.yaml
- vllm_omni/entrypoints/openai/serving_speech.py
- vllm_omni/entrypoints/openai/tts_adapters/
- vllm_omni/engine/stage_pool.py
- .buildkite/common/scripts/run_cov_split.sh
- pyproject.toml
# 40 -> 60: the coverage split runs the model twice.
timeout_in_minutes: 60
commands: commands:
- | - |
export VLLM_LOGGING_LEVEL=DEBUG timeout 40m bash -c "
export VLLM_ALLOW_LONG_MAX_MODEL_LEN=1 export VLLM_LOGGING_LEVEL=DEBUG
.buildkite/common/scripts/run_cov_split.sh \ export VLLM_ALLOW_LONG_MAX_MODEL_LEN=1
--model-id qwen3_tts \ pytest -s -v tests/e2e/online_serving/test_qwen3_tts_base.py tests/e2e/offline_inference/test_qwen3_tts_base.py -m 'advanced_model and cuda' --run-level 'advanced_model'
--offline tests/e2e/offline_inference/test_qwen3_tts_base.py \ "
--online tests/e2e/online_serving/test_qwen3_tts_base.py \ mirror_hardwares: l4_1
-- -m 'advanced_model and cuda' --run-level 'advanced_model'
- label: "TTS · Higgs-Audio-V3 Test"
source_file_dependencies:
- tests/e2e/online_serving/test_higgs_audio_v3.py
- vllm_omni/model_executor/models/higgs_audio_v3/
- vllm_omni/model_executor/stage_input_processors/higgs_audio_v3.py
- vllm_omni/deploy/higgs_multimodal_qwen3.yaml
commands:
- |
timeout 20m bash -c "
export VLLM_WORKER_MULTIPROC_METHOD=spawn
export VLLM_USE_DEEP_GEMM=0
export VLLM_MOE_USE_DEEP_GEMM=0
pytest -s -v tests/e2e/online_serving/test_higgs_audio_v3.py -m 'advanced_model' --run-level 'advanced_model'
"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- label: "Omni · Qwen3-Omni Test" - label: "Omni · Qwen3-Omni Test"
source_file_dependencies: source_file_dependencies:
- tests/e2e/offline_inference/test_qwen3_omni.py - tests/e2e/offline_inference/test_qwen3_omni.py
- tests/e2e/offline_inference/test_qwen3_omni_colocate_async.py
- tests/e2e/online_serving/test_qwen3_omni.py - tests/e2e/online_serving/test_qwen3_omni.py
- tests/helpers/runtime.py
- vllm_omni/entrypoints/openai/serving_chat.py
- vllm_omni/model_executor/models/qwen3_omni/ - vllm_omni/model_executor/models/qwen3_omni/
- vllm_omni/model_executor/models/common/qwen3_code_predictor.py
- vllm_omni/model_executor/models/common/snake_activation.py
- vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py
- vllm_omni/model_executor/models/qwen3_tts/cuda_graph_decoder_wrapper.py
- vllm_omni/model_executor/stage_input_processors/qwen3_omni.py - vllm_omni/model_executor/stage_input_processors/qwen3_omni.py
- vllm_omni/deploy/qwen3_omni_moe.yaml - vllm_omni/deploy/qwen3_omni_moe.yaml
- vllm_omni/deploy/qwen3_omni_moe_thinking.yaml
timeout_in_minutes: 50 timeout_in_minutes: 50
commands: commands:
- pytest -s -v tests/e2e/offline_inference/test_qwen3_omni.py tests/e2e/online_serving/test_qwen3_omni.py tests/e2e/offline_inference/test_qwen3_omni_colocate_async.py -m 'advanced_model and cuda' --run-level 'advanced_model' - 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'
mirror_hardwares: h100_2 mirror_hardwares: h100_2
- label: "Omni · MiniCPM-o 4.5 Test" - label: "Omni · MiniCPM-o 4.5 Test"
source_file_dependencies: source_file_dependencies:
- tests/e2e/offline_inference/test_minicpmo_4_5.py - tests/e2e/offline_inference/test_minicpmo_4_5.py
- tests/e2e/online_serving/test_minicpmo_4_5.py - tests/e2e/online_serving/test_minicpmo_4_5.py
- vllm_omni/transformers_utils/chat_templates/minicpmo45_native.jinja
- vllm_omni/model_executor/models/minicpmo_4_5/
- vllm_omni/model_executor/models/step_audio2/
- vllm_omni/model_executor/models/cosyvoice3/code2wav_core/
- vllm_omni/model_executor/models/cosyvoice3/utils.py
- vllm_omni/model_executor/stage_input_processors/minicpmo_4_5_omni.py
- vllm_omni/deploy/minicpmo_4_5.yaml - vllm_omni/deploy/minicpmo_4_5.yaml
timeout_in_minutes: 50 timeout_in_minutes: 50
commands: commands:
- pytest -s -v tests/e2e/offline_inference/test_minicpmo_4_5.py tests/e2e/online_serving/test_minicpmo_4_5.py -m 'advanced_model and cuda' --run-level 'advanced_model' - pytest -s -v tests/e2e/offline_inference/test_minicpmo_4_5.py tests/e2e/online_serving/test_minicpmo_4_5.py -m 'advanced_model and cuda' --run-level 'advanced_model'
# h100_2, not h100_1: advanced_model asserts transcribe every audio mirror_hardwares: h100_1
# response with Whisper, and tests/helpers/media.py requires at least
# 16 GiB free VRAM on a device to run Whisper on GPU. When the model
# server leaves insufficient memory, Whisper falls back to CPU:
# ~8-10 min per clip vs ~30 s, which overruns the 50-minute job timeout
# (build 2956). h100_2 guarantees a second GPU with ample free VRAM.
mirror_hardwares: h100_2
- label: "Omni · MiniCPM-o 4.5 Duplex Test" - label: "Omni · MiniCPM-o 4.5 Duplex Test"
source_file_dependencies: source_file_dependencies:
@@ -182,51 +150,63 @@ steps:
- tests/e2e/online_serving/test_minicpmo_realtime_duplex_drivers.py - tests/e2e/online_serving/test_minicpmo_realtime_duplex_drivers.py
- tests/e2e/online_serving/helpers/minicpmo_realtime_duplex_scenarios.py - tests/e2e/online_serving/helpers/minicpmo_realtime_duplex_scenarios.py
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_multi_session.py - tests/e2e/online_serving/run_minicpmo_realtime_duplex_multi_session.py
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_server_vad.py
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_soft_interrupt.py - tests/e2e/online_serving/run_minicpmo_realtime_duplex_soft_interrupt.py
- vllm_omni/deploy/minicpmo_4_5.yaml
- tests/e2e/online_serving/helpers/minicpmo_4_5_duplex.py - tests/e2e/online_serving/helpers/minicpmo_4_5_duplex.py
- vllm_omni/engine/duplex/ - vllm_omni/deploy/minicpmo_4_5_duplex.yaml
- vllm_omni/entrypoints/duplex/
# Public duplex client library + its unit suites: client-side
# changes must re-run the duplex e2e group. The live client probe
# (test_duplex_client_live.py, single GPU, core tier) runs in the
# ready pipeline's duplex group.
- vllm_omni/clients/
- tests/clients/
- vllm_omni/entrypoints/duplex_request_client.py
- vllm_omni/outputs/duplex.py
- vllm_omni/model_executor/duplex_sampling.py
- tests/engine/duplex/
- tests/entrypoints/duplex/
- tests/model_executor/models/minicpmo_4_5/duplex/
# Legacy Realtime probe client + video stacking used by the e2e drivers.
- vllm_omni/experimental/fullduplex/ - vllm_omni/experimental/fullduplex/
- vllm_omni/model_executor/models/minicpmo_4_5/ - vllm_omni/model_executor/models/minicpmo_4_5/
- vllm_omni/model_executor/models/step_audio2/
- vllm_omni/model_executor/models/cosyvoice3/code2wav_core/
- vllm_omni/model_executor/models/cosyvoice3/utils.py
- vllm_omni/model_executor/stage_input_processors/minicpmo_4_5_omni.py - vllm_omni/model_executor/stage_input_processors/minicpmo_4_5_omni.py
- vllm_omni/core/sched/ - vllm_omni/core/sched/
- vllm_omni/outputs/multimodal_accumulation.py - vllm_omni/outputs/multimodal_accumulation.py
- vllm_omni/engine/stage_init_utils.py - vllm_omni/engine/stage_init_utils.py
- vllm_omni/engine/async_omni_engine.py - vllm_omni/engine/async_omni_engine.py
- vllm_omni/engine/orchestrator.py
- vllm_omni/entrypoints/cli/serve.py - vllm_omni/entrypoints/cli/serve.py
- vllm_omni/entrypoints/openai/api_server.py
- vllm_omni/entrypoints/openai/duplex_capability.py
- vllm_omni/config/stage_config.py
timeout_in_minutes: 50 timeout_in_minutes: 50
commands: commands:
- pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex.py -m 'advanced_model and cuda' --run-level 'advanced_model' - pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex.py -m 'advanced_model and cuda' --run-level 'advanced_model'
mirror_hardwares: h100_1 mirror_hardwares: h100_1
- label: "TTS · VoxCPM2 Test"
source_file_dependencies:
- tests/e2e/offline_inference/test_voxcpm2_tts.py
- tests/e2e/online_serving/test_voxcpm2_tts.py
- vllm_omni/model_executor/models/voxcpm2/
- vllm_omni/deploy/voxcpm2.yaml
timeout_in_minutes: 30
commands:
- |
timeout 30m bash -c "
export VLLM_LOGGING_LEVEL=DEBUG
export VLLM_WORKER_MULTIPROC_METHOD=spawn
pytest -s -v tests/e2e/offline_inference/test_voxcpm2_tts.py tests/e2e/online_serving/test_voxcpm2_tts.py -m 'advanced_model and cuda' --run-level 'advanced_model'
"
mirror_hardwares: l4_1
- label: "Diffusion · Qwen Image Edit Test"
source_file_dependencies:
- tests/e2e/online_serving/test_qwen_image_edit.py
- vllm_omni/diffusion/models/qwen_image/
timeout_in_minutes: 20
commands:
- pytest -s -v tests/e2e/online_serving/test_qwen_image_edit.py -m 'advanced_model and cuda' --run-level 'advanced_model'
mirror_hardwares: h100_1
- label: "Diffusion · Bagel Test"
source_file_dependencies:
- tests/e2e/offline_inference/test_bagel.py
- tests/e2e/online_serving/test_bagel.py
- vllm_omni/model_executor/models/bagel/
- vllm_omni/diffusion/models/bagel/
- vllm_omni/model_executor/stage_input_processors/bagel.py
- vllm_omni/deploy/bagel.yaml
timeout_in_minutes: 20
commands:
- pytest -s -v tests/e2e/offline_inference/test_bagel.py tests/e2e/online_serving/test_bagel.py -m 'advanced_model and cuda' --run-level 'advanced_model'
mirror_hardwares: h100_1
- label: "Diffusion · Qwen Image Test" - label: "Diffusion · Qwen Image Test"
source_file_dependencies: source_file_dependencies:
- tests/e2e/online_serving/test_qwen_image.py - tests/e2e/online_serving/test_qwen_image.py
- vllm_omni/diffusion/models/qwen_image/ - vllm_omni/diffusion/models/qwen_image/
- vllm_omni/diffusion/models/dmd2/
- vllm_omni/diffusion/distributed/autoencoders/autoencoder_kl_qwenimage.py
commands: commands:
- | - |
timeout 40m bash -c ' timeout 40m bash -c '
@@ -235,12 +215,23 @@ steps:
' '
mirror_hardwares: h100_1 mirror_hardwares: h100_1
- label: "Diffusion · Qwen Image Layered Test"
source_file_dependencies:
- tests/e2e/online_serving/test_qwen_image_layered.py
- vllm_omni/diffusion/models/qwen_image/
commands:
- |
timeout 40m bash -c "
export VLLM_IMAGE_FETCH_TIMEOUT=60
pytest -s -v tests/e2e/online_serving/test_qwen_image_layered.py -m 'advanced_model and cuda' --run-level 'advanced_model'
"
mirror_hardwares: h100_1
- label: "Diffusion · Wan22 Test" - label: "Diffusion · Wan22 Test"
source_file_dependencies: source_file_dependencies:
- tests/e2e/offline_inference/test_wan22_t2v.py - tests/e2e/offline_inference/test_wan22_t2v.py
- tests/e2e/online_serving/test_wan22_t2v.py - tests/e2e/online_serving/test_wan22_t2v.py
- vllm_omni/diffusion/models/wan2_2/ - vllm_omni/diffusion/models/wan2_2/
- vllm_omni/diffusion/models/dmd2/
commands: commands:
- | - |
timeout 40m bash -c " timeout 40m bash -c "
@@ -249,49 +240,20 @@ steps:
" "
mirror_hardwares: h100_1 mirror_hardwares: h100_1
- label: "Diffusion · HunyuanVideo-1.5 Test" - label: "Diffusion · Z Image Test"
source_file_dependencies: source_file_dependencies:
- tests/e2e/online_serving/test_hunyuan_video_15_expansion.py - tests/e2e/offline_inference/test_z_image.py
- vllm_omni/diffusion/models/hunyuan_video/ - tests/e2e/online_serving/test_z_image.py
- vllm_omni/diffusion/models/z_image/
timeout_in_minutes: 30
commands: commands:
- | - pytest -s -v tests/e2e/offline_inference/test_z_image.py tests/e2e/online_serving/test_z_image.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model'
timeout 40m bash -c " mirror_hardwares: l4_1
export VLLM_IMAGE_FETCH_TIMEOUT=60 - label: "Diffusion · FLUX.2 Klein Test"
pytest -s -v tests/e2e/online_serving/test_hunyuan_video_15_expansion.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model'
"
mirror_hardwares: h100_1
- label: "Diffusion · MiniMax-H3 L3 DLO DP2 and FastH3"
source_file_dependencies: source_file_dependencies:
- tests/e2e/online_serving/minimax_h3/ - tests/e2e/offline_inference/test_flux2_klein.py
- tests/helpers/assertions.py - vllm_omni/diffusion/models/flux2_klein/
- tests/helpers/mark.py timeout_in_minutes: 30
- tests/helpers/media.py
- tests/helpers/runtime.py
- vllm_omni/diffusion/models/minimax_h3/
- vllm_omni/diffusion/lora/
- vllm_omni/diffusion/offloader/
- vllm_omni/diffusion/distributed/
- vllm_omni/entrypoints/openai/
# Keep four server lifecycles in one Buildkite job, but never in one
# pytest process. DLO uses multiprocessing shared-memory queues; the
# parent's resource_tracker can retain a stale /psm_* entry during
# teardown and strand the next Ref2VA collective.
# A per-file process plus a hard timeout also makes a regression fail
# instead of leaving the merge queue stuck indefinitely.
timeout_in_minutes: 150
commands: commands:
- | - pytest -s -v tests/e2e/offline_inference/test_flux2_klein.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model'
set -eu mirror_hardwares: l4_1
export VLLM_IMAGE_FETCH_TIMEOUT=60
if [ -f vllm_omni/diffusion/models/minimax_h3/lora.py ]; then
export VLLM_TEST_MINIMAX_H3_TURBO_LORA=$(python3 -c 'from huggingface_hub import hf_hub_download; print(hf_hub_download(repo_id="lightx2v/Minimax-h3-Turbo", filename="minimax_h3_fl2v_turbo_4step_v1.0_768p_bf16.safetensors"))')
fi
if [ -f vllm_omni/diffusion/models/minimax_h3/fasth3.py ]; then
export VLLM_TEST_MINIMAX_H3_FASTH3_LORA=$(python3 -c 'from huggingface_hub import hf_hub_download; print(hf_hub_download(repo_id="FastVideo/FastVideo-FastH3-4-step-Preview-v1-LoRA", filename="dense-datafree/adapter_model.safetensors"))')
fi
timeout 35m pytest -s -v tests/e2e/online_serving/minimax_h3/test_minimax_h3_dlo_dp2_fl2va.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model'
timeout 35m pytest -s -v tests/e2e/online_serving/minimax_h3/test_minimax_h3_dlo_dp2_ref2va.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model'
timeout 35m pytest -s -v tests/e2e/online_serving/minimax_h3/test_minimax_h3_turbo_lora.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model'
timeout 35m pytest -s -v tests/e2e/online_serving/minimax_h3/test_minimax_h3_fasth3_lora.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model'
mirror_hardwares: h100_2
+235 -163
View File
@@ -1,9 +1,6 @@
# CUDA CI L4 (`full_model`): nightly and release share this file.
# Uploaded by Upload Nightly Pipeline: NIGHTLY=1, or PR labels (nightly-test / omni-test / ...).
# Hardware: omit mirror_hardwares to compose from -m SKU + cards_n; or set a preset string to force.
env: env:
VLLM_WORKER_MULTIPROC_METHOD: spawn VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_USE_FLASHINFER_MOE_FP16: 0
HF_HUB_DOWNLOAD_TIMEOUT: 300 HF_HUB_DOWNLOAD_TIMEOUT: 300
HF_HUB_ETAG_TIMEOUT: 60 HF_HUB_ETAG_TIMEOUT: 60
@@ -15,33 +12,29 @@ steps:
if: build.env("NIGHTLY") == "1" || build.pull_request.labels includes "nightly-test" || build.pull_request.labels includes "omni-test" if: build.env("NIGHTLY") == "1" || build.pull_request.labels includes "nightly-test" || build.pull_request.labels includes "omni-test"
steps: steps:
- label: ":full_moon: Omni · Function Test with H100 · Single-GPU" - label: ":full_moon: Omni · Function Test with H100"
timeout_in_minutes: 120 timeout_in_minutes: 120
commands: commands:
- pytest -sv tests/e2e --run-level "full_model" -m "full_model and H100 and B200 and omni and cards_1" --ignore=tests/e2e/accuracy --ignore=tests/e2e/online_serving/test_minicpmo_4_5_duplex_expansion.py - pytest -sv tests/e2e --run-level "full_model" -m "full_model and H100 and omni" --ignore=tests/e2e/accuracy --ignore=tests/e2e/online_serving/test_qwen3_omni_multi_replicas.py --ignore=tests/e2e/online_serving/test_minicpmo_4_5_duplex_expansion.py
mirror_hardwares: h100_2
- label: ":full_moon: Omni · Function Test with H100 · 2-GPU"
timeout_in_minutes: 90
commands:
- pytest -sv tests/e2e --run-level "full_model" -m "full_model and H100 and B200 and omni and cards_2" --ignore=tests/e2e/accuracy --ignore=tests/e2e/online_serving/test_qwen3_omni_multi_replicas.py
- label: ":full_moon: Omni · MiniCPM-o 4.5 Duplex Test" - label: ":full_moon: Omni · MiniCPM-o 4.5 Duplex Test"
timeout_in_minutes: 50 timeout_in_minutes: 50
commands: commands:
- pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex_expansion.py -m "full_model and cuda and H100 and omni and cards_1" --run-level "full_model" - pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex_expansion.py -m "full_model and cuda and H100 and omni" --run-level "full_model"
mirror_hardwares: h100_1 mirror_hardwares: h100_1
- label: ":full_moon: Omni · Doc Test with L4 · 4-GPU" - label: ":full_moon: Omni · Doc Test with L4"
timeout_in_minutes: 90 timeout_in_minutes: 90
commands: commands:
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1" - export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
- pytest -s -v tests/examples/ -m "full_model and omni and L4 and cards_4" --run-level "full_model" - pytest -s -v tests/examples/ -m "full_model and omni and L4" --run-level "full_model"
mirror_hardwares: l4_4 mirror_hardwares: l4_4
- label: ":full_moon: Omni · Doc Test with H100 · 2-GPU" - label: ":full_moon: Omni · Doc Test with H100"
timeout_in_minutes: 90 timeout_in_minutes: 90
commands: commands:
- pytest -s -v tests/examples/ -m "full_model and omni and H100 and cards_2" --run-level "full_model" - pytest -s -v tests/examples/ -m "full_model and omni and H100" --run-level "full_model"
mirror_hardwares: h100_2 mirror_hardwares: h100_2
- label: ":full_moon: Omni · Accuracy Test" - label: ":full_moon: Omni · Accuracy Test"
@@ -90,12 +83,38 @@ steps:
- export BENCHMARK_DIR=tests/dfx/perf/results - export BENCHMARK_DIR=tests/dfx/perf/results
- | - |
set +e set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_async_chunk.json -m "H100 and full_model" pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_async_chunk.json
EXIT=$$? EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json" buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT exit $$EXIT
mirror_hardwares: h100_2 mirror_hardwares: h100_2
- label: ":full_moon: Omni · Perf Test · vLLM Text"
key: nightly-omni-performance-vllm-text
timeout_in_minutes: 180
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_vllm_text.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
mirror_hardwares: h100_2
- label: ":full_moon: Omni · Perf Test · Multi-Replica"
key: nightly-omni-performance-multi-replicas
timeout_in_minutes: 180
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_multi_replicas.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
mirror_hardwares: h100_3
- label: ":full_moon: Omni · MiniCPM-o 4.5 · Perf Test" - label: ":full_moon: Omni · MiniCPM-o 4.5 · Perf Test"
key: nightly-omni-performance-minicpmo-4-5 key: nightly-omni-performance-minicpmo-4-5
timeout_in_minutes: 180 timeout_in_minutes: 180
@@ -140,36 +159,63 @@ steps:
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1" - export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
- export VLLM_USE_DEEP_GEMM="0" - export VLLM_USE_DEEP_GEMM="0"
- export VLLM_MOE_USE_DEEP_GEMM="0" - export VLLM_MOE_USE_DEEP_GEMM="0"
- pytest -s -v tests/e2e/ -m "full_model and L4 and tts" --run-level "full_model" --ignore=tests/e2e/accuracy - pytest -s -v tests/e2e/ -m "full_model and L4 and tts" --run-level "full_model" --ignore=tests/e2e/accuracy --ignore=tests/e2e/online_serving/test_voxcpm2_tts_expansion.py
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- label: ":full_moon: TTS · Perf Test · Single-GPU" - label: ":full_moon: TTS · Accuracy Test · VoxCPM2"
key: nightly-tts-performance-single timeout_in_minutes: 30
commands:
- |
timeout 30m 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_voxcpm2_tts_expansion.py -m 'full_model and cuda and tts' --run-level 'full_model'
"
mirror_hardwares: l4_1
- label: ":full_moon: TTS · Perf Test"
key: nightly-tts-performance
timeout_in_minutes: 180 timeout_in_minutes: 180
commands: commands:
- export BENCHMARK_DIR=tests/dfx/perf/results - export BENCHMARK_DIR=tests/dfx/perf/results
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1" - export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
- | - |
set +e set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_tts.json -m "H100 and cards_1" pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_tts.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
mirror_hardwares: h100_2
- label: ":full_moon: TTS · VoxCPM2 · Perf Test"
key: nightly-tts-performance-voxcpm2
timeout_in_minutes: 180
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_voxcpm2.json
EXIT=$$? EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json" buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT exit $$EXIT
mirror_hardwares: h100_1 mirror_hardwares: h100_1
- label: ":full_moon: TTS · Perf Test · 2-GPU" - label: ":full_moon: TTS · Higgs-Audio-V3 · Perf Test"
key: nightly-tts-performance-2gpu key: nightly-tts-performance-higgs-audio-v3
timeout_in_minutes: 180 timeout_in_minutes: 180
commands: commands:
- export BENCHMARK_DIR=tests/dfx/perf/results - export BENCHMARK_DIR=tests/dfx/perf/results
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1" - export VLLM_USE_DEEP_GEMM="0"
- export VLLM_MOE_USE_DEEP_GEMM="0"
- | - |
set +e set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_tts.json -m "H100 and cards_2" pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_higgs_audio_v3.json
EXIT=$$? EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json" buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT exit $$EXIT
mirror_hardwares: h100_2 mirror_hardwares: h100_1
- group: ":card_index_dividers: Tiny Model Tests [Multi-GPU]" - group: ":card_index_dividers: Tiny Model Tests [Multi-GPU]"
key: nightly-tiny-model-test-group key: nightly-tiny-model-test-group
@@ -179,7 +225,7 @@ steps:
build.pull_request.labels includes "nightly-test" build.pull_request.labels includes "nightly-test"
steps: steps:
- label: ":full_moon: Tiny Model Tests · Diffusion (Multi-GPU accelerations) · 2-GPU" - label: ":full_moon: Tiny Model Tests · Diffusion (Multi-GPU accelerations)"
timeout_in_minutes: 30 timeout_in_minutes: 30
# NOTE: Single-GPU (core_model) tiny model tests run on the ready label, # NOTE: Single-GPU (core_model) tiny model tests run on the ready label,
# so nightly only adds the multi-GPU parallelism configurations. # so nightly only adds the multi-GPU parallelism configurations.
@@ -189,14 +235,7 @@ steps:
# for the CI, since these tests should pass irrespective of whether or not we use # for the CI, since these tests should pass irrespective of whether or not we use
# the full weights or not. # the full weights or not.
commands: commands:
- pytest -sv tests/model_tests/diffusion -m 'full_model and cuda and cards_2' --run-level "core_model" - timeout 25m pytest -sv tests/model_tests/diffusion -m 'full_model and cuda' --run-level "core_model"
mirror_hardwares: l4_2
- label: ":full_moon: Tiny Model Tests · Diffusion (Multi-GPU accelerations) · 4-GPU"
timeout_in_minutes: 30
# CFG+TP extra_test_groups request 2×2=4 devices (see get_required_device_count).
commands:
- pytest -sv tests/model_tests/diffusion -m 'full_model and cuda and cards_4' --run-level "core_model"
mirror_hardwares: l4_4 mirror_hardwares: l4_4
- group: ":card_index_dividers: Diffusion X2I(&A&T) Model Test" - group: ":card_index_dividers: Diffusion X2I(&A&T) Model Test"
@@ -207,6 +246,8 @@ steps:
build.pull_request.labels includes "nightly-test" || build.pull_request.labels includes "nightly-test" ||
build.pull_request.labels includes "diffusion-x2iat-test" build.pull_request.labels includes "diffusion-x2iat-test"
steps: steps:
# Use explicit file shards so single-card cases do not reserve 2+ GPUs,
# and Bagel feature coverage is not duplicated via a broad tests/e2e sweep.
- label: ":full_moon: Diffusion X2I(&A&T) · Function Test with H100 · Single-GPU" - label: ":full_moon: Diffusion X2I(&A&T) · Function Test with H100 · Single-GPU"
timeout_in_minutes: 120 timeout_in_minutes: 120
@@ -214,35 +255,66 @@ steps:
- >- - >-
pytest -sv pytest -sv
tests/e2e/online_serving/test_qwen_image_expansion.py tests/e2e/online_serving/test_qwen_image_expansion.py
-m "full_model and diffusion and H100 and cards_1" tests/e2e/online_serving/test_qwen_image_edit_expansion.py
tests/e2e/online_serving/test_qwen_image_layered_expansion.py
tests/e2e/online_serving/test_boogu_image.py
tests/e2e/online_serving/test_boogu_image_edit.py
tests/e2e/online_serving/test_boogu_image_expansion.py
tests/e2e/online_serving/test_boogu_image_edit_expansion.py
tests/e2e/online_serving/test_bagel_expansion.py
tests/e2e/online_serving/test_krea2_expansion.py
tests/e2e/offline_inference/test_krea2_expansion.py
-m "full_model and diffusion and H100 and not distributed_cuda"
--run-level "full_model" --run-level "full_model"
mirror_hardwares: h100_1 mirror_hardwares: h100_1
- label: ":full_moon: Diffusion X2I(&A&T) · Function Test with H100 · 2-GPU" - label: ":full_moon: Diffusion X2I(&A&T) · Function Test with H100 · Multi-GPU Qwen-Image"
timeout_in_minutes: 120 timeout_in_minutes: 120
commands: commands:
- >- - >-
pytest -sv pytest -sv
tests/e2e/online_serving/test_qwen_image_expansion.py tests/e2e/online_serving/test_qwen_image_expansion.py
-m "full_model and diffusion and H100 and cards_2" tests/e2e/online_serving/test_qwen_image_edit_expansion.py
tests/e2e/offline_inference/test_krea2_expansion.py
-m "full_model and diffusion and H100 and distributed_cuda"
--run-level "full_model" --run-level "full_model"
mirror_hardwares: h100_2 mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2I(&A&T) · Boogu Edit CFG · 3-GPU" - label: ":full_moon: Diffusion X2I(&A&T) · Function Test with H100 · Multi-GPU Layered/LongCat/FLUX"
timeout_in_minutes: 90
commands:
- >-
pytest -sv
tests/e2e/online_serving/test_qwen_image_layered_expansion.py
-m "full_model and diffusion and H100 and distributed_cuda"
--run-level "full_model"
mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2I(&A&T) · Function Test with H100 · Multi-GPU Bagel"
timeout_in_minutes: 120 timeout_in_minutes: 120
commands: commands:
- >- - >-
pytest -sv pytest -sv
tests/e2e/online_serving/test_boogu_image_edit_expansion.py tests/e2e/online_serving/test_bagel_expansion.py
-k "test_double_guidance_cfg_parallel and cfg_parallel_3_full" -m "full_model and diffusion and H100 and distributed_cuda"
-m "slow and diffusion and cuda and H100 and cards_3"
--run-level "full_model" --run-level "full_model"
mirror_hardwares: h100_3 mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2I(&A&T) · Function Test with L4 · 4-GPU" - label: ":full_moon: Diffusion X2I(&A&T) · GLM-Image Function Test with H100"
timeout_in_minutes: 120 timeout_in_minutes: 120
commands: commands:
- pytest -sv tests/e2e/ -k "not test_wan and not hunyuan" -m "full_model and diffusion and L4 and cards_4" --run-level "full_model" --ignore=tests/e2e/accuracy - >-
pytest -sv
tests/e2e/online_serving/test_glm_image_expansion.py
tests/e2e/offline_inference/test_glm_image_autoround_w4a16_expansion.py
-m "full_model and diffusion and H100"
--run-level "full_model"
mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2I(&A&T) · Function Test with L4"
timeout_in_minutes: 120
commands:
- pytest -sv tests/e2e/ -k "not test_wan and not test_bagel_expansion and not hunyuan" -m "full_model and diffusion and L4" --run-level "full_model" --ignore=tests/e2e/accuracy
mirror_hardwares: l4_4 mirror_hardwares: l4_4
- label: ":full_moon: Diffusion X2I(&A&T) · Doc Test" - label: ":full_moon: Diffusion X2I(&A&T) · Doc Test"
@@ -255,7 +327,7 @@ steps:
timeout_in_minutes: 180 timeout_in_minutes: 180
commands: commands:
- export VLLM_HTTP_TIMEOUT_KEEP_ALIVE=120 - export VLLM_HTTP_TIMEOUT_KEEP_ALIVE=120
- pytest -s -v tests/e2e/accuracy/test_qwen_image.py --run-level full_model - pytest -s -v tests/e2e/accuracy/test_qwen_image*.py --run-level full_model
- pytest -s -v tests/e2e/accuracy/test_diffusers_backend_similarity.py -k '2i_matches_diffusers' -m full_model --run-level full_model - pytest -s -v tests/e2e/accuracy/test_diffusers_backend_similarity.py -k '2i_matches_diffusers' -m full_model --run-level full_model
mirror_hardwares: h100_1 mirror_hardwares: h100_1
@@ -267,42 +339,85 @@ steps:
- pytest -s -v tests/e2e/accuracy/test_hunyuan_image3_pixel_accuracy.py -m full_model --run-level full_model - pytest -s -v tests/e2e/accuracy/test_hunyuan_image3_pixel_accuracy.py -m full_model --run-level full_model
mirror_hardwares: h100_4 mirror_hardwares: h100_4
- label: ":full_moon: HiDream-O1-Image · Accuracy Test" - label: ":full_moon: Diffusion X2I(&A&T) · Perf Test · Qwen-Image"
timeout_in_minutes: 180 key: nightly-diffusion-x2iat-performance-qwen-image
commands:
- pytest -s -v tests/e2e/accuracy/test_hidream_o1_image.py -m full_model --run-level full_model
mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2I(&A&T) · Perf Test · Qwen-Image · Single-GPU"
key: nightly-diffusion-x2iat-performance-qwen-image-single
timeout_in_minutes: 180 timeout_in_minutes: 180
commands: commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results - export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- export CACHE_DIT_VERSION=1.5.0 - export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
- export CACHE_DIT_VERSION=1.3.0
- | - |
set +e set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_vllm_omni.json -m "H100 and cards_1" pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_vllm_omni.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
exit $$EXIT
mirror_hardwares: h100_4
- label: ":full_moon: Diffusion X2I(&A&T) · Perf Test · Qwen-Image-Edit"
key: nightly-diffusion-x2iat-performance-qwen-image-edit
timeout_in_minutes: 180
commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
- export CACHE_DIT_VERSION=1.3.0
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_edit_vllm_omni.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
exit $$EXIT
mirror_hardwares: h100_4
- label: ":full_moon: Diffusion X2I(&A&T) · Perf Test · Qwen-Image-Edit-2511"
key: nightly-diffusion-x2iat-performance-qwen-image-edit-2511
timeout_in_minutes: 180
commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
- export CACHE_DIT_VERSION=1.3.0
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_edit_2511_vllm_omni.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
exit $$EXIT
mirror_hardwares: h100_4
- label: ":full_moon: Diffusion X2I(&A&T) · Perf Test · Qwen-Image-Layered"
key: nightly-diffusion-x2iat-performance-qwen-image-layered
timeout_in_minutes: 120
commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
- export CACHE_DIT_VERSION=1.3.0
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_layered_vllm_omni.json
EXIT=$$? EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json" buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log" buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
exit $$EXIT exit $$EXIT
mirror_hardwares: h100_1 mirror_hardwares: h100_1
- label: ":full_moon: Diffusion X2I(&A&T) · Perf Test · Qwen-Image · 4-GPU" - label: ":full_moon: Diffusion X2I(&A&T) · Perf Test · BAGEL"
key: nightly-diffusion-x2iat-performance-qwen-image-4gpu key: nightly-diffusion-x2iat-performance-bagel
timeout_in_minutes: 180 timeout_in_minutes: 180
commands: commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results - export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN - export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
- export CACHE_DIT_VERSION=1.5.0 - export CACHE_DIT_VERSION=1.3.0
- | - |
set +e set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_vllm_omni.json -m "H100 and cards_4" pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_bagel_vllm_omni.json
EXIT=$$? EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json" buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log" buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
exit $$EXIT exit $$EXIT
mirror_hardwares: h100_4 mirror_hardwares: h100_1
- group: ":card_index_dividers: Diffusion X2V Model Test" - group: ":card_index_dividers: Diffusion X2V Model Test"
key: nightly-diffusion-x2v-group key: nightly-diffusion-x2v-group
@@ -313,42 +428,24 @@ steps:
build.pull_request.labels includes "diffusion-x2v-test" build.pull_request.labels includes "diffusion-x2v-test"
steps: steps:
- label: ":full_moon: Diffusion X2V · Wan2.2 T2V Function Test · Single-GPU" - label: ":full_moon: Diffusion X2V · Wan2.2 T2V Function Test"
timeout_in_minutes: 90 timeout_in_minutes: 90
commands: commands:
- pytest -s -v tests/e2e/online_serving/test_wan22_expansion.py -k "t2v" -m "full_model and cuda and H100 and cards_1" --run-level "full_model" - pytest -s -v tests/e2e/online_serving/test_wan22_expansion.py -k "t2v" -m "full_model and cuda" --run-level "full_model"
mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2V · Wan2.2 I2V/TI2V Function Test"
timeout_in_minutes: 90
commands:
- pytest -s -v tests/e2e/online_serving/test_wan22_expansion.py -k "i2v" -m "full_model and cuda" --run-level "full_model"
mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2V · LingBot Video MoE Function Test"
timeout_in_minutes: 90
commands:
- pytest -s -v tests/e2e/online_serving/test_lingbot_video_moe.py -k "video_generation" -m "full_model and cuda" --run-level "full_model"
mirror_hardwares: h100_1 mirror_hardwares: h100_1
- label: ":full_moon: Diffusion X2V · Wan2.2 T2V Function Test · 2-GPU"
timeout_in_minutes: 90
commands:
- pytest -s -v tests/e2e/online_serving/test_wan22_expansion.py -k "t2v" -m "full_model and cuda and H100 and cards_2" --run-level "full_model"
mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2V · Wan2.2 I2V/TI2V Function Test · Single-GPU"
timeout_in_minutes: 90
commands:
- pytest -s -v tests/e2e/online_serving/test_wan22_expansion.py -k "i2v" -m "full_model and cuda and H100 and cards_1" --run-level "full_model"
mirror_hardwares: h100_1
- label: ":full_moon: Diffusion X2V · Wan2.2 I2V/TI2V Function Test · 2-GPU"
timeout_in_minutes: 90
commands:
- pytest -s -v tests/e2e/online_serving/test_wan22_expansion.py -k "i2v" -m "full_model and cuda and H100 and cards_2" --run-level "full_model"
mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2V · HunyuanVideo-1.5 Function Test"
timeout_in_minutes: 90
commands:
- pytest -s -v tests/e2e/online_serving/test_hunyuan_video_15_expansion.py -m "full_model and cuda" --run-level "full_model"
mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2V · MAGI-2 Preview Serving Smoke"
timeout_in_minutes: 180
commands:
- pytest -s -v tests/e2e/online_serving/test_magi2.py -m "advanced_model and diffusion and cuda and H100 and cards_4" --run-level "advanced_model"
mirror_hardwares: h100_4
- label: ":full_moon: Diffusion X2V · Doc Test" - label: ":full_moon: Diffusion X2V · Doc Test"
timeout_in_minutes: 60 timeout_in_minutes: 60
commands: commands:
@@ -359,6 +456,7 @@ steps:
timeout_in_minutes: 90 timeout_in_minutes: 90
commands: commands:
- pytest -s -v tests/e2e/offline_inference/test_wan22_autoround_w4a16_expansion.py -m "full_model and cuda" --run-level "full_model" - pytest -s -v tests/e2e/offline_inference/test_wan22_autoround_w4a16_expansion.py -m "full_model and cuda" --run-level "full_model"
- pytest -s -v tests/e2e/online_serving/test_wan_2_1_vace_expansion.py tests/e2e/online_serving/test_ltx2_expansion.py -m "full_model and cuda" --run-level "full_model"
mirror_hardwares: h100_2 mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2V · Wan2.2 Accuracy Test" - label: ":full_moon: Diffusion X2V · Wan2.2 Accuracy Test"
@@ -379,50 +477,21 @@ steps:
commands: commands:
- pytest -s -v tests/e2e/accuracy/hunyuanvideo15_t2v/test_hunyuanvideo15_t2v_video_similarity.py -m full_model --run-level full_model - pytest -s -v tests/e2e/accuracy/hunyuanvideo15_t2v/test_hunyuanvideo15_t2v_video_similarity.py -m full_model --run-level full_model
- pytest -s -v tests/e2e/accuracy/hunyuanvideo15_i2v/test_hunyuanvideo15_i2v_video_similarity.py -m full_model --run-level full_model - pytest -s -v tests/e2e/accuracy/hunyuanvideo15_i2v/test_hunyuanvideo15_i2v_video_similarity.py -m full_model --run-level full_model
- pytest -s -v 'tests/e2e/accuracy/ltx/test_ltx_official_similarity.py::test_ltx_one_stage_matches_official[ltx2_3_i2v]' -m full_model --run-level full_model
- pytest -s -v tests/e2e/accuracy/test_video_streaming_output_similarity.py -m full_model --run-level full_model
mirror_hardwares: h100_2 mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2V · Perf Test · Single-GPU" - label: ":full_moon: Diffusion X2V · Perf Test"
key: nightly-diffusion-x2v-performance-single key: nightly-diffusion-x2v-performance
timeout_in_minutes: 180
commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_wan22_i2v_vllm_omni.json -m "H100 and cards_1"
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
exit $$EXIT
mirror_hardwares: h100_1
- label: ":full_moon: Diffusion X2V · Perf Test · 2-GPU"
key: nightly-diffusion-x2v-performance-2gpu
timeout_in_minutes: 180 timeout_in_minutes: 180
commands: commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results - export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN - export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
- | - |
set +e set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_wan22_i2v_vllm_omni.json -m "H100 and cards_2" pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_wan22_i2v_vllm_omni.json
EXIT1=$$? EXIT1=$$?
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_cosmos3_vllm_omni.json -m "H100 and cards_2" pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_cosmos3_vllm_omni.json
EXIT2=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
if [ $$EXIT1 -ne 0 ]; then exit $$EXIT1; fi
exit $$EXIT2
mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2V · Perf Test · HunyuanVideo-1.5"
key: nightly-diffusion-x2v-performance-hunyuanvideo15
timeout_in_minutes: 180
commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_hunyuanvideo15_t2v_vllm_omni.json
EXIT1=$$?
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_hunyuanvideo15_i2v_vllm_omni.json
EXIT2=$$? EXIT2=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json" buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log" buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
@@ -435,6 +504,7 @@ steps:
timeout_in_minutes: 180 timeout_in_minutes: 180
commands: commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results - export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
- | - |
set +e set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_minimax_h3_vllm_omni.json pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_minimax_h3_vllm_omni.json
@@ -449,34 +519,16 @@ steps:
depends_on: upload-nightly-pipeline depends_on: upload-nightly-pipeline
if: build.env("NIGHTLY") == "1" || build.pull_request.labels includes "nightly-test" if: build.env("NIGHTLY") == "1" || build.pull_request.labels includes "nightly-test"
steps: steps:
- label: ":full_moon: Diffusion · Distributed Test with L4 · 2-GPU" - label: ":full_moon: Diffusion · Distributed Test with L4"
timeout_in_minutes: 60 timeout_in_minutes: 60
commands: commands:
- pytest -sv tests/diffusion/distributed/ -m 'full_model and cuda and L4 and cards_2' --run-level "full_model" - pytest -sv tests/diffusion/distributed/ -m 'full_model and cuda and L4' --run-level "full_model"
mirror_hardwares: l4_2
- label: ":full_moon: Diffusion · Distributed Test with L4 · 3-GPU"
timeout_in_minutes: 60
commands:
- pytest -sv tests/diffusion/distributed/ -m 'full_model and cuda and L4 and cards_3' --run-level "full_model"
mirror_hardwares: l4_3
- label: ":full_moon: Diffusion · Distributed Test with L4 · 4-GPU"
timeout_in_minutes: 60
commands:
- pytest -sv tests/diffusion/distributed/ -m 'full_model and cuda and L4 and cards_4' --run-level "full_model"
mirror_hardwares: l4_4 mirror_hardwares: l4_4
- label: ":full_moon: Diffusion · Distributed Test with H100 · 2-GPU" - label: ":full_moon: Diffusion · Distributed Test with H100"
timeout_in_minutes: 60 timeout_in_minutes: 60
commands: commands:
- pytest -sv tests/diffusion/distributed/ -m 'full_model and cuda and H100 and cards_2' --run-level "full_model" - pytest -sv tests/diffusion/distributed/ -m 'full_model and cuda and H100' --run-level "full_model"
mirror_hardwares: h100_2
- label: ":full_moon: Diffusion · Distributed Test with H100 · 4-GPU"
timeout_in_minutes: 60
commands:
- pytest -sv tests/diffusion/distributed/ -m 'full_model and cuda and H100 and cards_4' --run-level "full_model"
mirror_hardwares: h100_4 mirror_hardwares: h100_4
- label: ":full_moon: Diffusion · Offloader Test" - label: ":full_moon: Diffusion · Offloader Test"
@@ -500,10 +552,20 @@ steps:
- label: ":full_moon: Diffusion · Quantization Test with L4" - label: ":full_moon: Diffusion · Quantization Test with L4"
timeout_in_minutes: 60 timeout_in_minutes: 60
commands: commands:
- pip install "vllm-gguf-plugin==0.0.4" - pip install "vllm-gguf-plugin>=0.0.3"
- pytest -sv tests/diffusion/quantization -m 'full_model and cuda and L4' --run-level "full_model" - pytest -sv tests/diffusion/quantization -m 'full_model and cuda and L4' --run-level "full_model"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- label: ":bar_chart: Testcase Statistics"
key: nightly-testcase-statistics
timeout_in_minutes: 120
depends_on: upload-nightly-pipeline
if: build.env("NIGHTLY") == "1"
commands:
- python tools/nightly/buildkite_testcase_statistics.py -o tests/dfx/perf/results/buildkite_testcase_statistics.html
- buildkite-agent artifact upload "tests/dfx/perf/results/*.html"
mirror_hardwares: h100_1
# NOTE: ":email: Nightly Collection & Email" is deprecated and will be removed soon. # NOTE: ":email: Nightly Collection & Email" is deprecated and will be removed soon.
# Do not add new depends_on entries or extend this step. # Do not add new depends_on entries or extend this step.
- label: ":email: Nightly Collection & Email" - label: ":email: Nightly Collection & Email"
@@ -511,25 +573,35 @@ steps:
depends_on: depends_on:
- nightly-omni-performance-no-async-chunk - nightly-omni-performance-no-async-chunk
- nightly-omni-performance-async-chunk - nightly-omni-performance-async-chunk
- nightly-tts-performance-single - nightly-omni-performance-vllm-text
- nightly-tts-performance-2gpu - nightly-omni-performance-multi-replicas
- nightly-diffusion-x2iat-performance-qwen-image-single - nightly-tts-performance
- nightly-diffusion-x2iat-performance-qwen-image-4gpu - nightly-tts-performance-higgs-audio-v3
- nightly-diffusion-x2v-performance-single - nightly-diffusion-x2iat-performance-qwen-image
- nightly-diffusion-x2v-performance-2gpu - nightly-diffusion-x2iat-performance-qwen-image-edit
- nightly-diffusion-x2iat-performance-qwen-image-edit-2511
- nightly-diffusion-x2iat-performance-qwen-image-layered
- nightly-diffusion-x2iat-performance-bagel
- nightly-diffusion-x2v-performance
- nightly-testcase-statistics
if: build.env("NIGHTLY") == "1" && build.env("EMAIL_DISTRIBUTION") == "1" if: build.env("NIGHTLY") == "1" && build.env("EMAIL_DISTRIBUTION") == "1"
commands: commands:
- pip install openpyxl - pip install openpyxl
- export DEFAULT_INPUT_DIR=tests/dfx/perf/results - export DEFAULT_INPUT_DIR=tests/dfx/perf/results
- export DEFAULT_OUTPUT_DIR=tests/dfx/perf/results - export DEFAULT_OUTPUT_DIR=tests/dfx/perf/results
- buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-tts-performance-single - buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-tts-performance
- buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-tts-performance-2gpu - buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-tts-performance-higgs-audio-v3
- buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-omni-performance-no-async-chunk - buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-omni-performance-no-async-chunk
- buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-omni-performance-async-chunk - buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-omni-performance-async-chunk
- buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-diffusion-x2iat-performance-qwen-image-single - buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-omni-performance-vllm-text
- buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-diffusion-x2iat-performance-qwen-image-4gpu - buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-omni-performance-multi-replicas
- buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-diffusion-x2v-performance-single - buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-diffusion-x2iat-performance-qwen-image
- buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-diffusion-x2v-performance-2gpu - buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-diffusion-x2iat-performance-qwen-image-edit
- buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-diffusion-x2iat-performance-qwen-image-edit-2511
- buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-diffusion-x2iat-performance-qwen-image-layered
- buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-diffusion-x2iat-performance-bagel
- buildkite-agent artifact download "tests/dfx/perf/results/*.json" . --step nightly-diffusion-x2v-performance
- buildkite-agent artifact download "tests/dfx/perf/results/*.html" . --step nightly-testcase-statistics
- python tools/nightly/generate_nightly_perf_excel.py - python tools/nightly/generate_nightly_perf_excel.py
- python tools/nightly/generate_nightly_perf_html.py - python tools/nightly/generate_nightly_perf_html.py
- python tools/nightly/send_nightly_email.py --report-file "tests/dfx/perf/results/*.xlsx, tests/dfx/perf/results/*.html" - python tools/nightly/send_nightly_email.py --report-file "tests/dfx/perf/results/*.xlsx, tests/dfx/perf/results/*.html"
+106 -116
View File
@@ -1,8 +1,6 @@
# CUDA CI L2 (`core_model` e2e) plus L1 CPU Simple Test (`core_model and cpu`).
# Uploaded by Upload Ready Pipeline: PR label `ready`, or main+WEEKLY=1 with --e2e.
env: env:
VLLM_WORKER_MULTIPROC_METHOD: spawn VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_USE_FLASHINFER_MOE_FP16: 0
HF_HUB_DOWNLOAD_TIMEOUT: 300 HF_HUB_DOWNLOAD_TIMEOUT: 300
HF_HUB_ETAG_TIMEOUT: 60 HF_HUB_ETAG_TIMEOUT: 60
@@ -11,27 +9,23 @@ steps:
depends_on: upload-ready-pipeline depends_on: upload-ready-pipeline
steps: steps:
- label: "Simple · Model Executor Test" - label: "Simple · Model Executor Test"
timeout_in_minutes: 20
commands: commands:
- "pytest -sv tests/model_executor -m 'core_model and cpu'" - "timeout 20m pytest -sv tests/model_executor -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- label: "Simple · Diffusion Test" - label: "Simple · Diffusion Test"
timeout_in_minutes: 20
commands: commands:
- "pytest -sv tests/diffusion -m 'core_model and cpu'" - "timeout 20m pytest -sv tests/diffusion -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- label: "Simple · Engine&Entrypoints Test" - label: "Simple · Engine&Entrypoints Test"
timeout_in_minutes: 20
commands: commands:
- "pytest -sv tests/entrypoints tests/engine -m 'core_model and cpu'" - "timeout 20m pytest -sv tests/entrypoints tests/engine -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- label: "Simple · Other Test" - label: "Simple · Other Test"
timeout_in_minutes: 20
commands: commands:
- "pytest -sv tests/ -m 'core_model and cpu' --ignore=tests/diffusion --ignore=tests/model_executor --ignore=tests/entrypoints --ignore=tests/engine" - "timeout 20m pytest -sv tests/ -m 'core_model and cpu' --ignore=tests/diffusion --ignore=tests/model_executor --ignore=tests/entrypoints --ignore=tests/engine --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- group: ":card_index_dividers: Diffusion Test" - group: ":card_index_dividers: Diffusion Test"
@@ -53,20 +47,17 @@ steps:
- timeout 15m pytest -sv tests/diffusion/batching -m 'core_model and cuda' --run-level "core_model" - timeout 15m pytest -sv tests/diffusion/batching -m 'core_model and cuda' --run-level "core_model"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- label: "Diffusion · Distributed Test · Single-GPU" - label: "Diffusion · Distributed & Attention Test"
source_file_dependencies:
- tests/diffusion/distributed/
- tests/diffusion/attention/test_attention_sp.py
- vllm_omni/diffusion/distributed/
- vllm_omni/diffusion/attention/
- vllm_omni/diffusion/data.py
- vllm_omni/diffusion/models/
commands: commands:
- timeout 20m pytest -sv tests/diffusion/distributed -m 'core_model and cuda and L4 and cards_1' --run-level "core_model" - timeout 20m pytest -sv tests/diffusion/distributed -m 'core_model and cuda' --run-level "core_model"
mirror_hardwares: l4_1 - timeout 20m pytest -sv tests/diffusion/attention/test_attention_sp.py -m 'core_model and cuda' --run-level "core_model"
- label: "Diffusion · Distributed Test · 2-GPU"
commands:
- timeout 20m pytest -sv tests/diffusion/distributed -m 'core_model and cuda and L4 and cards_2' --run-level "core_model"
mirror_hardwares: l4_2
- label: "Diffusion · Distributed & Attention Test · 4-GPU"
commands:
- timeout 20m pytest -sv tests/diffusion/distributed -m 'core_model and cuda and L4 and cards_4' --run-level "core_model"
- timeout 20m pytest -sv tests/diffusion/attention/test_attention_sp.py -m 'core_model and cuda and L4 and cards_4' --run-level "core_model"
mirror_hardwares: l4_4 mirror_hardwares: l4_4
- label: "Diffusion · Model Test" - label: "Diffusion · Model Test"
@@ -76,7 +67,7 @@ steps:
- label: "Diffusion · Other Test" - label: "Diffusion · Other Test"
commands: commands:
- timeout 15m pytest -sv tests/diffusion/*.py tests/diffusion/ar_diffusion -m 'core_model and cuda' --run-level "core_model" - timeout 15m pytest -sv tests/diffusion/*.py -m 'core_model and cuda' --run-level "core_model"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- group: ":card_index_dividers: Tiny Model Tests [Single GPU]" - group: ":card_index_dividers: Tiny Model Tests [Single GPU]"
@@ -85,7 +76,7 @@ steps:
- label: "Tiny Model Tests · Diffusion (base)" - label: "Tiny Model Tests · Diffusion (base)"
commands: commands:
- timeout 15m pytest -sv -n 4 tests/model_tests/diffusion -m 'core_model and cuda' --run-level "core_model" - timeout 15m pytest -sv tests/model_tests/diffusion -m 'core_model and cuda' --run-level "core_model"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- label: "Engine&Model Executor Test" - label: "Engine&Model Executor Test"
@@ -93,7 +84,7 @@ steps:
commands: commands:
- | - |
timeout 15m bash -c " timeout 15m bash -c "
pytest -sv tests/engine/ tests/model_executor/ tests/worker/test_batched_omni_output.py -m 'core_model and cuda' --run-level 'core_model' pytest -sv tests/engine/ tests/model_executor/ -m 'core_model and cuda' --run-level 'core_model'
" "
mirror_hardwares: l4_1 mirror_hardwares: l4_1
@@ -130,8 +121,17 @@ steps:
- vllm_omni/outputs/ - vllm_omni/outputs/
commands: commands:
- timeout 20m pytest -s -v tests/e2e/features/custom_pipeline/ -m "core_model" - timeout 20m pytest -s -v tests/e2e/features/custom_pipeline/ -m "core_model"
mirror_hardwares: l4_1 agents:
queue: "gpu_1_queue"
plugins:
- docker#v5.2.0:
image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
always-pull: true
propagate-environment: true
environment:
- "HF_HOME=/fsx/hf_cache"
volumes:
- "/fsx/hf_cache:/fsx/hf_cache"
- label: "RLHF · VeRL-Omni E2E Test" - label: "RLHF · VeRL-Omni E2E Test"
source_file_dependencies: source_file_dependencies:
@@ -153,13 +153,7 @@ steps:
- label: "Omni · Qwen3-Omni Test" - label: "Omni · Qwen3-Omni Test"
source_file_dependencies: source_file_dependencies:
- tests/e2e/online_serving/test_qwen3_omni.py - tests/e2e/online_serving/test_qwen3_omni.py
- tests/helpers/runtime.py
- vllm_omni/entrypoints/openai/serving_chat.py
- vllm_omni/model_executor/models/qwen3_omni/ - vllm_omni/model_executor/models/qwen3_omni/
- vllm_omni/model_executor/models/common/qwen3_code_predictor.py
- vllm_omni/model_executor/models/common/snake_activation.py
- vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py
- vllm_omni/model_executor/models/qwen3_tts/cuda_graph_decoder_wrapper.py
- vllm_omni/model_executor/stage_input_processors/qwen3_omni.py - vllm_omni/model_executor/stage_input_processors/qwen3_omni.py
- vllm_omni/deploy/qwen3_omni_moe.yaml - vllm_omni/deploy/qwen3_omni_moe.yaml
commands: commands:
@@ -172,11 +166,6 @@ steps:
- label: "Omni · MiniCPM-o 4.5 Offline Test" - label: "Omni · MiniCPM-o 4.5 Offline Test"
source_file_dependencies: source_file_dependencies:
- tests/e2e/offline_inference/test_minicpmo_4_5.py - tests/e2e/offline_inference/test_minicpmo_4_5.py
- vllm_omni/model_executor/models/minicpmo_4_5/
- vllm_omni/model_executor/models/step_audio2/
- vllm_omni/model_executor/models/cosyvoice3/code2wav_core/
- vllm_omni/model_executor/models/cosyvoice3/utils.py
- vllm_omni/model_executor/stage_input_processors/minicpmo_4_5_omni.py
- vllm_omni/deploy/minicpmo_4_5.yaml - vllm_omni/deploy/minicpmo_4_5.yaml
commands: commands:
- | - |
@@ -188,12 +177,6 @@ steps:
- label: "Omni · MiniCPM-o 4.5 Online Test" - label: "Omni · MiniCPM-o 4.5 Online Test"
source_file_dependencies: source_file_dependencies:
- tests/e2e/online_serving/test_minicpmo_4_5.py - tests/e2e/online_serving/test_minicpmo_4_5.py
- vllm_omni/transformers_utils/chat_templates/minicpmo45_native.jinja
- vllm_omni/model_executor/models/minicpmo_4_5/
- vllm_omni/model_executor/models/step_audio2/
- vllm_omni/model_executor/models/cosyvoice3/code2wav_core/
- vllm_omni/model_executor/models/cosyvoice3/utils.py
- vllm_omni/model_executor/stage_input_processors/minicpmo_4_5_omni.py
- vllm_omni/deploy/minicpmo_4_5.yaml - vllm_omni/deploy/minicpmo_4_5.yaml
commands: commands:
- | - |
@@ -211,72 +194,45 @@ steps:
- tests/e2e/online_serving/test_minicpmo_realtime_duplex_drivers.py - tests/e2e/online_serving/test_minicpmo_realtime_duplex_drivers.py
- tests/e2e/online_serving/helpers/minicpmo_realtime_duplex_scenarios.py - tests/e2e/online_serving/helpers/minicpmo_realtime_duplex_scenarios.py
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_multi_session.py - tests/e2e/online_serving/run_minicpmo_realtime_duplex_multi_session.py
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_server_vad.py
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_soft_interrupt.py - tests/e2e/online_serving/run_minicpmo_realtime_duplex_soft_interrupt.py
- vllm_omni/deploy/minicpmo_4_5.yaml
- tests/e2e/online_serving/helpers/minicpmo_4_5_duplex.py - tests/e2e/online_serving/helpers/minicpmo_4_5_duplex.py
- vllm_omni/engine/duplex/ - vllm_omni/deploy/minicpmo_4_5_duplex.yaml
- vllm_omni/entrypoints/duplex/
# Public duplex client library + its unit suites: client-side
# changes must re-run the duplex e2e group, which includes the
# live client probe (test_duplex_client_live.py, single GPU).
- vllm_omni/clients/
- tests/clients/
- tests/e2e/online_serving/test_duplex_client_live.py
- vllm_omni/entrypoints/duplex_request_client.py
- vllm_omni/outputs/duplex.py
- vllm_omni/model_executor/duplex_sampling.py
- tests/engine/duplex/
- tests/entrypoints/duplex/
- tests/model_executor/models/minicpmo_4_5/duplex/
# Legacy Realtime probe client + video stacking used by the e2e drivers.
- vllm_omni/experimental/fullduplex/ - vllm_omni/experimental/fullduplex/
- vllm_omni/model_executor/models/minicpmo_4_5/ - vllm_omni/model_executor/models/minicpmo_4_5/
- vllm_omni/model_executor/models/step_audio2/
- vllm_omni/model_executor/models/cosyvoice3/code2wav_core/
- vllm_omni/model_executor/models/cosyvoice3/utils.py
- vllm_omni/model_executor/stage_input_processors/minicpmo_4_5_omni.py - vllm_omni/model_executor/stage_input_processors/minicpmo_4_5_omni.py
- vllm_omni/core/sched/ - vllm_omni/core/sched/
- vllm_omni/outputs/multimodal_accumulation.py - vllm_omni/outputs/multimodal_accumulation.py
- vllm_omni/engine/stage_init_utils.py - vllm_omni/engine/stage_init_utils.py
- vllm_omni/engine/async_omni_engine.py - vllm_omni/engine/async_omni_engine.py
- vllm_omni/engine/orchestrator.py
- vllm_omni/entrypoints/cli/serve.py - vllm_omni/entrypoints/cli/serve.py
- vllm_omni/entrypoints/openai/api_server.py
- vllm_omni/entrypoints/openai/duplex_capability.py
- vllm_omni/config/stage_config.py
commands: commands:
- | - |
timeout 35m bash -c " timeout 20m bash -c "
pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex.py tests/e2e/online_serving/test_duplex_client_live.py -m 'core_model and cuda' --run-level 'core_model' pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex.py -m 'core_model and cuda' --run-level 'core_model'
" "
mirror_hardwares: h100_1 mirror_hardwares: h100_1
- label: "Omni · MiniCPM-o 4.5 Perf Test" - label: "TTS · VoxCPM2 Test"
timeout_in_minutes: 60
source_file_dependencies: source_file_dependencies:
- tests/dfx/perf/tests/test_minicpmo_4_5_ready.json - tests/e2e/online_serving/test_voxcpm2_tts.py
- vllm_omni/deploy/minicpmo_4_5.yaml - vllm_omni/model_executor/models/voxcpm2/
- vllm_omni/model_executor/models/minicpmo_4_5/ - vllm_omni/deploy/voxcpm2.yaml
- vllm_omni/model_executor/stage_input_processors/minicpmo_4_5_omni.py timeout_in_minutes: 30
commands: commands:
- export BENCHMARK_DIR=tests/dfx/perf/results - |
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5_ready.json -m 'core_model and cuda' timeout 30m bash -c "
mirror_hardwares: h100_1 export VLLM_LOGGING_LEVEL=DEBUG
export VLLM_WORKER_MULTIPROC_METHOD=spawn
pytest -s -v tests/e2e/online_serving/test_voxcpm2_tts.py -m 'core_model' --run-level 'core_model'
"
mirror_hardwares: l4_1
- label: "TTS · Qwen3-TTS CustomVoice Test" - label: "TTS · Qwen3-TTS CustomVoice Test"
source_file_dependencies: source_file_dependencies:
- tests/helpers/media.py
- tests/e2e/online_serving/test_qwen3_tts_customvoice.py - tests/e2e/online_serving/test_qwen3_tts_customvoice.py
- vllm_omni/model_executor/models/qwen3_tts/ - vllm_omni/model_executor/models/qwen3_tts/
- vllm_omni/model_executor/models/common/qwen3_code_predictor.py
- vllm_omni/model_executor/models/common/snake_activation.py
- vllm_omni/model_executor/models/common/alias_free_activation.py
- vllm_omni/model_executor/stage_input_processors/qwen3_tts.py - vllm_omni/model_executor/stage_input_processors/qwen3_tts.py
- vllm_omni/deploy/qwen3_tts.yaml - vllm_omni/deploy/qwen3_tts.yaml
- vllm_omni/entrypoints/openai/serving_speech.py
- vllm_omni/entrypoints/openai/tts_adapters/
- vllm_omni/engine/stage_pool.py
commands: commands:
- | - |
timeout 20m bash -c " timeout 20m bash -c "
@@ -285,11 +241,68 @@ steps:
" "
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- label: "TTS · Qwen3-TTS Base Test"
source_file_dependencies:
- tests/e2e/online_serving/test_qwen3_tts_base.py
- vllm_omni/model_executor/models/qwen3_tts/
- vllm_omni/model_executor/stage_input_processors/qwen3_tts.py
- vllm_omni/deploy/qwen3_tts.yaml
commands:
- |
timeout 20m bash -c "
export VLLM_WORKER_MULTIPROC_METHOD=spawn
export VLLM_ALLOW_LONG_MAX_MODEL_LEN=1
export VLLM_OMNI_USE_V2_RUNNER=1
pytest -s -v tests/e2e/online_serving/test_qwen3_tts_base.py -m 'core_model' --run-level 'core_model'
"
mirror_hardwares: l4_1
- label: "TTS · Higgs-Audio-V3 Test"
source_file_dependencies:
- tests/e2e/online_serving/test_higgs_audio_v3.py
- vllm_omni/model_executor/models/higgs_audio_v3/
- vllm_omni/model_executor/stage_input_processors/higgs_audio_v3.py
- vllm_omni/deploy/higgs_multimodal_qwen3.yaml
commands:
- |
timeout 20m bash -c "
export VLLM_WORKER_MULTIPROC_METHOD=spawn
export VLLM_USE_DEEP_GEMM=0
export VLLM_MOE_USE_DEEP_GEMM=0
pytest -s -v tests/e2e/online_serving/test_higgs_audio_v3.py -m 'core_model' --run-level 'core_model'
"
mirror_hardwares: l4_1
- label: "Diffusion · Qwen Image Edit Test"
source_file_dependencies:
- tests/e2e/online_serving/test_qwen_image_edit.py
- vllm_omni/diffusion/models/qwen_image/
commands:
- |
timeout 20m bash -c "
pytest -s -v tests/e2e/online_serving/test_qwen_image_edit.py -m 'core_model' --run-level 'core_model'
"
mirror_hardwares: h100_1
- label: "Diffusion · Bagel Test"
source_file_dependencies:
- tests/e2e/online_serving/test_bagel.py
- vllm_omni/model_executor/models/bagel/
- vllm_omni/diffusion/models/bagel/
- vllm_omni/model_executor/stage_input_processors/bagel.py
- vllm_omni/deploy/bagel.yaml
commands:
- |
timeout 40m bash -c "
export VLLM_IMAGE_FETCH_TIMEOUT=60
pytest -s -v tests/e2e/online_serving/test_bagel.py -m 'core_model' --run-level 'core_model'
"
mirror_hardwares: h100_1
- label: "Diffusion · Wan22 Test" - label: "Diffusion · Wan22 Test"
source_file_dependencies: source_file_dependencies:
- tests/e2e/online_serving/test_wan22_t2v.py - tests/e2e/online_serving/test_wan22_t2v.py
- vllm_omni/diffusion/models/wan2_2/ - vllm_omni/diffusion/models/wan2_2/
- vllm_omni/diffusion/models/dmd2/
commands: commands:
- | - |
timeout 40m bash -c " timeout 40m bash -c "
@@ -302,9 +315,6 @@ steps:
source_file_dependencies: source_file_dependencies:
- tests/e2e/online_serving/test_cosmos3.py - tests/e2e/online_serving/test_cosmos3.py
- vllm_omni/diffusion/models/cosmos3/ - vllm_omni/diffusion/models/cosmos3/
- vllm_omni/diffusion/distributed/autoencoders/autoencoder_kl_wan.py
- vllm_omni/experimental/world_models/
- vllm_omni/model_extras/cosmos3.py
commands: commands:
- | - |
timeout 40m bash -c " timeout 40m bash -c "
@@ -313,30 +323,10 @@ steps:
" "
mirror_hardwares: h100_1 mirror_hardwares: h100_1
- label: "Diffusion · MiniMax-H3 DLO DP2 T2VA Test"
timeout_in_minutes: 90
source_file_dependencies:
- tests/e2e/online_serving/test_minimax_h3_dlo_dp2_t2va.py
- tests/helpers/assertions.py
- tests/helpers/runtime.py
- vllm_omni/diffusion/models/minimax_h3/
- vllm_omni/diffusion/offloader/
- vllm_omni/diffusion/distributed/
- vllm_omni/entrypoints/openai/
commands:
- |
timeout 80m bash -c "
export VLLM_IMAGE_FETCH_TIMEOUT=60
pytest -s -v tests/e2e/online_serving/test_minimax_h3_dlo_dp2_t2va.py -m 'core_model and cuda and diffusion' --run-level 'core_model'
"
mirror_hardwares: h100_2
- label: "Diffusion · Qwen Image Test" - label: "Diffusion · Qwen Image Test"
source_file_dependencies: source_file_dependencies:
- tests/e2e/online_serving/test_qwen_image.py - tests/e2e/online_serving/test_qwen_image.py
- vllm_omni/diffusion/models/qwen_image/ - vllm_omni/diffusion/models/qwen_image/
- vllm_omni/diffusion/models/dmd2/
- vllm_omni/diffusion/distributed/autoencoders/autoencoder_kl_qwenimage.py
commands: commands:
- | - |
timeout 40m bash -c " timeout 40m bash -c "
@@ -345,14 +335,14 @@ steps:
" "
mirror_hardwares: h100_1 mirror_hardwares: h100_1
- label: "Diffusion · HunyuanVideo-1.5 Test" - label: "Diffusion · Qwen Image Layered Test"
source_file_dependencies: source_file_dependencies:
- tests/e2e/online_serving/test_hunyuan_video_15_expansion.py - tests/e2e/online_serving/test_qwen_image_layered.py
- vllm_omni/diffusion/models/hunyuan_video/ - vllm_omni/diffusion/models/qwen_image/
commands: commands:
- | - |
timeout 40m bash -c " timeout 40m bash -c "
export VLLM_IMAGE_FETCH_TIMEOUT=60 export VLLM_IMAGE_FETCH_TIMEOUT=60
pytest -s -v tests/e2e/online_serving/test_hunyuan_video_15_expansion.py -m 'core_model and diffusion' --run-level 'core_model' pytest -s -v tests/e2e/online_serving/test_qwen_image_layered.py -m 'core_model' --run-level 'core_model'
" "
mirror_hardwares: h100_1 mirror_hardwares: h100_1
+49 -162
View File
@@ -1,159 +1,89 @@
# CUDA CI L5 (`slow` / reliability / extra coverage).
# Uploaded by Upload Weekly Pipeline: WEEKLY=1 / NON_CRITICAL=1, or PR label `weekly-test`.
env: env:
VLLM_WORKER_MULTIPROC_METHOD: spawn VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_USE_FLASHINFER_MOE_FP16: 0
HF_HUB_DOWNLOAD_TIMEOUT: 300 HF_HUB_DOWNLOAD_TIMEOUT: 300
HF_HUB_ETAG_TIMEOUT: 60 HF_HUB_ETAG_TIMEOUT: 60
steps: steps:
- label: "Simple · CPU Coverage Test"
key: weekly-simple-cpu-coverage - label: "Reliability Test - qwen3-omni"
timeout_in_minutes: 180
depends_on: upload-weekly-pipeline depends_on: upload-weekly-pipeline
if: build.env("WEEKLY") == "1" if: build.env("WEEKLY") == "1" || build.pull_request.labels includes "weekly-test"
timeout_in_minutes: 60
commands: commands:
- | - pytest -s -v tests/dfx/reliability/test_reliability_qwen3_omni.py -m "slow"
set +e mirror_hardwares: h100_2
REPORT="coverage-core-model-cpu-$${BUILDKITE_STEP_ID:-local}.xml"
pytest -sv tests/ -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml:$${REPORT} - label: "Reliability Test - wan22"
EXIT=$$? timeout_in_minutes: 180
gzip -9 -f "$${REPORT}" depends_on: upload-weekly-pipeline
buildkite-agent artifact upload "$${REPORT}.gz" if: build.env("WEEKLY") == "1" || build.pull_request.labels includes "weekly-test"
exit $$EXIT commands:
- pytest -s -v tests/dfx/reliability/test_reliability_wan22.py -m "slow"
mirror_hardwares: h100_2
- label: "Reliability Test - hunyuan-image"
timeout_in_minutes: 180
depends_on: upload-weekly-pipeline
if: build.env("WEEKLY") == "1" || build.pull_request.labels includes "weekly-test"
commands:
- pytest -s -v tests/dfx/reliability/test_reliability_hunyuan_image.py -m "slow"
mirror_hardwares: h100_4
- label: "Reliability Test - voxcpm2"
timeout_in_minutes: 180
depends_on: upload-weekly-pipeline
if: build.env("WEEKLY") == "1" || build.pull_request.labels includes "weekly-test"
commands:
- pytest -s -v tests/dfx/reliability/test_reliability_voxcpm2.py -m "slow"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- group: "Reliability Test" - group: "Reliability Test - Invalid parameters Test"
depends_on: upload-weekly-pipeline depends_on: upload-weekly-pipeline
if: build.env("WEEKLY") == "1" || build.pull_request.labels includes "weekly-test" if: build.env("WEEKLY") == "1" || build.pull_request.labels includes "weekly-test"
steps: steps:
- label: "Reliability Test · qwen3-omni" - label: "Invalid parameters Test · H100"
timeout_in_minutes: 180 timeout_in_minutes: 120
commands: commands:
- pytest -s -v tests/dfx/reliability/test_reliability_qwen3_omni.py -m "slow" - pytest -s -v tests/dfx/reliability/invalid_param_test/ -m "slow and H100"
mirror_hardwares: h100_2 mirror_hardwares: h100_2
- label: "Reliability Test · wan22" - label: "Invalid parameters Test · L4"
timeout_in_minutes: 180
commands:
- pytest -s -v tests/dfx/reliability/test_reliability_wan22.py -m "slow"
mirror_hardwares: h100_2
- label: "Reliability Test · hunyuan-image"
timeout_in_minutes: 180
commands:
- pytest -s -v tests/dfx/reliability/test_reliability_hunyuan_image.py -m "slow"
mirror_hardwares: h100_4
- label: "Reliability Test · Invalid parameters · H100 · Single-GPU"
timeout_in_minutes: 90
commands:
- pytest -s -v tests/dfx/reliability/invalid_param_test/ -m "slow and H100 and cards_1"
mirror_hardwares: h100_1
- label: "Reliability Test · Invalid parameters · H100 · 2-GPU"
timeout_in_minutes: 90
commands:
- pytest -s -v tests/dfx/reliability/invalid_param_test/ -m "slow and H100 and cards_2"
mirror_hardwares: h100_2
- label: "Reliability Test · Invalid parameters · L4"
timeout_in_minutes: 120 timeout_in_minutes: 120
commands: commands:
- pytest -s -v tests/dfx/reliability/invalid_param_test/ -m "slow and L4" - pytest -s -v tests/dfx/reliability/invalid_param_test/ -m "slow and L4"
mirror_hardwares: l4_1 mirror_hardwares: l4_1
- group: "Perf Test" - group: ":card_index_dividers: E2E Tests"
depends_on: upload-weekly-pipeline depends_on: upload-weekly-pipeline
if: build.env("WEEKLY") == "1" || build.pull_request.labels includes "weekly-test" if: build.env("WEEKLY") == "1" || build.pull_request.labels includes "weekly-test"
steps: steps:
- label: ":full_moon: Omni · Perf Test · vLLM Text" - label: "Omni · L4"
key: weekly-omni-performance-vllm-text
timeout_in_minutes: 180
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_vllm_text.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
mirror_hardwares: h100_2
- label: ":full_moon: Omni · Perf Test · Async Chunk · Random"
key: weekly-omni-performance-async-chunk-random
timeout_in_minutes: 180
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_async_chunk.json -m "H100 and slow"
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
mirror_hardwares: h100_2
- label: ":full_moon: Omni · Perf Test · Multi-Replica"
key: weekly-omni-performance-multi-replicas
timeout_in_minutes: 180
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen3_omni_multi_replicas.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
mirror_hardwares: h100_3
- group: ":card_index_dividers: E2E Tests"
depends_on: upload-weekly-pipeline
if: build.env("NON_CRITICAL") == "1"
steps:
# Online Qwen2.5 expansion is 2-card; offline expansion + AutoRound are 4-card.
- label: "Omni · L4 · 2-GPU"
timeout_in_minutes: 90 timeout_in_minutes: 90
commands: commands:
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1" - export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
- pytest -s -v tests/e2e/online_serving/test_qwen2_5_omni_expansion.py -m "slow and L4 and omni and cards_2" --run-level "core_model" - pytest -s -v tests/e2e/*/test_qwen2_5_omni_expansion.py --run-level "core_model"
mirror_hardwares: l4_2
- label: "Omni · L4 · 4-GPU"
timeout_in_minutes: 90
commands:
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
- pytest -s -v tests/e2e/offline_inference/test_qwen2_5_omni_expansion.py -m "slow and L4 and omni and cards_4" --run-level "core_model"
- >- - >-
pytest -s -v tests/e2e/ pytest -s -v tests/e2e/
--ignore=tests/e2e/online_serving/test_qwen2_5_omni_expansion.py --ignore=tests/e2e/online_serving/test_qwen2_5_omni_expansion.py
--ignore=tests/e2e/offline_inference/test_qwen2_5_omni_expansion.py --ignore=tests/e2e/offline_inference/test_qwen2_5_omni_expansion.py
-m "slow and L4 and omni and cards_4" -m "slow and L4 and omni"
--run-level "full_model" --run-level "full_model"
mirror_hardwares: l4_4 mirror_hardwares: l4_4
- label: "Omni · H100 · Single-GPU" - label: "Omni · H100"
timeout_in_minutes: 90 timeout_in_minutes: 90
commands: commands:
- >- - >-
pytest -sv pytest -sv
tests/e2e/ -m "slow and H100 and omni and cards_1" tests/e2e/ -m "slow and H100 and omni"
--run-level "full_model" --run-level "full_model"
mirror_hardwares: h100_1 mirror_hardwares: h100_2
- label: "Omni · H100 · 4-GPU"
timeout_in_minutes: 90
commands:
- >-
pytest -sv
tests/e2e/ -m "slow and H100 and omni and cards_4"
--run-level "full_model"
mirror_hardwares: h100_4
- label: "TTS · L4" - label: "TTS · L4"
timeout_in_minutes: 120 timeout_in_minutes: 90
commands: commands:
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1" - export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
- export VLLM_USE_DEEP_GEMM="0" - export VLLM_USE_DEEP_GEMM="0"
@@ -178,72 +108,29 @@ steps:
--run-level "full_model" --run-level "full_model"
mirror_hardwares: h100_1 mirror_hardwares: h100_1
# Own step: the image has no NanoCodec, so this file import-skips out of - label: "Diffusion · L4"
# the mark-selected TTS steps above. The overrides keep nemo_toolkit[tts]
# from downgrading transformers; see
# examples/offline_inference/text_to_speech/README.md.
- label: "TTS · Gepard-1.0 · L4"
timeout_in_minutes: 60
commands:
- export VLLM_ALLOW_LONG_MAX_MODEL_LEN="1"
- printf '%s\n' "transformers>=5.5.3" "huggingface-hub>=1.0" > /tmp/gepard-overrides.txt
- uv pip install --python "$(python3 -c 'import sys; print(sys.executable)')" "nemo_toolkit[tts]==2.7.3" --overrides /tmp/gepard-overrides.txt
- >-
pytest -s -v tests/e2e/offline_inference/test_gepard_tts.py
-m "tts and cuda"
--run-level "full_model"
mirror_hardwares: l4_1
- label: "Diffusion · L4 · Single-GPU"
timeout_in_minutes: 60 timeout_in_minutes: 60
commands: commands:
- >- - >-
pytest -sv pytest -sv
tests/e2e/ -m "slow and diffusion and L4 and cards_1" tests/e2e/ -m "slow and diffusion and L4"
--run-level "full_model"
mirror_hardwares: l4_1
- label: "Diffusion · L4 · 2-GPU"
timeout_in_minutes: 60
commands:
- >-
pytest -sv
tests/e2e/ -m "slow and diffusion and L4 and cards_2"
--run-level "full_model"
mirror_hardwares: l4_2
- label: "Diffusion · L4 · 4-GPU"
timeout_in_minutes: 60
commands:
- >-
pytest -sv
tests/e2e/ -m "slow and diffusion and L4 and cards_4"
--run-level "full_model" --run-level "full_model"
mirror_hardwares: l4_4 mirror_hardwares: l4_4
- label: "Diffusion · H100 · Single-GPU" - label: "Diffusion · H100 · Single-GPU"
timeout_in_minutes: 150 timeout_in_minutes: 120
commands: commands:
- >- - >-
pytest -sv pytest -sv
tests/e2e/ -m "slow and diffusion and H100 and cards_1" tests/e2e/ -m "slow and diffusion and H100 and not distributed_cuda"
--run-level "full_model" --run-level "full_model"
mirror_hardwares: h100_1 mirror_hardwares: h100_1
- label: "Diffusion · H100 · 2-GPU" - label: "Diffusion · H100 · Multi-GPU"
timeout_in_minutes: 90 timeout_in_minutes: 90
commands: commands:
- >- - >-
pytest -sv pytest -sv
tests/e2e/ -m "slow and diffusion and H100 and cards_2" tests/e2e/ -m "slow and diffusion and H100 and distributed_cuda"
--run-level "full_model" --run-level "full_model"
mirror_hardwares: h100_2 mirror_hardwares: h100_2
- label: "Diffusion · H100 · 4-GPU"
timeout_in_minutes: 90
commands:
- >-
pytest -sv
tests/e2e/ -m "slow and diffusion and H100 and cards_4"
--run-level "full_model"
mirror_hardwares: h100_4
+1 -1
View File
@@ -10,7 +10,7 @@ steps:
DOCKER_BUILDKIT: "1" DOCKER_BUILDKIT: "1"
# Buildkite will automatically replace this with the actual commit hash # Buildkite will automatically replace this with the actual commit hash
VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}" VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}"
VLLM_VERSION: "v0.28.0" VLLM_VERSION: "v0.26.0"
priority: 100 priority: 100
timeout_in_minutes: 60 timeout_in_minutes: 60
soft_fail: false soft_fail: false
+7 -55
View File
@@ -5,12 +5,7 @@ set -ex
omni_source_dir=$(git rev-parse --show-toplevel) omni_source_dir=$(git rev-parse --show-toplevel)
: "${VLLM_VERSION:?VLLM_VERSION must be set}" base_image_name="xpu/vllm-omni-ci-base:${VLLM_VERSION:?VLLM_VERSION must be set}"
# Official upstream XPU image. It is published from vllm's own
# docker/Dockerfile.xpu, which is exactly what build_vllm_base() clones and
# builds below, so pulling it is equivalent to building the fallback base.
upstream_base_image="vllm/vllm-openai-xpu:${VLLM_VERSION}"
local_base_image="xpu/vllm-omni-ci-base:${VLLM_VERSION}"
image_name="xpu/vllm-omni-ci:${BUILDKITE_COMMIT:?BUILDKITE_COMMIT must be set}" image_name="xpu/vllm-omni-ci:${BUILDKITE_COMMIT:?BUILDKITE_COMMIT must be set}"
container_name="xpu_${BUILDKITE_COMMIT}_$( container_name="xpu_${BUILDKITE_COMMIT}_$(
tr -dc A-Za-z0-9 </dev/urandom | head -c 10 tr -dc A-Za-z0-9 </dev/urandom | head -c 10
@@ -39,51 +34,9 @@ docker_build() {
docker build "${out[@]}" "$@" -f docker/Dockerfile.xpu . docker build "${out[@]}" "$@" -f docker/Dockerfile.xpu .
} }
build_vllm_base() ( if [ -z "$(docker images -q "${base_image_name}")" ]; then
local vllm_source_dir docker_build "${base_image_name}" --target vllm-base --build-arg "VLLM_VERSION=${VLLM_VERSION}"
vllm_source_dir=$(mktemp -d) fi
trap 'rm -rf "${vllm_source_dir}"' EXIT
git clone --depth 1 --branch "${VLLM_VERSION}" \
https://github.com/vllm-project/vllm "${vllm_source_dir}"
local out=("${export_args[@]/'{{IMAGE}}'/${base_image_name}}")
docker build "${out[@]}" \
--target vllm-openai \
-f "${vllm_source_dir}/docker/Dockerfile.xpu" \
"${vllm_source_dir}"
)
# Resolve the vLLM base image, in priority order:
# 1. the published upstream image for VLLM_VERSION, pulled fresh (so moving
# tags like nightly are refreshed rather than served stale from disk);
# 2. a local copy of that same image, when the registry is unreachable;
# 3. VLLM_BASE, if that image is already on disk;
# 4. otherwise build VLLM_BASE from the matching vLLM source tag (~30min).
# VLLM_BASE names the fallback base and defaults to ${local_base_image}; set it
# to reuse or produce a base under a different tag.
resolve_vllm_base() {
if docker pull "${upstream_base_image}"; then
base_image_name="${upstream_base_image}"
return
fi
# Registry unreachable, but a previous run may have left the image behind.
if [ -n "$(docker images -q "${upstream_base_image}")" ]; then
echo "WARNING: could not pull ${upstream_base_image}; using local copy" >&2
base_image_name="${upstream_base_image}"
return
fi
echo "WARNING: ${upstream_base_image} unavailable remotely and locally" >&2
base_image_name="${VLLM_BASE:-${local_base_image}}"
if [ -z "$(docker images -q "${base_image_name}")" ]; then
echo "WARNING: building ${base_image_name} from vLLM ${VLLM_VERSION} source" >&2
build_vllm_base
fi
}
resolve_vllm_base
# Try building the docker image # Try building the docker image
docker_build "${image_name}" --build-arg "VLLM_BASE=${base_image_name}" --build-arg "VLLM_VERSION=${VLLM_VERSION}" docker_build "${image_name}" --build-arg "VLLM_BASE=${base_image_name}" --build-arg "VLLM_VERSION=${VLLM_VERSION}"
@@ -119,9 +72,8 @@ time timeout -k 30 30m docker run \
echo $ZE_AFFINITY_MASK echo $ZE_AFFINITY_MASK
pip install tblib==3.1.0 pip install tblib==3.1.0
cd /workspace/vllm-omni cd /workspace/vllm-omni
XPU_TEST_PATHS="tests/diffusion tests/dfx tests/e2e" pytest -v -s -m "core_model and xpu and B60"
pytest -v -s $XPU_TEST_PATHS -m "core_model and xpu and B60"
pytest -v -s tests/diffusion/quantization/test_mxfp8_config.py pytest -v -s tests/diffusion/quantization/test_mxfp8_config.py
pytest -v -s $XPU_TEST_PATHS -m "advanced_model and xpu and B60" pytest -v -s -m "advanced_model and xpu and B60"
pytest -v -s $XPU_TEST_PATHS -m "omni and xpu and B60" pytest -v -s -m "omni and xpu and B60"
' '
+104 -30
View File
@@ -3,10 +3,10 @@
steps: steps:
- label: ":buildkit: Build and Push NPU Test Image - A2" - label: ":buildkit: Build and Push NPU Test Image - A2"
key: image-build-a2 key: image-build-a2
agents: &npu-image-build-agents agents:
queue: "ascend-a2b3" queue: "ascend-a2b3"
resource_class: "npu-1" resource_class: "npu-1"
plugins: &npu-image-build-plugins plugins:
- kubernetes: - kubernetes:
metadata: metadata:
annotations: annotations:
@@ -46,47 +46,123 @@ steps:
IMAGE_NAME: "vllm-omni-ci-npu" IMAGE_NAME: "vllm-omni-ci-npu"
IMAGE_REGISTRY: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry" IMAGE_REGISTRY: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry"
BUILDKITD_ADDR: "tcp://buildkitd-service.buildkitd:1234" BUILDKITD_ADDR: "tcp://buildkitd-service.buildkitd:1234"
command: bash .buildkite/npu/scripts/build-npu-image.sh Dockerfile.npu.ci "$${VLLM_IMAGE_TAG}" command: |
set -ex
echo "--- Building and pushing NPU Test Image"
echo "Image: $${IMAGE_REGISTRY}/$${IMAGE_NAME}:$${VLLM_IMAGE_TAG}"
echo "buildkitd address: $${BUILDKITD_ADDR}"
if ! command -v buildctl &> /dev/null; then
echo "Installing buildctl..."
mkdir -p /tmp/buildkit
BUILDKIT_VERSION="v0.29.0"
wget -q "https://github.com/moby/buildkit/releases/download/$${BUILDKIT_VERSION}/buildkit-$${BUILDKIT_VERSION}.linux-arm64.tar.gz" -O /tmp/buildkit.tar.gz
tar -xzf /tmp/buildkit.tar.gz -C /tmp/buildkit
cp /tmp/buildkit/bin/buildctl /usr/local/bin/
fi
export DOCKER_CONFIG=/home/user/.docker
buildctl \
--addr="$${BUILDKITD_ADDR}" \
--tlscacert=/home/user/.docker/ca.pem \
--tlscert=/home/user/.docker/cert.pem \
--tlskey=/home/user/.docker/key.pem \
build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=./docker \
--opt filename=Dockerfile.npu.ci \
--secret id=dockerconfig,src=/home/user/.docker/config.json \
--output type=image,name=$${IMAGE_REGISTRY}/$${IMAGE_NAME}:$${VLLM_IMAGE_TAG},push=true \
--progress=plain
echo "--- Image pushed successfully"
echo "$${IMAGE_REGISTRY}/$${IMAGE_NAME}:$${VLLM_IMAGE_TAG}"
- label: ":buildkit: Build and Push NPU Test Image - A3" - label: ":buildkit: Build and Push NPU Test Image - A3"
key: image-build-a3 key: image-build-a3
agents: *npu-image-build-agents agents:
plugins: *npu-image-build-plugins queue: "ascend-a2b3"
resource_class: "npu-1"
plugins:
- kubernetes:
metadata:
annotations:
vault.hashicorp.com/agent-init-first: "true"
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-perms-ca.pem: "0400"
vault.hashicorp.com/agent-inject-perms-cert.pem: "0400"
vault.hashicorp.com/agent-inject-perms-config.json: "0400"
vault.hashicorp.com/agent-inject-perms-key.pem: "0400"
vault.hashicorp.com/agent-inject-secret-ca.pem: internal/data/ascend/buildkitd
vault.hashicorp.com/agent-inject-secret-cert.pem: internal/data/ascend/buildkitd
vault.hashicorp.com/agent-inject-secret-config.json: internal/data/ascend/buildkitd
vault.hashicorp.com/agent-inject-secret-key.pem: internal/data/ascend/buildkitd
vault.hashicorp.com/agent-inject-template-ca.pem: "{{- with secret \"internal/data/ascend/buildkitd\" -}}\n{{ .Data.data.RootCA }}\n{{- end }}"
vault.hashicorp.com/agent-inject-template-cert.pem: "{{- with secret \"internal/data/ascend/buildkitd\" -}}\n{{ .Data.data.ClientCaCert }}\n{{- end }}"
vault.hashicorp.com/agent-inject-template-config.json: "{{- with secret \"internal/data/ascend/buildkitd\" -}}\n{{ .Data.data.dockerConfig }}\n{{- end }}"
vault.hashicorp.com/agent-inject-template-key.pem: "{{- with secret \"internal/data/ascend/buildkitd\" -}}\n{{ .Data.data.ClientCaKey }}\n{{- end }}"
vault.hashicorp.com/agent-pre-populate-only: "true"
vault.hashicorp.com/agent-run-as-group: "1000"
vault.hashicorp.com/agent-run-as-user: "1000"
vault.hashicorp.com/agent-service-account-token-volume-name: token-vol
vault.hashicorp.com/role: ascend-gha-runners
vault.hashicorp.com/secret-volume-path: /home/user/.docker/
vault.hashicorp.com/tls-skip-verify: "true"
podSpecPatch:
volumes:
- name: token-vol
projected:
defaultMode: 420
sources:
- serviceAccountToken:
audience: api
expirationSeconds: 600
path: token
env: env:
VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}-a3" VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}-a3"
IMAGE_NAME: "vllm-omni-ci-npu" IMAGE_NAME: "vllm-omni-ci-npu"
IMAGE_REGISTRY: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry" IMAGE_REGISTRY: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry"
BUILDKITD_ADDR: "tcp://buildkitd-service.buildkitd:1234" BUILDKITD_ADDR: "tcp://buildkitd-service.buildkitd:1234"
command: bash .buildkite/npu/scripts/build-npu-image.sh Dockerfile.npu.ci "$${VLLM_IMAGE_TAG}" -a3 command: |
set -ex
- label: ":buildkit: Build and Push NPU Test Image - A5" echo "--- Building and pushing NPU Test Image"
key: image-build-a5 echo "Image: $${IMAGE_REGISTRY}/$${IMAGE_NAME}:$${VLLM_IMAGE_TAG}"
agents: *npu-image-build-agents echo "buildkitd address: $${BUILDKITD_ADDR}"
plugins: *npu-image-build-plugins
env:
VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}-a5"
IMAGE_NAME: "vllm-omni-ci-npu"
IMAGE_REGISTRY: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry"
BUILDKITD_ADDR: "tcp://buildkitd-service.buildkitd:1234"
command: bash .buildkite/npu/scripts/build-npu-image.sh Dockerfile.npu.ci "$${VLLM_IMAGE_TAG}" -a5
- label: ":buildkit: Build and Push NPU Test Image - 310P" if ! command -v buildctl &> /dev/null; then
key: image-build-310p echo "Installing buildctl..."
agents: *npu-image-build-agents mkdir -p /tmp/buildkit
plugins: *npu-image-build-plugins BUILDKIT_VERSION="v0.29.0"
env: wget -q "https://github.com/moby/buildkit/releases/download/$${BUILDKIT_VERSION}/buildkit-$${BUILDKIT_VERSION}.linux-arm64.tar.gz" -O /tmp/buildkit.tar.gz
VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}-310p" tar -xzf /tmp/buildkit.tar.gz -C /tmp/buildkit
IMAGE_NAME: "vllm-omni-ci-npu" cp /tmp/buildkit/bin/buildctl /usr/local/bin/
IMAGE_REGISTRY: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry" fi
BUILDKITD_ADDR: "tcp://buildkitd-service.buildkitd:1234"
command: bash .buildkite/npu/scripts/build-npu-image.sh Dockerfile.npu.ci "$${VLLM_IMAGE_TAG}" -310p export DOCKER_CONFIG=/home/user/.docker
buildctl \
--addr="$${BUILDKITD_ADDR}" \
--tlscacert=/home/user/.docker/ca.pem \
--tlscert=/home/user/.docker/cert.pem \
--tlskey=/home/user/.docker/key.pem \
build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=./docker \
--opt filename=Dockerfile.npu.ci.a3 \
--secret id=dockerconfig,src=/home/user/.docker/config.json \
--output type=image,name=$${IMAGE_REGISTRY}/$${IMAGE_NAME}:$${VLLM_IMAGE_TAG},push=true \
--progress=plain
echo "--- Image pushed successfully"
echo "$${IMAGE_REGISTRY}/$${IMAGE_NAME}:$${VLLM_IMAGE_TAG}"
- label: "Upload Ready Pipeline" - label: "Upload Ready Pipeline"
depends_on: depends_on:
- image-build-a2 - image-build-a2
- image-build-a3 - image-build-a3
- image-build-a5
- image-build-310p
key: upload-ready-pipeline key: upload-ready-pipeline
agents: agents:
queue: "cpu_queue_premerge" queue: "cpu_queue_premerge"
@@ -98,8 +174,6 @@ steps:
depends_on: depends_on:
- image-build-a2 - image-build-a2
- image-build-a3 - image-build-a3
- image-build-a5
- image-build-310p
key: upload-nightly-pipeline key: upload-nightly-pipeline
commands: commands:
- python3 .buildkite/common/scripts/upload_pipeline.py --upload .buildkite/npu/test-npu-nightly.yml - python3 .buildkite/common/scripts/upload_pipeline.py --upload .buildkite/npu/test-npu-nightly.yml
-48
View File
@@ -1,48 +0,0 @@
#!/usr/bin/env bash
set -ex
dockerfile=${1:?Dockerfile is required}
image_tag=${2:?Image tag is required}
device_suffix=${3:-}
image_name=${IMAGE_NAME:-vllm-omni-ci-npu}
image_registry=${IMAGE_REGISTRY:-swr.cn-southwest-2.myhuaweicloud.com/modelfoundry}
buildkitd_addr=${BUILDKITD_ADDR:-tcp://buildkitd-service.buildkitd:1234}
echo "--- Building and pushing NPU Test Image"
echo "Image: ${image_registry}/${image_name}:${image_tag}"
echo "Dockerfile: ${dockerfile}"
if ! command -v buildctl &> /dev/null; then
mkdir -p /tmp/buildkit
buildkit_version=v0.29.0
wget -q \
"https://github.com/moby/buildkit/releases/download/${buildkit_version}/buildkit-${buildkit_version}.linux-arm64.tar.gz" \
-O /tmp/buildkit.tar.gz
tar -xzf /tmp/buildkit.tar.gz -C /tmp/buildkit
cp /tmp/buildkit/bin/buildctl /usr/local/bin/
fi
build_args=()
if [[ -n "${device_suffix}" ]]; then
build_args+=(--opt "build-arg:VLLM_ASCEND_DEVICE_SUFFIX=${device_suffix}")
fi
export DOCKER_CONFIG=/home/user/.docker
buildctl \
--addr="${buildkitd_addr}" \
--tlscacert=/home/user/.docker/ca.pem \
--tlscert=/home/user/.docker/cert.pem \
--tlskey=/home/user/.docker/key.pem \
build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=./docker \
--opt "filename=${dockerfile}" \
"${build_args[@]}" \
--secret id=dockerconfig,src=/home/user/.docker/config.json \
--output "type=image,name=${image_registry}/${image_name}:${image_tag},push=true" \
--progress=plain
echo "--- Image pushed successfully"
echo "${image_registry}/${image_name}:${image_tag}"
+27 -55
View File
@@ -69,67 +69,12 @@ steps:
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1" VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
HF_TOKEN: "${HF_TOKEN}" HF_TOKEN: "${HF_TOKEN}"
commands: commands:
# Pin the Seed-TTS scorer to the second card: _get_eval_device() otherwise
# auto-picks npu:0, which the omni server under test already fills with its
# KV cache, so Whisper OOMs on `.to(device)` and every clip reports
# "ASR / WER failed".
- export SEED_TTS_WER_EVAL=1
- export SEED_TTS_EVAL_DEVICE=npu:1
- | - |
set +e set +e
pytest -s -v tests/e2e/accuracy/minicpmo_4_5/test_minicpmo_4_5.py -m "full_model and npu and A3" --run-level full_model pytest -s -v tests/e2e/accuracy/minicpmo_4_5/test_minicpmo_4_5.py -m "full_model and npu and A3" --run-level full_model
EXIT=$$? EXIT=$$?
buildkite-agent artifact upload "tests/e2e/accuracy/minicpmo_4_5/results/*.json" buildkite-agent artifact upload "tests/e2e/accuracy/minicpmo_4_5/results/*.json"
exit $$EXIT exit $$EXIT
- label: ":full_moon: Omni · MiniCPM-o 4.5 · Perf Test"
key: nightly-omni-performance-minicpmo-4-5-npu
timeout_in_minutes: 180
mirror_hardwares: a3_npu_2
env:
VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
HF_TOKEN: "${HF_TOKEN}"
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py -m "npu and A3" --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
- label: ":full_moon: Omni · MiniCPM-o 4.5 · Perf Test (A2)"
key: nightly-omni-performance-minicpmo-4-5-npu-a2
timeout_in_minutes: 180
mirror_hardwares: a2b3_npu_1
env:
VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
HF_TOKEN: "${HF_TOKEN}"
HF_HUB_OFFLINE: "1"
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py -m "npu and A2" --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
- label: ":full_moon: Omni · MiniCPM-o 4.5 · Duplex Seed-TTS Perf Test"
key: nightly-omni-performance-minicpmo-4-5-duplex-seed-tts-npu
timeout_in_minutes: 180
mirror_hardwares: a3_npu_2
env:
VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
HF_TOKEN: "${HF_TOKEN}"
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py -m "npu and A3" --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5_duplex_seed_tts.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT
- group: ":card_index_dividers: TTS Model Test" - group: ":card_index_dividers: TTS Model Test"
key: nightly-tts-test-group key: nightly-tts-test-group
depends_on: upload-nightly-pipeline depends_on: upload-nightly-pipeline
@@ -151,6 +96,33 @@ steps:
EXIT=$$? EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/*.json" buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
exit $$EXIT exit $$EXIT
- group: ":card_index_dividers: Diffusion X2I(&A&T) Model Test"
key: nightly-diffusion-x2iat-group
depends_on: upload-nightly-pipeline
if: >-
build.env("NIGHTLY") == "1" ||
build.pull_request.labels includes "nightly-test" ||
build.pull_request.labels includes "diffusion-x2iat-test"
steps:
- label: ":full_moon: Diffusion X2I(&A&T) · Perf Test · Qwen-Image-Edit-2511"
key: nightly-diffusion-x2iat-performance-qwen-image-edit-2511
timeout_in_minutes: 180
mirror_hardwares: a3_npu_8
env:
VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
HF_TOKEN: "${HF_TOKEN}"
DIFFUSION_ATTENTION_BACKEND: TORCH_SDPA
commands:
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
- export CACHE_DIT_VERSION=1.3.0
- |
set +e
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py -m npu --test-config-file tests/dfx/perf/tests/test_qwen_image_edit_2511_vllm_omni.json
EXIT=$$?
buildkite-agent artifact upload "tests/dfx/perf/results/diffusion_result_*.json"
buildkite-agent artifact upload "tests/dfx/perf/results/logs/*.log"
exit $$EXIT
- group: ":card_index_dividers: Diffusion I2I Model Test" - group: ":card_index_dividers: Diffusion I2I Model Test"
key: nightly-HunyuanImage3-x2i-test-group key: nightly-HunyuanImage3-x2i-test-group
depends_on: upload-nightly-pipeline depends_on: upload-nightly-pipeline
+1 -66
View File
@@ -1,60 +1,4 @@
steps: steps:
- group: ":card_index_dividers: Simple Test"
depends_on: upload-ready-pipeline
steps:
- label: "Platforms Test"
key: ready-platforms-test
mirror_hardwares: a3_npu_2
commands:
- >-
pytest -s -v
tests/platforms/npu/test_minicpmo_code2wav_npugraph.py
-m 'core_model and npu and A3 and omni'
--run-level 'core_model'
- label: "Diffusion NPU layer tests - A2"
key: ready-diffusion-npu-layer-tests-a2
mirror_hardwares: a2b3_npu_1
commands:
- >-
pytest -s -v
tests/diffusion/layers/test_activation.py
-m 'core_model and npu and A2'
--run-level 'core_model'
- label: "Diffusion NPU layer tests - A3"
key: ready-diffusion-npu-layer-tests
mirror_hardwares: a3_npu_2
commands:
- >-
pytest -s -v
tests/diffusion/layers/test_activation.py
tests/diffusion/layers/test_fused_qk_norm_rope_npu.py
tests/diffusion/models/minimax_h3/test_minimax_h3_qwen3vl_swiglu.py
tests/diffusion/models/minimax_h3/test_minimax_h3_modulation.py
-m 'core_model and npu and A3'
--run-level 'core_model'
- label: "Diffusion NPU layer tests - A5"
key: ready-diffusion-npu-layer-tests-a5
mirror_hardwares: a5_npu_2
commands:
- >-
pytest -s -v
tests/diffusion/layers/test_activation.py
-m 'core_model and npu and A5'
--run-level 'core_model'
- label: "Diffusion NPU layer tests - 310P"
key: ready-diffusion-npu-layer-tests-310p
mirror_hardwares: 310p_npu_1
commands:
- >-
pytest -s -v
tests/diffusion/layers/test_activation.py
-m 'core_model and npu and 310P'
--run-level 'core_model'
- group: ":card_index_dividers: Omni Model Test" - group: ":card_index_dividers: Omni Model Test"
key: ready-omni-group key: ready-omni-group
depends_on: upload-ready-pipeline depends_on: upload-ready-pipeline
@@ -73,22 +17,13 @@ steps:
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1" VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
commands: commands:
- pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex.py -m 'core_model and npu and A3 and omni' --run-level 'core_model' - pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex.py -m 'core_model and npu and A3 and omni' --run-level 'core_model'
- label: "Omni · MiniCPM-o 4.5 Perf Test"
timeout_in_minutes: 60
mirror_hardwares: a3_npu_2
env:
VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
commands:
- export BENCHMARK_DIR=tests/dfx/perf/results
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5_ready.json -m 'core_model and npu and A3 and omni'
- group: ":card_index_dividers: TTS Model Test" - group: ":card_index_dividers: TTS Model Test"
key: ready-tts-group key: ready-tts-group
depends_on: upload-ready-pipeline depends_on: upload-ready-pipeline
steps: steps:
- label: ":sound: TTS · Qwen3-TTS CustomVoice Test" - label: ":sound: TTS · Qwen3-TTS CustomVoice Test"
mirror_hardwares: a3_npu_2 mirror_hardwares: a2b3_npu_1
env: env:
VLLM_WORKER_MULTIPROC_METHOD: spawn VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1" VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
+1 -23
View File
@@ -40,11 +40,9 @@ steps:
commands: commands:
- "bash .buildkite/release/scripts/publish-release-wheels-pypi.sh" - "bash .buildkite/release/scripts/publish-release-wheels-pypi.sh"
# Auto-skip when NIGHTLY=1 (scheduled); otherwise require manual unblock.
- block: "Unblock to build release Docker images" - block: "Unblock to build release Docker images"
depends_on: ~ depends_on: ~
key: block-build-release-images key: block-build-release-images
if: build.env("NIGHTLY") != "1"
- group: "Build release Docker images" - group: "Build release Docker images"
key: "build-release-images" key: "build-release-images"
@@ -65,7 +63,7 @@ steps:
--progress plain \ --progress plain \
-f docker/Dockerfile.cuda . -f docker/Dockerfile.cuda .
- "docker push public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT-$(uname -m)" - "docker push public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT-$(uname -m)"
# Fallback default tag (no arch suffix) if arm64 build fails. # re-tag to default image tag and push, just in case arm64 build fails
- "docker tag public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT-$(uname -m) public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT" - "docker tag public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT-$(uname -m) public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT"
- "docker push public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT" - "docker push public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT"
env: env:
@@ -88,26 +86,6 @@ steps:
env: env:
DOCKER_BUILDKIT: "1" DOCKER_BUILDKIT: "1"
# NIGHTLY=1 only: promote ECR images to DockerHub nightly tags, then prune.
- label: "Publish nightly multi-arch image to DockerHub"
depends_on:
- build-release-image-x86
- build-release-image-arm64
if: build.env("NIGHTLY") == "1"
key: publish-nightly-images-dockerhub
agents:
queue: small_cpu_queue_release
commands:
- "bash .buildkite/release/scripts/publish-release-images.sh nightly"
- "bash .buildkite/release/scripts/cleanup-nightly-builds.sh"
plugins:
- docker-login#v3.0.0:
username: vllmbot
password-env: DOCKERHUB_TOKEN
env:
DOCKER_BUILDKIT: "1"
DOCKERHUB_USERNAME: "vllmbot"
- block: "Publish release images to DockerHub" - block: "Publish release images to DockerHub"
key: block-publish-release-images key: block-publish-release-images
depends_on: depends_on:
@@ -1,147 +0,0 @@
#!/bin/bash
# SPDX-License-Identifier: Apache-2.0
#
# Clean up old nightly builds from DockerHub (vllm/vllm-omni), keeping the
# newest 14 tags with the nightly- prefix.
set -ex
TAG_PREFIX="nightly-"
REPO="vllm/vllm-omni"
REPO_API_URL="https://hub.docker.com/v2/repositories/${REPO}/tags"
echo "Cleaning up tags with prefix: $TAG_PREFIX in repository: $REPO"
if [ -z "$DOCKERHUB_TOKEN" ]; then
echo "Error: DOCKERHUB_TOKEN environment variable is not set"
exit 1
fi
if [ -z "$DOCKERHUB_USERNAME" ]; then
echo "Error: DOCKERHUB_USERNAME environment variable is not set"
exit 1
fi
echo "Getting DockerHub bearer token..."
set +x
BEARER_TOKEN=$(curl -s -X POST \
-H "Content-Type: application/json" \
-d "{\"username\": \"$DOCKERHUB_USERNAME\", \"password\": \"$DOCKERHUB_TOKEN\"}" \
"https://hub.docker.com/v2/users/login" | jq -r '.token')
set -x
if [ -z "$BEARER_TOKEN" ] || [ "$BEARER_TOKEN" = "null" ]; then
echo "Error: Failed to get DockerHub bearer token"
exit 1
fi
get_all_tags() {
local page=1
local all_tags=""
# Hub `name` is a partial match (not a strict prefix). Use it to narrow the
# list, then enforce startswith() client-side. Exhaust API pages; do not
# stop merely because a page has no matching tags after filtering.
local name_query
name_query=$(printf '%s' "$TAG_PREFIX" | jq -sRr @uri)
while true; do
set +x
local response
response=$(curl -s -H "Authorization: Bearer $BEARER_TOKEN" \
"$REPO_API_URL?page=$page&page_size=100&name=${name_query}")
set -x
local page_count
page_count=$(echo "$response" | jq -r '.results | length')
if [ -z "$page_count" ] || [ "$page_count" = "0" ] || [ "$page_count" = "null" ]; then
break
fi
local tags
tags=$(echo "$response" | jq -r --arg prefix "$TAG_PREFIX" \
'.results[] | select(.name | startswith($prefix)) | "\(.last_updated)|\(.name)"')
if [ -n "$tags" ]; then
all_tags="$all_tags$tags"$'\n'
fi
page=$((page + 1))
done
echo "$all_tags" | sort -r | cut -d'|' -f2
}
delete_tag() {
local tag_name="$1"
echo "Deleting tag: $tag_name"
local delete_url="https://hub.docker.com/v2/repositories/${REPO}/tags/$tag_name"
set +x
local response http_code body
response=$(curl -s -w "\n%{http_code}" -X DELETE \
-H "Authorization: Bearer $BEARER_TOKEN" "$delete_url")
set -x
http_code=$(printf '%s\n' "$response" | tail -n1)
body=$(printf '%s\n' "$response" | sed '$d')
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
echo "Successfully deleted tag: $tag_name (HTTP ${http_code})"
return 0
fi
local detail=""
if echo "$body" | jq -e '.detail' > /dev/null 2>&1; then
detail=$(echo "$body" | jq -r '.detail')
elif [ -n "$body" ]; then
detail="$body"
else
detail="(empty response body)"
fi
echo "Error: Failed to delete tag $tag_name (HTTP ${http_code}): ${detail}"
return 1
}
echo "Fetching all tags from DockerHub..."
all_tags=$(get_all_tags)
if [ -z "$all_tags" ]; then
echo "No tags found to clean up"
exit 0
fi
total_tags=$(echo "$all_tags" | wc -l)
echo "Found $total_tags tags"
tags_to_keep=14
tags_to_delete=$((total_tags - tags_to_keep))
if [ $tags_to_delete -le 0 ]; then
echo "No tags need to be deleted (only $total_tags tags found, keeping $tags_to_keep)"
exit 0
fi
echo "Will delete $tags_to_delete old tags, keeping the newest $tags_to_keep"
tags_to_delete_list=$(echo "$all_tags" | tail -n +$((tags_to_keep + 1)))
if [ -z "$tags_to_delete_list" ]; then
echo "No tags to delete"
exit 0
fi
echo "Deleting old tags..."
delete_failures=0
while IFS= read -r tag; do
if [ -n "$tag" ]; then
if ! delete_tag "$tag"; then
delete_failures=$((delete_failures + 1))
fi
sleep 1
fi
done <<< "$tags_to_delete_list"
if [ "$delete_failures" -gt 0 ]; then
echo "Cleanup finished with ${delete_failures} deletion failure(s)"
exit 1
fi
echo "Cleanup completed successfully"
@@ -1,87 +1,50 @@
#!/bin/bash #!/bin/bash
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
# Publish Docker images from ECR staging to DockerHub. # Publish release Docker images from ECR to DockerHub.
# # Pulls per-arch images, tags with `latest` and versioned tags, pushes them,
# Usage: # then creates and pushes a multi-arch manifest.
# publish-release-images.sh # release mode (default)
# publish-release-images.sh release
# publish-release-images.sh nightly
#
# Release tags: latest, v<version>
# Nightly tags: nightly, nightly-<commit>
set -euo pipefail set -euo pipefail
MODE="${1:-release}" RELEASE_VERSION=$(buildkite-agent meta-data get release-version --default "" | sed 's/^v//')
if [ -z "${RELEASE_VERSION}" ]; then
echo "ERROR: release-version metadata not set"
exit 1
fi
COMMIT="$BUILDKITE_COMMIT"
DOCKERHUB_REPO="vllm/vllm-omni" DOCKERHUB_REPO="vllm/vllm-omni"
ECR_REPO="public.ecr.aws/q9t5s3a7/vllm-omni-release-repo" ECR_REPO="public.ecr.aws/q9t5s3a7/vllm-omni-release-repo"
COMMIT="$BUILDKITE_COMMIT"
PRIMARY_TAGS=()
EXTRA_MANIFEST_TAGS=()
case "${MODE}" in
release)
RELEASE_VERSION=$(buildkite-agent meta-data get release-version --default "" | sed 's/^v//')
if [ -z "${RELEASE_VERSION}" ]; then
echo "ERROR: release-version metadata not set"
exit 1
fi
PRIMARY_TAGS=("latest" "v${RELEASE_VERSION}")
;;
nightly)
# Commit-pinned manifest reuses nightly-{x86_64,aarch64} arch images.
PRIMARY_TAGS=("nightly")
EXTRA_MANIFEST_TAGS=("nightly-${COMMIT}")
;;
*)
echo "Usage: $0 {release|nightly}"
exit 2
;;
esac
echo "========================================" echo "========================================"
echo "Publishing ${MODE} images to ${DOCKERHUB_REPO}" echo "Publishing release images v${RELEASE_VERSION}"
echo " Commit: ${COMMIT}" echo " Commit: ${COMMIT}"
echo " Primary tags: ${PRIMARY_TAGS[*]}"
if [ ${#EXTRA_MANIFEST_TAGS[@]} -gt 0 ]; then
echo " Extra manifests: ${EXTRA_MANIFEST_TAGS[*]}"
fi
echo "========================================" echo "========================================"
# Login to ECR to pull staging images
aws ecr-public get-login-password --region us-east-1 | \ aws ecr-public get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin public.ecr.aws/q9t5s3a7 docker login --username AWS --password-stdin public.ecr.aws/q9t5s3a7
SRC_X86="${ECR_REPO}:${COMMIT}-x86_64" docker pull "${ECR_REPO}:${COMMIT}-x86_64"
SRC_ARM="${ECR_REPO}:${COMMIT}-aarch64" docker pull "${ECR_REPO}:${COMMIT}-aarch64"
docker pull "${SRC_X86}" docker tag "${ECR_REPO}:${COMMIT}-x86_64" "${DOCKERHUB_REPO}:latest-x86_64"
docker pull "${SRC_ARM}" docker tag "${ECR_REPO}:${COMMIT}-x86_64" "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-x86_64"
docker push "${DOCKERHUB_REPO}:latest-x86_64"
docker push "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-x86_64"
publish_manifest() { docker tag "${ECR_REPO}:${COMMIT}-aarch64" "${DOCKERHUB_REPO}:latest-aarch64"
local tag="$1" docker tag "${ECR_REPO}:${COMMIT}-aarch64" "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-aarch64"
local arch_tag_base="$2" docker push "${DOCKERHUB_REPO}:latest-aarch64"
docker manifest rm "${DOCKERHUB_REPO}:${tag}" || true docker push "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-aarch64"
docker manifest create \
"${DOCKERHUB_REPO}:${tag}" \
"${DOCKERHUB_REPO}:${arch_tag_base}-x86_64" \
"${DOCKERHUB_REPO}:${arch_tag_base}-aarch64"
docker manifest push "${DOCKERHUB_REPO}:${tag}"
}
for tag in "${PRIMARY_TAGS[@]}"; do docker manifest rm "${DOCKERHUB_REPO}:latest" || true
docker tag "${SRC_X86}" "${DOCKERHUB_REPO}:${tag}-x86_64" docker manifest rm "${DOCKERHUB_REPO}:v${RELEASE_VERSION}" || true
docker tag "${SRC_ARM}" "${DOCKERHUB_REPO}:${tag}-aarch64" docker manifest create "${DOCKERHUB_REPO}:latest" "${DOCKERHUB_REPO}:latest-x86_64" "${DOCKERHUB_REPO}:latest-aarch64"
docker push "${DOCKERHUB_REPO}:${tag}-x86_64" docker manifest create "${DOCKERHUB_REPO}:v${RELEASE_VERSION}" "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-x86_64" "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-aarch64"
docker push "${DOCKERHUB_REPO}:${tag}-aarch64" docker manifest push "${DOCKERHUB_REPO}:latest"
publish_manifest "${tag}" "${tag}" docker manifest push "${DOCKERHUB_REPO}:v${RELEASE_VERSION}"
done
ARCH_BASE="${PRIMARY_TAGS[0]}"
for tag in "${EXTRA_MANIFEST_TAGS[@]}"; do
publish_manifest "${tag}" "${ARCH_BASE}"
done
echo "" echo ""
echo "Successfully published ${MODE} images" echo "Successfully published release images for v${RELEASE_VERSION}"
+210 -111
View File
@@ -1,6 +1,6 @@
--- ---
name: add-diffusion-model name: add-diffusion-model
description: Add a new diffusion model (text-to-image, text-to-video, image-to-video, text-to-audio, image editing) to vLLM-Omni, including native non-Diffusers ports, reference-parity validation, Cache-DiT, offload, and parallelism support (TP, SP/USP, CFG-Parallel, HSDP). Use when integrating or reviewing a new diffusion model, porting a Diffusers pipeline or custom model repository, creating a DiT adapter, reusing shared examples, or qualifying multi-GPU and memory optimizations. description: Add a new diffusion model (text-to-image, text-to-video, image-to-video, text-to-audio, image editing) to vLLM-Omni, including Cache-DiT acceleration and parallelism support (TP, SP/USP, CFG-Parallel, HSDP). Use when integrating a new diffusion model, porting a diffusers pipeline or a custom model repo to vllm-omni, creating a new DiT transformer adapter, adding diffusion model support, or enabling multi-GPU parallelism and cache acceleration for an existing model.
--- ---
# Adding a Diffusion Model to vLLM-Omni # Adding a Diffusion Model to vLLM-Omni
@@ -26,16 +26,9 @@ Check the model's HF repo for `model_index.json`. This determines your path:
|----------|----------------|----------------| |----------|----------------|----------------|
| **Already supported** | `_class_name` in `model_index.json` matches a key in `_DIFFUSION_MODELS` in `registry.py` | Skip implementation, then validate model-specific examples, tests, and docs as needed | | **Already supported** | `_class_name` in `model_index.json` matches a key in `_DIFFUSION_MODELS` in `registry.py` | Skip implementation, then validate model-specific examples, tests, and docs as needed |
| **Diffusers-based** | Has standard `model_index.json` with `_diffusers_version`, subfolders for `transformer/`, `vae/`, etc. | Follow **Path A** below | | **Diffusers-based** | Has standard `model_index.json` with `_diffusers_version`, subfolders for `transformer/`, `vae/`, etc. | Follow **Path A** below |
| **Native non-Diffusers model** | No Diffusers index, non-standard checkpoint hierarchy, or custom architecture in a separate repo | Follow **Path B** below; port the runtime natively unless an external adapter was explicitly requested | | **Custom/private repo** | No diffusers `model_index.json`, weights in non-standard format, custom model code in a separate git repo | Follow **Path B** below |
| **Hybrid** | Has some diffusers components (VAE) but custom transformer/fusion | Mix of Path A and Path B | | **Hybrid** | Has some diffusers components (VAE) but custom transformer/fusion | Mix of Path A and Path B |
Before coding, write a short integration contract covering runtime ownership,
checkpoint discovery, reference revision, I/O geometry, attention semantics,
CFG behavior, auxiliary components, target hardware, and the default deployment.
For Path B, hybrid models, or any optimization work, read
[references/native-model-integration-checklist.md](references/native-model-integration-checklist.md)
and use its phase gates.
## Path A: Diffusers-Based Model ## Path A: Diffusers-Based Model
For models with a standard diffusers layout. See [references/transformer-adaptation.md](references/transformer-adaptation.md) for detailed code patterns. For models with a standard diffusers layout. See [references/transformer-adaptation.md](references/transformer-adaptation.md) for detailed code patterns.
@@ -110,15 +103,9 @@ For custom loop structures, follow `vllm_omni/diffusion/models/progress_bar.py`
--- ---
## Path B: Native / Non-Diffusers Model ## Path B: Custom/Private Repo Model
For models without a Diffusers pipeline—weights in custom formats and model For models without a diffusers pipeline — weights in custom format, model code in a private repo. Real examples: DreamID-Omni, BAGEL, HunyuanImage3. See [references/custom-model-patterns.md](references/custom-model-patterns.md) for concrete custom-model integration patterns.
code in another public or private repository. Treat that repository as a pinned
correctness oracle. A request for native support means the vLLM-Omni runtime
must not import the reference implementation; an external adapter is appropriate
only when the requested scope explicitly permits that dependency. See
[references/custom-model-patterns.md](references/custom-model-patterns.md) for
concrete integration patterns.
### B1. Understand the reference repo ### B1. Understand the reference repo
@@ -139,25 +126,16 @@ This is the key design decision for custom models. Follow these placement rules:
| **Pipeline orchestration** (init, forward, denoise loop) | `vllm_omni/diffusion/models/<name>/pipeline_<name>.py` | Always required | | **Pipeline orchestration** (init, forward, denoise loop) | `vllm_omni/diffusion/models/<name>/pipeline_<name>.py` | Always required |
| **Custom transformer/backbone** (ported and adapted to vllm-omni) | `vllm_omni/diffusion/models/<name>/<name>_transformer.py` or similar | `wan2_2.py`, `fusion.py`, `bagel_transformer.py` | | **Custom transformer/backbone** (ported and adapted to vllm-omni) | `vllm_omni/diffusion/models/<name>/<name>_transformer.py` or similar | `wan2_2.py`, `fusion.py`, `bagel_transformer.py` |
| **Custom sub-models** (VAE, fusion, autoencoder) | `vllm_omni/diffusion/models/<name>/` as separate files | `autoencoder.py`, `fusion.py` | | **Custom sub-models** (VAE, fusion, autoencoder) | `vllm_omni/diffusion/models/<name>/` as separate files | `autoencoder.py`, `fusion.py` |
| **Reference-only code** | Keep outside the runtime; use a pinned revision for golden outputs and architecture analysis | Reference inference script | | **External dependency code** (original repo utilities) | **External repo**, installed via download script or pip | `dreamid_omni` package via git clone |
| **Explicit external adapter dependency** | External package, only when the requested scope and maintainer direction allow it | Compatibility adapter, not native support |
| **Hardcoded model configs** | Module-level dicts in pipeline file | `VIDEO_CONFIG`, `AUDIO_CONFIG` dicts | | **Hardcoded model configs** | Module-level dicts in pipeline file | `VIDEO_CONFIG`, `AUDIO_CONFIG` dicts |
| **Download/setup script** | `examples/offline_inference/<name>/download_<name>.py` | `download_<name>.py` | | **Download/setup script** | `examples/offline_inference/<name>/download_<name>.py` | `download_dreamid_omni.py` |
| **Custom `model_index.json`** | Generated by download script, placed at model root | Minimal: `{"_class_name": "YourPipeline", ...}` | | **Custom `model_index.json`** | Generated by download script, placed at model root | Minimal: `{"_class_name": "YourPipeline", ...}` |
### B3. Handle external dependencies ### B3. Handle external dependencies
If the model's code lives in a separate git repo, first decide whether the If the model's code lives in a separate git repo:
requested deliverable is native support or an external adapter. Do not silently
choose the adapter path.
**Option 1: Port the code directly** (default for native support) **Option 1: Import with graceful fallback** (recommended for models with external utils)
Copy the essential model files into `vllm_omni/diffusion/models/<name>/` and
adapt them to shared vLLM-Omni contracts. Keep checkpoint loading strict and
use the pinned reference only to generate parity evidence.
**Option 2: Import with graceful fallback** (adapter scope only)
```python ```python
try: try:
@@ -169,15 +147,17 @@ except ImportError:
) )
``` ```
Use an external runtime dependency only when the user explicitly requested an **Option 2: Port the code directly** (preferred when feasible)
adapter or a maintainer approved the exception. Document the dependency,
pinned revision, installation path, and unsupported native features. Copy the essential model files into `vllm_omni/diffusion/models/<name>/` and adapt them. This avoids external dependencies. BAGEL does this — `autoencoder.py` and `bagel_transformer.py` are ported directly.
**Decision criteria**: Port if the code is self-contained and won't diverge. Use external deps if the model repo is actively maintained and the code is complex.
### B4. Handle custom weight loading ### B4. Handle custom weight loading
Custom models have two common patterns for weight loading: Custom models have two common patterns for weight loading:
**Pattern 1: Bypass standard loader** (eager custom init) **Pattern 1: Bypass standard loader** (DreamID-Omni style)
When the original model has complex custom init functions that load weights in `__init__`: When the original model has complex custom init functions that load weights in `__init__`:
@@ -241,8 +221,7 @@ class CustomPipeline(nn.Module):
### B5. Create the `model_index.json` ### B5. Create the `model_index.json`
Prefer a `model_index.json` at the model root when vLLM-Omni owns an assembled Custom models need a `model_index.json` at the model root for vllm-omni to discover them. For custom models, this is minimal:
checkpoint directory. For custom models, this is minimal:
```json ```json
{ {
@@ -251,14 +230,7 @@ checkpoint directory. For custom models, this is minimal:
} }
``` ```
The `_class_name` must match a key in `_DIFFUSION_MODELS` in `registry.py`. The `_class_name` must match a key in `_DIFFUSION_MODELS` in `registry.py`. Additional keys are model-specific (accessed via `od_config.model_config`).
Additional keys are model-specific (accessed via `od_config.model_config`).
If the released repository is immutable and has neither a root `config.json`
nor a Diffusers index, add it to a generic native-checkpoint signature resolver:
match the exact Hub ID or a distinctive set of local files and return the
pipeline class. Do not use model-name substrings or add parallel one-off
predicates in CLI, config, and serving consumers.
If the model's weights come from multiple HF repos, write a **download script** that: If the model's weights come from multiple HF repos, write a **download script** that:
1. Downloads from each repo 1. Downloads from each repo
@@ -320,9 +292,7 @@ Use the appropriate existing example script:
| Image-to-Image | `examples/offline_inference/image_to_image/image_edit.py` | | Image-to-Image | `examples/offline_inference/image_to_image/image_edit.py` |
| Text-to-Audio | `examples/offline_inference/text_to_audio/text_to_audio.py` | | Text-to-Audio | `examples/offline_inference/text_to_audio/text_to_audio.py` |
Reuse these shared scripts even for custom models when their request and output For custom/Omni models that don't fit these categories, create a dedicated example script.
contracts fit. Create a dedicated model script only when the shared category
cannot represent the protocol, and document that gap in the PR.
**Validation**: No errors, output is meaningful, quality matches reference implementation. **Validation**: No errors, output is meaningful, quality matches reference implementation.
@@ -330,17 +300,13 @@ See [references/troubleshooting.md](references/troubleshooting.md) for common er
### Step 6: Add Example Scripts ### Step 6: Add Example Scripts
Only when the shared category scripts cannot represent the model, create: For Omni or custom models, create:
- `examples/offline_inference/your_model_name/` — offline script + README - `examples/offline_inference/your_model_name/` — offline script + README
- `examples/online_serving/your_model_name/` — server script + client - `examples/online_serving/your_model_name/` — server script + client
- Download script if weights require assembly from multiple sources - Download script if weights require assembly from multiple sources
### Step 7: Update Documentation ### Step 7: Update Documentation
Follow the [`add-recipe` skill](../add-recipe/SKILL.md) to add or update the
model-family recipe and its `recipes/README.md` row with verified specifications,
hardware, commands, feature links, and qualification evidence.
Required updates: Required updates:
1. `docs/user_guide/diffusion/parallelism/overview.md` — parallelism support overview/table 1. `docs/user_guide/diffusion/parallelism/overview.md` — parallelism support overview/table
2. `docs/user_guide/diffusion/cpu_offload.md` — if CPU offload supported (add to supported models table) 2. `docs/user_guide/diffusion/cpu_offload.md` — if CPU offload supported (add to supported models table)
@@ -372,7 +338,7 @@ Classify the model's **CI priority** first:
**L2 & L3 online — same file, dual marks on the baseline smoke:** The **first / simplest** case in `test_{slug}.py` (default deploy, minimal steps, single prompt) should carry **both** `@pytest.mark.core_model` and `@pytest.mark.advanced_model` on the **same** function so L2 (`test-ready.yml`) and L3 (`test-merge.yml`) share one smoke test. Heavier deploy variants or API paths in the same file use **`advanced_model` only**. When L3 moves to nightly, migrate those heavier cases into `test_{slug}_expansion.py` with `full_model` and remove the dedicated `test-merge.yml` job (see `test_longcat_image_expansion.py`, `test_qwen_image_expansion.py`). **L2 & L3 online — same file, dual marks on the baseline smoke:** The **first / simplest** case in `test_{slug}.py` (default deploy, minimal steps, single prompt) should carry **both** `@pytest.mark.core_model` and `@pytest.mark.advanced_model` on the **same** function so L2 (`test-ready.yml`) and L3 (`test-merge.yml`) share one smoke test. Heavier deploy variants or API paths in the same file use **`advanced_model` only**. When L3 moves to nightly, migrate those heavier cases into `test_{slug}_expansion.py` with `full_model` and remove the dedicated `test-merge.yml` job (see `test_longcat_image_expansion.py`, `test_qwen_image_expansion.py`).
**L4 design (high priority):** Combine multiple supported features (Cache-DiT, TP, USP, CFG, HSDP, CPU offload, quantization) into **few parametrized** `OmniServerParams` rows so each feature appears in at least one case without exploding GPU jobs. Shard single-GPU vs multi-GPU cases across the nightly X2I/X2V function steps (`cards_1` vs `not cards_1`). **L4 design (high priority):** Combine multiple supported features (Cache-DiT, TP, USP, CFG, HSDP, CPU offload, quantization) into **few parametrized** `OmniServerParams` rows so each feature appears in at least one case without exploding GPU jobs. Shard single-GPU vs multi-GPU cases across the nightly X2I/X2V function steps (1-GPU vs `distributed_cuda`).
**L4 design (medium / low):** One or two parametrized rows covering the best quality/perf trade-off; skip perf JSON unless the model is high priority. **L4 design (medium / low):** One or two parametrized rows covering the best quality/perf trade-off; skip perf JSON unless the model is high priority.
@@ -380,20 +346,107 @@ Classify the model's **CI priority** first:
**Keep model-specific code inside test modules — not `tests/helpers/{slug}.py`:** deploy constants, prompts, sampling dicts, and inline `request_config` / `form_data` belong in each `test_{slug}.py` and `test_{slug}_expansion.py`. Do not add per-model files under `tests/helpers/`; reuse only repo-wide harness (`mark`, `media`, `runtime`, `stage_config`, `assertions`). **L2+ online/offline e2e:** reuse or add `send_*_request` in `tests/helpers/runtime.py` — tests call the handler, not raw `omni.generate` / HTTP. See [vllm-omni-test skill](../vllm-omni-test/SKILL.md) § **Runtime send helpers**. **Keep model-specific code inside test modules — not `tests/helpers/{slug}.py`:** deploy constants, prompts, sampling dicts, and inline `request_config` / `form_data` belong in each `test_{slug}.py` and `test_{slug}_expansion.py`. Do not add per-model files under `tests/helpers/`; reuse only repo-wide harness (`mark`, `media`, `runtime`, `stage_config`, `assertions`). **L2+ online/offline e2e:** reuse or add `send_*_request` in `tests/helpers/runtime.py` — tests call the handler, not raw `omni.generate` / HTTP. See [vllm-omni-test skill](../vllm-omni-test/SKILL.md) § **Runtime send helpers**.
Keep the model suite proportional using the six distinct failure owners in the
native integration checklist. Combine supported features into a few
parametrized E2E rows instead of creating one test per optimization.
### Step 9: Add Cache-DiT Acceleration ### Step 9: Add Cache-DiT Acceleration
Add caching only after uncached single-device correctness. Read Cache-DiT accelerates inference by caching intermediate computation results across denoising steps. After your model is working correctly on a single GPU, add cache-dit support.
[references/cache-dit-patterns.md](references/cache-dit-patterns.md), use the
automatic single-block-list path when possible, and add a registered
`BlockAdapter` only for genuinely custom block topology.
Verify a real cache hit and compare quality with the uncached baseline. Make a See [references/cache-dit-patterns.md](references/cache-dit-patterns.md) for detailed code patterns.
speed claim only at a realistic step count where warmup permits hits; an
all-warmup smoke proves integration, not acceleration. #### 9a. Determine your model type
| Model Type | Description | Action |
|------------|-------------|--------|
| **Standard single-transformer** | One transformer with one `ModuleList` of blocks | No code needed — `CacheDiTBackend` auto-detects via `enable_cache_for_dit()` |
| **Multi-block-list** | One transformer with multiple block lists (e.g., `transformer_blocks` + `single_transformer_blocks`) | Write custom enabler with `BlockAdapter` |
| **Dual-transformer** | Two transformers (e.g., high-noise + low-noise) | Write custom enabler with `BlockAdapter` wrapping both |
#### 9b. Standard models — verify automatic support
For standard single-transformer models, test directly:
```python
omni = Omni(
model="your-model-name",
cache_backend="cache_dit",
cache_config={
"Fn_compute_blocks": 1,
"Bn_compute_blocks": 0,
"max_warmup_steps": 4,
}
)
```
Check logs for "Cache-dit enabled successfully on xxx". If it works, skip to Step 9e.
#### 9c. Custom architectures — write a custom enabler
For multi-block-list or dual-transformer models, write a custom enabler function:
```python
from cache_dit import BlockAdapter, ForwardPattern, ParamsModifier, DBCacheConfig
def enable_cache_for_your_model(pipeline, cache_config):
db_cache_config = DBCacheConfig(
num_inference_steps=None,
Fn_compute_blocks=cache_config.Fn_compute_blocks,
Bn_compute_blocks=cache_config.Bn_compute_blocks,
max_warmup_steps=cache_config.max_warmup_steps,
max_cached_steps=cache_config.max_cached_steps,
max_continuous_cached_steps=cache_config.max_continuous_cached_steps,
residual_diff_threshold=cache_config.residual_diff_threshold,
)
cache_dit.enable_cache(
BlockAdapter(
transformer=pipeline.transformer,
blocks=[
pipeline.transformer.transformer_blocks,
pipeline.transformer.single_transformer_blocks,
],
forward_pattern=[ForwardPattern.Pattern_1, ForwardPattern.Pattern_1],
params_modifiers=[ParamsModifier(...)],
),
cache_config=db_cache_config,
)
def refresh_cache_context(pipeline, num_inference_steps, verbose=True):
cache_dit.refresh_context(
pipeline.transformer, num_inference_steps=num_inference_steps, verbose=verbose
)
return refresh_cache_context
```
#### 9d. Register the custom enabler
Add your enabler to `CUSTOM_DIT_ENABLERS` in
`vllm_omni/diffusion/cache/cachedit/model_specific.py`:
```python
CUSTOM_DIT_ENABLERS = {
"Wan22Pipeline": enable_cache_for_wan22,
"LongCatImagePipeline": enable_cache_for_longcat_image,
"YourModelPipeline": enable_cache_for_your_model, # Add here
}
```
#### 9e. Test Cache-DiT
```python
omni = Omni(
model="your-model-name",
cache_backend="cache_dit",
cache_config={
"Fn_compute_blocks": 1, "Bn_compute_blocks": 0,
"max_warmup_steps": 4, "residual_diff_threshold": 0.24,
}
)
images = omni.generate("a beautiful landscape",
OmniDiffusionSamplingParams(num_inference_steps=50))
```
**Verify**: 1) logs show cache enabled, 2) 1.5-2x speedup, 3) output quality acceptable vs baseline.
If quality degrades, lower `residual_diff_threshold` (try 0.12-0.18) or increase `max_warmup_steps` (try 6-8).
--- ---
@@ -407,30 +460,107 @@ See [references/parallelism-patterns.md](references/parallelism-patterns.md) for
#### 10a. Tensor Parallelism (TP) #### 10a. Tensor Parallelism (TP)
Replace compatible projections with vLLM parallel linears, preserve checkpoint Shards DiT linear layers across GPUs. Requires code changes in the transformer.
fusion/loading, and use local head counts. Require query/KV head divisibility
and compare a multi-rank forward with the one-rank oracle. **What to change in the transformer**:
1. Replace `nn.Linear` with `ColumnParallelLinear` / `RowParallelLinear` / `QKVParallelLinear`
2. Update `load_weights()` to handle QKV fusion with `stacked_params_mapping`
3. Use `self.to_qkv.num_heads` (local heads) instead of total heads for split sizes
```python
from vllm.model_executor.layers.linear import (
QKVParallelLinear, RowParallelLinear, ColumnParallelLinear,
)
# Attention: QKV → RowParallel output
self.to_qkv = QKVParallelLinear(dim, head_dim, num_heads, num_kv_heads)
self.to_out = RowParallelLinear(dim, dim, input_is_parallel=True)
# FFN: ColumnParallel → RowParallel
self.w1 = ColumnParallelLinear(dim, ffn_dim)
self.w2 = RowParallelLinear(ffn_dim, dim, input_is_parallel=True)
```
**Constraints**: `num_heads % tp_size == 0` and `num_kv_heads % tp_size == 0`.
**Test**: `--tensor-parallel-size 2`
#### 10b. Sequence Parallelism (SP / USP) #### 10b. Sequence Parallelism (SP / USP)
Prefer the declarative `_sp_plan`. For packed variable-length attention or Splits sequence tokens across GPUs. Non-intrusive via `_sp_plan` on the transformer class — no changes to `forward()`.
learned-sink LSE correction, keep model math explicit and reuse shared exchange
utilities. Validate uneven sequence splits, RoPE coordinates, and outputs **What to change in the transformer**:
against the one-rank oracle.
Add `_sp_plan` class attribute:
```python
from vllm_omni.diffusion.distributed.sp_plan import (
SequenceParallelInput, SequenceParallelOutput,
)
class YourTransformer(nn.Module):
_sp_plan = {
"blocks.0": {
"hidden_states": SequenceParallelInput(split_dim=1, expected_dims=3),
},
"proj_out": SequenceParallelOutput(gather_dim=1, expected_dims=3),
}
```
If inline tensor ops (e.g., `torch.cat`) exist between shard/gather points, extract them into `nn.Module` submodules so hooks can intercept them.
For RoPE that needs splitting, add an entry for the RoPE module with `split_output=True`.
**Test**: `--ulysses-degree 2` (offline) or `--usp 2` (online serving)
#### 10c. CFG Parallel #### 10c. CFG Parallel
Confirm the model uses CFG. Then reuse `CFGParallelMixin`, overriding prediction Distributes positive/negative CFG branches across 2 GPUs. Requires the pipeline to inherit `CFGParallelMixin`.
or recombination only for non-standard or multi-output pipelines. Distinguish a
packed positive/negative implementation from two independent branches and **What to change in the pipeline**:
validate the two-rank result against the packed one-rank oracle.
```python
from vllm_omni.diffusion.distributed.cfg_parallel import CFGParallelMixin
class YourPipeline(nn.Module, CFGParallelMixin):
def diffuse(self, ...) -> torch.Tensor:
for i, t in enumerate(timesteps):
positive_kwargs = {...}
negative_kwargs = {...} if do_true_cfg else None
noise_pred = self.predict_noise_maybe_with_cfg(
do_true_cfg=do_true_cfg, true_cfg_scale=cfg_scale,
positive_kwargs=positive_kwargs, negative_kwargs=negative_kwargs,
)
latents = self.scheduler_step_maybe_with_cfg(
noise_pred, t, latents, do_true_cfg
)
return latents
```
Override `predict_noise()` if your transformer call is non-standard. Override `combine_cfg_noise()` for multi-output models (e.g., video + audio).
**Constraint**: Exactly 2 GPUs. Only for models using classifier-free guidance.
**Test**: `--cfg-parallel-size 2`
#### 10d. HSDP (Hybrid Sharded Data Parallel) #### 10d. HSDP (Hybrid Sharded Data Parallel)
Declare layer shard conditions and ignored rank-local modules; preserve mixed Shards transformer weights via PyTorch FSDP2 to reduce per-GPU VRAM. No code changes to the forward pass — just add a class attribute.
checkpoint dtypes. HSDP cannot combine with TP. Measure parameter loading,
FSDP materialization, warm HBM, and host PSS rather than assuming sharding saves **What to change in the transformer**:
peak memory. Keep the resident layout as default if HSDP is worse.
```python
class YourTransformer(nn.Module):
@staticmethod
def _is_transformer_block(name: str, module) -> bool:
return "blocks" in name and name.split(".")[-1].isdigit()
_hsdp_shard_conditions = [_is_transformer_block]
```
**Constraint**: Cannot combine with TP. For standalone HSDP, set `hsdp_shard_size` explicitly.
**Test**: `--use-hsdp` or `DiffusionParallelConfig(use_hsdp=True)`
#### 10e. Update parallelism documentation #### 10e. Update parallelism documentation
@@ -469,12 +599,6 @@ Pipelines without `SupportsComponentDiscovery` fall back to scanning
well-known attribute names (`transformer`, `text_encoder`, `vae`, well-known attribute names (`transformer`, `text_encoder`, `vae`,
etc.), which fails for non-standard names. etc.), which fails for non-standard names.
Keep model-specific checkpoint paths, nested block aliases, layout transforms,
and component lifecycles in the model package. Change a shared offloader only
for a general contract, demonstrate another consumer or a framework-level bug,
and add one focused shared regression. Avoid `if ModelName` branches in shared
backends.
### Step 12: Performance Profiling ### Step 12: Performance Profiling
After verifying correctness and implementing parallelism/caching, profile the model's performance to identify bottlenecks and ensure optimal execution. After verifying correctness and implementing parallelism/caching, profile the model's performance to identify bottlenecks and ensure optimal execution.
@@ -484,32 +608,8 @@ See the [Profiling Single-Stage Diffusion](../../../docs/contributing/profiling.
2. Using Nsight Systems (`nsys`) with `profiler: "cuda"` for low-overhead CUDA traces. 2. Using Nsight Systems (`nsys`) with `profiler: "cuda"` for low-overhead CUDA traces.
3. Controlling profiling via `omni.start_profile()` and `omni.stop_profile()`. 3. Controlling profiling via `omni.start_profile()` and `omni.stop_profile()`.
Report cold E2E, warm user latency, steady-state wave time, peak allocated and
reserved HBM, host PSS for the full process tree, and stage boundaries. State
whether prompt encoding and VAE/audio decoding are included. For multi-device
layouts, plot user latency against throughput per device and keep different
denoising-step counts on separate Pareto frontiers.
--- ---
## Pre-commit conventions
New library files must pass the local gates in
[docs/contributing/README.md](../../../docs/contributing/README.md#linting).
That page is the full hook list (SPDX, forbidden imports including Hugging Face
Hub / Triton / pickle, `torch.cuda`, mypy, test marks, markdownlint, Buildkite,
shellcheck). In particular:
- SPDX copyright is `vLLM-Omni project` (stale `vLLM project` is rewritten).
- Use `import regex as re` and `pybase64` in `vllm_omni/`; do not import stdlib
`re` or `base64`. Hugging Face Hub downloads go through
`vllm.transformers_utils.repo_utils`.
- Do not add `torch.cuda.*` call sites; use `current_omni_platform`.
- New `tests/**/test_*.py` files need a CI level mark and a hardware mark.
- Do not expand `CHECK_IMPORTS[*].allowed_files` or `ALLOWED_FILES` without review.
- GitHub Actions skips SPDX/shellcheck/mypy-3.10/test-marks/markdownlint; run
`pre-commit` locally.
## Iterative Development Tips ## Iterative Development Tips
1. **Start minimal**: Basic generation first, no parallelism/caching 1. **Start minimal**: Basic generation first, no parallelism/caching
@@ -517,7 +617,7 @@ shellcheck). In particular:
3. **Use small models**: Test with smaller variants first 3. **Use small models**: Test with smaller variants first
4. **Check tensor shapes**: Most errors are reshape mismatches in attention 4. **Check tensor shapes**: Most errors are reshape mismatches in attention
5. **Add features incrementally**: Single GPU → TP → SP → CFG → HSDP → Cache-DiT 5. **Add features incrementally**: Single GPU → TP → SP → CFG → HSDP → Cache-DiT
6. **For custom models**: Run the pinned reference separately, then port the runtime natively; do not ship temporary imports from the reference implementation 6. **For custom models**: Get the model running with the original code first, then progressively replace components with vllm-omni equivalents
7. **Cache-DiT before parallelism tuning**: Cache-DiT is lossy — verify quality at baseline before combining with parallelism 7. **Cache-DiT before parallelism tuning**: Cache-DiT is lossy — verify quality at baseline before combining with parallelism
8. **Combine lossless + lossy**: e.g., TP + SP + Cache-DiT for maximum throughput 8. **Combine lossless + lossy**: e.g., TP + SP + Cache-DiT for maximum throughput
@@ -526,7 +626,6 @@ shellcheck). In particular:
- [vllm-omni-test skill](../vllm-omni-test/SKILL.md) — L1–L4 markers, naming, Buildkite wiring, run commands - [vllm-omni-test skill](../vllm-omni-test/SKILL.md) — L1–L4 markers, naming, Buildkite wiring, run commands
- [Transformer Adaptation](references/transformer-adaptation.md) — porting transformers from diffusers - [Transformer Adaptation](references/transformer-adaptation.md) — porting transformers from diffusers
- [Custom Model Patterns](references/custom-model-patterns.md) — patterns for non-diffusers models - [Custom Model Patterns](references/custom-model-patterns.md) — patterns for non-diffusers models
- [Native Model Integration Checklist](references/native-model-integration-checklist.md) — ownership boundaries, phase gates, qualification matrix, and review evidence
- [Parallelism Patterns](references/parallelism-patterns.md) — TP, SP/USP, CFG parallel, HSDP implementation details - [Parallelism Patterns](references/parallelism-patterns.md) — TP, SP/USP, CFG parallel, HSDP implementation details
- [Cache-DiT Patterns](references/cache-dit-patterns.md) — cache-dit acceleration for standard and custom architectures - [Cache-DiT Patterns](references/cache-dit-patterns.md) — cache-dit acceleration for standard and custom architectures
- [Troubleshooting](references/troubleshooting.md) — common errors and fixes - [Troubleshooting](references/troubleshooting.md) — common errors and fixes
@@ -9,9 +9,7 @@ Three caching strategies:
- **TaylorSeer**: Calibration-based prediction using Taylor expansion to estimate block outputs - **TaylorSeer**: Calibration-based prediction using Taylor expansion to estimate block outputs
- **SCM** (Step Computation Masking): Dynamic step skipping based on configurable policies - **SCM** (Step Computation Masking): Dynamic step skipping based on configurable policies
Speedup is model-, step-, and policy-dependent. Treat a cache-enabled run as **Typical speedup**: 1.5-2.5x depending on model and configuration.
integration evidence until logs or a focused test show real hits and repeated
timings show an improvement over the uncached baseline.
**Official docs**: https://docs.vllm.ai/projects/vllm-omni/en/latest/design/feature/cache_dit **Official docs**: https://docs.vllm.ai/projects/vllm-omni/en/latest/design/feature/cache_dit
@@ -245,10 +243,8 @@ vllm serve your-model --omni --port 8098 \
**Verification checklist**: **Verification checklist**:
1. Logs show "Cache-dit enabled successfully on xxx" 1. Logs show "Cache-dit enabled successfully on xxx"
2. A focused test or summary confirms at least one real cache hit 2. Performance: 1.5-2x speedup vs no cache
3. Quality is compared with `cache_backend=None` 3. Quality: compare output with `cache_backend=None`
4. Performance uses a realistic step count, repeated timings, and a policy
whose warmup does not consume the whole run
## Excluded Models ## Excluded Models
@@ -16,22 +16,20 @@ vllm_omni/diffusion/models/wan2_2/
The transformer is loaded separately via `weights_sources` + `load_weights()`. Non-transformer components (VAE, text encoder) are loaded in `__init__` via `from_pretrained()`. The transformer is loaded separately via `weights_sources` + `load_weights()`. Non-transformer components (VAE, text encoder) are loaded in `__init__` via `from_pretrained()`.
### External adapter (not native support) ### Custom model with external deps (e.g., DreamID-Omni)
``` ```
vllm_omni/diffusion/models/<name>/ vllm_omni/diffusion/models/dreamid_omni/
├── __init__.py # Exports pipeline only ├── __init__.py # Exports pipeline only
├── pipeline_<name>.py # Pipeline: loads ALL weights in __init__ via custom helpers ├── pipeline_dreamid_omni.py # Pipeline: loads ALL weights in __init__ via custom helpers
└── <backbone>.py # Custom transformer / fusion architecture ├── fusion.py # Custom fusion architecture (video + audio cross-attention)
└── wan2_2.py # Re-implemented Wan backbone with split API
examples/offline_inference/<task>/ examples/offline_inference/x_to_video_audio/
└── download_<name>.py # Downloads weights and/or clones code repo └── download_dreamid_omni.py # Downloads weights from 3 HF repos + clones code repo
``` ```
All weights are loaded eagerly in `__init__`. `load_weights()` is a no-op. The All weights loaded eagerly in `__init__`. `load_weights()` is a no-op. External dependency (`dreamid_omni` package) imported with try/except.
external dependency is imported with try/except. Use this pattern only when the
requested scope explicitly allows an adapter; it does not satisfy a request for
a native vLLM-Omni implementation.
### Custom model with ported code (e.g., BAGEL) ### Custom model with ported code (e.g., BAGEL)
@@ -78,7 +76,7 @@ self.weights_sources = [
] ]
``` ```
### Pattern 3: Fully custom loading ### Pattern 3: Fully custom loading (DreamID-Omni)
``` ```
init → load ALL weights eagerly via custom helpers → load_weights() = no-op init → load ALL weights eagerly via custom helpers → load_weights() = no-op
@@ -89,7 +87,7 @@ init → load ALL weights eagerly via custom helpers → load_weights() = no-op
- `load_weights()` is `pass` - `load_weights()` is `pass`
- Weights may come from multiple HF repos in different formats (`.pth`, `.safetensors`) - Weights may come from multiple HF repos in different formats (`.pth`, `.safetensors`)
Use this only for an explicitly approved external adapter when: Use this when:
- The original model has complex, well-tested loading code you don't want to rewrite - The original model has complex, well-tested loading code you don't want to rewrite
- Weights span multiple HF repos - Weights span multiple HF repos
- Weight format is non-standard (e.g., a single `.pth` file, not sharded safetensors) - Weight format is non-standard (e.g., a single `.pth` file, not sharded safetensors)
@@ -110,23 +108,16 @@ Standard diffusers `model_index.json`:
Custom model `model_index.json` (minimal): Custom model `model_index.json` (minimal):
```json ```json
{ {
"_class_name": "YourCustomPipeline", "_class_name": "DreamIDOmniPipeline",
"fusion": "weights/model.safetensors" "fusion": "DreamID-Omni/dreamid_omni.safetensors"
} }
``` ```
The only **required** field is `_class_name` — it must match a key in The only **required** field is `_class_name` — it must match a key in `_DIFFUSION_MODELS` in `registry.py`. Other fields are model-specific and accessible via `od_config.model_config` dict.
`_DIFFUSION_MODELS` in `registry.py`. Other fields are model-specific and
accessible via `od_config.model_config`.
When the released repository cannot be modified and contains neither this file
nor a root `config.json`, use the generic native-checkpoint signature resolver.
Match the exact Hub ID or a distinctive local file set; never infer support from
a model-name substring.
## External Dependency Management ## External Dependency Management
### Git clone + .pth injection ### Git clone + .pth injection (DreamID-Omni pattern)
```python ```python
def download_dependency(): def download_dependency():
@@ -151,10 +142,6 @@ def download_dependency():
Copy essential files from the original repo into `vllm_omni/diffusion/models/<name>/`. Adapt imports to use vllm-omni utilities. Benefits: no external dependency, no git clone step. Drawback: must maintain the ported code. Copy essential files from the original repo into `vllm_omni/diffusion/models/<name>/`. Adapt imports to use vllm-omni utilities. Benefits: no external dependency, no git clone step. Drawback: must maintain the ported code.
This is the default for native model support. Keep the reference repository at
a pinned revision for architecture analysis and golden outputs, but do not
import it from production code.
## Multi-Modal Input/Output Protocols ## Multi-Modal Input/Output Protocols
Custom models that handle images, audio, or video I/O should implement protocol classes: Custom models that handle images, audio, or video I/O should implement protocol classes:
@@ -176,7 +163,7 @@ The engine checks `isinstance(pipeline, SupportImageInput)` at startup to config
Diffusers models use `config.json` in each subfolder. Custom models often use: Diffusers models use `config.json` in each subfolder. Custom models often use:
**Module-level config dicts**: **Module-level config dicts** (DreamID-Omni):
```python ```python
VIDEO_CONFIG = { VIDEO_CONFIG = {
"patch_size": [1, 2, 2], "model_type": "ti2v", "patch_size": [1, 2, 2], "model_type": "ti2v",
@@ -194,7 +181,7 @@ vae_cfg = bagel_cfg.get("vae_config", {})
## Custom Architecture Patterns ## Custom Architecture Patterns
### Split forward API ### Split forward API (DreamID-Omni)
When a fusion model needs to interleave blocks from two backbones: When a fusion model needs to interleave blocks from two backbones:
@@ -1,131 +0,0 @@
# Native Diffusion Model Integration Checklist
Use this checklist for native non-Diffusers models, hybrid pipelines, and
model-specific optimization work.
## 1. Freeze the contract before coding
Record:
- Reference repository and immutable revision.
- Official checkpoint ID, local layout, license, and required auxiliary files.
- Pipeline stages: prompt/image encoding, denoising, video/image/audio decoding,
and muxing.
- Token packing, RoPE coordinates, attention sinks/LSE behavior, MoE layout,
scheduler equations, CFG formulation, dtypes, and checkpoint transforms.
- Output geometry, frame count/FPS, audio rate/channels, supported tasks, and
default generation steps.
- Available local hardware versus the official qualification hardware.
- Intended default deployment and optional optimization paths.
State whether the deliverable is a native implementation or an external
adapter. For native support, use the reference as an oracle only.
## 2. Use phase gates
| Phase | Required exit evidence |
|---|---|
| Architecture | Component/weight inventory; unsupported reference dependencies identified; ownership decisions documented |
| Native correctness | Strict checkpoint load; tiny forward or one-step golden vs pinned reference; output contract validated |
| Framework integration | Registry/discovery, shared request types, existing category example, serving route, docs, and recipe work without model-name branching in shared scripts |
| Lossless distribution | One-rank oracle vs each supported TP/SP/CFG/HSDP/VAE layout; released-checkpoint smoke for the recommended layout |
| Memory optimization | Ordinary/layerwise/DLO behavior, peak HBM, full-tree host PSS, and stage timeline; shared backend unchanged unless a general contract is proven |
| Lossy optimization | Cache hit demonstrated; quality delta measured; speed claim made only when warmup permits hits |
| Full qualification | Default-resolution E2E at official step count for every supported task; media metadata and hashes recorded |
| Review readiness | Focused tests, Ruff/format, strict docs, DCO, current-main rebase, issue/RFC for large changes, concise PR evidence and limitations |
Do not start performance tuning before native one-step parity. Do not claim a
feature from topology validation alone when released weights can exercise it.
## 3. Keep ownership narrow
Default placement:
| Concern | Owner |
|---|---|
| Architecture, checkpoint remap, packed attention math, nested block alias, auxiliary lifecycle | Model package |
| Kernel/version dispatch already useful to multiple models | Shared attention utility |
| Request-dependent example defaults | Typed model-extra entry consumed through a registry |
| Exact-ID/local-file detection for repositories without standard metadata | Generic native-checkpoint signature table |
| General offload/cache/HSDP bug | Shared layer plus one focused shared regression |
Before changing shared code, answer:
1. Is this a framework contract or only this checkpoint's layout?
2. Can the model expose an existing declarative hook instead?
3. Is there a second consumer or a framework-level failing test?
4. Can the shared change avoid importing or naming the model?
If the answers do not justify a shared change, keep it model-local.
## 4. Reuse examples without leaking model logic
- Reuse the existing `x_to_y.py` category script.
- Resolve the pipeline class through standard metadata or native signature
discovery, not path substrings.
- Put dynamic model defaults and validation in `model_extras`; let shared
examples consume only the generic typed API.
- Do not create image/speech/default abstractions without a real consumer.
- Add a dedicated example only when the shared request/output protocol cannot
express the model.
## 5. Build a bounded evidence matrix
Start with the recommended resident layout. Add only supported, useful points:
- Single request: TP, SP, or hybrid TP/SP.
- Throughput: DLO/DP layouts when concurrent requests are valid.
- CFG: compare CFG-parallel output with the packed-CFG oracle only if the model
actually uses CFG.
- HSDP: record loading and materialization peaks; it can use more HBM than a
resident SP layout.
- VAE patch parallelism: compare decoded tensors or media exactly where the
algorithm should be lossless.
- Cache: separate cache integration from acceleration evidence.
With four devices, qualify four-device layouts such as DP4, DP2SP2, SP4, or
CFG2xSP2. Do not present CFG2xSP4 or an official eight-device runtime as locally
validated.
For each measured layout record:
- Cold E2E and warm user latency.
- Steady-state wave time and throughput per device.
- Prompt, denoise, video decode, audio decode, and mux times.
- Peak allocated/reserved HBM and full-process-tree host PSS.
- Step count, seed, output geometry, frame/FPS/audio metadata, and parity metric.
Plot latency versus throughput/device. Keep one-step, four-step, and full-step
results separate because they are not quality-equivalent.
## 6. Keep tests proportional
Prefer a small set with distinct failure ownership:
1. Strict weight-key/dtype/load contract.
2. Tiny native golden or one-step reference parity.
3. One parametrized distributed-parity test covering supported layouts.
4. Pipeline/request/output geometry and serving smoke.
5. Shared-example/default test when the category script changes.
6. One shared regression only for a genuine shared-layer fix.
Avoid duplicating the same shape or configuration assertion across
model-specific files. Keep expensive released-checkpoint evidence in the PR or
recipe unless the repository's CI tier explicitly requires it.
## 7. Write evidence, not marketing
In the PR description include:
- A compact architecture/data-flow explanation.
- Default deployment and why it is recommended.
- Code-change counts by component.
- Accuracy and distributed-parity evidence tied to commands/scripts.
- Separate Pareto figures for different step counts.
- Supported versus validated versus planned optimizations.
- Hardware gaps and untested official-runtime claims.
Call a result a bounded local qualification unless it includes repeated timing
and variance. State whether latency includes text/image encoding and all
decoders. Preserve the reference revision and exact commands needed to
reproduce the evidence.
-126
View File
@@ -1,126 +0,0 @@
---
name: add-recipe
description: Add or update an in-repository vLLM-Omni model recipe with verified task, input, output, hardware, command, feature, and validation contracts. Use when creating files under recipes/, restructuring a recipe after review, documenting a newly supported model, or synchronizing recipe claims with support tables and shared feature guides.
---
# Add a vLLM-Omni Recipe
## Source the contract
Read [`recipes/TEMPLATE.md`](../../../recipes/TEMPLATE.md) and
[`recipes/README.md`](../../../recipes/README.md) before editing. Inspect the
canonical model card/repository, implementation, shared examples, tests, and
available local qualification evidence.
Do not infer support from a model name, sibling recipe, registry entry, or
topology validator. Mark unexecuted configurations as configuration-only.
## Route by model family
Read only the contributor and user documentation relevant to the model:
| Family | Contributor guide | User-facing documentation to synchronize |
|---|---|---|
| Diffusion | [`adding a diffusion model`](../add-diffusion-model/SKILL.md) | `docs/user_guide/diffusion_features.md`, applicable diffusion feature guides, and shared image/video/audio examples |
| TTS | [`adding a TTS model`](../add-tts-model/SKILL.md) | `examples/offline_inference/text_to_speech/README.md`, `examples/online_serving/text_to_speech/README.md`, and `docs/serving/speech_api.md` |
| Omni | [`adding an omni model`](../../../docs/contributing/model/adding_omni_model.md) | model-family offline/online example docs, `docs/serving/chat_completions_api.md`, and `docs/user_guide/feature_compatibility.md` |
All families must update `docs/models/supported_models.md` and the matching row
in `recipes/README.md`. Do not add a modality-specific support document when
the repository has no such table; update the closest shared user contract.
## Structure the recipe
Keep the recipe task-oriented and use one hardware-specific file named
`recipes/<vendor>/<model>-<hardware>.md`. A recipe may contain multiple
deployment topologies on that hardware, but each additional accelerator model
or platform requires its own suffixed file.
1. **Summary:** vendor, exact model ID, runtime, modes, named hardware,
recommended deployment, and maintainer.
2. **Supported model contract:** put task, input, output, and provided-profile
tables before setup or commands.
3. **References:** link the canonical model source, shared examples, supported
model table, and feature matrix.
4. **Checkpoint/setup:** pin revisions when possible and state required assets.
5. **Hardware:** document the single accelerator named by the file suffix,
including every validated device-count/topology profile on it.
6. **Commands:** reuse shared `examples/` entrypoints unless the model contract
truly requires a dedicated script. Include offline and online paths that
were validated.
7. **Supported features:** use a compact model-specific topology/status table
with links to shared feature guides.
8. **Verification:** provide a quick command and exact expected output
contract.
9. **Qualification evidence:** report correctness, memory, and timing evidence
with its measurement scope and caveats.
## Separate hardware from software
For every locally validated hardware profile, record:
- accelerator vendor/model and per-device memory;
- number of devices;
- interconnect (`NVLink`, `PCIe`, or the platform equivalent);
- host memory when CPU staging/offload is material;
- whether the profile is runtime-qualified or configuration-only.
Record OS, Python, driver/runtime, framework versions, and the vLLM-Omni
revision in a separate software-environment table. Keep precision, worker
count, and DP/TP/SP/PP sizes with the deployment profile or exact command.
Never generalize a result from one accelerator family to another or combine
NVIDIA, AMD, NPU, or distinct accelerator models in one recipe. Create a
separate hardware-suffixed recipe and `recipes/README.md` row. Distinguish
upstream requirements from hardware exercised by the PR.
## Keep shared documentation concise
Link the applicable diffusion, TTS, omni, serving, or design guide for generic
feature semantics and launch instructions. The recipe should contain only the
model-specific status, valid topology, required flag difference, and evidence
boundary.
Put unsupported combinations in the same feature table. Avoid repeating the
same model-specific prose below the global feature matrix; the recipe is the
detailed source of truth.
## Report evidence precisely
For every performance or memory value, state:
- checkpoint/revision and workload;
- accelerator and device count;
- precision and topology;
- cold/warm scope, step count, and concurrency;
- peak HBM and host-memory metric when measured;
- output/quality guard and sample count;
- monitoring overhead or other known variance.
Call a single bounded run qualification evidence, not a benchmark. Do not add
a benchmark script unless the contribution explicitly requires one.
## Synchronize repository documentation
Update all applicable locations:
- the row in `recipes/README.md`;
- `docs/models/supported_models.md`;
- the family-specific documentation selected in **Route by model family**;
- shared example documentation when commands or flags change.
Keep global support tables compact. Link to the recipe instead of duplicating
its deployment explanation.
## Validate
Run, at minimum:
```bash
pre-commit run --files <changed recipe/docs/skill files>
mkdocs build --strict
git diff --check
```
Remove generated documentation artifacts after validation. Confirm every
recipe link resolves and every claimed profile has matching code or evidence.
+27 -50
View File
@@ -138,12 +138,9 @@ A shared buffer silently corrupts audio across concurrent requests — the sympt
- Load codec weights (may need lazy loading from separate checkpoint) - Load codec weights (may need lazy loading from separate checkpoint)
- Implement `forward()`: codec codes -> audio waveform - Implement `forward()`: codec codes -> audio waveform
- Return `OmniOutput` with `multimodal_outputs` - Return `OmniOutput` with `multimodal_outputs`
5. **Define and register the pipeline topology** in 5. **Create stage config YAML** defining both stages, memory allocation, and model paths
`vllm_omni/model_executor/models/<model>/pipeline.py` 6. **Create stage input processor** for prompt building
6. **Create the deploy YAML** under `vllm_omni/deploy/` for placement, 7. **Write end2end.py** test script
memory sizing, connectors, and runtime overrides
7. **Create stage input processor** for prompt building and inter-stage handoff
8. **Write end2end.py** test script
### Critical Parameters to Get Right ### Critical Parameters to Get Right
@@ -177,7 +174,7 @@ These bugs appear in almost every new TTS PR. Check all before the first push. S
- **All return paths must emit `model_outputs`** — if any early-return branch skips setting `model_outputs`, the serving layer silently drops that step's audio (fish speech: `fix: ensure ALL return paths emit model_outputs`) - **All return paths must emit `model_outputs`** — if any early-return branch skips setting `model_outputs`, the serving layer silently drops that step's audio (fish speech: `fix: ensure ALL return paths emit model_outputs`)
- **Per-request state isolation** — for batched concurrent requests, key all state by request ID; a shared buffer corrupts audio across requests (fish speech: `fix: per-request vocode + delta emission`) - **Per-request state isolation** — for batched concurrent requests, key all state by request ID; a shared buffer corrupts audio across requests (fish speech: `fix: per-request vocode + delta emission`)
- **Codec tensor device** — move codec codes to the codec decoder's device before calling decode; mismatches cause silent CPU fallback or crashes (fish speech: `fix: use model device for CUDA stream`) - **Codec tensor device** — move codec codes to the codec decoder's device before calling decode; mismatches cause silent CPU fallback or crashes (fish speech: `fix: use model device for CUDA stream`)
- **AR stage `max_num_seqs`** — set to at least 4 in production deploy configs; for single-stage models this is the only stage. For two-stage models, Stage 0 (AR) needs `max_num_seqs ≥ 4` to pipeline concurrent requests; Stage 1 (codec decoder) is model-specific and may intentionally use `max_num_seqs: 1`. Defaulting the AR stage to 1 causes audio gaps under concurrency because the codec window round-robins across requests (RFC #2568) - **AR stage `max_num_seqs`** — set to at least 4 in production configs; for single-stage models this is the only stage. For two-stage models, Stage 0 (AR) needs `max_num_seqs ≥ 4` to pipeline concurrent requests; Stage 1 (codec decoder) typically uses `max_num_seqs: 1` intentionally. Default of 1 everywhere causes audio gaps under concurrency because the codec window round-robins across requests (RFC #2568)
### Optional Dependency Handling ### Optional Dependency Handling
@@ -193,18 +190,18 @@ pattern, signature constraints, and MOSS-TTS-Nano reference.
When the upstream model cannot be cleanly split into an AR stage and a When the upstream model cannot be cleanly split into an AR stage and a
separate decoder, run the full pipeline inside a single AR worker and separate decoder, run the full pipeline inside a single AR worker and
stream audio through a per-request `inference_stream()` generator keyed by stream audio through a per-request `inference_stream()` generator keyed by
`_omni_req_id`. Define one `StagePipelineConfig` with `_omni_req_id`. Stage config must set `worker_type: ar`,
`execution_type=StageExecutionType.LLM_AR`, `engine_output_type="audio"`, `engine_output_type: audio`, `final_output: true`, `is_comprehension: true`,
`final_output=True`, and `owns_tokenizer=True`. Set `async_chunk: false` in and `async_chunk: false` at the top level. Only extract params from
the deploy YAML. Only extract params from
`additional_information` that you actually forward, or pre-commit fails `additional_information` that you actually forward, or pre-commit fails
`ruff F841`. `ruff F841`.
Full walkthrough with the complete `forward()` / `_create_stream_gen()` Full walkthrough with the complete `forward()` / `_create_stream_gen()`
skeleton plus pipeline/deploy definitions: skeleton and stage-config fields:
[references/single-stage-ar.md](references/single-stage-ar.md). For an [references/single-stage-ar.md](references/single-stage-ar.md). For an
in-tree reference, look for any single-stage AR model under in-tree reference, look for any single-stage AR model under
`vllm_omni/model_executor/models/`, such as MOSS-TTS-Nano. `vllm_omni/model_executor/models/` — e.g. the MOSS-TTS-Nano integration when
it lands.
**VoxCPM2 is a different pattern** and should not reuse this skeleton — it **VoxCPM2 is a different pattern** and should not reuse this skeleton — it
runs the base LM under vLLM PagedAttention with external side-computation. runs the base LM under vLLM PagedAttention with external side-computation.
@@ -213,8 +210,7 @@ See `plan/voxcpm2_native_ar_design.md`.
### Deliverables ### Deliverables
- Model files in `vllm_omni/model_executor/models/<model_name>/` - Model files in `vllm_omni/model_executor/models/<model_name>/`
- Registered `pipeline.py` topology - Stage config YAML
- Deploy YAML under `vllm_omni/deploy/`
- Working `end2end.py` at `examples/offline_inference/text_to_speech/<model>/end2end.py` - Working `end2end.py` at `examples/offline_inference/text_to_speech/<model>/end2end.py`
- New section in `examples/offline_inference/text_to_speech/README.md` (table row + per-model section). Do **not** create a top-level `examples/offline_inference/<model>/` dir or a per-model `README.md` inside `text_to_speech/<model>/` — the hub README is the documented surface and the mkdocs `generate_examples` hook only descends one level into `examples/<category>/`. - New section in `examples/offline_inference/text_to_speech/README.md` (table row + per-model section). Do **not** create a top-level `examples/offline_inference/<model>/` dir or a per-model `README.md` inside `text_to_speech/<model>/` — the hub README is the documented surface and the mkdocs `generate_examples` hook only descends one level into `examples/<category>/`.
@@ -345,8 +341,8 @@ Heavier scenarios in the same file use **`advanced_model` only** (streaming, ext
@pytest.mark.tts @pytest.mark.tts
@hardware_test(res={"cuda": "L4"}, num_cards=1) @hardware_test(res={"cuda": "L4"}, num_cards=1)
@pytest.mark.parametrize("omni_server", tts_server_params, indirect=True) @pytest.mark.parametrize("omni_server", tts_server_params, indirect=True)
def test_voice_clone_en_non_streaming_001(omni_server, online_client) -> None: def test_voice_clone_en_non_streaming_001(omni_server, openai_client) -> None:
online_client.send_audio_speech_request({...}) openai_client.send_audio_speech_request({...})
``` ```
**L4 consolidation:** Prefer parametrized `OmniServerParams` rows (`default`, `async_chunk`, feature flags) in one expansion module rather than many merge-only files ([#1832](https://github.com/vllm-project/vllm-omni/issues/1832)). **L4 consolidation:** Prefer parametrized `OmniServerParams` rows (`default`, `async_chunk`, feature flags) in one expansion module rather than many merge-only files ([#1832](https://github.com/vllm-project/vllm-omni/issues/1832)).
@@ -363,8 +359,8 @@ def test_voice_clone_en_non_streaming_001(omni_server, online_client) -> None:
| Path | Fixture | Call | | Path | Fixture | Call |
|------|---------|------| |------|---------|------|
| Online `/v1/*` | `online_client` | `online_client.send_*_request(request_config)` | | Online `/v1/*` | `openai_client` | `openai_client.send_*_request(request_config)` |
| Offline inference | `offline_client` | `offline_client.send_*_request(request_config)` | | Offline inference | `omni_runner_handler` | `omni_runner_handler.send_*_request(request_config)` |
1. **Grep `runtime.py` first** — reuse `send_omni_request`, `send_diffusion_request`, `send_audio_speech_request` (online + offline Qwen-style TTS), `send_single_stage_tts_request` (Coqui XTTS / MOSS-TTS-Nano offline), etc. 1. **Grep `runtime.py` first** — reuse `send_omni_request`, `send_diffusion_request`, `send_audio_speech_request` (online + offline Qwen-style TTS), `send_single_stage_tts_request` (Coqui XTTS / MOSS-TTS-Nano offline), etc.
2. **No matching helper** → add `send_<feature>_request` (or `send_<route>_http_request` for negative/dfx) in **`runtime.py`** with general `assert_*` bundled inside, **then** call it from the test. 2. **No matching helper** → add `send_<feature>_request` (or `send_<route>_http_request` for negative/dfx) in **`runtime.py`** with general `assert_*` bundled inside, **then** call it from the test.
@@ -396,7 +392,7 @@ See [vllm-omni-test skill](../vllm-omni-test/SKILL.md) § **Runtime send helpers
`/health` return non-200 until you're actually ready. `/health` return non-200 until you're actually ready.
- **Mark tests per the CI Levels table** — baseline smoke: `core_model` + `advanced_model`; heavier cases: `advanced_model` only; L4 expansion: `full_model` - **Mark tests per the CI Levels table** — baseline smoke: `core_model` + `advanced_model`; heavier cases: `advanced_model` only; L4 expansion: `full_model`
- **No per-model helper modules** — do not create `tests/helpers/{slug}.py`; keep constants and `request_config` payloads in the test file - **No per-model helper modules** — do not create `tests/helpers/{slug}.py`; keep constants and `request_config` payloads in the test file
- **Online and offline e2e go through `runtime.py`** — `online_client.send_audio_speech_request` (online); `offline_client.send_audio_speech_request` (Qwen-style offline) or `send_single_stage_tts_request` (single-stage offline). Add a new `send_*_request` in `runtime.py` when none fits; do not embed `omni.generate` or HTTP in tests - **Online and offline e2e go through `runtime.py`** — `openai_client.send_audio_speech_request` (online); `omni_runner_handler.send_audio_speech_request` (Qwen-style offline) or `send_single_stage_tts_request` (single-stage offline). Add a new `send_*_request` in `runtime.py` when none fits; do not embed `omni.generate` or HTTP in tests
## Phase 4: Async Chunk (Streaming) ## Phase 4: Async Chunk (Streaming)
@@ -404,29 +400,21 @@ See [vllm-omni-test skill](../vllm-omni-test/SKILL.md) § **Runtime send helpers
### Steps ### Steps
1. **Update the pipeline topology** so the producing stage declares its async 1. **Update stage config YAML**:
handoff processor with `async_chunk_process_next_stage_input_func`.
2. **Update the deploy YAML** to enable async chunk and configure the connector:
```yaml ```yaml
async_chunk: true async_chunk: true
codec_chunk_frames: 25 # frames per chunk
connectors: codec_left_context_frames: 25 # overlap for smooth boundaries
connector_of_shared_memory:
name: SharedMemoryConnector
extra:
codec_streaming: true
codec_chunk_frames: 25
codec_left_context_frames: 25
``` ```
3. **Implement chunk handling in Stage 1**: 2. **Implement chunk handling in Stage 1**:
- Accept partial input (chunk of codec codes) - Accept partial input (chunk of codec codes)
- Handle left context for smooth audio boundaries - Handle left context for smooth audio boundaries
- Return partial audio in `OmniOutput` - Return partial audio in `OmniOutput`
4. **Test streaming**: 3. **Test streaming**:
- Verify audio quality matches non-streaming output - Verify audio quality matches non-streaming output
- Check for artifacts at chunk boundaries - Check for artifacts at chunk boundaries
- Measure TTFA (time to first audio) - Measure TTFA (time to first audio)
5. **Update online serving** to support `stream=true` with PCM output 4. **Update online serving** to support `stream=true` with PCM output
### Streaming Architecture ### Streaming Architecture
@@ -447,8 +435,7 @@ Stage 0 (AR) Stage 1 (Decoder)
### Deliverables ### Deliverables
- Updated pipeline async handoff processor and deploy config with - Updated stage config with async_chunk enabled
`async_chunk: true`
- Smooth streaming audio without boundary artifacts - Smooth streaming audio without boundary artifacts
- TTFA metrics - TTFA metrics
@@ -499,13 +486,6 @@ Common pre-commit failures, recovery commands for missing sign-off, and the
full `pre-commit run` invocation for a TTS model: full `pre-commit run` invocation for a TTS model:
[references/precommit-dco.md](references/precommit-dco.md). [references/precommit-dco.md](references/precommit-dco.md).
## Documentation and recipe
Follow the [`add-recipe` skill](../add-recipe/SKILL.md) to add or update the
in-repository model-family recipe, the `recipes/README.md` index, the supported
models table, TTS example documentation, and the speech API contract using
validated evidence.
## Integration Checklist ## Integration Checklist
Use this checklist when integrating a new TTS model: Use this checklist when integrating a new TTS model:
@@ -527,15 +507,14 @@ Use this checklist when integrating a new TTS model:
- [ ] Config classes created with `model_type` registration - [ ] Config classes created with `model_type` registration
- [ ] Stage 0 (AR) implemented and generates correct tokens - [ ] Stage 0 (AR) implemented and generates correct tokens
- [ ] Stage 1 (Decoder) produces correct audio from tokens — dtype float32 for codec decoder - [ ] Stage 1 (Decoder) produces correct audio from tokens — dtype float32 for codec decoder
- [ ] AR stage `max_num_seqs` ≥ 4 in the production deploy config unless the model has a tested lower limit - [ ] Stage 1 `max_num_seqs` ≥ 4 in production config (default 1 causes gaps under concurrency)
- [ ] Optional dependency fallbacks handled at `load_weights()` time (torchaudio/soundfile/etc.) - [ ] Optional dependency fallbacks handled at `load_weights()` time (torchaudio/soundfile/etc.)
- [ ] Streaming: codec codes accumulated across AR steps (not reset per step) - [ ] Streaming: codec codes accumulated across AR steps (not reset per step)
- [ ] Streaming: delta audio emitted per chunk, not full re-decoded waveform - [ ] Streaming: delta audio emitted per chunk, not full re-decoded waveform
- [ ] Streaming: all `forward()` return paths emit `model_outputs` - [ ] Streaming: all `forward()` return paths emit `model_outputs`
- [ ] Streaming: per-request state keyed by request ID (not shared across requests) - [ ] Streaming: per-request state keyed by request ID (not shared across requests)
- [ ] Streaming: codec tensors moved to codec decoder device before decode - [ ] Streaming: codec tensors moved to codec decoder device before decode
- [ ] Pipeline topology defined in `pipeline.py` and registered in `OMNI_PIPELINES` - [ ] Stage config YAML created
- [ ] Deploy YAML created under `vllm_omni/deploy/`
- [ ] `end2end.py` produces audio matching reference quality - [ ] `end2end.py` produces audio matching reference quality
- [ ] README.md written - [ ] README.md written
@@ -550,11 +529,9 @@ Use this checklist when integrating a new TTS model:
- [ ] Buildkite entries match level: `test-ready.yml` / `test-merge.yml` or nightly TTS job / `test-nightly.yml` - [ ] Buildkite entries match level: `test-ready.yml` / `test-merge.yml` or nightly TTS job / `test-nightly.yml`
- [ ] Gradio demo working - [ ] Gradio demo working
- [ ] Documentation added (offline + online docs, nav, supported models) - [ ] Documentation added (offline + online docs, nav, supported models)
- [ ] In-repository recipe and `recipes/README.md` row added via the `add-recipe` skill
### Phase 4: Async Chunk ### Phase 4: Async Chunk
- [ ] Pipeline declares the async handoff processor - [ ] Stage config updated with `async_chunk: true`
- [ ] Deploy config sets `async_chunk: true` and connector chunk parameters
- [ ] Stage 1 handles partial chunks correctly - [ ] Stage 1 handles partial chunks correctly
- [ ] No audio artifacts at chunk boundaries - [ ] No audio artifacts at chunk boundaries
- [ ] Streaming via API (`stream=true`) works - [ ] Streaming via API (`stream=true`) works
@@ -571,7 +548,7 @@ Use this checklist when integrating a new TTS model:
- [ ] `pre-commit run --files <changed>` passes before every push - [ ] `pre-commit run --files <changed>` passes before every push
- [ ] Every commit has `Signed-off-by` matching the author email (`git commit -s`) - [ ] Every commit has `Signed-off-by` matching the author email (`git commit -s`)
- [ ] `git config user.email` matches the email registered on your GitHub account - [ ] `git config user.email` matches the email registered on your GitHub account
- [ ] Hook list, failure table, and DCO recovery: [references/precommit-dco.md](references/precommit-dco.md) - [ ] Details and failure-recovery commands: [references/precommit-dco.md](references/precommit-dco.md)
## References ## References
@@ -30,18 +30,6 @@ but the changes are correct — stage the modified files and re-commit.
| `ruff F841` | Variable extracted but never forwarded to model call | Remove the extraction or wire it through | | `ruff F841` | Variable extracted but never forwarded to model call | Remove the extraction or wire it through |
| `ruff E402` | Import added below function definitions | Move to top-level import block | | `ruff E402` | Import added below function definitions | Move to top-level import block |
| `ruff format` | Line length, spacing, quote style | Accept auto-fix, stage, re-commit | | `ruff format` | Line length, spacing, quote style | Accept auto-fix, stage, re-commit |
| `check-spdx-header` | Missing header, or copyright still says `vLLM project` | Two-line Omni header (`vLLM-Omni project`); restage the rewrite |
| `check-forbidden-imports` | Stdlib `re`/`base64`, pickle, Hugging Face Hub API, or direct Triton/TileLang | `import regex as re` and `pybase64`; use `vllm.transformers_utils.repo_utils`. Do **not** add the file to `allowed_files` without review |
| `check-torch-cuda-call` | New `torch.cuda.*` call site | Use `current_omni_platform` / `OmniPlatform`; do not grow `ALLOWED_FILES` without review |
| `check-tts-adapter-migration` | New `self._tts_model_type` branch in `serving_speech.py` | Put per-model logic in `tts_adapters/`. Lower `MAX_MODEL_TYPE_BRANCHES` when removing branches; do not raise it without review |
| `check-mark` | New `tests/**/test_*.py` missing level/hardware mark, or direct `pytest.mark.H100` | Add `core_model`/`advanced_model`/… plus `cpu`/`cuda`/`hardware_test(`; SKU marks only via helpers |
| `mypy-3.10` | Type error on changed `vllm_omni/` files | Fix the types; model trees are excluded. Extra versions: `pre-commit run --hook-stage manual mypy-3.12` |
| `markdownlint-cli2` | Docs/README markdown lint | Accept auto-fix under `docs/` / `recipes/` / root README |
| `check-buildkite` | Invalid `.buildkite/*.yml` | Fix the YAML; do not grow `SKIP_FILES` without review |
| `shellcheck` | No `shellcheck` on PATH, or script warning | `apt-get`/`dnf`/`brew install shellcheck`, or `shellcheck.exe` on Windows PATH |
Canonical policy, CI `SKIP` list, and allowlist locations:
[docs/contributing/README.md](../../../docs/contributing/README.md#linting).
## DCO sign-off ## DCO sign-off
@@ -86,51 +86,12 @@ class YourModelForCausalLM(nn.Module):
- Handle both `"audio"` (incremental) and `"result"` (final combined) event - Handle both `"audio"` (incremental) and `"result"` (final combined) event
types from upstream models. types from upstream models.
## Pipeline and deploy config ## Stage config
Define the fixed single-stage topology in `pipeline.py`: Single stage with `worker_type: ar`, `engine_output_type: audio`,
`final_output: true`, `is_comprehension: true`, and `async_chunk: false` at
```python the top level. Omitting any of these causes silent misclassification in the
from vllm_omni.config.stage_config import ( serving layer.
PipelineConfig,
StageExecutionType,
StagePipelineConfig,
)
YOUR_TTS_PIPELINE = PipelineConfig(
model_type="your_model_name",
default_deploy_config_name="your_model_name.yaml",
model_arch="YourModelForCausalLM",
stages=(
StagePipelineConfig(
stage_id=0,
model_stage="your_model_name",
execution_type=StageExecutionType.LLM_AR,
input_sources=(),
owns_tokenizer=True,
engine_output_type="audio",
final_output=True,
final_output_type="audio",
),
),
)
```
Register it in `vllm_omni/config/pipeline_registry.py`. Keep placement and
runtime sizing in `vllm_omni/deploy/your_model_name.yaml`:
```yaml
async_chunk: false
stages:
- stage_id: 0
devices: "0"
max_num_seqs: 4
gpu_memory_utilization: 0.8
```
Do not move `execution_type`, output ownership, tokenizer ownership, or model
architecture into the deploy YAML; those are pipeline topology.
## Lint discipline ## Lint discipline
@@ -140,5 +101,8 @@ pre-commit.
## Reference implementation ## Reference implementation
Use `vllm_omni/model_executor/models/moss_tts_nano/pipeline.py` and Look for any single-stage AR model under
`vllm_omni/deploy/moss_tts_nano.yaml` as the in-tree reference. `vllm_omni/model_executor/models/` — e.g. `moss_tts_nano/` when its
integration lands. If none is in tree yet, follow the skeleton above and
cross-check against the `is_comprehension: true` / `async_chunk: false`
dispatch in `vllm_omni/entrypoints/openai/serving_speech.py`.
@@ -1,135 +0,0 @@
---
name: find-simplifications
description: Find evidence-backed simplification candidates in vLLM-Omni and, when requested, turn them into focused proposals or code changes. Use for audits of dead, duplicated, speculative, over-generalized, unnecessarily defensive, or hand-rolled code. Use review-pr for ordinary correctness review and diffusion-perf-opt for performance-first optimization.
---
# Find vLLM-Omni Simplifications
Find a small number of well-proven ways to reduce code, state, APIs, or maintenance cost without weakening supported behavior. Treat this as an architecture and ownership audit, not a dead-code search or style cleanup.
The default deliverable is a read-only report. Do not add TODOs, design documents, issues, or code unless the user asks for those changes.
## Establish the Contract
Before judging a candidate:
1. Read the repository instructions supplied by the host.
2. Read [`docs/design/architecture_overview.md`](../../../docs/design/architecture_overview.md).
3. Select only the relevant active module and feature documents from [`docs/design/index.md`](../../../docs/design/index.md). Archived design pages are historical context, not current contracts.
4. Identify the comparison base with `git merge-base HEAD origin/main`; do not assume the current branch represents the intended baseline.
5. If tests or CI coverage may change, use [`vllm-omni-test`](../vllm-omni-test/SKILL.md) for test placement, markers, and commands.
Separate an implementation accident from an intentional boundary. In particular:
- vLLM-Omni extends and sometimes overrides upstream vLLM. A similar upstream implementation is evidence for reuse only after comparing API, lifecycle, and multi-stage semantics.
- Registries, import strings, model configuration, deployment YAML, plugins, and optional dependencies create dynamic consumers that a static reference count can miss.
- Hardware backends, model families, execution modes, and connector implementations may be intentionally parallel. Do not propose collapsing one only because it is inactive in the local environment.
- Stage, replica, request, cache, and transport state can look redundant while representing different owners. Prove that two fields or barriers encode the same fact before merging them.
- A shorter hot path is not a simplification if it adds synchronization, tensor copies, host materialization, recompilation, or a measurable quality/performance regression.
## Strong Candidates
A strong candidate removes a meaningful surface and has a clear replacement or deletion boundary. Examples include:
- A public method, configuration option, registry entry, route, message field, or event has no production consumer and no compatibility obligation.
- Tests or documentation are the only consumers of behavior that is not part of a supported contract.
- Multiple layers mirror the same request, lifecycle, cache-validity, readiness, or topology state.
- AR, diffusion, platform, or model-specific implementations duplicate a stable common operation and can share it without hiding performance-critical differences.
- An inherited upstream vLLM primitive is reimplemented locally without an Omni-specific semantic difference.
- Compatibility or fallback branches protect environments the project no longer supports.
- Serialization, IPC, output formatting, or connector code materializes the same payload more than once or converts between equivalent representations.
- A helper, abstraction, package split, or extension point has only one real implementation and adds more indirection than policy.
- Hand-written infrastructure can be replaced by an existing project dependency, Python standard-library feature, PyTorch primitive, or upstream vLLM facility with net deletion and equal observability.
- Tests maintain large mocks, snapshots, or expected data solely for an unused API or representation.
Do not elevate typo fixes, formatting, isolated renames, or vague complexity complaints into simplification candidates. Bundle small cleanups only when they support one substantive removal.
## Survey by Ownership Boundary
For broad audits, cover the largest or riskiest production deltas first. Useful domains are:
- `vllm_omni/engine/`: orchestration, stage pools, request state, output convergence, cancellation, and replica lifecycle.
- `vllm_omni/worker/` and AR model execution: upstream vLLM overlays, scheduler/cache state, collective RPC, adapters, and weight loading.
- `vllm_omni/diffusion/`: request/step execution, batching, output materialization, IPC, offload, parallelism, and model pipelines.
- `vllm_omni/entrypoints/`: duplicated validation/defaulting, offline/online parity, streaming, and route compatibility.
- Connectors and distributed code: ownership, copies, barriers, handles, cleanup, and failure propagation across process/device/node boundaries.
- Platform backends: genuine vendor differences versus copied control flow that has drifted.
- Configuration, registries, recipes, and examples: duplicate sources of truth and options that never reach a runtime consumer.
- Tests and CI: redundant fixtures, mocks of obsolete contracts, and expensive coverage that can move to a cheaper level without losing the guarded behavior.
When the user explicitly requests a repository-wide or many-candidate audit and parallel agent work is available and authorized, divide these domains among agents and require the same evidence format from each. Otherwise survey them sequentially.
## Prove or Reject Each Candidate
Use `rg` first, then read every relevant caller and implementation. Search exact Python symbols plus serialized names, route paths, configuration keys, registry strings, YAML values, and CLI spellings.
Classify evidence:
- **Production:** `vllm_omni/`, runtime configuration, registries, serving entrypoints, connectors, recipes used for supported deployments, and executable loader paths.
- **Validation/support:** `tests/`, benchmarks, examples, docs, CI, and developer tooling.
- **External/dynamic:** upstream vLLM contracts, third-party plugins, model repositories, optional backends, and string-loaded objects. State what was checked and what remains unknown.
For each candidate, answer:
1. What exact surface would disappear or merge?
2. Who owns the state or behavior today?
3. Which production consumers exist, including dynamic consumers?
4. What supported behavior, compatibility, performance, memory, or observability could change?
5. What is the smallest validation that would catch a bad simplification?
6. Is the result net deletion after tests, adapters, migration glue, and documentation are counted?
Reject or downgrade the candidate when a live consumer exists and removal is actually a feature decision; when an active design contract explains the separation; when the change only moves complexity; or when hardware/model evidence is unavailable for a performance-sensitive claim.
## Lifecycle and Data-Movement Audits
For asynchronous or distributed code, map the ownership graph before recommending deletion:
```text
request admission -> orchestrator -> stage/replica -> worker/device
-> connector or IPC boundary -> output aggregation -> terminal cleanup
```
Associate every lock, event, sentinel, queue, readiness flag, timeout, abort path, and cache marker with an owner and transition. Coalesce mechanisms only when they represent the same state and have the same failure boundary. Preserve separate mechanisms when they protect publication versus rollback, local versus remote ownership, first-terminal-outcome arbitration, or cleanup after partial failure.
For tensors and media, also trace device, process, and ownership transitions. Count GPU-to-CPU copies, shared-memory/object-store handles, serialization, decode/encode steps, and unlink/release responsibility. Prefer eliminating a transfer or representation over merely hiding it behind a helper.
## Dependency Replacements
Prefer facilities already available in the repository before adding a dependency. For a proposed replacement:
- Name the exact local behavior it covers and any semantics that remain in glue code.
- Check maintenance, license, optional-dependency boundaries, platform support, and transitive footprint.
- Compare net deletion, test burden, import/startup cost, and runtime behavior.
- Benchmark when the replaced code is on a model, scheduler, connector, serialization, or serving hot path.
A wrapper that retains the same state machine or conversion logic is not a simplification.
## Deliver the Result
Report only candidates supported by concrete evidence. For each one include:
- **Surface:** files and symbols involved.
- **Evidence:** production consumers found, dynamic paths checked, and relevant design contract.
- **Simplification:** exactly what to remove, merge, inline, or reuse.
- **Benefit:** deleted API/state/code, reduced data movement, or reduced maintenance/test burden.
- **Risk and validation:** behavior that could change and the narrowest useful tests or measurements.
- **Confidence:** high or medium, with the unresolved evidence named for medium-confidence items.
Also list representative ideas that were rejected when that prevents repeated investigation. Prefer three high-confidence candidates over a long speculative inventory.
If the user asks for a durable design proposal, place it under `docs/design/feature/` and register it in `docs/design/index.md`; use current design-document conventions and keep implementation details proportional to the decision. If the user asks for implementation, make the smallest coherent change, update affected tests/docs, and preserve public compatibility unless the user explicitly accepts a breaking change.
## Folding Work from Another Branch
Diff the source branch against its own merge base with `origin/main`, not against the current feature branch. Port only independently justified candidates. Consolidate overlaps into the candidate with stronger evidence, and do not preserve a candidate count by carrying weaker duplicates.
## Validation
Match validation to the outgoing diff:
- Documentation or skill changes: run the repository skill validator when available, check links/paths, search for stale source-specific terminology, and run `git diff --check`.
- Python changes: run the narrowest relevant pytest coverage through the repository environment, then lint/format the changed files.
- Distributed, model, hardware, performance, or accuracy changes: run available CPU/static checks and state the exact GPU/NPU/model gap; never present simulated evidence as device validation.
- Before submission, use [`precheck-pr`](../precheck-pr/SKILL.md) for the branch-level self-review.
When summarizing, distinguish checks that passed from checks that were not runnable, and state the snapshot or branch against which the audit was performed.
+1 -9
View File
@@ -1,6 +1,6 @@
--- ---
name: precheck-pr name: precheck-pr
description: Self-check your branch before creating a PR — catch dead code, prevent new model-specific Python examples, verify accuracy/perf claims, validate PR title format, and confirm merge readiness. Use when the user says "precheck", "self review", "pre-submit check", or "check my PR before I open it." Never posts to GitHub. description: Self-check your branch before creating a PR — catch dead code, verify accuracy/perf claims, validate PR title format, and confirm merge readiness. Use when the user says "precheck", "self review", "pre-submit check", or "check my PR before I open it." Never posts to GitHub.
--- ---
# PR Pre-Check # PR Pre-Check
@@ -65,12 +65,6 @@ Ask: "Quick mode or full mode?" Then walk the checklist for the detected PR type
**Also run the Code-Quality sweep on every PR**, regardless of type or mode: the five diff-scoped checks in [references/code-quality.md](references/code-quality.md) — kwargs fragility, broad-except swallow, `Any`/wrong type hints, hot-path `.clone()`/`deepcopy`, and event-loop blocking — plus the advisory conventions (log level, structured logging, synchronization, cleanup, dependencies, naming) in [checklists.md](references/checklists.md). These count only lines the PR *adds* — the pre-existing backlog across the repo is out of scope. **Also run the Code-Quality sweep on every PR**, regardless of type or mode: the five diff-scoped checks in [references/code-quality.md](references/code-quality.md) — kwargs fragility, broad-except swallow, `Any`/wrong type hints, hot-path `.clone()`/`deepcopy`, and event-loop blocking — plus the advisory conventions (log level, structured logging, synchronization, cleanup, dependencies, naming) in [checklists.md](references/checklists.md). These count only lines the PR *adds* — the pre-existing backlog across the repo is out of scope.
**Also run the Examples-Policy check on every PR** using [references/examples-policy.md](references/examples-policy.md). Inspect only Python paths introduced by the diff. Treat a new model-, checkpoint-, vendor-, or family-specific Python example as blocking; do not report pre-existing example debt.
**Also run the Simplification pass on every PR** using [find-simplifications](../find-simplifications/SKILL.md). Keep it diff-scoped: inspect added or modified public APIs, state, abstractions, fallback and compatibility paths, data movement, and only the adjacent ownership needed to prove a candidate. Do not expand the pass into a repository-wide audit. Zero candidates is a valid pass; report only evidence-backed opportunities. Mark a candidate as blocking only when it proves a correctness or merge-readiness problem, such as newly unreachable code. Otherwise report it as a warning.
**Also confirm local pre-commit gates** from [docs/contributing/README.md](../../../docs/contributing/README.md#linting). GitHub Actions `SKIP` does not prove they passed. Full new-hook list: Omni SPDX (`vLLM-Omni project`); forbidden imports (stdlib `re`/`base64`, pickle, Hugging Face Hub API, Triton/TileLang); no new `torch.cuda` call sites; shellcheck (macOS/Windows must install the binary); mypy-3.10; test files have CI level + hardware marks; TTS `_tts_model_type` branches in `serving_speech.py` must not increase; markdownlint on `docs/`; Buildkite YAML schema. ✗ for growing `allowed_files` / `ALLOWED_FILES` or raising `MAX_MODEL_TYPE_BRANCHES` without review.
### Step 5: Print Report ### Step 5: Print Report
``` ```
@@ -83,8 +77,6 @@ Pre-check report for <branch>
───────────────── ────── ───────────────── ──────
PR title format ✓ PR title format ✓
Code quality ⚠ 1 broad except, 2 Any hints Code quality ⚠ 1 broad except, 2 Any hints
Examples policy ✗ new model-specific Python example
Simplification ⚠ helper duplicates an existing owner
PR desc integrity ✓ PR desc integrity ✓
Registry/config ✓ Registry/config ✓
Dead code ⚠ 2 warnings Dead code ⚠ 2 warnings
@@ -47,19 +47,6 @@ Diff-scoped sweep for five fragility patterns that are pervasive in the existing
Roll the ⚠/✗ counts into the report as a single **Code quality** dimension row. Roll the ⚠/✗ counts into the report as a single **Code quality** dimension row.
---
## All PRs: Examples Policy (run on every PR, quick & full)
Inspect Python files introduced under `examples/` using
[examples-policy.md](examples-policy.md). This is a diff-scoped ratchet: existing
model-specific examples may be edited or removed without triggering the check.
- [ ] **No new model-specific Python examples:** the diff adds, copies, or renames no Python file whose path or behavior is specific to one model, checkpoint, vendor, or model family. ✗ for any violation. Route inference through a shared task/protocol entrypoint, model request behavior through `vllm_omni/model_extras`, and commands through task docs or `recipes/`.
- [ ] **New generic example code is justified:** any new task-neutral Python file under `examples/` is an actual user-facing entrypoint rather than reusable production logic, a benchmark, an app, a setup tool, or a reproducer. ⚠ when the placement is questionable.
Roll the result into the report as a separate **Examples policy** dimension.
### Conventions (eyeball — no grep) ### Conventions (eyeball — no grep)
Judgment calls that don't grep cleanly — apply them while reading the diff, not as a mechanical check: Judgment calls that don't grep cleanly — apply them while reading the diff, not as a mechanical check:
@@ -164,7 +151,6 @@ Diffusion models live under `vllm_omni/diffusion/`. In addition to the New Model
- [ ] **PR body matches diff:** description doesn't claim changes not in the diff - [ ] **PR body matches diff:** description doesn't claim changes not in the diff
- [ ] **Branch is rebased:** `git merge-base HEAD origin/main` is recent, no merge conflicts - [ ] **Branch is rebased:** `git merge-base HEAD origin/main` is recent, no merge conflicts
- [ ] **CI gates passing:** DCO, pre-commit, build — check `gh pr view --json statusCheckRollup` if a PR already exists - [ ] **CI gates passing:** DCO, pre-commit, build — check `gh pr view --json statusCheckRollup` if a PR already exists
- [ ] **Local pre-commit gates:** full list in [Linting](../../../../docs/contributing/README.md#linting). Blocking when the diff introduces: stale `vLLM project` SPDX; stdlib `re`/`base64` or pickle in library code; new `torch.cuda` call sites; `tests/**/test_*.py` without level+hardware marks; new `_tts_model_type` branches in `serving_speech.py`; invalid `.buildkite` YAML. macOS/Windows need a native `shellcheck`. ✗ for growing `CHECK_IMPORTS[*].allowed_files`, `ALLOWED_FILES`, or `MAX_MODEL_TYPE_BRANCHES` without review.
### Full (adds) ### Full (adds)
@@ -1,68 +0,0 @@
# Examples Python Policy
Prevent new model-specific Python examples without forcing cleanup of the
existing backlog. Run this check on every PR in quick and full modes.
## Scope
Set the merge base as in the main workflow, then list Python paths introduced
by the PR, including additions, copies, and renames:
```bash
BASE="$(git merge-base HEAD origin/main)"
git diff --name-status --diff-filter=ACR --find-renames --find-copies \
"${BASE}"...HEAD -- 'examples/**/*.py'
```
Inspect destination paths for copies and renames. Existing model-specific files
that are only modified or deleted are grandfathered and must not be reported.
## Blocking classification
Mark ✗ when a newly introduced Python file does any of the following:
- Names or scopes a path to one model, checkpoint, vendor, or model family.
- Hard-codes a model identifier, model-specific prompt, request shape, output
conversion, or launch configuration behind an otherwise generic filename.
- Duplicates an existing task/protocol runner to demonstrate one model.
- Adds a per-model helper below a generic task hub such as `text_to_speech/`.
Do not infer safety from a generic filename alone. For example, a file named
`text_to_audio.py` is still model-specific if it only implements one model's
contract.
## Acceptable additions
A new Python example may pass when it is a genuinely model-neutral,
user-facing task or protocol entrypoint. It must accept model selection through
configuration and keep model-specific behavior out of the script.
Use ⚠ when the file is model-neutral but reusable logic appears misplaced under
`examples/`. Route code by purpose:
| Code purpose | Preferred location |
|---|---|
| Model prompt, defaults, request/output adaptation | `vllm_omni/model_extras/` or production model modules |
| Runnable model command and validation evidence | Task documentation or `recipes/` |
| UI or interactive application | `apps/` |
| Benchmark or evaluator | `benchmarks/` |
| Regression reproducer | `tests/` |
| Download, conversion, or setup utility | `tools/` |
If a model cannot use an existing shared runner, report the missing generic
capability. Do not solve the gap by adding a model-specific Python example.
## Report format
Add one row to the pre-check report:
```text
Examples policy ✓ no new Python example paths
Examples policy ⚠ generic helper belongs in tools/
Examples policy ✗ examples/offline_inference/new_model/end2end.py is model-specific
Examples policy ✗ examples/offline_inference/x_to_y_model_name.py is model-specific
```
For a blocker, name the path, the model-specific behavior, and the appropriate
shared runner or destination. Do not propose deleting unrelated existing
examples as part of the PR.
@@ -1,458 +0,0 @@
---
name: production-add-diffusion-model
description: Productionize a vLLM-Omni diffusion model after its Day-0 vertical slice works. Use when work requires official API parity and input limits, feature-combination evidence, online FP8, distributed layerwise offload, per-request Cache-DiT, CUDA/ROCm/NPU/XPU recipes, faster or sparse attention, fused operators, USP or disaggregation analysis, continuous batching and abort handling, long-running RPS stability, or accuracy/performance/reliability CI. For initial architecture porting, registry wiring, and basic weight loading, use add-diffusion-model first.
---
# Productionizing a Diffusion Model
## Scope
Use this skill to move a working model integration from Day-0 support to a
measured, fail-closed production deployment. It is deliberately separate from
[`add-diffusion-model`](../add-diffusion-model/SKILL.md); do not modify or copy
that skill when productionizing a model. If registry, pipeline, or basic loader
work is incomplete, read that skill first and finish the Day-0 vertical slice.
MiniMax-H3 [PR #5691](https://github.com/vllm-project/vllm-omni/pull/5691)
is the merged Day-0 case study. Its open optimization
[issue #5700](https://github.com/vllm-project/vllm-omni/issues/5700) is an
actively maintained roadmap and evidence map, not a list of already supported
features. It can lag merged implementation: read its current revision, cited
PRs, target source, and maintained recipes, then leave every unverified row
`not tested` until the target revision has scoped evidence.
Read these references before the corresponding gate:
- [API and hardware recipes](references/api-and-recipes.md)
- [Production feature patterns](references/feature-patterns.md)
- [Performance patterns and code examples](references/performance-patterns.md)
- [Serving, soak, and CI validation](references/production-validation.md)
## Non-negotiable evidence contract
Create a matrix whose row key is at least:
```text
task x API mode x execution mode/capacity x shape/schedule x attention backend x
packed layout x cache policy x quantization x offload mode x topology x
hardware x dtype x output representation/transport
```
Use exactly these states:
| State | Meaning |
|---|---|
| `validated` | The exact row passed correctness and measured deployment tests, with reproducible artifacts. |
| `limited` | A bounded subset passed; the limitation and rejection/fallback behavior are explicit. |
| `unsupported` | A stable architectural/platform restriction is proven and fails early with an actionable error. |
| `not tested` | No adequate evidence exists. This is the default. |
Never infer compatibility from import success, server startup, another task,
another card, or another topology. Record the model/checkpoint revision,
vLLM-Omni commit, commands, request assets and hashes, seed/schedule/shape,
raw output artifacts, environment, and result for every `validated` row.
## Workflow
Follow the gates in order. Keep a dense BF16 single-device path as the
correctness oracle until every advertised fast path has parity evidence.
### Gate 0: Freeze the official contract
Pin the official implementation and checkpoint revision. Build a matrix for:
- constructor/call arguments, defaults, scheduler and sigma schedule;
- task-to-source modality/count/order rules;
- prompt, MIME, bytes, pixels, frames, duration, FPS, steps, seed, guidance,
output count, and output format limits;
- offline API and every public sync/async serving endpoint;
- checkpoint partitions and task selection.
Normalize offline, sync, and async requests through one validation contract.
Reject an invalid task/source combination before downloads, upload persistence,
temporary files, async job creation, or engine submission. Use 400 for invalid
semantics and 413 for payload limits; clean partial resources on every failure.
See [API and hardware recipes](references/api-and-recipes.md).
### Gate 1: Re-prove correctness under production shapes
1. Make loading strict: track expected, loaded, unexpected, and intentionally
ignored tensors. A fused destination is complete only after every source
shard arrives. Missing retained parameters abort startup.
2. Fix reference revision, asset hashes, seed, schedule, and shape. Compare
component outputs, intermediate denoise latents, and final artifacts.
3. Test unequal packed samples. Verify `cu_seqlens`, padding exclusion,
modality/CFG ownership, split/gather boundaries, and one output per request.
4. Test partial process groups with non-member ranks and group size smaller
than world size. Do not construct a private group per component or request.
5. Run negative API cases before positive E2E tests to prove side-effect-free
rejection and cleanup.
### Gate 2: Wire shared fast primitives
Prefer explicit, testable vLLM-Omni/vLLM operators with native fallbacks.
`torch.compile` remains a separately validated regional optimization; it is
not a substitute for correct operator selection or lifecycle-safe boundaries.
#### RMSNorm + RoPE baseline
Use the shared cross-platform operators, preserving the official Q/K norm and
RoPE order:
```python
from vllm_omni.diffusion.layers.norm import RMSNorm
from vllm_omni.diffusion.layers.rope import (
RotaryEmbedding,
apply_rope_to_qk,
)
self.norm_q = RMSNorm(head_dim, eps=eps)
self.norm_k = RMSNorm(head_dim, eps=eps)
self.rope = RotaryEmbedding(is_neox_style=False)
query = self.norm_q(query) # [B, S, Hq, D] or packed [T, Hq, D]
key = self.norm_k(key) # [B, S, Hkv, D] or packed [T, Hkv, D]
query, key = apply_rope_to_qk(
self.rope,
query,
key,
(cos, sin),
)
```
Verify `is_neox_style`, `half_head_dim`, partial rotary dimensions, cos/sin
layout, and packed row ownership against the official model. `False` means
interleaved/GPT-J style; do not assume the default is NeoX. Shared RMSNorm and
RoPE are two dispatched operators, not automatically one fused kernel.
For packed non-interleaved RoPE, use the shared fused boundary when its contract
matches the model:
```python
from vllm_omni.diffusion.layers.fused_qk_norm_rope import fused_qk_norm_rope
query, key = fused_qk_norm_rope(
query, # [T, Hq, D]
key, # [T, Hkv, D]
self.norm_q.weight, # [D]
self.norm_k.weight, # [D]
rope_table, # [T, rotary_dim] = [cos | sin]
self.norm_q.variance_epsilon,
)
```
The current CUDA fast path specializes BF16 `head_dim=128`, `rotary_dim=96`;
the public function keeps an eager fallback for unsupported inputs. Verify the
packed row/frequency contract and trace the actual fast path. Do not reshape an
incompatible official RoPE layout merely to enter this kernel.
MiniMax-H3-style partial NeoX RoPE rotates a prefix and passes the suffix
through:
```python
self.q_norm = RMSNorm(head_dim, eps=eps, dtype=torch.bfloat16)
self.k_norm = RMSNorm(head_dim, eps=eps, dtype=torch.bfloat16)
self.rope = RotaryEmbedding(is_neox_style=True, half_head_dim=False)
def apply_partial_rope(self, x, freqs, rot_dim):
x_rot, x_pass = x[..., :rot_dim], x[..., rot_dim:]
cos, sin = torch.cos(freqs).to(x.dtype), torch.sin(freqs).to(x.dtype)
return torch.cat((self.rope(x_rot, cos, sin), x_pass), dim=-1)
```
The partial-RoPE snippet assumes `import torch` and lives on the owning
attention module; adapt names and validated frequency shapes rather than
copying it at module scope.
Call the modules normally so `CustomOp` selects CUDA/HIP/NPU/XPU/native
implementations. Do not call `forward_cuda()` directly. Compare the shared
path with native BF16 at operator, block, denoise-trajectory, and artifact
levels before claiming either accuracy or speed.
#### Faster BF16 attention and fused projections
Use local TP head counts and role-aware shared attention:
```python
from vllm.model_executor.layers.linear import QKVParallelLinear
from vllm_omni.diffusion.attention.layer import Attention
self.qkv_proj = QKVParallelLinear(
hidden_size=hidden_size,
head_size=head_dim,
total_num_heads=num_heads,
total_num_kv_heads=num_kv_heads,
bias=False,
quant_config=quant_config,
prefix=f"{prefix}.qkv_proj",
)
self.attn = Attention(
num_heads=self.qkv_proj.num_heads,
num_kv_heads=self.qkv_proj.num_kv_heads,
head_size=head_dim,
softmax_scale=head_dim**-0.5,
causal=False,
qkv_layout="BSND",
role="self",
prefix=prefix,
)
```
Prove the selected backend from runtime logs/trace and compare it with dense
SDPA. Packed metadata must express real sample boundaries; do not build masks
that the selected backend ignores. A backend that is faster for one shape or
platform is not a global default. See [performance patterns](references/performance-patterns.md)
for QKV, SwiGLU, AdaLN, sparse attention, redundancy, and fusion examples.
### Gate 3: Add online FP8 without breaking the loader
Route the active config to each component, then pass a stable runtime prefix
and config into every quantizable vLLM linear:
```python
def resolve_component_quant_config(quant_config, component):
return quant_config.resolve(component) if hasattr(quant_config, "resolve") else quant_config
transformer_quant_config = resolve_component_quant_config(
od_config.quantization_config,
"transformer",
)
self.transformer = MyDiT(
od_config,
quant_config=transformer_quant_config,
prefix="transformer",
)
self.qkv_proj = QKVParallelLinear(
hidden_size=hidden_size,
head_size=head_dim,
total_num_heads=num_heads,
total_num_kv_heads=num_kv_heads,
quant_config=quant_config,
prefix=f"{prefix}.qkv_proj",
)
```
Use `build_quant_config()` for direct construction/testing:
```python
from vllm_omni.quantization import build_quant_config
quant_config = build_quant_config({
"transformer": {"method": "fp8"},
"text_encoder": None,
"vae": None,
"default": None,
})
```
Preserve each parameter's vLLM `weight_loader`; perform checkpoint layout
conversion before invoking it. Account for every QKV/gate-up source shard.
Keep precision-sensitive norm/modulation/embedders in BF16 unless separately
proven, using the current `safe_quant_config` pattern where applicable.
Validate resident DiT FP8 first: prove which modules are quantized, HBM saved,
BF16-vs-FP8 trajectory/artifact quality, latency, and throughput. Text encoder,
VAE, other hardware, TP/cache/DLO combinations are separate rows.
Pre-quantized, pruned, rotated, or adapter-modified checkpoints are separate
model/loading lanes; compare them with the released BF16 model without calling
the result a pure quantization ablation. Read
[`../quantization/SKILL.md`](../quantization/SKILL.md) if present and prefer the
target revision's implementation/docs when sibling guidance differs.
### Gate 4: Make every component offloadable
Declare topology instead of relying on heuristic discovery:
```python
from typing import ClassVar
import torch.nn as nn
from vllm_omni.diffusion.models.interface import SupportsComponentDiscovery
from vllm_omni.diffusion.offloader import OffloadPlan
class MyPipeline(nn.Module, SupportsComponentDiscovery):
_dit_modules: ClassVar[list[str]] = ["transformer"]
_encoder_modules: ClassVar[list[str]] = ["text_encoder"]
_vae_modules: ClassVar[list[str]] = ["vae"]
_resident_modules: ClassVar[list[str]] = []
_offload_plan: ClassVar[OffloadPlan] = OffloadPlan(
on_demand_component_paths=frozenset({"text_encoder", "vae"}),
block_attrs={"transformer": ("blocks",)},
offload_submodules={"token_refiner": "blocks"},
resident_dit_paths=frozenset({"transformer"}),
encoder_block_attrs={"text_encoder": ("encoder.layers",)},
)
```
Add a test that resolves every dotted component/block path and fails if it is
missing or is not the expected module/container; discovery may otherwise warn
and skip. Validate ordinary layerwise offload and distributed layerwise
offload (DLO) independently.
Keep component lifecycle and host-weight storage separate. `OffloadPlan`
declares what can be streamed; the diffusion loader owns any `HostWeightPlan`
and hands the exact prevalidated plan to DLO. Do not make the offloader rescan
checkpoint files or duplicate loader name/shape/dtype decisions. Direct mmap is
a fail-closed optimization: current preflight requires TP1, no HSDP, no online
quantization, complete bindings, and represented transforms. Otherwise the
ordinary loader remains authoritative.
If the target revision supports Host Weight Runtime (HWR), treat it as a
separate, exact-identity startup/cache path rather than generic offload or
zero-copy execution. Qualify `preferred` population/fallback and `required`
consume-only failure independently; verify immutable manifests, final-layout
restore, lease cleanup, corruption/quarantine, and node-local sharing. Current
model contracts may be BF16 no-AllGather-only, so reject AllGather, online
quantization, HSDP, LoRA/adapted weights, and unrecognized load formats unless
the exact target revision explicitly adds them.
On small-HBM cards, record load/materialization peak, resident peak, encode,
each denoise wave, decode, transient per-rank HBM, host PSS, H2D, and collective
time. Test DLO AllGather and `--dlo-no-use-allgather` as different deployments.
Sweep the supported resident-block policies and publish the non-dominated
latency-HBM frontier; a single minimum-memory or fastest point is not the DLO
trade-off. Keep request-specific preparation replica-local. Only weight
materialization may enter the declared DLO collective group; never use WORLD
for per-request work in a DP deployment.
Direct checkpoint mmap preflight is limited to TP1 without HSDP or online
quantization; TP>1 falls back to the ordinary TP-aware loader and can still feed
DLO AllGather or no-AllGather after scoped E2E. HSDP remains incompatible with
DLO AllGather. A target revision may allow finalized per-tensor online FP8
through the ordinary loader and AllGather; keep every other online quantizer
fail-closed, and require model/card/topology E2E before promotion.
Use deploy YAML for DP under `--omni`; vLLM DP CLI flags are rejected.
### Gate 5: Add request-scoped acceleration safely
Server-wide Cache-DiT support does not imply a per-request quality API. Only
add request policy after defining calibrated quality tiers and a safe hook
lifecycle using `SupportsRequestScopedCacheDiT`, `CacheDiTRequestSpec`, and
`RequestScopedCacheDiTRuntime`. Validate alternating and concurrent quality
tiers, real cache hits, refresh/reset, success, error, disconnect, abort, and
the next uncached request. Do not batch different quality policies unless the
batch compatibility key and hook ownership make that safe.
Then test Cache-DiT against FP8, DLO, TP/SP, compile, and batching one
combination at a time. See [production feature patterns](references/feature-patterns.md)
for the lifecycle skeleton and compatibility matrix.
### Gate 6: Optimize only from profiles
Freeze one canonical workload manifest before profiling. Keep lossless runtime
A/Bs, accelerated paths with numerical/quality trade-offs, and production
topology studies in separate result lanes. Reconcile the client boundary as
queue + encode + denoise + video/audio decode + output transport/codec +
residual; do not hide a material residual in another stage.
Profile stage time, GPU kernels, CPU/GPU synchronization, allocations, H2D,
output payload/copies, and collectives before choosing work. Apply and A/B one
change at a time:
1. Remove synchronization and redundant denoise-loop work: hoist static prompt
conditioning/token refinement, RoPE tables, masks/metadata, bounded AdaLN
schedule projections, and reference scans; remove per-layer `.item()` and
unused masks/allocations.
2. Select the fastest correct BF16 attention backend for each role and shape.
3. Prefer explicit fused QKV, gate-up + `SiluAndMul`, RMSNorm/RoPE, AdaLN, and
layout/residual operators with guarded native fallbacks. Preserve every
materialized dtype/rounding boundary consumed by the unfused graph.
4. Enable sparse attention only when platform, shape, topology, realized
sparsity, quality, fallback, and end-to-end speedup are all proven.
5. Profile USP all-to-all count/bytes, packing, contiguous copies, overlap, and
scaling efficiency. Audit replicated cross-attention before using
`skip_sequence_parallel`. Qualify regular and accelerated Ulysses transport
independently; activation logs, JIT/readiness time, workspace growth,
stream ownership, maximum-shape warmup, and numerical drift are gates.
Preserve the checkpoint's Q:KV head ratio when padding GQA for Ulysses, and
validate strided QKV staging rather than assuming packed contiguity.
6. Evaluate text-encoder or VAE disaggregation only if stage share, transfer
volume, reuse/concurrency, and independent scaling justify it. Tiling,
offload, or patch parallelism is not disaggregation.
Do not combine independent optimizations into one performance claim. Preserve
raw before/after runs with fixed workload and warmup policy.
### Gate 7: Make serving interruptible and load-stable
Treat request-mode batching and step-wise execution as separate capabilities:
- request batching requires `supports_request_batch = True` and
`DiffusionRequestBatch -> list[DiffusionOutput]`, exactly one output per
request;
- step execution requires the complete `SupportsStepExecution` contract:
`prepare_encode`, `denoise_step`, `step_scheduler`, and `post_decode`.
Keep all mutable scheduler, RNG, latent, mask, cache, and step state request
scoped. First validate `--step-execution --max-num-seqs 1` and step-boundary
abort; only then test heterogeneous multi-request waves. Step continuous
batching is experimental and is not automatically compatible with Cache-DiT.
Before recommending it, predeclare a useful case and success threshold, then
A/B it against request mode under the same arrival process. Structural support
or a generic bridge is not latency, throughput, or cancellation evidence for a
model.
Test queued and in-flight abort, client disconnect, timeout, OOM, worker error,
and restart. Require one terminal result, idempotent cleanup, no post-decode
after abort, no cache/temp/VRAM/request-ID leak, and a successful next request.
Inject late worker results/exceptions after cancellation and prove the result
pump stays alive instead of raising `InvalidStateError`. For large batched or
long-window video outputs, freeze dtype, range, layout, contiguity, ownership,
payload size, serializer/codec limits, and offline-versus-HTTP semantics.
Validate device-side output preparation, D2H/IPC or shared handles, remote
encoding, event-loop responsiveness, and client materialization as separate
stages, including abort and consumer failure cleanup.
A model/VAE callback that publishes ordered chunks is only a producer contract;
it is not transport, backpressure, cancellation, public streaming, or
time-to-first-frame evidence. Distinguish complete-response faster-than-playback
(`client E2E / output duration <= 1`) from streaming and report first-chunk,
steady cadence, finalization, and complete-artifact latency separately.
Run below-, near-, and above-saturation mixed-RPS soaks and report success/error
rate, throughput, queue time, memory slope, temp growth, and worker health.
Report p50/p95/p99 only from a declared arrival model with enough measured
requests for the claimed percentile. See
[serving and CI validation](references/production-validation.md).
### Gate 8: Publish recipes and four independent CI tracks
For every target vendor/card/topology, publish exact environment, serve/deploy
command, one complete JSON or multipart curl per supported task, expected
output checks, benchmark command, warmup/repeat/concurrency policy, raw metrics,
per-rank HBM/host PSS, accuracy artifact, and limitations. Mark each
CUDA/ROCm/NPU/XPU row `validated`, `unsupported`, or `not tested`; never inherit
support from the platform abstraction or another card. Re-run DLO on each
small-HBM recipe.
Maintain separate CI gates:
1. Function: strict load, API/offline positive and negative contracts.
2. Accuracy: fixed reference revision/assets/seed/schedule and scoped tolerance.
3. Performance: fixed best deployment plus memory-constrained/DLO row, raw JSON,
explicit warmup, enough samples for each claimed statistic, and owned
regression thresholds.
4. Reliability: long mixed-RPS soak, abort/disconnect/fault cleanup, memory and
temporary-resource trend.
## Production Definition of Done
A model is production-ready only when:
- every official advertised task passes API parity, offline/online parity,
strict loading, fixed-reference quality, and negative validation;
- the recommended deployment row passes correctness, isolation, abort/error
cleanup, benchmark, and soak gates on named hardware;
- online FP8 and DLO are each implemented and either validated in scoped rows
or explicitly limited/unsupported with fail-fast behavior;
- per-request cache quality tiers have measured speed/quality frontiers and
request isolation, if advertised;
- CUDA/ROCm/NPU/XPU recipes describe evidence rather than inferred support;
- Function, Accuracy, Performance, and Reliability CI have owners, artifacts,
and actionable failure output;
- accelerated paths identify every precision/quality trade-off, and the raw
offline plus online encoded output contracts are versioned and tested;
- all unverified task/backend/cache/quant/offload/topology/hardware combinations
remain `not tested` and are not presented as supported.
Day-0 support is still a valid milestone, but title, docs, and matrix must say
which representative task passed and which production gates remain.
@@ -1,4 +0,0 @@
interface:
display_name: "Production Add Diffusion Model"
short_description: "Productionize vLLM-Omni diffusion models"
default_prompt: "Use $production-add-diffusion-model to take this integrated diffusion model from Day-0 support to evidence-backed production readiness."
@@ -1,315 +0,0 @@
# API Contract and Hardware Recipes
## Contents
1. [Freeze the upstream contract](#freeze-the-upstream-contract)
2. [Validate before side effects](#validate-before-side-effects)
3. [Task request templates](#task-request-templates)
4. [Serving benchmark template](#serving-benchmark-template)
5. [Hardware recipe contract](#hardware-recipe-contract)
6. [Best deployment selection](#best-deployment-selection)
## Freeze the upstream contract
Pin three revisions independently:
- official source implementation commit or package version;
- checkpoint/model-card revision and partition;
- vLLM-Omni target commit.
Build a table before editing code. One row is one official task/partition.
| Field | Required content |
|---|---|
| Task and partition | Official task name, checkpoint subdirectory, task selection rules |
| Call contract | Constructor and call arguments, types, required/optional, official defaults |
| Prompt/input | Prompt count/length, modality, MIME, order, count, decoded shape |
| Size/time | Width/height alignment, pixels, frames, duration, FPS, start offset |
| Denoise | Scheduler, sigma schedule, steps, CFG/guidance, seed behavior |
| Output | Count, format, codec/sample rate, return type |
| API | Offline mapping, sync endpoint, async endpoint/job lifecycle |
| Rejection | Status code and stable error class/message for every invalid boundary |
Official API parity includes limits and defaults, not just names. Do not add a
generic default that changes a distilled checkpoint's fixed schedule, or accept
an input combination the official task rejects.
For every accepted boundary, add the neighboring rejected values. Examples:
`max_images` and `max_images + 1`, smallest/largest legal duration, aligned and
misaligned dimensions, omitted and illegal steps, one and too many outputs.
## Validate before side effects
Use one normalization/validation path for offline, sync, and async requests.
The ordering must be:
```text
parse -> normalize aliases -> validate task/source matrix -> validate declared
limits -> stream with byte caps -> inspect/decode with frame/pixel/duration caps
-> persist only if needed -> submit to engine
```
Fail before download, persistence, job creation, or engine submit whenever the
available metadata is sufficient. For streamed uploads, enforce:
- total reference count and per-modality count before reading bodies;
- per-file bytes while streaming;
- aggregate bytes across all files;
- decoded frames, duration, pixels, and media type before engine submission.
On rejection, close streams and remove every partial temporary resource. Use
HTTP 400 for invalid task/field combinations and 413 for payload limits. Test
both sync and async paths; an invalid async request must not create a job ID.
Do not trust client MIME alone. Sniff/validate content and keep ordered repeated
multipart fields ordered. Avoid logging prompts, raw references, signed URLs,
or base64 payloads.
## Task request templates
These are endpoint-shape templates, not model recipes. Replace placeholders
with the exact official defaults and validated assets for the target model.
Every production recipe must include all advertised tasks and an expected
output check.
### Text to image: JSON
```bash
curl --fail-with-body -sS -X POST http://127.0.0.1:8091/v1/images/generations \
-H 'Content-Type: application/json' \
-d '{
"model": "<model>",
"prompt": "<validated prompt>",
"size": "1024x1024",
"num_inference_steps": 30,
"seed": 42,
"response_format": "b64_json"
}' | jq -er '.data[0].b64_json' | base64 -d > output.png
file output.png
```
### Image edit: multipart
```bash
curl --fail-with-body -sS -X POST http://127.0.0.1:8091/v1/images/edits \
-F 'model=<model>' \
-F 'prompt=<validated edit prompt>' \
-F 'image=@/path/to/reference.png;type=image/png' \
-F 'size=1024x1024' \
-F 'num_inference_steps=30' \
-F 'seed=42' \
-F 'output_format=png' \
| jq -er '.data[0].b64_json' | base64 -d > edited.png
file edited.png
```
Use repeated `image` fields only if the model's official input matrix allows
multiple ordered sources. Never describe a file upload as JSON-only.
### Public request fields versus internal sampling fields
Use the field exposed by the target revision's endpoint schema; do not rename
it from the pipeline's internal attribute. For example, the current video JSON
and multipart contracts expose `extra_params`, while serving translates that
object into `DiffusionSamplingParams.extra_args`. Other request paths may
expose canonical `extra_args` or nest it under `extra_body`. Inspect the exact
Pydantic model/FastAPI form dependency and exercise the generated curl before
publishing a recipe. A deprecation warning in a different endpoint is not
evidence that an unrecognized multipart form field works here.
Current endpoint map (re-check it at the target commit):
| Endpoint | Public model-specific field |
|---|---|
| `/v1/videos`, `/v1/videos/sync` | Multipart `extra_params` JSON string |
| `/v1/images/generations` | JSON `extra_params` object |
| `/v1/chat/completions` diffusion | `extra_body.extra_args` |
| `/v1/images/edits` | Declared typed form fields only |
| `/v1/audio/generate` | Declared typed JSON fields only |
Prefer declared top-level fields. Use a generic model-specific envelope only
when the endpoint actually exposes it.
### Text to video: multipart
```bash
curl --fail-with-body -sS -X POST http://127.0.0.1:8091/v1/videos/sync \
-H 'Accept: video/mp4' \
-F 'model=<model>' \
-F 'prompt=<validated prompt>' \
-F 'size=1280x720' \
-F 'num_frames=81' \
-F 'fps=16' \
-F 'num_inference_steps=30' \
-F 'seed=42' \
-F 'extra_params={"task":"<official-task>"}' \
-o output.mp4
ffprobe -v error -show_entries stream=codec_name,width,height,r_frame_rate \
-of json output.mp4
```
### Image/video to video: multipart
```bash
curl --fail-with-body -sS -X POST http://127.0.0.1:8091/v1/videos/sync \
-H 'Accept: video/mp4' \
-F 'model=<model>' \
-F 'prompt=<validated prompt>' \
-F 'num_frames=81' \
-F 'fps=16' \
-F 'num_inference_steps=30' \
-F 'seed=42' \
-F 'extra_params={"task":"<official-task>","start_time_seconds":0}' \
-F 'input_reference=@/path/to/reference.png;type=image/png' \
-o i2v.mp4
curl --fail-with-body -sS -X POST http://127.0.0.1:8091/v1/videos/sync \
-H 'Accept: video/mp4' \
-F 'model=<model>' \
-F 'prompt=<validated prompt>' \
-F 'num_frames=81' \
-F 'num_inference_steps=30' \
-F 'seed=43' \
-F 'extra_params={"task":"<official-task>","start_time_seconds":0}' \
-F 'input_reference=@/path/to/reference.mp4;type=video/mp4' \
-o v2v.mp4
```
For multiple references, repeat the model's accepted multipart field in
official order. Include complete downloadable or repository fixtures plus
SHA256 hashes. A placeholder-only recipe is not runnable evidence.
### Text to audio: JSON
```bash
curl --fail-with-body -sS -X POST http://127.0.0.1:8091/v1/audio/generate \
-H 'Content-Type: application/json' \
-d '{
"model": "<model>",
"input": "<validated audio prompt>",
"audio_length": 10.0,
"num_inference_steps": 100,
"seed": 42,
"response_format": "wav"
}' -o output.wav
ffprobe -v error -show_entries stream=codec_name,sample_rate,channels \
-of json output.wav
```
Use `/v1/audio/generate` for general diffusion audio. Do not substitute the
speech API unless the model is actually served through the speech contract.
### Async video lifecycle
Alongside `/v1/videos/sync`, include `POST /v1/videos`, status polling,
content retrieval, cancellation/abort if public, and cleanup assertions. The
same request must normalize to the same engine sampling parameters in both
modes.
## Serving benchmark template
Run a smoke first, then fixed-concurrency and arrival-rate tests. Video task
names for the harness are `t2v`, `i2v`, `ti2v`, or `v2v`; model-specific task
selection belongs in `--extra-body` when required.
```bash
python benchmarks/diffusion/diffusion_benchmark_serving.py \
--base-url http://127.0.0.1:8091 \
--endpoint /v1/videos \
--model '<model>' \
--dataset random \
--task t2v \
--num-prompts 100 \
--width 1280 --height 720 \
--num-frames 81 --fps 16 \
--num-inference-steps 30 \
--request-rate 0.2 \
--max-concurrency 4 \
--warmup-requests 4 \
--warmup-concurrency 4 \
--extra-body '{"extra_params":"{\"task\":\"<official-task>\"}"}' \
--output-file result.json
```
The video backend currently stringifies each multipart value. Pre-serialize
the nested `extra_params` object as above; passing it as a nested object would
produce Python dict syntax rather than valid JSON. If the target revision
changes the backend to JSON-encode dict/list values, update and smoke-test this
command against that revision.
For i2v/v2v, use the harness's validated dataset/trace path or provide the
documented media source; do not silently benchmark t2v and label it i2v. Keep
prompt, assets, seed, dimensions, frames, steps, guidance, warmup, concurrency,
and arrival process identical across A/B runs.
Keep three benchmark lanes distinct:
1. lossless runtime A/B with the released schedule/precision/attention policy;
2. accelerated-path A/B that changes one adapter, quantization, cache, or
sparsity policy and adds same-seed quality evidence;
3. production-topology studies that vary placement or parallelism and report a
latency/throughput/memory frontier rather than a kernel speedup.
For a fixed single-request A/B, retain every measured run and report median or
mean with range after an explicit warmup; do not infer p95/p99 from a few
repetitions. Tail latency requires a declared arrival process, request count,
concurrency, and enough successful samples. Reconcile client E2E with queue,
encoder, denoise, video/audio decode, D2H/IPC, codec, and residual timings.
For generated media, define `real-time` rather than relying on the label. A
complete-response claim uses `client E2E / validated output duration <= 1` and
does not imply streaming or low time-to-first-frame. Report first chunk/fragment
and cadence separately when a public streaming route exists. Never add gains
across benchmark lanes with different schedules, prompts, seeds, artifacts, or
topologies.
The diffusion serving harness currently covers image/video task families, not
`/v1/audio/generate`. For text-to-audio, use the target model's task-specific
audio benchmark if one exists; otherwise add a serving harness that records
request rate, concurrency, latency, generated-audio seconds per second, error
rate, and output validation. Do not relabel the TTS harness or a video workload
as diffusion-audio evidence.
## Hardware recipe contract
Create a separate row and runnable section for each vendor/card/topology:
| Evidence | Required detail |
|---|---|
| Identity | Vendor, exact SKU, device count, HBM, interconnect |
| Software | Driver, CUDA/ROCm/CANN/oneAPI, framework/container, dependency versions |
| Source | vLLM-Omni commit, official/checkpoint revision and hashes |
| Deployment | dtype, attention backend per role, TP/SP/CFG/HSDP/DP, cache, quant, offload |
| Commands | Complete deploy YAML/serve command, every task curl, benchmark command |
| Method | Warmup exclusion, repetitions or request count, concurrency, arrival distribution, duration, statistic definitions |
| Results | Success rate, p50/p95/p99, RPS/throughput per device, stage latency |
| Memory | Load/materialize, resident, encode, denoise, decode, transient per-rank HBM and host PSS |
| Quality | Metric/tolerance, artifact links/hashes, temporal/audio checks |
| Output | Raw/offline dtype, range, layout and ownership; online codec, payload bytes, transport route and client boundary |
| Limits | `validated`/`limited`/`unsupported`/`not tested`, fallback/rejection behavior |
Use current paths such as `recipes/<vendor>/`, `recipes/README.md`, supported
models, and feature compatibility tables. Top-level documentation must link to
the scoped matrix; it must not use an unqualified checkmark that hides a ROCm,
NPU, XPU, task, topology, or dtype limitation.
DLO evidence is per card, especially on small-HBM devices. A DLO result on one
CUDA card does not establish another CUDA SKU, ROCm, NPU, or XPU support.
## Best deployment selection
Benchmark a correctness-approved candidate matrix, not every theoretical
combination. Choose the recommended row by the production objective—latency,
throughput, cost, or small-HBM fit—and document that objective.
Do not recommend a row until it passes:
- all advertised task/API contracts;
- fixed-reference quality;
- request isolation and abort/error cleanup;
- sustained arrival-rate tests;
- per-rank HBM and host PSS limits;
- hardware-specific accuracy and performance CI.
Keep the most memory-constrained DLO row even when it is not the fastest; it is
a separate production recipe, not a failed latency candidate.
@@ -1,462 +0,0 @@
# Production Feature Patterns
## Contents
1. [Compatibility matrix](#compatibility-matrix)
2. [Online FP8](#online-fp8)
3. [Strict fused-weight loading](#strict-fused-weight-loading)
4. [Distributed layerwise offload](#distributed-layerwise-offload)
5. [Per-request Cache-DiT](#per-request-cache-dit)
6. [Combination gates](#combination-gates)
## Compatibility matrix
Create rows at the granularity below. Add evidence links and a reason for every
non-validated state.
| Task/shape | Execution mode/capacity | Backend/layout | Quant | Cache policy | Offload | Topology | Hardware/dtype | Output contract/transport | State | Evidence |
|---|---|---|---|---|---|---|---|---|---|---|
| `<task>/<shape>/<schedule>` | serial request / 1 | SDPA, padded | BF16 | none | resident | 1 device | `<card>` BF16 | raw float / local | `not tested` | — |
| `<task>/<shape>/<schedule>` | step / N | `<fast>`, packed | BF16 | none | resident | `<TP/SP>` | `<card>` BF16 | encoded / subprocess | `not tested` | — |
| `<task>/<shape>/<schedule>` | step / N | `<fast>`, packed | FP8 | high | DLO no-AG | `<TP/SP>` | `<card>` | device uint8 / SHM | `not tested` | — |
Initialize every row as `not tested`; the table is a work queue, not evidence.
Promote the dense BF16 row to the oracle only after its scoped parity passes.
Add one axis at a time. If the runtime cannot
enforce a known-incompatible combination, add construction/admission
validation before writing a recipe.
## Online FP8
### Component routing
The public builder accepts one method or a longest-prefix component map:
```python
from vllm_omni.quantization import build_quant_config
config = build_quant_config({
"transformer": {
"method": "fp8",
"ignored_layers": ["transformer.final_layer"],
},
"text_encoder": None,
"vae": None,
"default": None,
})
assert config.resolve("transformer.blocks.0.attn.qkv_proj") is not None
assert config.resolve("text_encoder.layers.0.mlp") is None
assert config.resolve("vae.decoder.conv_out") is None
```
Use actual runtime prefixes. Multi-DiT or nested pipelines may not use the
generic names `transformer` and `vae`. A prefix that falls through to a default
is not component validation. Add tests for every included and ignored prefix.
For CLI serving, first validate resident DiT-only FP8:
```bash
vllm serve '<model>' --omni --quantization fp8 --max-num-seqs 1
```
For scoped routing:
```bash
vllm serve '<model>' --omni \
--diffusion-quantization-config \
'{"<dit-runtime-prefix>":{"method":"fp8","ignored_layers":["<exact-linear-prefix>"]},"<vae-runtime-prefix>":null,"default":null}' \
--max-num-seqs 1
```
Do not publish these placeholders. A recipe must contain the target model's
verified prefixes.
### Quantizable module wiring
Pass `quant_config` and a stable prefix through every vLLM linear. Keep
precision-sensitive modulation/norm layers safe unless proven otherwise.
```python
from vllm.model_executor.layers.activation import SiluAndMul
from vllm.model_executor.layers.linear import (
MergedColumnParallelLinear,
QKVParallelLinear,
RowParallelLinear,
)
self.qkv_proj = QKVParallelLinear(
hidden_size=hidden_size,
head_size=head_dim,
total_num_heads=num_heads,
total_num_kv_heads=num_kv_heads,
bias=False,
quant_config=quant_config,
prefix=f"{prefix}.attn.qkv_proj",
)
self.gate_up_proj = MergedColumnParallelLinear(
hidden_size,
[intermediate_size, intermediate_size],
bias=False,
quant_config=quant_config,
prefix=f"{prefix}.mlp.gate_up_proj",
)
self.act_fn = SiluAndMul()
self.down_proj = RowParallelLinear(
intermediate_size,
hidden_size,
bias=False,
input_is_parallel=True,
quant_config=quant_config,
prefix=f"{prefix}.mlp.down_proj",
)
```
Set required checkpoint parameters to fail rather than silently initialize if
the current loader supports `missing_param_init = "error"`.
### FP8 evidence
For each task/hardware row, capture:
- startup proves the FP8 quant method is attached to intended linear modules;
- no unexpected BF16 fallback and no meta/uninitialized parameters;
- same-seed component, trajectory, and final-artifact comparison with BF16;
- HBM at load, resident, encode, denoise, decode, and peak;
- cold load time; fixed-work warm-latency raw runs; and serving p50/p95/p99 plus
throughput only from a declared arrival load with enough samples;
- named ignored layers and why their BF16 retention is required.
DiT FP8 evidence does not cover the text encoder or VAE. CUDA evidence does not
cover ROCm/NPU/XPU. A pre-quantized checkpoint is a different loading path from
online FP8 and needs its own state. If it is also pruned, rotated, distilled, or
adapter-modified, report that model delta explicitly; BF16-versus-artifact
quality and speed are deployment comparisons, not pure quantization ablations.
## Strict fused-weight loading
Never use `param.data.copy_()` for quantizable vLLM parameters. Transform the
checkpoint layout before invoking the parameter loader so the online quant
loader remains outermost:
```python
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
param = params[target_name]
weight_loader = getattr(param, "weight_loader", default_weight_loader)
if target_name.endswith(".qkv_proj.weight"):
packed_qkv = reorder_checkpoint_qkv(checkpoint_weight)
weight_loader(param, packed_qkv)
elif target_name.endswith(".gate_up_proj.weight"):
gate, up = checkpoint_weight.chunk(2, dim=0)
weight_loader(param, gate, 0)
weight_loader(param, up, 1)
else:
weight_loader(param, checkpoint_weight)
```
Track source shards per fused destination:
```python
expected = {
"blocks.0.attn.qkv_proj.weight": {"q", "k", "v"},
"blocks.0.mlp.gate_up_proj.weight": {0, 1},
}
seen = {name: set() for name in expected}
# After mapping/loading each source:
seen[target_name].add(shard_id)
incomplete = {
name: sorted(shards - seen[name], key=str)
for name, shards in expected.items()
if shards - seen[name]
}
if incomplete:
raise RuntimeError(f"incomplete fused checkpoint parameters: {incomplete}")
```
Also compare retained model parameters with the returned loaded set. Explicit
checkpoint ignores need a reason and test; an unknown or missing weight is not
an ignore policy.
Load-time adapter fusion creates a dedicated model, not a request-switchable
LoRA. Pin the base and adapter revisions, validate every low-rank and full-rank
delta target, freeze the adapter-declared schedule/task, and fuse before
sharding only when the loader contract proves identical rank-local weights.
Reject offload or host-weight paths that bypass that fusion rather than serving
the unfused base model silently.
## Distributed layerwise offload
### Declare component topology
Use `SupportsComponentDiscovery` and `OffloadPlan`. Validate all dotted paths
at construction/test time because discovery may otherwise warn and skip.
Treat a future/requested `--layerwise-offload-components` selector as a public
API only if it exists in the target revision. Today, express component topology
with `OffloadPlan` (`on_demand_component_paths`, `block_attrs`,
`encoder_block_attrs`, `resident_dit_paths`, and `offload_submodules`). Do not
invent a CLI flag or claim component-selective DLO from discovery declarations
alone; validate the selector separately when it lands.
```python
from operator import attrgetter
import torch
def assert_offload_path(root, path):
value = attrgetter(path)(root)
if not isinstance(value, torch.nn.Module):
raise TypeError(f"offload path {path!r} is not an nn.Module")
return value
for path in (
*pipeline._dit_modules,
*pipeline._encoder_modules,
*pipeline._vae_modules,
*pipeline._offload_plan.on_demand_component_paths,
):
assert_offload_path(pipeline, path)
```
For `block_attrs`, resolve the DiT first and then each declared block attribute.
Require an indexable block container with at least one `nn.Module`. Validate
`offload_submodules`, `resident_dit_paths`, and `encoder_block_attrs` similarly.
### Keep host storage loader-owned
Current DLO accepts a loader-produced `HostWeightPlan`. Pipeline authors should
not instantiate this plan or add ad hoc mmap capability flags. The loader must
preflight complete runtime/checkpoint bindings, names, shapes, dtypes, persistent
buffers, and represented transforms before it skips ordinary materialization.
The exact accepted plan is consumed once by DLO; a rejected plan records an
observable fallback reason and continues through the ordinary loader.
Direct checkpoint mmap currently requires TP1 without HSDP or online
quantization. TP>1 falls back to the ordinary TP-aware loader and may feed DLO
AllGather or no-AllGather; the current design records a bounded DP2xTP2
AllGather smoke, but that path does not receive direct-mmap host-page sharing.
Per-tensor online FP8 must use the ordinary loader. Current revisions can admit
its finalized weight/scale layout to DLO AllGather, including transposed runtime
weights, while unsupported online quantizers still fail closed. Direct mmap
remains unavailable, so capture the temporary full-model host materialization
before rank sharding.
Host Weight Runtime is a distinct opt-in path. Current final-layout consumers
use no-AllGather DLO and exact model-declared BF16 representations. In
`preferred` mode, consume an exact local hit or canonically load and publish for
a future startup; in `required` mode, consume only and fail on a miss or unusable
artifact. HWR must not interact with DLO AllGather, online quantization, HSDP,
LoRA/adapted weights, or an unrecognized load format unless the target revision
adds an exact producer/restore contract. It is immutable node-local CPU backing,
not zero-copy GPU execution; the DLO transport still owns staging or direct H2D.
Qualify identity inputs, cold population, warm reuse, atomic concurrent
publication, source digests, corruption/quarantine, lease cleanup, capacity,
node-local PSS, H2D, and E2E latency. A host-memory saving that materially
regresses transfer or request latency is a separate memory-first deployment.
### DLO deployment modes
Treat these paths separately:
| Path | Weight/runtime behavior | Required boundaries |
|---|---|---|
| DLO AllGather | Rank-sharded pinned host tensors; reconstruct layer through collective | TP>1 uses ordinary TP-aware loader output; HSDP and unsupported online quantizers are rejected; finalized per-tensor online FP8 is eligible only when recognized by the target revision; concurrent ranks must participate consistently |
| DLO no-AllGather | Loader-approved checkpoint mmap or ordinary runtime tensors; each rank streams a complete block | Direct mmap is TP1/non-HSDP/non-online-quant only; TP/HSDP/online quant use ordinary runtime tensors and require scoped E2E |
| SP + DLO | SP group can supply the DLO collective group | Packed boundaries and collective order require E2E |
Single-stage examples:
```bash
# SP + DLO AllGather candidate; validate exact model/card before publishing.
vllm serve '<model>' --omni \
--enable-distributed-layerwise-offload \
--usp 4 \
--max-num-seqs 1
# TP + DLO rank-local candidate; not a support statement.
vllm serve '<model>' --omni \
--tensor-parallel-size 2 \
--enable-distributed-layerwise-offload \
--dlo-no-use-allgather \
--max-num-seqs 1
```
Under `--omni`, configure DP in a deploy YAML rather than passing vLLM DP CLI
flags:
```yaml
pipeline: <registered-pipeline>
async_chunk: false
data_parallel_size: 4
stages:
- stage_id: 0
devices: "0,1,2,3"
max_num_seqs: 1
enable_distributed_layerwise_offload: true
dlo_use_allgather: true
parallel_config:
tensor_parallel_size: 1
sequence_parallel_size: 1
```
```bash
vllm serve '<model>' --omni --deploy-config /path/to/dlo_dp4.yaml
```
Keep the YAML next to the validated recipe. Match its schema to the target
revision; do not assume a model accepts another pipeline's deploy config.
### DLO validation sequence
1. Resident BF16 reference.
2. Ordinary layerwise offload parity.
3. DLO enable, warmup, same-seed generation, disable/restart.
4. AllGather and no-AllGather separately.
5. Concurrent requests with different prompts and identical collective-affecting
parameters; then reject or schedule incompatible steps/shapes safely.
6. Queued/in-flight abort, error, idle rank, worker exit, and next request.
7. TP, SP, cache, compile, online FP8 one axis at a time.
8. Loader-plan selection/fallback reason, cold/warm storage lifecycle, and
corruption/stale-entry recovery when mmap/cache backing is advertised.
9. Replica-local request preparation plus DLO-group-only weight collectives in
DP; exercise concurrent replicas to catch accidental WORLD rendezvous.
10. Sweep resident block counts; publish raw latency/HBM pairs, dominated
points, and the non-dominated Pareto frontier.
11. Per-rank HBM, host PSS for the full process tree, H2D and collective trace.
On a small-HBM card, measure transient encode/VAE peaks as well as resident DiT
weights. If a non-DiT component still exceeds HBM, combine only independently
validated on-demand component staging, VAE tiling/patching, or another topology;
do not hide the remaining peak behind the phrase “DLO enabled.”
## Per-request Cache-DiT
### Policy and lifecycle skeleton
First verify that the target revision contains the shared protocol/runtime.
If not, follow the nearest current model's lifecycle without inventing a new
public API.
The following is a **serial/exclusive request-mode sketch only**. Pipeline-wide
hooks are mutable; concurrent requests need an admission/batching key and
runner-owned transition serialization before this shape is safe.
```python
import torch.nn as nn
from vllm_omni.diffusion.cache.cachedit import (
CacheDiTBackend,
CacheDiTRequestSpec,
RequestScopedCacheDiTRuntime,
)
class MyPipeline(nn.Module):
def __init__(self, *args, **kwargs):
super().__init__()
# ... components ...
self._cache_dit_runtime = RequestScopedCacheDiTRuntime(self)
def adopt_cache_dit_backend(self, backend: CacheDiTBackend) -> None:
self._cache_dit_runtime.adopt(
backend,
installation_key="my_model.generic",
)
def is_cache_dit_enabled(self) -> bool:
return self._cache_dit_runtime.is_enabled
def _cache_spec(self, quality, steps):
if quality == "lossless":
return None
if quality == "high":
return CacheDiTRequestSpec(
installation_key="my_model.high.v1",
cache_config=self._validated_high_config,
num_inference_steps=steps,
)
raise ValueError(f"unsupported quality tier: {quality!r}")
def forward(self, req):
quality, steps = validate_and_resolve_request(req)
self._cache_dit_runtime.prepare(self._cache_spec(quality, steps))
return self._run_validated_request(req)
```
The snippet shows the model-owned transition for exclusive execution. Put
disable/restore handling in the runtime/runner lifecycle that owns success,
exception, disconnect, and asynchronous abort; a `try/finally` inside
`forward()` alone cannot observe every server cancellation boundary.
The `installation_key` must include every policy dimension that changes hook
installation; changes in step count can use runtime refresh. `None` disables
installed hooks. Do not mutate the global startup cache config for a request.
Centralize cleanup in the owning request/runner boundary. An exception, abort,
disconnect, or admission failure must not leave a changed hook policy visible
to the next request. The exact owner depends on the target revision; add a
failure-injection test rather than relying on `forward()` alone to observe
asynchronous abort.
Example model-specific request form after calibration:
```bash
curl --fail-with-body -sS -X POST http://127.0.0.1:8091/v1/videos/sync \
-F 'model=<model>' \
-F 'prompt=<prompt>' \
-F 'quality=lossless' \
-F 'num_inference_steps=<validated-steps>' \
-o lossless.mp4
curl --fail-with-body -sS -X POST http://127.0.0.1:8091/v1/videos/sync \
-F 'model=<model>' \
-F 'prompt=<prompt>' \
-F 'quality=high' \
-F 'num_inference_steps=<validated-steps>' \
-o high.mp4
```
Do not expose these tier names merely because another model uses them.
### Cache-DiT evidence
For every tier/task/shape/schedule:
- show real cache hits/skipped blocks, not only “backend enabled” logs;
- measure fixed-work speed and quality against lossless/native;
- publish a speed/quality frontier and chosen tier mapping;
- alternate lossless/high/lossless and vary steps to prove restore/refresh;
- interleave tasks and shapes; run concurrent requests only when scheduling
guarantees exclusive compatible hook state;
- inject validation error, denoise error, disconnect, and abort, then prove the
next request starts with its requested policy;
- bound any cached tensors and clear request-local state.
## Combination gates
Start every combination as `not tested`.
| Combination | Default production posture |
|---|---|
| Direct checkpoint mmap + TP>1/HSDP/online quant | Preflight falls back to the ordinary loader; do not claim direct-mmap savings |
| Per-tensor online FP8 + DLO AllGather | Generic compatibility exists for finalized weights/scales; each model/card/topology remains a candidate until E2E, and other online quantizers fail closed |
| Online FP8 + DLO no-AllGather | Candidate only after model/card/topology E2E |
| TP>1 + DLO AllGather | Ordinary TP-aware loader only; bounded DP2xTP2 smoke exists, but each new model/card/topology still needs E2E |
| TP>1 + DLO no-AllGather | Ordinary TP-aware loader only; candidate with limited generic coverage |
| HSDP + DLO AllGather | Reject; it would double-shard HSDP-managed parameters |
| HSDP + DLO no-AllGather | Configuration-compatible candidate with limited E2E coverage |
| Cache-DiT + online FP8 | Separate trajectory, hit-rate, HBM, latency test |
| Cache-DiT + DLO | Verify hooks, streamed blocks, memory peaks, abort cleanup |
| Cache-DiT + step execution | Do not advertise until lifecycle/batching support is explicit |
| Sparse attention + Ring | Verify backend execution; reject silently ignored sparse settings |
| Load-time fused adapter + offload/HWR | Reject unless the selected loader path applies and validates the same fusion before sharding/storage |
| Packed attention + any topology | Unequal-sample boundary parity is mandatory |
| Device-side uint8 preparation + offline caller | Version the dtype/range/layout change; HTTP MP4 parity does not preserve the raw tensor contract |
| Device-side preparation + remote codec | Validate route selection, strided/contiguous layouts, payload bytes, ordering, fallback, and encoded-media parity |
A startup-only test never upgrades a combination. Require request output,
parity/quality, actual fast-path evidence, resource cleanup, and a benchmark on
the named hardware.
@@ -1,481 +0,0 @@
# Performance Patterns and Code Examples
## Contents
1. [Measurement loop](#measurement-loop)
2. [Faster BF16 attention](#faster-bf16-attention)
3. [Sparse attention](#sparse-attention)
4. [Fusion-first operator patterns](#fusion-first-operator-patterns)
5. [Remove redundant computation](#remove-redundant-computation)
6. [USP communication optimization](#usp-communication-optimization)
7. [Text encoder and VAE disaggregation](#text-encoder-and-vae-disaggregation)
8. [Performance acceptance](#performance-acceptance)
## Measurement loop
Do not start with a list of optimizations. Start with a fixed correctness and
performance workload:
```text
model/checkpoint revision + prompt/media hashes + seed + dimensions/frames +
scheduler/sigma/steps/guidance + task + dtype + backend + topology + hardware
```
Capture:
- stage time: load, encode, conditioning, denoise, VAE decode/postprocess;
- GPU trace: kernel duration/count, launch gaps, allocations, H2D, collectives;
- CPU trace: preprocessing, per-layer sync, Python dispatch, temporary files;
- memory: per-rank HBM peaks and process-tree host PSS;
- output: device preparation, D2H/IPC, payload bytes, codec wall/process CPU,
event-loop wait, client materialization, and signed residual;
- serving: queue time, throughput, errors, request mix, and distribution samples.
For a fixed-work single-request A/B, run one explicit warmup policy and at
least three measured repetitions, keep every raw run, and report median or mean
with range. Do not derive p95/p99 from three samples. For tail latency, declare
the arrival model and collect enough successful requests for the percentile and
confidence claimed. Implement one optimization at a time, re-run parity, and
report both local kernel and end-to-end deltas. A kernel win that moves no E2E
metric is not a production speed claim.
Use the pipeline profiler already exposed by the target revision when
available, and use PyTorch/CUDA/ROCm/NPU/XPU profiling tools appropriate to the
platform. Do not force every platform through a CUDA-only workflow.
## Faster BF16 attention
### Shared attention wiring
Use the shared role-aware attention layer and give it the true local head
counts and QKV layout:
```python
import torch
from vllm.model_executor.layers.linear import (
QKVParallelLinear,
RowParallelLinear,
)
from vllm_omni.diffusion.attention.layer import Attention
class SelfAttention(torch.nn.Module):
def __init__(
self,
hidden_size,
head_dim,
num_heads,
num_kv_heads,
quant_config=None,
prefix="",
):
super().__init__()
self.head_dim = head_dim
self.qkv_proj = QKVParallelLinear(
hidden_size=hidden_size,
head_size=head_dim,
total_num_heads=num_heads,
total_num_kv_heads=num_kv_heads,
bias=False,
quant_config=quant_config,
prefix=f"{prefix}.qkv_proj",
)
self.attention = Attention(
num_heads=self.qkv_proj.num_heads,
num_kv_heads=self.qkv_proj.num_kv_heads,
head_size=head_dim,
softmax_scale=head_dim**-0.5,
causal=False,
qkv_layout="BSND",
role="self",
prefix=prefix,
)
self.out_proj = RowParallelLinear(
num_heads * head_dim,
hidden_size,
bias=False,
input_is_parallel=True,
quant_config=quant_config,
prefix=f"{prefix}.out_proj",
)
def forward(self, x, attn_metadata=None):
qkv, _ = self.qkv_proj(x)
q_size = self.qkv_proj.num_heads * self.head_dim
kv_size = self.qkv_proj.num_kv_heads * self.head_dim
q, k, v = qkv.split([q_size, kv_size, kv_size], dim=-1)
q = q.unflatten(-1, (self.qkv_proj.num_heads, self.head_dim))
k = k.unflatten(-1, (self.qkv_proj.num_kv_heads, self.head_dim))
v = v.unflatten(-1, (self.qkv_proj.num_kv_heads, self.head_dim))
out = self.attention(q, k, v, attn_metadata)
out = out.flatten(-2)
out, _ = self.out_proj(out)
return out
```
Add Q/K norm and RoPE in the official order before the `Attention` call. Keep
checkpoint mapping for the fused QKV and output projection complete.
### Backend qualification
For each role (`self`, cross-attention category, token refiner, etc.), record:
- actual selected backend from logs/trace;
- supported dtype, head size, QKV layout, mask/metadata, packed varlen behavior;
- platform and compute capability;
- TP/SP/Ring/AllGather-KV restrictions;
- fallback behavior and whether fallback changes the claimed performance.
Use unequal packed samples and compare with dense SDPA. Verify `cu_seqlens`,
max sequence length, padding exclusion, modality boundaries, CFG ownership, and
output slicing. Do not pass an `attn_mask` and assume it is consumed. Inspect
the backend contract and trace.
TRTLLM, Flash Attention, cuDNN, NPU varlen, and other backends are independent
qualification rows. The shared `Attention` class does not make every backend
packed-safe or faster automatically. Ring and hybrid paths require their own
boundary parity. AllGather-KV and specific backend combinations may fail fast;
keep that behavior visible in docs.
## Sparse attention
Sparse attention is a backend/model/platform optimization, not a synonym for
low-precision dense attention. Enable it only after dense BF16 parity.
For every candidate task/shape/topology:
1. State the sparsity policy, selected backend/kernel, block/head dimensions,
supported hardware, external kernel/package revision, and fallback.
2. Prove the sparse kernel executed and report realized sparsity; an enabled
flag is insufficient.
3. Compare same-seed per-block outputs, denoise trajectory, and final artifact
with dense attention.
4. Add modality-specific metrics: temporal consistency and audio/video sync for
video, spectral/audio quality for audio, and prompt/image metrics as relevant.
5. Report attention time and full E2E time. Include preprocessing/index-build
overhead, peak memory, and the exact dense control.
6. Validate each task and shape. Do not extrapolate a T2V result to I2V/Ref2V,
or NPU sparse support to CUDA/ROCm/XPU.
If Ring bypasses the selected backend or a sparse setting would be silently
ignored, reject the combination. Prefer a visible dense fallback only when the
recipe labels the row `limited` and does not claim sparse speed.
## Fusion-first operator patterns
Prefer current shared/custom operators with explicit guards and native
fallbacks. Prove parity at operator, block, trajectory, and artifact levels,
then use a trace to prove fewer launches/bytes or lower latency.
### Q/K RMSNorm + RoPE
```python
from vllm_omni.diffusion.layers.norm import RMSNorm
from vllm_omni.diffusion.layers.rope import RotaryEmbedding, apply_rope_to_qk
self.norm_q = RMSNorm(head_dim, eps=eps)
self.norm_k = RMSNorm(head_dim, eps=eps)
# Example only: set these booleans from the official checkpoint contract.
use_neox_layout = False
cos_sin_are_half_dim = True
self.rope = RotaryEmbedding(
is_neox_style=use_neox_layout,
half_head_dim=cos_sin_are_half_dim,
)
q = self.norm_q(q)
k = self.norm_k(k)
q, k = apply_rope_to_qk(self.rope, q, k, (cos, sin))
```
Replace the illustrative booleans with the official layout in real code.
Preserve partial rotary dimensions by splitting the rotated prefix and concatenating
the pass-through suffix. If 3D cos/sin includes a batch axis, the current
shared path assumes the values are shared across the batch; per-sample
different positions require a proven representation/path.
For packed `[tokens, heads, head_dim]` tensors with a non-interleaved
`[cos | sin]` table, the current shared fused boundary is:
```python
from vllm_omni.diffusion.layers.fused_qk_norm_rope import fused_qk_norm_rope
q, k = fused_qk_norm_rope(
q,
k,
self.norm_q.weight,
self.norm_k.weight,
rope_table,
self.norm_q.variance_epsilon,
)
```
Its CUDA fast path currently specializes BF16 `head_dim=128` and
`rotary_dim=96`; other valid inputs take the eager implementation. The separate
shared RMSNorm and RotaryEmbedding still provide the general cross-platform
baseline. Treat NPU/MUSA and other vendor fusions as independent qualification
rows, and do not generalize a model-specific Triton kernel without compatible
shape, layout, frequency partition, dtype, and platform evidence.
### Fused QKV projection
Use `QKVParallelLinear` as shown above. Qualification includes:
- `num_heads` and `num_kv_heads` divisibility by TP;
- local split sizes from the layer, never reusing global counts after TP;
- Q/K/V checkpoint order conversion before the vLLM loader;
- all Q, K, and V shards present;
- quantization scale/loader mapping when FP8 or another method is enabled.
### Gate-up projection + fused SwiGLU
```python
from vllm.model_executor.layers.activation import SiluAndMul
from vllm.model_executor.layers.linear import (
MergedColumnParallelLinear,
RowParallelLinear,
)
self.gate_up_proj = MergedColumnParallelLinear(
input_size=hidden_size,
output_sizes=[intermediate_size, intermediate_size],
bias=False,
quant_config=quant_config,
prefix=f"{prefix}.gate_up_proj",
)
self.act_fn = SiluAndMul()
self.down_proj = RowParallelLinear(
input_size=intermediate_size,
output_size=hidden_size,
bias=False,
input_is_parallel=True,
quant_config=quant_config,
prefix=f"{prefix}.down_proj",
)
gate_up, _ = self.gate_up_proj(x)
x = self.act_fn(gate_up)
x, _ = self.down_proj(x)
```
Map both checkpoint `gate_proj` and `up_proj` shards into the merged parameter
and require both. A merged projection followed by `F.silu(gate) * up` is not
the same claim as the `SiluAndMul` fused activation kernel; identify what the
trace actually uses.
### AdaLN
Use a shared layer only when its mathematical/return contract matches the
official model:
```python
from vllm_omni.diffusion.layers.adalayernorm import AdaLayerNorm
self.adaln = AdaLayerNorm(
hidden_size,
elementwise_affine=False,
eps=1e-6,
)
x = self.adaln(x, scale[:, None, :], shift[:, None, :])
```
`AdaLayerNormZero`, `AdaLayerNormZeroSingle`, and continuous variants include
different projections, broadcasts, and tuple outputs; they are not drop-in
replacements. A shared AdaLN may have an NPU fast path while using native ops on
CUDA/HIP/XPU. Do not call it a universal fused AdaLN kernel.
For sigma schedules with a small finite set of timesteps, profile whether
precomputing the timestep/AdaLN projection once per request is worthwhile.
Cache keys must include the exact schedule/config/device/dtype and stay bounded.
### Layout, residual, and other candidates
After profiling, inspect:
- bias/dropout/add or gated residual chains;
- norm + linear or norm + modulation boundaries;
- repeated transpose/reshape/contiguous around attention;
- VAE normalization/activation/convolution boundaries;
- MoE routing and fused experts for models that actually use MoE.
For fused residual-plus-norm or modulation chains, preserve the unfused
materialization boundary. If the residual was stored in BF16 before RMSNorm,
round to BF16 and promote again inside the fused kernel; using the unrounded
FP32 temporary changes the denoising trajectory even when the stored residual
looks correct.
Keep DLO materialize/release hooks, process-group collectives, request cache
transitions, and cleanup outside compiled/fused regions unless lifecycle safety
is explicitly tested. Compile only stable regions and retain eager fallback.
## Remove redundant computation
### Hoist request invariants
Audit the denoise loop for work that depends only on the request rather than
the current timestep:
```python
# Before the denoise loop.
condition = self.condition_proj(text_embeddings)
refined_condition = self.token_refiner(condition, packed_metadata)
rope_tables = self.build_rope_tables(position_ids)
attn_metadata = self.build_attention_metadata(layout)
for step, sigma in enumerate(sigmas):
timestep_state = self.prepare_timestep_state(sigma)
noise = self.transformer(
latents,
refined_condition,
rope_tables=rope_tables,
attn_metadata=attn_metadata,
timestep_state=timestep_state,
)
latents = self.scheduler_step(noise, sigma, latents)
```
Candidates include prompt conditioning, token-refiner output, reference
encoding/scans, position IDs/RoPE tables, packed masks/metadata, static CFG
branches, and finite-schedule AdaLN projections. First prove they are invariant
for all official tasks, LoRA/adapters, prompt-update features, shapes, and
schedules. In continuous batching, store request-specific results on request
state rather than the pipeline singleton.
### Eliminate per-layer synchronization
Avoid device-to-host scalar extraction such as `.item()` inside every layer:
```python
# Bad: forces a GPU/CPU synchronization in every attention layer.
max_len = int(cu_seqlens[-1].item())
# Better: compute validated host metadata once during request packing and pass it.
packed = PackedLayout(
cu_seqlens=cu_seqlens,
max_seqlen=max(sample_lengths),
)
for block in self.blocks:
hidden = block(hidden, packed_layout=packed)
```
Trace to verify the synchronization disappears. Do not replace a correct
dynamic value with a stale constant merely to remove `.item()`.
### Remove unused masks and allocations
If packed attention consumes `cu_seqlens` and ignores a dense mask, do not
construct the dense mask. Reuse immutable request metadata and preallocated
buffers when safe. Prefer direct indexed scatter/gather over constructing large
temporary tensors only when duplicate-index semantics and gradients (normally
disabled for inference) remain correct.
Any cross-request cache needs a bounded lifetime and a complete key containing
shape, layout, device, dtype, schedule, task, adapter/LoRA, and any value that
changes the tensor. Otherwise keep it request-scoped.
### VAE and reference work
Profile duplicate VAE decode/postprocess, repeated reference decoding/scans,
unnecessary host-device round trips, and full-frame materialization. Apply VAE
tiling, slicing, patch parallelism, or on-demand staging only with seam,
ordering, color/range, and memory parity tests.
## USP communication optimization
“USP works” is not evidence that USP communication is optimized. For Ulysses,
Ring, hybrid, and AllGather-KV separately, profile:
- collective count, bytes, duration, stream, and overlap with compute;
- Q/K/V and output all-to-all boundaries;
- pack/unpack, padding, metadata broadcast, transpose, and contiguous copies;
- head divisibility or advanced uneven-head support;
- scaling efficiency from 1 to N devices on the same workload/interconnect;
- load balance for unequal packed samples.
Declare `_sp_plan` split/gather boundaries around meaningful module outputs,
not arbitrary Python statements. Keep packed metadata sharded/gathered in the
same ownership model as tokens.
When the target revision offers accelerated Ulysses transport such as
`--ulysses-a2a-permute`, qualify it separately from regular Ulysses. Prove the
strict scatter-heads/gather-sequence layout selected the fast path, record
one-time JIT/readiness cost, prewarm the maximum workspace shape before CUDA
graph capture, keep the grow-only workspace on one stream, and exercise cleanup
before process-group destruction. Compare collective and E2E deltas after the
dense backend and topology are frozen; keep unsupported layouts on an explicit
regular-Ulysses fallback.
For GQA, validate `Q_heads % KV_heads == 0`, pad KV heads first, and derive Q
padding from the original ratio. Exercise both contiguous and row-strided QKV
layouts; a fast copy path must preserve bitwise values and packed boundaries.
Cross-attention sometimes uses replicated text K/V while only video/audio Q is
sequence-sharded. In that specific case, `skip_sequence_parallel=True` can
avoid incorrect or wasteful Q/K/V redistribution:
```python
self.cross_attention = Attention(
num_heads=local_heads,
num_kv_heads=local_kv_heads,
head_size=head_dim,
softmax_scale=head_dim**-0.5,
causal=False,
role="cross",
skip_sequence_parallel=True,
prefix=f"{prefix}.cross_attn",
)
```
Use it only after proving K/V are replicated and Q/output ownership is correct.
Otherwise it disables needed communication. Report whether the optimization
reduces collective bytes/time and improves E2E scaling.
## Text encoder and VAE disaggregation
Disaggregation is a measured architecture decision. First collect:
| Question | Required measurement |
|---|---|
| Is the stage large enough? | Encode/decode share at target concurrency and shape |
| Is transfer economical? | Tensor/media bytes, serialization, connector latency and bandwidth |
| Is there reuse? | Prompt/reference reuse rate, cacheability, fan-out |
| Can it scale independently? | Queue saturation and resource utilization per stage |
| Is the contract stable? | Shape, dtype, mask/tag/position metadata, ordering |
### Text encoder candidate
Define an explicit tensor contract containing hidden states, masks, tags or
position IDs, dtype, shape, task/checkpoint revision, and request ID. Support
disabling the local encoder so the DiT cannot accidentally recompute it. Test
all tasks, prompt lengths, partial TP group membership, retries, backpressure,
abort, connector cleanup, and independent replica scaling.
### VAE candidate
Separate VAE encode and decode contracts if both exist. Define latent shape,
scale/shift, dtype/range, tiling/chunk order, output media ordering, and stage
metadata. Measure latent/media transfer against local decode cost. Test tiling
seams, RNG/determinism where relevant, chunk ordering, codec/postprocess,
backpressure, abort, and worker failure.
Do not label VAE tiling, patch parallelism, compile, or offload as VAE
disaggregation. Likewise, a separate text-encoder TP group is not a separate
stage. If no reusable generic stage/connector exists in the target revision,
produce a feasibility result or RFC and keep the capability `not tested` rather
than inventing a model-local production architecture.
## Performance acceptance
For each accepted optimization, require:
- same fixed workload and correctness artifacts before/after;
- actual fast-path execution proven by logs/trace/counters;
- local metric plus E2E latency/throughput and memory;
- at least three raw fixed-work repetitions after explicit warmup, or a
declared arrival-load sample set large enough for every tail percentile;
- named hardware, backend, topology, dtype, and task scope;
- fallback/rejection tests for unsupported shapes/platforms;
- no regression in abort/error cleanup or long-run memory trend.
Update the feature matrix narrowly. Do not convert a profile-driven direction
into a generic “supported” feature merely because a model-specific PR exists.
@@ -1,464 +0,0 @@
# Production Serving and Validation
## Contents
1. [Execution-mode decision](#execution-mode-decision)
2. [Request-level batching](#request-level-batching)
3. [Step execution and continuous batching](#step-execution-and-continuous-batching)
4. [Abort and cleanup](#abort-and-cleanup)
5. [Mixed-RPS soak](#mixed-rps-soak)
6. [Four CI tracks](#four-ci-tracks)
7. [PR evidence checklist](#pr-evidence-checklist)
## Execution-mode decision
Request batching and step-wise execution solve different problems:
| Mode | Pipeline contract | Scheduling/abort boundary |
|---|---|---|
| Serial request | `forward(DiffusionRequestBatch)` for one request | Whole request; in-denoise preemption is unavailable |
| Request batching | `supports_request_batch = True`; one forward consumes compatible independent requests | Whole batch/request |
| Single-request step | Full `SupportsStepExecution`; `max_num_seqs=1` | Between denoise steps |
| Step continuous batching | Full step contract plus heterogeneous batched-step evidence | Between denoise waves/steps |
Do not expose four stub methods as an opt-out. The current protocol is
structural; if a native pipeline is not step-capable, do not define the four
step methods. If it is capable, implement and test all four.
Treat structural support, a generic request-batch bridge, and model benefit as
three separate claims. Before recommending step execution, name a hypothesis
that request mode cannot satisfy, define a success threshold, and run a matched
request-mode control. If throughput or latency regresses, retain step execution
as functional/limited and keep request mode as the production recommendation.
Choose compatibility keys from every value that changes tensor shapes,
semantics, hooks, or scheduler behavior: task, output shape/count, frames,
steps/schedule, guidance/CFG, dtype/backend, LoRA/adapter, cache quality policy,
and model-specific conditions. Reject or schedule separately rather than
silently using the first request's settings.
## Request-level batching
A request-batched pipeline returns exactly one `DiffusionOutput` per logical
request. This is model-owned pseudocode: `validate_batch_compatibility()` and
`_generate_batch()` are placeholders to implement from the official contract,
not shared APIs to import.
```python
import torch
from vllm_omni.diffusion.data import DiffusionOutput
from vllm_omni.diffusion.worker.request_batch import (
DiffusionRequestBatch,
split_diffusion_output_by_request,
)
class MyPipeline(torch.nn.Module):
supports_request_batch = True
def forward(self, req: DiffusionRequestBatch) -> list[DiffusionOutput]:
validate_batch_compatibility(req.sampling_params_list, req.prompts)
common = req.sampling_params_list[0]
text_prompts: list[str] | None = [
prompt
if isinstance(prompt, str)
else (prompt.get("prompt") or "")
for prompt in req.prompts
]
prompt_fields = DiffusionRequestBatch.collate_prompt_field_map(
req.prompts,
{"prompt_embeds": None, "prompt_mask": None},
)
if prompt_fields["prompt_embeds"] is not None:
text_prompts = None
generators = req.collate_request_generators(
common.num_outputs_per_prompt,
default_generator=None,
)
output = self._generate_batch(
prompt=text_prompts,
prompt_fields=prompt_fields,
generators=generators,
sampling=common,
)
return split_diffusion_output_by_request(
DiffusionOutput(output=output),
req,
num_outputs_per_prompt=common.num_outputs_per_prompt,
)
```
The prompt-field collator only handles tensor fields; extract raw text prompts
and model-specific negative/alias fields separately, as current in-tree batched
pipelines do. Set a raw prompt field to `None` when the matching precomputed
embedding is supplied. The exact aliases and generator device depend on the
model. Use current collators rather than manual `torch.cat` when they cover the
contract; they reject mixed present/missing tensors and incompatible
shape/dtype/device.
Before reading `sampling_params_list[0]`, validate every field that must be
common. Tests must cover:
- two and N requests, independent seeds/generators, one output each;
- mixed prompt lengths and packed/padded boundaries;
- multiple outputs per prompt and correct result slicing;
- compatible versus incompatible shapes, steps, guidance, LoRA, and quality;
- one request failure without cross-request output or state corruption;
- output ordering and request IDs under different completion times.
After the model advertises support, candidate serving is:
```bash
vllm serve '<model>' --omni \
--max-num-seqs 4 \
--request-batch-max-wait-ms 20
```
The wait window trades admission latency for batch formation. Benchmark at 0
and candidate nonzero values under the target arrival process.
## Step execution and continuous batching
### Full protocol skeleton
Use the current shared state types and make every mutable value request-local:
The helpers beginning with `_` and `validate_step_batch()` below are
model-owned placeholders; scheduler initialization is scheduler-specific.
```python
import copy
from collections.abc import Sequence
from typing import Any, ClassVar
import torch
from vllm_omni.diffusion.data import DiffusionOutput
from vllm_omni.diffusion.models.interface import SupportsStepExecution
from vllm_omni.diffusion.worker.input_batch import InputBatch
from vllm_omni.diffusion.worker.utils import StepRequestState
class MyPipeline(torch.nn.Module, SupportsStepExecution):
supports_step_execution: ClassVar[bool] = True
def prepare_encode(
self,
state: StepRequestState,
**kwargs: Any,
) -> StepRequestState:
prepared = self._prepare_request(state.prompt, state.sampling)
state.prompt_embeds = prepared.prompt_embeds
state.latents = prepared.latents
state.timesteps = prepared.timesteps
state.scheduler = copy.deepcopy(self.scheduler)
state.scheduler.set_begin_index(0)
state.step_index = 0
state.extra["task_state"] = prepared.task_state
return state
def denoise_step(
self,
input_batch: InputBatch,
*,
states: Sequence[StepRequestState] | None = None,
**kwargs: Any,
) -> torch.Tensor | None:
validate_step_batch(input_batch, states)
return self.transformer(**build_batched_denoise_inputs(input_batch))
def step_scheduler(
self,
state: StepRequestState,
noise_pred: torch.Tensor,
**kwargs: Any,
) -> None:
timestep = state.current_timestep
if timestep is None:
raise RuntimeError("step_scheduler called without a current timestep")
state.latents = state.scheduler.step(
noise_pred,
timestep,
state.latents,
return_dict=False,
)[0].to(state.latents.dtype)
state.step_index += 1
def post_decode(
self,
state: StepRequestState,
**kwargs: Any,
) -> DiffusionOutput:
return DiffusionOutput(output=self.decode(state.latents))
```
This is a shape guide, not drop-in code: verify the target revision's
`StepRequestState.extra`, scheduler signature, CFG handling, state fields, and
decode contract. Follow a current native pipeline such as Qwen-Image for the
exact revision.
Never use the shared `self.scheduler` to advance a request. Store scheduler,
latents, embeddings, timesteps, masks, RNG/generator, task metadata, cache
policy, adapter, and output assembly state on `StepRequestState` or its
request-local extension. Pipeline fields may hold immutable weights/config and
bounded immutable caches only.
### Qualification sequence
1. Compare request-mode and step-mode at `max_num_seqs=1` for every task using
identical seed/schedule/shape and intermediate latents.
2. Abort queued and in-flight requests at several step boundaries.
3. Increase to two compatible requests; verify independent scheduler/RNG/state
and output order.
4. Increase to the target capacity and mix prompt lengths.
5. Exercise mixed shape, frames, steps/schedule, task, guidance, and quality.
Batch only compatible rows; otherwise split/reject predictably.
6. Inject a failure in encode, denoise, scheduler, and decode independently.
7. Run the production soak and fault matrix.
Candidate commands only after capability validation:
```bash
# Conservative step execution and step-boundary abort.
vllm serve '<model>' --omni \
--step-execution \
--max-num-seqs 1
# Experimental batched-step candidate after multi-request E2E.
vllm serve '<model>' --omni \
--step-execution \
--max-num-seqs 4
```
If an attention backend is only correct for single-request step mode, fail
early or keep the recipe at `max_num_seqs=1`. Do not describe generic runtime
support as model support.
## Abort and cleanup
### Required semantics
For queued and in-flight abort/disconnect:
- emit exactly one terminal aborted/error result;
- do not run `post_decode` after abort unless a documented partial-output mode
explicitly requires it;
- do not increment `step_index` or apply a scheduler result after cancellation;
- remove scheduler/state cache entries and request IDs;
- release latents, prompt/reference tensors, cache hooks, DLO buffers, connector
messages, uploaded/temp files, and output buffers;
- keep other requests alive;
- make cleanup idempotent when disconnect, timeout, and worker error race;
- drop late results/exceptions for already-resolved or cancelled futures
without terminating the shared result-pump thread;
- complete the next valid request with its requested cache/offload policy.
Test cancellation before engine admission, while queued, after encode, during
early/middle/final denoise steps, before decode, and while returning/persisting
output. Include async job cancellation and client socket disconnect.
### Failure injection matrix
| Location | Injection | Assertions |
|---|---|---|
| Validation/upload | bad MIME, count/bytes overflow, truncated media | 400/413, no job/engine submit, no temp leak |
| Load/start | missing fused shard, unsupported topology | startup fails, actionable error, no silent init |
| Encode | exception/OOM/timeout | one error, request state/temp released |
| Denoise | exception/OOM/worker exit | peers handled, hooks/buffers released, health behavior explicit |
| Scheduler | invalid state/shape | no latent/step partial commit |
| Decode/post | exception/codec error | no corrupt successful result, partial output removed |
| Result/IPC | late result after cancel, large split tensor, orphan SHM | pump remains alive, one terminal result, exact ownership cleanup |
| Connector/disagg | timeout/retry/duplicate | idempotency, backpressure, no orphan payload |
After each injection, check server health and issue a known-good request. If
the process must restart, document and test the restart policy; do not claim
transparent recovery.
### Large-output transport
Treat worker-to-engine IPC and engine-to-client transport as separate hops.
For batched video, prove that each logical output does not accidentally retain
or serialize the complete batch storage. Exercise the target revision's shared
memory or artifact-handle path above and below its threshold, including
non-contiguous views, multiple outputs, consumer failure, timeout, abort, and
worker exit. Verify every shared segment/file is released exactly once.
Freeze the media contract at each hop: dtype, value range, shape/layout,
contiguity, request ownership, encoding/color model, payload bytes, and pending
consumers. Device-side float-to-uint8 preparation, D2H/IPC, planar/interleaved
conversion, CPU encode/mux, and delivery off the asyncio event loop are
different optimizations. Validate their route selection and fallbacks
independently. Byte-identical HTTP MP4 output does not prove that a raw offline
tensor kept its dtype or range.
For long-window output, validate segment ordering and audio/video continuity,
then exercise payloads around serializer, shared-memory, HTTP, and codec size
limits. Chunking must reassemble exactly, reject malformed/missing chunks, and
release partial state after abort, timeout, or peer failure.
Qualify chunk production, transport, and public streaming as separate layers.
A VAE callback must define owned representation, frame offsets, final marker,
rank ownership, source/config gate, and collective-safe failure, but it does not
prove SHM/ZMQ delivery, bounded backpressure, cancellation, codec reuse, or
client streaming. Measure time to first owned chunk, first encoded fragment,
steady cadence, mux finalization, and complete client artifact independently.
Measure remote MP4 encoding, serialization/copies, network transfer, and client
materialization separately from denoise/decode. An inference-kernel speedup does
not establish serving throughput if output transport dominates or leaks.
## Mixed-RPS soak
Use a realistic request mix and arrival distribution. Include small/large
shapes, short/long prompts, all advertised tasks, cache tiers, and a small rate
of abort/disconnect/error injections. Run phases below, near, and above measured
saturation so queue/backpressure behavior is visible.
Example serving benchmark phase:
```bash
python benchmarks/diffusion/diffusion_benchmark_serving.py \
--base-url http://127.0.0.1:8091 \
--endpoint /v1/videos \
--model '<model>' \
--dataset trace \
--dataset-path /path/to/versioned-production-trace.jsonl \
--task t2v \
--num-prompts 500 \
--request-rate 0.5 \
--max-concurrency 8 \
--warmup-requests 8 \
--warmup-concurrency 8 \
--output-file soak-near-saturation.json
```
Use a task-appropriate trace/media dataset. Run the same phase at rates selected
from an initial saturation curve; a literal `0.5` is illustrative only.
On supported GPU hosts, wrap a test or soak with the repository monitor:
```bash
bash tests/dfx/stability/scripts/resource_monitor.sh run --backend gpu -- \
pytest -s -v tests/dfx/stability/scripts/test_stability_<model>.py -m slow
```
Check the target revision's monitor backends. Do not claim CPU/NPU monitoring
from a reserved CLI choice unless collection is implemented and verified.
Report:
- offered and achieved RPS, success/error/abort rates;
- p50/p95/p99 and queue/service/stage times, with the successful sample count
and arrival process used for each percentile;
- throughput per device and batch/wave utilization;
- per-rank HBM and process-tree PSS trend/slope;
- cache size/hit rate, temp-file/disk growth, open FDs/handles;
- outstanding shared-memory/artifact handles and encoded/network bytes;
- worker/engine health, restarts, OOM/timeouts, backpressure response;
- total duration, request count/mix, raw logs/JSON/monitor artifact hashes.
A short burst or repeated serial curl loop is not a stability test.
## Four CI tracks
Keep the tracks independent so a failure answers a specific question. These
four readiness tracks are not the repository's numbered CI levels. Map them
onto the current L1-L5 taxonomy:
| Readiness track | Repository CI placement |
|---|---|
| Function | L1 unit/logic plus L2 basic online/offline E2E; extend real-model scenarios in L3/L4 |
| Accuracy | L3 real-model gates for important cases; deeper and broader nightly coverage in L4 |
| Performance | Important thresholds in L3 where they fit the time budget; full baselines/regression jobs in nightly L4 |
| Reliability | Cheap rejection/recovery assertions can run earlier, but long stability, fault injection, and reliability suites belong to weekly L5 |
Distributed topology is a test case within the appropriate level, not a
replacement definition for a level.
### 1. Function CI
Cover:
- strict unquantized and advertised quantized loading;
- fused source-shard completeness and prefix routing;
- official task positive boundaries and neighboring negative cases;
- offline, sync, async normalization/parity;
- single-device reference plus every advertised topology smoke;
- output type/count/order and request ID isolation;
- feature rejection paths that must fail early.
### 2. Accuracy CI
Pin official implementation/checkpoint/vLLM-Omni revisions, prompts and media
hashes, seed, scheduler/sigma/steps, dimensions/frames/FPS, guidance, dtype,
backend, and topology. Compare at suitable levels:
- component outputs or selected blocks;
- one or more intermediate denoise latents;
- final image/video/audio artifact metrics;
- temporal consistency and audio/video synchronization where relevant.
Set tolerance per modality/hardware/backend with rationale. Keep failure
artifacts and metadata. A single visual inspection does not define CI.
For quantization trajectory tooling, inspect the target revision's current
`vllm_omni/quantization/tools/compare_diffusion_trajectory_similarity.py` and
reuse it when compatible instead of creating an ad hoc comparator.
### 3. Performance CI
Pin workload and best deployment. Include a resident recommended row and a
small-HBM/DLO row when both are production targets. Declare:
- one explicit warmup exclusion and at least three measured fixed-work
repetitions;
- raw result JSON and environment identity;
- median/mean plus range for small fixed-work A/Bs; p50/p95/p99 only for an
arrival-load run with enough samples, plus throughput/RPS, stage time,
HBM/PSS;
- regression metric, threshold, baseline owner, and update procedure;
- correctness/accuracy check linked to the same configuration.
Use the current harness config style, for example:
```bash
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py \
--test-config-file tests/dfx/perf/tests/test_<model>_vllm_omni.json
```
Verify parser options on the target revision. Do not copy a JSON schema from a
different model without matching its endpoint, task, dataset, and deploy path.
### 4. Reliability CI
Run a bounded CI soak plus a longer scheduled soak. Cover:
- below/near/above-saturation arrival phases;
- queued/in-flight abort and disconnect;
- OOM/timeout and worker/process failure appropriate to the platform;
- DLO/cache/connector cleanup;
- health, fast-fail/backpressure, restart/recovery policy;
- memory/temp/FD trend and successful known-good request after each fault.
Keep reliability thresholds independent from latency thresholds. A fast server
that leaks memory or hangs after cancellation fails production readiness.
## PR evidence checklist
The PR description must include or link:
- official source/checkpoint revisions and API/limit matrix;
- scoped feature compatibility matrix with the four allowed states;
- implementation notes for FP8, DLO, request cache, attention/fusion, batching;
- exact environment, deploy/serve, every task curl, benchmark and soak commands;
- fixed accuracy inputs and artifacts;
- raw before/after performance runs and memory traces;
- output-contract manifests and stage accounting from device preparation
through the complete client artifact;
- abort/disconnect/fault results and cleanup evidence;
- Function/Accuracy/Performance/Reliability CI files and ownership;
- limitations/TODOs that remain `not tested` rather than implied support.
Separate the Day-0 milestone from production readiness. If the PR only closes
some gates, title and description should say which scoped gates it closes.
+2 -12
View File
@@ -26,24 +26,14 @@ self-check.
- [`add-diffusion-model`](add-diffusion-model/SKILL.md): guides integration of - [`add-diffusion-model`](add-diffusion-model/SKILL.md): guides integration of
a new diffusion model into `vllm-omni` a new diffusion model into `vllm-omni`
- [`add-recipe`](add-recipe/SKILL.md): creates or updates in-repository model
recipes with verified task/input/output contracts, concrete hardware,
shared feature-guide links, exact commands, and qualification evidence
- [`add-tts-model`](add-tts-model/SKILL.md): covers integration of new TTS - [`add-tts-model`](add-tts-model/SKILL.md): covers integration of new TTS
models and related serving workflows models and related serving workflows
- [`diffusion-perf-opt`](diffusion-perf-opt/SKILL.md): guides diffusion model - [`diffusion-perf-opt`](diffusion-perf-opt/SKILL.md): guides diffusion model
performance optimization, including profiling traces, parallel strategies, performance optimization, including profiling traces, parallel strategies,
stage timing analysis, and benchmark-driven tuning stage timing analysis, and benchmark-driven tuning
- [`find-simplifications`](find-simplifications/SKILL.md): finds evidence-backed
opportunities to remove or merge dead, duplicated, speculative,
over-generalized, or unnecessarily defensive vLLM-Omni code
- [`precheck-pr`](precheck-pr/SKILL.md): self-checks a branch before creating a - [`precheck-pr`](precheck-pr/SKILL.md): self-checks a branch before creating a
PR by validating title format, dead code, simplification opportunities, PR by validating title format, dead code, accuracy and performance claims,
accuracy and performance claims, and merge readiness and merge readiness
- [`production-add-diffusion-model`](production-add-diffusion-model/SKILL.md):
takes a working Day-0 diffusion integration through API/limit parity,
feature-combination evidence, quantization/offload, performance, production
serving, hardware recipes, and CI readiness
- [`quantization`](quantization/SKILL.md): guides quantization method selection, - [`quantization`](quantization/SKILL.md): guides quantization method selection,
model integration, checkpoint loading, and quality/performance validation model integration, checkpoint loading, and quality/performance validation
for vLLM-Omni for vLLM-Omni
+10 -27
View File
@@ -21,10 +21,7 @@ Make every finding:
- **Calibrated:** match severity to user and maintainer impact. - **Calibrated:** match severity to user and maintainer impact.
Do not report unrelated backlog, style already enforced by pre-commit, or a Do not report unrelated backlog, style already enforced by pre-commit, or a
missing test that would not protect changed behavior. Do report new allowlist missing test that would not protect changed behavior.
or budget entries in `check_forbidden_imports.py` / `check_torch_cuda.py` /
`check_tts_adapter.py` / `check_buildkite.py` unless the PR justifies them:
those are policy changes, not lint noise.
## Select the input and depth ## Select the input and depth
@@ -95,13 +92,11 @@ If docs and live code disagree, verify the code/tests and report the drift.
| Reference | Read when | | Reference | Read when |
| --- | --- | | --- | --- |
| [model-addition-checklist.md](references/checks/model-addition-checklist.md) | A model, architecture, loader, processor, registry, pipeline config, or deploy config is added. | | [model-addition-checklist.md](references/checks/model-addition-checklist.md) | A model, architecture, loader, processor, registry, or stage config is added. |
| [perf-verification.md](references/checks/perf-verification.md) | The PR makes a latency, throughput, memory, or quality claim. | | [perf-verification.md](references/checks/perf-verification.md) | The PR makes a latency, throughput, memory, or quality claim. |
| [test-quality-evaluation.md](references/checks/test-quality-evaluation.md) | Tests change, are absent for risky code, or may not exercise production behavior. | | [test-quality-evaluation.md](references/checks/test-quality-evaluation.md) | Tests change, are absent for risky code, or may not exercise production behavior. |
| [tests-docs-checklist.md](references/checks/tests-docs-checklist.md) | Coverage, CI markers, examples, user docs, or PR evidence need review. | | [tests-docs-checklist.md](references/checks/tests-docs-checklist.md) | Coverage, CI markers, examples, user docs, or PR evidence need review. |
| [verification.md](references/checks/verification.md) | Hardware, a server, or a runnable affected path is available for active verification. | | [verification.md](references/checks/verification.md) | Hardware, a server, or a runnable affected path is available for active verification. |
| [examples-policy.md](../precheck-pr/references/examples-policy.md) | The PR adds, copies, or renames Python under `examples/`; apply the canonical policy shared with `precheck-pr`. |
| [find-simplifications](../find-simplifications/SKILL.md) | Every review; run a diff-scoped subtraction and simplification pass after correctness blockers. |
### Delivery and reviewer coordination ### Delivery and reviewer coordination
@@ -123,11 +118,10 @@ it changes again, report the churn and wait for a stable target.
For a trusted PR head, materialize the pinned head in an isolated detached For a trusted PR head, materialize the pinned head in an isolated detached
worktree. A worktree freezes identity but is not a security sandbox. Treat fork worktree. A worktree freezes identity but is not a security sandbox. Treat fork
heads as untrusted until the user and environment policy explicitly establish heads as untrusted unless the user and environment policy explicitly establish
trust: execute their code only after that trust is recorded for this host, otherwise: execute them only in a disposable, secret-free sandbox with restricted
with the trusted SHA noted in the review state and secrets kept out of the filesystem, network, and resources; without one, use static SHA-addressed reads
execution scope; without recorded trust, use static SHA-addressed reads and CI and CI evidence only. For a local review, freeze the committed, index, worktree,
evidence only. For a local review, freeze the committed, index, worktree,
and NUL-safe in-scope untracked contents. Follow and NUL-safe in-scope untracked contents. Follow
[review-execution.md](references/process/review-execution.md) for trust gates, [review-execution.md](references/process/review-execution.md) for trust gates,
state fingerprints, and byte-for-byte staleness checks. state fingerprints, and byte-for-byte staleness checks.
@@ -158,13 +152,6 @@ contract they protect or use only the applicable evidence checks.
Apply every category in [general-checks.md](references/process/general-checks.md) before Apply every category in [general-checks.md](references/process/general-checks.md) before
lower-priority comments. lower-priority comments.
If the diff census contains an added, copied, or renamed Python path under
`examples/`, read and apply the canonical
[examples policy](../precheck-pr/references/examples-policy.md). Treat a new
model-specific Python example as blocking. Do not flag model-specific example
debt that the PR only modifies or removes, and do not run the rest of the
author-oriented `precheck-pr` workflow.
For each changed value or behavior, trace: For each changed value or behavior, trace:
```text ```text
@@ -185,19 +172,15 @@ promotion gate. Candidate or draft rules are questions, not blockers, unless
current code, tests, or policy enforce them. Inspect both sides of any config, current code, tests, or policy enforce them. Inspect both sides of any config,
registry, serialization, connector, cache, or stage boundary. registry, serialization, connector, cache, or stage boundary.
Read and apply [find-simplifications](../find-simplifications/SKILL.md) on every When a diff adds or expands a helper, class, fallback, compatibility branch, or
review. Constrain it to the diff and the adjacent ownership, callers, or public behavior, run a subtraction pass: remove out-of-scope behavior and check
consumers needed to prove a candidate. Check whether added or expanded helpers, whether each new abstraction can be deleted, merged, moved, or inlined.
classes, state, fallback and compatibility branches, data movement, or public
behavior can be deleted, merged, moved, or inlined. Zero candidates is a valid
result. Do not widen the review into repository backlog or report speculative
style preferences as simplification findings.
### 6. Verify the changed path ### 6. Verify the changed path
Before each validation group, verify the frozen SHA plus the tracked, index, Before each validation group, verify the frozen SHA plus the tracked, index,
untracked, and ignored-file fingerprint, or recreate a pristine snapshot. On a untracked, and ignored-file fingerprint, or recreate a pristine snapshot. On a
head the user explicitly trusted for this host, run an import/version preflight, trusted head or inside the required sandbox, run an import/version preflight,
then the narrowest relevant tests and low-cost static checks. Bind every result then the narrowest relevant tests and low-cost static checks. Bind every result
to the head SHA, snapshot fingerprint, and environment fingerprint. Never run to the head SHA, snapshot fingerprint, and environment fingerprint. Never run
imports, tests, builds, hooks, or repo-configurable tooling from an untrusted imports, tests, builds, hooks, or repo-configurable tooling from an untrusted
@@ -1,7 +1,7 @@
# Model Addition Checklist # Model Addition Checklist
Read this reference when a PR adds or expands a model architecture, pipeline, Read this reference when a PR adds or expands a model architecture, pipeline,
loader, processor, registry entry, pipeline config, or deploy config. loader, processor, registry entry, or stage configuration.
Official docs: [model contribution guides](https://docs.vllm.ai/projects/vllm-omni/en/latest/contributing/model/), Official docs: [model contribution guides](https://docs.vllm.ai/projects/vllm-omni/en/latest/contributing/model/),
[adding an omni model](https://docs.vllm.ai/projects/vllm-omni/en/latest/contributing/model/adding_omni_model/), [adding an omni model](https://docs.vllm.ai/projects/vllm-omni/en/latest/contributing/model/adding_omni_model/),
@@ -22,19 +22,6 @@ Keep exact commands and report variability rather than a cherry-picked run.
Use the repository's benchmark when it covers the claim; otherwise use the Use the repository's benchmark when it covers the claim; otherwise use the
smallest reproducible workload that reaches the changed production path. smallest reproducible workload that reaches the changed production path.
## Isolation per claim
Isolate one claim per comparison. When a PR stacks several changes (a fused or
restructured load path, a distilled step schedule, a new cache), request one
column per claim at equal steps/workload instead of one blended base-vs-head
delta, and name the unchanged stage or metric that pins equivalence.
When the changed path is runtime-switchable (feature flag or environment
variable), run every column on the same head and toggle the switch rather than
diffing against a base commit that cannot separate the stacked changes:
base, head with the switch off, head with the switch on, then head with the
switch on plus each further claim.
## Evidence table ## Evidence table
| Dimension | Typical evidence | | Dimension | Typical evidence |
@@ -47,100 +34,6 @@ switch on plus each further claim.
Every optimization needs correctness or quality evidence. A faster result on a Every optimization needs correctness or quality evidence. A faster result on a
different workload, precision, seed, or topology is not a valid comparison. different workload, precision, seed, or topology is not a valid comparison.
## E2e stage-attribution table
For an end-to-end latency claim, request the stage-attribution table the
repository's perf PRs use:
- Rows carry the benchmark's own field names: total e2e, per-stage execution,
postprocess, response encoding, and a request-to-output roll-up. Add a stage
handoff/idle row when the pipeline spans stages — connector transfer and
scheduling gaps are invisible in per-stage latency deltas.
- Sanity rows pin comparability: inference steps, output frames/pixels/samples,
and generated tokens held identical across columns.
- Report P50 and P100 from the stated warmup and measured counts. One
population per number: never mix cold-start and warm samples into one value.
- Name the baseline and candidate commit SHAs, and close with an isolation
statement naming the metric that stayed unchanged ("X remained effectively
unchanged, isolating the gain to Y").
When the serving path caches, captures, or regenerates at runtime (CUDA-graph
bucket growth, cache rebuilds, recompilation), medians hide the resulting tail.
Also request the event count and the maximum observed stall.
Component-level tables pair with, not replace, the e2e table: per-op reference
vs candidate timings (with the numerical-equality result per op) plus one
complete-pipeline check under the production shapes.
## Proportionality
When a PR adds a model and explicitly labels its numbers as smoke observations
rather than claims, do not demand the attribution machinery. Ask only for
evidence protecting the model's stated operating contract — for a
realtime/duplex model that is sustained cadence over a realistic session
(enough ticks to leave warmup), with any mid-session capture/regeneration
event and its stall reported, since a cadence that collapses mid-session is a
functional failure, not a perf claim.
## Realtime/duplex world models
Request the operating-contract evidence as serving metrics, not claims:
- Sustained per-tick latency over one realistic session: median, max, and the
argmax tick.
- Stall events with their causes.
- Peak memory at first versus last tick — session-owned state must not grow
per tick.
- Control latency from a submitted action to the tick that applies it.
- Realtime-versus-offline parity under the same controls, using the
repository's video-similarity helpers. Parity plotted over tick index is the
drift-over-horizon curve and the honest measure of session lifetime;
distribution-level suites (FVD, VBench) belong to model evaluation, not the
serving integration.
Watch the contract boundaries the model documents: if a control input is
tick-API-only, the parity pair must use a horizon both paths can run.
## Speech-generation (TTS) models
The streaming contract outranks the average — a synthesis that pauses
mid-stream is broken for realtime use regardless of its averages. Request:
- The benchmark's own speech metrics over one long utterance (enough audio to
leave warmup, tens of seconds): `audio_ttfp` (time to first packet),
`audio_rtf` (real-time factor; above 1.0 cannot keep up), `e2el`, and
`ttft`/`tpot` where tokens precede audio.
- Mid-utterance stalls as the benchmark's continuity fields, not prose:
`audio_underrun` percentiles and the `audio_continuity_ok` rate
(`vllm_omni/benchmarks/metrics/metrics.py`), computed by
`vllm_omni/benchmarks/audio_continuity.py` — the companion to `audio_ttfp`
and `audio_rtf`. A synthesis that bursts chunks fast enough on average but
underruns mid-stream fails the streaming contract even when the averages
pass.
- Quality as a paired comparison against the reference implementation under
the same text, speaker prompt, and seed; report sample rate and frame
count, and assert output completeness — truncated or dropped trailing audio
is a known failure class, not a quality nuance.
- For realtime or duplex TTS: interruption (barge-in) latency and the
per-session memory trend across turns.
## Omni models
Apply the e2e stage-attribution table directly to the voice-to-voice path of
omni (speech-to-speech and multimodal AR) models:
- TTFT for text output, `audio_ttfp` for audio output, and per-stage
ITL/TPOT cadence across the perception-understanding-generation chain.
- Cross-modal parity — the omni analogue of realtime-versus-offline parity:
the same semantic request through text and audio inputs, compared on output
content or timing.
- For duplex omni sessions: turn-taking latency, interruption handling, the
concurrent session count actually validated, and a per-turn memory trend —
session-owned KV state must not grow per turn. When either turn produces
audio, continuity is judged with the same `audio_underrun` and
`audio_continuity_ok` fields as TTS, since turn-taking latency alone does
not expose mid-utterance underruns.
## Reviewer verification ladder ## Reviewer verification ladder
1. Run base/head A/B on suitable hardware when affordable. 1. Run base/head A/B on suitable hardware when affordable.
@@ -3,7 +3,7 @@
Primary design: [vLLM-Omni Configuration](https://docs.vllm.ai/projects/vllm-omni/en/latest/design/module/vllm_omni_config/). Primary design: [vLLM-Omni Configuration](https://docs.vllm.ai/projects/vllm-omni/en/latest/design/module/vllm_omni_config/).
Use for config construction, deploy YAML, registries, schema, defaults, Use for config construction, deploy YAML, registries, schema, defaults,
aliases, CLI projection, pipeline config, and topology. Inspect the reviewed aliases, CLI projection, stage config, and topology. Inspect the reviewed
page's status: while its contract remains deferred or draft, current code and page's status: while its contract remains deferred or draft, current code and
tests—not proposed precedence rules—are authoritative. tests—not proposed precedence rules—are authoritative.
@@ -121,19 +121,6 @@ Search bounded callers and sibling implementations before reporting dead code,
duplication, or a missed consumer. Keep new abstractions only when they have a duplication, or a missed consumer. Keep new abstractions only when they have a
distinct live caller, invariant, or compatibility need. distinct live caller, invariant, or compatibility need.
Verify absence and equality claims with whole-file evidence. A line window, a
grep for one symbol, or a commit message proves presence, never absence: to
assert that a file matches another version or lacks a change, diff or hash the
full files (for example `git diff <sha1>:<path> <sha2>:<path>` or the contents
API's size and SHA fields) before anchoring the finding. A verification
shortcut that would itself be wrong to run is not evidence.
For a value written or finalized in more than one place, establish which site
executes first on each entry path — and whether the earlier site makes the
later ones unreachable — before naming where a fix belongs. Enumerating all
call sites correctly still misses the bug when a path the enumeration ranked
last actually finalizes first and clears the state the proposed fix checks.
## Finding bar ## Finding bar
Anchor each finding to a changed `path:line`; name the trigger or call path, Anchor each finding to a changed `path:line`; name the trigger or call path,
@@ -39,13 +39,13 @@ isolated detached worktree. Run every source read, `rg` search, import, and test
from that snapshot, not the caller's checkout. A detached worktree provides from that snapshot, not the caller's checkout. A detached worktree provides
snapshot isolation, not a security boundary. snapshot isolation, not a security boundary.
Treat a fork head as untrusted until the user and environment policy Treat a fork head as untrusted unless the user and environment policy explicitly
explicitly establish trust. Without recorded trust, never run its imports, establish trust. Never run its imports, tests, builds, hooks, package setup, or
tests, builds, hooks, package setup, or repo-configurable linters/plugins on repo-configurable linters/plugins on the reviewer host. Execute them only in a
the reviewer host. Once the user explicitly trusts this pinned commit for disposable sandbox or VM with no credentials or inherited secrets, no host agent
this host, record the trusted SHA with the review state and execute with or service sockets, minimal read-only host mounts, disabled or explicitly
credentials, agent sockets, and other secrets out of scope. Without recorded allowlisted network, resource/time limits, and destruction after the run. If
trust, limit the review to the remote diff, SHA-addressed that boundary is unavailable, limit the review to the remote diff, SHA-addressed
reads such as `git show <head_sha>:<path>`, and existing CI evidence; report all reads such as `git show <head_sha>:<path>`, and existing CI evidence; report all
executable validation as a gap. executable validation as a gap.
@@ -111,17 +111,6 @@ Mark early findings as preliminary and continue. This is not a GitHub comment.
but do not publish review events without separate authorization. but do not publish review events without separate authorization.
- Record DCO, pre-commit, required CI, and mergeability. Pending or unknown gates - Record DCO, pre-commit, required CI, and mergeability. Pending or unknown gates
do not block source review. do not block source review.
- GitHub Actions `SKIP` omits SPDX, shellcheck, markdownlint, mypy-3.10, and
test-mark coverage. A green GHA pre-commit job does not prove those local
gates passed. New files still need the full Linting list: Omni SPDX
(`vLLM-Omni project`), no stdlib `re`/`base64` in `vllm_omni/`, no new
pickle / Hugging Face Hub API / `torch.cuda` call sites, test marks, TTS
adapter ratchet, Buildkite schema, and native shellcheck on macOS/Windows.
See [Linting](https://vllm-omni.readthedocs.io/en/latest/contributing/#linting).
- Expanding `CHECK_IMPORTS[*].allowed_files`, `ALLOWED_FILES`,
`MAX_MODEL_TYPE_BRANCHES`, or Buildkite `SKIP_FILES` is a policy change.
Do not rubber-stamp allowlist/budget growth; require justification and
prefer fixing the call site.
- Treat a failed gate as its own evidence. Do not restate its formatting/lint - Treat a failed gate as its own evidence. Do not restate its formatting/lint
output as a new code-review finding. output as a new code-review finding.
- Open CI logs only when the first failing step overlaps the frozen diff or - Open CI logs only when the first failing step overlaps the frozen diff or
@@ -48,7 +48,7 @@ compatibility changes.
| Signal | Read | Optional repo-local skill | | Signal | Read | Optional repo-local skill |
| --- | --- | --- | | --- | --- | --- |
| New or expanded model, loader, processor, registry, pipeline config, or deploy config | [model-addition-checklist.md](../checks/model-addition-checklist.md) | [`add-tts-model`](../../../add-tts-model/SKILL.md) or [`add-diffusion-model`](../../../add-diffusion-model/SKILL.md) | | New or expanded model, loader, processor, registry, or stage config | [model-addition-checklist.md](../checks/model-addition-checklist.md) | [`add-tts-model`](../../../add-tts-model/SKILL.md) or [`add-diffusion-model`](../../../add-diffusion-model/SKILL.md) |
| Latency, throughput, memory, scaling, precision, or quality claim | [perf-verification.md](../checks/perf-verification.md) | [`diffusion-perf-opt`](../../../diffusion-perf-opt/SKILL.md) for diffusion | | Latency, throughput, memory, scaling, precision, or quality claim | [perf-verification.md](../checks/perf-verification.md) | [`diffusion-perf-opt`](../../../diffusion-perf-opt/SKILL.md) for diffusion |
| Tests changed, absent for risky behavior, or test-only | [test-quality-evaluation.md](../checks/test-quality-evaluation.md) | [`vllm-omni-test`](../../../vllm-omni-test/SKILL.md) | | Tests changed, absent for risky behavior, or test-only | [test-quality-evaluation.md](../checks/test-quality-evaluation.md) | [`vllm-omni-test`](../../../vllm-omni-test/SKILL.md) |
| CI, examples, docs, public behavior, or contributor evidence | [tests-docs-checklist.md](../checks/tests-docs-checklist.md) | None | | CI, examples, docs, public behavior, or contributor evidence | [tests-docs-checklist.md](../checks/tests-docs-checklist.md) | None |
+50 -51
View File
@@ -52,8 +52,8 @@ Always attach markers deliberately:
- `tts` — speech synthesis / TTS-only models (`/v1/audio/speech`, voice clone, etc.) - `tts` — speech synthesis / TTS-only models (`/v1/audio/speech`, voice clone, etc.)
- `diffusion` — generative diffusion models (image / audio / text / video from noise) - `diffusion` — generative diffusion models (image / audio / text / video from noise)
- **Cross-cutting area** (when relevant): `parallel`, `cache`, `example`, `benchmark` - **Cross-cutting area** (when relevant): `parallel`, `cache`, `example`, `benchmark`
- **Hardware**: `cpu`, `gpu`, `cuda`, `rocm`, `npu`, …; SKU (`H100`, `L4`, …) and `cards_{n}` via `@hardware_test(...)` / `hardware_marks(...)` only — never `pytest.mark.H100` (`check-mark` rejects it) - **Hardware**: `cpu`, `gpu`, `cuda`, `rocm`, `npu`, `L4`, `H100`, `distributed_cuda`, …
- Optional: `slow`. Multi-card: `num_cards=N` on the helper (filter with `cards_N` / `not cards_1`) - Optional: `slow`, distributed markers when multi-card is required
**Baseline smoke (L2 + L3):** The simplest e2e case per model — default deploy, minimal request — should usually carry **both** `@pytest.mark.core_model` and `@pytest.mark.advanced_model` on the **same** test function so `test-ready.yml` and `test-merge.yml` share one test. `send_*_request` picks validation depth from `--run-level`. References: `test_voxcpm2_tts.py::test_text_to_audio_001`, `test_qwen3_tts_customvoice.py::test_text_to_audio_001`. Heavier scenarios use **`advanced_model` only**; L4 expansion uses **`full_model`**. **Baseline smoke (L2 + L3):** The simplest e2e case per model — default deploy, minimal request — should usually carry **both** `@pytest.mark.core_model` and `@pytest.mark.advanced_model` on the **same** test function so `test-ready.yml` and `test-merge.yml` share one test. `send_*_request` picks validation depth from `--run-level`. References: `test_voxcpm2_tts.py::test_text_to_audio_001`, `test_qwen3_tts_customvoice.py::test_text_to_audio_001`. Heavier scenarios use **`advanced_model` only**; L4 expansion uses **`full_model`**.
@@ -94,8 +94,8 @@ Existing references: `tests/e2e/offline_inference/test_qwen2_5_omni.py` (L2-styl
| Scenario | Typical location | Fixtures / runner pattern | Baseline markers & level | | Scenario | Typical location | Fixtures / runner pattern | Baseline markers & level |
|----------|------------------|---------------------------|---------------------------| |----------|------------------|---------------------------|---------------------------|
| **Offline inference e2e** | `tests/e2e/offline_inference/` | **Module (default):** `omni_runner` + `offline_client`. **Function (isolation only):** `omni_runner_function` + `offline_client_function`. Diffusion/TTS may use `Omni(...).generate` directly | L2: `core_model` + **one of** `omni` / `tts` / `diffusion`; `@hardware_test(...)` when GPU/NPU is required | | **Offline inference e2e** | `tests/e2e/offline_inference/` | **Module (default):** `omni_runner` + `omni_runner_handler`. **Function (isolation only):** `omni_runner_function` + `omni_runner_handler_function`. Diffusion/TTS may use `Omni(...).generate` directly | L2: `core_model` + **one of** `omni` / `tts` / `diffusion`; `@hardware_test(...)` when GPU/NPU is required |
| **Online serving e2e** | `tests/e2e/online_serving/` | **Module (default):** `omni_server` + `online_client`. **Function (isolation only):** `omni_server_function` + `online_client_function`. Clients: `send_omni_request` (omni), `send_audio_speech_request` (tts), `send_diffusion_request` / `send_video_diffusion_request` / `send_images_generations_request` (diffusion) | Baseline smoke: **`core_model` + `advanced_model`**; heavier paths: `advanced_model` only; L4 expansion: `full_model` | | **Online serving e2e** | `tests/e2e/online_serving/` | **Module (default):** `omni_server` + `openai_client`. **Function (isolation only):** `omni_server_function` + `openai_client_function`. Clients: `send_omni_request` (omni), `send_audio_speech_request` (tts), `send_diffusion_request` / `send_video_diffusion_request` / `send_images_generations_request` (diffusion) | Baseline smoke: **`core_model` + `advanced_model`**; heavier paths: `advanced_model` only; L4 expansion: `full_model` |
| **Documentation / runnable examples** | `tests/examples/offline_inference/`, `tests/examples/online_serving/` | **Offline docs (preferred):** extract Python/Bash blocks from the doc README (e.g. `ReadmeSnippet.extract_readme_snippets`), `pytest.mark.parametrize` each snippet, run via `example_runner.run` with a stable `output_subfolder`. **Online docs:** copy client/request scripts into dedicated tests and keep them in sync with the doc page. | Usually **L4**: `advanced_model`, often `example` plus hardware marks matching the nightly docs-example job (see `.buildkite/cuda/test-nightly.yml`). Full conventions: [docs/contributing/ci/test_examples/l4_doc_example_tests.inc.md](../../../docs/contributing/ci/test_examples/l4_doc_example_tests.inc.md) (introduced in [PR #1910](https://github.com/vllm-project/vllm-omni/pull/1910): naming, output directory layout, skip rules, avoid trimming `num_inference_steps` without a strong CI reason). | | **Documentation / runnable examples** | `tests/examples/offline_inference/`, `tests/examples/online_serving/` | **Offline docs (preferred):** extract Python/Bash blocks from the doc README (e.g. `ReadmeSnippet.extract_readme_snippets`), `pytest.mark.parametrize` each snippet, run via `example_runner.run` with a stable `output_subfolder`. **Online docs:** copy client/request scripts into dedicated tests and keep them in sync with the doc page. | Usually **L4**: `advanced_model`, often `example` plus hardware marks matching the nightly docs-example job (see `.buildkite/cuda/test-nightly.yml`). Full conventions: [docs/contributing/ci/test_examples/l4_doc_example_tests.inc.md](../../../docs/contributing/ci/test_examples/l4_doc_example_tests.inc.md) (introduced in [PR #1910](https://github.com/vllm-project/vllm-omni/pull/1910): naming, output directory layout, skip rules, avoid trimming `num_inference_steps` without a strong CI reason). |
| **Performance / benchmark** | `tests/dfx/perf/tests/*.json` + `run_*_benchmark.py` | JSON or script-driven server + load config; assert explicit metrics / baselines | L4 Perf: JSON `mark` with `full_model` + `omni`/`tts`/`diffusion`; wire `test-nightly.yml` Perf steps | | **Performance / benchmark** | `tests/dfx/perf/tests/*.json` + `run_*_benchmark.py` | JSON or script-driven server + load config; assert explicit metrics / baselines | L4 Perf: JSON `mark` with `full_model` + `omni`/`tts`/`diffusion`; wire `test-nightly.yml` Perf steps |
| **Invalid parameter / negative HTTP validation** | `tests/dfx/reliability/invalid_param_test/` | Live `omni_server` + low-level `send_*_http_request` with `err_code` / `err_message` | `pytest.mark.slow` + `omni` / `tts` / `diffusion` + `@hardware_marks` (`H100` or `L4`); CI in **`test-weekly.yml`** (not ready/merge/nightly) | | **Invalid parameter / negative HTTP validation** | `tests/dfx/reliability/invalid_param_test/` | Live `omni_server` + low-level `send_*_http_request` with `err_code` / `err_message` | `pytest.mark.slow` + `omni` / `tts` / `diffusion` + `@hardware_marks` (`H100` or `L4`); CI in **`test-weekly.yml`** (not ready/merge/nightly) |
@@ -110,10 +110,10 @@ After choosing offline/online/docs/perf, classify the **product under test** and
|-----------|------------------------------|----------------------------|----------------------------------------| |-----------|------------------------------|----------------------------|----------------------------------------|
| **What it is** | Multimodal LLM pipeline (thinker/talker/stages; text + vision + audio I/O) | Speech synthesis / voice models | Generative diffusion (noise → image, audio, text, or video) | | **What it is** | Multimodal LLM pipeline (thinker/talker/stages; text + vision + audio I/O) | Speech synthesis / voice models | Generative diffusion (noise → image, audio, text, or video) |
| **Examples** | Qwen2.5-Omni, Qwen3-Omni | Qwen3-TTS, VoxCPM2, Higgs-Audio, Step-Audio2 | Qwen-Image, BAGEL, Wan2.2, HunyuanVideo | | **Examples** | Qwen2.5-Omni, Qwen3-Omni | Qwen3-TTS, VoxCPM2, Higgs-Audio, Step-Audio2 | Qwen-Image, BAGEL, Wan2.2, HunyuanVideo |
| **Offline runner** | `omni_runner` + `offline_client`, `generate_multimodal` | `Omni(...).generate` with deploy YAML + TTS params | `Omni(...).generate` + `OmniDiffusionSamplingParams` | | **Offline runner** | `omni_runner` + `omni_runner_handler`, `generate_multimodal` | `Omni(...).generate` or stage YAML + TTS params | `Omni(...).generate` + `OmniDiffusionSamplingParams` |
| **Online client** | `online_client.send_omni_request` (chat completions, modalities) | `online_client.send_audio_speech_request` (`/v1/audio/speech`) | `send_diffusion_request` (chat/T2I), `send_video_diffusion_request` (`/v1/videos`, X2V), or `send_images_generations_http_request` / `send_images_edits_http_request` (DALL-E routes) | | **Online client** | `openai_client.send_omni_request` (chat completions, modalities) | `openai_client.send_audio_speech_request` (`/v1/audio/speech`) | `send_diffusion_request` (chat/T2I), `send_video_diffusion_request` (`/v1/videos`, X2V), or `send_images_generations_http_request` / `send_images_edits_http_request` (DALL-E routes) |
| **Typical assertions** | Stage outputs, text/audio keywords via `OfflineOmniClient` / response handler | WAV bytes, stream chunks, speech endpoint contract | Image/video dimensions, `final_output_type`, `assert_diffusion_response` | | **Typical assertions** | Stage outputs, text/audio keywords via `OmniRunnerHandler` / response handler | WAV bytes, stream chunks, speech endpoint contract | Image/video dimensions, `final_output_type`, `assert_diffusion_response` |
| **Deploy YAML** | Per-model deploy configs (`ci/qwen3_omni_moe.yaml`, …) | `qwen3_tts.yaml`, `voxcpm2.yaml`, … | Often default serve; parallel/offload YAML for heavy DiT | | **Stage / deploy YAML** | Per-model omni stage configs (`ci/qwen3_omni_moe.yaml`, …) | `qwen3_tts.yaml`, `voxcpm2.yaml`, … | Often default serve; parallel/offload YAML for heavy DiT |
| **Nightly group** (`test-nightly.yml`) | **Omni Model Test** — `-m "full_model and omni"` | **TTS Model Test** — `-m "full_model and tts"` | **Diffusion X2I(&A&T)** *or* **Diffusion X2V** (see Step 3 table; same `diffusion` marker, different YAML group / file shard) | | **Nightly group** (`test-nightly.yml`) | **Omni Model Test** — `-m "full_model and omni"` | **TTS Model Test** — `-m "full_model and tts"` | **Diffusion X2I(&A&T)** *or* **Diffusion X2V** (see Step 3 table; same `diffusion` marker, different YAML group / file shard) |
| **L4 pressure** | Expansion per modality/model as needed | Expansion + accuracy/perf in TTS group | X2I: merge feature combos per [#1832](https://github.com/vllm-project/vllm-omni/issues/1832); X2V: separate nightly group | | **L4 pressure** | Expansion per modality/model as needed | Expansion + accuracy/perf in TTS group | X2I: merge feature combos per [#1832](https://github.com/vllm-project/vllm-omni/issues/1832); X2V: separate nightly group |
@@ -175,9 +175,9 @@ See **L1 unit test constraints (mocking)** below for the full do/don't list.
@pytest.mark.omni @pytest.mark.omni
@hardware_test(...) @hardware_test(...)
@pytest.mark.parametrize("omni_runner", test_params, indirect=True) @pytest.mark.parametrize("omni_runner", test_params, indirect=True)
def test_<scenario>(omni_runner, offline_client) -> None: def test_<scenario>(omni_runner, omni_runner_handler) -> None:
request_config = {"prompts": ..., "modalities": [...]} # optional: images, videos, audios request_config = {"prompts": ..., "modalities": [...]} # optional: images, videos, audios
offline_client.send_omni_request(request_config) omni_runner_handler.send_omni_request(request_config)
``` ```
*Offline generative e2e — **Diffusion** (representative):* *Offline generative e2e — **Diffusion** (representative):*
@@ -187,8 +187,8 @@ def test_<scenario>(omni_runner, offline_client) -> None:
@pytest.mark.diffusion @pytest.mark.diffusion
@hardware_test(...) @hardware_test(...)
@pytest.mark.parametrize("omni_runner", test_params, indirect=True) @pytest.mark.parametrize("omni_runner", test_params, indirect=True)
def test_text_to_image_001(offline_client) -> None: def test_text_to_image_001(omni_runner_handler) -> None:
offline_client.send_diffusion_request({"prompt": "...", "extra_body": {"num_inference_steps": 4, ...}}) omni_runner_handler.send_diffusion_request({"prompt": "...", "extra_body": {"num_inference_steps": 4, ...}})
``` ```
*Offline TTS e2e — **Qwen3-TTS** (two-stage; representative):* *Offline TTS e2e — **Qwen3-TTS** (two-stage; representative):*
@@ -198,8 +198,8 @@ def test_text_to_image_001(offline_client) -> None:
@pytest.mark.tts @pytest.mark.tts
@hardware_test(...) @hardware_test(...)
@pytest.mark.parametrize("omni_runner", tts_server_params, indirect=True) @pytest.mark.parametrize("omni_runner", tts_server_params, indirect=True)
def test_text_to_audio_001(omni_runner, offline_client) -> None: def test_text_to_audio_001(omni_runner, omni_runner_handler) -> None:
offline_client.send_audio_speech_request({ omni_runner_handler.send_audio_speech_request({
"input": "...", "input": "...",
"task_type": "Base", "task_type": "Base",
"ref_audio": REF_AUDIO_URL, "ref_audio": REF_AUDIO_URL,
@@ -214,8 +214,8 @@ def test_text_to_audio_001(omni_runner, offline_client) -> None:
@pytest.mark.tts @pytest.mark.tts
@hardware_test(...) @hardware_test(...)
@pytest.mark.parametrize("omni_runner", tts_server_params, indirect=True) @pytest.mark.parametrize("omni_runner", tts_server_params, indirect=True)
def test_voice_clone_001(offline_client) -> None: def test_voice_clone_001(omni_runner_handler) -> None:
offline_client.send_single_stage_tts_request({ omni_runner_handler.send_single_stage_tts_request({
"input": "...", "input": "...",
"language": "en", "language": "en",
"prompt_audio_path": REF_AUDIO_PATH, "prompt_audio_path": REF_AUDIO_PATH,
@@ -231,9 +231,9 @@ def test_voice_clone_001(offline_client) -> None:
@pytest.mark.omni @pytest.mark.omni
@hardware_test(...) @hardware_test(...)
@pytest.mark.parametrize("omni_server", test_params, indirect=True) @pytest.mark.parametrize("omni_server", test_params, indirect=True)
def test_text_to_text_001(omni_server, online_client) -> None: def test_text_to_text_001(omni_server, openai_client) -> None:
request_config = {"model": omni_server.model, "messages": ..., "modalities": ["text"]} request_config = {"model": omni_server.model, "messages": ..., "modalities": ["text"]}
online_client.send_omni_request(request_config) openai_client.send_omni_request(request_config)
``` ```
*Online serving e2e — **TTS** (representative):* *Online serving e2e — **TTS** (representative):*
@@ -244,9 +244,9 @@ def test_text_to_text_001(omni_server, online_client) -> None:
@pytest.mark.tts @pytest.mark.tts
@hardware_test(...) @hardware_test(...)
@pytest.mark.parametrize("omni_server", tts_server_params, indirect=True) @pytest.mark.parametrize("omni_server", tts_server_params, indirect=True)
def test_text_to_audio_001(omni_server, online_client) -> None: def test_text_to_audio_001(omni_server, openai_client) -> None:
request_config = {"model": omni_server.model, "input": "...", "response_format": "wav", ...} request_config = {"model": omni_server.model, "input": "...", "response_format": "wav", ...}
online_client.send_audio_speech_request(request_config) openai_client.send_audio_speech_request(request_config)
``` ```
*Online serving e2e — **Diffusion** X2I (representative):* *Online serving e2e — **Diffusion** X2I (representative):*
@@ -255,8 +255,8 @@ def test_text_to_audio_001(omni_server, online_client) -> None:
@pytest.mark.core_model @pytest.mark.core_model
@pytest.mark.diffusion @pytest.mark.diffusion
@pytest.mark.parametrize("omni_server", _get_default_case(MODEL), indirect=True) @pytest.mark.parametrize("omni_server", _get_default_case(MODEL), indirect=True)
def test_text_to_image_001(omni_server, online_client) -> None: def test_text_to_image_001(omni_server, openai_client) -> None:
online_client.send_diffusion_request({...}) # chat completions + extra_body openai_client.send_diffusion_request({...}) # chat completions + extra_body
``` ```
*Online serving e2e — **Diffusion** X2V (representative):* *Online serving e2e — **Diffusion** X2V (representative):*
@@ -264,8 +264,8 @@ def test_text_to_image_001(omni_server, online_client) -> None:
```python ```python
@pytest.mark.core_model @pytest.mark.core_model
@pytest.mark.diffusion @pytest.mark.diffusion
def test_text_to_video_001(omni_server, online_client) -> None: def test_text_to_video_001(omni_server, openai_client) -> None:
online_client.send_video_diffusion_request({"model": ..., "form_data": {...}}) # /v1/videos openai_client.send_video_diffusion_request({"model": ..., "form_data": {...}}) # /v1/videos
``` ```
*Documentation example tests:* follow the **Preferred Test Strategy** in [l4_doc_example_tests.inc.md](../../../docs/contributing/ci/test_examples/l4_doc_example_tests.inc.md): dynamic extraction for offline READMEs; explicit copied client code for online pages until extraction is justified; use the documented **naming**, **output directory** (page folder + case id), and **skipping** rules (e.g. Gradio-only scripts). *Documentation example tests:* follow the **Preferred Test Strategy** in [l4_doc_example_tests.inc.md](../../../docs/contributing/ci/test_examples/l4_doc_example_tests.inc.md): dynamic extraction for offline READMEs; explicit copied client code for online pages until extraction is justified; use the documented **naming**, **output directory** (page folder + case id), and **skipping** rules (e.g. Gradio-only scripts).
@@ -274,7 +274,6 @@ def test_text_to_video_001(omni_server, online_client) -> None:
**3. Cross-cutting rules** **3. Cross-cutting rules**
- New `.py` files need the Omni SPDX header (`Copyright contributors to the vLLM-Omni project`, not `vLLM project`). `tests/` may keep stdlib `re`/`base64`; pickle is still banned unless the file is already on the pickle allowlist. Every collected `tests/**/test_*.py` needs a CI **level** mark and a **hardware** platform mark/helper (`check-mark` runs locally; GHA skips it). Do not write `pytest.mark.H100` / `pytest.mark.L4`; use the helpers so `cards_{n}` is attached. Do not grow `allowed_files` to land a test. Policy: [docs/contributing/README.md](../../../docs/contributing/README.md#linting).
- Reuse existing fixtures for the chosen scenario; do not mix “online client” assumptions into offline `OmniRunner` tests without a clear reason. - Reuse existing fixtures for the chosen scenario; do not mix “online client” assumptions into offline `OmniRunner` tests without a clear reason.
- Avoid external network dependency in assertions unless the scenario is explicitly “online serving” or doc examples that require a model hub (then align with CI secrets/cache). - Avoid external network dependency in assertions unless the scenario is explicitly “online serving” or doc examples that require a model hub (then align with CI secrets/cache).
- Keep **one test function = one intent** (one modality combo, one endpoint contract, or one acceleration combo). - Keep **one test function = one intent** (one modality combo, one endpoint contract, or one acceleration combo).
@@ -324,21 +323,21 @@ vLLM-Omni e2e tests start a real **OmniServer** (online) or **OmniRunner** (offl
| Scope | Online fixtures | Offline fixtures | When to use | | Scope | Online fixtures | Offline fixtures | When to use |
|-------|-----------------|------------------|-------------| |-------|-----------------|------------------|-------------|
| **Module** (default) | `omni_server` → `online_client` | `omni_runner` → `offline_client` | **Default for L2/L3/L4 expansion** — amortize model/server init across `test_*` in the same module. Same `OmniServerParams` / runner config can be reused by multiple tests. | | **Module** (default) | `omni_server` → `openai_client` | `omni_runner` → `omni_runner_handler` | **Default for L2/L3/L4 expansion** — amortize model/server init across `test_*` in the same module. Same `OmniServerParams` / runner config can be reused by multiple tests. |
| **Function** | `omni_server_function` → `online_client_function` | `omni_runner_function` → `offline_client_function` | **Only when required** — each `test_*` must get a **clean** server/runner (no shared engine/GPU state). Typical: `tests/dfx/reliability/`, sleep/wakeup, crash/restart, tests that mutate global server state. | | **Function** | `omni_server_function` → `openai_client_function` | `omni_runner_function` → `omni_runner_handler_function` | **Only when required** — each `test_*` must get a **clean** server/runner (no shared engine/GPU state). Typical: `tests/dfx/reliability/`, sleep/wakeup, crash/restart, tests that mutate global server state. |
**Rules:** **Rules:**
1. **Default to module scope** (`omni_server` / `omni_runner`) unless the scenario **explicitly** needs a fresh instance per test function. 1. **Default to module scope** (`omni_server` / `omni_runner`) unless the scenario **explicitly** needs a fresh instance per test function.
2. **Indirect parametrize name must match the fixture name**: `@pytest.mark.parametrize("omni_server", ...)` with `omni_server` + `online_client`; `@pytest.mark.parametrize("omni_server_function", ...)` with `omni_server_function` + `online_client_function`. Do not mix module fixture with function client (or vice versa). 2. **Indirect parametrize name must match the fixture name**: `@pytest.mark.parametrize("omni_server", ...)` with `omni_server` + `openai_client`; `@pytest.mark.parametrize("omni_server_function", ...)` with `omni_server_function` + `openai_client_function`. Do not mix module fixture with function client (or vice versa).
3. Different `OmniServerParams` per `test_*` (e.g. default vs `--enable-cpu-offload`) is still OK with **module** `omni_server` — pytest parametrizes per test node; only switch to `_function` when isolation between tests matters, not merely because `server_args` differ. 3. Different `OmniServerParams` per `test_*` (e.g. default vs `--enable-cpu-offload`) is still OK with **module** `omni_server` — pytest parametrizes per test node; only switch to `_function` when isolation between tests matters, not merely because `server_args` differ.
4. One `test_*` with **many** server configs (expansion matrix) → single function + `@pytest.mark.parametrize("omni_server", [...], indirect=True)` + module `omni_server` (see in-tree `test_qwen_image_expansion.py`). 4. One `test_*` with **many** server configs (expansion matrix) → single function + `@pytest.mark.parametrize("omni_server", [...], indirect=True)` + module `omni_server` (see in-tree `test_qwen_image_expansion.py`).
```python ```python
# Default — module-scoped server (L4 expansion) # Default — module-scoped server (L4 expansion)
@pytest.mark.parametrize("omni_server", [pytest.param(OmniServerParams(model=MODEL), marks=H100)], indirect=True) @pytest.mark.parametrize("omni_server", [pytest.param(OmniServerParams(model=MODEL), marks=H100)], indirect=True)
def test_foo_images_generations_default_1024(omni_server, online_client) -> None: def test_foo_images_generations_default_1024(omni_server, openai_client) -> None:
online_client.send_images_generations_request({...}) openai_client.send_images_generations_request({...})
# Function-scoped — reliability / per-test clean state only # Function-scoped — reliability / per-test clean state only
@pytest.mark.parametrize( @pytest.mark.parametrize(
@@ -346,9 +345,9 @@ def test_foo_images_generations_default_1024(omni_server, online_client) -> None
[pytest.param(OmniServerParams(model=MODEL), marks=H100)], [pytest.param(OmniServerParams(model=MODEL), marks=H100)],
indirect=True, indirect=True,
) )
def test_foo_sleep_wakeup_cycle(omni_server_function, online_client_function) -> None: def test_foo_sleep_wakeup_cycle(omni_server_function, openai_client_function) -> None:
online_client_function.send_omni_sleep_http_request({...}) openai_client_function.send_omni_sleep_http_request({...})
online_client_function.send_omni_wakeup_http_request({...}) openai_client_function.send_omni_wakeup_http_request({...})
``` ```
#### Runtime send helpers — online **and** offline (`tests/helpers/runtime.py`) #### Runtime send helpers — online **and** offline (`tests/helpers/runtime.py`)
@@ -357,19 +356,19 @@ def test_foo_sleep_wakeup_cycle(omni_server_function, online_client_function) ->
| Principle | Action | | Principle | Action |
|-----------|--------| |-----------|--------|
| **Reuse first** | Grep `runtime.py` for an existing **`send_*_request`** on `OnlineOmniClient` (online) or `OfflineOmniClient` (offline) that matches the endpoint / pipeline shape. | | **Reuse first** | Grep `runtime.py` for an existing **`send_*_request`** on `OpenAIClientHandler` (online) or `OmniRunnerHandler` (offline) that matches the endpoint / pipeline shape. |
| **Extend when close** | If an existing `send_*_request` almost fits (e.g. missing one optional field), extend it in `runtime.py` — do not fork logic in the test file. | | **Extend when close** | If an existing `send_*_request` almost fits (e.g. missing one optional field), extend it in `runtime.py` — do not fork logic in the test file. |
| **Add when missing** | No suitable helper → add **`send_<feature>_request`** (high-level: call + general `assert_*`) or **`send_<route>_<verb>_http_request`** (low-level HTTP for negative/dfx) in **`runtime.py` first**, then call it from tests. | | **Add when missing** | No suitable helper → add **`send_<feature>_request`** (high-level: call + general `assert_*`) or **`send_<route>_<verb>_http_request`** (low-level HTTP for negative/dfx) in **`runtime.py` first**, then call it from tests. |
| **Test module owns payload only** | In `test_*.py`: `MODEL`, deploy path, vendored media, `get_prompt()`, and inline **`request_config` dicts** only. **No** `omni.generate(...)`, raw `requests.post`, OpenAI SDK calls, or `_collect_audio()` / `_process_output()` in e2e tests. | | **Test module owns payload only** | In `test_*.py`: `MODEL`, deploy path, vendored media, `get_prompt()`, and inline **`request_config` dicts** only. **No** `omni.generate(...)`, raw `requests.post`, OpenAI SDK calls, or `_collect_audio()` / `_process_output()` in e2e tests. |
**Online** (`online_client` from `omni_server`): `OnlineOmniClient.send_*_request`. **Online** (`openai_client` from `omni_server`): `OpenAIClientHandler.send_*_request`.
**Offline** (`offline_client` from `omni_runner`): `OfflineOmniClient.send_*_request`. **Offline** (`omni_runner_handler` from `omni_runner`): `OmniRunnerHandler.send_*_request`.
| Do | Don't | | Do | Don't |
|----|-------| |----|-------|
| Online: `online_client.send_omni_request`, `send_diffusion_request`, `send_audio_speech_request`, `send_video_diffusion_request`, `send_images_generations_request`, … | `requests.post(f"{base_url}/v1/...", json=…)` or `client.chat.completions.create(...)` inside a test | | Online: `openai_client.send_omni_request`, `send_diffusion_request`, `send_audio_speech_request`, `send_video_diffusion_request`, `send_images_generations_request`, … | `requests.post(f"{base_url}/v1/...", json=…)` or `client.chat.completions.create(...)` inside a test |
| Offline: `offline_client.send_omni_request`, `send_diffusion_request`, `send_audio_speech_request`, `send_single_stage_tts_request`, `send_single_stage_tts_batch_request`, … | `omni_runner.omni.generate(...)` + hand-rolled tensor/WAV extraction in `test_*.py` | | Offline: `omni_runner_handler.send_omni_request`, `send_diffusion_request`, `send_audio_speech_request`, `send_single_stage_tts_request`, `send_single_stage_tts_batch_request`, … | `omni_runner.omni.generate(...)` + hand-rolled tensor/WAV extraction in `test_*.py` |
| Add missing `send_*` to **`runtime.py`** first; bundle general `assert_*` inside the send helper | A one-off `def _post_*` / `def _collect_audio` at the bottom of a test module | | Add missing `send_*` to **`runtime.py`** first; bundle general `assert_*` inside the send helper | A one-off `def _post_*` / `def _collect_audio` at the bottom of a test module |
| Mirror naming/style of neighboring `send_*` (docstring, `request_config` dict, optional `run_level`, `err_code` / `err_message` for negative cases) | Different parameter shapes per test file for the same endpoint | | Mirror naming/style of neighboring `send_*` (docstring, `request_config` dict, optional `run_level`, `err_code` / `err_message` for negative cases) | Different parameter shapes per test file for the same endpoint |
@@ -392,7 +391,7 @@ def test_foo_sleep_wakeup_cycle(omni_server_function, online_client_function) ->
Changing `request_config` fields (`size`, `n`, `seed`, server flags) is **not** special validation — the general `assert_*` should read those from `request_config` inside `send_*`. Changing `request_config` fields (`size`, `n`, `seed`, server flags) is **not** special validation — the general `assert_*` should read those from `request_config` inside `send_*`.
**`send_*` ↔ `assert_*` pairing (online `OnlineOmniClient`):** **`send_*` ↔ `assert_*` pairing (online `OpenAIClientHandler`):**
| High-level `send_*` (prefer in L2+ e2e) | Assert already invoked inside `runtime.py` | Low-level HTTP-only sibling | | High-level `send_*` (prefer in L2+ e2e) | Assert already invoked inside `runtime.py` | Low-level HTTP-only sibling |
|----------------------------------------|--------------------------------------------|-----------------------------| |----------------------------------------|--------------------------------------------|-----------------------------|
@@ -405,7 +404,7 @@ Changing `request_config` fields (`size`, `n`, `seed`, server flags) is **not**
**Rule:** Tests call high-level `send_*_request` for the general contract. **Never** call the same bundled `assert_*` again. Call an extra `assert_*` in the test **only** for special, case-specific validation. **Rule:** Tests call high-level `send_*_request` for the general contract. **Never** call the same bundled `assert_*` again. Call an extra `assert_*` in the test **only** for special, case-specific validation.
**Common `OnlineOmniClient` entry points** (non-exhaustive — grep `runtime.py` before adding): **Common `OpenAIClientHandler` entry points** (non-exhaustive — grep `runtime.py` before adding):
| Area | High-level (SDK + assert) | Low-level HTTP (`*_http_request`) | | Area | High-level (SDK + assert) | Low-level HTTP (`*_http_request`) |
|------|---------------------------|-----------------------------------| |------|---------------------------|-----------------------------------|
@@ -416,7 +415,7 @@ Changing `request_config` fields (`size`, `n`, `seed`, server flags) is **not**
| TTS (online) | `send_audio_speech_request` | `send_audio_speech_http_request`, `send_audio_generate_http_request`, … | | TTS (online) | `send_audio_speech_request` | `send_audio_speech_http_request`, `send_audio_generate_http_request`, … |
| Ops / meta | — | `send_health_http_request`, `send_models_http_request`, `send_omni_sleep_http_request`, … | | Ops / meta | — | `send_health_http_request`, `send_models_http_request`, `send_omni_sleep_http_request`, … |
**Common `OfflineOmniClient` entry points** (offline — grep `runtime.py` before adding): **Common `OmniRunnerHandler` entry points** (offline — grep `runtime.py` before adding):
| Area | High-level (`send_*_request` + assert) | Notes | | Area | High-level (`send_*_request` + assert) | Notes |
|------|----------------------------------------|-------| |------|----------------------------------------|-------|
@@ -426,7 +425,7 @@ Changing `request_config` fields (`size`, `n`, `seed`, server flags) is **not**
| Single-stage TTS (Coqui XTTS, MOSS-TTS-Nano, …) | `send_single_stage_tts_request`, `send_single_stage_tts_batch_request` | `prompt` + `additional_information` + `omni.generate` — **do not** duplicate in tests | | Single-stage TTS (Coqui XTTS, MOSS-TTS-Nano, …) | `send_single_stage_tts_request`, `send_single_stage_tts_batch_request` | `prompt` + `additional_information` + `omni.generate` — **do not** duplicate in tests |
| New model family | **Add** `send_<family>_request` here first | Then call from `tests/e2e/offline_inference/test_*.py` | | New model family | **Add** `send_<family>_request` here first | Then call from `tests/e2e/offline_inference/test_*.py` |
L1 tests under `tests/entrypoints/` may use **FastAPI `TestClient`** or direct handler calls with mocks; they do **not** need `OnlineOmniClient` / `OfflineOmniClient`. **L2+ online and offline e2e** must use `runtime.py` `send_*` helpers. L1 tests under `tests/entrypoints/` may use **FastAPI `TestClient`** or direct handler calls with mocks; they do **not** need `OpenAIClientHandler` / `OmniRunnerHandler`. **L2+ online and offline e2e** must use `runtime.py` `send_*` helpers.
#### Invalid parameter validation (`tests/dfx/reliability/invalid_param_test/`) #### Invalid parameter validation (`tests/dfx/reliability/invalid_param_test/`)
@@ -452,7 +451,7 @@ When the user asks for **invalid parameter validation**, **invalid request bodie
| **Module markers** | `pytestmark = [pytest.mark.slow, pytest.mark.<omni or tts or diffusion>]` | | **Module markers** | `pytestmark = [pytest.mark.slow, pytest.mark.<omni or tts or diffusion>]` |
| **Server fixture** | `_PARAMS` / `_QWEN3_TTS_SPEECH`-style list of `pytest.param(OmniServerParams(...), id="...", marks=hardware_marks(...))`; `@pytest.mark.parametrize("omni_server", _PARAMS, indirect=True)` | | **Server fixture** | `_PARAMS` / `_QWEN3_TTS_SPEECH`-style list of `pytest.param(OmniServerParams(...), id="...", marks=hardware_marks(...))`; `@pytest.mark.parametrize("omni_server", _PARAMS, indirect=True)` |
| **Hardware** | `hardware_marks(res={"cuda": "H100"})` for heavy diffusion/omni/video; `hardware_marks(res={"cuda": "L4"})` for smaller TTS models (must match weekly `-m "slow and L4"` step) | | **Hardware** | `hardware_marks(res={"cuda": "H100"})` for heavy diffusion/omni/video; `hardware_marks(res={"cuda": "L4"})` for smaller TTS models (must match weekly `-m "slow and L4"` step) |
| **HTTP client** | **Low-level** `online_client.send_*_http_request({..., "err_code": 400, "err_message": (...)} )` — **not** `send_*_request` (success path) | | **HTTP client** | **Low-level** `openai_client.send_*_http_request({..., "err_code": 400, "err_message": (...)} )` — **not** `send_*_request` (success path) |
| **Case shape** | Prefer **one `test_*` per route family** + `@pytest.mark.parametrize("body_spec, err_message", [...])` with stable `id=` per case; or dedicated `test_<route>_malformed_json` when not parametrized | | **Case shape** | Prefer **one `test_*` per route family** + `@pytest.mark.parametrize("body_spec, err_message", [...])` with stable `id=` per case; or dedicated `test_<route>_malformed_json` when not parametrized |
| **Body helpers** | `_minimal_<endpoint>_json(omni_server)` / `_minimal_*_form_data()`; merge overrides with `body.update(body_spec)` | | **Body helpers** | `_minimal_<endpoint>_json(omni_server)` / `_minimal_*_form_data()`; merge overrides with `body.update(body_spec)` |
| **Known gaps** | `pytest.mark.skip(reason="…#3649")` as `_SKIP_ISSUE_3649` when server validation is not yet strict (mirror neighboring cases) | | **Known gaps** | `pytest.mark.skip(reason="…#3649")` as `_SKIP_ISSUE_3649` when server validation is not yet strict (mirror neighboring cases) |
@@ -485,13 +484,13 @@ When the user asks for **invalid parameter validation**, **invalid request bodie
@pytest.mark.parametrize("omni_server", _PARAMS, indirect=True) @pytest.mark.parametrize("omni_server", _PARAMS, indirect=True)
def test_images_generations_invalid_requests( def test_images_generations_invalid_requests(
omni_server: OmniServer, omni_server: OmniServer,
online_client: OnlineOmniClient, openai_client: OpenAIClientHandler,
body_spec: dict[str, object], body_spec: dict[str, object],
err_message: str | tuple[str, ...], err_message: str | tuple[str, ...],
) -> None: ) -> None:
body = _minimal_images_gen_json(omni_server) body = _minimal_images_gen_json(omni_server)
body.update(body_spec) body.update(body_spec)
online_client.send_images_generations_http_request( openai_client.send_images_generations_http_request(
{"json": body, "timeout": 300, "err_code": 400, "err_message": err_message} {"json": body, "timeout": 300, "err_code": 400, "err_message": err_message}
) )
``` ```
@@ -542,18 +541,18 @@ def send_images_generations_request(self, request_config: dict[str, Any], ...) -
# tests/e2e/.../test_foo_expansion.py — one case per test_* (no case_id branching) # tests/e2e/.../test_foo_expansion.py — one case per test_* (no case_id branching)
@pytest.mark.parametrize("omni_server", [pytest.param(OmniServerParams(model=MODEL), marks=SINGLE_L4)], indirect=True) @pytest.mark.parametrize("omni_server", [pytest.param(OmniServerParams(model=MODEL), marks=SINGLE_L4)], indirect=True)
def test_foo_images_generations_default_1024(omni_server, online_client) -> None: def test_foo_images_generations_default_1024(omni_server, openai_client) -> None:
online_client.send_images_generations_request({"json": body, "timeout": 300}) openai_client.send_images_generations_request({"json": body, "timeout": 300})
@pytest.mark.parametrize("omni_server", [pytest.param(OmniServerParams(model=MODEL), marks=SINGLE_L4)], indirect=True) @pytest.mark.parametrize("omni_server", [pytest.param(OmniServerParams(model=MODEL), marks=SINGLE_L4)], indirect=True)
def test_foo_images_generations_sizes_256_512_1024(omni_server, online_client) -> None: def test_foo_images_generations_sizes_256_512_1024(omni_server, openai_client) -> None:
for size in ("256x256", "512x512", "1024x1024"): for size in ("256x256", "512x512", "1024x1024"):
online_client.send_images_generations_request({"json": {**body, "size": size}, "timeout": 300}) openai_client.send_images_generations_request({"json": {**body, "size": size}, "timeout": 300})
# chat diffusion — separate test; send_diffusion_request bundles assert_diffusion_response # chat diffusion — separate test; send_diffusion_request bundles assert_diffusion_response
@pytest.mark.parametrize("omni_server", [pytest.param(OmniServerParams(model=MODEL), marks=SINGLE_L4)], indirect=True) @pytest.mark.parametrize("omni_server", [pytest.param(OmniServerParams(model=MODEL), marks=SINGLE_L4)], indirect=True)
def test_foo_chat_completions_t2i_fallback(omni_server, online_client) -> None: def test_foo_chat_completions_t2i_fallback(omni_server, openai_client) -> None:
online_client.send_diffusion_request({...}) openai_client.send_diffusion_request({...})
``` ```
L1 tests under `tests/entrypoints/` may keep **minimal** asserts next to the handler under test when validating a single branch; still prefer `assertions.py` when the same JSON/media check appears in more than one file or level (e.g. L1 protocol test + L4 e2e). L1 tests under `tests/entrypoints/` may keep **minimal** asserts next to the handler under test when validating a single branch; still prefer `assertions.py` when the same JSON/media check appears in more than one file or level (e.g. L1 protocol test + L4 e2e).
@@ -79,7 +79,7 @@ E2E (L2+) should not rely on mocks unless documenting a rare exception; prefer r
- `full_model` — L4 nightly (`test-nightly.yml`); some expansion tests still carry both `advanced_model` and `full_model` during migration - `full_model` — L4 nightly (`test-nightly.yml`); some expansion tests still carry both `advanced_model` and `full_model` during migration
2. **Model type** (pick one): `omni`, `tts`, or `diffusion` 2. **Model type** (pick one): `omni`, `tts`, or `diffusion`
3. **Cross-cutting** when relevant: `parallel`, `cache`, `example`, `benchmark` 3. **Cross-cutting** when relevant: `parallel`, `cache`, `example`, `benchmark`
4. **Hardware**: `cpu`, `cuda`, `rocm`, `npu`, `L4`, `H100`, `cards_1`, … 4. **Hardware**: `cpu`, `cuda`, `rocm`, `npu`, `L4`, `H100`, `distributed_cuda`, …
5. Multi-card: `@hardware_test(...)` in `tests/helpers/mark.py` 5. Multi-card: `@hardware_test(...)` in `tests/helpers/mark.py`
## Command Templates ## Command Templates
@@ -244,7 +244,7 @@ pytest -s -v dfx/reliability/invalid_param_test/test_invalid_image_generation.py
```bash ```bash
cd tests cd tests
pytest -s -v -m "core_model and L4 and not cards_1" --run-level=core_model pytest -s -v -m "core_model and distributed_cuda and L4" --run-level=core_model
``` ```
### Concrete e2e paths (common in-tree) ### Concrete e2e paths (common in-tree)
@@ -279,7 +279,7 @@ When adding or modifying tests, do not stop at “where the file lives” — al
4. **API client + assert placement**: **General** validation → implement in `assertions.py`, call **inside** `send_*_request` in `runtime.py`; tests call **`send_*_request` only**. **Special** case validation → `assert_*` in `assertions.py`, called **in the test** after `send_*_request`. Low-level `send_*_http_request` is for negative/dfx tests (`err_code`), not ordinary L2+ success e2e. 4. **API client + assert placement**: **General** validation → implement in `assertions.py`, call **inside** `send_*_request` in `runtime.py`; tests call **`send_*_request` only**. **Special** case validation → `assert_*` in `assertions.py`, called **in the test** after `send_*_request`. Low-level `send_*_http_request` is for negative/dfx tests (`err_code`), not ordinary L2+ success e2e.
5. **Shared assertions**: logic in **`tests/helpers/assertions.py`**; general checks inside `send_*_request`; special checks only in the test. No `_assert_*` in `test_*.py`. 5. **Shared assertions**: logic in **`tests/helpers/assertions.py`**; general checks inside `send_*_request`; special checks only in the test. No `_assert_*` in `test_*.py`.
6. **One case → one `test_*`**: function name reflects what is validated; no `if case_id == ...` mega-test merging multiple scenarios. 6. **One case → one `test_*`**: function name reflects what is validated; no `if case_id == ...` mega-test merging multiple scenarios.
7. **Fixture scope**: default **`omni_server` + `online_client`** / **`omni_runner` + `offline_client`** (module). Use **`omni_server_function` + `online_client_function`** / **`omni_runner_function` + `offline_client_function`** only when each `test_*` must spawn a fresh instance. Parametrize name must match fixture (`omni_server` vs `omni_server_function`). 7. **Fixture scope**: default **`omni_server` + `openai_client`** / **`omni_runner` + `omni_runner_handler`** (module). Use **`omni_server_function` + `openai_client_function`** / **`omni_runner_function` + `omni_runner_handler_function`** only when each `test_*` must spawn a fresh instance. Parametrize name must match fixture (`omni_server` vs `omni_server_function`).
8. **Type marker**: `omni`, `tts`, or `diffusion` on every model e2e module. 8. **Type marker**: `omni`, `tts`, or `diffusion` on every model e2e module.
9. **Diffusion L4 Function**: wire `*_expansion.py` into **X2I(&A&T)** or **X2V** **Function Test** in **`test-nightly.yml` only** — do not add `test-merge.yml` unless the user also requested L3. 9. **Diffusion L4 Function**: wire `*_expansion.py` into **X2I(&A&T)** or **X2V** **Function Test** in **`test-nightly.yml` only** — do not add `test-merge.yml` unless the user also requested L3.
10. **Diffusion L4 Perf** (only when requested): add `tests/dfx/perf/tests/test_<slug>_vllm_omni.json` + **Perf Test · &lt;Model&gt;** step (artifact upload); not part of “L4 functional cases” by default. 10. **Diffusion L4 Perf** (only when requested): add `tests/dfx/perf/tests/test_<slug>_vllm_omni.json` + **Perf Test · &lt;Model&gt;** step (artifact upload); not part of “L4 functional cases” by default.
+2
View File
@@ -79,6 +79,8 @@
/vllm_omni/model_executor/models/ @tzhouam @gcanlin @Sy0307 @amy-why-3459 @linyueqian /vllm_omni/model_executor/models/ @tzhouam @gcanlin @Sy0307 @amy-why-3459 @linyueqian
/vllm_omni/model_extras/ @tzhouam @gcanlin @princepride /vllm_omni/model_extras/ @tzhouam @gcanlin @princepride
/vllm_omni/plugins/ @gcanlin @tjtanaa @xuechendi /vllm_omni/plugins/ @gcanlin @tjtanaa @xuechendi
# Config override within model_executor — docs/design/module/vllm_omni_config.md
/vllm_omni/model_executor/stage_configs/ @lishunyang12 @alex-jw-brooks
# Execution platforms (hardware backends) — docs/design/module/execution_platforms.md # Execution platforms (hardware backends) — docs/design/module/execution_platforms.md
/vllm_omni/platforms/ @gcanlin @tjtanaa @xuechendi /vllm_omni/platforms/ @gcanlin @tjtanaa @xuechendi
+1 -1
View File
@@ -83,7 +83,7 @@ body:
omni = Omni( omni = Omni(
model="Qwen/Qwen-Image", model="Qwen/Qwen-Image",
deploy_config="/path/to/deploy_config.yaml", stage_configs_path="/path/to/stage_configs.yaml",
) )
prompts = [{"prompt": "A scenic watercolor painting of a lighthouse at sunset"}] prompts = [{"prompt": "A scenic watercolor painting of a lighthouse at sunset"}]
+1 -1
View File
@@ -45,7 +45,7 @@ pull_request_rules:
> # For mypy (substitute "3.10" with the failing version if needed) > # For mypy (substitute "3.10" with the failing version if needed)
> pre-commit run --hook-stage manual mypy-3.10 > pre-commit run --hook-stage manual mypy-3.10
> # For markdownlint > # For markdownlint
> pre-commit run markdownlint-cli2 > pre-commit run --hook-stage manual markdownlint
> ``` > ```
> </details> > </details>
+5 -3
View File
@@ -24,7 +24,9 @@ jobs:
python-version: "3.12" python-version: "3.12"
cache: 'pip' cache: 'pip'
- uses: pre-commit/action@v3.0.1 - uses: pre-commit/action@v3.0.1
# Don't run historical-debt hooks against the whole tree in CI yet. # Don't run the pytest mark check against everything in the CI
# They still run locally on changed files. # for now since a lot of files are still missing them. This check
# is used to help contributors remember to add the marks when they
# commit.
env: env:
SKIP: check-mark,markdownlint-cli2,shellcheck,check-spdx-header,mypy-3.10 SKIP: check-test-ci-coverage
+1 -7
View File
@@ -46,8 +46,6 @@ htmlcov/
.cache .cache
nosetests.xml nosetests.xml
coverage.xml coverage.xml
coverage-*.xml
coverage-*.xml.gz
*.cover *.cover
*.py,cover *.py,cover
.hypothesis/ .hypothesis/
@@ -242,9 +240,9 @@ data/
datasets/ datasets/
*.csv *.csv
*.json *.json
!recipes/cosmos3/negative_prompt.json
!tests/dfx/perf/tests/*.json !tests/dfx/perf/tests/*.json
!apps/ComfyUI-vLLM-Omni/example_workflows/*.json !apps/ComfyUI-vLLM-Omni/example_workflows/*.json
!tests/assets/soulxsinger/*.json
*.jsonl *.jsonl
*.parquet *.parquet
@@ -254,7 +252,6 @@ outputs/
results/ results/
generated/ generated/
output_*/ output_*/
/tests/e2e/accuracy/artifacts/
# Configuration overrides # Configuration overrides
configs/local.yaml configs/local.yaml
@@ -278,6 +275,3 @@ tests/.ci_generated/
# Local Lance precision-matching dump scripts (not part of the repo) # Local Lance precision-matching dump scripts (not part of the repo)
lance_compare/ lance_compare/
# Linting binaries downloaded by local pre-commit hooks
shellcheck*/
-17
View File
@@ -1,17 +0,0 @@
# Keep these rules aligned with vLLM so MkDocs Material syntax is tolerated.
MD007:
indent: 4
MD013: false
# GitHub READMEs start with a centered logo, not an H1.
MD041: false
MD024:
siblings_only: true
MD025:
# Allow front matter title to be different from the first heading in the document.
front_matter_title: ""
MD031:
list_items: false
MD033: false
MD046: false
MD052: false
MD059: false
+12 -94
View File
@@ -24,7 +24,7 @@ repos:
- id: ruff-format - id: ruff-format
- repo: https://github.com/crate-ci/typos - repo: https://github.com/crate-ci/typos
rev: v1.49.0 rev: typos-dict-v0.13.13
hooks: hooks:
- id: typos - id: typos
# only for staged files # only for staged files
@@ -38,19 +38,7 @@ repos:
- id: actionlint - id: actionlint
files: ^\.github/workflows/.*\.ya?ml$ files: ^\.github/workflows/.*\.ya?ml$
- repo: https://github.com/DavidAnson/markdownlint-cli2
rev: v0.23.2
hooks:
- id: markdownlint-cli2
language_version: lts
args: [--fix]
files: (^docs/|^recipes/|(^|/)README\.md$|^CONTRIBUTING\.md$)
# Agent skill docs and CLAUDE.md are not user-facing docs/READMEs.
exclude: (^|/)CLAUDE\.md$|^\.claude/|^\.cursor/
# Keep `local` last so `suggestion` stays the final hook. Autofix repos
# (ruff, typos, markdownlint, trailing-whitespace, etc.) must run first;
# otherwise their "files were modified" output displaces the SKIP tip.
- repo: local - repo: local
hooks: hooks:
- id: signoff-commit - id: signoff-commit
@@ -66,47 +54,8 @@ repos:
verbose: true verbose: true
stages: [commit-msg] stages: [commit-msg]
# TODO: Use https://github.com/pre-commit/mirrors-mypy when mypy setup - id: check-test-ci-coverage
# is less awkward. Local wrapper groups files so follow_imports=skip on name: Ensure test files have pytestmarks
# tests does not leak into library code.
# mypy-3.10 runs on git commit (changed files). CI SKIPs it because
# --all-files is not clean yet. Extra versions are manual-stage only
# (not in default `pre-commit run --all-files`):
# pre-commit run --hook-stage manual mypy-3.12
- id: mypy-3.10
name: Run mypy for Python 3.10
entry: python tools/pre_commit/mypy.py "3.10"
<<: &mypy_common
language: python
types_or: [python, pyi]
require_serial: true
additional_dependencies:
- mypy==1.11.1
- regex
- types-cachetools
- types-setuptools
- types-PyYAML
- types-requests
- types-torch
- pydantic
- id: mypy-3.11
name: Run mypy for Python 3.11
entry: python tools/pre_commit/mypy.py "3.11"
<<: *mypy_common
stages: [manual]
- id: mypy-3.12
name: Run mypy for Python 3.12
entry: python tools/pre_commit/mypy.py "3.12"
<<: *mypy_common
stages: [manual]
- id: mypy-3.13
name: Run mypy for Python 3.13
entry: python tools/pre_commit/mypy.py "3.13"
<<: *mypy_common
stages: [manual]
- id: check-mark
name: Ensure test files have CI marks (no direct SKU)
entry: python tools/pre_commit/check_test_marks.py entry: python tools/pre_commit/check_test_marks.py
language: python language: python
types: [python] types: [python]
@@ -117,50 +66,12 @@ repos:
entry: python tools/pre_commit/check_tts_adapter.py entry: python tools/pre_commit/check_tts_adapter.py
language: python language: python
pass_filenames: false pass_filenames: false
files: ^(vllm_omni/entrypoints/openai/serving_speech\.py|tools/pre_commit/check_tts_adapter\.py)$ files: ^(vllm_omni/entrypoints/openai/(serving_speech\.py|tts_adapters/__init__\.py)|tools/pre_commit/check_tts_adapter\.py)$
# The budget-is-now-slack reminder is printed on a PASSING run, and # The budget-is-now-slack reminder is printed on a PASSING run, and
# pre-commit hides output from passing hooks unless the hook is verbose. # pre-commit hides output from passing hooks unless the hook is verbose.
verbose: true verbose: true
- id: shellcheck # Keep `suggestion` last
name: Lint shell scripts
# language: script breaks on Windows (shebang path loses slashes).
# Invoke the vLLM-style wrapper with bash so Linux CI and Win/Git Bash work.
entry: bash
args: [tools/pre_commit/shellcheck.sh]
language: system
types: [shell]
- id: check-spdx-header
name: Check SPDX headers
entry: python tools/pre_commit/check_spdx_header.py
language: python
types_or: [python, pyi, rust, proto, shell]
files: \.(py|pyi|rs|proto|sh)$
- id: check-forbidden-imports
name: Check forbidden imports
entry: python tools/pre_commit/check_forbidden_imports.py
language: python
types: [python]
additional_dependencies: [regex]
- id: check-torch-cuda-call
name: Prevent new torch.cuda API calls
entry: python tools/pre_commit/check_torch_cuda.py
language: python
types: [python]
additional_dependencies: [regex]
- id: check-buildkite
name: Validate Buildkite Pipelines
entry: python tools/pre_commit/check_buildkite.py
language: python
files: ^\.buildkite/.*\.ya?ml$
additional_dependencies: [pyyaml, check-jsonschema==0.38.0]
# Keep `suggestion` as the final hook. Run after autofix hooks so the
# bypass/SKIP tip is not displaced.
- id: suggestion - id: suggestion
name: Suggestion name: Suggestion
entry: bash -c 'echo "To bypass all the pre-commit hooks, add --no-verify to git commit. To skip a specific hook, prefix the commit command with SKIP=<hook-id>."' entry: bash -c 'echo "To bypass all the pre-commit hooks, add --no-verify to git commit. To skip a specific hook, prefix the commit command with SKIP=<hook-id>."'
@@ -168,3 +79,10 @@ repos:
verbose: true verbose: true
pass_filenames: false pass_filenames: false
# Insert new entries above the `suggestion` entry # Insert new entries above the `suggestion` entry
- id: check-pickle-imports
name: Prevent new pickle/cloudpickle imports
entry: python tools/pre_commit/check_pickle_imports.py
language: python
types: [python]
additional_dependencies: [regex]
+2 -7
View File
@@ -11,15 +11,10 @@ build:
post_checkout: post_checkout:
- git fetch --unshallow || true - git fetch --unshallow || true
pre_install: pre_install:
# `pre_install` runs before RTD upgrades pip, and the seeded pip 23.1
# rejects the `typing_extensions` wheel torch needs (fixed in pip 23.2).
- python -m pip install --upgrade pip
# Pre-install CPU torch so the subsequent `pip install .[docs]` does not # Pre-install CPU torch so the subsequent `pip install .[docs]` does not
# pull ~2 GB of CUDA 13 libs (cuBLAS/cuDNN/NCCL/...) that exceed RTD's # pull ~2 GB of CUDA 13 libs (cuBLAS/cuDNN/NCCL/...) that exceed RTD's
# 1 GB memory cap and time out the mkdocs build. Wheel-only: `--index-url` # 1 GB memory cap and time out the mkdocs build.
# replaces PyPI, so an sdist fallback cannot reach its build deps. The - python -m pip install --index-url https://download.pytorch.org/whl/cpu torch
# quotes keep YAML from reading `:all:` as a mapping key.
- "python -m pip install --only-binary=:all: --index-url https://download.pytorch.org/whl/cpu torch"
mkdocs: mkdocs:
configuration: mkdocs.yml configuration: mkdocs.yml
+1 -9
View File
@@ -1,13 +1,5 @@
# Contributing to vLLM-Omni # Contributing to vLLM-Omni
You may find information about contributing to vLLM-Omni on [Contributing](https://vllm-omni.readthedocs.io/en/latest/contributing/). You may find information about contributing to vLLM-Omni on [Contributing](https://vllm-omni.readthedocs.io/en/latest/contributing/)
AI-assisted contributions must follow the [AI Usage Policy](docs/contributing/README.md#ai-usage-policy), including disclosure, human review, and validation requirements.
Local `pre-commit` gates (SPDX, forbidden imports, `torch.cuda`, shellcheck,
mypy, test marks, TTS adapter ratchet, markdownlint, Buildkite schema, and
how to extend allowlists) are documented in the
[Linting](https://vllm-omni.readthedocs.io/en/latest/contributing/#linting)
section of that guide.
Before submitting a PR, run the [precheck-pr skill](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/precheck-pr/SKILL.md) with the code agent for a self-check against project conventions. Before submitting a PR, run the [precheck-pr skill](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/precheck-pr/SKILL.md) with the code agent for a self-check against project conventions.
+6 -16
View File
@@ -12,12 +12,10 @@ Easy, fast, and cheap omni-modality model serving for everyone
| <a href="https://vllm-omni.readthedocs.io/en/latest/"><b>Documentation</b></a> | <a href="https://deepwiki.com/vllm-project/vllm-omni"><b>DeepWiki</b></a> | <a href="https://discuss.vllm.ai"><b>User Forum</b></a> | <a href="https://slack.vllm.ai"><b>Developer Slack</b></a> | <a href="docs/assets/WeChat.jpg"><b>WeChat</b></a> | <a href="https://arxiv.org/abs/2602.02204"><b>Paper</b></a> | <a href="https://docs.google.com/presentation/d/1aPj0OGl_-ZVoib-Qne5dGDAlrRFB-PdHl6E-EE99g8E/edit?usp=sharing"><b>Slides</b></a> | | <a href="https://vllm-omni.readthedocs.io/en/latest/"><b>Documentation</b></a> | <a href="https://deepwiki.com/vllm-project/vllm-omni"><b>DeepWiki</b></a> | <a href="https://discuss.vllm.ai"><b>User Forum</b></a> | <a href="https://slack.vllm.ai"><b>Developer Slack</b></a> | <a href="docs/assets/WeChat.jpg"><b>WeChat</b></a> | <a href="https://arxiv.org/abs/2602.02204"><b>Paper</b></a> | <a href="https://docs.google.com/presentation/d/1aPj0OGl_-ZVoib-Qne5dGDAlrRFB-PdHl6E-EE99g8E/edit?usp=sharing"><b>Slides</b></a> |
</p> </p>
--- ---
*Latest News* 🔥 *Latest News* 🔥
- [2026/08] We released [0.28.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.28.0), featuring production-ready [MiniMax H3](recipes/MiniMaxAI/MiniMax-H3.md) serving on GPU and NPU, a unified AR/DiT paged KV cache runtime, and enhanced realtime full-duplex serving for the [MiniCPM-o series](recipes/OpenBMB/MiniCPM-o-4_5.md).
- [2026/08] [VeRL-Omni](https://github.com/verl-project/verl-omni) `v0.2.0` is released: faster diffusion RL powered by vLLM-Omni (request-level/step-wise batching with FA3), rebuilt Qwen3-Omni multimodal training (DPO & GSPO), plus LTX-2.3, Qwen-Image-Edit support and more. See the [release notes](https://github.com/verl-project/verl-omni/releases/tag/v0.2.0).
- [2026/08] We released [0.26.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.26.0) - aligned with the vLLM 0.26 release line, featuring [MiniMax H3](recipes/MiniMaxAI/MiniMax-H3.md) joint video/audio generation, an experimental full-duplex realtime runtime for [MiniCPM-o 4.5](recipes/OpenBMB/MiniCPM-o-4_5.md), distributed layerwise diffusion offload, and broader model, hardware, streaming, TTS, and quantization support. - [2026/08] We released [0.26.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.26.0) - aligned with the vLLM 0.26 release line, featuring [MiniMax H3](recipes/MiniMaxAI/MiniMax-H3.md) joint video/audio generation, an experimental full-duplex realtime runtime for [MiniCPM-o 4.5](recipes/OpenBMB/MiniCPM-o-4_5.md), distributed layerwise diffusion offload, and broader model, hardware, streaming, TTS, and quantization support.
- [2026/07] We released [0.24.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.24.0) - aligned with the vLLM 0.24 release line, expanding production-ready coverage across TTS, speech, diffusion, image/video generation, and robot-policy serving, with major Omni stage runtime refactoring, diffusion request-level batching, async output materialization, quantization/cache/memory improvements, and broad CUDA/ROCm/XPU/NPU support. - [2026/07] We released [0.24.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.24.0) - aligned with the vLLM 0.24 release line, expanding production-ready coverage across TTS, speech, diffusion, image/video generation, and robot-policy serving, with major Omni stage runtime refactoring, diffusion request-level batching, async output materialization, quantization/cache/memory improvements, and broad CUDA/ROCm/XPU/NPU support.
- [2026/06] Starting with [0.14.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.14.0), vLLM-Omni publishes a stable release aligned with every even-numbered upstream vLLM minor version. [0.16.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.16.0), [0.18.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.18.0), [0.20.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.20.0), and [0.22.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.22.0) continued this cadence, expanding omni and world-model support with [NVIDIA Cosmos3](recipes/cosmos3/Cosmos3-Nano.md) and DreamZero, adding models such as MiniCPM-o 4.5, MOSS-TTS, and Lance, and advancing TTS, diffusion, distributed execution, quantization, RL integration through [VeRL-Omni](https://github.com/verl-project/verl-omni), and CUDA/ROCm/MUSA/NPU/XPU coverage. - [2026/06] Starting with [0.14.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.14.0), vLLM-Omni publishes a stable release aligned with every even-numbered upstream vLLM minor version. [0.16.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.16.0), [0.18.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.18.0), [0.20.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.20.0), and [0.22.0](https://github.com/vllm-project/vllm-omni/releases/tag/v0.22.0) continued this cadence, expanding omni and world-model support with [NVIDIA Cosmos3](recipes/cosmos3/Cosmos3-Nano.md) and DreamZero, adding models such as MiniCPM-o 4.5, MOSS-TTS, and Lance, and advancing TTS, diffusion, distributed execution, quantization, RL integration through [VeRL-Omni](https://github.com/verl-project/verl-omni), and CUDA/ROCm/MUSA/NPU/XPU coverage.
@@ -53,14 +51,14 @@ vLLM-Omni is flexible and easy to use with:
- Tensor, pipeline, data and expert parallelism support for distributed inference - Tensor, pipeline, data and expert parallelism support for distributed inference
- Streaming outputs - Streaming outputs
- OpenAI-compatible API server - OpenAI-compatible API server
- [Full-duplex realtime serving](docs/serving/realtime_duplex_api.md) with streaming audio input and output - Full-duplex realtime serving with streaming audio input and output (experimental)
vLLM-Omni seamlessly supports most popular open-source models on HuggingFace, including: vLLM-Omni seamlessly supports most popular open-source models on HuggingFace, including:
- **Omni-modality models** (e.g. Qwen3-Omni, MiniCPM-o 4.5, Cosmos3, HunyuanImage, BAGEL) - **Omni-modality models** (e.g. Qwen3-Omni, MiniCPM-o 4.5, Cosmos3, HunyuanImage, BAGEL)
- **TTS models** (e.g. Qwen3-TTS, IndexTTS 2.5, dots.tts, CosyVoice3) - **TTS models** (e.g. Qwen3-TTS, VoxCPM2, Ming-Omni-TTS, CosyVoice3)
- **Diffusion models** — image, video, and audio generation (e.g. MiniMax H3, LTX-2.5, SANA-Video, Wan2.2) - **Diffusion models** — image, video, and audio generation (e.g. MiniMax H3, Qwen-Image, Wan2.2, FLUX)
- **Robot-policy and action models** (e.g. π0, GR00T-N1.7, DreamZero-DROID, InternVLA-A1) - **Robot-policy and action models** (e.g. GR00T-N1.7, DreamZero-DROID, InternVLA-A1, Cosmos3 action policy)
## Getting Started ## Getting Started
@@ -68,7 +66,6 @@ Visit our [documentation](https://vllm-omni.readthedocs.io/en/latest/) to learn
- [Installation](https://vllm-omni.readthedocs.io/en/latest/getting_started/installation/) - [Installation](https://vllm-omni.readthedocs.io/en/latest/getting_started/installation/)
- [Quickstart](https://vllm-omni.readthedocs.io/en/latest/getting_started/quickstart/) - [Quickstart](https://vllm-omni.readthedocs.io/en/latest/getting_started/quickstart/)
- [Nightly CUDA Docker images](https://hub.docker.com/r/vllm/vllm-omni/tags?name=nightly) built from the latest `main`
- [List of Supported Models](https://vllm-omni.readthedocs.io/en/latest/models/supported_models/) - [List of Supported Models](https://vllm-omni.readthedocs.io/en/latest/models/supported_models/)
- [Deployment Recipes](https://recipes.vllm.ai) for vLLM-Omni model serving - [Deployment Recipes](https://recipes.vllm.ai) for vLLM-Omni model serving
@@ -91,18 +88,11 @@ If you use vLLM-Omni for your research, please cite our [paper](https://arxiv.or
``` ```
## Join the Community ## Join the Community
Feel free to ask questions, provide feedbacks and discuss with fellow users of vLLM-Omni in `#sig-omni` slack channel at [slack.vllm.ai](https://slack.vllm.ai) or vLLM user forum at [discuss.vllm.ai](https://discuss.vllm.ai). Feel free to ask questions, provide feedbacks and discuss with fellow users of vLLM-Omni in `#sig-omni` slack channel at [slack.vllm.ai](https://slack.vllm.ai) or vLLM user forum at [discuss.vllm.ai](https://discuss.vllm.ai).
## Star History ## Star History
<a href="https://www.star-history.com/?repos=vllm-project%2Fvllm-omni&type=date&legend=top-left"> [![Star History Chart](https://api.star-history.com/svg?repos=vllm-project/vllm-omni&type=date&legend=top-left)](https://www.star-history.com/#vllm-project/vllm-omni&type=date&legend=top-left)
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=vllm-project/vllm-omni&type=date&theme=dark&legend=top-left&sealed_token=ExgLDZJoQEg27Zfhhut2LqN0GYO6Fw2PWLwPE6JYBUp2BgM3hmsYlwaIVopnUEfbRXidQ4nisumrTdKYydiKhy1SZXipw47qY2_tiUDhCpsPXeXtPuEVKVzBwKs3pw0tiHsJgtSfwXx5yjHXck0Y2SblzFWeJYCkTe1WLGTbUAOIETjXXQJjyCGZvKz5" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=vllm-project/vllm-omni&type=date&legend=top-left&sealed_token=ExgLDZJoQEg27Zfhhut2LqN0GYO6Fw2PWLwPE6JYBUp2BgM3hmsYlwaIVopnUEfbRXidQ4nisumrTdKYydiKhy1SZXipw47qY2_tiUDhCpsPXeXtPuEVKVzBwKs3pw0tiHsJgtSfwXx5yjHXck0Y2SblzFWeJYCkTe1WLGTbUAOIETjXXQJjyCGZvKz5" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=vllm-project/vllm-omni&type=date&legend=top-left&sealed_token=ExgLDZJoQEg27Zfhhut2LqN0GYO6Fw2PWLwPE6JYBUp2BgM3hmsYlwaIVopnUEfbRXidQ4nisumrTdKYydiKhy1SZXipw47qY2_tiUDhCpsPXeXtPuEVKVzBwKs3pw0tiHsJgtSfwXx5yjHXck0Y2SblzFWeJYCkTe1WLGTbUAOIETjXXQJjyCGZvKz5" />
</picture>
</a>
## License ## License
+15 -30
View File
@@ -17,18 +17,18 @@ The main entrypoint is:
vllm serve Qwen/Qwen-Image --omni --port 8099 vllm serve Qwen/Qwen-Image --omni --port 8099
``` ```
1. Run a minimal benchmark: 2. Run a minimal benchmark:
```bash ```bash
python3 benchmarks/diffusion/diffusion_benchmark_serving.py \ python3 benchmarks/diffusion/diffusion_benchmark_serving.py \
--base-url http://localhost:8099 \ --base-url http://localhost:8099 \
--model Qwen/Qwen-Image \ --model Qwen/Qwen-Image \
--task t2i \ --task t2i \
--dataset vbench \ --dataset vbench \
--num-prompts 5 --num-prompts 5
``` ```
### Notes **Notes**
- By default, image tasks talk to `http://<host>:<port>/v1/chat/completions`; video tasks talk to `/v1/videos`. - By default, image tasks talk to `http://<host>:<port>/v1/chat/completions`; video tasks talk to `/v1/videos`.
- If you run the server on another host or port, pass `--base-url` accordingly. - If you run the server on another host or port, pass `--base-url` accordingly.
@@ -52,14 +52,14 @@ Example (`t2v`):
```bash ```bash
python3 benchmarks/diffusion/diffusion_benchmark_serving.py \ python3 benchmarks/diffusion/diffusion_benchmark_serving.py \
--base-url http://localhost:8099 \ --base-url http://localhost:8099 \
--model Wan-AI/Wan2.2-T2V-A14B-Diffusers \ --model Wan-AI/Wan2.2-T2V-A14B-Diffusers \
--task t2v \ --task t2v \
--dataset vbench \ --dataset vbench \
--num-prompts 50 \ --num-prompts 50 \
--width 640 --height 480 \ --width 640 --height 480 \
--num-frames 81 --fps 16 \ --num-frames 81 --fps 16 \
--num-inference-steps 40 --num-inference-steps 40
``` ```
Note: `vbench` can also be used for other tasks such as `t2i` / `i2v` (and `i2i`). For `t2i`, the loader reuses VBench t2v text prompts; for `i2v` / `i2i`, it loads the VBench i2v dataset (with image paths). Note: `vbench` can also be used for other tasks such as `t2i` / `i2v` (and `i2i`). For `t2i`, the loader reuses VBench t2v text prompts; for `i2v` / `i2i`, it loads the VBench i2v dataset (with image paths).
@@ -140,21 +140,6 @@ Traffic / concurrency flags:
- `--request-rate`: Target request rate (requests/second). If set to `inf`, the script sends all requests immediately. - `--request-rate`: Target request rate (requests/second). If set to `inf`, the script sends all requests immediately.
- `--max-concurrency`: Max number of in-flight requests (default: `1`). This can hard-cap the achieved QPS: if it is too small, requests will queue behind the semaphore, and both achieved throughput and observed SLO attainment can be skewed. - `--max-concurrency`: Max number of in-flight requests (default: `1`). This can hard-cap the achieved QPS: if it is too small, requests will queue behind the semaphore, and both achieved throughput and observed SLO attainment can be skewed.
### Video job timeout and failures
`--video-job-timeout` controls how long each `/v1/videos` job may be polled,
including time queued on the server (default: `900` seconds). For long video
jobs at high concurrency, use a larger budget, for example
`--video-job-timeout 1800`. This also applies to warmup jobs. The polling budget
starts after job creation; it excludes time waiting for the client concurrency
semaphore. Individual HTTP calls still use the aiohttp session timeout.
An expired job is counted as failed and deleted, which cancels queued or running
server generation. The benchmark continues processing the remaining requests.
The progress bar counts both successful and failed requests. The final report
shows the failure count and the first 10 errors; `--output-file` saves all errors
with their request IDs in `request_errors`.
### Batched warmup note ### Batched warmup note
For batched serving runs, warm the same in-flight shape you plan to measure. For batched serving runs, warm the same in-flight shape you plan to measure.
+57 -64
View File
@@ -1,6 +1,3 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import asyncio import asyncio
import base64 import base64
import json import json
@@ -46,7 +43,6 @@ class RequestFuncInput:
video_paths: list[str] | None = None video_paths: list[str] | None = None
request_id: str = field(default_factory=lambda: str(uuid.uuid4())) request_id: str = field(default_factory=lambda: str(uuid.uuid4()))
default_bot_task: str | None = DEFAULT_EDITS_BOT_TASK default_bot_task: str | None = DEFAULT_EDITS_BOT_TASK
video_job_timeout: float = 900.0
@dataclass @dataclass
@@ -335,64 +331,64 @@ async def async_request_v1_videos(
output = RequestFuncOutput() output = RequestFuncOutput()
output.start_time = time.perf_counter() output.start_time = time.perf_counter()
files = dict(input.extra_body)
if input.prompt:
files.setdefault("prompt", input.prompt)
if input.width and input.height:
files.setdefault("height", input.height)
files.setdefault("width", input.width)
if input.num_frames:
files.setdefault("num_frames", input.num_frames)
if input.num_inference_steps:
files.setdefault("num_inference_steps", input.num_inference_steps)
if input.seed is not None:
files.setdefault("seed", input.seed)
if input.fps:
files.setdefault("fps", input.fps)
form = aiohttp.FormData()
for k, v in files.items():
form.add_field(k, str(v))
image_file = None image_file = None
job_id = None if input.image_paths and input.video_paths:
try: output.error = "Only one of image_paths or video_paths can be provided"
files = dict(input.extra_body) output.success = False
if input.prompt: return output
files.setdefault("prompt", input.prompt)
if input.width and input.height:
files.setdefault("height", input.height)
files.setdefault("width", input.width)
if input.num_frames:
files.setdefault("num_frames", input.num_frames)
if input.num_inference_steps:
files.setdefault("num_inference_steps", input.num_inference_steps)
if input.seed is not None:
files.setdefault("seed", input.seed)
if input.fps:
files.setdefault("fps", input.fps)
form = aiohttp.FormData() if input.image_paths and len(input.image_paths) > 0:
for k, v in files.items(): image_path = input.image_paths[0]
form.add_field(k, str(v)) image_file = open(image_path, "rb")
form.add_field(
if input.image_paths and input.video_paths: "input_reference",
output.error = "Only one of image_paths or video_paths can be provided" image_file,
output.success = False filename=os.path.basename(image_path),
return output content_type=_guess_mime_type(image_path),
)
if input.image_paths and len(input.image_paths) > 0: elif input.video_paths and len(input.video_paths) > 0:
image_path = input.image_paths[0] video_path = input.video_paths[0]
image_file = open(image_path, "rb") if _is_minimax_h3_mixed_reference(input.model):
with open(video_path, "rb") as reference_file:
video_b64 = base64.b64encode(reference_file.read()).decode("ascii")
form.add_field(
"video_reference",
json.dumps([{"video_url": f"data:video/mp4;base64,{video_b64}"}]),
)
else:
image_file = open(video_path, "rb")
form.add_field( form.add_field(
"input_reference", "input_reference",
image_file, image_file,
filename=os.path.basename(image_path), filename=os.path.basename(video_path),
content_type=_guess_mime_type(image_path), content_type=_guess_mime_type(video_path),
) )
elif input.video_paths and len(input.video_paths) > 0:
video_path = input.video_paths[0]
if _is_minimax_h3_mixed_reference(input.model):
with open(video_path, "rb") as reference_file:
video_b64 = base64.b64encode(reference_file.read()).decode("ascii")
form.add_field(
"video_reference",
json.dumps([{"video_url": f"data:video/mp4;base64,{video_b64}"}]),
)
else:
image_file = open(video_path, "rb")
form.add_field(
"input_reference",
image_file,
filename=os.path.basename(video_path),
content_type=_guess_mime_type(video_path),
)
job_status = None job_id = None
poll_json = {} job_status = None
resp_json = {} poll_json = {}
resp_json = {}
try:
# invoke a post request (POST /v1/videos) # invoke a post request (POST /v1/videos)
async with session.post(input.api_url, data=form) as response: async with session.post(input.api_url, data=form) as response:
if response.status == 200: if response.status == 200:
@@ -410,7 +406,7 @@ async def async_request_v1_videos(
# invoke a poll request (GET /v1/videos/{video_id}) # invoke a poll request (GET /v1/videos/{video_id})
poll_interval = 2.0 # Unit(s) poll_interval = 2.0 # Unit(s)
timeout_seconds = input.video_job_timeout timeout_seconds = 600.0
deadline = time.perf_counter() + timeout_seconds deadline = time.perf_counter() + timeout_seconds
job_url = f"{input.api_url}/{job_id}" job_url = f"{input.api_url}/{job_id}"
@@ -426,11 +422,8 @@ async def async_request_v1_videos(
poll_json = await poll_response.json() poll_json = await poll_response.json()
job_status = poll_json.get("status") job_status = poll_json.get("status")
if job_status not in {"completed", "failed"} and time.perf_counter() >= deadline: if time.perf_counter() >= deadline:
output.error = ( output.error = f"Timed out waiting for video job {job_id} to complete."
f"Timed out after {timeout_seconds:g}s waiting for video job {job_id} to complete. "
"Increase --video-job-timeout for long-running or queued video jobs."
)
output.success = False output.success = False
return output return output
@@ -459,7 +452,7 @@ async def async_request_v1_videos(
elif "peak_memory_mb" in resp_json: elif "peak_memory_mb" in resp_json:
output.peak_memory_mb = resp_json["peak_memory_mb"] output.peak_memory_mb = resp_json["peak_memory_mb"]
except Exception as e: except Exception as e:
output.error = f"{type(e).__name__}: {e}" output.error = str(e)
output.success = False output.success = False
finally: finally:
if image_file is not None: if image_file is not None:
@@ -472,13 +465,13 @@ async def async_request_v1_videos(
except Exception as e: except Exception as e:
print(f"Failed to clean up video job {job_id}: {e}") print(f"Failed to clean up video job {job_id}: {e}")
output.latency = time.perf_counter() - output.start_time output.latency = time.perf_counter() - output.start_time
if pbar is not None:
pbar.update(1)
if output.success and input.slo_ms is not None: if output.success and input.slo_ms is not None:
output.slo_achieved = (output.latency * 1000.0) <= float(input.slo_ms) output.slo_achieved = (output.latency * 1000.0) <= float(input.slo_ms)
if pbar:
pbar.update(1)
return output return output
@@ -1,6 +1,6 @@
# adapted from fastvideo # adapted from fastvideo
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
""" """
Benchmark online serving for diffusion models (Image/Video Generation). Benchmark online serving for diffusion models (Image/Video Generation).
@@ -87,7 +87,6 @@ import base64
import glob import glob
import json import json
import logging import logging
import math
import os import os
import random import random
import tempfile import tempfile
@@ -1117,11 +1116,6 @@ def calculate_metrics(
"duration": total_duration, "duration": total_duration,
"completed_requests": num_success, "completed_requests": num_success,
"failed_requests": len(error_outputs), "failed_requests": len(error_outputs),
"request_errors": [
{"request_id": req.request_id, "error": out.error}
for req, out in zip(requests_list, outputs)
if not out.success
],
"throughput_qps": num_success / total_duration if total_duration > 0 else 0, "throughput_qps": num_success / total_duration if total_duration > 0 else 0,
"latency_mean": np.mean(latencies) if latencies else 0, "latency_mean": np.mean(latencies) if latencies else 0,
"latency_median": np.median(latencies) if latencies else 0, "latency_median": np.median(latencies) if latencies else 0,
@@ -1267,9 +1261,6 @@ def _default_endpoint_for_task(task: str) -> str:
async def benchmark(args): async def benchmark(args):
if not math.isfinite(args.video_job_timeout) or args.video_job_timeout <= 0:
raise ValueError("--video-job-timeout must be a positive finite number of seconds.")
# Construct base_url if not provided # Construct base_url if not provided
if args.base_url is None: if args.base_url is None:
args.base_url = f"http://{args.host}:{args.port}" args.base_url = f"http://{args.host}:{args.port}"
@@ -1321,8 +1312,6 @@ async def benchmark(args):
print("Loading requests...") print("Loading requests...")
requests_list = dataset.get_requests() requests_list = dataset.get_requests()
print(f"Prepared {len(requests_list)} requests from {args.dataset} dataset.") print(f"Prepared {len(requests_list)} requests from {args.dataset} dataset.")
for req in requests_list:
req.video_job_timeout = args.video_job_timeout
if args.return_stage_metrics and args.endpoint in _STAGE_METRICS_ENDPOINTS: if args.return_stage_metrics and args.endpoint in _STAGE_METRICS_ENDPOINTS:
for req in requests_list: for req in requests_list:
@@ -1387,8 +1376,6 @@ async def benchmark(args):
metrics["model"] = args.model metrics["model"] = args.model
metrics["dataset"] = args.dataset metrics["dataset"] = args.dataset
metrics["task"] = args.task metrics["task"] = args.task
if args.endpoint == "/v1/videos":
metrics["video_job_timeout"] = args.video_job_timeout
if args.endpoint == "/v1/images/edits": if args.endpoint == "/v1/images/edits":
metrics["bot_task"] = args.bot_task metrics["bot_task"] = args.bot_task
@@ -1411,11 +1398,6 @@ async def benchmark(args):
) )
) )
print("{:<40} {}/{:<15}".format("Successful requests:", metrics["completed_requests"], len(requests_list))) print("{:<40} {}/{:<15}".format("Successful requests:", metrics["completed_requests"], len(requests_list)))
print("{:<40} {:<15}".format("Failed requests:", metrics["failed_requests"]))
for failure in metrics["request_errors"][:10]:
print(f"Request {failure['request_id']} failed: {failure['error']}")
if metrics["failed_requests"] > 10:
print("Showing first 10 failures; use --output-file to save all request errors.")
# Section 3: Performance Metrics # Section 3: Performance Metrics
print(f"{'-' * 50}") print(f"{'-' * 50}")
@@ -1499,13 +1481,6 @@ if __name__ == "__main__":
help="Path to local dataset file (optional).", help="Path to local dataset file (optional).",
) )
parser.add_argument("--num-prompts", type=int, default=10, help="Number of prompts to benchmark.") parser.add_argument("--num-prompts", type=int, default=10, help="Number of prompts to benchmark.")
parser.add_argument(
"--video-job-timeout",
type=float,
default=900.0,
help="Maximum polling time in seconds per video job, including server queue time (default: 900). "
"Increase this for long-running video generation or high concurrency.",
)
parser.add_argument( parser.add_argument(
"--max-concurrency", "--max-concurrency",
type=int, type=int,
+11 -10
View File
@@ -158,6 +158,7 @@ def _build_omni_kwargs(args, quantization=None):
def _generate_image(omni, args, prompt, seed): def _generate_image(omni, args, prompt, seed):
"""Generate a single image and return (PIL.Image, time_seconds, memory_gib).""" """Generate a single image and return (PIL.Image, time_seconds, memory_gib)."""
from vllm_omni.inputs.data import OmniDiffusionSamplingParams from vllm_omni.inputs.data import OmniDiffusionSamplingParams
from vllm_omni.outputs import OmniRequestOutput
from vllm_omni.platforms import current_omni_platform from vllm_omni.platforms import current_omni_platform
generator = torch.Generator(device=current_omni_platform.device_type).manual_seed(seed) generator = torch.Generator(device=current_omni_platform.device_type).manual_seed(seed)
@@ -175,7 +176,7 @@ def _generate_image(omni, args, prompt, seed):
elapsed = time.perf_counter() - start elapsed = time.perf_counter() - start
peak_mem = torch.accelerator.max_memory_allocated() / (1024**3) peak_mem = torch.accelerator.max_memory_allocated() / (1024**3)
req_out = outputs[0] if isinstance(outputs, list) else outputs req_out = OmniRequestOutput.unwrap_result(outputs)
if not req_out.images: if not req_out.images:
raise ValueError("Could not extract image output from result.") raise ValueError("Could not extract image output from result.")
img = req_out.images[0] img = req_out.images[0]
@@ -205,15 +206,15 @@ def _generate_video(omni, args, prompt, seed):
elapsed = time.perf_counter() - start elapsed = time.perf_counter() - start
peak_mem = torch.accelerator.max_memory_allocated() / (1024**3) peak_mem = torch.accelerator.max_memory_allocated() / (1024**3)
if isinstance(outputs, list) and isinstance(outputs[0], OmniRequestOutput): first = outputs[0]
first = outputs[0] if hasattr(first, "request_output") and isinstance(first.request_output, list):
elif isinstance(outputs, OmniRequestOutput): inner = first.request_output[0]
first = outputs if isinstance(inner, OmniRequestOutput) and hasattr(inner, "images"):
else: frames = inner.images[0] if inner.images else None
raise ValueError("Could not extract video frames from output.") else:
frames = inner
if hasattr(first, "images"): elif hasattr(first, "images") and first.images:
frames = first.images[0] if first.images else None frames = first.images[0]
else: else:
raise ValueError("Could not extract video frames from output.") raise ValueError("Could not extract video frames from output.")
+1 -1
View File
@@ -300,7 +300,7 @@ def benchmark(args: argparse.Namespace) -> None:
output_idx = 0 output_idx = 0
for stage_outputs in omni.generate(all_prompts, sampling_params_list, py_generator=True): for stage_outputs in omni.generate(all_prompts, sampling_params_list, py_generator=True):
if stage_outputs.final_output_type == "image": if stage_outputs.final_output_type == "image":
request_output = stage_outputs request_output = stage_outputs.request_output
request_id = getattr(request_output, "request_id", "") request_id = getattr(request_output, "request_id", "")
images = getattr(request_output, "images", []) images = getattr(request_output, "images", [])
@@ -1,156 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Benchmark diffusion LoRA expand-and-accumulate implementations.
This compares the previous functional expand path with direct accumulation into
the selected output slice. It is a synthetic kernel microbenchmark and does not
load a diffusion model.
Example:
python benchmarks/kernels/benchmark_diffusion_lora_expand.py \
--m 1,16,128,512,4096,16384 \
--in-dim 4096 --out-dim 4096 --rank 64 \
--dtype bfloat16 --packed --warmup 30 \
--small-iterations 500 --large-iterations 100
"""
import argparse
import math
import statistics
from collections.abc import Callable
import torch
MiB = 1024**2
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--m", default="1,16,128,512,4096,16384")
parser.add_argument("--in-dim", type=int, default=4096)
parser.add_argument("--out-dim", type=int, default=4096)
parser.add_argument("--rank", type=int, default=64)
parser.add_argument("--dtype", choices=("bfloat16", "float16"), default="bfloat16")
parser.add_argument(
"--packed",
action="store_true",
help="Accumulate into the second half of a packed output tensor.",
)
parser.add_argument("--warmup", type=int, default=30)
parser.add_argument("--small-iterations", type=int, default=500)
parser.add_argument("--large-iterations", type=int, default=100)
parser.add_argument("--large-m-threshold", type=int, default=1024)
return parser.parse_args()
def percentile(samples: list[float], probability: float) -> float:
ordered = sorted(samples)
position = probability * (len(ordered) - 1)
lower = math.floor(position)
upper = math.ceil(position)
if lower == upper:
return ordered[lower]
weight = position - lower
return ordered[lower] * (1 - weight) + ordered[upper] * weight
def measure_latency_ms(operation: Callable[[], None], warmup: int, iterations: int) -> list[float]:
for _ in range(warmup):
operation()
torch.accelerator.synchronize()
starts = [torch.cuda.Event(enable_timing=True) for _ in range(iterations)]
ends = [torch.cuda.Event(enable_timing=True) for _ in range(iterations)]
for start, end in zip(starts, ends, strict=True):
start.record()
operation()
end.record()
torch.accelerator.synchronize()
return [start.elapsed_time(end) for start, end in zip(starts, ends, strict=True)]
def measure_extra_peak_mib(operation: Callable[[], None]) -> float:
torch.accelerator.synchronize()
torch.accelerator.reset_peak_memory_stats()
allocated = torch.accelerator.memory_allocated()
operation()
torch.accelerator.synchronize()
peak = torch.accelerator.max_memory_allocated()
return (peak - allocated) / MiB
def print_header(args: argparse.Namespace, dtype: torch.dtype) -> None:
device_name = torch.cuda.get_device_name()
print(f"device={device_name} torch={torch.__version__} dtype={dtype}")
print(f"in_dim={args.in_dim} out_dim={args.out_dim} rank={args.rank} packed={args.packed} warmup={args.warmup}")
print("M iters old_p10 old_p50 old_p90 new_p10 new_p50 new_p90 speedup old_MiB new_MiB max_abs")
def benchmark_shape(m: int, args: argparse.Namespace, dtype: torch.dtype) -> None:
device = torch.device("cuda")
output_width = args.out_dim * (2 if args.packed else 1)
offset = args.out_dim if args.packed else 0
output_slice = slice(offset, offset + args.out_dim)
scale = 1 / math.sqrt(args.in_dim)
x = torch.randn(m, args.in_dim, device=device, dtype=dtype)
lora_a = torch.randn(args.rank, args.in_dim, device=device, dtype=dtype) * scale
lora_b = torch.randn(args.out_dim, args.rank, device=device, dtype=dtype) / math.sqrt(args.rank)
initial_output = torch.randn(m, output_width, device=device, dtype=dtype)
def baseline(output: torch.Tensor) -> None:
buffer = x @ lora_a.t()
delta = buffer @ lora_b.t()
selected = output[:, output_slice]
selected[:] = selected + delta
def optimized(output: torch.Tensor) -> None:
buffer = x @ lora_a.t()
selected = output[:, output_slice]
torch.addmm(selected, buffer, lora_b.t(), out=selected)
reference = initial_output.clone()
candidate = initial_output.clone()
baseline(reference)
optimized(candidate)
torch.testing.assert_close(candidate, reference, rtol=1e-2, atol=1e-2)
max_abs = (candidate - reference).abs().max().item()
old_output = initial_output.clone()
new_output = initial_output.clone()
iterations = args.large_iterations if m >= args.large_m_threshold else args.small_iterations
old_peak = measure_extra_peak_mib(lambda: baseline(old_output))
new_peak = measure_extra_peak_mib(lambda: optimized(new_output))
old_samples = measure_latency_ms(lambda: baseline(old_output), args.warmup, iterations)
new_samples = measure_latency_ms(lambda: optimized(new_output), args.warmup, iterations)
old_p50 = statistics.median(old_samples)
new_p50 = statistics.median(new_samples)
print(
f"{m:<7} {iterations:<6} "
f"{percentile(old_samples, 0.1):>8.5f} {old_p50:>8.5f} {percentile(old_samples, 0.9):>8.5f} "
f"{percentile(new_samples, 0.1):>8.5f} {new_p50:>8.5f} {percentile(new_samples, 0.9):>8.5f} "
f"{old_p50 / new_p50:>8.3f} {old_peak:>8.2f} {new_peak:>8.2f} {max_abs:>8.5f}"
)
def main() -> None:
args = parse_args()
if not torch.cuda.is_available():
raise RuntimeError("This benchmark requires a CUDA GPU")
if args.warmup < 0 or args.small_iterations <= 0 or args.large_iterations <= 0:
raise ValueError("warmup must be non-negative and iteration counts must be positive")
dtype = getattr(torch, args.dtype)
torch.manual_seed(0)
print_header(args, dtype)
with torch.inference_mode():
for m in (int(value) for value in args.m.split(",")):
if m <= 0:
raise ValueError(f"M must be positive, got {m}")
benchmark_shape(m, args, dtype)
if __name__ == "__main__":
main()
@@ -167,11 +167,9 @@ def main() -> None:
] ]
native_cmd = [ native_cmd = [
sys.executable, sys.executable,
"examples/offline_inference/text_to_video/text_to_video.py", "examples/offline_inference/text_to_video/text_to_video_lingbot.py",
"--model", "--model",
args.model, args.model,
"--model-class-name",
"LingBotVideoPipeline",
"--prompt", "--prompt",
args.prompt, args.prompt,
"--output", "--output",
+26 -83
View File
@@ -2,7 +2,7 @@
A model-agnostic serving benchmark for TTS models in vllm-omni. One CLI A model-agnostic serving benchmark for TTS models in vllm-omni. One CLI
(`bench_tts.py`) + one YAML registry (`model_configs.yaml`) drive perf and (`bench_tts.py`) + one YAML registry (`model_configs.yaml`) drive perf and
quality runs for every registered checkpoint: **Qwen3-TTS** (Base / CustomVoice / VoiceDesign) quality runs for every registered checkpoint: **Qwen3-TTS** (Base / CustomVoice)
and **VoxCPM2** today, more to come. and **VoxCPM2** today, more to come.
The same three task types — `voice_clone`, `default_voice`, `voice_design` — The same three task types — `voice_clone`, `default_voice`, `voice_design` —
@@ -18,13 +18,8 @@ vllm serve Qwen/Qwen3-TTS-12Hz-1.7B-Base --omni --port 8000
``` ```
The server auto-loads its Deploy YAML from `vllm_omni/deploy/qwen3_tts.yaml` The server auto-loads its Deploy YAML from `vllm_omni/deploy/qwen3_tts.yaml`
(Pipeline + Deploy schema introduced in #2383). No explicit `--deploy-config` (Pipeline + Deploy schema introduced in #2383). No `--stage-configs-path` or
flag is needed for a registered model. `--deploy-config` flag is needed for any registered model.
Start the checkpoint that matches the benchmark task below: `-Base` for
`voice_clone`, `-CustomVoice` for `default_voice`, or `-VoiceDesign` for
`voice_design`. Restart the server with the matching checkpoint when switching
tasks; changing only the benchmark's `--model` does not reload the server.
### 2. Run the benchmark (`vllm bench serve --omni`) ### 2. Run the benchmark (`vllm bench serve --omni`)
@@ -67,12 +62,12 @@ vllm bench serve --omni \
--save-result --result-dir ./results --save-result --result-dir ./results
``` ```
#### voice_design (Qwen3-TTS-VoiceDesign, bundled seed_tts_design) #### voice_design (Qwen3-TTS-CustomVoice, bundled seed_tts_design)
```bash ```bash
vllm bench serve --omni \ vllm bench serve --omni \
--host 127.0.0.1 --port 8000 \ --host 127.0.0.1 --port 8000 \
--model Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign \ --model Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice \
--backend openai-audio-speech \ --backend openai-audio-speech \
--endpoint /v1/audio/speech \ --endpoint /v1/audio/speech \
--dataset-name seed-tts-design \ --dataset-name seed-tts-design \
@@ -85,25 +80,6 @@ vllm bench serve --omni \
--save-result --result-dir ./results --save-result --result-dir ./results
``` ```
#### Set the sample rate for models that are not 24 kHz
Streamed PCM carries no header, so the harness derives `audio_duration` from the
byte count and a **default 24000 Hz** assumption
(`vllm_omni/metrics/definitions.py:DEFAULT_AUDIO_SAMPLE_RATE`). For a model that
emits another rate, export the real one or every duration-derived metric is
wrong by `real_sr / 24000`:
```bash
# Audio8 TTS, Fish Speech S2 Pro, Ming-omni-tts, ... all decode at 44.1 kHz
export VLLM_OMNI_BENCH_AUDIO_SAMPLE_RATE=44100
```
Without it, a 44.1 kHz model reports `audio_duration` 1.84x too long, so
`audio_rtf` looks 1.84x *better* than reality and `audio_underrun` /
`Streaming continuity OK rate` are measured against inflated chunk budgets.
A quick sanity check: compare `Mean AUDIO_DURATION (s)` against the actual
length of a saved WAV for the same prompt.
#### Streaming continuity (`audio_underrun`) #### Streaming continuity (`audio_underrun`)
`audio_underrun` (seconds) is the per-request worst-case buffer deficit `audio_underrun` (seconds) is the per-request worst-case buffer deficit
@@ -163,34 +139,6 @@ python benchmarks/tts/bench_tts.py \
--output-dir ./results --output-dir ./results
``` ```
#### Fixed IndexTTS 2.5 performance sweep
IndexTTS 2.5 is registered in `model_configs.yaml` like the other TTS models.
Use `--served-model-name` when the server was launched from a local native
bundle instead of the registry key. The command below reproduces the acceptance
workload: Seed-TTS Eval EN, `n=500`, concurrency `4/8/16/32`, five warmups, and
dataset seed 0.
```bash
python benchmarks/tts/bench_tts.py \
--model IndexTeam/IndexTTS-2.5 \
--served-model-name /path/to/indextts-2.5 \
--task voice_clone --locale en \
--dataset-path /path/to/seedtts_testset \
--host 127.0.0.1 --port 8092 \
--concurrency 4 8 16 32 \
--num-prompts 500 --num-warmups 5 \
--output-dir ./results/indextts2_5 \
-- \
--tokenizer /path/to/indextts-2.5/qwen0.6bemo4-merge \
--seed 0 --metric-percentiles 50,95,99 --disable-tqdm --save-detailed
```
The wrapper benchmarks an already-running server. For reproducible quality
comparisons, add `--request-seed 42`; production-performance runs should omit
it. The trailing `--seed 0` controls Seed-TTS row selection rather than model
sampling.
### 4. Plot a sweep ### 4. Plot a sweep
```bash ```bash
@@ -204,11 +152,11 @@ Outputs TTFP / RTF / throughput curves (and a markdown table) for every
## Task types ## Task types
| Task | Dataset | Request body | Checkpoints that support it | | Task | Dataset | Request body | Checkpoints that support it |
| --------------- | ----------------- | ----------------------------------------------------------------------- | ----------------------------- | |-----------------|-------------------|-----------------------------------------------------|------------------------------------------|
| `voice_clone` | `seed-tts` | `ref_audio` + `ref_text` + `task_type=Base` | `Qwen3-TTS-*-Base`, `VoxCPM2` | | `voice_clone` | `seed-tts` | `ref_audio` + `ref_text` + `task_type=Base` | `Qwen3-TTS-*-Base`, `VoxCPM2` |
| `default_voice` | `seed-tts-text` | `voice=Vivian` + `task_type=CustomVoice` | `Qwen3-TTS-*-CustomVoice` | | `default_voice` | `seed-tts-text` | `voice=Vivian` + `task_type=CustomVoice` | `Qwen3-TTS-*-CustomVoice` |
| `voice_design` | `seed-tts-design` | `instructions=<natural-language description>` + `task_type=VoiceDesign` | `Qwen3-TTS-*-VoiceDesign` | | `voice_design` | `seed-tts-design` | `instructions=<natural-language description>` + `task_type=VoiceDesign` | `Qwen3-TTS-*-CustomVoice` |
**`-CustomVoice` checkpoints do NOT ship `speaker_encoder` weights**, so **`-CustomVoice` checkpoints do NOT ship `speaker_encoder` weights**, so
voice_clone requests raise `ValueError` at model runtime. Use `-Base` for voice_clone requests raise `ValueError` at model runtime. Use `-Base` for
@@ -234,12 +182,12 @@ Then add the model's Deploy YAML under `vllm_omni/deploy/<model>.yaml`
## Datasets ## Datasets
| Dataset | Bundled? | Format | Source | | Dataset | Bundled? | Format | Source |
| ----------------- | -------- | ----------------------- | -------------------------------------------------------------------- | |--------------------|----------|-------------------|----------------------------------------------------------------|
| `seed-tts-design` | ✅ | 5-field meta.lst | `benchmarks/build_dataset/seed_tts_design/en/meta.lst` (20 prompts) | | `seed-tts-design` | ✅ | 5-field meta.lst | `benchmarks/build_dataset/seed_tts_design/en/meta.lst` (20 prompts) |
| `seed_tts_smoke` | ✅ | 4-field meta.lst | `benchmarks/build_dataset/seed_tts_smoke/en/meta.lst` (20 text-only) | | `seed_tts_smoke` | ✅ | 4-field meta.lst | `benchmarks/build_dataset/seed_tts_smoke/en/meta.lst` (20 text-only) |
| `seed-tts` | ❌ | 4-field meta.lst + WAVs | Google-Drive: [BytedanceSpeech/seed-tts-eval][seedtts] (~1.2 GB) | | `seed-tts` | ❌ | 4-field meta.lst + WAVs | Google-Drive: [BytedanceSpeech/seed-tts-eval][seedtts] (~1.2 GB) |
| `seed-tts-text` | ❌ | 4-field meta.lst | Same archive as `seed-tts` (wav column unused) | | `seed-tts-text` | ❌ | 4-field meta.lst | Same archive as `seed-tts` (wav column unused) |
[seedtts]: https://github.com/BytedanceSpeech/seed-tts-eval [seedtts]: https://github.com/BytedanceSpeech/seed-tts-eval
@@ -251,11 +199,11 @@ For manual voice_clone / default_voice runs against the full corpus, follow
`tests/dfx/perf/tests/test_tts.json` wires three perf regimes plus quality: `tests/dfx/perf/tests/test_tts.json` wires three perf regimes plus quality:
| eval_phase | concurrency | purpose | Baseline metrics | | eval_phase | concurrency | purpose | Baseline metrics |
| ------------ | ----------- | -------------------------------------------------------- | ------------------------------------------ | |---------------|-------------|---------------------------------------------------------|-----------------------------------------|
| `latency` | 1 | Single-request TTFP / RTF SLO | `median_audio_ttfp_ms`, `median_audio_rtf` | | `latency` | 1 | Single-request TTFP / RTF SLO | `median_audio_ttfp_ms`, `median_audio_rtf` |
| `throughput` | 8 | Codec-batching cliff sentinel (PDF #272 concurrency≥8) | `median_audio_ttfp_ms`, `median_audio_rtf` | | `throughput` | 8 | Codec-batching cliff sentinel (PDF #272 concurrency≥8) | `median_audio_ttfp_ms`, `median_audio_rtf` |
| `quality` | 4 | WER / SIM / UTMOS regression (disabled in CI by default) | `mean_audio_rtf` | | `quality` | 4 | WER / SIM / UTMOS regression (disabled in CI by default)| `mean_audio_rtf` |
Why `median_*` for latency/throughput and `mean_*` for quality: latency Why `median_*` for latency/throughput and `mean_*` for quality: latency
distributions have cold-start tails that drag the mean; quality aggregates distributions have cold-start tails that drag the mean; quality aggregates
@@ -269,15 +217,10 @@ PR #2558 — quality runs are manual / release-validation, not nightly).
Observed on H20-3e, Qwen3-TTS-1.7B (measured pre-merge on this branch): Observed on H20-3e, Qwen3-TTS-1.7B (measured pre-merge on this branch):
| Task | Model | c=1 | c=4 | **c=8** | c=16 | c=32 | | Task | Model | c=1 | c=4 | **c=8** | c=16 | c=32 |
| ------------ | ---------------- | --------------------- | ------------ | ----------------- | ------------- | ------------- | |---------------|---------------|--------|--------|------------|--------|--------|
| voice_clone | 1.7B-Base | RTF 0.15 / TTFP 165ms | 0.28 / 412ms | **0.49 / 1701ms** | 0.72 / 3355ms | 0.77 / 3772ms | | voice_clone | 1.7B-Base | RTF 0.15 / TTFP 165ms | 0.28 / 412ms | **0.49 / 1701ms** | 0.72 / 3355ms | 0.77 / 3772ms |
| voice_design | 1.7B-CustomVoice | RTF 0.08 / TTFP 53ms | 0.11 / 154ms | **0.21 / 872ms** | 0.33 / 1801ms | 0.38 / 1989ms | | voice_design | 1.7B-CustomVoice | RTF 0.08 / TTFP 53ms | 0.11 / 154ms | **0.21 / 872ms** | 0.33 / 1801ms | 0.38 / 1989ms |
The historical `voice_design` row above lists a CustomVoice checkpoint, which
current task validation rejects for VoiceDesign requests. It does not establish
a VoiceDesign checkpoint baseline; rerun with `Qwen3-TTS-12Hz-1.7B-VoiceDesign`
before comparing performance.
Both models show a **4–6× TTFP jump from c=4 to c=8** while audio throughput Both models show a **4–6× TTFP jump from c=4 to c=8** while audio throughput
saturates around c=4–8 — the codec-bs=1 bottleneck documented in saturates around c=4–8 — the codec-bs=1 bottleneck documented in
@@ -286,7 +229,7 @@ sentinel for regressions in this area.
## File layout ## File layout
```text ```
benchmarks/tts/ benchmarks/tts/
├── README.md (this file) ├── README.md (this file)
├── bench_tts.py CLI — serve-mode benchmark driver ├── bench_tts.py CLI — serve-mode benchmark driver
+4 -29
View File
@@ -84,17 +84,12 @@ def build_bench_args(
result_filename: str | None, result_filename: str | None,
extra_cli_args: list[str], extra_cli_args: list[str],
output_len: int | None = None, output_len: int | None = None,
served_model_name: str | None = None,
num_warmups: int = 2,
request_seed: int | None = None,
) -> list[str]: ) -> list[str]:
"""Build the ``vllm bench serve --omni`` command for one (task, concurrency) run.""" """Build the ``vllm bench serve --omni`` command for one (task, concurrency) run."""
dataset_name = _TASK_TO_DATASET[task] dataset_name = _TASK_TO_DATASET[task]
backend: str = model_cfg["backend"] backend: str = model_cfg["backend"]
endpoint: str = model_cfg["endpoint"] endpoint: str = model_cfg["endpoint"]
task_extra_body: dict[str, Any] = dict((model_cfg.get("task_extra_body") or {}).get(task) or {}) task_extra_body: dict[str, Any] = (model_cfg.get("task_extra_body") or {}).get(task) or {}
if request_seed is not None:
task_extra_body["seed"] = request_seed
# Resolve dataset path # Resolve dataset path
if dataset_path: if dataset_path:
@@ -114,7 +109,7 @@ def build_bench_args(
"--port", "--port",
str(port), str(port),
"--model", "--model",
served_model_name or model, model,
"--backend", "--backend",
backend, backend,
"--endpoint", "--endpoint",
@@ -124,7 +119,7 @@ def build_bench_args(
"--num-prompts", "--num-prompts",
str(num_prompts), str(num_prompts),
"--num-warmups", "--num-warmups",
str(num_warmups), "2",
"--percentile-metrics", "--percentile-metrics",
"ttft,e2el,audio_rtf,audio_ttfp,audio_duration,audio_underrun", "ttft,e2el,audio_rtf,audio_ttfp,audio_duration,audio_underrun",
] ]
@@ -157,8 +152,7 @@ def build_bench_args(
if result_filename: if result_filename:
cmd += ["--result-filename", result_filename] cmd += ["--result-filename", result_filename]
passthrough = extra_cli_args[1:] if extra_cli_args[:1] == ["--"] else extra_cli_args cmd += extra_cli_args
cmd += passthrough
return cmd return cmd
@@ -231,12 +225,6 @@ def main() -> None:
parser.add_argument( parser.add_argument(
"--model", required=True, help="HuggingFace model ID (e.g. Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice)" "--model", required=True, help="HuggingFace model ID (e.g. Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice)"
) )
parser.add_argument(
"--served-model-name",
default=None,
help="Model name/path accepted by the running server. Use this when --model selects a registry entry "
"but the server was launched from a local bundle path.",
)
parser.add_argument("--task", default="all", help="Task type: voice_clone | default_voice | voice_design | all") parser.add_argument("--task", default="all", help="Task type: voice_clone | default_voice | voice_design | all")
parser.add_argument("--locale", default="en", choices=["en", "zh"]) parser.add_argument("--locale", default="en", choices=["en", "zh"])
parser.add_argument("--concurrency", type=int, nargs="+", default=[1, 4], metavar="N") parser.add_argument("--concurrency", type=int, nargs="+", default=[1, 4], metavar="N")
@@ -248,13 +236,6 @@ def main() -> None:
metavar="N", metavar="N",
help="Number of prompts per run. If one value, applied to all concurrency levels.", help="Number of prompts per run. If one value, applied to all concurrency levels.",
) )
parser.add_argument("--num-warmups", type=int, default=2, help="Warmup requests before each measured run")
parser.add_argument(
"--request-seed",
type=int,
default=None,
help="Optional model sampling seed added to every request body for reproducible quality runs",
)
parser.add_argument( parser.add_argument(
"--dataset-path", default=None, help="Root of seed-tts-eval dataset (required for voice_clone/default_voice)" "--dataset-path", default=None, help="Root of seed-tts-eval dataset (required for voice_clone/default_voice)"
) )
@@ -275,9 +256,6 @@ def main() -> None:
parser.add_argument("extra", nargs=argparse.REMAINDER, help="Extra args passed directly to vllm bench serve") parser.add_argument("extra", nargs=argparse.REMAINDER, help="Extra args passed directly to vllm bench serve")
args = parser.parse_args() args = parser.parse_args()
if args.num_warmups < 0:
parser.error("--num-warmups cannot be negative")
model_configs = load_model_configs(Path(args.model_configs)) model_configs = load_model_configs(Path(args.model_configs))
if args.model not in model_configs: if args.model not in model_configs:
known = "\n ".join(model_configs.keys()) known = "\n ".join(model_configs.keys())
@@ -330,9 +308,6 @@ def main() -> None:
result_filename=result_filename, result_filename=result_filename,
extra_cli_args=args.extra or [], extra_cli_args=args.extra or [],
output_len=args.output_len, output_len=args.output_len,
served_model_name=args.served_model_name,
num_warmups=args.num_warmups,
request_seed=args.request_seed,
) )
result = run_one_benchmark(cmd) result = run_one_benchmark(cmd)
if result is not None: if result is not None:
@@ -1,151 +0,0 @@
"""Benchmark CosyVoice3-style CUDA stream handoff strategies."""
import argparse
import json
import statistics
import time
import torch
from vllm_omni.platforms import current_omni_platform
def run_case(
*,
device: torch.device,
steps: int,
warmup_steps: int,
producer_cycles: int,
estimator_cycles: int,
use_stream_dependencies: bool,
) -> dict[str, float | int | str]:
caller_stream = torch.cuda.Stream(device=device)
estimator_stream = torch.cuda.Stream(device=device)
input_tensor = torch.zeros(1024, device=device)
output_tensor = torch.empty_like(input_tensor)
submission_ms = []
def step(measure: bool) -> None:
with torch.cuda.stream(caller_stream):
torch.cuda._sleep(producer_cycles)
input_tensor.add_(1)
start = time.perf_counter_ns()
if use_stream_dependencies:
estimator_stream.wait_stream(caller_stream)
else:
caller_stream.synchronize()
with torch.cuda.stream(estimator_stream):
torch.cuda._sleep(estimator_cycles)
output_tensor.copy_(input_tensor)
if use_stream_dependencies:
caller_stream.wait_stream(estimator_stream)
else:
estimator_stream.synchronize()
if measure:
submission_ms.append((time.perf_counter_ns() - start) / 1e6)
for _ in range(warmup_steps):
step(measure=False)
caller_stream.synchronize()
estimator_stream.synchronize()
input_tensor.zero_()
torch.accelerator.reset_peak_memory_stats(device)
initial_allocated = torch.accelerator.memory_allocated(device)
start_event = torch.cuda.Event(enable_timing=True)
end_event = torch.cuda.Event(enable_timing=True)
wall_start = time.perf_counter()
with torch.cuda.stream(caller_stream):
start_event.record()
for _ in range(steps):
step(measure=True)
with torch.cuda.stream(caller_stream):
end_event.record()
end_event.synchronize()
wall_ms = (time.perf_counter() - wall_start) * 1e3
peak_delta_bytes = torch.accelerator.max_memory_allocated(device) - initial_allocated
expected = torch.full_like(output_tensor, steps)
torch.testing.assert_close(output_tensor, expected, rtol=0, atol=0)
return {
"mode": "wait_stream" if use_stream_dependencies else "synchronize",
"steps": steps,
"submission_median_ms": statistics.median(submission_ms),
"submission_p95_ms": statistics.quantiles(submission_ms, n=20)[18],
"submission_mean_ms": statistics.mean(submission_ms),
"gpu_elapsed_ms": start_event.elapsed_time(end_event),
"wall_elapsed_ms": wall_ms,
"peak_allocation_delta_bytes": peak_delta_bytes,
"final_value": int(output_tensor[0].item()),
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--device", default="cuda:0")
parser.add_argument("--steps", type=int, default=100)
parser.add_argument("--warmup-steps", type=int, default=10)
parser.add_argument("--repeats", type=int, default=7)
parser.add_argument("--producer-cycles", type=int, default=1_000_000)
parser.add_argument("--estimator-cycles", type=int, default=2_000_000)
return parser.parse_args()
def main() -> None:
args = parse_args()
device = torch.device(args.device)
current_omni_platform.set_device(device)
torch.manual_seed(0)
old_results = []
new_results = []
for repeat in range(args.repeats):
common_args = {
"device": device,
"steps": args.steps,
"warmup_steps": args.warmup_steps,
"producer_cycles": args.producer_cycles,
"estimator_cycles": args.estimator_cycles,
}
if repeat % 2 == 0:
old_results.append(run_case(**common_args, use_stream_dependencies=False))
new_results.append(run_case(**common_args, use_stream_dependencies=True))
else:
new_results.append(run_case(**common_args, use_stream_dependencies=True))
old_results.append(run_case(**common_args, use_stream_dependencies=False))
old_submission_ms = statistics.median(result["submission_median_ms"] for result in old_results)
new_submission_ms = statistics.median(result["submission_median_ms"] for result in new_results)
old_gpu_ms = statistics.median(result["gpu_elapsed_ms"] for result in old_results)
new_gpu_ms = statistics.median(result["gpu_elapsed_ms"] for result in new_results)
print(
json.dumps(
{
"device": torch.cuda.get_device_name(device),
"torch": torch.__version__,
"cuda": torch.version.cuda,
"repeats": args.repeats,
"warmup_steps": args.warmup_steps,
"producer_cycles": args.producer_cycles,
"estimator_cycles": args.estimator_cycles,
"old_submission_median_ms": old_submission_ms,
"new_submission_median_ms": new_submission_ms,
"submission_median_speedup": old_submission_ms / new_submission_ms,
"old_gpu_elapsed_median_ms": old_gpu_ms,
"new_gpu_elapsed_median_ms": new_gpu_ms,
"gpu_elapsed_change_percent": (new_gpu_ms - old_gpu_ms) / old_gpu_ms * 100,
"parity": all(result["final_value"] == args.steps for result in old_results + new_results),
"old_runs": old_results,
"new_runs": new_results,
},
indent=2,
)
)
if __name__ == "__main__":
main()
+1 -41
View File
@@ -7,33 +7,11 @@
# is tracked here. # is tracked here.
models: models:
IndexTeam/IndexTTS-2.5:
supported_tasks: [voice_clone]
backend: openai-audio-speech
endpoint: /v1/audio/speech
trust_remote_code: true
task_extra_body:
voice_clone:
extra_params:
lang: en
text_normalization: true
# Audio8 TTS Preview: DualAR, 44.1 kHz. trust_remote_code stays false.
# Bench at 44.1 kHz: export VLLM_OMNI_BENCH_AUDIO_SAMPLE_RATE=44100, otherwise
# the streamed-PCM duration defaults to 24 kHz and RTF looks 1.84x too good.
Audio8/Audio8-TTS-Preview-0.6b:
supported_tasks: [default_voice, voice_clone]
backend: openai-audio-speech
endpoint: /v1/audio/speech
trust_remote_code: false
task_extra_body:
default_voice: {}
voice_clone: {}
# -CustomVoice checkpoints lack speaker_encoder weights, so voice_clone is # -CustomVoice checkpoints lack speaker_encoder weights, so voice_clone is
# NOT supported (an attempt raises ValueError from _extract_speaker_embedding # NOT supported (an attempt raises ValueError from _extract_speaker_embedding
# at model runtime). Use -Base for voice_clone. # at model runtime). Use -Base for voice_clone.
Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice: Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice:
supported_tasks: [default_voice] supported_tasks: [default_voice, voice_design]
backend: openai-audio-speech backend: openai-audio-speech
endpoint: /v1/audio/speech endpoint: /v1/audio/speech
trust_remote_code: true trust_remote_code: true
@@ -42,13 +20,6 @@ models:
voice: Vivian voice: Vivian
language: English language: English
task_type: CustomVoice task_type: CustomVoice
Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign:
supported_tasks: [voice_design]
backend: openai-audio-speech
endpoint: /v1/audio/speech
trust_remote_code: true
task_extra_body:
voice_design: voice_design:
task_type: VoiceDesign task_type: VoiceDesign
language: English language: English
@@ -150,14 +121,3 @@ models:
endpoint: /v1/audio/speech endpoint: /v1/audio/speech
task_extra_body: task_extra_body:
voice_design: {} voice_design: {}
# OmniVoice serves plain text-to-speech over /v1/audio/speech; voice cloning
# and voice design are offline-only for this model, so default_voice is the
# only task the server can answer.
k2-fsa/OmniVoice:
supported_tasks: [default_voice]
backend: openai-audio-speech
endpoint: /v1/audio/speech
trust_remote_code: true
task_extra_body:
default_voice: {}
+6 -21
View File
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# ruff: noqa # ruff: noqa
# code borrowed from https://github.com/pytorch/pytorch/blob/main/torch/utils/collect_env.py # code borrowed from https://github.com/pytorch/pytorch/blob/main/torch/utils/collect_env.py
@@ -494,16 +494,6 @@ def is_xnnpack_available():
def get_env_vars(): def get_env_vars():
try:
from vllm_omni.config.environment_variable_inventory import ENVIRONMENT_VARIABLE_INVENTORY
except Exception:
# Keep environment collection usable when an incomplete/broken Omni
# installation is the reason the user is running this diagnostic.
# Importing an Omni submodule executes package initialization, which
# may fail with RuntimeError, AssertionError, or OSError as well as
# ImportError when vLLM or the accelerator stack is broken.
ENVIRONMENT_VARIABLE_INVENTORY = {}
env_vars = "" env_vars = ""
secret_terms = ("secret", "token", "api", "access", "password") secret_terms = ("secret", "token", "api", "access", "password")
report_prefix = ( report_prefix = (
@@ -517,17 +507,12 @@ def get_env_vars():
"MKL_", "MKL_",
"NVIDIA", "NVIDIA",
) )
for k, v in sorted(os.environ.items()): for k, v in os.environ.items():
omni_classification = ENVIRONMENT_VARIABLE_INVENTORY.get(k) if any(term in k.lower() for term in secret_terms):
if (omni_classification is not None and omni_classification.redact_value) or any(
term in k.lower() for term in secret_terms
):
continue continue
if ( if k in environment_variables:
k in environment_variables env_vars = env_vars + "{}={}".format(k, v) + "\n"
or (omni_classification is not None and omni_classification.is_public_omni) if k.startswith(report_prefix):
or k.startswith(report_prefix)
):
env_vars = env_vars + "{}={}".format(k, v) + "\n" env_vars = env_vars + "{}={}".format(k, v) + "\n"
return env_vars return env_vars
+3 -6
View File
@@ -1,10 +1,7 @@
# Target vLLM version: v0.29.0 (tag @ 98dff2a81d74). # Target vllm version: v0.26.0 (releases/v0.26.0 branch).
# Use the matching release image and its coherent CUDA/PyTorch/FlashInfer # Keep VLLM_BASE_TAG aligned with the target vLLM release on every rebase.
# runtime. Do not reinstall a commit wheel or repair dependencies on top of
# an older base image at a release boundary. For a future unreleased target,
# restore an explicit wheel pin and validate its complete runtime stack.
ARG VLLM_BASE_IMAGE=vllm/vllm-openai ARG VLLM_BASE_IMAGE=vllm/vllm-openai
ARG VLLM_BASE_TAG=v0.29.0 ARG VLLM_BASE_TAG=v0.26.0
# We use registry-based layer caching for the dependencies, # We use registry-based layer caching for the dependencies,
# where the tag is determined based on the hash of the contents # where the tag is determined based on the hash of the contents
+1 -1
View File
@@ -1,4 +1,4 @@
ARG BASE_IMAGE=vllm/vllm-openai:v0.28.0 ARG BASE_IMAGE=vllm/vllm-openai:v0.26.0
FROM ${BASE_IMAGE} FROM ${BASE_IMAGE}
ARG COMMON_WORKDIR=/app ARG COMMON_WORKDIR=/app
+16 -5
View File
@@ -1,7 +1,20 @@
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
ARG VLLM_ASCEND_VERSION=v0.28.0 ARG VLLM_ASCEND_TAG=v0.26.0
ARG VLLM_ASCEND_DEVICE_SUFFIX= FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_VERSION}${VLLM_ASCEND_DEVICE_SUFFIX}
# WORKDIR /vllm-workspace/vllm
# RUN git fetch origin --tags && git checkout v0.18.0
# WORKDIR /vllm-workspace/vllm-ascend
# RUN git fetch origin releases/v0.18.0 && git checkout d781902ce9dbda8ab1e11bb0f2f0c1bc508fee7a
# # Install vllm-ascend
# # Append `libascend_hal.so` path (devlib) to LD_LIBRARY_PATH
# RUN export PIP_EXTRA_INDEX_URL=https://mirrors.huaweicloud.com/ascend/repos/pypi && \
# source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
# source /usr/local/Ascend/nnal/atb/set_env.sh && \
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
# python3 -m pip install -v -e /vllm-workspace/vllm-ascend/ --extra-index https://download.pytorch.org/whl/cpu/ && \
# python3 -m pip cache purge
ARG APP_DIR=/vllm-workspace/vllm-omni ARG APP_DIR=/vllm-workspace/vllm-omni
WORKDIR ${APP_DIR} WORKDIR ${APP_DIR}
@@ -11,14 +24,12 @@ RUN export PIP_EXTRA_INDEX_URL=https://mirrors.huaweicloud.com/ascend/repos/pypi
source /usr/local/Ascend/ascend-toolkit/set_env.sh && \ source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
source /usr/local/Ascend/nnal/atb/set_env.sh && \ source /usr/local/Ascend/nnal/atb/set_env.sh && \
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
python3 -m pip install -v "mindiesd==3.1.0" && \
python3 -m pip install -v -e /vllm-workspace/vllm-omni/ --no-build-isolation python3 -m pip install -v -e /vllm-workspace/vllm-omni/ --no-build-isolation
RUN python3 -m pip install -v --no-deps \ RUN python3 -m pip install -v --no-deps \
"stepaudio2-minicpmo>=0.1.1" \ "stepaudio2-minicpmo>=0.1.1" \
"step-audio2>=1.0.0" "step-audio2>=1.0.0"
ENV VLLM_TARGET_DEVICE=npu
ENV VLLM_WORKER_MULTIPROC_METHOD=spawn ENV VLLM_WORKER_MULTIPROC_METHOD=spawn
ENTRYPOINT [] ENTRYPOINT []
+35
View File
@@ -0,0 +1,35 @@
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
ARG VLLM_ASCEND_TAG=v0.26.0-a3
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
# WORKDIR /vllm-workspace/vllm
# RUN git fetch origin --tags && git checkout v0.18.0
# WORKDIR /vllm-workspace/vllm-ascend
# RUN git fetch origin releases/v0.18.0 && git checkout d781902ce9dbda8ab1e11bb0f2f0c1bc508fee7a
# # Install vllm-ascend
# # Append `libascend_hal.so` path (devlib) to LD_LIBRARY_PATH
# RUN export PIP_EXTRA_INDEX_URL=https://mirrors.huaweicloud.com/ascend/repos/pypi && \
# source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
# source /usr/local/Ascend/nnal/atb/set_env.sh && \
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
# python3 -m pip install -v -e /vllm-workspace/vllm-ascend/ --extra-index https://download.pytorch.org/whl/cpu/ && \
# python3 -m pip cache purge
ARG APP_DIR=/vllm-workspace/vllm-omni
WORKDIR ${APP_DIR}
COPY . .
RUN export PIP_EXTRA_INDEX_URL=https://mirrors.huaweicloud.com/ascend/repos/pypi && \
source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
source /usr/local/Ascend/nnal/atb/set_env.sh && \
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
python3 -m pip install -v -e /vllm-workspace/vllm-omni/ --no-build-isolation
RUN python3 -m pip install -v --no-deps \
"stepaudio2-minicpmo>=0.1.1" \
"step-audio2>=1.0.0"
ENV VLLM_WORKER_MULTIPROC_METHOD=spawn
ENTRYPOINT []
+8 -13
View File
@@ -1,10 +1,10 @@
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
ARG VLLM_ASCEND_VERSION=v0.28.0 ARG VLLM_ASCEND_TAG=v0.26.0
ARG VLLM_ASCEND_DEVICE_SUFFIX= FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_VERSION}${VLLM_ASCEND_DEVICE_SUFFIX}
ARG APP_DIR=/vllm-workspace/vllm-omni ARG APP_DIR=/vllm-workspace/vllm-omni
WORKDIR ${APP_DIR} WORKDIR ${APP_DIR}
COPY . .
RUN sed -i "s@^\(deb.*\)https\?://[a-z0-9.-]*\.ubuntu\.com@\1http://cache-service.nginx-pypi-cache.svc.cluster.local:8081@g" /etc/apt/sources.list RUN sed -i "s@^\(deb.*\)https\?://[a-z0-9.-]*\.ubuntu\.com@\1http://cache-service.nginx-pypi-cache.svc.cluster.local:8081@g" /etc/apt/sources.list
@@ -22,9 +22,11 @@ RUN apt-get update && \
rm -rf /var/cache/apt/* && \ rm -rf /var/cache/apt/* && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
RUN python3 -m pip install -U pip && \ RUN source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
source /usr/local/Ascend/nnal/atb/set_env.sh && \
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
python3 -m pip install -U pip && \
python3 -m pip install -v \ python3 -m pip install -v \
"mindiesd==3.1.0" \
"pytest>=7.0.0" \ "pytest>=7.0.0" \
"pytest-asyncio>=0.21.0" \ "pytest-asyncio>=0.21.0" \
"pytest-cov>=4.0.0" \ "pytest-cov>=4.0.0" \
@@ -46,13 +48,7 @@ RUN python3 -m pip install -U pip && \
"datasets>=3.0.0" \ "datasets>=3.0.0" \
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \ --index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \ --trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
--default-timeout=60 --default-timeout=60 && \
COPY . .
RUN source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
source /usr/local/Ascend/nnal/atb/set_env.sh && \
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
python3 -m pip install -v -e /vllm-workspace/vllm-omni/ --no-build-isolation \ python3 -m pip install -v -e /vllm-workspace/vllm-omni/ --no-build-isolation \
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \ --index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \ --trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
@@ -69,7 +65,6 @@ RUN python3 -m pip install -v --no-deps \
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \ --trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
--default-timeout=60 --default-timeout=60
ENV VLLM_TARGET_DEVICE=npu
ENV VLLM_WORKER_MULTIPROC_METHOD=spawn ENV VLLM_WORKER_MULTIPROC_METHOD=spawn
ENTRYPOINT [] ENTRYPOINT []
+71
View File
@@ -0,0 +1,71 @@
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
ARG VLLM_ASCEND_TAG=v0.26.0-a3
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
ARG APP_DIR=/vllm-workspace/vllm-omni
WORKDIR ${APP_DIR}
COPY . .
RUN sed -i "s@^\(deb.*\)https\?://[a-z0-9.-]*\.ubuntu\.com@\1http://cache-service.nginx-pypi-cache.svc.cluster.local:8081@g" /etc/apt/sources.list
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ffmpeg \
libgl1 \
libglib2.0-0 \
libgomp1 \
libsm6 \
libxext6 \
libxrender1 \
libxcb1 \
espeak-ng && \
rm -rf /var/cache/apt/* && \
rm -rf /var/lib/apt/lists/*
RUN source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
source /usr/local/Ascend/nnal/atb/set_env.sh && \
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
python3 -m pip install -U pip && \
python3 -m pip install -v \
"pytest>=7.0.0" \
"pytest-asyncio>=0.21.0" \
"pytest-cov>=4.0.0" \
"pytest-mock>=3.10.0" \
"openpyxl>=3.0.0" \
"psutil>=7.2.0" \
"soundfile>=0.13.1" \
"pyttsx3>=2.99" \
"opencc==1.4.1" \
"opencv-python>=4.12.0.88" \
"imageio[ffmpeg]>=0.6.0" \
"jiwer>=3.0.0" \
"zhon>=2.0.0" \
"zhconv>=1.4.2" \
"funasr>=1.0.0" \
"hyperpyyaml>=1.2.0" \
"onnx>=1.16.0" \
"librosa>=0.10.0" \
"torchmetrics>=1.9.0" \
"datasets>=3.0.0" \
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
--default-timeout=60 && \
python3 -m pip install -v -e /vllm-workspace/vllm-omni/ --no-build-isolation \
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
--default-timeout=60
# MiniCPM-o 4.5 Stage-2 vocoder assets: s3tokenizer plus flashcosyvoice, which
# is shipped by step-audio2. --no-deps keeps pip from replacing the Ascend torch
# build and from shadowing onnxruntime-cann with the vanilla onnxruntime wheel;
# the deps these two actually need are installed above.
RUN python3 -m pip install -v --no-deps \
"s3tokenizer>=0.3.0" \
"step-audio2>=1.0.0" \
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
--default-timeout=60
ENV VLLM_WORKER_MULTIPROC_METHOD=spawn
ENTRYPOINT []
+2 -9
View File
@@ -1,4 +1,4 @@
ARG BASE_IMAGE=vllm/vllm-openai-rocm:v0.28.0 ARG BASE_IMAGE=vllm/vllm-openai-rocm:v0.26.0
FROM ${BASE_IMAGE} AS base FROM ${BASE_IMAGE} AS base
# Declare a variable to know if we want to use the nightly build or the stable build. # 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 swap over to use stable release ASAP.
# We should avoid relying on custom commits. # We should avoid relying on custom commits.
ARG USE_NIGHTLY_BUILD=0 ARG USE_NIGHTLY_BUILD=0
ARG VLLM_VERSION_OR_COMMIT_HASH=v0.28.0 ARG VLLM_VERSION_OR_COMMIT_HASH=v0.26.0
ARG ARG_PYTORCH_ROCM_ARCH ARG ARG_PYTORCH_ROCM_ARCH
ENV PYTORCH_ROCM_ARCH=${ARG_PYTORCH_ROCM_ARCH:-${PYTORCH_ROCM_ARCH}} ENV PYTORCH_ROCM_ARCH=${ARG_PYTORCH_ROCM_ARCH:-${PYTORCH_ROCM_ARCH}}
@@ -79,16 +79,9 @@ RUN printf '%s\n' \
' os._exit(_exit_code)' \ ' os._exit(_exit_code)' \
> ${COMMON_WORKDIR}/vllm-omni/conftest.py > ${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 && \ RUN cd ${COMMON_WORKDIR}/vllm-omni && \
uv pip install --python "$(python3 -c 'import sys; print(sys.executable)')" \ uv pip install --python "$(python3 -c 'import sys; print(sys.executable)')" \
--no-cache-dir ".[dev]" --no-build-isolation && \ --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" python3 -c "from torchcodec.decoders import VideoDecoder"
RUN ln -sf /usr/bin/python3 /usr/bin/python RUN ln -sf /usr/bin/python3 /usr/bin/python
+136 -30
View File
@@ -1,33 +1,140 @@
# Target vLLM version: v0.28.0. Keep VLLM_VERSION aligned with the target vLLM # Argument to configure vllm base image if pre-built
# release on every rebase. ARG VLLM_BASE=vllm-base
#
# This file layers vLLM-Omni on top of the published upstream XPU image, which FROM intel/deep-learning-essentials:2025.3.2-0-devel-ubuntu24.04 AS vllm-base
# already ships a self-consistent XPU runtime (torch-xpu, oneCCL/UCX+NIXL, the
# pinned UMD/compute-runtime stack, and vLLM itself) built from WORKDIR /workspace/
# https://github.com/vllm-project/vllm/blob/main/docker/Dockerfile.xpu. Docker
# pulls that base automatically, so there is nothing to build here beyond the ARG PYTHON_VERSION=3.12
# Omni layers. Mirrors how docker/Dockerfile.ci consumes vllm/vllm-openai. ARG PIP_EXTRA_INDEX_URL="https://download.pytorch.org/whl/xpu"
#
# If the tag does not exist yet (targeting an unreleased vLLM commit), build the RUN wget -O- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | gpg --dearmor | tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null && \
# base from upstream's own Dockerfile and point VLLM_BASE at the result: echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | tee /etc/apt/sources.list.d/oneAPI.list
#
# git clone --depth 1 --branch $VLLM_VERSION https://github.com/vllm-project/vllm /tmp/vllm RUN apt clean && apt-get update -y && \
# docker build -t vllm-openai-xpu:local --target vllm-openai \ apt-get install -y --no-install-recommends --fix-missing \
# -f /tmp/vllm/docker/Dockerfile.xpu /tmp/vllm curl \
# docker build -f docker/Dockerfile.xpu --build-arg VLLM_BASE=vllm-openai-xpu:local . ffmpeg \
# git \
# .buildkite/intel/scripts/run-xpu-test.sh automates that pull-then-build fallback. libsndfile1 \
ARG VLLM_BASE_IMAGE=vllm/vllm-openai-xpu libsm6 \
ARG VLLM_VERSION=v0.28.0 libxext6 \
ARG VLLM_BASE=${VLLM_BASE_IMAGE}:${VLLM_VERSION} libgl1 \
lsb-release \
libaio-dev \
numactl \
wget \
vim \
python3.12 \
python3.12-dev \
python3-pip
# Add oneAPI repo, pin oneAPI to 2025.3, then install pinned packages in one layer.
RUN wget -O- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | gpg --dearmor | tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null && \
echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | tee /etc/apt/sources.list.d/oneAPI.list && \
printf '%s\n' \
'Package: intel-oneapi-* intel-deep-learning-essentials* intel-pti*' \
'Pin: version 2025.3*' \
'Pin-Priority: 1001' \
> /etc/apt/preferences.d/oneapi-2025.3.pref && \
apt-get update -y && \
apt-get install -y --no-install-recommends \
intel-oneapi-compiler-dpcpp-cpp-2025.3 \
intel-oneapi-mkl-devel-2025.3 \
intel-oneapi-dnnl-devel-2025.3 && \
rm -rf /var/lib/apt/lists/*
# Install UMD with fixed version
RUN mkdir neo && \
cd neo && \
wget https://github.com/intel/intel-graphics-compiler/releases/download/v2.24.8/intel-igc-core-2_2.24.8+20344_amd64.deb && \
wget https://github.com/intel/intel-graphics-compiler/releases/download/v2.24.8/intel-igc-opencl-2_2.24.8+20344_amd64.deb && \
wget https://github.com/intel/compute-runtime/releases/download/25.48.36300.8/intel-ocloc_25.48.36300.8-0_amd64.deb && \
wget https://github.com/intel/compute-runtime/releases/download/25.48.36300.8/intel-opencl-icd_25.48.36300.8-0_amd64.deb && \
wget https://github.com/intel/compute-runtime/releases/download/25.48.36300.8/libigdgmm12_22.8.2_amd64.deb && \
wget https://github.com/intel/compute-runtime/releases/download/25.48.36300.8/libze-intel-gpu1_25.48.36300.8-0_amd64.deb && \
wget https://github.com/oneapi-src/level-zero/releases/download/v1.26.0/level-zero_1.26.0+u24.04_amd64.deb && \
dpkg -i *.deb && \
cd .. && \
rm -rf neo
ENV PATH="/root/.local/bin:$PATH"
ENV VIRTUAL_ENV="/opt/venv"
ENV UV_PYTHON_INSTALL_DIR=/opt/uv/python
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
RUN uv venv --python ${PYTHON_VERSION} --seed ${VIRTUAL_ENV}
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# This oneccl contains the BMG support which is not the case for default version of oneapi 2025.3.
ARG ONECCL_INSTALLER="intel-oneccl-2021.15.9.14_offline.sh"
RUN wget "https://github.com/uxlfoundation/oneCCL/releases/download/2021.15.9/${ONECCL_INSTALLER}" && \
bash "${ONECCL_INSTALLER}" -a --silent --eula accept && \
rm "${ONECCL_INSTALLER}" && \
echo "source /opt/intel/oneapi/setvars.sh --force" >> /root/.bashrc && \
echo "source /opt/intel/oneapi/ccl/2021.15/env/vars.sh --force" >> /root/.bashrc
RUN rm -f /opt/intel/oneapi/ccl/latest && \
ln -s /opt/intel/oneapi/ccl/2021.15 /opt/intel/oneapi/ccl/latest
SHELL ["bash", "-c"]
CMD ["bash", "-c", "source /root/.bashrc && exec bash"]
WORKDIR /workspace/
ENV UV_HTTP_TIMEOUT=500
# Configure package index for XPU
ENV PIP_EXTRA_INDEX_URL=${PIP_EXTRA_INDEX_URL}
ENV UV_EXTRA_INDEX_URL=${PIP_EXTRA_INDEX_URL}
ENV UV_INDEX_STRATEGY="unsafe-best-match"
ENV UV_LINK_MODE="copy"
ARG VLLM_VERSION=v0.26.0
RUN git clone -b ${VLLM_VERSION} https://github.com/vllm-project/vllm
WORKDIR /workspace/vllm
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install --upgrade pip && \
uv pip install -r requirements/xpu.txt && \
uv pip install setuptools_scm &&\
uv pip install grpcio-tools protobuf nanobind && \
source /opt/intel/oneapi/setvars.sh --force && \
source /opt/intel/oneapi/ccl/2021.15/env/vars.sh --force && \
export CMAKE_PREFIX_PATH="$(python3 -c 'import site; print(site.getsitepackages()[0])'):${CMAKE_PREFIX_PATH}"
ENV LD_LIBRARY_PATH="/opt/intel/oneapi/ccl/2021.15/lib:$LD_LIBRARY_PATH:/usr/local/lib/"
ENV VLLM_TARGET_DEVICE=xpu
ENV VLLM_WORKER_MULTIPROC_METHOD=spawn
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip uninstall triton triton-xpu && \
uv pip install triton-xpu==3.7.1 && \
uv pip install --no-build-isolation --no-deps .
CMD ["/bin/bash"]
FROM vllm-base AS vllm-openai
# install nixl from source code
RUN uv pip install nixl
# install additional dependencies for openai api server
RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install accelerate hf_transfer pytest pytest_asyncio lm_eval[api] modelscope
# install development dependencies (for testing)
RUN uv pip install -e tests/vllm_test_utils
# ensure vllm is properly installed
RUN python -c "import vllm, inspect; print(vllm.__file__)"
RUN uv pip show vllm
CMD ["/bin/bash"]
ENTRYPOINT []
FROM ${VLLM_BASE} AS vllm-omni FROM ${VLLM_BASE} AS vllm-omni
# The base image ships ENTRYPOINT ["vllm", "serve"]; clear it so this stage stays
# usable as a plain shell/test container regardless of which VLLM_BASE was used.
ENTRYPOINT []
CMD ["/bin/bash"]
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y espeak-ng ffmpeg git jq && \ apt-get install -y espeak-ng ffmpeg git jq && \
apt-get clean && \ apt-get clean && \
@@ -37,13 +144,12 @@ WORKDIR /workspace/vllm-omni
COPY . . COPY . .
ENV VLLM_OMNI_TARGET_DEVICE=xpu ENV VLLM_OMNI_TARGET_DEVICE=xpu
# Keep the upstream oneCCL packages paired with the XPU torch runtime.
RUN --mount=type=cache,target=/root/.cache/uv \ RUN --mount=type=cache,target=/root/.cache/uv \
uv pip install --no-build-isolation \ uv pip install --no-build-isolation \
--extra-index-url https://mirrors.ustc.edu.cn/pypi/simple/ \ --extra-index-url https://mirrors.ustc.edu.cn/pypi/simple/ \
".[dev]" && \ ".[dev]" && \
uv pip uninstall triton && \ uv pip uninstall triton oneccl oneccl-devel && \
uv pip install triton-xpu==3.7.2 --reinstall uv pip install triton-xpu==3.7.1 --reinstall
FROM vllm-omni AS vllm-omni-openai FROM vllm-omni AS vllm-omni-openai
+13 -44
View File
@@ -5,23 +5,14 @@ nav:
- getting_started/quickstart.md - getting_started/quickstart.md
- getting_started/installation/* - getting_started/installation/*
- Serving: - Serving:
- API Server: - OpenAI-Compatible API:
- Overview: serving/README.md - Chat Completions API: serving/chat_completions_api.md
- Core APIs: - Image Generation: serving/image_generation_api.md
- Chat Completions: serving/chat_completions_api.md - Image Edit: serving/image_edit_api.md
- Text to Speech: serving/speech_api.md - Text to Speech: serving/speech_api.md
- Audio Generation: serving/audio_generate_api.md - Videos: serving/videos_api.md
- Image Generation: serving/image_generation_api.md - Audio Generation: serving/audio_generate_api.md
- Image Edit: serving/image_edit_api.md - Streaming Video Input: serving/video_stream_api.md
- Videos: serving/videos_api.md
- Streaming and Realtime:
- Realtime Audio: serving/realtime_api.md
- Full Duplex: serving/full_duplex_api.md
- Realtime Duplex Reference: serving/realtime_duplex_api.md
- Streaming Video Input: serving/video_stream_api.md
- Streaming Video Output: serving/streaming_video_output_api.md
- OpenPI Robot Policy: serving/openpi_api.md
- Standalone Experimental Servers: serving/standalone_servers.md
- Examples: - Examples:
- examples/README.md - examples/README.md
- Offline Inference: - Offline Inference:
@@ -33,6 +24,7 @@ nav:
- Text-To-Speech: user_guide/examples/offline_inference/text_to_speech.md - Text-To-Speech: user_guide/examples/offline_inference/text_to_speech.md
- Text-To-Video: user_guide/examples/offline_inference/text_to_video.md - Text-To-Video: user_guide/examples/offline_inference/text_to_video.md
- X-To-Text: user_guide/examples/offline_inference/x_to_text.md - X-To-Text: user_guide/examples/offline_inference/x_to_text.md
- X-To-Video-Audio: user_guide/examples/offline_inference/x_to_video_audio.md
- Online Serving: - Online Serving:
- Image-To-Image: user_guide/examples/online_serving/image_to_image.md - Image-To-Image: user_guide/examples/online_serving/image_to_image.md
- Image-To-Video: user_guide/examples/online_serving/image_to_video.md - Image-To-Video: user_guide/examples/online_serving/image_to_video.md
@@ -51,7 +43,6 @@ nav:
- Overview: features/README.md - Overview: features/README.md
- Runtime and Stage Execution: - Runtime and Stage Execution:
- Execution Modes and Streaming: user_guide/diffusion/execution_modes.md - Execution Modes and Streaming: user_guide/diffusion/execution_modes.md
- Prefill-Decode Disaggregation (experimental): features/pd_disaggregation.md
- Sleep Mode: features/sleep_mode.md - Sleep Mode: features/sleep_mode.md
- Quantization: - Quantization:
- Overview: user_guide/quantization/overview.md - Overview: user_guide/quantization/overview.md
@@ -62,20 +53,14 @@ nav:
- BitsAndBytes W4: user_guide/quantization/bitsandbytes.md - BitsAndBytes W4: user_guide/quantization/bitsandbytes.md
- MXFP8 W8A8: user_guide/quantization/mxfp8.md - MXFP8 W8A8: user_guide/quantization/mxfp8.md
- MXFP4 W4A4: user_guide/quantization/mxfp4.md - MXFP4 W4A4: user_guide/quantization/mxfp4.md
- SVDQuant W4A4: user_guide/quantization/svdquant.md
- ModelOpt: user_guide/quantization/modelopt.md - ModelOpt: user_guide/quantization/modelopt.md
- AutoRound: user_guide/quantization/autoround.md - AutoRound: user_guide/quantization/autoround.md
- msModelSlim: user_guide/quantization/msmodelslim.md - msModelSlim: user_guide/quantization/msmodelslim.md
- TorchAO: user_guide/quantization/torchao.md
- Diffusion Acceleration: - Diffusion Acceleration:
- Overview and Compatibility: - Overview and Compatibility:
- Overview: user_guide/diffusion_features.md - Overview: user_guide/diffusion_features.md
- Feature Compatibility: user_guide/feature_compatibility.md - Feature Compatibility: user_guide/feature_compatibility.md
- CPU Offloading: - CPU Offloading: user_guide/diffusion/cpu_offload.md
- Overview: user_guide/diffusion/cpu_offload.md
- Model-Level: user_guide/diffusion/offloader/module_offload.md
- Layerwise: user_guide/diffusion/offloader/layerwise_offload.md
- Distributed Layerwise: user_guide/diffusion/offloader/distributed_layerwise_offload.md
- Cache Acceleration: - Cache Acceleration:
- TeaCache: user_guide/diffusion/cache_acceleration/teacache.md - TeaCache: user_guide/diffusion/cache_acceleration/teacache.md
- Cache-DiT: user_guide/diffusion/cache_acceleration/cache_dit.md - Cache-DiT: user_guide/diffusion/cache_acceleration/cache_dit.md
@@ -87,13 +72,7 @@ nav:
- Sequence Parallel: user_guide/diffusion/parallelism/sequence_parallel.md - Sequence Parallel: user_guide/diffusion/parallelism/sequence_parallel.md
- Tensor Parallel: user_guide/diffusion/parallelism/tensor_parallel.md - Tensor Parallel: user_guide/diffusion/parallelism/tensor_parallel.md
- VAE Parallelism: user_guide/diffusion/parallelism/vae_parallelism.md - VAE Parallelism: user_guide/diffusion/parallelism/vae_parallelism.md
- Attention Backends: - Attention Backends: user_guide/diffusion/attention_backends.md
- Overview: user_guide/diffusion/attention_backends.md
- Dense Backends: user_guide/diffusion/attention_backends/dense_backends.md
- TRTLLM: user_guide/diffusion/attention_backends/trtllm.md
- SageAttention: user_guide/diffusion/attention_backends/sage.md
- Hugging Face Hub: user_guide/diffusion/attention_backends/huggingface_hub.md
- RainFusion: user_guide/diffusion/attention_backends/rainfusion.md
- Regional Compilation: user_guide/diffusion/regional_compilation.md - Regional Compilation: user_guide/diffusion/regional_compilation.md
- Frame Interpolation: user_guide/diffusion/frame_interpolation.md - Frame Interpolation: user_guide/diffusion/frame_interpolation.md
- Startup and Loading: user_guide/diffusion/startup_and_loading.md - Startup and Loading: user_guide/diffusion/startup_and_loading.md
@@ -123,13 +102,10 @@ nav:
- Feature Design: - Feature Design:
- Runtime and Stage Execution: - Runtime and Stage Execution:
- Disaggregated Inference: design/feature/disaggregated_inference.md - Disaggregated Inference: design/feature/disaggregated_inference.md
- Host Weight Runtime: design/feature/host_weight_runtime.md
- Async Chunk: design/feature/async_chunk.md - Async Chunk: design/feature/async_chunk.md
- Async Diffusion Output: design/feature/async_diffusion_output.md - Async Diffusion Output: design/feature/async_diffusion_output.md
- Async Omni Output Materialization: design/feature/omni_async_output_materialization.md - Async Omni Output Materialization: design/feature/omni_async_output_materialization.md
- Prefill/Decode Phase Contract: design/feature/preprocess_phase_contract.md
- Automatic Prefix Caching: design/feature/prefix_caching.md - Automatic Prefix Caching: design/feature/prefix_caching.md
- Realtime AR-Diffusion: design/feature/realtime_ar_diffusion.md
- Communication: - Communication:
- OmniConnector Implementations: - OmniConnector Implementations:
- Mooncake Store Connector: design/feature/omni_connectors/mooncake_store_connector.md - Mooncake Store Connector: design/feature/omni_connectors/mooncake_store_connector.md
@@ -149,16 +125,11 @@ nav:
- Tensor Parallel: design/feature/tensor_parallel.md - Tensor Parallel: design/feature/tensor_parallel.md
- VAE Patch Parallelism: design/feature/vae_parallel.md - VAE Patch Parallelism: design/feature/vae_parallel.md
- Attention Optimization: - Attention Optimization:
- Backend Selection: design/feature/attention_backend_selection.md
- Skip-Softmax: design/feature/skip_softmax.md - Skip-Softmax: design/feature/skip_softmax.md
- CPU Offloading:
- Overview: design/feature/offloader/README.md
- Model-Level: design/feature/offloader/module_offload.md
- Layerwise: design/feature/offloader/layerwise_offload.md
- Distributed Layerwise: design/feature/offloader/distributed_layerwise_offload.md
- Cache-DiT: design/feature/cache_dit.md - Cache-DiT: design/feature/cache_dit.md
- TeaCache: design/feature/teacache.md - TeaCache: design/feature/teacache.md
- Diffusion Continuous Batching: design/feature/diffusion_continuous_batching.md - Diffusion Continuous Batching: design/feature/diffusion_continuous_batching.md
- Distributed Layerwise Offload: design/feature/distributed_layerwise_offload.md
- Infrastructure and Performance: - Infrastructure and Performance:
- Prometheus Metrics: design/metrics.md - Prometheus Metrics: design/metrics.md
- Speech Generation Performance Optimizations: design/qwen3_omni_tts_performance_optimization.md - Speech Generation Performance Optimizations: design/qwen3_omni_tts_performance_optimization.md
@@ -181,7 +152,6 @@ nav:
- Offloader: design/module/diffusion/offloader.md - Offloader: design/module/diffusion/offloader.md
- Execution Platforms: design/module/execution_platforms.md - Execution Platforms: design/module/execution_platforms.md
- Cache Management: design/module/cache_management.md - Cache Management: design/module/cache_management.md
- Host Weight Runtime: design/module/host_weight_runtime.md
- Quantization: design/module/quantization.md - Quantization: design/module/quantization.md
- Observability: design/module/observability.md - Observability: design/module/observability.md
- Profiling: design/module/profiling.md - Profiling: design/module/profiling.md
@@ -193,8 +163,7 @@ nav:
- CLI Reference: cli - CLI Reference: cli
- Community: - Community:
- Governance: community/governance.md - Governance: community/governance.md
- Contact Us: community/contact_us.md - community/*
- Community Meetings: community/meetings.md
- Slack: https://slack.vllm.ai - Slack: https://slack.vllm.ai
- Blog: https://blog.vllm.ai - Blog: https://blog.vllm.ai
- Forum: https://discuss.vllm.ai - Forum: https://discuss.vllm.ai
+5 -4
View File
@@ -23,6 +23,7 @@ Easy, fast, and cheap omni-modality model serving for everyone
<a class="github-button" href="https://github.com/vllm-project/vllm-omni/fork" data-show-count="true" data-icon="octicon-repo-forked" data-size="large" aria-label="Fork">Fork</a> <a class="github-button" href="https://github.com/vllm-project/vllm-omni/fork" data-show-count="true" data-icon="octicon-repo-forked" data-size="large" aria-label="Fork">Fork</a>
</p> </p>
## About ## About
[vLLM](https://github.com/vllm-project/vllm) was originally designed to support large language models for text-based autoregressive generation tasks. vLLM-Omni is a framework that extends its support for omni-modality model inference and serving: [vLLM](https://github.com/vllm-project/vllm) was originally designed to support large language models for text-based autoregressive generation tasks. vLLM-Omni is a framework that extends its support for omni-modality model inference and serving:
@@ -51,14 +52,14 @@ vLLM-Omni is flexible and easy to use with:
- Tensor, pipeline, data and expert parallelism support for distributed inference - Tensor, pipeline, data and expert parallelism support for distributed inference
- Streaming outputs - Streaming outputs
- OpenAI-compatible API server - OpenAI-compatible API server
- [Full-duplex realtime serving](serving/realtime_duplex_api.md) with streaming audio input and output - Full-duplex realtime serving with streaming audio input and output (experimental)
vLLM-Omni seamlessly supports most popular open-source models on HuggingFace, including: vLLM-Omni seamlessly supports most popular open-source models on HuggingFace, including:
- Omni-modality models (e.g. Qwen3-Omni, MiniCPM-o 4.5, Cosmos3, HunyuanImage, BAGEL) - Omni-modality models (e.g. Qwen3-Omni, MiniCPM-o 4.5, Cosmos3, HunyuanImage, BAGEL)
- TTS models (e.g. Qwen3-TTS, IndexTTS 2.5, dots.tts, CosyVoice3) - TTS models (e.g. Qwen3-TTS, VoxCPM2, Ming-Omni-TTS, CosyVoice3)
- Diffusion models — image, video, and audio generation (e.g. MiniMax H3, LTX-2.5, SANA-Video, Wan2.2) - Diffusion models — image, video, and audio generation (e.g. MiniMax H3, Qwen-Image, Wan2.2, FLUX)
- Robot-policy and action models (e.g. π0, GR00T-N1.7, DreamZero-DROID, InternVLA-A1) - Robot-policy and action models (e.g. GR00T-N1.7, DreamZero-DROID, InternVLA-A1, Cosmos3 action policy)
For more information, checkout the following: For more information, checkout the following:
-30
View File
@@ -15,36 +15,6 @@ Main entry points for vLLM-Omni inference and serving.
- [vllm_omni.entrypoints.omni_base.OmniBase][] - [vllm_omni.entrypoints.omni_base.OmniBase][]
- [vllm_omni.entrypoints.pd_utils.PDDisaggregationMixin][] - [vllm_omni.entrypoints.pd_utils.PDDisaggregationMixin][]
## Clients
Client libraries that talk to a running vLLM-Omni server (the full-duplex Realtime client and its per-model session presets).
- [vllm_omni.clients.duplex.AudioDelta][]
- [vllm_omni.clients.duplex.AudioFormat][]
- [vllm_omni.clients.duplex.ConnectionResumed][]
- [vllm_omni.clients.duplex.DuplexClient][]
- [vllm_omni.clients.duplex.DuplexClientError][]
- [vllm_omni.clients.duplex.DuplexConnectionError][]
- [vllm_omni.clients.duplex.DuplexEvent][]
- [vllm_omni.clients.duplex.DuplexProtocolError][]
- [vllm_omni.clients.duplex.DuplexSessionClosedError][]
- [vllm_omni.clients.duplex.ErrorEvent][]
- [vllm_omni.clients.duplex.EventCollector][]
- [vllm_omni.clients.duplex.ListenDecision][]
- [vllm_omni.clients.duplex.ReconnectPolicy][]
- [vllm_omni.clients.duplex.ResponseCreated][]
- [vllm_omni.clients.duplex.ResponseDone][]
- [vllm_omni.clients.duplex.ResponseHandle][]
- [vllm_omni.clients.duplex.SessionClosed][]
- [vllm_omni.clients.duplex.SessionConfig][]
- [vllm_omni.clients.duplex.SessionCreated][]
- [vllm_omni.clients.duplex.SessionExpired][]
- [vllm_omni.clients.duplex.SessionResumed][]
- [vllm_omni.clients.duplex.SpeakDecision][]
- [vllm_omni.clients.duplex.TextDelta][]
- [vllm_omni.clients.duplex.TranscriptDelta][]
- [vllm_omni.clients.duplex.WebSocketTransport][]
## Inputs ## Inputs
Input data structures for multi-modal inputs. Input data structures for multi-modal inputs.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

After

Width:  |  Height:  |  Size: 260 KiB

+1 -7
View File
@@ -2,11 +2,6 @@
The CLI for vLLM-Omni inherits from vllm with some additional arguments. The CLI for vLLM-Omni inherits from vllm with some additional arguments.
Environment variables are process configuration rather than CLI-only options.
See the [Environment Variables](../configuration/environment_variables.md)
reference for ownership, precedence, evaluation time, and per-stage `env`
scoping.
## serve ## serve
Starts the vLLM-Omni OpenAI Compatible API server. Starts the vLLM-Omni OpenAI Compatible API server.
@@ -44,5 +39,4 @@ vllm bench serve --omni \
--num-prompts 5 --num-prompts 5
``` ```
See [vllm bench serve](./bench/serve.md) for serving benchmark arguments and See [vllm bench serve](./bench/serve.md) for the full reference of all available arguments.
dataset-specific examples, including OmniInteract native-duplex sessions.
+8 -154
View File
@@ -1,20 +1,16 @@
# vLLM-Omni Benchmark CLI Guide # vLLM-Omni Benchmark CLI Guide
The vllm bench command launches the vLLM-Omni benchmark to evaluate the performance of multimodal models. The vllm bench command launches the vLLM-Omni benchmark to evaluate the performance of multimodal models.
## Notes ## Notes
vLLM-Omni registers the `openai-chat-omni`, `openai-audio-speech`, `openai-image-edits-omni`, and `daily-omni` serving benchmark backends.
vLLM-Omni registers the `openai-chat-omni`, `openai-audio-speech`, `openai-image-edits-omni`, `daily-omni`, and `openai-realtime-duplex` serving benchmark backends. It also adds the `omniinteract` dataset.
## Basic Parameter Description ## Basic Parameter Description
You can use `vllm bench serve --omni --help=all` to get descriptions of all parameters. The commonly used parameters are described below: You can use `vllm bench serve --omni --help=all` to get descriptions of all parameters. The commonly used parameters are described below:
- `--omni` - `--omni`
Enable Omni (multimodal) mode, supporting multimodal inputs and outputs such as images, videos, and audio. Enable Omni (multimodal) mode, supporting multimodal inputs and outputs such as images, videos, and audio.
- `--backend` - `--backend`
Specify the backend adapter. vLLM-Omni adds `openai-chat-omni`, `openai-audio-speech`, `openai-image-edits-omni`, `daily-omni`, and `openai-realtime-duplex` to the upstream vLLM backend choices. Specify the backend adapter. vLLM-Omni adds `openai-chat-omni`, `openai-audio-speech`, `openai-image-edits-omni`, and `daily-omni` to the upstream vLLM backend choices.
- `--model` - `--model`
The model identifier to load, filled according to the models supported by vLLM-Omni. The model identifier to load, filled according to the models supported by vLLM-Omni.
@@ -23,7 +19,7 @@ You can use `vllm bench serve --omni --help=all` to get descriptions of all para
The API endpoint exposed externally, to which clients send their requests. The API endpoint exposed externally, to which clients send their requests.
- `--dataset-name` - `--dataset-name`
The name of the dataset used; random-mm indicates generating random multimodal inputs (images, videos, audio), while `omniinteract` replays official OmniInteract videos. The name of the dataset used; random-mm indicates generating random multimodal inputs (images, videos, audio).
- `--num-prompts` - `--num-prompts`
The total number of requests to send, an integer. The total number of requests to send, an integer.
@@ -81,8 +77,8 @@ Specify to save benchmark results to a json file
- `--random-prefix-len` - `--random-prefix-len`
Number of fixed prefix tokens before the random context in a request. Number of fixed prefix tokens before the random context in a request.
The total input length is the sum of random-prefix-len and a random The total input length is the sum of random-prefix-len and a random
context length sampled from [input_len *(1 - range_ratio), context length sampled from [input_len * (1 - range_ratio),
input_len* (1 + range_ratio)].Only the random and random-mm modes input_len * (1 + range_ratio)].Only the random and random-mm modes
support this parameter. support this parameter.
- `--random-input-len` - `--random-input-len`
@@ -95,7 +91,7 @@ Specify to save benchmark results to a json file
Range ratio for sampling input/output length, Range ratio for sampling input/output length,
used only for random sampling. Must be in the range [0, 1) to define used only for random sampling. Must be in the range [0, 1) to define
a symmetric sampling range a symmetric sampling range
[length *(1 - range_ratio), length* (1 + range_ratio)]. [length * (1 - range_ratio), length * (1 + range_ratio)].
Only the random and random-mm modes support this parameter. Only the random and random-mm modes support this parameter.
- `--random-mm-base-items-per-request` - `--random-mm-base-items-per-request`
@@ -141,7 +137,6 @@ Specify to save benchmark results to a json file
## Usage Examples ## Usage Examples
### Online Benchmark ### Online Benchmark
<details class="admonition abstract" markdown="1"> <details class="admonition abstract" markdown="1">
<summary>Show more</summary> <summary>Show more</summary>
@@ -167,9 +162,7 @@ vllm bench serve \
--dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \ --dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \
--percentile-metrics ttft,tpot,itl,e2el --percentile-metrics ttft,tpot,itl,e2el
``` ```
If successful, you will see the following output: If successful, you will see the following output:
```text ```text
============ Serving Benchmark Result ============ ============ Serving Benchmark Result ============
Successful requests: 2 Successful requests: 2
@@ -271,74 +264,11 @@ Median AUDIO_RTF: 0.47
P99 AUDIO_RTF: 0.48 P99 AUDIO_RTF: 0.48
================================================== ==================================================
``` ```
Notes: Notes:
We use audio generation time / audio duration to calculate RTF. We use audio generation time / audio duration to calculate RTF.
</details> </details>
### OmniInteract Realtime Benchmark
OmniInteract runs each video as one native-duplex WebSocket sample in the standard serving-benchmark lifecycle:
```bash
vllm bench serve --omni \
--backend openai-realtime-duplex \
--dataset-name omniinteract \
--dataset-path /path/to/OmniInteract \
--model openbmb/MiniCPM-o-4_5 \
--base-url http://127.0.0.1:8000 \
--endpoint /v1/realtime \
--omniinteract-ref-audio /path/to/reference.wav \
--omniinteract-output-dir ./omniinteract-artifacts \
--num-prompts 3 \
--num-warmups 0
```
`--dataset-path` accepts an extracted directory, `data.tar[.gz]`, or a Hugging Face dataset ID; omitting it uses
`lucky-lance/OmniInteract`. `--num-prompts` is the total across subsets and defaults to 3 for OmniInteract; explicit `0`
selects all and oversize values use all available cases. Reference audio is required, and OmniInteract uses the
`/v1/realtime` endpoint.
Audio is replayed as 16 kHz PCM16 in 200 ms chunks and video at 1 FPS with real-time pacing. All selected media is decoded
before timing and remains in client memory for the run, so `--max-concurrency` does not limit media preparation memory; use
explicit `--num-prompts 0` only when the client has enough RAM for the full dataset. Media commands are bounded by
`--omniinteract-media-timeout-s`, and concurrency defaults to 1. Standard request-rate, warmup, result-saving, and summary
options apply. Use `--omniinteract-require-response` only for functional E2E cases; LISTEN is a valid benchmark result.
Each completed case writes `output.wav`, `wav_transcript.json`, `events.json`, `result.json`, and a final `.done` marker under
`--omniinteract-output-dir`. The root also contains `batch_summary.json` and `official_eval_manifest.jsonl`; failed cases write
`.failed.json`. Runs sharing one output root are serialized. Completion validates transport, response lifecycle, and artifacts,
not answer accuracy. Transcript timestamps are serialized playback-queue times. Playback ACKs report cumulative progress
incrementally along that serialized clock, like a live listener; the first ack for a response goes out as soon as its audio
arrives, checkpointing the response's history position so a later committed user input updates it in place. A residual
`playback_ack_too_late` rejection is recorded as an artifact warning rather than failing the case. Clipped or cancelled
outputs are ineligible and omitted from the official manifest; `audio_clipped_bytes` records output beyond the rounded video
horizon.
TTFT, TTFP, and RTF start at client receipt of `response.created`. TPOT/ITL use engine stage-0 timing; ITL is emitted only when
every token interval is present.
The checked-in local performance configuration measures four deterministic cases from each OmniInteract subset (12 videos
total), with no benchmark warmups and a maximum concurrency of two. Each subset also sends one readiness request before its
measured cases. Run it from the repository root with:
```bash
export HF_HOME=/path/to/persistent/huggingface-cache
export BENCHMARK_DIR=tests/dfx/perf/results
bash tools/nightly/run_nightly_jobs.sh \
--test-type local \
--model-type omni \
--label-substr minicpmo_4_5_omniinteract
```
The first run downloads the pinned OmniInteract archive into `HF_HOME`; later runs reuse that cache.
It requires every case to commit its input, complete any emitted response lifecycles, and publish the expected WAV,
transcript, event, and result artifacts without errors. A valid LISTEN-only case may have no response audio or transcript
chunks. Official-manifest eligibility is reported separately because clipped or cancelled output is a benchmark-quality
signal, not a transport failure. This local performance test does not score answer accuracy.
### Multi-Modal Benchmark ### Multi-Modal Benchmark
<details class="admonition abstract" markdown="1"> <details class="admonition abstract" markdown="1">
@@ -351,8 +281,8 @@ Generate synthetic image、video、audio inputs alongside random text prompts to
Notes: Notes:
- Works only with online benchmark via: - Works only with online benchmark via:
- the OpenAI chat backend (`--backend openai-chat-omni`) and endpoint `/v1/chat/completions`. - the OpenAI chat backend (`--backend openai-chat-omni`) and endpoint `/v1/chat/completions`.
- the OpenAI edit image backend (`--backend openai-image-edits-omni`) and endpoint `/v1/images/edits`. - the OpenAI edit image backend (`--backend openai-image-edits-omni`) and endpoint `/v1/images/edits`.
Start the server (example): Start the server (example):
@@ -363,7 +293,6 @@ vllm serve Qwen/Qwen2.5-Omni-7B --omni
It is recommended to use the flag `--ignore-eos` to simulate real responses. You can set the size of the output via the arg `random-output-len`. It is recommended to use the flag `--ignore-eos` to simulate real responses. You can set the size of the output via the arg `random-output-len`.
Then run the benchmarking script: Then run the benchmarking script:
```bash ```bash
vllm bench serve \ vllm bench serve \
--omni \ --omni \
@@ -433,79 +362,8 @@ How sampling works:
- If a modality (e.g., image) reaches its limit from `--random-mm-limit-mm-per-prompt`, all buckets of that modality are excluded and the remaining bucket probabilities are renormalized before continuing. - If a modality (e.g., image) reaches its limit from `--random-mm-limit-mm-per-prompt`, all buckets of that modality are excluded and the remaining bucket probabilities are renormalized before continuing.
This should be seen as an edge case, and if this behavior can be avoided by setting `--random-mm-limit-mm-per-prompt` to a large number. Note that this might result in errors due to engine config `--limit-mm-per-prompt`. This should be seen as an edge case, and if this behavior can be avoided by setting `--random-mm-limit-mm-per-prompt` to a large number. Note that this might result in errors due to engine config `--limit-mm-per-prompt`.
- The resulting request contains synthetic image data in `multi_modal_data` (OpenAI Chat format). When `random-mm` is used with the OpenAI Chat backend, prompts remain text and MM content is attached via `multi_modal_data`. - The resulting request contains synthetic image data in `multi_modal_data` (OpenAI Chat format). When `random-mm` is used with the OpenAI Chat backend, prompts remain text and MM content is attached via `multi_modal_data`.
</details> </details>
### Image / video generation
For `/v1/images/generations`, `/v1/images/edits`, and `/v1/videos`, set `--endpoint` to that path and omit `--backend`. The endpoint path is registered as the bench request adapter, so a separate named backend is not required.
Example (`/v1/images/generations`):
```bash
vllm bench serve --omni \
--endpoint /v1/images/generations \
--dataset-name random \
--model ~/models/Qwen/Qwen-Image \
--tokenizer ~/models/Qwen/Qwen-Image/tokenizer \
--max-concurrency 1 \
--num-warmups 2 \
--num-prompts 10 \
--random-input-len 64 \
--random-output-len 1 \
--ignore-eos \
--percentile-metrics e2el \
--extra-body '{
"num_inference_steps": 20,
"seed": 42,
"true_cfg_scale": 4.0
}'
```
If successful, following output is like:
```text
============ Serving Benchmark Result ============
Successful requests: 3
Failed requests: 0
Maximum request concurrency: 1
Benchmark duration (s): 8.42
Request throughput (req/s): 0.36
Peak concurrent requests: 2.00
-------------------Peak Memory--------------------
Mean PEAK_MEMORY_MB (MB): 58832.00
Median PEAK_MEMORY_MB (MB): 58832.00
P99 PEAK_MEMORY_MB (MB): 58832.00
----------------End-to-end Latency----------------
Mean E2EL (ms): 2798.16
Median E2EL (ms): 2799.83
P99 E2EL (ms): 2800.03
================== Image Result ==================
Total images generated: 3
Image throughput (img/s): 0.36
Average pixels per image: 1048576.00
Mean denoise step latency (ms): 136.59
---------------- Image Generation ----------------
Mean IMAGE_GENERATION (ms): 2731.84
Median IMAGE_GENERATION (ms): 2732.02
P99 IMAGE_GENERATION (ms): 2734.65
==================================================
```
Use the same pattern with `--endpoint /v1/images/edits` or `--endpoint /v1/videos` (and model-specific `--extra-body` as needed). Pure image/video runs omit the Text Result section when there is no generated text.
`/v1/images/edits` defaults to **non-streaming JSON** (`stream=false`) so single-stage edit models are not rejected by the server. For multi-stage pipelines that need SSE (AR TTFT / image chunks), pass `"stream": true` in `--extra-body`.
`/v1/videos` is an async job API: the client creates a job, then polls until `completed`/`failed`. Measured **e2el** therefore includes client poll sleep and any overshoot after the job actually finishes. Tune polling via `--extra-body`:
- `poll_interval_s` (default `2.0`): sleep between status polls
- `poll_timeout_s` (default `21600`, i.e. 6 hours): give up waiting for the job
**VIDEO_RTF** prefers server-reported generation time when available:
- `video_rtf = video_generation_time_ms / 1000 / video_duration`, where `video_generation_time_ms` comes from response `stage_durations` (or `inference_time_s` as fallback)
- If generation time is missing, falls back to `e2el / video_duration` (this fallback *does* include poll overhead)
### Multi-Stage Benchmark ### Multi-Stage Benchmark
<details class="admonition abstract" markdown="1"> <details class="admonition abstract" markdown="1">
@@ -518,7 +376,6 @@ vllm serve --omni --port 29999 --model ~/Qwen3-Omni-30B-A3B-Instruct
``` ```
Then run the benchmarking script (remember to add the flag `--print-stage`): Then run the benchmarking script (remember to add the flag `--print-stage`):
```bash ```bash
vllm bench serve --omni \ vllm bench serve --omni \
--dataset-name random \ --dataset-name random \
@@ -538,7 +395,6 @@ vllm bench serve --omni \
``` ```
Besides the "Serving Benchmark Result", there will be "Stage Benchmark Result" below: Besides the "Serving Benchmark Result", there will be "Stage Benchmark Result" below:
```text ```text
============= Stage Benchmark Result ============= ============= Stage Benchmark Result =============
=============== Stage 0 (thinker) ================ =============== Stage 0 (thinker) ================
@@ -595,9 +451,7 @@ Mean AUDIO_RTF: 0.24
Median AUDIO_RTF: 0.26 Median AUDIO_RTF: 0.26
P99 AUDIO_RTF: 0.27 P99 AUDIO_RTF: 0.27
``` ```
Explanation: Explanation:
- stage_gen_time: Time from submitting a request to a specific stage to that stage finishing generation. - stage_gen_time: Time from submitting a request to a specific stage to that stage finishing generation.
- Serving TTFC (Time to First Chunk): Time from the HTTP request being accepted by the serving frontend to - Serving TTFC (Time to First Chunk): Time from the HTTP request being accepted by the serving frontend to
+3 -7
View File
@@ -1,9 +1,5 @@
# Contact Us # Contact Us
- For bugs and feature requests, search or open a GitHub [issue](https://github.com/vllm-project/vllm-omni/issues). - For technical questions and feature requests, please use GitHub [Issues](https://github.com/vllm-project/vllm-omni/issues)
- For technical and usage questions, join the `sig-omni` channel in [vLLM Slack](https://slack.vllm.ai/) or use the [vLLM Forum](https://discuss.vllm.ai/). - For coordinating contributions and development and discussing with other users and developers, please join `sig-omni` channel in our [Slack](https://slack.vllm.ai/) or use the [vLLM Forum](https://discuss.vllm.ai/)
- For coordinating contributions and development, use `sig-omni`, the forum, or join a [community meeting](meetings.md). - For security disclosures, please use GitHub's [Security Advisories](https://github.com/vllm-project/vllm-omni/security/advisories) feature
## Security Disclosures
Do not share vulnerability details in public GitHub issues, pull requests, discussions, or Slack channels. To arrange a private report, send a direct message to one of the [lead maintainers](governance.md#lead-maintainers) in [vLLM Slack](https://slack.vllm.ai/). If you cannot send a direct message, you may post a detail-free request in `sig-omni` asking a lead maintainer to contact you privately; do not include any vulnerability details in that request.
+23 -3
View File
@@ -52,11 +52,31 @@ Committers have write access and merge rights. They typically have deep expertis
- [@ZJY0516](https://github.com/ZJY0516): Diffusion attention backend, kernel fusion, and CustomOp - [@ZJY0516](https://github.com/ZJY0516): Diffusion attention backend, kernel fusion, and CustomOp
- [@yuanheng-zhao](https://github.com/yuanheng-zhao): Diffusion cache, offload, and Omni Support - [@yuanheng-zhao](https://github.com/yuanheng-zhao): Diffusion cache, offload, and Omni Support
## Reviewer Routing ## Diffusion Workload Division
For path-based reviewer routing, consult [`.github/CODEOWNERS`](https://github.com/vllm-project/vllm-omni/blob/main/.github/CODEOWNERS). Detailed subsystem boundaries and safe-change guidance live in the [module design documents](../design/index.md#module-design-documents). This section breaks down diffusion-related responsibilities. If you have PRs touching these areas, please ping the listed owners for review.
`CODEOWNERS` is operational routing metadata, not a governance or approval policy. Being listed does not grant committer status or merge rights, and it does not mean every listed reviewer must approve a change. The maintainer roster above is authoritative for project roles and merge rights. ### Runtime-related
- Diffusion models: @RuixiangMa, @wtomin, @xuechendi
### Optimization
- Parallel: @RuixiangMa, @wtomin
- Attention backend: @ZJY0516
- Cache: @yuanheng-zhao, @RuixiangMa, @SamitHuang
- Offload: @yuanheng-zhao
- Intel GPU support: @xuechendi
- Quantization: @lishunyang12, @david6666666
- Kernel fusion / communication-computation: @ZJY0516
### Docs & Test
- Diffusion docs: @RuixiangMa, @wtomin
## Meetings
Committers hold **bi-weekly meetings** to discuss future directions and collaborations of the project.
## Committer Nomination Process ## Committer Nomination Process
-8
View File
@@ -1,8 +0,0 @@
# Community Meetings
vLLM-Omni hosts developer-facing meetings for Chinese- and English-language audiences. Schedules and access details can change, so use these maintained documents as the source of truth:
- [Chinese-language meeting document](https://docs.google.com/document/d/1pdUBiS_7mdOUNDtdwy-9OUf7jsMWN324BIbps5olDME/edit?tab=t.0)
- [English-language meeting document](https://docs.google.com/document/d/19AlD08T8uJ7wqfs8VtYCi0iGMTwlTYVVlAxFZ3S7OIg/edit?tab=t.0)
Each document contains the current schedule, access details, agendas, and past meeting notes. For broader vLLM community events, see the [vLLM events calendar](https://vllm.ai/events/).
+1
View File
@@ -0,0 +1 @@
# Meetups
+12
View File
@@ -0,0 +1,12 @@
# Volunteers for Bugfix and CI
We encourage you to check current docs and [issues](https://github.com/vllm-project/vllm-omni/issues) to find possible solutions for your questions. If non of these can solve it, please propose an issue to describe your questions about bug or CI problems for developing.
If you have urgent need for locating and solving bugfix or CI problems, please find community volunteers below.
| Dec 4-Dec 12 | Dec 15-Dec 19 | Dec 22-Dec 26 | Dec 29- Jan 2, 2026| Jan 5-Jan 9 | Jan 12-Jan 16 |
|----------|----------|----------|----------|----------|----------|
| <a href="https://github.com/congw729">Conw729</a> | <a href="https://github.com/yinpeiqi">yinpeiqi</a> | <a href="https://github.com/tzhouam">tzhouam</a> | <a href="https://github.com/SamitHuang">SamitHuang</a> | <a href="https://github.com/gcanlin">gcanlin</a> | <a href="https://github.com/natureofnature">natureofnature</a> |
| <a href="https://github.com/david6666666">david6666666</a> | <a href="https://github.com/R2-Y">R2-Y</a> | <a href="https://github.com/hsliuustc0106">hsliuustc0106</a> | <a href="https://github.com/Gaohan123">Gaohan123</a> | <a href="https://github.com/ZJY0516">ZJY0516</a> | <a href="https://github.com/qibaoyuan">qibaoyuan</a> |
We kindly welcome more contributors to fix bugs and contribute new features!
+7 -7
View File
@@ -2,22 +2,22 @@
This section lists the most common options for running vLLM-Omni. This section lists the most common options for running vLLM-Omni.
For options within a vLLM Engine, please refer to the [vLLM 0.29 configuration guide](https://docs.vllm.ai/en/v0.29.0/configuration/index.html). For options within a vLLM Engine, please refer to the [vLLM 0.26 configuration guide](https://docs.vllm.ai/en/v0.26.0/configuration/index.html).
Each model defines fixed topology in a registered `PipelineConfig` and runtime Currently, the main options are maintained by stage configs for each model.
overrides in a deploy YAML.
For process-level settings shared by the CLI, deploy YAML, workers, and model
integrations, see [Environment Variables](./environment_variables.md).
For a specific example, see the [Qwen2.5-Omni deploy config](gh-file:vllm_omni/deploy/qwen2_5_omni.yaml). The matching frozen pipeline topology lives at [vllm_omni/model_executor/models/qwen2_5_omni/pipeline.py](gh-file:vllm_omni/model_executor/models/qwen2_5_omni/pipeline.py). For a specific example, see the [Qwen2.5-Omni deploy config](gh-file:vllm_omni/deploy/qwen2_5_omni.yaml). The matching frozen pipeline topology lives at [vllm_omni/model_executor/models/qwen2_5_omni/pipeline.py](gh-file:vllm_omni/model_executor/models/qwen2_5_omni/pipeline.py).
For an introduction, see [Pipeline and deploy configurations](./stage_configs.md). For introduction, please check [Introduction for stage config](./stage_configs.md)
## Memory Configuration ## Memory Configuration
- **[GPU Memory Calculation and Configuration](./gpu_memory_utilization.md)** - Guide on how to calculate memory requirements and set up `gpu_memory_utilization` for optimal performance - **[GPU Memory Calculation and Configuration](./gpu_memory_utilization.md)** - Guide on how to calculate memory requirements and set up `gpu_memory_utilization` for optimal performance
## Multi-Stage Recipes
- **[Prefill-Decode Disaggregation](./pd_disaggregation.md)** - How to derive a PD-aware Qwen3-Omni stage config from the default config without introducing another bundled YAML
## Optimization Features ## Optimization Features
- **[Diffusion Features Overview](../user_guide/diffusion_features.md)** - Complete overview of all diffusion model features and supported models - **[Diffusion Features Overview](../user_guide/diffusion_features.md)** - Complete overview of all diffusion model features and supported models
+2 -2
View File
@@ -1,6 +1,6 @@
# Composable parallel strategies # Composable parallel strategies
In vLLM-Omni, the `composable_parallel` system layers a declarative *parallel strategy* on top of the registered pipeline and deploy config. A strategy YAML maps each pipeline stage (by its `model_stage` name) to a stack of axis declarations — one per parallelism dimension — that the engine translates into per-stage parallel sizing (`tensor_parallel_size`, `data_parallel_size`, `pipeline_parallel_size`, expert-parallel toggle, replica count) and, when relevant, an orchestrator-wide load-balancer policy. In vLLM-Omni, the `composable_parallel` system layers a declarative *parallel strategy* on top of the existing stage configs. A strategy YAML maps each pipeline stage (by its `model_stage` name) to a stack of axis declarations — one per parallelism dimension — that the engine translates into per-stage parallel sizing (`tensor_parallel_size`, `data_parallel_size`, `pipeline_parallel_size`, expert-parallel toggle, replica count) and, when relevant, an orchestrator-wide load-balancer policy.
Strategies are intentionally narrow: each one only writes the axes it declares, leaving every other engine arg untouched. This makes a strategy file a small, reusable overlay you can swap independently of the deploy YAML (see `configuration/stage_configs.md`). Strategies are intentionally narrow: each one only writes the axes it declares, leaving every other engine arg untouched. This makes a strategy file a small, reusable overlay you can swap independently of the deploy YAML (see `configuration/stage_configs.md`).
@@ -195,7 +195,7 @@ Because `omni_lb_policy` is read once at `AsyncOmniEngine` construction — not
Default: no default — required for every entry under `strategies:`. Default: no default — required for every entry under `strategies:`.
Strategy keys MUST be `model_stage` *names* (strings), the same labels defined by [`StagePipelineConfig.model_stage`](stage_configs.md#pipeline-configuration). Integer `stage_id` values are not accepted; the resolver matches on `model_stage` exactly (`_resolve_stage` in `vllm_omni/config/composable_parallel/apply.py`). Strategy keys MUST be `model_stage` *names* (strings), the same labels stage configs use for `engine_args.model_stage` (see [`model_stage` in `stage_configs.md`](stage_configs.md#engine_argsmodel_stage)). Integer `stage_id` values are not accepted; the resolver matches on `model_stage` exactly (`_resolve_stage` in `vllm_omni/config/composable_parallel/apply.py`).
## Common pitfalls ## Common pitfalls
-238
View File
@@ -1,238 +0,0 @@
# Environment Variables
Environment variables are process-level configuration shared by the CLI,
deploy YAML, Python APIs, worker processes, and model integrations. They are
therefore documented under **User Guide → Configuration**, rather than under
CLI Reference.
This page describes the public vLLM-Omni environment-variable contract on the
`latest` documentation branch. Use the documentation for your installed
vLLM-Omni release when behavior differs.
!!! important "Public contract"
Only names in [Public vLLM-Omni variables](#public-vllm-omni-variables) are
public Omni configuration. A name found in source code is not automatically
supported: model-specific, benchmark, platform, and internal variables have
separate ownership and lifecycle rules.
## Naming and implementation boundary
New public variables owned by vLLM-Omni must use the `VLLM_OMNI_` prefix.
Existing public names with older prefixes remain supported for compatibility,
but they do not establish naming precedent for new settings. Inherited vLLM
and third-party names retain the prefix chosen by their owning project.
The source [environment-variable inventory](gh-file:vllm_omni/config/environment_variable_inventory.py)
records ownership, migration disposition, and redaction metadata. Unlike
`vllm.envs`, it is not an executable value resolver: existing consumers still
own parsing, defaults, precedence, and evaluation time. The public tables below
describe those live consumer contracts. Stable model or stage behavior should
move to typed configuration, and request-varying behavior should move to a
request schema instead of adding another environment variable.
## Precedence and evaluation time
There is no repository-wide rule that makes environment variables override (or
lose to) every CLI, YAML, or request option. The tables below state precedence
for each public setting. In general:
- request fields should control behavior that can vary per request;
- typed deploy or stage configuration should control stable model and stage
behavior;
- environment variables are appropriate for process launch, platform
integration, emergency diagnostics, and compatibility fallbacks.
An environment variable can be read while a module is imported, when server or
model configuration is constructed, when a worker is spawned, or whenever a
property is accessed. Set import- and startup-time variables before invoking
`vllm serve` or importing vLLM-Omni. Changing the parent shell after workers
start does not update existing worker environments.
The lifecycle labels used below are:
- **Stable** — reviewed public configuration; changes require compatibility
consideration.
- **Experimental** — public for evaluation, but its contract may still evolve.
- **Diagnostic** — supported for investigation or temporary mitigation, not as
a normal tuning interface.
- **Deprecated** — compatibility alias; migrate to the named replacement.
## Public vLLM-Omni variables
### Diffusion backend and cache selection
| Name | Type and default | Applies to and read time | Precedence and invalid values | Lifecycle |
| --- | --- | --- | --- | --- |
| `DIFFUSION_ATTENTION_BACKEND` | Backend name or `auto`; default `auto` (platform selection) | Diffusion stages; read when `OmniDiffusionConfig` is constructed | `diffusion_attention_config.default` wins. An unknown backend fails during backend resolution. | Stable fallback |
| `DIFFUSION_CACHE_BACKEND` | `none`, `cache_dit`, `tea_cache`, `mag_cache`, `step_cache`, `stepcache`, or `step_cache_dit`; default `none` | Diffusion runner startup | Explicit `cache_backend` in config wins. Otherwise this name wins over the deprecated alias. Unsupported values raise `ValueError` during runner setup. | Stable fallback |
| `DIFFUSION_CACHE_ADAPTER` | Same values as `DIFFUSION_CACHE_BACKEND`; default `none` | Diffusion runner startup | Used only when neither explicit `cache_backend` nor `DIFFUSION_CACHE_BACKEND` is set. Unsupported values raise `ValueError`. | Deprecated; use `DIFFUSION_CACHE_BACKEND` |
| `OMNI_DIFFUSION_PROMPT_EMBED_CACHE` | Boolean: `1`, `true`, `yes`, `on`, `0`, `false`, `no`, or `off`; default disabled | Each diffusion runner; resolved during model setup | A recognized environment value overrides the explicit enable setting. An unrecognized value is ignored. | Experimental |
| `OMNI_DIFFUSION_PROMPT_EMBED_CACHE_SIZE` | Positive integer; default `32` entries | Each diffusion runner; resolved during model setup | A valid environment value overrides the explicit cache size. A non-integer logs a warning; a non-positive value is ignored. | Experimental |
| `OMNI_DIFFUSION_SESSION_STATE_MANAGER` | Boolean with the same accepted spellings as the prompt cache; default disabled | Experimental diffusion session manager; model setup | A recognized environment value overrides the explicit enable setting. An unrecognized value is ignored. | Experimental |
| `OMNI_DIFFUSION_SESSION_STATE_MANAGER_MAX_SESSIONS` | Positive integer; default `64` | Experimental diffusion session manager; model setup | A valid environment value overrides the explicit maximum. A non-integer logs a warning; a non-positive value is ignored. | Experimental |
Backend names for `DIFFUSION_ATTENTION_BACKEND` are the members of
`DiffusionAttentionBackendEnum`, such as `FLASH_ATTN`, `TORCH_SDPA`,
`SAGE_ATTN`, `FLASHINFER_ATTN`, and `TRTLLM_ATTN`. Platform support still
depends on the installed kernels and model path.
### Serving and runtime
| Name | Type and default | Applies to and read time | Precedence and invalid values | Lifecycle |
| --- | --- | --- | --- | --- |
| `SPEAKER_SAMPLES_DIR` | Filesystem path; default `~/.cache/vllm-omni/speakers` | Speech server; read when speaker storage initializes | Environment-only setting. The directory is created; filesystem errors propagate. | Stable |
| `SPEAKER_MAX_UPLOADED` | Integer; default `1000` | Speech server; read when speaker storage initializes | Environment-only setting. A non-integer logs a warning and uses `1000`; range is not otherwise validated. | Stable |
| `VLLM_OMNI_ASYNC_OUTPUT_TIMEOUT` | Float seconds; default `600` | Diffusion engine async-output wait in `step_streaming`; resolved per call on the request path, not at import | Environment-only setting. A non-float or `<=0` value warns once and uses the default. | Experimental |
| `VLLM_OMNI_EVENT_DRIVEN_ORCH` | `1`, `true`, `yes` or `on` enables; default `0` (off) | Orchestration loop and the serving-side final-output drain; read once when the `Orchestrator` is constructed | Environment-only setting. Values are stripped and case-normalized; any unrecognized value leaves the legacy poll loop selected. | Experimental |
| `VLLM_OMNI_INPUT_WAIT_TIMEOUT_S` | Float seconds; default `600`; `<=0` disables | Full-payload input coordinator, not async-chunk transfer; read when the scheduler module imports in each worker | Environment-only setting. A non-float logs a warning and uses `600`. | Stable operational control |
| `VLLM_OMNI_ORCH_MONITOR_PATH` | Filesystem path; default `<current-working-directory>/vllm_omni_orch_monitor_<timestamp>.json` | Orchestrator monitor enabled by `--enable-orch-monitor`; read when the monitor is created | Environment-only path override. Parent directories are created; write errors are logged. | Diagnostic |
| `VLLM_OMNI_VIDEO_SYNC_TIMEOUT` | Float seconds; default `600` | Synchronous Videos API; read when the API server module imports | Environment-only setting. A non-float raises `ValueError` during import. | Experimental |
| `VLLM_VIDEO_ASYNC_CHUNK` | `on` or `off`; default `on` | Streaming video output; read on attribute access | Environment-only setting. Values are trimmed and case-normalized; an invalid value warns once and uses `on`. | Experimental |
| `VLLM_VIDEO_AUDIO_DELTA_MODE` | `fast` or `slow`; default `fast` | Streaming video audio deltas; read on attribute access | Environment-only setting. Values are trimmed and case-normalized; an invalid value warns once and uses `fast`. | Experimental |
### Server storage
Storage names use Pydantic's nested-settings delimiter (`__`). They are read
when `ServerSettings` is initialized, normally during server import/startup.
Invalid integer values raise a Pydantic validation error.
| Name | Type and default | Meaning | Lifecycle |
| --- | --- | --- | --- |
| `VLLM_OMNI_SERVER_STORAGE__PATH` | Path; default `/tmp/storage` | Directory for completed server files. | Stable |
| `VLLM_OMNI_SERVER_STORAGE__FILE_CONCURRENCY` | Integer; default `4` | Maximum concurrent file operations. No additional range validation is currently applied. | Stable |
| `VLLM_OMNI_SERVER_STORAGE__FILE_TTL` | Integer seconds or unset; default unset | Optional lifetime for locally stored files. | Stable |
| `VLLM_OMNI_SERVER_STORAGE__TTL_SWEEP_INTERVAL` | Integer seconds or unset | Sweep frequency. If file TTL is set and this is unset, the effective default is `300`. | Stable |
| `VLLM_OMNI_STORAGE_PATH` | Path | Deprecated alias for `VLLM_OMNI_SERVER_STORAGE__PATH`. The new name wins when both are set. | Deprecated |
| `VLLM_OMNI_STORAGE_MAX_CONCURRENCY` | Integer | Deprecated alias for `VLLM_OMNI_SERVER_STORAGE__FILE_CONCURRENCY`. The new name wins when both are set. | Deprecated |
The deprecated aliases emit `DeprecationWarning`. They do not affect file TTL
settings.
### Quantization diagnostics and performance
| Name | Type and default | Applies to and read time | Precedence and invalid values | Lifecycle |
| --- | --- | --- | --- | --- |
| `VLLM_OMNI_SKIP_NVFP4_NAN_CLAMP` | Boolean truthy spellings: `1`, `true`, `yes`, `on`; default false | ModelOpt NVFP4 compatibility patch; read when `vllm_omni.patch` imports | Environment-only escape hatch. Any other value means false. Set only to diagnose the upstream NaN-scale issue. | Diagnostic and temporary |
| `VLLM_OMNI_USE_QUACK_FP8` | Boolean truthy spellings: `1`, `true`, `yes`, `on`; unset means hardware auto-detection | FP8 scaled matrix multiplication; evaluated when quack capability is selected | A set value overrides auto-detection. Any non-truthy value forces quack off. If quack cannot load, vLLM-Omni warns and falls back to FlashInfer. | Experimental performance control |
`QUACK_CACHE_DIR` is owned by the external quack library and is not an
Omni-owned variable, even though vLLM-Omni supplies a persistent default when
it is unset.
## Per-stage environment
Deploy configurations can set arbitrary environment keys for one stage:
```yaml
stages:
- stage_id: 0
env:
HCCL_IF_BASE_PORT: 23000
MY_MODEL_CACHE: /mnt/model-cache
```
The legacy nested form under `runtime.env` is also accepted. vLLM-Omni converts
keys and values to strings, applies them while the stage engine or child process
is launched, logs keys but not values, and restores the parent process's prior
environment afterward. A child process inherits the scoped values. This keeps
sibling stages from permanently changing one another's parent environment.
This scoping has two consequences:
1. A setting read inside stage/model initialization can see the stage value.
2. A setting already cached by an import that occurred before stage launch
cannot be changed retroactively with stage `env`.
Stage `env` is an escape hatch for process and third-party settings, not a
replacement for typed stage configuration. Do not put request-varying behavior
there. Secret values may reach child processes; logs and diagnostics must emit
their keys only.
## Inherited vLLM variables
vLLM-Omni also reads variables through its aligned vLLM dependency. Refer to
the [vLLM 0.29 environment-variable reference](https://docs.vllm.ai/en/v0.29.0/configuration/env_vars.html)
for their definitions. This includes vLLM launch, cache, logging, plugin, ROCm,
XPU, ModelScope, and FlashInfer workspace settings.
An inherited name does not become Omni-owned because Omni reads or forwards it.
Where behavior differs, the Omni-specific page should document only that
difference. For example, device-visibility values can be further narrowed by a
stage's `devices` setting.
## Platform, external, and secret variables
Hardware runtimes, distributed launchers, and third-party libraries retain
ownership of their variables. Common groups include:
- device visibility (`ASCEND_RT_VISIBLE_DEVICES`, `HIP_VISIBLE_DEVICES`, and
`MUSA_VISIBLE_DEVICES`);
- distributed launch (`MASTER_ADDR`, `MASTER_PORT`, `RANK`, and `WORLD_SIZE`);
- Hugging Face cache and authentication (`HF_HOME`, `HF_TOKEN`, and related
names);
- accelerator libraries such as NCCL, FlashInfer, MindIE, MORI, and quack.
Consult the owning project's documentation for accepted values and support.
`HF_TOKEN`, `HUGGINGFACE_HUB_TOKEN`, and `OPENAI_API_KEY` are explicitly marked
for redaction in the reviewed inventory. Never print their values in environment
collection, examples, bug reports, or logs.
## Model-specific variables
The audit found 58 variables read by a single model or pipeline family. They are
not listed as public usage options here because doing so would turn implementation
escape hatches into an accidental compatibility contract.
Every audited model-specific name has a migration disposition in the
[environment-variable inventory](gh-file:vllm_omni/config/environment_variable_inventory.py):
| Disposition | Count | Required outcome |
| --- | ---: | --- |
| Promote | 35 | Move a stable setting into typed stage or model configuration. |
| Request scope | 6 | Move request-varying behavior into a declared request-option schema. |
| External | 0 | Retain only when a supported third-party library owns the contract. |
| Internalize | 11 | Keep a debug or diagnostic switch out of public documentation and configuration. |
| Deprecate/remove | 6 | Remove a compatibility escape hatch that has no continuing contract. |
The disposition is a migration target, not a statement that the existing
environment switch is stable. Promote or request-scope work should land in
model-owner-reviewed follow-up changes with validation and model-page updates.
The family-sized migration packages and their ownership gate are tracked in
[#6232](https://github.com/vllm-project/vllm-omni/issues/6232).
## Environment collection and troubleshooting
Run the repository collector when preparing a bug report:
```bash
python collect_env.py
```
The environment section includes the safe public Omni allowlist, registered
vLLM variables, and selected platform prefixes. It omits names marked for
redaction and names containing common secret terms. Review the output before
sharing it. A key supplied through stage `env` is not collected merely because
it appears in deploy configuration; the same allowlist and prefix rules apply.
If a setting appears ineffective, first check its read time in the tables
above. Restart processes after changing import- or startup-time values, verify
that the variable reaches the intended worker, and check whether an explicit
typed configuration field takes precedence.
## Benchmark and internal variables
The reviewed inventory marks 20 benchmark variables as transitional while
benchmark configuration moves to CLI/request schemas in
[issue #5376](https://github.com/vllm-project/vllm-omni/issues/5376). They are not
part of the serving/runtime contract.
Internal process-coordination and debug variables are deliberately excluded
from this page. The CI scanner resolves string literals and module-level string
constants used by direct `os` access, local wrapper helpers, aliased `os`
imports, and environment membership checks. Server-storage names generated by
Pydantic settings are checked separately against their model fields.
Names assembled from cross-module metadata and arbitrary keys supplied through
stage `env` cannot be inferred reliably. Contributors must classify those names
explicitly. CI fails when a statically resolved name has no classification or
when a reviewed public name is absent from this page.
+32 -24
View File
@@ -80,15 +80,19 @@ When multiple stages share the same GPU, you must ensure the sum of their `gpu_m
**Example: Two stages on GPU 0** **Example: Two stages on GPU 0**
```yaml ```yaml
stages: stage_args:
- stage_id: 0 - stage_id: 0
devices: "0" runtime:
gpu_memory_utilization: 0.6 # Uses 60% of GPU 0 devices: "0"
engine_args:
gpu_memory_utilization: 0.6 # Uses 60% of GPU 0
- stage_id: 1 - stage_id: 1
devices: "0" runtime:
gpu_memory_utilization: 0.3 # Uses 30% of GPU 0 devices: "0"
# Total: 90% of GPU 0 (safe, leaves 10% buffer) engine_args:
gpu_memory_utilization: 0.3 # Uses 30% of GPU 0
# Total: 90% of GPU 0 (safe, leaves 10% buffer)
``` ```
**Important:** If stages run on different GPUs, each can use up to 1.0 independently. **Important:** If stages run on different GPUs, each can use up to 1.0 independently.
@@ -99,12 +103,14 @@ When using `tensor_parallel_size > 1`, the model is split across multiple GPUs,
**Example: 2-way tensor parallelism** **Example: 2-way tensor parallelism**
```yaml ```yaml
stages: stage_args:
- stage_id: 0 - stage_id: 0
devices: "0,1" # Uses both GPUs runtime:
tensor_parallel_size: 2 devices: "0,1" # Uses both GPUs
gpu_memory_utilization: 0.6 # 60% per GPU engine_args:
# Model is split, so each GPU uses ~30% of model memory tensor_parallel_size: 2
gpu_memory_utilization: 0.6 # 60% per GPU
# Model is split, so each GPU uses ~30% of model memory
``` ```
## Examples ## Examples
@@ -112,25 +118,27 @@ stages:
### Qwen3-Omni-MoE on 2x H100-80GB ### Qwen3-Omni-MoE on 2x H100-80GB
```yaml ```yaml
base_config: /path/to/vllm_omni/deploy/qwen3_omni_moe.yaml stage_args:
stages:
- stage_id: 0 # Thinker stage with TP=2 - stage_id: 0 # Thinker stage with TP=2
devices: "0,1" runtime:
tensor_parallel_size: 2 devices: "0,1"
gpu_memory_utilization: 0.6 # 48GB per GPU engine_args:
tensor_parallel_size: 2
gpu_memory_utilization: 0.6 # 48GB per GPU
- stage_id: 1 # Talker stage - stage_id: 1 # Talker stage
devices: "1" runtime:
gpu_memory_utilization: 0.3 # 24GB on GPU 1 devices: "1"
engine_args:
gpu_memory_utilization: 0.3 # 24GB on GPU 1
- stage_id: 2 # Code2Wav stage - stage_id: 2 # Code2Wav stage
devices: "0" runtime:
gpu_memory_utilization: 0.1 # 8GB on GPU 0 devices: "0"
engine_args:
gpu_memory_utilization: 0.1 # 8GB on GPU 0
``` ```
**Note:** Stage 0 uses GPUs 0 and 1, so the combined utilization is `0.7` on **Note:** In this configuration, stages 0 and 2 share GPU 0, but they run at different times in the pipeline, so their memory usage doesn't overlap.
GPU 0 and `0.9` on GPU 1. Keep the sum of all resident stages below `1.0` on
each device.
## Troubleshooting ## Troubleshooting
+171
View File
@@ -0,0 +1,171 @@
# Prefill-Decode (PD) Disaggregation
PD disaggregation splits the Qwen3-Omni thinker into separate prefill and decode
stages so prompt processing and token generation can run on different workers.
This is documented as a stage-config recipe instead of a bundled YAML because the
deployment-specific values usually change per environment:
- GPU placement
- `tensor_parallel_size`
- connector backend and connector ports
- connector IPs or bootstrap addresses
Start from the [default Qwen3-Omni stage config](gh-file:vllm_omni/deploy/qwen3_omni_moe.yaml)
and copy it to your own file, for example `qwen3_omni_pd.yaml`. Then apply the
changes below.
## Requirements
- 3+ GPUs for a basic layout: prefill, decode, and talker+code2wav
- A KV connector supported by vLLM, such as `MooncakeConnector`
- Matching `tensor_parallel_size` on the prefill and decode thinker stages
## 1. Split the thinker into prefill and decode stages
Replace the original thinker stage with two stages:
```yaml
stage_args:
- stage_id: 0
stage_type: llm
is_prefill_only: true
runtime:
devices: "0"
engine_args:
max_num_seqs: 16
model_stage: thinker
model_arch: Qwen3OmniMoeForConditionalGeneration
worker_type: ar
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
gpu_memory_utilization: 0.9
enforce_eager: true
trust_remote_code: true
engine_output_type: latent
distributed_executor_backend: "mp"
enable_prefix_caching: false
max_num_batched_tokens: 32768
hf_config_name: thinker_config
tensor_parallel_size: 1
kv_transfer_config:
kv_connector: "MooncakeConnector"
kv_role: "kv_producer"
kv_rank: 0
kv_parallel_size: 2
kv_connector_extra_config:
mooncake_bootstrap_port: 25201
final_output: false
is_comprehension: true
default_sampling_params:
temperature: 0.4
top_p: 0.9
top_k: 1
max_tokens: 2048
seed: 42
detokenize: True
repetition_penalty: 1.05
- stage_id: 1
stage_type: llm
is_decode_only: true
runtime:
devices: "1"
engine_args:
max_num_seqs: 64
model_stage: thinker
model_arch: Qwen3OmniMoeForConditionalGeneration
worker_type: ar
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
gpu_memory_utilization: 0.9
enforce_eager: true
trust_remote_code: true
engine_output_type: latent
distributed_executor_backend: "mp"
enable_prefix_caching: false
max_num_batched_tokens: 32768
hf_config_name: thinker_config
tensor_parallel_size: 1
kv_transfer_config:
kv_connector: "MooncakeConnector"
kv_role: "kv_consumer"
kv_rank: 1
kv_parallel_size: 2
kv_connector_extra_config:
mooncake_bootstrap_port: 25202
engine_input_source: [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: 2048
seed: 42
detokenize: True
repetition_penalty: 1.05
```
Notes:
- `is_prefill_only: true` marks the thinker stage that only saves KV.
- `is_decode_only: true` marks the thinker stage that resumes from remote KV.
- `kv_transfer_config` is required on both stages.
- The orchestrator forces the prefill stage to run with `max_tokens=1`, so the
prefill side only processes the prompt and exports KV.
## 2. Shift the downstream stages by one index
After inserting the extra thinker stage, renumber the remaining stages:
```yaml
- stage_id: 2
runtime:
devices: "2"
engine_input_source: [1]
sync_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_token_only
- stage_id: 3
runtime:
devices: "2"
engine_args:
max_num_seqs: 1
engine_input_source: [2]
sync_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_token_only
```
Compared with the default Qwen3-Omni config:
- the talker becomes stage `2` instead of stage `1`
- the code2wav stage becomes stage `3` instead of stage `2`
- the talker now reads from decode stage `1`
## 3. Add runtime edges for the four-stage pipeline
```yaml
runtime:
enabled: true
edges:
- from: 0
to: 1
- from: 1
to: 2
- from: 2
to: 3
```
## 4. Launch with your custom config
```bash
vllm serve Qwen/Qwen3-Omni-30B-A3B-Instruct --omni --port 8091 \
--deploy-config /path/to/qwen3_omni_pd.yaml
```
## Operational Notes
- `MooncakeConnector` does not support heterogeneous TP sizes across the PD
pair. Keep prefill and decode at the same `tensor_parallel_size`.
- If the thinker requires TP=2, both thinker stages must use TP=2 and be given
separate GPU sets, for example `"0,1"` for prefill and `"2,3"` for decode.
- Choose connector ports and addresses that match your deployment. The values
shown above are examples only.
+311 -80
View File
@@ -3,104 +3,52 @@
In vLLM-Omni, a model's `PipelineConfig` defines its fixed stage topology, while a deploy configuration controls how those stages run. In vLLM-Omni, a model's `PipelineConfig` defines its fixed stage topology, while a deploy configuration controls how those stages run.
!!! note !!! note
Default deploy config YAMLs (for example, `vllm_omni/deploy/qwen2_5_omni.yaml`, `vllm_omni/deploy/qwen3_omni_moe.yaml`, and `vllm_omni/deploy/qwen3_tts.yaml`) are bundled and loaded automatically when `--deploy-config` is omitted. The resolved pipeline selects its default through `default_deploy_config_name`. Default deploy config YAMLs (for example, `vllm_omni/deploy/qwen2_5_omni.yaml`, `vllm_omni/deploy/qwen3_omni_moe.yaml`, and `vllm_omni/deploy/qwen3_tts.yaml`) are bundled and loaded automatically when `--deploy-config` is omitted. The model registry resolves the right pipeline and deploy YAML by `model_type`.
## Pipeline configuration
`PipelineConfig` and its `StagePipelineConfig` entries are Python definitions
owned by the model implementation and registered in `OMNI_PIPELINES`. They
describe fixed model topology and are not accepted in deploy YAMLs.
Common `PipelineConfig` fields include:
| Field | Description |
| ------- | ------------- |
| `model_type` | Pipeline identifier used during model and config resolution. |
| `default_deploy_config_name` | Bundled deploy YAML loaded when the user does not pass `deploy_config`. |
| `model_arch` | Default Hugging Face architecture for the pipeline. |
| `hf_architectures` | Architecture names used to identify checkpoints whose `model_type` is shared. |
| `hf_config_predicate` | Optional predicate used to select between pipelines with otherwise identical HF metadata. |
| `diffusers_class_name` | Diffusers `_class_name` used to identify Diffusers-style repositories. |
| `stages` | Ordered tuple of fixed `StagePipelineConfig` definitions. |
Common `StagePipelineConfig` fields include:
| Field | Description |
| ------- | ------------- |
| `stage_id` | Stable stage identifier. |
| `model_stage` | Logical stage name used by runtime and strategy resolution. |
| `execution_type` | `LLM_AR`, `LLM_GENERATION`, or `DIFFUSION`. |
| `input_sources` | Upstream stage IDs that provide this stage's inputs. |
| `final_output` / `final_output_type` | Whether the stage produces a user-visible output and its modality. |
| `owns_tokenizer` | Whether this stage owns the pipeline tokenizer. |
| `model_arch`, `hf_config_name` | Stage-specific model architecture and nested HF config selector. |
| `engine_output_type` | Runtime output representation such as `text`, `latent`, or `audio`. |
| `custom_process_input_func` | Processor applied to this stage's incoming payload. |
| `custom_process_next_stage_input_func` | Processor used for full-payload handoff to the next stage. |
| `async_chunk_process_next_stage_input_func` | Processor used for async chunk handoff. |
| `sampling_constraints` | Model-owned sampling constraints that deploy defaults cannot override. |
To add or change topology, define and register a new pipeline variant. Use
deploy YAML only for runtime placement, resource sizing, connectors, and other
deployment overrides.
## Deploy configuration schema ## Deploy configuration schema
The new deploy schema lives under `vllm_omni/deploy/` and is paired with a frozen `PipelineConfig` registered by the model's `pipeline.py`. Each deploy YAML has these top-level fields: The new deploy schema lives under `vllm_omni/deploy/` and is paired with a frozen `PipelineConfig` registered by the model's `pipeline.py`. Each deploy YAML has these top-level fields:
| Field | Type | Required | Default | Description | | Field | Type | Required | Default | Description |
| ------- | ------ | ---------- | --------- | ------------- | |-------|------|----------|---------|-------------|
| `base_config` | str (path) | optional | — | Overlay parent (relative or absolute). `stages:` / `platforms:` deep-merged by stage_id; other scalars overlay-wins. Intended for user-authored overlays; prod yamls stay flat. | | `base_config` | str (path) | optional | — | Overlay parent (relative or absolute). `stages:` / `platforms:` deep-merged by stage_id; other scalars overlay-wins. Intended for user-authored overlays; prod yamls stay flat. |
| `async_chunk` | bool | optional | `true` | Enable chunked streaming between stages. Pin to `false` if the pipeline runs end-to-end. | | `async_chunk` | bool | optional | `true` | Enable chunked streaming between stages. Pin to `false` if the pipeline runs end-to-end. |
| `session_mode` | str | optional | `"turn"` | Session behavior. MiniCPM-o 4.5 deploy YAMLs set `"duplex"` so `/v1/realtime` and chat share the same profile. |
| `active_stream_window` | int | optional | `0` | Number of active downstream stream slots; `0` preserves all-stream cycling. |
| `duplex_session` | dict | optional | runtime defaults | Full-duplex session lifecycle, buffering, replay, and capacity limits. |
| `connectors` | dict | optional | `null` | Named connector specs (`{name, extra}`). Referenced by each stage's `input_connectors` / `output_connectors`. See [Connector schema](#connector-schema). | | `connectors` | dict | optional | `null` | Named connector specs (`{name, extra}`). Referenced by each stage's `input_connectors` / `output_connectors`. See [Connector schema](#connector-schema). |
| `edges` | list | optional | `null` | Explicit edge list for the KV transfer graph. Auto-derived from stage inputs if omitted. | | `edges` | list | optional | `null` | Explicit edge list for the KV transfer graph. Auto-derived from stage inputs if omitted. |
| `stages` | list | optional | `[]` | Per-stage runtime overrides matched by `stage_id`. Pipeline stages are still created from `PipelineConfig` when this list is empty. | | `stages` | list | required | — | Per-stage engine args + wiring (see [Stage fields](#stage-fields)). |
| `platforms` | dict | optional | `null` | Keyed by `npu` / `rocm` / `xpu`, each contains a `stages:` list with per-platform overrides applied on top of the CUDA defaults. | | `platforms` | dict | optional | `null` | Keyed by `npu` / `rocm` / `xpu`, each contains a `stages:` list with per-platform overrides applied on top of the CUDA defaults. |
| `pipeline` | str | optional | `null` | Override the auto-detected pipeline registry key (used for structural variants like `qwen2_5_omni_thinker_only` / `qwen3_omni_moe_thinker_only`). | | `pipeline` | str | optional | `null` | Override the auto-detected pipeline registry key (used for structural variants like `qwen2_5_omni_thinker_only`). |
| `trust_remote_code` | bool \| null | optional | `null` | **Pipeline-wide.** Trust HF remote code on model load; applies to every stage when specified. | | `trust_remote_code` | bool | optional | `true` | **Pipeline-wide.** Trust HF remote code on model load; applies to every stage. |
| `distributed_executor_backend` | str \| null | optional | `null` | **Pipeline-wide.** Distributed executor backend forwarded to vLLM (`"mp"`, `"ray"`, `"external_launcher"`). If omitted, vLLM auto-selects backend from runtime topology. | | `distributed_executor_backend` | str \| null | optional | `null` | **Pipeline-wide.** Distributed executor backend forwarded to vLLM (`"mp"`, `"ray"`, `"external_launcher"`). If omitted, vLLM auto-selects backend from runtime topology. |
| `dtype` | str \| null | optional | `null` | **Pipeline-wide.** Model dtype for every stage. | | `dtype` | str \| null | optional | `null` | **Pipeline-wide.** Model dtype for every stage. |
| `quantization` | str \| null | optional | `null` | **Pipeline-wide.** Quantization method for every stage. | | `quantization` | str \| null | optional | `null` | **Pipeline-wide.** Quantization method for every stage. |
| `enable_prefix_caching` | bool \| null | optional | `null` | **Pipeline-wide.** Prefix cache toggle applied to every stage when specified. | | `enable_prefix_caching` | bool | optional | `false` | **Pipeline-wide.** Prefix cache toggle applied to every stage. |
| `enable_chunked_prefill` | bool \| null | optional | `null` | **Pipeline-wide.** Chunked prefill toggle applied to every stage. | | `enable_chunked_prefill` | bool \| null | optional | `null` | **Pipeline-wide.** Chunked prefill toggle applied to every stage. |
| `data_parallel_size` | int \| null | optional | `null` | **Pipeline-wide.** DP degree for every stage. | | `data_parallel_size` | int | optional | `1` | **Pipeline-wide.** DP degree for every stage. |
| `pipeline_parallel_size` | int \| null | optional | `null` | **Pipeline-wide.** PP degree for every stage. | | `pipeline_parallel_size` | int | optional | `1` | **Pipeline-wide.** PP degree for every stage. |
| `custom_voice_dir` | str \| null | optional | `null` | **Pipeline-wide.** Directory containing custom voice profiles for supported TTS models. |
For fields whose deploy default is `null`, the deploy layer contributes no Note: for diffusion path, `distributed_executor_backend` currently defaults to
override. The effective value may still come from a platform section, an `mp`, and `ray` / `external_launcher` are not fully supported yet.
explicit CLI or stage override, or the downstream vLLM engine default.
Note: for the diffusion path, an omitted `distributed_executor_backend` selects
`uni` on a single GPU (in-process worker, no MessageQueue / `/dev/shm` output
segments) and `mp` when `num_gpus > 1`. Set `mp` explicitly to keep a worker
subprocess on one GPU. `ray` / `external_launcher` are not fully supported yet.
### Stage fields ### Stage fields
Each entry under `stages:` accepts any `StageDeployConfig` field directly (no nested `engine_args:`). Only fields whose value legitimately varies across stages live here; pipeline-wide settings (trust_remote_code, distributed_executor_backend, dtype, quantization, prefix/chunked prefill, DP/PP sizes) are declared at the top level and applied to every stage. Unknown keys fall through to `engine_extras:` and are forwarded to the engine. Frequently used fields are listed below; the source-of-truth schema is `StageDeployConfig` in `vllm_omni/config/stage_config.py`. Each entry under `stages:` accepts any `StageDeployConfig` field directly (no nested `engine_args:`). Only fields whose value legitimately varies across stages live here; pipeline-wide settings (trust_remote_code, distributed_executor_backend, dtype, quantization, prefix/chunked prefill, DP/PP sizes) are declared at the top level and applied to every stage. Unknown keys fall through to `engine_extras:` and are forwarded to the engine.
| Field | Type | Required | Default | Description | | Field | Type | Required | Default | Description |
| ------- | ------ | ---------- | --------- | ------------- | |-------|------|----------|---------|-------------|
| `stage_id` | int | required | — | Stage identity; matched against `PipelineConfig.stages[*].stage_id`. | | `stage_id` | int | required | — | Stage identity; matched against `PipelineConfig.stages[*].stage_id`. |
| `max_num_seqs` | int \| null | optional | `null` | Max concurrent sequences per stage. | | `max_num_seqs` | int | optional | `64` | Max concurrent sequences per stage. |
| `gpu_memory_utilization` | float \| null | optional | `null` | Per-stage total memory target; used for automatic KV-cache sizing. | | `gpu_memory_utilization` | float | optional | `0.9` | Per-stage memory budget. |
| `kv_cache_memory_bytes` | int \| null | optional | `null` | Explicit per-rank KV-cache budget in bytes; overrides automatic sizing. | | `tensor_parallel_size` | int | optional | `1` | TP degree for this stage. |
| `tensor_parallel_size` | int \| null | optional | `null` | TP degree for this stage. | | `enforce_eager` | bool | optional | `false` | Disable CUDA graphs. |
| `enforce_eager` | bool \| null | optional | `null` | Disable CUDA graphs. | | `max_num_batched_tokens` | int | optional | `32768` | Prefill budget. |
| `max_num_batched_tokens` | int \| null | optional | `null` | Per-stage prefill/token budget; also contributes to the native maximum in-flight token limit. | | `max_model_len` | int \| null | optional | `null` | Per-stage context length (auto-sets `VLLM_ALLOW_LONG_MAX_MODEL_LEN=1` when larger than HF default). |
| `max_model_len` | int \| null | optional | `null` | Per-sequence context or KV length; `-1` enables native cache-capacity auto-fitting, while values above the HF default auto-set `VLLM_ALLOW_LONG_MAX_MODEL_LEN=1`. |
| `async_scheduling` | bool \| null | optional | `null` | Per-stage async scheduling toggle. | | `async_scheduling` | bool \| null | optional | `null` | Per-stage async scheduling toggle. |
| `devices` | str \| null | optional | `null` | Device list assigned to this stage. The number of device ids must equal this stage's local world size (`tensor_parallel_size` × local data-parallel size × `pipeline_parallel_size`, or `num_replicas` × that product for a replica pool); a mismatch fails early — see the note below. | | `devices` | str | optional | `"0"` | `CUDA_VISIBLE_DEVICES`-style device list. |
| `output_connectors` | dict \| null | optional | `null` | Keyed by `to_stage_<n>`; values are names registered under top-level `connectors:`. | | `output_connectors` | dict \| null | optional | `null` | Keyed by `to_stage_<n>`; values are names registered under top-level `connectors:`. |
| `input_connectors` | dict \| null | optional | `null` | Keyed by `from_stage_<n>`; values are names registered under top-level `connectors:`. | | `input_connectors` | dict \| null | optional | `null` | Keyed by `from_stage_<n>`; values are names registered under top-level `connectors:`. |
| `default_sampling_params` | dict \| null | optional | `null` | Baseline sampling params. Deep-merged with pipeline `sampling_constraints` (pipeline wins). | | `default_sampling_params` | dict \| null | optional | `null` | Baseline sampling params. Deep-merged with pipeline `sampling_constraints` (pipeline wins). |
| `engine_extras` | dict | optional | `{}` | Catch-all for engine fields not listed above; deep-merged across overlays and forwarded to the stage engine. | | `engine_extras` | dict | optional | `{}` | Catch-all for keys not listed above; deep-merged across overlays. Also carries per-stage overrides of pipeline-wide settings (e.g. stage-specific `dtype`). |
**Note:** a stage's `devices` count must equal its local world size (`tensor_parallel_size` × `data_parallel_size_local` × `pipeline_parallel_size`, falling back to global `data_parallel_size` when the local size is unset), or `num_replicas` × that product for a replica pool. A mismatch fails early and names the offending stage. A top-level `--tensor-parallel-size` is broadcast to every stage, so it can make a single-GPU stage violate this contract ([issue #5003](gh-issue:5003)); fix that case with `--stage-overrides` (set `tensor_parallel_size` and `devices` together per stage) or set TP only on the multi-GPU stage.
### Connector schema ### Connector schema
@@ -112,12 +60,12 @@ connectors:
name: <ConnectorClassName> # required — class registered in vllm_omni.distributed name: <ConnectorClassName> # required — class registered in vllm_omni.distributed
extra: # optional — forwarded to the connector's __init__ extra: # optional — forwarded to the connector's __init__
<key>: <value> <key>: <value>
# Additional connector-specific options ...
``` ```
| Connector class | Use case | `extra` keys | | Connector class | Use case | `extra` keys |
|--------------------------|-----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| |-----------------|----------|--------------|
| `SharedMemoryConnector` | Same-host KV transfer between stages (default for bundled YAMLs). | None. All payloads use shared memory. | | `SharedMemoryConnector` | Same-host KV transfer between stages (default for bundled YAMLs). | None. All payloads use shared memory. |
| `MooncakeStoreConnector` | Cross-host KV transfer over TCP. Required for multi-node deployments. | `host`, `metadata_server`, `master`, `segment` (int bytes), `localbuf` (int bytes), `proto` (`"tcp"` / `"rdma"`). | | `MooncakeStoreConnector` | Cross-host KV transfer over TCP. Required for multi-node deployments. | `host`, `metadata_server`, `master`, `segment` (int bytes), `localbuf` (int bytes), `proto` (`"tcp"` / `"rdma"`). |
A stage references a connector by name in its `input_connectors` / `output_connectors`: A stage references a connector by name in its `input_connectors` / `output_connectors`:
@@ -137,7 +85,7 @@ stages:
### CLI flags ### CLI flags
| Flag | Description | | Flag | Description |
| ------ | ------------- | |------|-------------|
| `--deploy-config PATH` | Load a deploy YAML. **Optional** — when omitted, the bundled `vllm_omni/deploy/<model_type>.yaml` is auto-loaded by the model registry. | | `--deploy-config PATH` | Load a deploy YAML. **Optional** — when omitted, the bundled `vllm_omni/deploy/<model_type>.yaml` is auto-loaded by the model registry. |
| `--stage-overrides JSON` | Per-stage JSON overrides, e.g. `'{"0":{"gpu_memory_utilization":0.5}}'`. Per-stage values always win over global flags. | | `--stage-overrides JSON` | Per-stage JSON overrides, e.g. `'{"0":{"gpu_memory_utilization":0.5}}'`. Per-stage values always win over global flags. |
| `--async-chunk` / `--no-async-chunk` | Flip the deploy YAML's `async_chunk:` bool. Unset (default) leaves the YAML value in force. | | `--async-chunk` / `--no-async-chunk` | Flip the deploy YAML's `async_chunk:` bool. Unset (default) leaves the YAML value in force. |
@@ -251,7 +199,7 @@ CUDA_VISIBLE_DEVICES=0 vllm serve Qwen/Qwen3-Omni-30B-A3B-Instruct --omni \
Effective config per stage after the merge: Effective config per stage after the merge:
| Stage | Field | Final value | Source | | Stage | Field | Final value | Source |
| ------- | ------- | ------------- | -------- | |-------|-------|-------------|--------|
| 0 | `gpu_memory_utilization` | `0.9` | base YAML (overlay didn't touch stage 0) | | 0 | `gpu_memory_utilization` | `0.9` | base YAML (overlay didn't touch stage 0) |
| 0 | `max_num_seqs` | `8` | per-stage CLI (`--stage-overrides`) — wins over base `32` | | 0 | `max_num_seqs` | `8` | per-stage CLI (`--stage-overrides`) — wins over base `32` |
| 0 | `max_model_len` | `16384` | global CLI | | 0 | `max_model_len` | `16384` | global CLI |
@@ -275,17 +223,300 @@ when loading a deploy configuration.
Examples: Examples:
For offline inference (assuming the necessary dependencies have been imported): For offline inference (assuming the necessary dependencies have been imported):
```python ```python
model_name = "Qwen/Qwen2.5-Omni-7B" model_name = "Qwen/Qwen2.5-Omni-7B"
omni = Omni(model=model_name, deploy_config="/path/to/deploy_config.yaml") omni = Omni(model=model_name, deploy_config="/path/to/deploy_config.yaml")
``` ```
For online serving: For online serving:
```bash ```bash
vllm serve Qwen/Qwen2.5-Omni-7B --omni --port 8091 --deploy-config /path/to/deploy_config.yaml vllm serve Qwen/Qwen2.5-Omni-7B --omni --port 8091 --deploy-config /path/to/deploy_config.yaml
``` ```
!!! important !!! important
We are actively iterating on the definition of deployment configurations, and we welcome feedback from users and developers. We are actively iterating on the definition of deployment configurations, and we welcome feedback from users and developers.
## Legacy stage configuration reference
The remainder of this page documents the deprecated `stage_args` schema for models that have not migrated to deploy configurations. The following historical Qwen2.5-Omni configuration illustrates that schema; current Qwen2.5-Omni deployments should use `vllm_omni/deploy/qwen2_5_omni.yaml` instead.
```python
# Historical stage configuration for Qwen2.5-Omni.
stage_args:
- stage_id: 0 # mark the unique id for each stage
runtime: # The disaggregated configuration
process: true # Run this stage in a separate process
devices: "0" # Logical device index for this stage (mapped through CUDA_VISIBLE_DEVICES / ASCEND_RT_VISIBLE_DEVICES if set)
engine_args: # Engine arguments for a certain engine
model_stage: thinker
max_num_seqs: 1
model_arch: Qwen2_5OmniForConditionalGeneration # The model implementation registered in model_executor/models/registry.py
worker_type: ar # The specific worker used
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler # The specific scehduler used
gpu_memory_utilization: 0.8 # The gpu memory allocation for the stage within a single chip
enforce_eager: true # Now we only support eager mode
trust_remote_code: true # Needed by huggingface config parsing
engine_output_type: latent # It claims that the stage will input latent hiddenstates besides token ids
enable_prefix_caching: false # For request with hiddenstates output, the prefix caching is not supported now
is_comprehension: true # If the stage is a text or multimodal comprehension module. If it is, the AsyncOmni will use its tokenizer as default
final_output: true # If the stage has output as part of final outputs. If it is false, which means that the stage only works as a intermediate role.
final_output_type: text # What is the final output type. It can be text and audio now.
default_sampling_params: # sampling parameters for the stage. Their meaning aligns with vLLM.
temperature: 0.0
top_p: 1.0
top_k: -1
max_tokens: 2048
seed: 42
detokenize: True
repetition_penalty: 1.1
- stage_id: 1
runtime:
process: true
devices: "1"
engine_args:
model_stage: talker
max_num_seqs: 3
model_arch: Qwen2_5OmniForConditionalGeneration
worker_type: ar
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
gpu_memory_utilization: 0.8
enforce_eager: true
trust_remote_code: true
enable_prefix_caching: false
engine_output_type: latent
engine_input_source: [0]
default_sampling_params:
temperature: 0.9
top_p: 0.8
top_k: 40
max_tokens: 2048
seed: 42
detokenize: True
repetition_penalty: 1.05
stop_token_ids: [8294]
- stage_id: 2
runtime:
process: true
devices: "0" # Example: use a different GPU than the previous stage; use "0" if single GPU
engine_args:
model_stage: code2wav
max_num_seqs: 1
model_arch: Qwen2_5OmniForConditionalGeneration
worker_type: generation
scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
gpu_memory_utilization: 0.15
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
edges:
- from: 0 # thinker → talker: trigger only after receiving full input (-1)
to: 1
- from: 1 # talker → code2wav: trigger only after receiving full input (-1)
to: 2
```
## Legacy stage configuration arguments
Each stage in the `stage_args` list contains the following configuration options:
### `stage_id`
A unique identifier for each stage in the multi-stage pipeline. Stages are numbered sequentially starting from 0, and this ID is used to reference stages in inter-stage dependencies (e.g., `engine_input_source`).
### `prompt_expand_func` (Optional)
A custom Python function hook for the LLM stage (Stage 0) that expands a single incoming prompt object into multiple prompts. This is primarily used for multi-modal Classifier-Free Guidance (CFG), where it generates the necessary companion requests (like a negative text prompt) and tags them with internal roles (e.g., `cfg_text`). This ensures the upstream LLM generates the needed contextual hidden states for both the conditional and unconditional generations simultaneously.
### `cfg_kv_collect_func` (Optional)
A custom Python function hook for downstream diffusion stages (Stage 1+) to collect, map, and process the KV caches transferred from the companion requests fired by `prompt_expand_func`. It aggregates the hidden condition states cleanly (e.g., binding them as `cfg_text_past_key_values` and `cfg_text_kv_metadata`), allowing the diffusion runtime to perform CFG smoothly without redundantly evaluating text paths on the DiT workers.
### `runtime`
Configuration for disaggregated execution of the stage, controlling how the stage is deployed and executed.
#### `runtime.process`
Whether to run this stage in a separate process. When set to `true`, the stage will be executed in an isolated process, enabling better resource isolation and parallel execution across different stages. This is essential for multi-GPU deployments where different stages run on different devices.
Default: `true`
#### `runtime.devices`
Logical device indices for this stage, specified as a string. Values are **logical indices** (`0`, `1`, `2`, ...) — not physical GPU IDs — and are mapped through the platform's visibility env var (`CUDA_VISIBLE_DEVICES` on CUDA, `ASCEND_RT_VISIBLE_DEVICES` on NPU) before being applied via `torch.cuda.set_device()` (or the equivalent).
Example: if `CUDA_VISIBLE_DEVICES=0,2,4` is set in the environment, then `devices: "0"` selects physical GPU 0 (the first visible), `devices: "1"` selects physical GPU 2, and `devices: "0,1"` makes physical GPUs 0 and 2 available to the stage. If no visibility env var is set, logical and physical IDs coincide.
Default: `"0"`
#### `engine_args.max_num_seqs`
The maximum number of sequences for concurrent processing in this stage. For
LLM stages, this controls the vLLM scheduler's maximum concurrent sequences.
For diffusion stages, this controls scheduler wave capacity in both request
batch mode and step batch mode. `max_num_seqs: 1` is the serial/conservative
path; values above `1` allow compatible requests to batch when the selected
diffusion pipeline supports that batching mode.
Default: `1`
#### `engine_args.request_batch_max_wait_ms`
The maximum time, in milliseconds, that a diffusion request-mode stage may wait
before the first `schedule()` of a new scheduler wave so compatible requests can
accumulate for request-level batching. This only applies to diffusion pipelines
that support request-level batching with `step_execution` disabled.
Use this together with `max_num_seqs > 1` for bursty serving traffic. `0`
disables admission waiting and preserves the lowest first-request latency.
For diffusion execution and batching tuning, see
[Diffusion Execution Modes](../user_guide/diffusion/execution_modes.md).
Default: `0.0`
### `engine_args`
Engine arguments for configuring the LLM engine, diffusion engine, or other engine types used by this stage.
#### `engine_args.model_stage`
The name identifier for this model stage within the multi-stage architecture. This is used internally to distinguish different stages of the same model (e.g., "thinker", "talker", "code2wav" in Qwen2.5-Omni).
#### `engine_args.model_arch`
The model architecture class name that is registered in `model_executor/models/registry.py`. This specifies which model implementation to use for this stage. The class must be registered in the model registry for vLLM-Omni to locate and instantiate it.
#### `engine_args.worker_cls`
The specific worker class to use for this stage. This determines how the model computations are executed. Examples include `vllm_omni.worker.gpu_ar_worker.GPUARWorker` for autoregressive stages and `vllm_omni.worker.gpu_generation_worker.GPUGenerationWorker` for diffusion-based stages.
#### `engine_args.scheduler_cls`
The scheduler class to use for this stage. The scheduler manages request queuing, batching, and execution order. Examples include `vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler` for standard stages and `vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler` for diffusion stages.
#### `engine_args.gpu_memory_utilization`
The fraction of GPU memory to allocate for this stage within a single GPU chip. This is a value between 0.0 and 1.0, where 0.8 means 80% of the GPU memory will be used by this stage. This allows fine-grained control over memory allocation when multiple stages share the same GPU or when reserving memory for other operations.
Default: `0.8`
!!! tip "Memory Configuration Guide"
For detailed information on how to calculate memory requirements and properly configure `gpu_memory_utilization`, see the [GPU Memory Calculation and Configuration Guide](./gpu_memory_utilization.md).
#### `engine_args.enforce_eager`
Whether to enforce eager execution mode. When set to `true`, the engine will run in eager mode without using CUDA graphs or other compilation optimizations. Currently, vLLM-Omni only supports eager mode.
Default: `true`
#### `engine_args.trust_remote_code`
Whether to trust remote code when loading models from Hugging Face. This is required for models that use custom code in their configuration files. Set to `true` when loading models that require custom model implementations.
Default: `true`
#### `engine_args.engine_output_type`
Specifies the type of output produced by this stage's engine. This determines what kind of data flows to downstream stages. Possible values include `latent` (hidden states), `text` (tokenized text), and `audio` (audio waveforms). When set to `latent`, the stage outputs latent hidden states in addition to token IDs, which are consumed by downstream stages.
Default: `latent`
#### `engine_args.enable_prefix_caching`
Whether to enable prefix caching for this stage. Prefix caching can improve performance by caching KV cache for common prompt prefixes. However, for requests that output hidden states (when `engine_output_type` is `latent`), prefix caching is not currently supported and should be set to `false`.
Default: `false`
### `is_comprehension`
Whether this stage is a text or multimodal comprehension module. When set to `true`, the stage acts as a comprehension module that processes input text or multimodal content. If this is the first comprehension stage, `AsyncOmni` will use its tokenizer as the default tokenizer for the entire pipeline.
Default: `true`
### `final_output`
Whether this stage produces output that is part of the final outputs returned to the user. When set to `false`, the stage only works as an intermediate stage, processing data that flows to downstream stages but not contributing directly to the final response.
Default: `true`
### `final_output_type`
The type of final output produced by this stage. This specifies what format the output will be in when returned to the user. Currently supported values are `text` (for text generation) and `audio` (for audio generation).
Default: `text`
### `default_sampling_params`
Default sampling parameters for this stage. These parameters control the generation behavior and align with vLLM's sampling parameter semantics. These defaults are used when no explicit sampling parameters are provided in the request.
#### `default_sampling_params.temperature`
Sampling temperature for controlling randomness. Lower values (e.g., 0.0) make the output more deterministic and focused, while higher values increase randomness.
Default: `0.0`
#### `default_sampling_params.top_p`
Nucleus sampling parameter. Only tokens with cumulative probability mass up to `top_p` are considered. This helps filter out low-probability tokens while maintaining diversity.
Default: `1.0`
#### `default_sampling_params.top_k`
Top-k sampling parameter. Only the top `k` most likely tokens are considered. Set to `-1` to disable top-k filtering and consider all tokens.
Default: `-1`
#### `default_sampling_params.max_tokens`
Maximum number of tokens to generate in this stage. This limits the length of the output sequence.
Default: `2048`
#### `default_sampling_params.seed`
Random seed for reproducible generation. When set, the random number generator will be initialized with this seed to ensure consistent outputs across runs.
Default: `42`
#### `default_sampling_params.detokenize`
Whether to detokenize the output tokens into text. When set to `true`, token IDs are converted back to readable text strings.
Default: `True`
#### `default_sampling_params.repetition_penalty`
Penalty applied to tokens that have already appeared in the generated sequence. Values greater than 1.0 discourage repetition, while values less than 1.0 encourage it. A value of 1.0 applies no penalty.
Default: `1.1`
### `tts_args` (TTS stages only)
Configuration for Text-to-Speech specific parameters. This section is only applicable to TTS model stages (e.g., `qwen3_tts`).
#### `tts_args.max_instructions_length`
Maximum character length for voice style/emotion instructions. Instructions exceeding this limit will be rejected with a validation error.
Default: `500`
This value can be overridden at runtime using the `--tts-max-instructions-length` CLI parameter when starting the server.
+1 -1
View File
@@ -46,7 +46,7 @@ class Omni:
Args: Args:
model: Model name or path model: Model name or path
deploy_config: Optional path to a deploy configuration stage_configs: Optional stage configurations
**kwargs: Additional arguments passed to the engine **kwargs: Additional arguments passed to the engine
Example: Example:
Loaded 100 of 2138 files, more files were not shown because too many files have changed in this diff. Show more