Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
089851e55a | ||
|
|
3466ff9b98 | ||
|
|
e16473c6b5 | ||
|
|
14d67bcd60 | ||
|
|
5234e3599c | ||
|
|
3aefb31727 | ||
|
|
c8210b8b5c | ||
|
|
879d19c4c2 | ||
|
|
af1825e07d |
No files matched your search
@@ -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}}')
|
||||||
import torch
|
if [ -z "$docker_root" ]; then
|
||||||
|
echo "Failed to determine Docker root directory."
|
||||||
expected = int(os.environ.get("VLLM_CI_EXPECTED_GPU_COUNT", "1"))
|
exit 1
|
||||||
assert torch.version.hip, "PyTorch is not a ROCm build"
|
|
||||||
assert torch.cuda.is_available(), "ROCm GPU is not available to PyTorch"
|
|
||||||
actual = torch.cuda.device_count()
|
|
||||||
assert actual == expected, f"Expected {expected} ROCm GPU(s), found {actual}"
|
|
||||||
PY
|
|
||||||
|
|
||||||
echo "Commands:${commands}"
|
|
||||||
if /bin/bash -o pipefail -c '
|
|
||||||
set -E
|
|
||||||
test_status=0
|
|
||||||
trap '\''
|
|
||||||
command_status=$?
|
|
||||||
# Keep running subsequent commands, but retain a failure for the final exit.
|
|
||||||
# Prefer a test failure over pytest "no tests collected" when both occur.
|
|
||||||
if (( test_status == 0 || (test_status == 5 && command_status != 5) )); then
|
|
||||||
test_status=${command_status}
|
|
||||||
fi
|
fi
|
||||||
'\'' ERR
|
echo "Docker root directory: $docker_root"
|
||||||
eval "$1"
|
# Check disk usage of the filesystem where Docker's root directory is located
|
||||||
exit "${test_status}"
|
disk_usage=$(df "$docker_root" | tail -1 | awk '{print $5}' | sed 's/%//')
|
||||||
' _ "${commands}"; then
|
# Define the threshold
|
||||||
exit 0
|
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
|
else
|
||||||
exit_code=$?
|
echo "Disk usage is below $threshold%. No cleanup needed."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Call the cleanup docker function
|
||||||
|
cleanup_docker
|
||||||
|
|
||||||
|
echo "--- Pulling container"
|
||||||
|
## Temporary change to use AMD Docker Hub to store the vllm-omni image
|
||||||
|
# to bypass the rate limit issue with ECR Public Gallery.
|
||||||
|
# Images are now stored in a separate repository for vllm-omni, instead of vllm-ci.
|
||||||
|
# TODO: @tjtanaa point back to ECR Public Gallery
|
||||||
|
# once the amd agents are configured to use ECR Public Gallery.
|
||||||
|
# image_name="public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:${BUILDKITE_COMMIT}-rocm-omni"
|
||||||
|
image_name="rocm/vllm-omni:${BUILDKITE_COMMIT}"
|
||||||
|
container_name="rocm_${BUILDKITE_COMMIT}_$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 10; echo)"
|
||||||
|
|
||||||
|
# TODO: @tjtanaa uncomment this once the amd agents are configured to use ECR Public Gallery.
|
||||||
|
# # Install AWS CLI to authenticate to ECR Public Gallery to get higher rate limit for pulling images
|
||||||
|
# sudo apt-get update && sudo apt-get install -y awscli
|
||||||
|
## Use safe docker login helper to prevent race conditions
|
||||||
|
# source "$(dirname "${BASH_SOURCE[0]}")/../../common/scripts/docker_login_ecr_public.sh"
|
||||||
|
# safe_docker_login_ecr_public
|
||||||
|
|
||||||
|
## Pull the container from AMD Docker Hub
|
||||||
|
|
||||||
|
docker pull "${image_name}"
|
||||||
|
|
||||||
|
remove_docker_container() {
|
||||||
|
docker ps -aq --filter "name=${container_name}" | xargs -r docker rm -f || true
|
||||||
|
docker image rm -f "${image_name}" || true
|
||||||
|
}
|
||||||
|
trap remove_docker_container EXIT
|
||||||
|
|
||||||
|
echo "--- Running container"
|
||||||
|
|
||||||
|
HF_CACHE="$(realpath ~)/huggingface"
|
||||||
|
mkdir -p "${HF_CACHE}"
|
||||||
|
HF_MOUNT="/root/.cache/huggingface"
|
||||||
|
|
||||||
|
if [[ -n "${TEST_COMMAND:-}" ]]; then
|
||||||
|
commands="$TEST_COMMAND"
|
||||||
|
else
|
||||||
|
commands="$@"
|
||||||
|
fi
|
||||||
|
echo "Commands:$commands"
|
||||||
|
|
||||||
|
PARALLEL_JOB_COUNT=8
|
||||||
|
MYPYTHONPATH=".."
|
||||||
|
|
||||||
|
# Test that we're launching on the machine that has
|
||||||
|
# proper access to GPUs
|
||||||
|
render_gid=$(getent group render | cut -d: -f3)
|
||||||
|
if [[ -z "$render_gid" ]]; then
|
||||||
|
echo "Error: 'render' group not found. This is required for GPU access." >&2
|
||||||
|
exit 1
|
||||||
fi
|
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}"
|
|
||||||
@@ -7,64 +7,54 @@ 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 --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml:coverage-model-executor-$$BUILDKITE_PARALLEL_JOB.xml"
|
# 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: 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
|
# 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 --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml:coverage-diffusion-$$BUILDKITE_PARALLEL_JOB.xml"
|
|
||||||
|
|
||||||
- label: "Simple · Engine&Entrypoints Test"
|
|
||||||
agent_pool: mi300_1
|
|
||||||
depends_on: amd-build
|
|
||||||
mirror_hardwares: [amdproduction]
|
|
||||||
grade: Blocking
|
|
||||||
commands:
|
|
||||||
- export VLLM_ROCM_USE_AITER=0
|
|
||||||
- "pytest -sv tests/entrypoints tests/engine -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
|
|
||||||
|
|
||||||
- label: "Simple · Other Test"
|
- 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 --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
|
- "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
|
||||||
@@ -72,7 +62,7 @@ steps:
|
|||||||
- export VLLM_LOGGING_LEVEL=DEBUG
|
- export VLLM_LOGGING_LEVEL=DEBUG
|
||||||
- export VLLM_WORKER_MULTIPROC_METHOD=spawn
|
- export VLLM_WORKER_MULTIPROC_METHOD=spawn
|
||||||
- |
|
- |
|
||||||
bash -c '
|
timeout 20m bash -c '
|
||||||
set +e
|
set +e
|
||||||
pytest -s -v tests/diffusion/offloader/test_diffusion_cpu_offload.py
|
pytest -s -v tests/diffusion/offloader/test_diffusion_cpu_offload.py
|
||||||
EXIT1=\$?
|
EXIT1=\$?
|
||||||
@@ -82,7 +72,7 @@ steps:
|
|||||||
'
|
'
|
||||||
|
|
||||||
- label: "Diffusion Sequence Parallelism Test (Need 4 GPUs)"
|
- label: "Diffusion Sequence Parallelism Test (Need 4 GPUs)"
|
||||||
agent_pool: mi300_4
|
agent_pool: mi325_4
|
||||||
depends_on: amd-build
|
depends_on: amd-build
|
||||||
mirror_hardwares: [amdproduction]
|
mirror_hardwares: [amdproduction]
|
||||||
grade: Blocking
|
grade: Blocking
|
||||||
@@ -94,7 +84,7 @@ steps:
|
|||||||
|
|
||||||
# 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
|
||||||
@@ -102,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
|
||||||
@@ -110,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
|
||||||
@@ -118,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
|
||||||
@@ -131,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
|
||||||
@@ -142,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
|
||||||
@@ -157,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:
|
||||||
@@ -165,7 +155,7 @@ 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
|
||||||
@@ -173,23 +163,22 @@ steps:
|
|||||||
- 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 -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: 2
|
|
||||||
depends_on: amd-build
|
depends_on: amd-build
|
||||||
mirror_hardwares: [amdproduction]
|
mirror_hardwares: [amdproduction]
|
||||||
grade: Blocking
|
grade: Blocking
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
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"
|
||||||
pytest -s -v tests/e2e/online_serving/test_qwen3_tts_customvoice.py tests/e2e/offline_inference/test_qwen3_tts_customvoice.py -m "advanced_model" --run-level "advanced_model" --num-shards=$$BUILDKITE_PARALLEL_JOB_COUNT --shard-id=$$BUILDKITE_PARALLEL_JOB
|
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"
|
||||||
'
|
'
|
||||||
|
|
||||||
- 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
|
||||||
@@ -207,7 +196,7 @@ steps:
|
|||||||
# 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
|
||||||
@@ -219,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
|
||||||
@@ -231,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
|
||||||
|
|||||||
@@ -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 --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml:coverage-model-executor-$$BUILDKITE_PARALLEL_JOB.xml"
|
- "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 --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
|
|
||||||
|
|
||||||
- label: "Simple · Engine&Entrypoints Test"
|
|
||||||
agent_pool: mi300_1
|
|
||||||
depends_on: amd-build
|
|
||||||
mirror_hardwares: [amdproduction]
|
|
||||||
grade: Blocking
|
|
||||||
commands:
|
|
||||||
- export VLLM_ROCM_USE_AITER=0
|
|
||||||
- "pytest -sv tests/entrypoints tests/engine -m 'core_model and cpu' --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
|
|
||||||
|
|
||||||
- label: "Simple · Other Test"
|
- 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 --cov=vllm_omni --cov-report=term-missing:skip-covered --cov-report=xml"
|
- "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
|
||||||
@@ -109,7 +96,7 @@ steps:
|
|||||||
- 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
|
||||||
|
|
||||||
- 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
|
||||||
@@ -117,7 +104,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 · 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
|
||||||
@@ -125,7 +112,7 @@ steps:
|
|||||||
- timeout 15m pytest -sv tests/diffusion/models/ -m 'core_model and cuda' --run-level "core_model"
|
- timeout 15m pytest -sv tests/diffusion/models/ -m 'core_model and cuda' --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
|
||||||
@@ -139,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
|
||||||
@@ -150,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
|
||||||
@@ -162,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
|
||||||
@@ -175,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
|
||||||
@@ -186,7 +173,7 @@ 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
|
||||||
@@ -199,7 +186,7 @@ steps:
|
|||||||
# 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
|
||||||
@@ -211,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
|
||||||
@@ -223,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
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -9,11 +9,6 @@
|
|||||||
# 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_npu_2 — ascend-a3, resource_class npu-2
|
||||||
#
|
|
||||||
# The ``l4_*`` presets run under the ``docker`` plugin, which does not put
|
|
||||||
# ``buildkite-agent`` on PATH inside the container the way the ``kubernetes``
|
|
||||||
# presets do, so they set ``mount-buildkite-agent: true``; without it any step
|
|
||||||
# calling ``buildkite-agent`` (artifact upload, annotate, meta-data) fails there.
|
|
||||||
|
|
||||||
x-npu-k8s-plugins: &npu_k8s_plugins
|
x-npu-k8s-plugins: &npu_k8s_plugins
|
||||||
- kubernetes:
|
- kubernetes:
|
||||||
@@ -31,7 +26,6 @@ mirror_hardwares:
|
|||||||
image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
|
image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
|
||||||
always-pull: true
|
always-pull: true
|
||||||
propagate-environment: true
|
propagate-environment: true
|
||||||
mount-buildkite-agent: true
|
|
||||||
shm-size: "8gb"
|
shm-size: "8gb"
|
||||||
environment:
|
environment:
|
||||||
- HF_HOME=/fsx/hf_cache
|
- HF_HOME=/fsx/hf_cache
|
||||||
@@ -47,7 +41,6 @@ mirror_hardwares:
|
|||||||
image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
|
image: public.ecr.aws/q9t5s3a7/vllm-ci-test-repo:$BUILDKITE_COMMIT
|
||||||
always-pull: true
|
always-pull: true
|
||||||
propagate-environment: true
|
propagate-environment: true
|
||||||
mount-buildkite-agent: true
|
|
||||||
shm-size: "8gb"
|
shm-size: "8gb"
|
||||||
environment:
|
environment:
|
||||||
- HF_HOME=/fsx/hf_cache
|
- HF_HOME=/fsx/hf_cache
|
||||||
|
|||||||
@@ -1,82 +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.
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# 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}"
|
|
||||||
@@ -76,9 +76,6 @@ steps:
|
|||||||
- 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
|
||||||
commands:
|
commands:
|
||||||
@@ -95,24 +92,31 @@ steps:
|
|||||||
- 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
|
||||||
- .buildkite/common/scripts/run_cov_split.sh
|
|
||||||
- pyproject.toml
|
|
||||||
# 40 -> 60: the coverage split runs the model twice.
|
|
||||||
timeout_in_minutes: 60
|
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
|
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
|
||||||
.buildkite/common/scripts/run_cov_split.sh \
|
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'
|
||||||
--model-id qwen3_tts \
|
"
|
||||||
--offline tests/e2e/offline_inference/test_qwen3_tts_base.py \
|
mirror_hardwares: l4_1
|
||||||
--online tests/e2e/online_serving/test_qwen3_tts_base.py \
|
|
||||||
-- -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"
|
||||||
@@ -120,10 +124,6 @@ steps:
|
|||||||
- tests/e2e/offline_inference/test_qwen3_omni.py
|
- tests/e2e/offline_inference/test_qwen3_omni.py
|
||||||
- tests/e2e/online_serving/test_qwen3_omni.py
|
- tests/e2e/online_serving/test_qwen3_omni.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
|
||||||
timeout_in_minutes: 50
|
timeout_in_minutes: 50
|
||||||
@@ -135,11 +135,6 @@ steps:
|
|||||||
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/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:
|
||||||
@@ -161,35 +156,62 @@ steps:
|
|||||||
- 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_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/deploy/minicpmo_4_5_duplex.yaml
|
- vllm_omni/deploy/minicpmo_4_5_duplex.yaml
|
||||||
- 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 '
|
||||||
@@ -198,12 +220,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 "
|
||||||
@@ -211,3 +244,21 @@ steps:
|
|||||||
pytest -s -v tests/e2e/offline_inference/test_wan22_t2v.py tests/e2e/online_serving/test_wan22_t2v.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model'
|
pytest -s -v tests/e2e/offline_inference/test_wan22_t2v.py tests/e2e/online_serving/test_wan22_t2v.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model'
|
||||||
"
|
"
|
||||||
mirror_hardwares: h100_1
|
mirror_hardwares: h100_1
|
||||||
|
|
||||||
|
- label: "Diffusion · Z Image Test"
|
||||||
|
source_file_dependencies:
|
||||||
|
- tests/e2e/offline_inference/test_z_image.py
|
||||||
|
- tests/e2e/online_serving/test_z_image.py
|
||||||
|
- vllm_omni/diffusion/models/z_image/
|
||||||
|
timeout_in_minutes: 30
|
||||||
|
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'
|
||||||
|
mirror_hardwares: l4_1
|
||||||
|
- label: "Diffusion · FLUX.2 Klein Test"
|
||||||
|
source_file_dependencies:
|
||||||
|
- tests/e2e/offline_inference/test_flux2_klein.py
|
||||||
|
- vllm_omni/diffusion/models/flux2_klein/
|
||||||
|
timeout_in_minutes: 30
|
||||||
|
commands:
|
||||||
|
- pytest -s -v tests/e2e/offline_inference/test_flux2_klein.py -m 'advanced_model and cuda and diffusion' --run-level 'advanced_model'
|
||||||
|
mirror_hardwares: l4_1
|
||||||
@@ -159,7 +159,19 @@ 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
|
||||||
|
|
||||||
|
- label: ":full_moon: TTS · Accuracy Test · VoxCPM2"
|
||||||
|
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
|
mirror_hardwares: l4_1
|
||||||
|
|
||||||
- label: ":full_moon: TTS · Perf Test"
|
- label: ":full_moon: TTS · Perf Test"
|
||||||
@@ -176,6 +188,35 @@ steps:
|
|||||||
exit $$EXIT
|
exit $$EXIT
|
||||||
mirror_hardwares: h100_2
|
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=$$?
|
||||||
|
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
|
||||||
|
exit $$EXIT
|
||||||
|
mirror_hardwares: h100_1
|
||||||
|
|
||||||
|
- label: ":full_moon: TTS · Higgs-Audio-V3 · Perf Test"
|
||||||
|
key: nightly-tts-performance-higgs-audio-v3
|
||||||
|
timeout_in_minutes: 180
|
||||||
|
commands:
|
||||||
|
- export BENCHMARK_DIR=tests/dfx/perf/results
|
||||||
|
- export VLLM_USE_DEEP_GEMM="0"
|
||||||
|
- export VLLM_MOE_USE_DEEP_GEMM="0"
|
||||||
|
- |
|
||||||
|
set +e
|
||||||
|
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_higgs_audio_v3.json
|
||||||
|
EXIT=$$?
|
||||||
|
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
|
||||||
|
exit $$EXIT
|
||||||
|
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
|
||||||
depends_on: upload-nightly-pipeline
|
depends_on: upload-nightly-pipeline
|
||||||
@@ -205,7 +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.
|
# 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
|
||||||
@@ -213,6 +255,15 @@ steps:
|
|||||||
- >-
|
- >-
|
||||||
pytest -sv
|
pytest -sv
|
||||||
tests/e2e/online_serving/test_qwen_image_expansion.py
|
tests/e2e/online_serving/test_qwen_image_expansion.py
|
||||||
|
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"
|
-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
|
||||||
@@ -223,14 +274,47 @@ steps:
|
|||||||
- >-
|
- >-
|
||||||
pytest -sv
|
pytest -sv
|
||||||
tests/e2e/online_serving/test_qwen_image_expansion.py
|
tests/e2e/online_serving/test_qwen_image_expansion.py
|
||||||
|
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"
|
-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) · 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
|
||||||
|
commands:
|
||||||
|
- >-
|
||||||
|
pytest -sv
|
||||||
|
tests/e2e/online_serving/test_bagel_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) · GLM-Image Function Test with H100"
|
||||||
|
timeout_in_minutes: 120
|
||||||
|
commands:
|
||||||
|
- >-
|
||||||
|
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"
|
- label: ":full_moon: Diffusion X2I(&A&T) · Function Test with L4"
|
||||||
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" --run-level "full_model" --ignore=tests/e2e/accuracy
|
- 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"
|
||||||
@@ -243,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
|
||||||
|
|
||||||
@@ -271,6 +355,70 @@ steps:
|
|||||||
exit $$EXIT
|
exit $$EXIT
|
||||||
mirror_hardwares: h100_4
|
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.5.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.5.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.5.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=$$?
|
||||||
|
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 X2I(&A&T) · Perf Test · BAGEL"
|
||||||
|
key: nightly-diffusion-x2iat-performance-bagel
|
||||||
|
timeout_in_minutes: 180
|
||||||
|
commands:
|
||||||
|
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
|
||||||
|
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
|
||||||
|
- export CACHE_DIT_VERSION=1.5.0
|
||||||
|
- |
|
||||||
|
set +e
|
||||||
|
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_bagel_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_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
|
||||||
depends_on: upload-nightly-pipeline
|
depends_on: upload-nightly-pipeline
|
||||||
@@ -292,6 +440,12 @@ steps:
|
|||||||
- pytest -s -v tests/e2e/online_serving/test_wan22_expansion.py -k "i2v" -m "full_model and cuda" --run-level "full_model"
|
- 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
|
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
|
||||||
|
|
||||||
- label: ":full_moon: Diffusion X2V · Doc Test"
|
- label: ":full_moon: Diffusion X2V · Doc Test"
|
||||||
timeout_in_minutes: 60
|
timeout_in_minutes: 60
|
||||||
commands:
|
commands:
|
||||||
@@ -302,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"
|
||||||
@@ -322,6 +477,8 @@ 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"
|
- label: ":full_moon: Diffusion X2V · Perf Test"
|
||||||
@@ -399,6 +556,16 @@ steps:
|
|||||||
- 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"
|
||||||
@@ -409,20 +576,32 @@ steps:
|
|||||||
- nightly-omni-performance-vllm-text
|
- nightly-omni-performance-vllm-text
|
||||||
- nightly-omni-performance-multi-replicas
|
- nightly-omni-performance-multi-replicas
|
||||||
- nightly-tts-performance
|
- nightly-tts-performance
|
||||||
|
- nightly-tts-performance-higgs-audio-v3
|
||||||
- nightly-diffusion-x2iat-performance-qwen-image
|
- nightly-diffusion-x2iat-performance-qwen-image
|
||||||
|
- 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-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
|
- 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-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-omni-performance-vllm-text
|
- 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-omni-performance-multi-replicas
|
- 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-x2iat-performance-qwen-image
|
- 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-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/*.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"
|
||||||
|
|||||||
@@ -169,10 +169,6 @@ steps:
|
|||||||
source_file_dependencies:
|
source_file_dependencies:
|
||||||
- tests/e2e/online_serving/test_qwen3_omni.py
|
- tests/e2e/online_serving/test_qwen3_omni.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:
|
||||||
@@ -185,11 +181,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:
|
||||||
- |
|
- |
|
||||||
@@ -201,11 +192,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/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:
|
||||||
- |
|
- |
|
||||||
@@ -224,24 +210,16 @@ steps:
|
|||||||
- 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_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/deploy/minicpmo_4_5_duplex.yaml
|
- vllm_omni/deploy/minicpmo_4_5_duplex.yaml
|
||||||
- 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 20m bash -c "
|
timeout 20m bash -c "
|
||||||
@@ -261,13 +239,25 @@ steps:
|
|||||||
- 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'
|
- 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'
|
||||||
mirror_hardwares: h100_1
|
mirror_hardwares: h100_1
|
||||||
|
|
||||||
|
- label: "TTS · VoxCPM2 Test"
|
||||||
|
source_file_dependencies:
|
||||||
|
- 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/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/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
|
||||||
commands:
|
commands:
|
||||||
@@ -282,9 +272,6 @@ steps:
|
|||||||
source_file_dependencies:
|
source_file_dependencies:
|
||||||
- tests/e2e/online_serving/test_qwen3_tts_base.py
|
- tests/e2e/online_serving/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
|
||||||
commands:
|
commands:
|
||||||
@@ -297,11 +284,52 @@ steps:
|
|||||||
"
|
"
|
||||||
mirror_hardwares: l4_1
|
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 "
|
||||||
@@ -314,9 +342,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 "
|
||||||
@@ -329,8 +354,6 @@ steps:
|
|||||||
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 "
|
||||||
@@ -338,3 +361,15 @@ steps:
|
|||||||
pytest -s -v tests/e2e/online_serving/test_qwen_image.py -m 'core_model' --run-level 'core_model'
|
pytest -s -v tests/e2e/online_serving/test_qwen_image.py -m 'core_model' --run-level 'core_model'
|
||||||
"
|
"
|
||||||
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 'core_model' --run-level 'core_model'
|
||||||
|
"
|
||||||
|
mirror_hardwares: h100_1
|
||||||
@@ -30,6 +30,14 @@ steps:
|
|||||||
- pytest -s -v tests/dfx/reliability/test_reliability_hunyuan_image.py -m "slow"
|
- pytest -s -v tests/dfx/reliability/test_reliability_hunyuan_image.py -m "slow"
|
||||||
mirror_hardwares: h100_4
|
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
|
||||||
|
|
||||||
- group: "Reliability Test - Invalid parameters 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"
|
||||||
@@ -100,22 +108,6 @@ 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
|
|
||||||
# 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"
|
- label: "Diffusion · L4"
|
||||||
timeout_in_minutes: 60
|
timeout_in_minutes: 60
|
||||||
commands:
|
commands:
|
||||||
@@ -141,4 +133,4 @@ steps:
|
|||||||
pytest -sv
|
pytest -sv
|
||||||
tests/e2e/ -m "slow and diffusion and H100 and distributed_cuda"
|
tests/e2e/ -m "slow and diffusion and H100 and distributed_cuda"
|
||||||
--run-level "full_model"
|
--run-level "full_model"
|
||||||
mirror_hardwares: h100_4
|
mirror_hardwares: h100_2
|
||||||
@@ -87,9 +87,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"
|
||||||
'
|
'
|
||||||
@@ -69,12 +69,6 @@ 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
|
||||||
@@ -102,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.5.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,15 +1,4 @@
|
|||||||
steps:
|
steps:
|
||||||
- label: "Platforms Test"
|
|
||||||
key: ready-platforms-test
|
|
||||||
depends_on: upload-ready-pipeline
|
|
||||||
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'
|
|
||||||
|
|
||||||
- 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
|
||||||
|
|||||||
@@ -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/
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ 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
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ Easy, fast, and cheap omni-modality model serving for everyone
|
|||||||
---
|
---
|
||||||
|
|
||||||
*Latest News* 🔥
|
*Latest News* 🔥
|
||||||
- [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.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
ARG BASE_IMAGE=vllm/vllm-openai-rocm:v0.27.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.27.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
|
||||||
|
|||||||
+5
-22
@@ -43,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
|
||||||
@@ -61,11 +60,7 @@ nav:
|
|||||||
- 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/cpu_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
|
||||||
@@ -76,14 +71,8 @@ nav:
|
|||||||
- Hybrid Sharded Data Parallel: user_guide/diffusion/parallelism/hsdp.md
|
- Hybrid Sharded Data Parallel: user_guide/diffusion/parallelism/hsdp.md
|
||||||
- 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 Parallel: user_guide/diffusion/parallelism/vae_parallel.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
|
||||||
@@ -117,7 +106,6 @@ nav:
|
|||||||
- 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
|
||||||
- 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
|
||||||
@@ -135,18 +123,13 @@ nav:
|
|||||||
- Pipeline Parallel: design/feature/pipeline_parallel.md
|
- Pipeline Parallel: design/feature/pipeline_parallel.md
|
||||||
- Sequence Parallel: design/feature/sequence_parallel.md
|
- Sequence Parallel: design/feature/sequence_parallel.md
|
||||||
- Tensor Parallel: design/feature/tensor_parallel.md
|
- Tensor Parallel: design/feature/tensor_parallel.md
|
||||||
- VAE Patch Parallelism: design/feature/vae_parallel.md
|
- VAE Parallel: 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/cpu_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
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ For introduction, please check [Introduction for stage config](./stage_configs.m
|
|||||||
|
|
||||||
- **[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
|
||||||
@@ -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.
|
||||||
@@ -38,12 +38,11 @@ Each entry under `stages:` accepts any `StageDeployConfig` field directly (no ne
|
|||||||
|-------|------|----------|---------|-------------|
|
|-------|------|----------|---------|-------------|
|
||||||
| `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 | optional | `64` | Max concurrent sequences per stage. |
|
| `max_num_seqs` | int | optional | `64` | Max concurrent sequences per stage. |
|
||||||
| `gpu_memory_utilization` | float | optional | `0.9` | 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 | optional | `1` | TP degree for this stage. |
|
||||||
| `enforce_eager` | bool | optional | `false` | Disable CUDA graphs. |
|
| `enforce_eager` | bool | optional | `false` | Disable CUDA graphs. |
|
||||||
| `max_num_batched_tokens` | int | optional | `32768` | Per-stage prefill/token budget; also contributes to the native maximum in-flight token limit. |
|
| `max_num_batched_tokens` | int | optional | `32768` | Prefill budget. |
|
||||||
| `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`. |
|
| `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). |
|
||||||
| `async_scheduling` | bool \| null | optional | `null` | Per-stage async scheduling toggle. |
|
| `async_scheduling` | bool \| null | optional | `null` | Per-stage async scheduling toggle. |
|
||||||
| `devices` | str | optional | `"0"` | `CUDA_VISIBLE_DEVICES`-style device list. |
|
| `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:`. |
|
||||||
|
|||||||
@@ -327,99 +327,6 @@ On PR builds, `upload_pipeline.py` logs `skip '…' (no changes under …)` for
|
|||||||
- Use **groups** for dashboard readability; keep **E2E Test** as the group name CUDA diff filtering expects for `--e2e` nightly runs on main.
|
- Use **groups** for dashboard readability; keep **E2E Test** as the group name CUDA diff filtering expects for `--e2e` nightly runs on main.
|
||||||
- Prefix labels by model domain: **Omni ·**, **TTS ·**, **Diffusion ·**, **Simple ·**, etc., matching existing steps.
|
- Prefix labels by model domain: **Omni ·**, **TTS ·**, **Diffusion ·**, **Simple ·**, etc., matching existing steps.
|
||||||
|
|
||||||
### Per-model coverage
|
|
||||||
|
|
||||||
Pilot (v1): one Merge-tier (L3) job in `.buildkite/cuda/test-merge.yml` uploads
|
|
||||||
per-model, per-entry-mode coverage as Buildkite artifacts — "TTS · Qwen3-TTS Base
|
|
||||||
Test". It runs on a single GPU so the pilot is cheap to reproduce. (A second
|
|
||||||
diffusion pilot, "Diffusion · Bagel Test", was retired from merge; reintroduce
|
|
||||||
another diffusion coverage job if a second pilot is needed again.) This is a
|
|
||||||
pilot, not a rollout: no dashboard/visualization lives in this repo, and nothing
|
|
||||||
is gated on coverage.
|
|
||||||
|
|
||||||
**Naming convention**: `coverage-<model_id>-<mode>-<step_id>.xml.gz`, where
|
|
||||||
`<model_id>` is the model's directory name under
|
|
||||||
`vllm_omni/diffusion/models/<model_id>/` or
|
|
||||||
`vllm_omni/model_executor/models/<model_id>/` (e.g. `z_image`, `qwen3_omni`),
|
|
||||||
`<mode>` is `online` or `offline`, and `<step_id>` is `BUILDKITE_STEP_ID`
|
|
||||||
(`local` outside Buildkite). `gunzip` before feeding a report to a Cobertura
|
|
||||||
consumer.
|
|
||||||
|
|
||||||
Reports are gzipped because `--cov=vllm_omni` sets coverage's `source`, which
|
|
||||||
makes it walk the package and emit one `<line>` element per statement for every
|
|
||||||
file — including the ones a single model's tests never import. That inventory,
|
|
||||||
not the coverage, is the size: a Bagel report is 7.1 MB, of which 96.5% is
|
|
||||||
`<line>` elements and 77% belongs to the 840 of 1169 files that run never
|
|
||||||
imported. A run covering zero lines produces the same 7 MB. gzip takes it to
|
|
||||||
roughly 410 KB and changes nothing about the data.
|
|
||||||
|
|
||||||
`<step_id>` is what keeps two steps that cover the same model apart. Artifacts are
|
|
||||||
scoped to the build, and a nightly build carries the ready, merge and nightly tiers
|
|
||||||
at once, so several same-model steps land in one namespace — `qwen3_tts` already has
|
|
||||||
both a Base and a CustomVoice job, and `minicpmo_4_5` a base and a duplex one. Two
|
|
||||||
uploads on one path do not fail loudly: an exact-name download can report the path
|
|
||||||
as ambiguous, and a glob download fetches every match concurrently and renames each
|
|
||||||
onto the same destination, so whichever finishes last wins. The step id remains the
|
|
||||||
logical step identifier across retries, while Buildkite's default artifact lookup
|
|
||||||
selects only the latest attempt — `BUILDKITE_JOB_ID` would instead give every
|
|
||||||
attempt its own filename, leaving a failed attempt's partial report to be picked up
|
|
||||||
alongside the real one. A step using `parallelism`/matrix would need
|
|
||||||
`BUILDKITE_PARALLEL_JOB` added, since its jobs share a step id; neither pilot is
|
|
||||||
one.
|
|
||||||
|
|
||||||
**Opting in a new model**: replace the job's combined `pytest` command with
|
|
||||||
[`run_cov_split.sh`](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/common/scripts/run_cov_split.sh),
|
|
||||||
which runs each mode, names the reports, uploads them, and fails the step if
|
|
||||||
either half or the upload failed:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
commands:
|
|
||||||
- |
|
|
||||||
.buildkite/common/scripts/run_cov_split.sh \
|
|
||||||
--model-id <model_id> \
|
|
||||||
--offline <offline test path> \
|
|
||||||
--online <online test path> \
|
|
||||||
-- <the job's existing pytest flags, e.g. -m '...' --run-level '...'>
|
|
||||||
```
|
|
||||||
|
|
||||||
Everything after `--` is forwarded verbatim to both runs with quoting preserved,
|
|
||||||
so an expression like `-m 'advanced_model and cuda'` survives intact and a job can
|
|
||||||
pass anything else it needs. `--offline`/`--online` are repeatable and only one is
|
|
||||||
required, so a job with tests in a single mode runs just that half. Runtime is
|
|
||||||
bounded solely by the step's `timeout_in_minutes` — raise it, since the split
|
|
||||||
loads the model once per mode.
|
|
||||||
|
|
||||||
Before splitting, confirm each half actually collects at least one test under the job's forwarded filters — pytest exits with code 5 ("no tests collected") if a half is empty, which the script reports as a failure, red-ing a job that used to pass as one combined invocation.
|
|
||||||
|
|
||||||
Also check the tests' `num_cards` against the job's `mirror_hardwares` preset.
|
|
||||||
`cuda_marks` turns `num_cards > 1` into `skipif(device_count() < num_cards)`, so a
|
|
||||||
test asking for more GPUs than the preset provides is silently skipped and its
|
|
||||||
coverage artifact comes back empty. Splitting such a job produces a file that
|
|
||||||
looks fine and measures nothing — "Diffusion · Z Image Test" is the current
|
|
||||||
example (online tests want 4 cards, the job runs on `l4_1`), which is why it is
|
|
||||||
not a coverage pilot.
|
|
||||||
|
|
||||||
Online-mode coverage depends on the `[tool.coverage.run]` `patch`/`sigterm`
|
|
||||||
settings in `pyproject.toml`: online tests launch the server with
|
|
||||||
`subprocess.Popen` and stop it with SIGTERM, and without those settings the XML
|
|
||||||
reflects only the pytest parent process, not the server's code paths.
|
|
||||||
|
|
||||||
The upload depends on `buildkite-agent` being callable inside the container. The
|
|
||||||
`kubernetes` presets (`h100_*`, `*_npu_*`) provide it; the `docker` ones (`l4_*`)
|
|
||||||
only do because they set `mount-buildkite-agent: true`. A new docker preset that
|
|
||||||
runs a coverage job needs the same.
|
|
||||||
|
|
||||||
List both `run_cov_split.sh` and `pyproject.toml` in every opted-in job's
|
|
||||||
`source_file_dependencies` — both change what the job measures, so without them a
|
|
||||||
change there is filtered out of normal PR builds and only surfaces in a later
|
|
||||||
nightly. `tests/buildkite/test_upload_pipeline.py` covers the filter behavior with
|
|
||||||
a synthetic job (it does not pin real merge labels).
|
|
||||||
Editing only the surrounding CI YAML still does not schedule them, so a PR that
|
|
||||||
touches just the wiring needs a full E2E run (or the commands run on a GPU host)
|
|
||||||
to produce artifacts. When checking a new model's
|
|
||||||
artifacts, compare `lines-covered` between the online and offline XML rather than
|
|
||||||
just confirming both files exist.
|
|
||||||
|
|
||||||
### Validation checklist
|
### Validation checklist
|
||||||
|
|
||||||
| Check | Command / location |
|
| Check | Command / location |
|
||||||
|
|||||||
@@ -184,6 +184,9 @@ Failed jobs: 1/2
|
|||||||
# Reliability: HunyuanImage DiT (H100 × 4)
|
# Reliability: HunyuanImage DiT (H100 × 4)
|
||||||
pytest -s -v dfx/reliability/test_reliability_hunyuan_image.py -m slow
|
pytest -s -v dfx/reliability/test_reliability_hunyuan_image.py -m slow
|
||||||
|
|
||||||
|
# Reliability: VoxCPM2 (L4)
|
||||||
|
pytest -s -v dfx/reliability/test_reliability_voxcpm2.py -m slow
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The latest L5 CI jobs (reliability + invalid-parameter weekly steps) are in [test-weekly.yml](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/cuda/test-weekly.yml).
|
The latest L5 CI jobs (reliability + invalid-parameter weekly steps) are in [test-weekly.yml](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/cuda/test-weekly.yml).
|
||||||
|
|||||||
@@ -562,6 +562,7 @@ L5 level testing focuses on the performance of model services under ***long-runn
|
|||||||
- `test_reliability_qwen3_omni.py` — Qwen3-Omni chat / multimodal reliability (GPU OOM, process kill, recovery, error contract under `--async-chunk` vs default).
|
- `test_reliability_qwen3_omni.py` — Qwen3-Omni chat / multimodal reliability (GPU OOM, process kill, recovery, error contract under `--async-chunk` vs default).
|
||||||
- `test_reliability_wan22.py` — Wan2.2 I2V video API reliability (`/v1/videos` under OOM and process kill, recovery).
|
- `test_reliability_wan22.py` — Wan2.2 I2V video API reliability (`/v1/videos` under OOM and process kill, recovery).
|
||||||
- `test_reliability_hunyuan_image.py` — HunyuanImage-3.0-Instruct DiT-only reliability (`/v1/images/generations` under OOM and process kill; deploy `hunyuan_image3_dit.yaml`, H100 × 4).
|
- `test_reliability_hunyuan_image.py` — HunyuanImage-3.0-Instruct DiT-only reliability (`/v1/images/generations` under OOM and process kill; deploy `hunyuan_image3_dit.yaml`, H100 × 4).
|
||||||
|
- `test_reliability_voxcpm2.py` — VoxCPM2 TTS reliability (`/v1/audio/speech` under OOM and process kill; deploy `voxcpm2.yaml`, L4 × 1).
|
||||||
- `helpers.py` — Shared primitives used by current suites: raw HTTP probes for `/v1/chat/completions` and `/health`, OpenAI-style error parsing, GPU OOM sidecar (`inject_gpu_oom` / `stop_gpu_oom_hogs`), and `pgrep`-based process-kill injector construction (`make_process_kill_fault_injector`).
|
- `helpers.py` — Shared primitives used by current suites: raw HTTP probes for `/v1/chat/completions` and `/health`, OpenAI-style error parsing, GPU OOM sidecar (`inject_gpu_oom` / `stop_gpu_oom_hogs`), and `pgrep`-based process-kill injector construction (`make_process_kill_fault_injector`).
|
||||||
- `conftest.py` — `fault_injector` and `omni_server_after_fault` / `omni_server_after_fault_function` fixtures to run a callable **after** the server is ready.
|
- `conftest.py` — `fault_injector` and `omni_server_after_fault` / `omni_server_after_fault_function` fixtures to run a callable **after** the server is ready.
|
||||||
- `README.md` — Short local run commands for this directory.
|
- `README.md` — Short local run commands for this directory.
|
||||||
@@ -661,12 +662,13 @@ Reliability tests are **short fault-injection** integration runs (L5 **(b)** in
|
|||||||
| `test_reliability_qwen3_omni.py` | Qwen3-Omni: OOM vs **text vs text+audio** error contract, large multimodal chat under OOM, concurrent pressure, **SIGKILL** on `VLLM::Worker`, `/health` → 503 + fast-fail + concurrent chat; optional OOM recovery scenario (may be skipped while tracked in issues). |
|
| `test_reliability_qwen3_omni.py` | Qwen3-Omni: OOM vs **text vs text+audio** error contract, large multimodal chat under OOM, concurrent pressure, **SIGKILL** on `VLLM::Worker`, `/health` → 503 + fast-fail + concurrent chat; optional OOM recovery scenario (may be skipped while tracked in issues). |
|
||||||
| `test_reliability_wan22.py` | Wan2.2 I2V: large `/v1/videos` under OOM, **SIGKILL** on `multiprocessing.spawn` chain, health / fast-fail / concurrent video requests; optional recovery test (may be skipped). |
|
| `test_reliability_wan22.py` | Wan2.2 I2V: large `/v1/videos` under OOM, **SIGKILL** on `multiprocessing.spawn` chain, health / fast-fail / concurrent video requests; optional recovery test (may be skipped). |
|
||||||
| `test_reliability_hunyuan_image.py` | HunyuanImage DiT-only: large `/v1/images/generations` under OOM, **SIGKILL** on `vLLM-Omni::` workers and serve/tree targets, health / fast-fail / concurrent image requests; some OOM/recovery cases may be skipped while tracked in issues. |
|
| `test_reliability_hunyuan_image.py` | HunyuanImage DiT-only: large `/v1/images/generations` under OOM, **SIGKILL** on `vLLM-Omni::` workers and serve/tree targets, health / fast-fail / concurrent image requests; some OOM/recovery cases may be skipped while tracked in issues. |
|
||||||
|
| `test_reliability_voxcpm2.py` | VoxCPM2: `/v1/audio/speech` under OOM (error contract), **SIGKILL** on `VLLM::` workers and serve/tree targets, health / fast-fail / concurrent speech requests; some OOM cases may be skipped while tracked in issues. |
|
||||||
| `README.md` | Minimal run / collect examples. |
|
| `README.md` | Minimal run / collect examples. |
|
||||||
|
|
||||||
##### Parametrization and markers
|
##### Parametrization and markers
|
||||||
|
|
||||||
- Each test module defines a **`RELIABILITY_SCENARIOS`** list (`test_name`, `server_params`: model, `stage_config_name` or diffusion `server_args`, etc.). **`create_reliability_omni_server_params()`** in `tests/dfx/conftest.py` resolves stage paths (including XPU substitutions where applicable) and builds **`OmniServerParams`** lists consumed by **`@pytest.mark.parametrize(..., indirect=True)`** on `omni_server` or `omni_server_function`.
|
- Each test module defines a **`RELIABILITY_SCENARIOS`** list (`test_name`, `server_params`: model, `stage_config_name` or diffusion `server_args`, etc.). **`create_reliability_omni_server_params()`** in `tests/dfx/conftest.py` resolves stage paths (including XPU substitutions where applicable) and builds **`OmniServerParams`** lists consumed by **`@pytest.mark.parametrize(..., indirect=True)`** on `omni_server` or `omni_server_function`.
|
||||||
- Cases are tagged **`@pytest.mark.slow`** for weekly / selective CI. GPU-heavy suites use **`@hardware_test(res={"cuda": "H100"}, num_cards=...)`** (Qwen3-Omni **2**× H100; Wan2.2 **1**× H100; HunyuanImage DiT **4**× H100).
|
- Cases are tagged **`@pytest.mark.slow`** for weekly / selective CI. GPU-heavy suites use **`@hardware_test(res={"cuda": "H100"}, num_cards=...)`** or **`@hardware_test(res={"cuda": "L4"}, num_cards=1)`** (Qwen3-Omni **2**× H100; Wan2.2 **1**× H100; HunyuanImage DiT **4**× H100; VoxCPM2 **1**× L4).
|
||||||
- **Process-kill** tests use **`@pytest.mark.skipif(os.name == "nt", ...)`** because injection uses POSIX **`pgrep` / `kill`**.
|
- **Process-kill** tests use **`@pytest.mark.skipif(os.name == "nt", ...)`** because injection uses POSIX **`pgrep` / `kill`**.
|
||||||
|
|
||||||
##### CI trigger
|
##### CI trigger
|
||||||
@@ -678,11 +680,13 @@ Weekly Buildkite (`.buildkite/cuda/test-weekly.yml`) runs one step per model sui
|
|||||||
| Reliability Test - qwen3-omni | `test_reliability_qwen3_omni.py` | H100 × 2 (`mithril-h100-pool`) |
|
| Reliability Test - qwen3-omni | `test_reliability_qwen3_omni.py` | H100 × 2 (`mithril-h100-pool`) |
|
||||||
| Reliability Test - wan22 | `test_reliability_wan22.py` | H100 × 2 (`mithril-h100-pool`) |
|
| Reliability Test - wan22 | `test_reliability_wan22.py` | H100 × 2 (`mithril-h100-pool`) |
|
||||||
| Reliability Test - hunyuan-image | `test_reliability_hunyuan_image.py` | H100 × 4 (`mithril-h100-pool`) |
|
| Reliability Test - hunyuan-image | `test_reliability_hunyuan_image.py` | H100 × 4 (`mithril-h100-pool`) |
|
||||||
|
| Reliability Test - voxcpm2 | `test_reliability_voxcpm2.py` | L4 × 1 (`gpu_1_queue`) |
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pytest -s -v tests/dfx/reliability/test_reliability_qwen3_omni.py -m "slow"
|
pytest -s -v tests/dfx/reliability/test_reliability_qwen3_omni.py -m "slow"
|
||||||
pytest -s -v tests/dfx/reliability/test_reliability_wan22.py -m "slow"
|
pytest -s -v tests/dfx/reliability/test_reliability_wan22.py -m "slow"
|
||||||
pytest -s -v tests/dfx/reliability/test_reliability_hunyuan_image.py -m "slow"
|
pytest -s -v tests/dfx/reliability/test_reliability_hunyuan_image.py -m "slow"
|
||||||
|
pytest -s -v tests/dfx/reliability/test_reliability_voxcpm2.py -m "slow"
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Local commands
|
##### Local commands
|
||||||
@@ -692,6 +696,7 @@ pytest --collect-only tests/dfx/reliability
|
|||||||
pytest -s -v tests/dfx/reliability/test_reliability_qwen3_omni.py -m slow
|
pytest -s -v tests/dfx/reliability/test_reliability_qwen3_omni.py -m slow
|
||||||
pytest -s -v tests/dfx/reliability/test_reliability_wan22.py -m slow
|
pytest -s -v tests/dfx/reliability/test_reliability_wan22.py -m slow
|
||||||
pytest -s -v tests/dfx/reliability/test_reliability_hunyuan_image.py -m slow
|
pytest -s -v tests/dfx/reliability/test_reliability_hunyuan_image.py -m slow
|
||||||
|
pytest -s -v tests/dfx/reliability/test_reliability_voxcpm2.py -m slow
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Adding a new model suite
|
##### Adding a new model suite
|
||||||
@@ -704,4 +709,4 @@ pytest -s -v tests/dfx/reliability/test_reliability_hunyuan_image.py -m slow
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
- - ***Stability***: `pytest -s -v tests/dfx/stability/scripts/test_stability_qwen3_omni.py` or `pytest -s -v tests/dfx/stability/scripts/test_stability_wan22.py` (or add `test_stability_<model>.py` alongside a matching JSON config)
|
- - ***Stability***: `pytest -s -v tests/dfx/stability/scripts/test_stability_qwen3_omni.py` or `pytest -s -v tests/dfx/stability/scripts/test_stability_wan22.py` (or add `test_stability_<model>.py` alongside a matching JSON config)
|
||||||
- ***Reliability***: `pytest -s -v tests/dfx/reliability/test_reliability_<model>.py -m slow` (current suites: `qwen3_omni`, `wan22`, `hunyuan_image`; add `test_reliability_<suite>.py` and a matching step in `.buildkite/cuda/test-weekly.yml` for new models)
|
- ***Reliability***: `pytest -s -v tests/dfx/reliability/test_reliability_<model>.py -m slow` (current suites: `qwen3_omni`, `wan22`, `hunyuan_image`, `voxcpm2`; add `test_reliability_<suite>.py` and a matching step in `.buildkite/cuda/test-weekly.yml` for new models)
|
||||||
@@ -4,9 +4,6 @@
|
|||||||
> `experimental/ar_diffusion` contract. It follows the ownership and lifecycle
|
> `experimental/ar_diffusion` contract. It follows the ownership and lifecycle
|
||||||
> direction in [#5137](https://github.com/vllm-project/vllm-omni/issues/5137),
|
> direction in [#5137](https://github.com/vllm-project/vllm-omni/issues/5137),
|
||||||
> but it is not yet part of the stable module-design hierarchy.
|
> but it is not yet part of the stable module-design hierarchy.
|
||||||
>
|
|
||||||
> The end-to-end request, identity, failure, and memory contracts are documented
|
|
||||||
> in [Realtime AR-Diffusion sessions](feature/realtime_ar_diffusion.md).
|
|
||||||
|
|
||||||
`ARDiffusionEngine` is an opt-in diffusion backend for pipelines that keep
|
`ARDiffusionEngine` is an opt-in diffusion backend for pipelines that keep
|
||||||
autoregressive attention KV across requests. The engine only selects
|
autoregressive attention KV across requests. The engine only selects
|
||||||
@@ -54,13 +51,14 @@ time, and its inherited batch/step entry points also fail explicitly. Supporting
|
|||||||
those paths requires a batch-aware state-binding contract rather than silently
|
those paths requires a batch-aware state-binding contract rather than silently
|
||||||
bypassing `bind_ar_diffusion_state()`.
|
bypassing `bind_ar_diffusion_state()`.
|
||||||
|
|
||||||
The implementation may retain multiple sessions per model instance. It selects
|
The current implementation retains one session per model instance. A request
|
||||||
the largest feasible capacity up to the pipeline's `session_capacity`, based on
|
for a different `session_id` closes the resident session through the same LRU
|
||||||
self-attention pages, cross-attention allocations, scratch, and declared
|
lifecycle used by explicit close and failure cleanup before allocating the new
|
||||||
model-owned state. One session is admitted whenever it fits actual free device
|
one. Self-attention and cross-attention memory are therefore sized for one
|
||||||
memory; `gpu_memory_fraction` controls additional residency. Requests beyond
|
resident session even if the pipeline declares a larger `session_capacity`.
|
||||||
that capacity evict the least-recently-used session through the same lifecycle
|
This prioritizes rollout throughput and prevents block-pool exhaustion before a
|
||||||
hook used by explicit close and failure cleanup.
|
count-based eviction can run. Multi-session residency can be added later with a
|
||||||
|
memory-backed capacity calculation or allocation-pressure eviction.
|
||||||
|
|
||||||
Scratch storage is also capability-driven. `frames_per_block` reserves space for
|
Scratch storage is also capability-driven. `frames_per_block` reserves space for
|
||||||
an uncommitted current video block, while `max_scratch_tokens_per_branch`
|
an uncommitted current video block, while `max_scratch_tokens_per_branch`
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ the source for configuration, compatibility, and implementation details.
|
|||||||
* **Composable parallelism:** Diffusion stages can combine [CFG-Parallel](feature/cfg_parallel.md),
|
* **Composable parallelism:** Diffusion stages can combine [CFG-Parallel](feature/cfg_parallel.md),
|
||||||
[Expert Parallel](feature/expert_parallel.md), [HSDP](feature/hsdp.md),
|
[Expert Parallel](feature/expert_parallel.md), [HSDP](feature/hsdp.md),
|
||||||
[Pipeline Parallel](feature/pipeline_parallel.md), [Sequence Parallel](feature/sequence_parallel.md),
|
[Pipeline Parallel](feature/pipeline_parallel.md), [Sequence Parallel](feature/sequence_parallel.md),
|
||||||
[Tensor Parallel](feature/tensor_parallel.md), and [VAE Patch Parallelism](feature/vae_parallel.md)
|
[Tensor Parallel](feature/tensor_parallel.md), and [VAE Parallel](feature/vae_parallel.md)
|
||||||
according to the pipeline and hardware topology.
|
according to the pipeline and hardware topology.
|
||||||
* **Attention and cache acceleration:** [Skip-Softmax](feature/skip_softmax.md),
|
* **Attention and cache acceleration:** [Skip-Softmax](feature/skip_softmax.md),
|
||||||
[Cache-DiT](feature/cache_dit.md), and [TeaCache](feature/teacache.md)
|
[Cache-DiT](feature/cache_dit.md), and [TeaCache](feature/teacache.md)
|
||||||
@@ -406,7 +406,7 @@ the source for configuration, compatibility, and implementation details.
|
|||||||
stage contract.
|
stage contract.
|
||||||
* **Quantization and memory efficiency:** [Quantization](feature/quantization.md)
|
* **Quantization and memory efficiency:** [Quantization](feature/quantization.md)
|
||||||
resolves per-pipeline or per-component quantization configurations, while
|
resolves per-pipeline or per-component quantization configurations, while
|
||||||
[Distributed Layerwise Offload](feature/offloader/distributed_layerwise_offload.md)
|
[Distributed Layerwise Offload](feature/distributed_layerwise_offload.md)
|
||||||
streams diffusion blocks from host memory within the existing parallel
|
streams diffusion blocks from host memory within the existing parallel
|
||||||
topology.
|
topology.
|
||||||
|
|
||||||
|
|||||||
@@ -117,22 +117,7 @@ After (async D2H):
|
|||||||
|
|
||||||
### Batch Split
|
### Batch Split
|
||||||
|
|
||||||
When `execute_model_batch` returns a `COMPUTE_DONE` with a single `async_output_id` for the entire batch, the executor splits it into per-request `async_output_id`s (format `{batch_id}/{request_id}`) via `_batch_split_map`. When `OUTPUT_READY` arrives, the pump extracts per-request results from the batch output and resolves each request's output future independently. If `OUTPUT_READY` arrives before `execute_batch` registers the split map, `execute_batch` directly adopts the early output.
|
When `execute_model_batch` returns a `COMPUTE_DONE` with a single `async_output_id` for the entire batch, the executor splits it into per-request `async_output_id`s (format `{batch_id}/{request_id}`) via `_batch_split_map`. When `OUTPUT_READY` arrives, the pump extracts per-request results from the batch output and resolves each request's output future independently.
|
||||||
|
|
||||||
### Reliability, Lifecycle & Timeout Behavior
|
|
||||||
|
|
||||||
1. **Drain Before Memory Release (`drain_async_outputs` / `_ASYNC_OUTPUT_DRAIN_TIMEOUT_S`)**:
|
|
||||||
When workers transition into sleep states (`handle_sleep_task`) or invoke memory-releasing methods, `WorkerProc.drain_async_outputs()` blocks up to `_ASYNC_OUTPUT_DRAIN_TIMEOUT_S` (10.0s) until all in-flight background D2H/SHM packing tasks finish. This acts as a synchronization barrier preventing worker sleep cycles from releasing device tensors while the side CUDA stream is still actively reading them. Draining typically completes within 10–50 ms under normal operation.
|
|
||||||
|
|
||||||
2. **Timeout Diagnostics (`_ASYNC_OUTPUT_TIMEOUT`)**:
|
|
||||||
The engine waits for `wait_output_ready()` with a timeout of `_ASYNC_OUTPUT_TIMEOUT` (30.0s). If a timeout occurs, the executor logs detailed diagnostic bookkeeping (pending futures, cached outputs, and batch split maps) to identify whether the stall occurred in the worker, pump, or waiter.
|
|
||||||
|
|
||||||
3. **Queue Serialization & Atomic Future Resolution**:
|
|
||||||
- `result_mq` writes are serialized with `_result_mq_lock` to protect the single-writer `MessageQueue` against concurrent writes from the worker main loop (`COMPUTE_DONE`) and background thread (`OUTPUT_READY`).
|
|
||||||
- `ResultPumpThread` unpacks shared-memory payloads first, and then atomically resolves waiting futures or populates `_completed_outputs` under `_futures_lock`, avoiding race conditions with concurrent `wait_output_ready()` invocations.
|
|
||||||
|
|
||||||
4. **Storage-Aware IPC Packing**:
|
|
||||||
`pack_diffusion_output_shm()` evaluates `max(view_bytes, storage_bytes)` against `_SHM_TENSOR_THRESHOLD` (1 MB) to ensure that per-request slices sharing a large batch storage are moved to POSIX shared memory rather than serialized through pickle over the MessageQueue inline buffer.
|
|
||||||
|
|
||||||
## Model Coverage
|
## Model Coverage
|
||||||
|
|
||||||
@@ -146,7 +131,6 @@ All models running in request-mode (`step_execution=False`, the default) automat
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **HunyuanImage-3.0** | Image | `False` |
|
| **HunyuanImage-3.0** | Image | `False` |
|
||||||
| **Qwen-Image** | Image | `True` |
|
| **Qwen-Image** | Image | `True` |
|
||||||
| **LTX-2.3** | Video / Audio | `False` |
|
|
||||||
|
|
||||||
Other models with `step_execution=False` are also supported but not yet verified.
|
Other models with `step_execution=False` are also supported but not yet verified.
|
||||||
|
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
# Diffusion Attention Backend Selection
|
|
||||||
|
|
||||||
This document defines the selection and extension contract for diffusion
|
|
||||||
attention backends. User-facing backend choices, installation, and tuning are
|
|
||||||
in the [attention backend guides](../../user_guide/diffusion/attention_backends.md).
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
The contract applies to DiT and other diffusion attention layers. It is
|
|
||||||
separate from vLLM's autoregressive attention selector.
|
|
||||||
|
|
||||||
The selection path has four responsibilities:
|
|
||||||
|
|
||||||
1. normalize user configuration into `AttentionConfig` and `AttentionSpec`;
|
|
||||||
2. resolve a spec for an attention role;
|
|
||||||
3. ask the active platform to validate an explicit backend or choose a
|
|
||||||
hardware default; and
|
|
||||||
4. load the selected `AttentionBackend` class from the registry path.
|
|
||||||
|
|
||||||
## Resolution contract
|
|
||||||
|
|
||||||
`get_attn_backend_for_role()` resolves configuration in this order:
|
|
||||||
|
|
||||||
1. exact `per_role[role]`;
|
|
||||||
2. category `per_role[role_category]`;
|
|
||||||
3. `default`;
|
|
||||||
4. platform default.
|
|
||||||
|
|
||||||
An explicit resolution returns both the backend class and its
|
|
||||||
`AttentionSpec`. A platform-default resolution returns the class and `None`.
|
|
||||||
Layers must therefore treat the spec as optional and must not infer that a
|
|
||||||
missing spec means SDPA.
|
|
||||||
|
|
||||||
Class resolution is cached by backend name, head size, and whether TRTLLM may
|
|
||||||
be selected automatically. Logging is cached separately by role and source so
|
|
||||||
the startup record identifies why each role received its backend.
|
|
||||||
|
|
||||||
## Registry and platform boundary
|
|
||||||
|
|
||||||
`DiffusionAttentionBackendEnum` maps stable configuration names to default
|
|
||||||
qualified class paths. `register_diffusion_backend()` may replace a path at
|
|
||||||
runtime without changing the public enum value.
|
|
||||||
|
|
||||||
The active `OmniPlatform` owns compatibility policy through
|
|
||||||
`get_diffusion_attn_backend_cls()`. It must:
|
|
||||||
|
|
||||||
- validate explicit selections and fail with an actionable error when the
|
|
||||||
requested kernel cannot run;
|
|
||||||
- choose only an available, compatible default when no backend is explicit;
|
|
||||||
- account for head size and other platform-visible constraints; and
|
|
||||||
- return a qualified class path implementing `AttentionBackend`.
|
|
||||||
|
|
||||||
The selector must not duplicate device capability or package-availability
|
|
||||||
policy that belongs to the platform.
|
|
||||||
|
|
||||||
## Typed backend options
|
|
||||||
|
|
||||||
Backend-specific settings remain typed fields on `AttentionSpec` rather than
|
|
||||||
unstructured keyword dictionaries:
|
|
||||||
|
|
||||||
- `quant` is consumed by FlashInfer and TRTLLM with backend-specific value
|
|
||||||
validation;
|
|
||||||
- `skip_softmax` is consumed by TRTLLM; and
|
|
||||||
- `block_sparse` is consumed by block-sparse backends such as RainFusion.
|
|
||||||
|
|
||||||
A backend reads only the fields it owns and rejects incompatible values. New
|
|
||||||
options should be added to a shared typed spec only when more than one backend
|
|
||||||
shares their semantics; otherwise add a dedicated typed block.
|
|
||||||
|
|
||||||
## Model integration contract
|
|
||||||
|
|
||||||
Each diffusion attention call declares a stable role and, when appropriate, a
|
|
||||||
broader category. Model-specific roles permit precise overrides; categories
|
|
||||||
preserve common `self` or `cross` policies. A model also decides whether its
|
|
||||||
path is compatible with automatic TRTLLM selection, because only the model
|
|
||||||
knows whether masking and packing satisfy the kernel contract.
|
|
||||||
|
|
||||||
## Adding or changing a backend
|
|
||||||
|
|
||||||
An implementation change is complete only when it:
|
|
||||||
|
|
||||||
1. implements the `AttentionBackend` interface and exposes a stable enum name;
|
|
||||||
2. adds platform validation and default routing where appropriate;
|
|
||||||
3. defines and validates any typed configuration;
|
|
||||||
4. tests explicit selection, default selection, and incompatible paths;
|
|
||||||
5. documents installation, fallback behavior, and quality implications in the
|
|
||||||
corresponding user guide; and
|
|
||||||
6. updates the overview matrix without moving algorithm details back into the
|
|
||||||
landing page.
|
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
# Distributed Layerwise Offload
|
||||||
|
|
||||||
|
This document describes distributed layerwise offload (DLO) for diffusion
|
||||||
|
models. DLO keeps only a small number of DiT blocks on the accelerator and
|
||||||
|
streams the remaining blocks from host memory. The distributed backend can
|
||||||
|
either shard those host-side weights across an existing parallel group or keep
|
||||||
|
the standard loader's rank-local weights and avoid an additional collective.
|
||||||
|
|
||||||
|
For user-facing commands, see the
|
||||||
|
[distributed layerwise offloading guide](../../user_guide/diffusion/cpu_offload.md)
|
||||||
|
and the [Cosmos3 recipe](https://github.com/vllm-project/vllm-omni/blob/main/recipes/cosmos3/Cosmos3-DistOffload.md).
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
DLO is implemented for multi-device diffusion execution. The default
|
||||||
|
AllGather path is the primary path for DP and SP deployments. The
|
||||||
|
`--dlo-no-use-allgather` path is a rank-local compatibility mode: it is useful
|
||||||
|
for standard-loader sharding, workstation bring-up, and systems where an
|
||||||
|
additional DLO collective is undesirable, but it does not reduce host weight
|
||||||
|
storage across ranks.
|
||||||
|
|
||||||
|
The compatibility matrix below describes the current implementation. The
|
||||||
|
unit-level guards are covered, but not every parallelism combination has a
|
||||||
|
full model-and-hardware end-to-end test.
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
### DLO consumes the existing parallel topology
|
||||||
|
|
||||||
|
DLO does not create a new DP, TP, or SP topology. It reads the configured
|
||||||
|
`DiffusionParallelConfig` and attaches offload hooks to the DiT blocks after the
|
||||||
|
standard distributed groups have been initialized.
|
||||||
|
|
||||||
|
The DLO weight-sharding group is selected as follows:
|
||||||
|
|
||||||
|
1. Use the existing DP group when `data_parallel_size > 1`.
|
||||||
|
2. When DP is one and SP is greater than one, use the SP group.
|
||||||
|
3. Otherwise, run rank-locally without a DLO process group.
|
||||||
|
|
||||||
|
TP is deliberately not used as DLO's AllGather group. HSDP has its own
|
||||||
|
parameter-sharding lifecycle and is not allowed to be sharded a second time by
|
||||||
|
DLO's AllGather path.
|
||||||
|
|
||||||
|
### AllGather path
|
||||||
|
|
||||||
|
With the default `dlo_use_allgather=True`, each rank stores approximately
|
||||||
|
`1 / group_size` of each streamable block in pinned host memory. The next
|
||||||
|
block's shard is copied to a device buffer and reconstructed with
|
||||||
|
`all_gather_into_tensor` on a communication stream while the current block is
|
||||||
|
executing.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Compute: [Block N] [Block N+1] [Block N+2]
|
||||||
|
H2D: [shard N+1] [shard N+2]
|
||||||
|
AllGather: [full N+1] [full N+2]
|
||||||
|
Buffers: [current slot] [prefetch slot] [current slot]
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The backend uses two shared device buffers, so accelerator weight residency is
|
||||||
|
bounded by the largest streamed blocks rather than the complete model.
|
||||||
|
|
||||||
|
When DP is greater than one, the engine can process one request per DP rank in
|
||||||
|
the same denoising wave. Because AllGather is a collective, all participating
|
||||||
|
requests must take the same execution path at every denoising step.
|
||||||
|
|
||||||
|
### Rank-local path without DLO AllGather
|
||||||
|
|
||||||
|
With `--dlo-no-use-allgather`, DLO forces its internal offload shard size to
|
||||||
|
one. The regular model loader remains responsible for preparing each rank's
|
||||||
|
weights, including TP-local tensors or HSDP-managed parameters. DLO then
|
||||||
|
streams those rank-local tensors block by block using H2D copies only.
|
||||||
|
|
||||||
|
This mode means:
|
||||||
|
|
||||||
|
- DP still provides independent replicas, but DLO does not shard weights
|
||||||
|
across DP ranks.
|
||||||
|
- SP still performs its normal activation/attention collectives, but DLO does
|
||||||
|
not shard weights across SP ranks.
|
||||||
|
- TP/HSDP/SP collectives, if configured, are not disabled by this flag; only
|
||||||
|
DLO's additional weight AllGather is disabled.
|
||||||
|
- Pure DP deployments keep a full host-side model copy per rank, subject to
|
||||||
|
shared OS page-cache behavior.
|
||||||
|
- The scheduler does not require a synchronized DP request wave for DLO.
|
||||||
|
|
||||||
|
This path is intentionally not implemented by reusing the AllGather mmap
|
||||||
|
loader. It relies on the standard loader so model-specific TP/HSDP transforms
|
||||||
|
remain intact.
|
||||||
|
|
||||||
|
## Parallelism compatibility
|
||||||
|
|
||||||
|
| Parallelism | DLO + AllGather | DLO without AllGather |
|
||||||
|
|---|---|---|
|
||||||
|
| **DP** | Supported primary path. DLO shards host weights across the DP group and can run DP multi-concurrency. | Supported rank-local path. DP replicas remain independent; no DLO weight sharding or cross-DP DLO collective. |
|
||||||
|
| **SP** | Supported in the implementation. With DP=1, DLO uses the SP group for host-weight sharding; SP still shards sequence/activation work. | SP remains active, but DLO keeps standard-loader rank-local weights and adds no SP weight collective. |
|
||||||
|
| **TP > 1** | Unsupported in the DLO mmap path. TP-aware loader callbacks are bypassed, so the backend rejects this configuration when it enters that path. | Standard loading is retained and TP-local tensors can be streamed. This is the intended compatibility path, but it still needs broader model and hardware validation. |
|
||||||
|
| **HSDP** | Rejected. HSDP has already sharded parameters, so DLO AllGather would double-shard them. | Accepted by configuration. HSDP owns parameter sharding and its own gathers; DLO only stages rank-local parameters. End-to-end coverage is limited. |
|
||||||
|
|
||||||
|
### Combined dimensions
|
||||||
|
|
||||||
|
- **DP + SP:** DLO uses the DP group for weight sharding when DP is greater
|
||||||
|
than one; SP continues to use its own sequence-parallel group. If DP is one,
|
||||||
|
the SP group becomes DLO's sharding group in AllGather mode.
|
||||||
|
- **DP + TP/SP without AllGather:** standard model loading defines the
|
||||||
|
rank-local tensor layout. DLO adds no cross-DP, cross-TP, or cross-SP weight
|
||||||
|
collective.
|
||||||
|
- **HSDP + SP:** the general parallel configuration permits HSDP over SP, but
|
||||||
|
DLO must use `--dlo-no-use-allgather`. HSDP remains responsible for weight
|
||||||
|
materialization and synchronization.
|
||||||
|
- **HSDP + DP or TP:** rejected independently by the diffusion parallel
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
## Request and loading constraints
|
||||||
|
|
||||||
|
AllGather DP multi-concurrency requires:
|
||||||
|
|
||||||
|
- explicit `num_inference_steps`;
|
||||||
|
- the same `num_inference_steps` for all requests in a wave; and
|
||||||
|
- identical request arguments that affect the collective execution path.
|
||||||
|
|
||||||
|
The no-AllGather path does not impose these DLO-specific synchronized-wave
|
||||||
|
requirements.
|
||||||
|
|
||||||
|
The mmap loader is used only by the supported DLO+AllGather path when the
|
||||||
|
model has a compatible checkpoint layout. Online quantization is incompatible
|
||||||
|
with that sharded mmap path; use `--dlo-no-use-allgather` or disable online
|
||||||
|
quantization. Models that do not meet the mmap requirements use the regular
|
||||||
|
loader path.
|
||||||
|
|
||||||
|
## Validation coverage
|
||||||
|
|
||||||
|
Current source-level validation includes:
|
||||||
|
|
||||||
|
- HSDP + DLO + AllGather rejection;
|
||||||
|
- HSDP + DLO without AllGather acceptance at configuration level;
|
||||||
|
- TP rejection in the DLO+AllGather mmap path;
|
||||||
|
- resident-layer requests requiring no-AllGather;
|
||||||
|
- DP request-wave validation for denoising-step compatibility;
|
||||||
|
- sharding, double-buffer, AllGather-size, and heterogeneous-block regression
|
||||||
|
tests.
|
||||||
|
|
||||||
|
The highest-value missing coverage is end-to-end numerical comparison against
|
||||||
|
ordinary layerwise offload for DP+SP, TP+no-AllGather, and HSDP+SP+no-AllGather
|
||||||
|
on the target CUDA/NCCL or CANN/HCCL hardware.
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
- Use **DP + DLO AllGather** for the supported throughput and host-memory
|
||||||
|
scaling path.
|
||||||
|
- Use **SP + DLO AllGather** for long-sequence workloads when DP concurrency is
|
||||||
|
not the goal.
|
||||||
|
- Use **no-AllGather** to bring up TP or workstation PCIe configurations, with
|
||||||
|
the expectation of higher host-memory use and lower validation confidence.
|
||||||
|
- Prefer **HSDP alone** for production HSDP deployments until the combined
|
||||||
|
HSDP + DLO no-AllGather path has broader end-to-end coverage.
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
# CPU Offloading
|
|
||||||
|
|
||||||
This document defines the shared architecture for diffusion CPU offloading.
|
|
||||||
The three strategies have separate user and design pages:
|
|
||||||
|
|
||||||
| Strategy | User guide | Design contract |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Model-level | [Guide](../../../user_guide/diffusion/offloader/cpu_offload.md) | [Design](cpu_offload.md) |
|
|
||||||
| Layerwise | [Guide](../../../user_guide/diffusion/offloader/layerwise_offload.md) | [Design](layerwise_offload.md) |
|
|
||||||
| Distributed layerwise | [Guide](../../../user_guide/diffusion/offloader/distributed_layerwise_offload.md) | [Design](distributed_layerwise_offload.md) |
|
|
||||||
|
|
||||||
## Strategy selection
|
|
||||||
|
|
||||||
`OffloadConfig.from_od_config()` converts diffusion configuration into one
|
|
||||||
`OffloadStrategy`. If callers set multiple flags, selection is deterministic:
|
|
||||||
|
|
||||||
1. `DISTRIBUTED_LAYERWISE`;
|
|
||||||
2. `LAYERWISE`;
|
|
||||||
3. `MODEL_LEVEL`; and
|
|
||||||
4. `NONE`.
|
|
||||||
|
|
||||||
The factory logs when a higher-priority strategy disables another. It derives
|
|
||||||
parallel and HSDP state from `DiffusionParallelConfig`; callers do not provide
|
|
||||||
a separate offload group size.
|
|
||||||
|
|
||||||
`get_offload_backend()` then validates platform offload support, resolves the
|
|
||||||
device, and creates exactly one backend. Returning `None` means offloading is
|
|
||||||
disabled or unsupported and must not leave partially installed hooks.
|
|
||||||
|
|
||||||
## Shared lifecycle
|
|
||||||
|
|
||||||
Every backend implements `OffloadBackend`:
|
|
||||||
|
|
||||||
- `enable(pipeline)` discovers modules, establishes initial residency, and
|
|
||||||
installs hooks;
|
|
||||||
- `disable()` removes owned hooks and resources; and
|
|
||||||
- `is_enabled()` reports lifecycle state.
|
|
||||||
|
|
||||||
`disable()` does not promise to restore the pipeline's original device
|
|
||||||
placement. The caller owns any subsequent rematerialization.
|
|
||||||
|
|
||||||
Hooks are registered through `HookRegistry` and `ModelHook`; offload backends
|
|
||||||
must use distinct hook names and remove only hooks they own.
|
|
||||||
|
|
||||||
## Discovery and topology
|
|
||||||
|
|
||||||
Pipeline component discovery prefers `SupportsComponentDiscovery` declarations:
|
|
||||||
|
|
||||||
- `_dit_modules`;
|
|
||||||
- `_encoder_modules`;
|
|
||||||
- `_vae_modules`; and
|
|
||||||
- `_resident_modules`.
|
|
||||||
|
|
||||||
Dotted paths are supported. Legacy pipelines may use the fallback scan of
|
|
||||||
well-known attribute names, but new integrations should declare components
|
|
||||||
explicitly.
|
|
||||||
|
|
||||||
Ordinary layerwise offload obtains topology from declared
|
|
||||||
`_layerwise_offload_blocks_attrs` (with a deprecated singular-name
|
|
||||||
compatibility path). Distributed layerwise offload may instead use a pipeline
|
|
||||||
`OffloadPlan`, then falls back to declared block attributes and its documented
|
|
||||||
compatibility discovery. Discovery metadata describes structure only; the
|
|
||||||
backend remains responsible for transfer, synchronization, and storage
|
|
||||||
ownership.
|
|
||||||
|
|
||||||
## Cross-strategy invariants
|
|
||||||
|
|
||||||
- At most one strategy owns offload hooks for a pipeline.
|
|
||||||
- A parameter has one authoritative host representation while offloaded.
|
|
||||||
- Device storage is not freed until dependent compute or transfer work is
|
|
||||||
complete.
|
|
||||||
- Non-persistent and model-specific buffers remain correct after movement or
|
|
||||||
checkpoint rematerialization.
|
|
||||||
- Unsupported parallel or loading combinations fail before hooks mutate the
|
|
||||||
model.
|
|
||||||
- Platform streams, events, synchronization, and cache management go through
|
|
||||||
the vLLM-Omni platform abstraction.
|
|
||||||
|
|
||||||
The diffusion [Offloader module design](../../module/diffusion/offloader.md)
|
|
||||||
describes how these feature contracts fit into the larger diffusion runtime.
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
# Model-Level Offload
|
|
||||||
|
|
||||||
Model-level offload enforces mutual exclusion between pipeline component
|
|
||||||
groups. For commands and model support, see the
|
|
||||||
[model-level user guide](../../../user_guide/diffusion/offloader/cpu_offload.md).
|
|
||||||
|
|
||||||
## Generic hook path
|
|
||||||
|
|
||||||
`ModelLevelOffloadBackend.enable()` discovers DiTs, encoders, and VAEs. It
|
|
||||||
makes encoders and VAEs device resident for initialization, then installs
|
|
||||||
`SequentialOffloadHook` instances:
|
|
||||||
|
|
||||||
- each DiT hook offloads all encoders and other DiTs before loading itself;
|
|
||||||
- each encoder hook offloads every DiT before loading itself; and
|
|
||||||
- VAEs are not part of the mutual-exclusion hook set.
|
|
||||||
|
|
||||||
The hook moves parameters and buffers without recursively calling
|
|
||||||
`module.to()`. This avoids recursion through modules that retain references to
|
|
||||||
other transformer blocks. Host tensors are pinned when configured; DTensors
|
|
||||||
are not pinned through the ordinary tensor path.
|
|
||||||
|
|
||||||
Before forward returns control to the model, the hook synchronizes the active
|
|
||||||
platform so compute cannot race the transfer. XPU uses blocking device-to-host
|
|
||||||
copies because its allocator cannot safely evict storage while an asynchronous
|
|
||||||
copy is pending.
|
|
||||||
|
|
||||||
## Split-model delegation
|
|
||||||
|
|
||||||
If a pipeline provides callable `enable_omni_model_cpu_offload`, the backend
|
|
||||||
delegates instead of installing generic encoder/DiT hooks. This supports models
|
|
||||||
whose mutually exclusive components are nested inside one transformer, such
|
|
||||||
as a reasoner and generator.
|
|
||||||
|
|
||||||
The delegated pipeline owns its internal contexts, but must preserve the same
|
|
||||||
contract:
|
|
||||||
|
|
||||||
- only the executing component is device resident;
|
|
||||||
- enable and disable are idempotent;
|
|
||||||
- transfers use the supplied device, pinning, and HSDP settings; and
|
|
||||||
- disabling removes all model-local offload hooks or contexts.
|
|
||||||
|
|
||||||
The delegation is currently duck typed. If another independent implementation
|
|
||||||
is added, formalize the enable/disable pair as a protocol rather than expanding
|
|
||||||
attribute-name heuristics.
|
|
||||||
|
|
||||||
## Extension invariants
|
|
||||||
|
|
||||||
- New ordinary pipelines declare both DiT and encoder groups through
|
|
||||||
`SupportsComponentDiscovery`.
|
|
||||||
- Models with multiple DiTs offload the inactive DiTs as well as encoders.
|
|
||||||
- Empty or parameterless modules do not trigger transfers.
|
|
||||||
- Hook removal does not move modules; callers must not assume original
|
|
||||||
residency is restored.
|
|
||||||
|
|
||||||
Shared selection and lifecycle behavior is defined in the
|
|
||||||
[CPU Offloading design](README.md).
|
|
||||||
@@ -1,662 +0,0 @@
|
|||||||
# Distributed Layerwise Offload
|
|
||||||
|
|
||||||
This document describes distributed layerwise offload (DLO) for diffusion
|
|
||||||
models. DLO keeps only a small number of DiT blocks on the accelerator and
|
|
||||||
streams the remaining blocks from host memory. The distributed backend can
|
|
||||||
either shard those host-side weights across an existing parallel group or keep
|
|
||||||
complete rank-local block sources and avoid an additional collective.
|
|
||||||
|
|
||||||
For user-facing commands, see the
|
|
||||||
[distributed layerwise offloading guide](../../../user_guide/diffusion/offloader/distributed_layerwise_offload.md)
|
|
||||||
and the [Cosmos3 recipe](https://github.com/vllm-project/vllm-omni/blob/main/recipes/cosmos3/Cosmos3-DistOffload.md).
|
|
||||||
|
|
||||||
## Feature compatibility
|
|
||||||
|
|
||||||
Host-storage optimization and runtime compatibility are separate decisions.
|
|
||||||
When direct checkpoint mmap is unavailable, DLO can still use tensors produced
|
|
||||||
by the ordinary loader. "Compatibility path" below means that fallback is
|
|
||||||
implemented but has less end-to-end coverage than the primary path.
|
|
||||||
|
|
||||||
Legend: ✅ supported, ⚠️ compatibility path or limited validation, ❌ unsupported.
|
|
||||||
|
|
||||||
| Feature | DLO + AllGather | DLO without AllGather |
|
|
||||||
|---|---|---|
|
|
||||||
| **DP** | ✅ Primary path; host weights are sharded across the DP group. | ✅ Each DP rank streams complete rank-local blocks. |
|
|
||||||
| **SP** | ✅ When DP=1, DLO uses the SP group for weight sharding. | ✅ SP remains active without a DLO weight collective. |
|
|
||||||
| **TP > 1** | ⚠️ Ordinary TP-aware loader only; no direct checkpoint mmap. | ⚠️ Ordinary TP-aware loader only; no direct checkpoint mmap. |
|
|
||||||
| **HSDP** | ❌ Rejected to avoid double-sharding parameters. | ⚠️ Limited end-to-end coverage. |
|
|
||||||
| **Per-tensor online FP8 linears** | ✅ Ordinary loader finalizes weights and scales before DLO sharding. | ✅ Ordinary loader retains complete rank-local tensors. |
|
|
||||||
| **Other online quantization methods** | ❌ Rejected until runtime packing and scale layouts are validated. | ⚠️ Allowed through the ordinary loader; validation is method-specific. |
|
|
||||||
| **Model-level or standard layerwise CPU offload** | ❌ Disabled because DLO takes priority. | ❌ Disabled because DLO takes priority. |
|
|
||||||
| **Resident leading layers** | ❌ Rejected. | ✅ Requires eligible resident paths in the model's `OffloadPlan`. |
|
|
||||||
|
|
||||||
See [Parallelism compatibility](#parallelism-compatibility) and
|
|
||||||
[Request and loading constraints](#request-and-loading-constraints) for the
|
|
||||||
detailed contracts and validation boundaries.
|
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
DLO is implemented for multi-device diffusion execution. The default
|
|
||||||
AllGather path is the primary path for DP and SP deployments. The
|
|
||||||
`--dlo-no-use-allgather` path streams complete blocks independently and adds no
|
|
||||||
DLO weight collective.
|
|
||||||
|
|
||||||
Host storage is selected separately from the transfer protocol. The loader can
|
|
||||||
produce a direct-checkpoint mmap plan for a proven-compatible runtime layout;
|
|
||||||
otherwise it uses the ordinary loader. In no-AllGather mode, the Phase B host
|
|
||||||
weight cache can explicitly normalize supported final ordinary-loader DiT
|
|
||||||
tensors into an immutable node-local mmap entry. Ordinary pinned weights remain
|
|
||||||
the default after checkpoint mmap fallback when the cache is not selected; cache
|
|
||||||
publication and validation add startup and disk lifecycle costs. Replicas can
|
|
||||||
share either proven-compatible checkpoint pages or, when selected, equivalent
|
|
||||||
final runtime layouts. Under the existing
|
|
||||||
pinned-memory policy, the backend attempts to make the complete final mapping
|
|
||||||
directly H2D-copyable when the platform provides registration, avoiding the
|
|
||||||
recurrent host packing otherwise required by mmap. An optional positive limit
|
|
||||||
adds a per-worker safety ceiling. CUDA is the first implementation.
|
|
||||||
|
|
||||||
The Phase A shared-mmap support boundary is TP1. TP greater than one is an
|
|
||||||
ordinary-loader compatibility path: DLO can consume the resulting TP-local
|
|
||||||
tensors. Direct checkpoint mmap still does not cover TP, but the Phase B cache
|
|
||||||
can share each final TP coordinate among equivalent processes.
|
|
||||||
|
|
||||||
The compatibility matrix below defines the current implementation boundary.
|
|
||||||
Hardware and performance qualification is tracked separately from this
|
|
||||||
long-lived design contract.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
DLO is divided into four planes: configuration, loader-owned host storage,
|
|
||||||
backend setup, and the per-block inference hot path. Keeping these decisions
|
|
||||||
separate is intentional. In particular, an mmap cache is a host-storage choice;
|
|
||||||
it does not imply a DLO AllGather, and disabling DLO AllGather does not decide
|
|
||||||
whether the host source is private memory, checkpoint mmap, or host weight cache
|
|
||||||
mmap. The shared control plane is described once below; the AllGather and
|
|
||||||
no-AllGather startup and hot paths are then shown separately.
|
|
||||||
|
|
||||||
### Component ownership
|
|
||||||
|
|
||||||
The map includes optional mode-specific components, but the ownership rules
|
|
||||||
and loader-to-backend boundary are shared.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TB
|
|
||||||
CLI["CLI / EngineArgs"] --> Config["OmniDiffusionConfig<br/>validation and operator policy"]
|
|
||||||
Config --> Runner["DiffusionModelRunner<br/>startup coordinator"]
|
|
||||||
|
|
||||||
subgraph LoaderPlane["Loader-owned host-storage plane"]
|
|
||||||
Loader["DiffusersPipelineLoader"]
|
|
||||||
Checkpoint["checkpoint mmap preflight"]
|
|
||||||
Ordinary["ordinary load + callbacks + calibration"]
|
|
||||||
HostWeightCache["host_weight_cache<br/>final-layout publication / join"]
|
|
||||||
Contract["HostWeightPlan<br/>backing + bindings + transforms"]
|
|
||||||
Loader --> Checkpoint
|
|
||||||
Loader -->|"registered final layout requested"| Ordinary
|
|
||||||
Checkpoint -->|"compatible"| Contract
|
|
||||||
Checkpoint -->|"fallback"| Ordinary
|
|
||||||
Ordinary -->|"host weight cache enabled"| HostWeightCache
|
|
||||||
HostWeightCache -->|"enabled and compatible"| Contract
|
|
||||||
end
|
|
||||||
|
|
||||||
Runner -->|"load_model"| Loader
|
|
||||||
Loader -->|"pipeline + optional HostWeightPlan"| Runner
|
|
||||||
|
|
||||||
Config --> Factory["get_offload_backend<br/>OffloadConfig normalization"]
|
|
||||||
Runner -->|"device + optional plan"| Factory
|
|
||||||
Factory --> Backend["DistributedLayerwiseOffloadBackend<br/>resource and transfer owner"]
|
|
||||||
Topology["existing DP / SP process groups"] --> Backend
|
|
||||||
Discovery["ModuleDiscovery + optional OffloadPlan"] --> Backend
|
|
||||||
Contract -.->|"passed once by the runner"| Backend
|
|
||||||
|
|
||||||
Backend -.->|"host weight cache + pinned-memory policy"| Registration["host_registration<br/>platform-neutral lifecycle"]
|
|
||||||
Registration --> CUDA["CUDA registration backend"]
|
|
||||||
Backend --> Hooks["DistributedLayerwiseOffloadHook<br/>one circular hook per streamed block"]
|
|
||||||
Forward["pipeline.forward"] --> Hooks
|
|
||||||
Hooks --> DataPath["two device slots<br/>H2D only or H2D + AllGather"]
|
|
||||||
|
|
||||||
Shutdown["DiffusionWorker.shutdown"] -->|"disable before process-group teardown"| Backend
|
|
||||||
```
|
|
||||||
|
|
||||||
| Component | Owns | Must not own |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| CLI, engine arguments, and `OmniDiffusionConfig` | User policy, defaults, validation, and propagation to every worker | Checkpoint inspection, cache publication, or buffer allocation |
|
|
||||||
| `OffloadConfig.from_od_config()` | Offload-strategy selection, effective DLO group size, AllGather policy, resident-layer count, pinned-memory policy, and optional registration ceiling | Host-weight compatibility decisions |
|
|
||||||
| `DiffusersPipelineLoader` | Ordinary loading lifecycle and the decision to return an optional `HostWeightPlan` | DLO process groups or block-stream scheduling |
|
|
||||||
| Checkpoint mmap planner | Proving that checkpoint tensors can represent the required runtime layout before ordinary DiT materialization is skipped | Host weight cache identity or transfer policy |
|
|
||||||
| `host_weight_cache` | Normalizing final ordinary-loader tensors into an immutable node-local entry and returning a validated plan | Model mutation, host registration, H2D, or collectives |
|
|
||||||
| `HostWeightPlan` | The one-way loader-to-offloader contract: backing kind, tensor bindings, source coverage, and any bounded transforms | Cache construction or runtime scheduling |
|
|
||||||
| `ModuleDiscovery` and model `OffloadPlan` | Discovering DiT/components and declaring block, resident, and on-demand paths | Loader or cache behavior embedded in model pipelines |
|
|
||||||
| `DistributedLayerwiseOffloadBackend` | Consuming the exact plan, selecting the existing group, mapping/repointing storage, allocating shared buffers and streams, optional host registration, installing hooks, and cleanup | Re-running loader compatibility checks or orchestrating replicas |
|
|
||||||
| `DistributedLayerwiseOffloadHook` | The block hot path: prefetch, optional DLO AllGather, readiness events, parameter repointing, and replacement with offload placeholders | Cache keys, manifest validation, or request routing |
|
|
||||||
| `host_registration` and platform backend | All-or-nothing registration lifecycle for existing immutable mappings | Choosing which weights are cached or changing the two-slot device-buffer contract |
|
|
||||||
| `DiffusionWorker` | Calling backend teardown before destroying the distributed environment | Managing backend-owned mappings or registrations directly |
|
|
||||||
|
|
||||||
The configuration fields are routed to the component that can enforce their
|
|
||||||
contract. The cache root and lock timeout are consumed by the loader.
|
|
||||||
`dlo_use_host_weight_cache` explicitly selects final-layout cache publication
|
|
||||||
instead of direct-checkpoint mmap. It defaults to false so the ordinary
|
|
||||||
pinned-loader path remains available without requiring an opt-out.
|
|
||||||
With `pin_cpu_memory=True`, selecting the host weight cache deliberately chooses
|
|
||||||
the transform-complete layout even when direct checkpoint mmap is available,
|
|
||||||
because only that final layout can be registered without deferred transforms.
|
|
||||||
The backend consumes `dlo_host_weight_cache_pin_limit_gib` as an optional
|
|
||||||
registration ceiling; zero applies no additional ceiling and does not select
|
|
||||||
the cache by itself.
|
|
||||||
`dlo_use_allgather` controls loader plan routing because the host weight cache
|
|
||||||
is no-AllGather-only, and it selects the backend transfer protocol. This is
|
|
||||||
coordination through typed configuration and `HostWeightPlan`, not through
|
|
||||||
model-specific global state.
|
|
||||||
|
|
||||||
### Runtime host-layout identity (v1)
|
|
||||||
|
|
||||||
Host weight cache v1 identifies the loader-produced host representation, not
|
|
||||||
the topology used later to move weights to devices. These are separate
|
|
||||||
contracts:
|
|
||||||
|
|
||||||
- `RuntimeHostLayout` describes tensor ownership and representation after the
|
|
||||||
ordinary loader has applied its parallel-aware transforms. This is the
|
|
||||||
layout stored in the host weight cache.
|
|
||||||
- `DLOTransferLayout` describes whether each worker copies its local weights
|
|
||||||
directly or participates in the existing DLO AllGather protocol. It is not
|
|
||||||
part of the host weight cache in v1; AllGather continues to use its existing
|
|
||||||
process-owned host shards.
|
|
||||||
|
|
||||||
The v1 physical-sharing invariant is one cache entry per TP coordinate,
|
|
||||||
shared by equivalent DP and SP consumers on the same node. The identity
|
|
||||||
therefore includes TP world size and TP rank, because different TP coordinates
|
|
||||||
own different tensors. It excludes DP world size/rank and SP rank, because
|
|
||||||
those dimensions do not change the final tensor ownership supported by v1.
|
|
||||||
SP world size and implementation details remain conservative compatibility
|
|
||||||
guards until equivalent layouts across those configurations are proven.
|
|
||||||
|
|
||||||
PP, HSDP/DTensor, expert parallelism, and CFG parallelism are rejected rather
|
|
||||||
than assigned speculative cache identities. Supporting one of these layouts
|
|
||||||
requires first defining its loader-produced ownership, representation, and
|
|
||||||
sharing domain; it does not follow automatically from supporting its runtime
|
|
||||||
collectives. Consequently, this phase implements a TP-coordinate runtime host
|
|
||||||
layout, not a general parallel-layout cache.
|
|
||||||
|
|
||||||
### Shared startup and lifecycle
|
|
||||||
|
|
||||||
The shared control path selects host backing and transfers its ownership to
|
|
||||||
the backend before either transfer mode installs hooks:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant C as OmniDiffusionConfig
|
|
||||||
participant R as DiffusionModelRunner
|
|
||||||
participant L as DiffusersPipelineLoader
|
|
||||||
participant B as DLO backend
|
|
||||||
participant W as DiffusionWorker
|
|
||||||
|
|
||||||
C->>R: validated offload policy
|
|
||||||
R->>L: load_model(config)
|
|
||||||
L->>L: select and validate host backing
|
|
||||||
L-->>R: pipeline + optional HostWeightPlan
|
|
||||||
R->>B: enable(config, device, optional plan)
|
|
||||||
B->>B: discover modules and consume host backing
|
|
||||||
B-->>R: mode-specific backend is ready
|
|
||||||
W->>B: shutdown calls disable()
|
|
||||||
B->>B: synchronize, unregister, remove hooks, close mappings
|
|
||||||
```
|
|
||||||
|
|
||||||
The loader returns a plan only after its contract is valid: checkpoint plans
|
|
||||||
have preflight-proven source coverage and metadata, while host weight cache
|
|
||||||
plans also have full manifest, file, and mapped-content validation. The runner
|
|
||||||
then transfers ownership exactly once with
|
|
||||||
`model_loader.take_host_weight_plan()`. If no plan is returned, the backend
|
|
||||||
requires materialized ordinary-loader tensors. If a plan caused ordinary DiT
|
|
||||||
materialization to be skipped, failure to create or enable its consumer is a
|
|
||||||
hard startup error rather than a fallback to uninitialized parameters.
|
|
||||||
|
|
||||||
At inference time, the model pipeline remains the caller: normal block forward
|
|
||||||
calls activate the installed hooks. Both modes use one copy stream and two
|
|
||||||
shared device slots sized for the largest streamed block. The hooks do not
|
|
||||||
call the loader or cache.
|
|
||||||
|
|
||||||
### AllGather startup and hot path
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant B as DLO backend
|
|
||||||
participant H as block hooks
|
|
||||||
participant G as existing DP or SP group
|
|
||||||
|
|
||||||
B->>B: choose existing group and prepare persistent local shards
|
|
||||||
B->>B: allocate two device slots and a communication stream
|
|
||||||
B->>H: install hooks and prime the first block
|
|
||||||
loop each streamed block
|
|
||||||
H->>H: copy the next local shard to the alternate slot
|
|
||||||
H->>G: all_gather_into_tensor(next block)
|
|
||||||
G-->>H: complete next block is ready
|
|
||||||
H->>H: execute current block and rotate slots
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
AllGather uses the existing DP group when DP is greater than one, otherwise
|
|
||||||
the existing SP group when SP is greater than one. TP is never selected as
|
|
||||||
DLO's weight-sharding group. Every participating rank must request blocks in
|
|
||||||
the same order; fast P2P reduces communication cost but does not remove that
|
|
||||||
lockstep contract. An effective group size of one issues no collective and
|
|
||||||
uses rank-local transfer.
|
|
||||||
|
|
||||||
### No-AllGather startup and hot path
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant L as DiffusersPipelineLoader
|
|
||||||
participant C as host_weight_cache
|
|
||||||
participant B as DLO backend
|
|
||||||
participant P as platform registration
|
|
||||||
participant H as block hooks
|
|
||||||
|
|
||||||
alt host weight cache explicitly selected
|
|
||||||
L->>L: load, mutate, validate, and calibrate final tensors
|
|
||||||
L->>C: publish or join final-layout entry
|
|
||||||
C-->>L: validated host weight cache plan or fail-closed fallback
|
|
||||||
L-->>B: cache plan or materialized fallback via runner
|
|
||||||
else automatic checkpoint selection
|
|
||||||
L->>L: checkpoint mmap preflight
|
|
||||||
alt loader selects a compatible checkpoint plan
|
|
||||||
L-->>B: checkpoint plan via runner
|
|
||||||
else ordinary loading
|
|
||||||
L->>L: load, mutate, validate, and calibrate final tensors
|
|
||||||
L-->>B: materialized tensors via runner
|
|
||||||
end
|
|
||||||
end
|
|
||||||
B->>B: consume complete rank-local block sources
|
|
||||||
opt host weight cache plan and pinned-memory policy
|
|
||||||
B->>P: register the complete immutable mapping
|
|
||||||
P-->>B: direct-H2D source or bounded-staging fallback
|
|
||||||
end
|
|
||||||
B->>H: install hooks and prime the first block
|
|
||||||
loop each streamed block
|
|
||||||
alt registered host weight cache mapping
|
|
||||||
H->>H: copy complete next block directly to device
|
|
||||||
else pageable mmap mapping
|
|
||||||
H->>H: pack into a bounded staging slot, then copy to device
|
|
||||||
else ordinary runtime tensors
|
|
||||||
H->>H: copy complete next block to device
|
|
||||||
end
|
|
||||||
H->>H: execute current block and rotate slots
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
No-AllGather creates no DLO weight group and issues no DLO collective.
|
|
||||||
Workers may therefore schedule requests independently. Ordinary TP and SP
|
|
||||||
collectives remain inside model execution; host registration changes only
|
|
||||||
the source of the complete-block H2D copy.
|
|
||||||
|
|
||||||
### Host backing and transfer composition
|
|
||||||
|
|
||||||
| Loader-selected host backing | Backend transfer mode | Setup result | Per-block hot path |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| Ordinary runtime tensors | AllGather group larger than one | Each rank retains a pinned local weight shard | shard H2D → DLO AllGather → device slot |
|
|
||||||
| Checkpoint mmap | AllGather group larger than one | Each rank prepares its persistent shard, then releases the source mapping | shard H2D → DLO AllGather → device slot |
|
|
||||||
| Ordinary runtime tensors | Rank-local / no-AllGather | Each rank retains the complete loader-produced local layout | complete-block H2D → device slot |
|
|
||||||
| Checkpoint mmap, or host weight cache with pinned memory disabled/registration unavailable | Rank-local / no-AllGather | Immutable mapping plus two bounded host-staging slots | mmap views → staging slot → device slot |
|
|
||||||
| Host weight cache mmap with pinned memory enabled and registration available | Rank-local / no-AllGather | Complete mapping is registered by the platform backend | registered mmap views → device slot |
|
|
||||||
|
|
||||||
The three axes therefore remain independent:
|
|
||||||
|
|
||||||
1. **Offload policy:** whether DLO is enabled and which layers are resident.
|
|
||||||
2. **Host backing:** ordinary tensors, checkpoint mmap, or final host weight cache
|
|
||||||
mmap, selected by the loader.
|
|
||||||
3. **Transfer protocol:** rank-local H2D or H2D plus DLO AllGather, selected by
|
|
||||||
the backend from validated configuration and existing topology.
|
|
||||||
|
|
||||||
### Failure and lifecycle boundaries
|
|
||||||
|
|
||||||
- Invalid option combinations fail during configuration validation.
|
|
||||||
- Checkpoint mmap preflight failure falls back before ordinary loading is
|
|
||||||
skipped.
|
|
||||||
- Host weight cache incompatibility, timeout, or publication failure retains
|
|
||||||
the already-valid ordinary tensors.
|
|
||||||
- Host-registration failure rolls back and retains bounded staging. A rollback
|
|
||||||
failure aborts startup because unmapping a range still owned by the platform
|
|
||||||
would be unsafe.
|
|
||||||
- During shutdown, DLO synchronizes outstanding device work, unregisters host
|
|
||||||
mappings, removes hooks, and closes mmap handles only after safe unregister;
|
|
||||||
the worker destroys its process groups afterward.
|
|
||||||
|
|
||||||
DLO deliberately does not own DP replica placement, independent-engine request
|
|
||||||
routing, cross-engine admission control, or cross-node cache distribution.
|
|
||||||
Those belong to an external router/orchestrator. It also does not replace TP or
|
|
||||||
SP model collectives; it only adds an optional weight collective over an
|
|
||||||
already-existing DP or SP group.
|
|
||||||
|
|
||||||
## Shared design
|
|
||||||
|
|
||||||
The following topology and loader contracts apply to both transfer modes.
|
|
||||||
|
|
||||||
### DLO consumes the existing parallel topology
|
|
||||||
|
|
||||||
DLO does not create a new DP, TP, or SP topology. It reads the configured
|
|
||||||
`DiffusionParallelConfig` and attaches offload hooks to the DiT blocks after the
|
|
||||||
standard distributed groups have been initialized.
|
|
||||||
|
|
||||||
AllGather mode selects one existing group for DLO weight sharding; no-AllGather
|
|
||||||
mode creates no DLO group. Neither mode changes ordinary TP or SP model
|
|
||||||
collectives. The mode-specific group and scheduling contracts are described
|
|
||||||
below.
|
|
||||||
|
|
||||||
### The loader owns host-weight planning
|
|
||||||
|
|
||||||
Before it decides whether ordinary weight materialization can be skipped, the
|
|
||||||
diffusion loader builds one `HostWeightPlan`. A direct-checkpoint mmap plan is
|
|
||||||
accepted only when preflight proves all of the following:
|
|
||||||
|
|
||||||
- every required DiT parameter and persistent buffer has exactly one source;
|
|
||||||
- runtime names, checkpoint keys, shapes, and dtypes match;
|
|
||||||
- the runtime topology is TP1 without HSDP or online quantization; and
|
|
||||||
- every custom loader operation is represented by a loader-owned checkpoint
|
|
||||||
adapter.
|
|
||||||
|
|
||||||
The exact plan object is handed to DLO. The backend does not rescan checkpoint
|
|
||||||
files, repeat the capability decision, or reconstruct names from its block
|
|
||||||
topology. If preflight fails, the loader materializes weights normally and DLO
|
|
||||||
consumes those runtime tensors.
|
|
||||||
|
|
||||||
The plan owns only dedicated DiT component sources. If a pipeline also exposes
|
|
||||||
ordinary sources for a text encoder or another non-DiT component, the loader
|
|
||||||
still consumes those sources and includes their loaded names in its strict
|
|
||||||
coverage check. Only the source prefixes covered by the plan skip ordinary
|
|
||||||
materialization. A source that mixes DiT and non-DiT weights fails closed to
|
|
||||||
the complete ordinary-loader path because it cannot be skipped safely as a
|
|
||||||
unit.
|
|
||||||
|
|
||||||
This boundary keeps checkpoint semantics out of DLO and avoids model-pipeline
|
|
||||||
flags such as `_supports_mmap_loading` or parameter attributes for mmap-only
|
|
||||||
transforms. Model-specific direct-layout knowledge, when required, lives in a
|
|
||||||
checkpoint adapter beside the ordinary loader.
|
|
||||||
|
|
||||||
## AllGather design
|
|
||||||
|
|
||||||
### Group selection
|
|
||||||
|
|
||||||
The DLO weight-sharding group is selected in this order:
|
|
||||||
|
|
||||||
1. Use the existing DP group when `data_parallel_size > 1`.
|
|
||||||
2. When DP is one and SP is greater than one, use the existing SP group.
|
|
||||||
3. Otherwise, use an effective group size of one and issue no collective.
|
|
||||||
|
|
||||||
TP is deliberately not used as DLO's AllGather group. HSDP has its own
|
|
||||||
parameter-sharding lifecycle and cannot be sharded a second time by DLO.
|
|
||||||
|
|
||||||
### Sharded transfer and double buffering
|
|
||||||
|
|
||||||
The loader may provide ordinary runtime tensors or a compatible checkpoint
|
|
||||||
plan. During backend setup, either source is normalized into persistent local
|
|
||||||
shards. The final-layout host weight cache is not selected in AllGather mode.
|
|
||||||
|
|
||||||
With the default `dlo_use_allgather=True`, each rank stores approximately
|
|
||||||
`1 / group_size` of each streamable block in pinned host memory. The next
|
|
||||||
block's shard is copied to a device buffer and reconstructed with
|
|
||||||
`all_gather_into_tensor` on a communication stream while the current block is
|
|
||||||
executing.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The figure uses DP2 with TP2 to expose both parallel dimensions. DLO creates
|
|
||||||
one AllGather group across the DP replicas for each TP coordinate; TP0 workers
|
|
||||||
reconstruct TP0 blocks together and TP1 workers reconstruct TP1 blocks
|
|
||||||
together. Ordinary TP collectives remain inside each DP replica. Unlike the
|
|
||||||
no-AllGather host weight cache path, these persistent pinned host shards are
|
|
||||||
process-owned, and every participating rank must follow the same block order.
|
|
||||||
|
|
||||||
```text
|
|
||||||
Compute: [Block N] [Block N+1] [Block N+2]
|
|
||||||
H2D: [shard N+1] [shard N+2]
|
|
||||||
AllGather: [full N+1] [full N+2]
|
|
||||||
Buffers: [current slot] [prefetch slot] [current slot]
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The backend uses two shared device buffers, so accelerator weight residency is
|
|
||||||
bounded by the largest streamed blocks rather than the complete model.
|
|
||||||
|
|
||||||
When direct checkpoint mmap is selected, the checkpoint mappings are only the
|
|
||||||
source used to prepare each rank's persistent shard. They can be closed after
|
|
||||||
shard preparation. Across the AllGather group, those private shards total
|
|
||||||
approximately one runtime model copy.
|
|
||||||
|
|
||||||
An effective DLO group size of one performs no collective, even when
|
|
||||||
`dlo_use_allgather=True`; it follows the rank-local transfer path described
|
|
||||||
below.
|
|
||||||
|
|
||||||
When DP is greater than one, the engine can process one request per DP rank in
|
|
||||||
the same denoising wave. Because AllGather is a collective, all participating
|
|
||||||
requests must take the same execution path at every denoising step.
|
|
||||||
|
|
||||||
Fast NVLink/NVSwitch changes collective cost, not this execution contract. For
|
|
||||||
independently scheduled replicas—even on one NVSwitch baseboard—no-AllGather
|
|
||||||
plus shared mmap storage avoids coupling their request schedules and failure
|
|
||||||
domains.
|
|
||||||
|
|
||||||
## No-AllGather design
|
|
||||||
|
|
||||||
### Rank-local transfer
|
|
||||||
|
|
||||||
With `--dlo-no-use-allgather`, DLO forces its internal offload shard size to
|
|
||||||
one and streams complete blocks using H2D copies only. The host backing may be
|
|
||||||
a loader-approved checkpoint mapping, a normalized host weight cache mapping, or
|
|
||||||
ordinary runtime tensors.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The figure uses TP2 to make per-coordinate physical sharing explicit. Each
|
|
||||||
engine contains two rank-local workers and devices. The cache keeps one
|
|
||||||
immutable entry per TP coordinate, so equivalent TP0 workers across engines
|
|
||||||
share the TP0 entry and equivalent TP1 workers share the separate TP1 entry.
|
|
||||||
An external router or orchestrator chooses each engine's requests; DLO
|
|
||||||
coordinates only the worker-local mapping, hooks, transfer streams, events,
|
|
||||||
and device slots.
|
|
||||||
|
|
||||||
For direct mmap, each process retains immutable safetensors views and uses two
|
|
||||||
bounded pinned host staging slots. Processes on the same node that map the same
|
|
||||||
files share physical checkpoint pages through the OS page cache. This removes
|
|
||||||
the persistent private full-model copy per pure-DP process, but each process
|
|
||||||
still packs and transfers every complete block. Sharing is node-local; each
|
|
||||||
node has its own page cache.
|
|
||||||
|
|
||||||
For a host weight cache mapping, the default pinned-memory policy attempts to
|
|
||||||
replace those host slots with direct copies from the shared final tensor views.
|
|
||||||
Disabling pinned memory or a failed registration retains staging behavior.
|
|
||||||
|
|
||||||
When direct mmap preflight fails or the host weight cache is selected, the
|
|
||||||
regular model loader remains responsible for preparing each rank's weights,
|
|
||||||
including TP-local tensors or HSDP-managed parameters. Ordinary pinned tensors
|
|
||||||
remain the default without cache selection. When the host weight cache is
|
|
||||||
explicitly selected,
|
|
||||||
supported CPU layouts enter the cache; unsupported layouts keep one private
|
|
||||||
runtime copy per process.
|
|
||||||
|
|
||||||
This mode means:
|
|
||||||
|
|
||||||
- DP still provides independent replicas, but DLO does not shard weights
|
|
||||||
across DP ranks.
|
|
||||||
- SP still performs its normal activation/attention collectives, but DLO does
|
|
||||||
not shard weights across SP ranks.
|
|
||||||
- TP/HSDP/SP collectives, if configured, are not disabled by this flag; only
|
|
||||||
DLO's additional weight AllGather is disabled.
|
|
||||||
- Pure DP deployments share one checkpoint-backed copy per node when direct
|
|
||||||
mmap is selected. The host weight cache can share equivalent ordinary-loader
|
|
||||||
outputs; its fallback keeps one private runtime copy per rank.
|
|
||||||
- The scheduler does not require a synchronized DP request wave for DLO.
|
|
||||||
|
|
||||||
### Post-load host weight cache
|
|
||||||
|
|
||||||
The Phase B cache preserves the same ownership boundary. DLO neither derives
|
|
||||||
cache keys nor interprets model loaders. No-AllGather DLO first attempts a
|
|
||||||
compatible direct-checkpoint mmap plan. When that plan is unavailable, every
|
|
||||||
rank completes the ordinary loader, custom callbacks, post-load processing,
|
|
||||||
strict validation, and calibration. When cache use or registration is
|
|
||||||
explicitly selected, the loader:
|
|
||||||
|
|
||||||
1. discovers final CPU DiT parameters and persistent buffers;
|
|
||||||
2. rejects unsupported non-contiguous, aliased, quantized, distributed, or
|
|
||||||
ambiguous layouts before changing the model;
|
|
||||||
3. hashes tensor names, metadata, and final bytes;
|
|
||||||
4. builds or joins one content-addressed entry under a strict POSIX lock; and
|
|
||||||
5. returns a `HostWeightPlan(backing_kind="host_weight_cache")` only after file,
|
|
||||||
manifest, and remapped-tensor content validation succeeds.
|
|
||||||
|
|
||||||
The identity excludes DP size/rank and SP rank. It includes TP world size/rank
|
|
||||||
and conservative SP implementation/world-size guards. DP replicas and
|
|
||||||
equivalent SP ranks can therefore share; different TP coordinates cannot.
|
|
||||||
Final tensor content is authoritative, so an unmodeled loader/config change
|
|
||||||
that changes bytes creates a different entry instead of risking silent
|
|
||||||
sharing.
|
|
||||||
|
|
||||||
Publication writes sharded safetensors and a JSON manifest into a same-
|
|
||||||
filesystem temporary directory, flushes them, and atomically renames the
|
|
||||||
directory. The manifest includes per-file SHA-256 digests and the final tensor
|
|
||||||
content digest. Kernel `flock` ownership distinguishes a live writer from a
|
|
||||||
stale lock file: process death releases the lock automatically. Stale temporary
|
|
||||||
entries are removed only by the next lock owner. A bounded wait or any cache
|
|
||||||
error retains the already-valid ordinary tensors.
|
|
||||||
|
|
||||||
DLO maps this plan without moving the DiT to `meta` and without rerunning any
|
|
||||||
post-load hook. It changes tensor storage while preserving `Parameter` objects,
|
|
||||||
then runs the existing `gc.collect()`/`malloc_trim()` cleanup before installing
|
|
||||||
streaming hooks. The source mappings remain open through DLO disable. With
|
|
||||||
pinned memory disabled, or when registration is unavailable, they feed the
|
|
||||||
existing bounded two-slot host-staging path. Every streamed block is first
|
|
||||||
packed from pageable mmap storage into a staging slot and is then copied to the
|
|
||||||
device. This removes model-sized private pinned storage, but adds a recurrent
|
|
||||||
host-to-host copy and can amplify TP synchronization waits when several ranks
|
|
||||||
compete for host bandwidth.
|
|
||||||
|
|
||||||
This version is deliberately steady-state-oriented. It requires roughly one
|
|
||||||
local-disk model copy per runtime identity, has no eviction, and still performs
|
|
||||||
ordinary loading plus full content-hash passes in every rank. Cache publication
|
|
||||||
or validation failure falls back to the already-valid ordinary host tensors.
|
|
||||||
Skip-load-on-hit is a separate follow-up after loader side effects can be
|
|
||||||
modeled safely.
|
|
||||||
|
|
||||||
### Registered host weight cache transfer
|
|
||||||
|
|
||||||
Selecting the host weight cache with `pin_cpu_memory=True` opts a worker into
|
|
||||||
registration of the complete mapping when its platform supports it. It also
|
|
||||||
changes loader selection: no-AllGather uses the ordinary loader and final-layout
|
|
||||||
host weight cache even when a TP1 direct-checkpoint plan is available. A
|
|
||||||
checkpoint binding can carry a deferred adapter such as
|
|
||||||
grouped-QKV reordering. Registering that raw source would not remove the
|
|
||||||
adapter's recurrent CPU allocation/copy, whereas the host weight cache contains
|
|
||||||
the transform-complete final tensors.
|
|
||||||
|
|
||||||
After mapping and validating every final tensor, DLO groups source storages by
|
|
||||||
backing file, page-aligns their address ranges, and coalesces overlapping or
|
|
||||||
adjacent ranges within that mapping. When a positive per-worker ceiling is
|
|
||||||
configured, the complete byte count is checked against it before any platform
|
|
||||||
mutation. The CUDA backend
|
|
||||||
first requires `cudaDevAttrHostRegisterReadOnlySupported`, then registers every
|
|
||||||
range with the read-only host-registration flag and verifies that PyTorch
|
|
||||||
recognizes every mapped tensor as pinned. A device without that capability
|
|
||||||
retains bounded staging; vLLM-Omni does not replace immutable mappings with a
|
|
||||||
writable or copy-on-write CUDA-specific layout. Other registration failures
|
|
||||||
roll back any ranges already registered and preserve the existing two-slot
|
|
||||||
staging path. Partial direct transfer is deliberately unsupported. A rollback
|
|
||||||
failure aborts worker initialization so a still-registered range is retained
|
|
||||||
until process/context teardown rather than being unsafely unmapped.
|
|
||||||
|
|
||||||
On success, the hooks copy each immutable tensor view directly into its offset
|
|
||||||
in the existing flattened rotating device buffer on the copy stream. Parameter
|
|
||||||
repointing, two-block HBM residency, and H2D/compute overlap are unchanged. The
|
|
||||||
direct path trades recurrent host packing for finer-grained H2D operations. A
|
|
||||||
packed cache schema should be considered only if that additional format and
|
|
||||||
publication complexity becomes necessary.
|
|
||||||
|
|
||||||
Registration is per process and platform context; physical file-cache pages
|
|
||||||
remain shared and are not copied into one private host allocation per worker.
|
|
||||||
The operator must nevertheless budget page-locked memory and satisfy the
|
|
||||||
platform and OS registration limits for every worker. During shutdown, DLO
|
|
||||||
first synchronizes outstanding device work, unregisters all ranges, and then
|
|
||||||
closes the safetensors mappings. Worker teardown invokes this cleanup before
|
|
||||||
destroying the distributed environment and platform context.
|
|
||||||
|
|
||||||
## Parallelism compatibility
|
|
||||||
|
|
||||||
| Parallelism | DLO + AllGather | DLO without AllGather |
|
|
||||||
|---|---|---|
|
|
||||||
| **DP** | Supported primary path. DLO shards host weights across the DP group and can run DP multi-concurrency. | Supported rank-local path. Compatible TP1 replicas can share checkpoint pages; the optional host weight cache shares equivalent final layouts while excluding DP rank from its identity. CUDA workers may register the complete final mapping for direct H2D. |
|
|
||||||
| **SP** | Supported in the implementation. With DP=1, DLO uses the SP group for host-weight sharding; SP still shards sequence/activation work. | SP remains active without a DLO weight collective. Host weight cache v1 excludes SP rank and includes conservative SP implementation/world-size guards. Registered transfer does not remove ordinary SP activation/attention collectives. |
|
|
||||||
| **TP > 1** | Outside the Phase A direct-mmap scope. The loader preserves TP-local layouts and DLO may apply DP/SP host sharding to ordinary runtime tensors. | The ordinary TP-aware loader produces rank-local tensors. Host weight cache v1 creates one entry per TP coordinate, shareable by equivalent DP/SP processes. Registering those final mappings avoids the recurrent staging copy but does not remove ordinary TP collectives. |
|
|
||||||
| **HSDP** | Rejected. HSDP has already sharded parameters, so DLO AllGather would double-shard them. | Accepted only with ordinary-loader rank-local tensors. HSDP owns parameter sharding and its own gathers; host weight cache v1 rejects HSDP/DTensor layouts. |
|
|
||||||
|
|
||||||
### Combined dimensions
|
|
||||||
|
|
||||||
- **DP + SP:** DLO uses the DP group for weight sharding when DP is greater
|
|
||||||
than one; SP continues to use its own sequence-parallel group. If DP is one,
|
|
||||||
the SP group becomes DLO's sharding group in AllGather mode.
|
|
||||||
- **DP + TP/SP without AllGather:** standard model loading defines the
|
|
||||||
rank-local tensor layout. DLO adds no cross-DP, cross-TP, or cross-SP weight
|
|
||||||
collective.
|
|
||||||
- **HSDP + SP:** the general parallel configuration permits HSDP over SP, but
|
|
||||||
DLO must use `--dlo-no-use-allgather`. HSDP remains responsible for weight
|
|
||||||
materialization and synchronization.
|
|
||||||
- **HSDP + DP or TP:** rejected independently by the diffusion parallel
|
|
||||||
configuration.
|
|
||||||
|
|
||||||
## Mode constraints
|
|
||||||
|
|
||||||
### AllGather constraints
|
|
||||||
|
|
||||||
AllGather DP multi-concurrency requires:
|
|
||||||
|
|
||||||
- explicit `num_inference_steps`;
|
|
||||||
- the same `num_inference_steps` for all requests in a wave; and
|
|
||||||
- identical request arguments that affect the collective execution path.
|
|
||||||
|
|
||||||
Per-tensor online FP8 linears can use DLO AllGather after the ordinary loader
|
|
||||||
finalizes their runtime weights and scales. Other online methods must use
|
|
||||||
`--dlo-no-use-allgather` or disable online quantization until their runtime
|
|
||||||
layouts are validated.
|
|
||||||
|
|
||||||
### No-AllGather constraints
|
|
||||||
|
|
||||||
The no-AllGather path does not impose these DLO-specific synchronized-wave
|
|
||||||
requirements. Host weight cache v1 is no-AllGather only and rejects all
|
|
||||||
quantization configurations, HSDP/DTensor, expert parallelism, CFG
|
|
||||||
parallelism, PP, non-contiguous tensors, and tied/shared storage.
|
|
||||||
|
|
||||||
### Shared loading constraint
|
|
||||||
|
|
||||||
Direct checkpoint mmap can back either transfer path. It is currently limited
|
|
||||||
to proven TP1, non-HSDP, non-online-quantized layouts. Other layouts use the
|
|
||||||
ordinary loader.
|
|
||||||
|
|
||||||
## Validation status
|
|
||||||
|
|
||||||
Historical test counts and hardware measurements are intentionally not kept
|
|
||||||
in this design document because they can drift as the implementation changes.
|
|
||||||
Revalidation must run against the final commit and publish commit-pinned
|
|
||||||
artifacts before making hardware or performance claims. Track that work in
|
|
||||||
[issue #6231](https://github.com/vllm-project/vllm-omni/issues/6231).
|
|
||||||
|
|
||||||
## Recommendations
|
|
||||||
|
|
||||||
### AllGather
|
|
||||||
|
|
||||||
- Use **DP + DLO AllGather** when ranks already execute the same block path in
|
|
||||||
lockstep and belong to the same fast P2P domain. It shards persistent host
|
|
||||||
weights across that group.
|
|
||||||
- Use **SP + DLO AllGather** for long-sequence workloads when DP concurrency is
|
|
||||||
not the goal.
|
|
||||||
|
|
||||||
### No-AllGather
|
|
||||||
|
|
||||||
- Use **no-AllGather** when independent replica execution is required. TP1
|
|
||||||
direct-mmap deployments can share checkpoint pages per node. Enable the
|
|
||||||
host weight cache only when independent replicas have repeated final layouts
|
|
||||||
and benchmark the target CPU/memory topology, especially for matching
|
|
||||||
coordinates across multiple TP engines. With the default pinned-memory
|
|
||||||
policy, supported CUDA workers prefer registered direct H2D. Configure a
|
|
||||||
positive ceiling when an explicit page-locking bound is required, or disable
|
|
||||||
pinned memory when pageable staging is operationally preferable.
|
|
||||||
|
|
||||||
### HSDP
|
|
||||||
|
|
||||||
- Do not combine HSDP with DLO AllGather or host weight cache v1. HSDP with
|
|
||||||
no-AllGather remains an ordinary-loader rank-local path.
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# Layerwise Offload
|
|
||||||
|
|
||||||
Layerwise offload streams transformer blocks through one accelerator. For
|
|
||||||
commands and model support, see the
|
|
||||||
[layerwise user guide](../../../user_guide/diffusion/offloader/layerwise_offload.md).
|
|
||||||
|
|
||||||
## Hook ring
|
|
||||||
|
|
||||||
`LayerWiseOffloadBackend` discovers every streamable block and registers one
|
|
||||||
`LayerwiseOffloadHook` per block. Hooks form a ring: each hook owns the pinned
|
|
||||||
host representation of its registered block and a reference to the next block
|
|
||||||
it must prefetch; the final hook prefetches the first block for the next
|
|
||||||
denoising iteration.
|
|
||||||
|
|
||||||
During initialization, parameters and buffers are grouped by dtype and copied
|
|
||||||
into contiguous pinned CPU tensors. Original tensor storage is replaced with
|
|
||||||
an empty or meta placeholder while identity and module structure are retained.
|
|
||||||
Metadata records names, offsets, shapes, and dtypes for rematerialization.
|
|
||||||
|
|
||||||
## Forward lifecycle
|
|
||||||
|
|
||||||
For each block:
|
|
||||||
|
|
||||||
1. the previous hook's completion event ensures this block is materialized;
|
|
||||||
2. the copy stream asynchronously allocates and fills the next block's device
|
|
||||||
tensors;
|
|
||||||
3. parameter and buffer storage is rebound to views of those tensors;
|
|
||||||
4. the current block computes on the normal stream; and
|
|
||||||
5. its post-forward hook replaces device storage with placeholders after all
|
|
||||||
dependent work is safe.
|
|
||||||
|
|
||||||
The copy stream waits on the compute stream before reusing storage, and the
|
|
||||||
compute stream waits on the per-block prefetch event before forward. These
|
|
||||||
dependencies, not incidental global synchronization, define correctness.
|
|
||||||
|
|
||||||
## Topology contract
|
|
||||||
|
|
||||||
A DiT declares ordered block-container attributes using
|
|
||||||
`_layerwise_offload_blocks_attrs` or an `OffloadPlan`. Each resolved item must
|
|
||||||
be an executable `nn.Module`; ordering must match forward execution. Multiple
|
|
||||||
containers are concatenated in execution order.
|
|
||||||
|
|
||||||
Non-block DiT modules, encoders, VAEs, and declared resident modules are not
|
|
||||||
streamed by this backend.
|
|
||||||
|
|
||||||
## Invariants and limitations
|
|
||||||
|
|
||||||
- Each offloaded tensor has one pinned host backing store.
|
|
||||||
- Parameter identity is preserved when storage is rebound.
|
|
||||||
- Blocks with heterogeneous shapes and dtypes use their recorded metadata;
|
|
||||||
implementations must not assume identical blocks.
|
|
||||||
- The ring is initialized before the first forward so block zero is available.
|
|
||||||
- This backend is single-device; distributed weight reconstruction belongs to
|
|
||||||
[Distributed Layerwise Offload](distributed_layerwise_offload.md).
|
|
||||||
|
|
||||||
Shared selection and lifecycle behavior is defined in the
|
|
||||||
[CPU Offloading design](README.md).
|
|
||||||
@@ -76,12 +76,6 @@ equivalent. `auto-round` and `auto_round` use the Omni INC/AutoRound adapter.
|
|||||||
ModelOpt checkpoint metadata is detected from its method, producer metadata, or
|
ModelOpt checkpoint metadata is detected from its method, producer metadata, or
|
||||||
`quant_algo`, then mapped to the corresponding vLLM ModelOpt configuration.
|
`quant_algo`, then mapped to the corresponding vLLM ModelOpt configuration.
|
||||||
|
|
||||||
Unless the input is a per-component dictionary, the resolved configuration is
|
|
||||||
passed unchanged to every quantization-aware component constructed by the
|
|
||||||
pipeline. It affects only layers supported by that component's quantization
|
|
||||||
method; it does not rewrite arbitrary `torch.nn` modules. A per-component
|
|
||||||
dictionary narrows this global scope through runtime layer-prefix routing.
|
|
||||||
|
|
||||||
The factory uses Omni-specific overrides for methods whose diffusion behavior
|
The factory uses Omni-specific overrides for methods whose diffusion behavior
|
||||||
or platform dispatch is not provided by the vLLM registry. The current override
|
or platform dispatch is not provided by the vLLM registry. The current override
|
||||||
set includes INT8, BitsAndBytes, MXFP8, MXFP4, MXFP4 dual-scale, and INC/
|
set includes INT8, BitsAndBytes, MXFP8, MXFP4, MXFP4 dual-scale, and INC/
|
||||||
@@ -160,11 +154,9 @@ quantizable vLLM layers. Each method configuration is responsible for:
|
|||||||
Model code must keep precision-sensitive or unsupported modules unquantized.
|
Model code must keep precision-sensitive or unsupported modules unquantized.
|
||||||
The shared `safe_quant_config()` helper is used by model integrations for
|
The shared `safe_quant_config()` helper is used by model integrations for
|
||||||
normalization and modulation layers where a generic FP8 configuration would be
|
normalization and modulation layers where a generic FP8 configuration would be
|
||||||
unsafe. Encoders exposed through the quantization factory receive a global
|
unsafe. Text encoders, audio/vision encoders, VAEs, schedulers, and tokenizers
|
||||||
configuration just like the main transformer, but only their supported vLLM
|
are not implicitly quantized just because the main transformer is quantized;
|
||||||
quantizable layers are affected. Ordinary `torch.nn` encoder layers, VAEs,
|
they require explicit component routing and method support.
|
||||||
schedulers, and tokenizers are not rewritten automatically. A component map can
|
|
||||||
select a narrower scope, but it cannot add quantization support to a layer.
|
|
||||||
|
|
||||||
Weight quantization and quantized KV cache are separate paths. The former is
|
Weight quantization and quantized KV cache are separate paths. The former is
|
||||||
resolved through `quantization_config`; runtime attention/KV-cache quantization
|
resolved through `quantization_config`; runtime attention/KV-cache quantization
|
||||||
@@ -190,15 +182,12 @@ configured parallelism strategy:
|
|||||||
- `WeightsMapper` transformations must be applied before matching ignored-layer
|
- `WeightsMapper` transformations must be applied before matching ignored-layer
|
||||||
lists or component prefixes.
|
lists or component prefixes.
|
||||||
|
|
||||||
Distributed layerwise offload has an additional boundary: direct checkpoint
|
Distributed layerwise offload has an additional boundary: its sharded mmap
|
||||||
mmap cannot prepare online-quantized tensors because it bypasses the ordinary
|
AllGather path is not compatible with online quantization because the path
|
||||||
loader that creates their runtime weights and scales. Per-tensor online FP8
|
expects already materialized checkpoint weights. Use the rank-local
|
||||||
linears can still use DLO AllGather through the ordinary loader: each rank first
|
`--dlo-no-use-allgather` path or a compatible pre-quantized checkpoint when
|
||||||
finalizes the FP8 tensors, then DLO shards those runtime tensors for
|
combining these features. See the [DLO design document](distributed_layerwise_offload.md)
|
||||||
reconstruction during prefetch. Other online methods remain limited to
|
for the full compatibility matrix.
|
||||||
`--dlo-no-use-allgather` until their packing and scale layouts are validated.
|
|
||||||
See the [DLO design document](offloader/distributed_layerwise_offload.md) for
|
|
||||||
the full compatibility matrix.
|
|
||||||
|
|
||||||
## Adding a quantization backend
|
## Adding a quantization backend
|
||||||
|
|
||||||
|
|||||||
@@ -1,213 +0,0 @@
|
|||||||
# Realtime AR-Diffusion sessions
|
|
||||||
|
|
||||||
> **Status:** experimental. This document describes the internal execution
|
|
||||||
> contract implemented under `vllm_omni.experimental.ar_diffusion`. It does not
|
|
||||||
> define a public HTTP or WebSocket schema.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
Realtime AR-Diffusion serves world models that generate one autoregressive
|
|
||||||
latent block per request while retaining model state across requests. The
|
|
||||||
generic runtime owns identity, event ordering, session lifecycle, routing, and
|
|
||||||
KV allocation. A model adapter owns model-specific controls and conditioning.
|
|
||||||
|
|
||||||
LingBot World v2 is the first single-KV-branch integration. DreamZero uses the
|
|
||||||
same runner capability with two logical CFG branches.
|
|
||||||
|
|
||||||
## Identities
|
|
||||||
|
|
||||||
Four identifiers have distinct lifetimes:
|
|
||||||
|
|
||||||
| Identifier | Lifetime | Owner | Contract |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `session_id` | One persistent world | Session manager | Selects runner KV and model-owned state. |
|
|
||||||
| `event_id` | One control/prompt update | Transport adapter | Unique and monotonically increasing within a session, including across reset. |
|
|
||||||
| `chunk_index` | One committed AR block position | Session | Contiguous from zero; reset starts again at zero. |
|
|
||||||
| `request_id` | One chunk execution | Session | Correlates the tick with its returned metadata. |
|
|
||||||
|
|
||||||
`AsyncOmni` keeps its normal UUID-suffixed engine routing ID. The tick
|
|
||||||
`request_id` remains inside the immutable AR-Diffusion snapshot and the
|
|
||||||
standard output metadata, while caller-visible `OmniRequestOutput.request_id`
|
|
||||||
continues to use AsyncOmni's existing external-ID contract. Protocol identity
|
|
||||||
therefore does not require a private entrypoint override.
|
|
||||||
|
|
||||||
## Request lifecycle
|
|
||||||
|
|
||||||
1. `ARDiffusionSession.accept_event()` validates and queues an immutable
|
|
||||||
`ARDiffusionSessionEvent`. Prompt and opaque control tracks may be updated in
|
|
||||||
the same event.
|
|
||||||
2. `ARDiffusionSession.next_chunk()` serializes chunk execution and calls
|
|
||||||
`_build_tick_locked()`. The resulting `ARDiffusionTickRequest` is a snapshot
|
|
||||||
of the current prompt, reduced controls, ordered event IDs, and chunk index.
|
|
||||||
3. `ARDiffusionOmniTickConsumer.execute_tick()` copies the stage sampling
|
|
||||||
parameters, stores the typed tick in
|
|
||||||
`sampling_params.extra_args["ar_diffusion_tick"]`, constructs the normal Omni
|
|
||||||
prompt through its `prompt_provider`, and invokes `AsyncOmni.generate()`.
|
|
||||||
4. `AsyncOmni` allocates its normal unique engine routing ID and submits one
|
|
||||||
`OmniDiffusionRequest` through the orchestrator.
|
|
||||||
5. `ARDiffusionModelRunner._request_session()` parses the typed tick.
|
|
||||||
`_get_or_create_session()` obtains runner-owned paged KV, and
|
|
||||||
`bind_ar_diffusion_state()` exposes it to the pipeline only for the duration
|
|
||||||
of `execute_model()`.
|
|
||||||
6. The model pipeline validates the tick and generates exactly one latent
|
|
||||||
block. LingBot interprets camera controls in
|
|
||||||
`LingBotCameraControlReducer` and
|
|
||||||
`LingBotWorldCausalDMDPipeline.forward()`; the generic runtime does not parse
|
|
||||||
camera or action schemas.
|
|
||||||
7. The pipeline returns one `DiffusionOutput` with the generated payload and an
|
|
||||||
`ARDiffusionChunkMetadata` mapping under `metadata["ar_diffusion"]`.
|
|
||||||
`AsyncOmni` exposes it as
|
|
||||||
`OmniRequestOutput.multimodal_output["metadata"]["ar_diffusion"]`.
|
|
||||||
8. The consumer parses the standard envelope. The session commits its reducer
|
|
||||||
state, prompt, controls, event IDs, and next chunk index only when returned
|
|
||||||
metadata exactly equals the submitted tick snapshot.
|
|
||||||
|
|
||||||
The metadata envelope is:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"metadata": {
|
|
||||||
"ar_diffusion": {
|
|
||||||
"session_id": "world-7",
|
|
||||||
"request_id": "ar-world-7-3-...",
|
|
||||||
"chunk_index": 3,
|
|
||||||
"applied_event_ids": [10, 11]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Transaction and failure semantics
|
|
||||||
|
|
||||||
Accepted events are not acknowledged as applied until the model output and
|
|
||||||
metadata are validated. Reducer `prepare()` is speculative; reducer `commit()`
|
|
||||||
and the session snapshot form one logical commit. A model, metadata, or reducer
|
|
||||||
failure leaves the chunk index and queued events uncommitted and transitions the
|
|
||||||
session to `FAILED`.
|
|
||||||
|
|
||||||
After a tick failure, worker state is closed because a forward may have
|
|
||||||
partially modified or already released KV. The same chunk is not retried in
|
|
||||||
place. A caller must explicitly reset the session, starting again at chunk
|
|
||||||
zero, or close it.
|
|
||||||
|
|
||||||
Explicit close follows this state machine:
|
|
||||||
|
|
||||||
```text
|
|
||||||
ACTIVE or FAILED
|
|
||||||
|
|
|
||||||
v
|
|
||||||
CLOSING ---- worker cleanup succeeds ----> CLOSED
|
|
||||||
|
|
|
||||||
+------- worker cleanup fails --------> CLEANUP_FAILED
|
|
||||||
|
|
|
||||||
+-- close retry --> CLOSING
|
|
||||||
```
|
|
||||||
|
|
||||||
`CLEANUP_FAILED` is a local tombstone. The session manager retains it, rejects
|
|
||||||
creation of the same `session_id`, and rejects reset. Only an idempotent close
|
|
||||||
retry that succeeds removes the session. Transport disconnect uses the same
|
|
||||||
path.
|
|
||||||
|
|
||||||
## KV ownership and capacity
|
|
||||||
|
|
||||||
`ARDiffusionKVCacheSpec` is the pipeline-owned immutable geometry contract. It
|
|
||||||
declares:
|
|
||||||
|
|
||||||
- TP-local self-attention geometry, AR block size, sink, and recent window;
|
|
||||||
- logical KV branches and their worker-local slots;
|
|
||||||
- fixed-length cross-attention caches;
|
|
||||||
- scratch tokens required by an uncommitted forward;
|
|
||||||
- persistent model-owned CUDA bytes per resident session; and
|
|
||||||
- the requested resident-session capacity.
|
|
||||||
|
|
||||||
The runner computes:
|
|
||||||
|
|
||||||
```text
|
|
||||||
required(capacity) =
|
|
||||||
scratch
|
|
||||||
+ managed_self_attention_pages(capacity)
|
|
||||||
+ capacity * cross_attention_bytes_per_session
|
|
||||||
+ capacity * model_owned_state_bytes_per_session
|
|
||||||
```
|
|
||||||
|
|
||||||
`gpu_memory_fraction` is a soft expansion budget:
|
|
||||||
|
|
||||||
```text
|
|
||||||
configured_budget = available_device_bytes * gpu_memory_fraction
|
|
||||||
effective_budget = max(configured_budget, required(1))
|
|
||||||
```
|
|
||||||
|
|
||||||
If `required(1)` exceeds actual available device memory, initialization fails.
|
|
||||||
Otherwise the runner always admits one viable session and uses the configured
|
|
||||||
fraction to select additional resident sessions up to the model-declared cap.
|
|
||||||
All model-owned reservations are deducted before the paged self-attention pool
|
|
||||||
is allocated.
|
|
||||||
|
|
||||||
LingBot reports its persistent full-horizon image-condition tensor. DreamZero
|
|
||||||
reports the measured 603 MiB per-session Wan VAE causal-convolution state.
|
|
||||||
|
|
||||||
## Concurrency and routing limits
|
|
||||||
|
|
||||||
The current runner executes one request at a time:
|
|
||||||
|
|
||||||
- `max_num_seqs` must be one;
|
|
||||||
- request batching and diffusion step execution are rejected;
|
|
||||||
- one AR block is generated per engine request; and
|
|
||||||
- an AR-Diffusion stage must have exactly one replica.
|
|
||||||
|
|
||||||
The single-replica restriction is intentional. Multi-replica support requires
|
|
||||||
session-affine routing so every tick, reset, and close reaches the worker that
|
|
||||||
owns the session. Random or round-robin routing is not correct.
|
|
||||||
|
|
||||||
Multiple resident sessions may share one runner. Capacity selection accounts
|
|
||||||
for all persistent pools, and the runner performs LRU eviction through the same
|
|
||||||
model lifecycle hook used by explicit close.
|
|
||||||
|
|
||||||
## Model boundary
|
|
||||||
|
|
||||||
The generic runtime transports controls as opaque
|
|
||||||
`ARDiffusionControlInput(track, schema, data)` values. Model adapters validate
|
|
||||||
and reduce those values. This keeps target/velocity, key state, pose,
|
|
||||||
intrinsics, and other model-specific conventions out of the session protocol.
|
|
||||||
|
|
||||||
For LingBot World v2:
|
|
||||||
|
|
||||||
- `lingbot_world/actions.py` parses key-state and trajectory controls;
|
|
||||||
- `lingbot_world/camera.py` loads or constructs camera trajectories and Plücker embeddings;
|
|
||||||
- `lingbot_world/transformer.py` implements checkpoint-compatible causal attention; and
|
|
||||||
- `lingbot_world/pipeline.py` constructs conditioning, owns small non-KV session state, and
|
|
||||||
produces one block plus the standard metadata envelope.
|
|
||||||
|
|
||||||
## Non-goals
|
|
||||||
|
|
||||||
This contract does not currently provide:
|
|
||||||
|
|
||||||
- a public HTTP or WebSocket request schema (the structured-interaction
|
|
||||||
frontend is tracked separately in
|
|
||||||
[#5527](https://github.com/vllm-project/vllm-omni/pull/5527));
|
|
||||||
- camera/action semantics shared by every world model;
|
|
||||||
- session migration or replication across workers;
|
|
||||||
- retry of an ambiguous partially executed chunk;
|
|
||||||
- cross-stage KV transfer; or
|
|
||||||
- stateful streaming VAE decode.
|
|
||||||
|
|
||||||
Those features can be layered on top without adding LingBot-specific identity
|
|
||||||
or lifecycle fields to the generic runtime.
|
|
||||||
|
|
||||||
## Required regression coverage
|
|
||||||
|
|
||||||
CPU contract tests cover:
|
|
||||||
|
|
||||||
- separation of tick correlation IDs from internal engine routing IDs;
|
|
||||||
- event ordering, metadata equality, and reducer fail-closed commit;
|
|
||||||
- close/disconnect failure tombstones and cleanup retry;
|
|
||||||
- capacity one under the default fraction for shipped LingBot geometry;
|
|
||||||
- capacity expansion under a tuned fraction;
|
|
||||||
- DreamZero model-owned state accounting;
|
|
||||||
- runner session reuse, reset, close, LRU eviction, and forward cleanup; and
|
|
||||||
- LingBot model registration, imports, camera/action reduction, and one-block
|
|
||||||
output metadata.
|
|
||||||
|
|
||||||
GPU validation must additionally exercise real model loading, action input,
|
|
||||||
prompt switching, rolling-window replacement, reset, close, finite output
|
|
||||||
latents, and exact metadata/request identity.
|
|
||||||
@@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
Skip-Softmax is the sparse-attention mode of the `TRTLLM_ATTN` backend. Usage — the config keys
|
Skip-Softmax is the sparse-attention mode of the `TRTLLM_ATTN` backend. Usage — the config keys
|
||||||
and how to pick an operating point — is in
|
and how to pick an operating point — is in
|
||||||
[TRTLLM Attention](../../user_guide/diffusion/attention_backends/trtllm.md#skip-softmax).
|
[Diffusion Attention Backends](../../user_guide/diffusion/attention_backends.md). This page explains
|
||||||
The shared selector contract is documented in
|
the algorithm.
|
||||||
[Diffusion Attention Backend Selection](attention_backend_selection.md). This
|
|
||||||
page explains the algorithm.
|
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +1,57 @@
|
|||||||
# VAE Patch Parallelism
|
# VAE Parallel
|
||||||
|
|
||||||
This document describes how to add **VAE Patch Parallelism** support to a diffusion model.
|
This document defines the VAE parallelism strategy contract and describes how to
|
||||||
We use **Qwen-Image** as the reference implementation for decode parallel, and **Wan2.2** for encode parallel.
|
add a strategy to a diffusion model. Tile/patch parallelism is the portable
|
||||||
|
baseline. Spatial-shard parallelism is a model-specific decode strategy that can
|
||||||
|
be selected explicitly or through a request-aware policy.
|
||||||
|
|
||||||
|
We use **Qwen-Image** as the reference implementation for tile-parallel decode,
|
||||||
|
**Wan2.2** for tile-parallel encode, and **Wan2.2** as the first spatial-shard
|
||||||
|
decode implementation. Other VAE families can add spatial strategies later
|
||||||
|
without changing the public mode meanings defined here.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [Step-by-Step Implementation (Decode)](#step-by-step-implementation-decode)
|
- [Strategy Selection Contract](#strategy-selection-contract)
|
||||||
- [Encode Parallel Implementation](#encode-parallel-implementation)
|
- [Tile/Patch Implementation (Decode)](#tilepatch-implementation-decode)
|
||||||
|
- [Tile/Patch Encode Implementation](#tilepatch-encode-implementation)
|
||||||
- [Testing](#testing)
|
- [Testing](#testing)
|
||||||
- [Reference Implementations](#reference-implementations)
|
- [Reference Implementations](#reference-implementations)
|
||||||
|
- [Spatially-Sharded Decode](#spatially-sharded-decode)
|
||||||
|
- [Adding Another VAE Strategy](#adding-another-vae-strategy)
|
||||||
- [Summary](#summary)
|
- [Summary](#summary)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
### What is Vae Patch parallel?
|
### What is VAE parallelism?
|
||||||
|
|
||||||
**VAE Patch Parallelism** is an acceleration technique for both **encoding** and **decoding**. Instead of processing the entire tensor at once, the tensor is:
|
VAE parallelism distributes VAE **encoding** or **decoding** across multiple
|
||||||
|
ranks. The implementation currently has two strategy families:
|
||||||
|
|
||||||
+ Split into multiple spatial tiles
|
| Strategy | Unit of work | Communication | Current scope |
|
||||||
|
|----------|--------------|---------------|---------------|
|
||||||
|
| Tile/patch | Independent overlapping tiles | Gather, stitch, and optionally broadcast | Encode and decode across supported distributed VAEs |
|
||||||
|
| Spatial shard | One feature map sharded along a spatial axis | Per-layer halo exchange followed by output gather | Wan decode |
|
||||||
|
|
||||||
+ Distributed across multiple ranks
|
Both strategies preserve the model-specific receptive field and reconstruct the
|
||||||
|
same logical output as single-rank execution. They differ in how work is divided:
|
||||||
|
tile parallelism assigns complete overlapping tiles to ranks, while spatial
|
||||||
|
sharding keeps one logical feature map partitioned throughout the decoder.
|
||||||
|
|
||||||
+ Encoded/Decoded in parallel
|
This approach can:
|
||||||
|
|
||||||
+ Merged to reconstruct the final output
|
+ Distribute computation across multiple devices
|
||||||
|
+ Reduce peak memory usage per device
|
||||||
|
+ Accelerate encoding or decoding latency
|
||||||
|
|
||||||
This approach:
|
The actual benefit depends on shape, topology, communication cost, and VAE
|
||||||
|
architecture. A strategy must not become the global default without parity and
|
||||||
+ Distributes computation across multiple devices
|
performance evidence on its supported models.
|
||||||
|
|
||||||
+ Reduces peak memory usage per device
|
|
||||||
|
|
||||||
+ Accelerates encoding/decoding latency
|
|
||||||
|
|
||||||
### When to Use Encode vs Decode Parallel
|
### When to Use Encode vs Decode Parallel
|
||||||
|
|
||||||
@@ -45,8 +60,16 @@ This approach:
|
|||||||
| **Decode Parallel** | Text-to-Image, Text-to-Video | Latent → Image/Video |
|
| **Decode Parallel** | Text-to-Image, Text-to-Video | Latent → Image/Video |
|
||||||
| **Encode Parallel** | Image-to-Video (I2V) | Image → Latent (for conditioning) |
|
| **Encode Parallel** | Image-to-Video (I2V) | Image → Latent (for conditioning) |
|
||||||
|
|
||||||
### Architecture
|
### Architecture and ownership
|
||||||
We introduce **DistributedVaeExecutor** as the core component responsible for distributed VAE encoding/decoding.
|
|
||||||
|
`DiffusionParallelConfig.vae_parallel_mode` carries the public strategy request.
|
||||||
|
The shared registry forwards that value to the VAE, but each distributed VAE
|
||||||
|
adapter owns its supported modes, request-shape selection, topology validation,
|
||||||
|
and fallback behavior. Model-specific policy must not be added to the shared
|
||||||
|
runner or registry.
|
||||||
|
|
||||||
|
**DistributedVaeExecutor** is the model-agnostic core for tile-parallel VAE
|
||||||
|
encoding and decoding.
|
||||||
|
|
||||||
The executor is model-agnostic and accepts three function parameters:
|
The executor is model-agnostic and accepts three function parameters:
|
||||||
|
|
||||||
@@ -76,6 +99,11 @@ This design separates:
|
|||||||
|
|
||||||
+ Model-specific tiling and merging logic
|
+ Model-specific tiling and merging logic
|
||||||
|
|
||||||
|
Spatial-shard implementations use the same configured VAE process group but do
|
||||||
|
not use the tile task executor. They keep the feature map sharded across decoder
|
||||||
|
layers and own their halo-exchange and final-gather contracts inside the model's
|
||||||
|
distributed autoencoder implementation.
|
||||||
|
|
||||||
#### Why split / exec / merge is necessary?
|
#### Why split / exec / merge is necessary?
|
||||||
|
|
||||||
The latent tensor cannot be arbitrarily partitioned.
|
The latent tensor cannot be arbitrarily partitioned.
|
||||||
@@ -92,7 +120,34 @@ Therefore:
|
|||||||
|
|
||||||
+ Merge must perform blending to avoid seams
|
+ Merge must perform blending to avoid seams
|
||||||
|
|
||||||
## Step-by-Step Implementation (Decode)
|
## Strategy Selection Contract
|
||||||
|
|
||||||
|
`vae_parallel_mode="tile"` is the compatibility-preserving global default.
|
||||||
|
Other values are stable strategy requests, but support remains model-specific.
|
||||||
|
|
||||||
|
| Mode | Meaning | Current implementation | Unsupported or ineligible request |
|
||||||
|
|------|---------|------------------------|-----------------------------------|
|
||||||
|
| `tile` | Use the existing tile/patch executor | All distributed VAE adapters | Not applicable |
|
||||||
|
| `auto` | Let the VAE adapter select a validated strategy for each request | Wan decode selects height or width spatial sharding | Wan uses tile when ineligible; unsupported VAE families reject or document their fallback |
|
||||||
|
| `spatial_shard_height` | Force spatial-shard decode along height | Wan decode | Wan warns and uses tile when ineligible; unsupported VAE families reject |
|
||||||
|
| `spatial_shard_width` | Force spatial-shard decode along width | Wan decode | Wan warns and uses tile when ineligible; unsupported VAE families reject |
|
||||||
|
|
||||||
|
The shared configuration layer validates the mode name. It does not decide
|
||||||
|
whether a model supports that mode. A VAE adapter adding a mode must:
|
||||||
|
|
||||||
|
1. Keep `tile` as an exact feature-off path.
|
||||||
|
2. Select a strategy per request rather than from stale process-global state.
|
||||||
|
3. Validate input rank/shape and process-group membership before collectives.
|
||||||
|
4. Reject or explicitly fall back from unsupported combinations; never silently
|
||||||
|
reinterpret one forced strategy as another.
|
||||||
|
5. Preserve direct encode/decode behavior when Diffusers does not select a tiled
|
||||||
|
path.
|
||||||
|
|
||||||
|
`auto` is a policy hook, not a universal heuristic. Each VAE family may select
|
||||||
|
from only the strategies it has validated. Adding another family must not change
|
||||||
|
Wan's selector or the meanings of the explicit modes.
|
||||||
|
|
||||||
|
## Tile/Patch Implementation (Decode)
|
||||||
|
|
||||||
### Step 1: Implement DistributedAutoencoderKLQwenImage
|
### Step 1: Implement DistributedAutoencoderKLQwenImage
|
||||||
`QwenImagePipeline` use `AutoencoderKLQwenImage` for vae, so implement a distributed version:
|
`QwenImagePipeline` use `AutoencoderKLQwenImage` for vae, so implement a distributed version:
|
||||||
@@ -251,7 +306,7 @@ class YourModelPipeline(nn.Module):
|
|||||||
+ ).to(self.device)
|
+ ).to(self.device)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Encode Parallel Implementation
|
## Tile/Patch Encode Implementation
|
||||||
|
|
||||||
For models that require VAE encoding (e.g., Image-to-Video), you can also parallelize the encode operation. We use **Wan2.2** as the reference implementation.
|
For models that require VAE encoding (e.g., Image-to-Video), you can also parallelize the encode operation. We use **Wan2.2** as the reference implementation.
|
||||||
|
|
||||||
@@ -449,18 +504,24 @@ Complete examples in the codebase:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Spatially-Sharded Decode (Wan)
|
## Spatially-Sharded Decode
|
||||||
|
|
||||||
The tile-parallel executor above assigns independent spatial **tiles** to ranks. The Wan VAE additionally supports a **spatially-sharded decode** backend, selected through `DiffusionParallelConfig.vae_parallel_mode` (`"spatial_shard_height"` or `"spatial_shard_width"`; default is `"tile"`).
|
The tile-parallel executor above assigns independent spatial **tiles** to ranks.
|
||||||
|
A spatial-shard backend instead partitions one global decoder feature map along
|
||||||
|
height or width. Wan is the first supported implementation.
|
||||||
|
|
||||||
|
`DiffusionParallelConfig.vae_parallel_mode="tile"` remains the default. For Wan,
|
||||||
|
opt into request-shape selection with `"auto"`, or force an axis with
|
||||||
|
`"spatial_shard_height"` or `"spatial_shard_width"`.
|
||||||
|
|
||||||
### How it differs from tile parallel
|
### How it differs from tile parallel
|
||||||
|
|
||||||
| Aspect | Tile parallel (`"tile"`) | Spatially-sharded (`"spatial_shard_height"`/`"spatial_shard_width"`) |
|
| Aspect | Tile parallel (`"tile"`) | Automatic (`"auto"`) | Spatially-sharded (`"spatial_shard_height"`/`"spatial_shard_width"`) |
|
||||||
|--------|--------------------------|-----------------------------------------------|
|
|--------|--------------------------|----------------------|-----------------------------------------------|
|
||||||
| Unit of work | Independent overlapping tiles | A single global feature map sharded along H or W |
|
| Unit of work | Independent overlapping tiles | Chooses tile or a spatial shard per request | A single global feature map sharded along H or W |
|
||||||
| Cross-rank communication | Gather tiles to rank 0, stitch + blend | Per-conv **halo exchange** of boundary rows/cols (P2P) |
|
| Cross-rank communication | Gather tiles to rank 0, stitch + blend | Depends on the selected path | Per-conv **halo exchange** of boundary rows/cols (P2P) |
|
||||||
| Output assembly | Blend overlapping tiles | All-gather shards on rank 0, trim padding (matches `broadcast_result=False`) |
|
| Output assembly | Blend overlapping tiles | Depends on the selected path | All-gather shards on rank 0, trim padding (matches `broadcast_result=False`) |
|
||||||
| Scope | Decode + encode | Decode only |
|
| Scope | Decode + encode | Wan decode selection; encode remains tiled | Wan decode only |
|
||||||
|
|
||||||
Spatial-shard decode swaps the decoder's spatial convolutions/padding for halo-exchanging variants (`WanDistConv2d`, `WanDistCausalConv3d`, `WanDistZeroPad2d`) so each rank only holds a shard of the activations but still sees the correct receptive field at shard boundaries. Implementation lives in `vllm_omni/diffusion/distributed/autoencoders/wan_spatial_shard.py`.
|
Spatial-shard decode swaps the decoder's spatial convolutions/padding for halo-exchanging variants (`WanDistConv2d`, `WanDistCausalConv3d`, `WanDistZeroPad2d`) so each rank only holds a shard of the activations but still sees the correct receptive field at shard boundaries. Implementation lives in `vllm_omni/diffusion/distributed/autoencoders/wan_spatial_shard.py`.
|
||||||
|
|
||||||
@@ -476,21 +537,65 @@ serve.py (--vae-parallel-mode) / OmniEngineArgs
|
|||||||
-> DistributedAutoencoderKLWan.tiled_decode dispatch
|
-> DistributedAutoencoderKLWan.tiled_decode dispatch
|
||||||
```
|
```
|
||||||
|
|
||||||
`DistributedAutoencoderKLWan._spatial_shard_decode_enabled()` gates the path: it requires distributed decode to be enabled, a 5D latent, and `vae_patch_parallel_size == DiT group size`. Otherwise it logs a warning and falls back to tile-parallel decode.
|
`DistributedAutoencoderKLWan._spatial_shard_decode_enabled()` gates the path: it
|
||||||
|
requires distributed decode to be enabled, a 5D latent, and
|
||||||
|
`vae_patch_parallel_size == DiT group size`. The method is reached only after
|
||||||
|
Diffusers has selected tiled decode for the request. In `auto` mode the longer
|
||||||
|
axis that can cover every rank is selected, with width winning ties. If neither
|
||||||
|
axis can cover the group, auto mode uses tile decode. This policy is cached only
|
||||||
|
through normal kernel shape caches; it is reevaluated for every request.
|
||||||
|
|
||||||
### Notes
|
### Notes
|
||||||
|
|
||||||
- The decoder is patched **in place** the first time spatial-shard decode runs and is bound to a single split dimension for the lifetime of the VAE instance.
|
- The decoder is patched **in place** the first time spatial-shard decode runs. Its wrappers consult a context-local split dimension and retain an exact direct fallback, allowing later requests to select tile, height, or width safely.
|
||||||
- Numerical correctness vs. single-GPU decode is covered by `tests/diffusion/distributed/test_wan_spatial_shard.py::test_spatial_shard_decode_matches_reference` (multi-GPU, nightly `full_model` + `distributed_cuda`).
|
- Numerical correctness vs. single-GPU decode is covered by `tests/diffusion/distributed/test_wan_spatial_shard.py::test_spatial_shard_decode_matches_reference` (multi-GPU, nightly `full_model` + `distributed_cuda`).
|
||||||
|
|
||||||
|
## Adding Another VAE Strategy
|
||||||
|
|
||||||
|
Add future strategies through the distributed autoencoder that owns the model
|
||||||
|
semantics. Do not add model-name dispatch to the shared runner, registry, or
|
||||||
|
`DistributedVaeExecutor`.
|
||||||
|
|
||||||
|
For a new VAE family:
|
||||||
|
|
||||||
|
1. **Declare capability**: document which modes, operations, shapes, dtypes, and
|
||||||
|
process-group layouts the adapter supports.
|
||||||
|
2. **Implement model-local selection**: keep `auto` policy and explicit-mode
|
||||||
|
dispatch in the distributed autoencoder. The selector may use request shape,
|
||||||
|
but must be deterministic and side-effect free.
|
||||||
|
3. **Preserve the baseline**: retain exact tile/direct fallbacks and state-dict
|
||||||
|
compatibility. Structural wrappers must not register duplicate parameters or
|
||||||
|
alter checkpoint names.
|
||||||
|
4. **Define communication**: derive shards from the configured VAE group, keep
|
||||||
|
collectives symmetric, specify padding/halo ownership, and return the same
|
||||||
|
output placement as the tile path.
|
||||||
|
5. **Bound request state**: make the selected axis and scratch buffers
|
||||||
|
request-scoped, release temporary buffers on success and failure, and support
|
||||||
|
alternating modes in one long-lived worker.
|
||||||
|
6. **Validate independently**: compare each explicit strategy against the
|
||||||
|
single-rank reference, then test `auto`, tile fallback, unsupported topology,
|
||||||
|
and a mixed request sequence. Record per-rank peak memory and decode latency
|
||||||
|
before documenting a preferred mode.
|
||||||
|
|
||||||
|
Promote a selector into shared infrastructure only after at least two VAE
|
||||||
|
families use the same inputs, invariants, fallback behavior, and lifecycle. Until
|
||||||
|
then, shared code owns transport and configuration while each model adapter owns
|
||||||
|
policy.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Adding VAE Patch Parallel support to diffusion model:
|
Adding VAE parallel support to a diffusion model:
|
||||||
|
|
||||||
1. **Implement Distributed VAE** - Inherit from base VAE class and `DistributedVaeMixin`
|
1. **Implement the distributed VAE** - Inherit from the base VAE class and
|
||||||
2. **Decode Parallel** - Refactor `tiled_decode` into `tile_split`/`tile_exec`/`tile_merge`
|
`DistributedVaeMixin`.
|
||||||
3. **Encode Parallel** (optional) - Implement `encode_tile_split`/`encode_tile_exec`/`encode_tile_merge` for I2V models
|
2. **Keep tile as the baseline** - For tile decode, refactor `tiled_decode` into
|
||||||
4. **Change VAE model in pipeline** - Use the distributed version
|
`tile_split`/`tile_exec`/`tile_merge`.
|
||||||
5. **Test** - Verify numerical consistency with `vae_patch_parallel_size=1` vs `N`
|
3. **Add encode parallelism when needed** - Implement
|
||||||
|
`encode_tile_split`/`encode_tile_exec`/`encode_tile_merge` for I2V models.
|
||||||
|
4. **Add model-specific strategies locally** - Own selectors, topology checks,
|
||||||
|
communication, and fallback in the distributed autoencoder.
|
||||||
|
5. **Wire the pipeline** - Replace the pipeline VAE with its distributed version.
|
||||||
|
6. **Test strategy and lifecycle parity** - Compare one rank with every supported
|
||||||
|
strategy, including feature-off, fallback, and mixed-request sequences.
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="850" viewBox="0 0 1280 850" role="img" aria-labelledby="title desc">
|
|
||||||
<title id="title">AllGather DLO sharded host weights and lockstep device reconstruction</title>
|
|
||||||
<desc id="desc">An external orchestrator submits one synchronized wave to a DP2 by TP2 diffusion engine. Each DP replica contains TP0 and TP1 workers with private pinned host shards and two rotating GPU weight slots. DLO AllGather groups connect matching TP coordinates across DP replicas, reconstructing full rank-local blocks on each device in lockstep. Ordinary TP groups remain within each DP replica.</desc>
|
|
||||||
<defs>
|
|
||||||
<marker id="request-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
|
||||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#7c3aed"/>
|
|
||||||
</marker>
|
|
||||||
<marker id="h2d-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
|
||||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#d97706"/>
|
|
||||||
</marker>
|
|
||||||
<marker id="collective-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
|
||||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#7c3aed"/>
|
|
||||||
</marker>
|
|
||||||
<style>
|
|
||||||
.canvas { fill: #ffffff; stroke: #e2e8f0; }
|
|
||||||
.node { fill: #f8fafc; stroke: #cbd5e1; }
|
|
||||||
.engine { fill: #ffffff; stroke: #cbd5e1; }
|
|
||||||
.replica { fill: #f8fafc; stroke: #cbd5e1; }
|
|
||||||
.router { fill: #f5f3ff; stroke: #c4b5fd; }
|
|
||||||
.scope { fill: #f8fafc; stroke: #cbd5e1; }
|
|
||||||
.host { fill: #fff7ed; stroke: #fdba74; }
|
|
||||||
.shard { fill: #d97706; }
|
|
||||||
.gpu { fill: #f0fdf4; stroke: #86efac; }
|
|
||||||
.slot-current { fill: #16a34a; }
|
|
||||||
.slot-next { fill: #bbf7d0; stroke: #22c55e; }
|
|
||||||
.lockstep { fill: #f5f3ff; stroke: #c4b5fd; }
|
|
||||||
.title { font: 700 25px system-ui, -apple-system, Segoe UI, sans-serif; fill: #0f172a; }
|
|
||||||
.subtitle { font: 500 13px system-ui, -apple-system, Segoe UI, sans-serif; fill: #64748b; }
|
|
||||||
.section { font: 700 15px system-ui, -apple-system, Segoe UI, sans-serif; fill: #1e293b; }
|
|
||||||
.section-sub { font: 500 11.5px system-ui, -apple-system, Segoe UI, sans-serif; fill: #64748b; }
|
|
||||||
.eyebrow { font: 700 10px system-ui, -apple-system, Segoe UI, sans-serif; letter-spacing: .8px; fill: #64748b; }
|
|
||||||
.box-title { font: 700 12px system-ui, -apple-system, Segoe UI, sans-serif; fill: #1e293b; }
|
|
||||||
.box-text { font: 500 10.5px system-ui, -apple-system, Segoe UI, sans-serif; fill: #475569; }
|
|
||||||
.shard-title { font: 700 13px system-ui, -apple-system, Segoe UI, sans-serif; fill: #ffffff; }
|
|
||||||
.shard-text { font: 500 10.5px system-ui, -apple-system, Segoe UI, sans-serif; fill: #ffffff; }
|
|
||||||
.slot-title { font: 700 10px system-ui, -apple-system, Segoe UI, sans-serif; fill: #ffffff; }
|
|
||||||
.slot-text { font: 500 9px system-ui, -apple-system, Segoe UI, sans-serif; fill: #ffffff; }
|
|
||||||
.slot-next-title { font: 700 10px system-ui, -apple-system, Segoe UI, sans-serif; fill: #166534; }
|
|
||||||
.slot-next-text { font: 500 9px system-ui, -apple-system, Segoe UI, sans-serif; fill: #166534; }
|
|
||||||
.request-label { font: 600 10.5px system-ui, -apple-system, Segoe UI, sans-serif; fill: #6d28d9; }
|
|
||||||
.h2d-label { font: 600 10.5px system-ui, -apple-system, Segoe UI, sans-serif; fill: #b45309; }
|
|
||||||
.collective-label { font: 700 10px system-ui, -apple-system, Segoe UI, sans-serif; fill: #6d28d9; }
|
|
||||||
.lock-title { font: 700 12px system-ui, -apple-system, Segoe UI, sans-serif; fill: #6d28d9; }
|
|
||||||
.lock-text { font: 500 10.5px system-ui, -apple-system, Segoe UI, sans-serif; fill: #5b21b6; }
|
|
||||||
.request-path { fill: none; stroke: #7c3aed; stroke-width: 2; marker-end: url(#request-arrow); }
|
|
||||||
.h2d-path { fill: none; stroke: #d97706; stroke-width: 2; marker-end: url(#h2d-arrow); }
|
|
||||||
.collective-path { fill: none; stroke: #7c3aed; stroke-width: 2.5; marker-start: url(#collective-arrow); marker-end: url(#collective-arrow); }
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<rect x="12" y="12" width="1256" height="826" rx="14" class="canvas"/>
|
|
||||||
|
|
||||||
<text id="title-text" x="40" y="50" class="title">AllGather DLO: sharded host weights, lockstep reconstruction</text>
|
|
||||||
<text x="40" y="74" class="subtitle">DP2 × TP2 shown: DLO groups span DP replicas per TP coordinate; ordinary TP stays within each replica</text>
|
|
||||||
|
|
||||||
<!-- The external control plane submits one synchronized DP wave. -->
|
|
||||||
<rect x="390" y="94" width="500" height="68" rx="10" class="router"/>
|
|
||||||
<text x="640" y="121" text-anchor="middle" class="section">External router / orchestrator</text>
|
|
||||||
<text x="640" y="144" text-anchor="middle" class="section-sub">submits one synchronized DP wave · outside vLLM-Omni DLO</text>
|
|
||||||
|
|
||||||
<rect x="30" y="184" width="1220" height="634" rx="14" class="node"/>
|
|
||||||
<text x="52" y="211" class="eyebrow">ONE NODE · ONE SYNCHRONIZED ENGINE, PROCESS-OWNED HOST SHARDS</text>
|
|
||||||
|
|
||||||
<path d="M 640 162 L 640 232" class="request-path"/>
|
|
||||||
<text x="654" y="202" class="request-label">request wave</text>
|
|
||||||
|
|
||||||
<!-- One engine contains two DP replicas that execute in lockstep. -->
|
|
||||||
<rect x="45" y="232" width="1190" height="262" rx="12" class="engine"/>
|
|
||||||
<text x="68" y="261" class="section">One synchronized diffusion engine · DP2 × TP2</text>
|
|
||||||
<text x="68" y="281" class="section-sub">two DP requests follow the same block order; DLO reuses existing DP groups at each TP coordinate</text>
|
|
||||||
|
|
||||||
<!-- DP replica 0. -->
|
|
||||||
<rect x="65" y="296" width="515" height="186" rx="10" class="replica"/>
|
|
||||||
<text x="82" y="318" class="box-title">DP replica 0 · request 0</text>
|
|
||||||
<text x="563" y="318" text-anchor="end" class="section-sub">ordinary TP group</text>
|
|
||||||
|
|
||||||
<rect x="82" y="332" width="473" height="64" rx="8" class="gpu"/>
|
|
||||||
<text x="98" y="351" class="eyebrow">TP0 · GPU 0</text>
|
|
||||||
<text x="98" y="376" class="box-text">DLO backend · local shard H2D</text>
|
|
||||||
<rect x="250" y="340" width="132" height="44" rx="6" class="slot-current"/>
|
|
||||||
<text x="316" y="357" text-anchor="middle" class="slot-title">SLOT 0 · CURRENT</text>
|
|
||||||
<text x="316" y="374" text-anchor="middle" class="slot-text">full TP-local N · compute</text>
|
|
||||||
<rect x="397" y="340" width="142" height="44" rx="6" class="slot-next"/>
|
|
||||||
<text x="468" y="357" text-anchor="middle" class="slot-next-title">SLOT 1 · PREFETCH</text>
|
|
||||||
<text x="468" y="374" text-anchor="middle" class="slot-next-text">full TP-local N+1 after AG</text>
|
|
||||||
|
|
||||||
<rect x="82" y="410" width="473" height="64" rx="8" class="gpu"/>
|
|
||||||
<text x="98" y="429" class="eyebrow">TP1 · GPU 1</text>
|
|
||||||
<text x="98" y="454" class="box-text">DLO backend · local shard H2D</text>
|
|
||||||
<rect x="250" y="418" width="132" height="44" rx="6" class="slot-current"/>
|
|
||||||
<text x="316" y="435" text-anchor="middle" class="slot-title">SLOT 0 · CURRENT</text>
|
|
||||||
<text x="316" y="452" text-anchor="middle" class="slot-text">full TP-local N · compute</text>
|
|
||||||
<rect x="397" y="418" width="142" height="44" rx="6" class="slot-next"/>
|
|
||||||
<text x="468" y="435" text-anchor="middle" class="slot-next-title">SLOT 1 · PREFETCH</text>
|
|
||||||
<text x="468" y="452" text-anchor="middle" class="slot-next-text">full TP-local N+1 after AG</text>
|
|
||||||
|
|
||||||
<!-- DP replica 1. -->
|
|
||||||
<rect x="700" y="296" width="515" height="186" rx="10" class="replica"/>
|
|
||||||
<text x="717" y="318" class="box-title">DP replica 1 · request 1</text>
|
|
||||||
<text x="1198" y="318" text-anchor="end" class="section-sub">ordinary TP group</text>
|
|
||||||
|
|
||||||
<rect x="725" y="332" width="473" height="64" rx="8" class="gpu"/>
|
|
||||||
<text x="741" y="351" class="eyebrow">TP0 · GPU 2</text>
|
|
||||||
<text x="741" y="376" class="box-text">DLO backend · local shard H2D</text>
|
|
||||||
<rect x="893" y="340" width="132" height="44" rx="6" class="slot-current"/>
|
|
||||||
<text x="959" y="357" text-anchor="middle" class="slot-title">SLOT 0 · CURRENT</text>
|
|
||||||
<text x="959" y="374" text-anchor="middle" class="slot-text">full TP-local N · compute</text>
|
|
||||||
<rect x="1040" y="340" width="142" height="44" rx="6" class="slot-next"/>
|
|
||||||
<text x="1111" y="357" text-anchor="middle" class="slot-next-title">SLOT 1 · PREFETCH</text>
|
|
||||||
<text x="1111" y="374" text-anchor="middle" class="slot-next-text">full TP-local N+1 after AG</text>
|
|
||||||
|
|
||||||
<rect x="725" y="410" width="473" height="64" rx="8" class="gpu"/>
|
|
||||||
<text x="741" y="429" class="eyebrow">TP1 · GPU 3</text>
|
|
||||||
<text x="741" y="454" class="box-text">DLO backend · local shard H2D</text>
|
|
||||||
<rect x="893" y="418" width="132" height="44" rx="6" class="slot-current"/>
|
|
||||||
<text x="959" y="435" text-anchor="middle" class="slot-title">SLOT 0 · CURRENT</text>
|
|
||||||
<text x="959" y="452" text-anchor="middle" class="slot-text">full TP-local N · compute</text>
|
|
||||||
<rect x="1040" y="418" width="142" height="44" rx="6" class="slot-next"/>
|
|
||||||
<text x="1111" y="435" text-anchor="middle" class="slot-next-title">SLOT 1 · PREFETCH</text>
|
|
||||||
<text x="1111" y="452" text-anchor="middle" class="slot-next-text">full TP-local N+1 after AG</text>
|
|
||||||
|
|
||||||
<!-- DLO collectives connect matching TP coordinates across DP replicas. -->
|
|
||||||
<text x="640" y="350" text-anchor="middle" class="collective-label">DLO AG · TP0 GROUP</text>
|
|
||||||
<path d="M 555 364 L 725 364" class="collective-path"/>
|
|
||||||
<text x="640" y="428" text-anchor="middle" class="collective-label">DLO AG · TP1 GROUP</text>
|
|
||||||
<path d="M 555 442 L 725 442" class="collective-path"/>
|
|
||||||
|
|
||||||
<!-- All participants must reach the same collective in the same order. -->
|
|
||||||
<rect x="365" y="516" width="550" height="48" rx="8" class="lockstep"/>
|
|
||||||
<circle cx="395" cy="540" r="12" fill="#7c3aed"/>
|
|
||||||
<text x="395" y="545" text-anchor="middle" font-family="system-ui, sans-serif" font-size="15" font-weight="700" fill="#ffffff">⇄</text>
|
|
||||||
<text x="420" y="536" class="lock-title">DLO AllGather requires lockstep block order</text>
|
|
||||||
<text x="420" y="553" class="lock-text">every participant enters each block collective in the same sequence</text>
|
|
||||||
|
|
||||||
<!-- Each replica copies two process-owned host shards before reconstruction. -->
|
|
||||||
<path d="M 455 650 C 412 625, 340 610, 340 575 L 340 540 C 340 516, 318 500, 300 482" class="h2d-path"/>
|
|
||||||
<text x="276" y="618" text-anchor="middle" class="h2d-label">local shard H2D ×2</text>
|
|
||||||
<path d="M 825 650 C 868 625, 940 610, 940 575 L 940 540 C 940 516, 962 500, 980 482" class="h2d-path"/>
|
|
||||||
<text x="1004" y="618" text-anchor="middle" class="h2d-label">local shard H2D ×2</text>
|
|
||||||
|
|
||||||
<!-- Persistent host shards are private to workers, not mmap-shared. -->
|
|
||||||
<rect x="58" y="650" width="242" height="140" rx="10" class="scope"/>
|
|
||||||
<text x="78" y="676" class="eyebrow">GROUP TOPOLOGY</text>
|
|
||||||
<text x="78" y="702" class="box-title">DP2 × TP2</text>
|
|
||||||
<text x="78" y="725" class="box-text">DLO groups: same TP coordinate</text>
|
|
||||||
<text x="78" y="746" class="box-text">across DP replicas</text>
|
|
||||||
<text x="78" y="767" class="box-text">TP stays the model-parallel group.</text>
|
|
||||||
|
|
||||||
<rect x="330" y="650" width="620" height="140" rx="10" class="host"/>
|
|
||||||
<text x="350" y="676" class="eyebrow">PROCESS-OWNED PINNED HOST SHARDS · NOT HOST WEIGHT CACHE MMAP</text>
|
|
||||||
<rect x="370" y="692" width="260" height="60" rx="8" class="shard"/>
|
|
||||||
<text x="500" y="716" text-anchor="middle" class="shard-title">DP replica 0 · private</text>
|
|
||||||
<text x="500" y="738" text-anchor="middle" class="shard-text">TP0 shard 0/2 · TP1 shard 0/2</text>
|
|
||||||
<rect x="650" y="692" width="260" height="60" rx="8" class="shard"/>
|
|
||||||
<text x="780" y="716" text-anchor="middle" class="shard-title">DP replica 1 · private</text>
|
|
||||||
<text x="780" y="738" text-anchor="middle" class="shard-text">TP0 shard 1/2 · TP1 shard 1/2</text>
|
|
||||||
<text x="640" y="773" text-anchor="middle" class="box-text">Across each DLO group, shards total approximately one rank-local runtime block copy</text>
|
|
||||||
|
|
||||||
<rect x="980" y="650" width="242" height="140" rx="10" class="scope"/>
|
|
||||||
<text x="1000" y="676" class="eyebrow">HOT PATH · PER WORKER</text>
|
|
||||||
<text x="1000" y="702" class="box-title">Prefetch next block</text>
|
|
||||||
<text x="1000" y="725" class="box-text">local pinned shard → GPU</text>
|
|
||||||
<text x="1000" y="746" class="box-text">DLO AllGather → full block</text>
|
|
||||||
<text x="1000" y="767" class="box-text">rotate slots; overlap compute</text>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,202 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="850" viewBox="0 0 1280 850" role="img" aria-labelledby="title desc">
|
|
||||||
<title id="title">No-AllGather DLO shared host weights and independent runtime orchestration</title>
|
|
||||||
<desc id="desc">An external orchestrator routes requests to two independently scheduled TP2 diffusion engines on one node. Each engine contains two rank-local workers and devices, with private loaders, mmap views, DLO runtime hooks, streams, events, and rotating GPU slots. Equivalent TP0 workers across engines map one immutable TP0 entry in the host weight cache, while equivalent TP1 workers map a separate TP1 entry, without a cross-engine DLO AllGather.</desc>
|
|
||||||
<defs>
|
|
||||||
<marker id="request-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
|
||||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#7c3aed"/>
|
|
||||||
</marker>
|
|
||||||
<marker id="mapping-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
|
||||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#d97706"/>
|
|
||||||
</marker>
|
|
||||||
<marker id="runtime-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto">
|
|
||||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#2563eb"/>
|
|
||||||
</marker>
|
|
||||||
<style>
|
|
||||||
.canvas { fill: #ffffff; stroke: #e2e8f0; }
|
|
||||||
.node { fill: #f8fafc; stroke: #cbd5e1; }
|
|
||||||
.engine { fill: #ffffff; stroke: #cbd5e1; }
|
|
||||||
.router { fill: #f5f3ff; stroke: #c4b5fd; }
|
|
||||||
.scope { fill: #f8fafc; stroke: #cbd5e1; }
|
|
||||||
.cache { fill: #fff7ed; stroke: #fdba74; }
|
|
||||||
.payload { fill: #d97706; }
|
|
||||||
.loader { fill: #f1f5f9; stroke: #cbd5e1; }
|
|
||||||
.mapping { fill: #fffbeb; stroke: #f59e0b; }
|
|
||||||
.hook { fill: #eff6ff; stroke: #60a5fa; }
|
|
||||||
.gpu { fill: #f0fdf4; stroke: #86efac; }
|
|
||||||
.device { fill: #ffffff; stroke: #86efac; }
|
|
||||||
.slot-current { fill: #16a34a; }
|
|
||||||
.slot-next { fill: #bbf7d0; stroke: #22c55e; }
|
|
||||||
.no-collective { fill: #fef2f2; stroke: #fca5a5; }
|
|
||||||
.title { font: 700 25px system-ui, -apple-system, Segoe UI, sans-serif; fill: #0f172a; }
|
|
||||||
.subtitle { font: 500 13px system-ui, -apple-system, Segoe UI, sans-serif; fill: #64748b; }
|
|
||||||
.section { font: 700 15px system-ui, -apple-system, Segoe UI, sans-serif; fill: #1e293b; }
|
|
||||||
.section-sub { font: 500 11.5px system-ui, -apple-system, Segoe UI, sans-serif; fill: #64748b; }
|
|
||||||
.eyebrow { font: 700 10px system-ui, -apple-system, Segoe UI, sans-serif; letter-spacing: .8px; fill: #64748b; }
|
|
||||||
.box-title { font: 700 12px system-ui, -apple-system, Segoe UI, sans-serif; fill: #1e293b; }
|
|
||||||
.box-text { font: 500 10.5px system-ui, -apple-system, Segoe UI, sans-serif; fill: #475569; }
|
|
||||||
.payload-title { font: 700 14px system-ui, -apple-system, Segoe UI, sans-serif; fill: #ffffff; }
|
|
||||||
.payload-text { font: 500 11px system-ui, -apple-system, Segoe UI, sans-serif; fill: #ffffff; }
|
|
||||||
.device-title { font: 700 9px system-ui, -apple-system, Segoe UI, sans-serif; fill: #166534; }
|
|
||||||
.slot-text { font: 700 8px system-ui, -apple-system, Segoe UI, sans-serif; fill: #ffffff; }
|
|
||||||
.slot-next-text { font: 700 8px system-ui, -apple-system, Segoe UI, sans-serif; fill: #166534; }
|
|
||||||
.arrow-label { font: 600 10.5px system-ui, -apple-system, Segoe UI, sans-serif; fill: #475569; }
|
|
||||||
.request-label { font: 600 10.5px system-ui, -apple-system, Segoe UI, sans-serif; fill: #6d28d9; }
|
|
||||||
.mapping-label { font: 600 10.5px system-ui, -apple-system, Segoe UI, sans-serif; fill: #b45309; }
|
|
||||||
.no-title { font: 700 12px system-ui, -apple-system, Segoe UI, sans-serif; fill: #b91c1c; }
|
|
||||||
.no-text { font: 500 10.5px system-ui, -apple-system, Segoe UI, sans-serif; fill: #7f1d1d; }
|
|
||||||
.request-path { fill: none; stroke: #7c3aed; stroke-width: 2; marker-end: url(#request-arrow); }
|
|
||||||
.mapping-path { fill: none; stroke: #d97706; stroke-width: 2; marker-end: url(#mapping-arrow); }
|
|
||||||
.runtime-path { fill: none; stroke: #2563eb; stroke-width: 2; marker-end: url(#runtime-arrow); }
|
|
||||||
.startup-path { fill: none; stroke: #94a3b8; stroke-width: 1.5; stroke-dasharray: 4 3; marker-end: url(#runtime-arrow); }
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<rect x="12" y="12" width="1256" height="826" rx="14" class="canvas"/>
|
|
||||||
|
|
||||||
<text id="title-text" x="40" y="50" class="title">No-AllGather DLO: TP-coordinate host sharing, independent engines</text>
|
|
||||||
<text x="40" y="74" class="subtitle">TP2 shown: each engine owns two devices; equivalent workers share one host payload per TP coordinate</text>
|
|
||||||
|
|
||||||
<!-- The control plane remains outside DLO. -->
|
|
||||||
<rect x="390" y="94" width="500" height="68" rx="10" class="router"/>
|
|
||||||
<text x="640" y="121" text-anchor="middle" class="section">External router / orchestrator</text>
|
|
||||||
<text x="640" y="144" text-anchor="middle" class="section-sub">independent request placement · outside vLLM-Omni DLO</text>
|
|
||||||
|
|
||||||
<!-- One node contains independent engine runtimes over one shared physical backing. -->
|
|
||||||
<rect x="30" y="184" width="1220" height="634" rx="14" class="node"/>
|
|
||||||
<text x="52" y="211" class="eyebrow">ONE NODE · PROCESS-LOCAL ENGINES OVER SHARED PHYSICAL HOST MEMORY</text>
|
|
||||||
|
|
||||||
<!-- Independent request routing from the external orchestrator. -->
|
|
||||||
<path d="M 500 162 C 456 181, 400 204, 332 232" class="request-path"/>
|
|
||||||
<text x="416" y="194" class="request-label">request A</text>
|
|
||||||
<path d="M 780 162 C 824 181, 880 204, 948 232" class="request-path"/>
|
|
||||||
<text x="864" y="194" class="request-label">request B</text>
|
|
||||||
|
|
||||||
<!-- Independent engine A. -->
|
|
||||||
<rect x="45" y="232" width="575" height="250" rx="12" class="engine"/>
|
|
||||||
<text x="68" y="261" class="section">Independent engine A</text>
|
|
||||||
<text x="68" y="281" class="section-sub">TP2 · two rank-local workers · own request schedule and DLO state</text>
|
|
||||||
|
|
||||||
<rect x="68" y="310" width="98" height="130" rx="8" class="loader"/>
|
|
||||||
<text x="117" y="335" text-anchor="middle" class="eyebrow">STARTUP ×2</text>
|
|
||||||
<text x="117" y="359" text-anchor="middle" class="box-title">rank-local</text>
|
|
||||||
<text x="117" y="377" text-anchor="middle" class="box-title">loaders</text>
|
|
||||||
<text x="117" y="402" text-anchor="middle" class="box-text">final transforms</text>
|
|
||||||
<text x="117" y="421" text-anchor="middle" class="box-text">publish / join</text>
|
|
||||||
|
|
||||||
<rect x="180" y="310" width="112" height="130" rx="8" class="mapping"/>
|
|
||||||
<text x="236" y="335" text-anchor="middle" class="eyebrow">HOST VIEWS ×2</text>
|
|
||||||
<text x="236" y="359" text-anchor="middle" class="box-title">TP0 mmap</text>
|
|
||||||
<text x="236" y="377" text-anchor="middle" class="box-title">TP1 mmap</text>
|
|
||||||
<text x="236" y="402" text-anchor="middle" class="box-text">process-local</text>
|
|
||||||
<text x="236" y="421" text-anchor="middle" class="box-text">optional registration</text>
|
|
||||||
|
|
||||||
<rect x="306" y="310" width="120" height="130" rx="8" class="hook"/>
|
|
||||||
<text x="366" y="335" text-anchor="middle" class="eyebrow">RUNTIME ×2</text>
|
|
||||||
<text x="366" y="359" text-anchor="middle" class="box-title">DLO backends</text>
|
|
||||||
<text x="366" y="377" text-anchor="middle" class="box-title">+ block hooks</text>
|
|
||||||
<text x="366" y="402" text-anchor="middle" class="box-text">private streams</text>
|
|
||||||
<text x="366" y="421" text-anchor="middle" class="box-text">events + state</text>
|
|
||||||
|
|
||||||
<rect x="440" y="310" width="157" height="130" rx="8" class="gpu"/>
|
|
||||||
<text x="518" y="330" text-anchor="middle" class="eyebrow">TWO DEVICES</text>
|
|
||||||
<rect x="450" y="340" width="137" height="42" rx="6" class="device"/>
|
|
||||||
<text x="518" y="353" text-anchor="middle" class="device-title">TP0 · GPU A0</text>
|
|
||||||
<rect x="457" y="359" width="58" height="16" rx="4" class="slot-current"/>
|
|
||||||
<text x="486" y="370" text-anchor="middle" class="slot-text">SLOT 0 NOW</text>
|
|
||||||
<rect x="521" y="359" width="58" height="16" rx="4" class="slot-next"/>
|
|
||||||
<text x="550" y="370" text-anchor="middle" class="slot-next-text">SLOT 1 NEXT</text>
|
|
||||||
<rect x="450" y="389" width="137" height="42" rx="6" class="device"/>
|
|
||||||
<text x="518" y="402" text-anchor="middle" class="device-title">TP1 · GPU A1</text>
|
|
||||||
<rect x="457" y="408" width="58" height="16" rx="4" class="slot-current"/>
|
|
||||||
<text x="486" y="419" text-anchor="middle" class="slot-text">SLOT 0 NOW</text>
|
|
||||||
<rect x="521" y="408" width="58" height="16" rx="4" class="slot-next"/>
|
|
||||||
<text x="550" y="419" text-anchor="middle" class="slot-next-text">SLOT 1 NEXT</text>
|
|
||||||
|
|
||||||
<path d="M 166 375 L 180 375" class="startup-path"/>
|
|
||||||
<path d="M 292 375 L 306 375" class="runtime-path"/>
|
|
||||||
<path d="M 426 375 L 440 375" class="runtime-path"/>
|
|
||||||
|
|
||||||
<!-- Independent engine B. -->
|
|
||||||
<rect x="660" y="232" width="575" height="250" rx="12" class="engine"/>
|
|
||||||
<text x="683" y="261" class="section">Independent engine B</text>
|
|
||||||
<text x="683" y="281" class="section-sub">TP2 · two rank-local workers · own request schedule and DLO state</text>
|
|
||||||
|
|
||||||
<rect x="683" y="310" width="98" height="130" rx="8" class="loader"/>
|
|
||||||
<text x="732" y="335" text-anchor="middle" class="eyebrow">STARTUP ×2</text>
|
|
||||||
<text x="732" y="359" text-anchor="middle" class="box-title">rank-local</text>
|
|
||||||
<text x="732" y="377" text-anchor="middle" class="box-title">loaders</text>
|
|
||||||
<text x="732" y="402" text-anchor="middle" class="box-text">final transforms</text>
|
|
||||||
<text x="732" y="421" text-anchor="middle" class="box-text">publish / join</text>
|
|
||||||
|
|
||||||
<rect x="795" y="310" width="112" height="130" rx="8" class="mapping"/>
|
|
||||||
<text x="851" y="335" text-anchor="middle" class="eyebrow">HOST VIEWS ×2</text>
|
|
||||||
<text x="851" y="359" text-anchor="middle" class="box-title">TP0 mmap</text>
|
|
||||||
<text x="851" y="377" text-anchor="middle" class="box-title">TP1 mmap</text>
|
|
||||||
<text x="851" y="402" text-anchor="middle" class="box-text">process-local</text>
|
|
||||||
<text x="851" y="421" text-anchor="middle" class="box-text">optional registration</text>
|
|
||||||
|
|
||||||
<rect x="921" y="310" width="120" height="130" rx="8" class="hook"/>
|
|
||||||
<text x="981" y="335" text-anchor="middle" class="eyebrow">RUNTIME ×2</text>
|
|
||||||
<text x="981" y="359" text-anchor="middle" class="box-title">DLO backends</text>
|
|
||||||
<text x="981" y="377" text-anchor="middle" class="box-title">+ block hooks</text>
|
|
||||||
<text x="981" y="402" text-anchor="middle" class="box-text">private streams</text>
|
|
||||||
<text x="981" y="421" text-anchor="middle" class="box-text">events + state</text>
|
|
||||||
|
|
||||||
<rect x="1055" y="310" width="157" height="130" rx="8" class="gpu"/>
|
|
||||||
<text x="1133" y="330" text-anchor="middle" class="eyebrow">TWO DEVICES</text>
|
|
||||||
<rect x="1065" y="340" width="137" height="42" rx="6" class="device"/>
|
|
||||||
<text x="1133" y="353" text-anchor="middle" class="device-title">TP0 · GPU B0</text>
|
|
||||||
<rect x="1072" y="359" width="58" height="16" rx="4" class="slot-current"/>
|
|
||||||
<text x="1101" y="370" text-anchor="middle" class="slot-text">SLOT 0 NOW</text>
|
|
||||||
<rect x="1136" y="359" width="58" height="16" rx="4" class="slot-next"/>
|
|
||||||
<text x="1165" y="370" text-anchor="middle" class="slot-next-text">SLOT 1 NEXT</text>
|
|
||||||
<rect x="1065" y="389" width="137" height="42" rx="6" class="device"/>
|
|
||||||
<text x="1133" y="402" text-anchor="middle" class="device-title">TP1 · GPU B1</text>
|
|
||||||
<rect x="1072" y="408" width="58" height="16" rx="4" class="slot-current"/>
|
|
||||||
<text x="1101" y="419" text-anchor="middle" class="slot-text">SLOT 0 NOW</text>
|
|
||||||
<rect x="1136" y="408" width="58" height="16" rx="4" class="slot-next"/>
|
|
||||||
<text x="1165" y="419" text-anchor="middle" class="slot-next-text">SLOT 1 NEXT</text>
|
|
||||||
|
|
||||||
<path d="M 781 375 L 795 375" class="startup-path"/>
|
|
||||||
<path d="M 907 375 L 921 375" class="runtime-path"/>
|
|
||||||
<path d="M 1041 375 L 1055 375" class="runtime-path"/>
|
|
||||||
|
|
||||||
<!-- The runtimes are deliberately not coupled by a DLO collective. -->
|
|
||||||
<rect x="365" y="516" width="550" height="44" rx="8" class="no-collective"/>
|
|
||||||
<circle cx="395" cy="538" r="12" fill="#dc2626"/>
|
|
||||||
<path d="M 389 532 L 401 544 M 401 532 L 389 544" stroke="#ffffff" stroke-width="2.2"/>
|
|
||||||
<text x="420" y="534" class="no-title">No cross-engine DLO AllGather</text>
|
|
||||||
<text x="420" y="551" class="no-text">shared host pages do not couple request order, progress, or failure domains</text>
|
|
||||||
<text x="640" y="592" text-anchor="middle" class="section-sub">Ordinary TP / SP model collectives, when configured, remain inside each engine</text>
|
|
||||||
|
|
||||||
<!-- Each engine maps both TP-coordinate payloads from the shared host cache. -->
|
|
||||||
<path d="M 455 650 C 412 625, 340 610, 340 570 L 340 525 C 340 478, 278 454, 236 440" class="mapping-path"/>
|
|
||||||
<text x="286" y="617" text-anchor="middle" class="mapping-label">TP0 + TP1 mappings</text>
|
|
||||||
<path d="M 825 650 C 868 625, 940 610, 940 570 L 940 525 C 940 478, 893 454, 851 440" class="mapping-path"/>
|
|
||||||
<text x="994" y="617" text-anchor="middle" class="mapping-label">TP0 + TP1 mappings</text>
|
|
||||||
|
|
||||||
<!-- One node-local physical backing, visually beneath its consumers. -->
|
|
||||||
<rect x="58" y="650" width="242" height="140" rx="10" class="scope"/>
|
|
||||||
<text x="78" y="676" class="eyebrow">SHARING SCOPE</text>
|
|
||||||
<text x="78" y="702" class="box-title">TP2: two coordinate entries</text>
|
|
||||||
<text x="78" y="725" class="box-text">one physical copy per coordinate</text>
|
|
||||||
<text x="78" y="746" class="box-text">A/TP0 + B/TP0 → TP0 entry</text>
|
|
||||||
<text x="78" y="767" class="box-text">A/TP1 + B/TP1 → TP1 entry</text>
|
|
||||||
|
|
||||||
<rect x="330" y="650" width="620" height="140" rx="10" class="cache"/>
|
|
||||||
<text x="350" y="676" class="eyebrow">NODE-LOCAL IMMUTABLE HOST WEIGHT CACHE · ONE COPY PER TP COORDINATE</text>
|
|
||||||
<rect x="370" y="692" width="260" height="60" rx="8" class="payload"/>
|
|
||||||
<text x="500" y="717" text-anchor="middle" class="payload-title">TP0 physical payload</text>
|
|
||||||
<text x="500" y="738" text-anchor="middle" class="payload-text">shared by A/TP0 + B/TP0</text>
|
|
||||||
<rect x="650" y="692" width="260" height="60" rx="8" class="payload"/>
|
|
||||||
<text x="780" y="717" text-anchor="middle" class="payload-title">TP1 physical payload</text>
|
|
||||||
<text x="780" y="738" text-anchor="middle" class="payload-text">shared by A/TP1 + B/TP1</text>
|
|
||||||
<text x="640" y="773" text-anchor="middle" class="box-text">Identity includes final layout + TP coordinate; engine and DP replica are excluded</text>
|
|
||||||
|
|
||||||
<rect x="980" y="650" width="242" height="140" rx="10" class="scope"/>
|
|
||||||
<text x="1000" y="676" class="eyebrow">WHAT STAYS PRIVATE</text>
|
|
||||||
<text x="1000" y="702" class="box-title">Per worker / process</text>
|
|
||||||
<text x="1000" y="725" class="box-text">mmap object and validation state</text>
|
|
||||||
<text x="1000" y="746" class="box-text">optional host registration</text>
|
|
||||||
<text x="1000" y="767" class="box-text">hooks, streams, events, GPU slots</text>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 14 KiB |
+4
-12
@@ -22,7 +22,6 @@ implementation contract; it is not, by itself, a general support claim.
|
|||||||
- [Async Diffusion Output](feature/async_diffusion_output.md)
|
- [Async Diffusion Output](feature/async_diffusion_output.md)
|
||||||
- [Async Omni Output Materialization](feature/omni_async_output_materialization.md)
|
- [Async Omni Output Materialization](feature/omni_async_output_materialization.md)
|
||||||
- [Automatic Prefix Caching in Omni Models](feature/prefix_caching.md)
|
- [Automatic Prefix Caching in Omni Models](feature/prefix_caching.md)
|
||||||
- [Realtime AR-Diffusion Sessions](feature/realtime_ar_diffusion.md)
|
|
||||||
|
|
||||||
### Communication
|
### Communication
|
||||||
|
|
||||||
@@ -49,27 +48,20 @@ implementation contract; it is not, by itself, a general support claim.
|
|||||||
- [Pipeline Parallel](feature/pipeline_parallel.md)
|
- [Pipeline Parallel](feature/pipeline_parallel.md)
|
||||||
- [Sequence Parallel](feature/sequence_parallel.md)
|
- [Sequence Parallel](feature/sequence_parallel.md)
|
||||||
- [Tensor Parallel](feature/tensor_parallel.md)
|
- [Tensor Parallel](feature/tensor_parallel.md)
|
||||||
- [VAE Patch Parallelism](feature/vae_parallel.md)
|
- [VAE Parallel](feature/vae_parallel.md)
|
||||||
|
|
||||||
#### Attention optimization
|
#### Attention optimization
|
||||||
|
|
||||||
The [Diffusion Attention Backends](../user_guide/diffusion/attention_backends.md)
|
The [Diffusion Attention Backends](../user_guide/diffusion/attention_backends.md)
|
||||||
guides list selectable backends, platform defaults, installation, and tuning.
|
guide lists all selectable backends and their platform defaults. Skip-Softmax
|
||||||
The design contracts separate selection mechanics from backend algorithms:
|
is the backend-related optimization with a standalone feature-design contract:
|
||||||
|
|
||||||
- [Attention Backend Selection](feature/attention_backend_selection.md)
|
|
||||||
- [Skip-Softmax](feature/skip_softmax.md)
|
- [Skip-Softmax](feature/skip_softmax.md)
|
||||||
|
|
||||||
#### CPU offloading
|
|
||||||
|
|
||||||
- [Overview and Shared Contracts](feature/offloader/README.md)
|
|
||||||
- [Model-Level Offload](feature/offloader/cpu_offload.md)
|
|
||||||
- [Layerwise Offload](feature/offloader/layerwise_offload.md)
|
|
||||||
- [Distributed Layerwise Offload](feature/offloader/distributed_layerwise_offload.md)
|
|
||||||
|
|
||||||
- [Cache-DiT](feature/cache_dit.md)
|
- [Cache-DiT](feature/cache_dit.md)
|
||||||
- [TeaCache](feature/teacache.md)
|
- [TeaCache](feature/teacache.md)
|
||||||
- [Diffusion Continuous Batching](feature/diffusion_continuous_batching.md)
|
- [Diffusion Continuous Batching](feature/diffusion_continuous_batching.md)
|
||||||
|
- [Distributed Layerwise Offload](feature/distributed_layerwise_offload.md)
|
||||||
|
|
||||||
## Infrastructure and Performance
|
## Infrastructure and Performance
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ validation_paths:
|
|||||||
- tests/config/**
|
- tests/config/**
|
||||||
upstream_refs:
|
upstream_refs:
|
||||||
- vllm.config
|
- vllm.config
|
||||||
last_reviewed: 2026-08-11
|
last_reviewed: 2026-08-07
|
||||||
last_verified_commit: e356708da4cd39992405686dacfc32b18fcccc7f
|
last_verified_commit: 3d7fc3b9ba3cac88d579d4dc35b78b0b641675fc
|
||||||
---
|
---
|
||||||
|
|
||||||
# vLLM-Omni configuration
|
# vLLM-Omni configuration
|
||||||
@@ -39,66 +39,11 @@ The PR review proposal to capture environment-derived values in configuration
|
|||||||
objects at initialization is an open design question, not a current
|
objects at initialization is an open design question, not a current
|
||||||
invariant.
|
invariant.
|
||||||
|
|
||||||
## Current structured-stage reuse behavior
|
|
||||||
|
|
||||||
The current RFC #4021 implementation uses upstream vLLM config classes as the
|
|
||||||
schema source for AR and generation stages:
|
|
||||||
|
|
||||||
- `OmniStageLoadConfig` inherits `vllm.config.LoadConfig`.
|
|
||||||
- `OmniStageCacheConfig` inherits `vllm.config.CacheConfig`.
|
|
||||||
- `OmniStageSchedulerConfig` inherits `vllm.config.SchedulerConfig`.
|
|
||||||
- `OmniStageParallelConfig` inherits `vllm.config.ParallelConfig`.
|
|
||||||
|
|
||||||
This inheritance preserves the existing load, cache, scheduler, and parallel
|
|
||||||
concern boundaries while reducing duplicate downstream declarations. It does
|
|
||||||
not, by itself, make every inherited field an effective vLLM-Omni runtime
|
|
||||||
option. Construction and runtime consumption are separate surfaces:
|
|
||||||
|
|
||||||
| Surface | Current behavior |
|
|
||||||
| --- | --- |
|
|
||||||
| Structured schema | Upstream dataclass fields are inherited. Their defaults, default factories, and applicable Pydantic validation can participate when the structured object is constructed. |
|
|
||||||
| Omni input ownership | Pipeline/deploy/CLI construction continues to accept only fields with an existing structured owner. Direct construction of an inherited sub-config can expose a wider upstream schema. |
|
|
||||||
| Engine projection | For AR and generation stages, reusable fields are discovered by intersecting each upstream config dataclass with upstream `EngineArgs`. Only constructor-explicit values are emitted; inherited defaults remain deferred to terminal materialization. |
|
|
||||||
| Terminal materialization | The engine-owning process constructs the final upstream `VllmConfig` and performs model-, platform-, rank-, port-, and backend-dependent initialization. |
|
|
||||||
|
|
||||||
The generated projection maps include known upstream naming differences such
|
|
||||||
as `cache_dtype` to `kv_cache_dtype`, `policy` to `scheduling_policy`, and
|
|
||||||
`data_parallel_master_ip` to `data_parallel_address`. A field added to both an
|
|
||||||
upstream concern config and `EngineArgs` therefore enters the AR/generation
|
|
||||||
projection without requiring a second downstream allowlist. Explicit inherited
|
|
||||||
fields that have no projection raise an error rather than being silently
|
|
||||||
dropped.
|
|
||||||
|
|
||||||
Ownership exclusions remain deliberate. Stage topology owns `scheduler_cls`,
|
|
||||||
cache owns `disable_hybrid_kv_cache_manager`, and Omni runtime owns
|
|
||||||
`distributed_executor_backend` and `worker_cls`; vLLM's private API-process
|
|
||||||
fields are terminal internals. These exclusions prevent one input from being
|
|
||||||
constructed or projected through two config concerns. Effective-engine-argument
|
|
||||||
tests cover both the dynamic mapping and this exclusion boundary.
|
|
||||||
|
|
||||||
`CompilationConfig` and `ProfilerConfig` are direct structured-stage fields:
|
|
||||||
`stage.compilation_config` and `stage.profiler_config`. They accept mapping/YAML
|
|
||||||
inputs and complete upstream value objects. Mapping inputs are validated and
|
|
||||||
materialized as their concrete upstream types when the structured config is
|
|
||||||
constructed, so downstream consumers see one resolved representation.
|
|
||||||
Prebuilt upstream objects retain their type across the structured projection
|
|
||||||
boundary.
|
|
||||||
|
|
||||||
Quantization remains on its existing Omni-owned transport contract. This
|
|
||||||
reuse step does not adopt upstream `QuantizationConfigArgs`, because the
|
|
||||||
current engine-specific quantization split needs to be resolved separately.
|
|
||||||
|
|
||||||
Diffusion keeps its existing engine-field ownership and projection surface.
|
|
||||||
Shared Python inheritance must not be treated as evidence that an LLM-only
|
|
||||||
parallel input is effective for a diffusion stage.
|
|
||||||
|
|
||||||
## Safe-change guide while deferred
|
## Safe-change guide while deferred
|
||||||
|
|
||||||
Changes should trace every affected producer to its runtime consumer and test
|
Changes should trace every affected producer to its runtime consumer and test
|
||||||
the structured, legacy, CLI, and deployment paths that are actually supported.
|
the structured, legacy, CLI, and deployment paths that are actually supported.
|
||||||
For inherited vLLM fields, tests should separately cover constructor behavior
|
Do not infer a stable contract from this placeholder page.
|
||||||
and effective `EngineArgs` projection. Do not infer a stable contract from
|
|
||||||
this placeholder page.
|
|
||||||
|
|
||||||
## Promotion gate
|
## Promotion gate
|
||||||
|
|
||||||
|
|||||||
+5
-11
@@ -20,19 +20,13 @@ an implementation contract has a user-facing workflow.
|
|||||||
| Choose serial, batched, step-wise, or streaming diffusion execution | [Execution Modes and Streaming](../user_guide/diffusion/execution_modes.md) | [Diffusion Continuous Batching](../design/feature/diffusion_continuous_batching.md), [Async Diffusion Output](../design/feature/async_diffusion_output.md) |
|
| Choose serial, batched, step-wise, or streaming diffusion execution | [Execution Modes and Streaming](../user_guide/diffusion/execution_modes.md) | [Diffusion Continuous Batching](../design/feature/diffusion_continuous_batching.md), [Async Diffusion Output](../design/feature/async_diffusion_output.md) |
|
||||||
| Reclaim stage memory without restarting the server | [Sleep Mode](sleep_mode.md) | Runtime lifecycle behavior is documented in the user guide |
|
| Reclaim stage memory without restarting the server | [Sleep Mode](sleep_mode.md) | Runtime lifecycle behavior is documented in the user guide |
|
||||||
|
|
||||||
The [Prefill-Decode Disaggregation design reference](pd_disaggregation.md)
|
|
||||||
describes the experimental Qwen3-Omni runtime topology and its current
|
|
||||||
integration boundaries. It is not yet a supported launch recipe.
|
|
||||||
|
|
||||||
Some runtime designs are deliberately not promoted as standalone User Guide
|
Some runtime designs are deliberately not promoted as standalone User Guide
|
||||||
features yet:
|
features yet:
|
||||||
|
|
||||||
- Generic, OmniConnector-based
|
- [Disaggregated Inference](../design/feature/disaggregated_inference.md) and
|
||||||
[Disaggregated Inference](../design/feature/disaggregated_inference.md) is not
|
|
||||||
yet a standalone User Guide feature. Its
|
|
||||||
[OmniConnector implementations](../design/index.md#communication) describe
|
[OmniConnector implementations](../design/index.md#communication) describe
|
||||||
transport contracts, while practical connector configuration remains under
|
topology and transport contracts. Practical deployment configuration remains
|
||||||
[Pipeline and deploy configurations](../configuration/stage_configs.md).
|
under [Pipeline and deploy configurations](../configuration/stage_configs.md).
|
||||||
- [Async Chunk](../design/feature/async_chunk.md) and
|
- [Async Chunk](../design/feature/async_chunk.md) and
|
||||||
[Async Omni Output Materialization](../design/feature/omni_async_output_materialization.md)
|
[Async Omni Output Materialization](../design/feature/omni_async_output_materialization.md)
|
||||||
are model- and pipeline-dependent. Use the selected model's deploy
|
are model- and pipeline-dependent. Use the selected model's deploy
|
||||||
@@ -54,10 +48,10 @@ defines the shared configuration and backend extension points.
|
|||||||
| Goal | User guide | Related design contract |
|
| Goal | User guide | Related design contract |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Compare acceleration methods and supported combinations | [Overview](../user_guide/diffusion_features.md), [Feature Compatibility](../user_guide/feature_compatibility.md) | [Diffusion acceleration designs](../design/index.md#diffusion-acceleration) |
|
| Compare acceleration methods and supported combinations | [Overview](../user_guide/diffusion_features.md), [Feature Compatibility](../user_guide/feature_compatibility.md) | [Diffusion acceleration designs](../design/index.md#diffusion-acceleration) |
|
||||||
| Move weights between host and device memory | [CPU Offloading](../user_guide/diffusion/cpu_offload.md) | [CPU Offloading](../design/feature/offloader/README.md) |
|
| Move weights between host and device memory | [CPU Offloading](../user_guide/diffusion/cpu_offload.md) | [Distributed Layerwise Offload](../design/feature/distributed_layerwise_offload.md) |
|
||||||
| Reuse denoising computation | [Cache Acceleration](../user_guide/diffusion/cache_acceleration/cache_dit.md) | [Cache-DiT](../design/feature/cache_dit.md), [TeaCache](../design/feature/teacache.md) |
|
| Reuse denoising computation | [Cache Acceleration](../user_guide/diffusion/cache_acceleration/cache_dit.md) | [Cache-DiT](../design/feature/cache_dit.md), [TeaCache](../design/feature/teacache.md) |
|
||||||
| Distribute diffusion work across devices | [Parallelism](../user_guide/diffusion/parallelism/overview.md) | [Parallelism designs](../design/index.md#parallelism) |
|
| Distribute diffusion work across devices | [Parallelism](../user_guide/diffusion/parallelism/overview.md) | [Parallelism designs](../design/index.md#parallelism) |
|
||||||
| Select dense, sparse, or quantized attention paths | [Attention Backends](../user_guide/diffusion/attention_backends.md) | [Attention Backend Selection](../design/feature/attention_backend_selection.md) |
|
| Select dense, sparse, or quantized attention paths | [Attention Backends](../user_guide/diffusion/attention_backends.md) | [Skip-Softmax](../design/feature/skip_softmax.md) |
|
||||||
| Compile repeated diffusion regions | [Regional Compilation](../user_guide/diffusion/regional_compilation.md) | User-facing optimization guide |
|
| Compile repeated diffusion regions | [Regional Compilation](../user_guide/diffusion/regional_compilation.md) | User-facing optimization guide |
|
||||||
| Add generated video frames | [Frame Interpolation](../user_guide/diffusion/frame_interpolation.md) | User-facing extension guide |
|
| Add generated video frames | [Frame Interpolation](../user_guide/diffusion/frame_interpolation.md) | User-facing extension guide |
|
||||||
| Reduce diffusion model startup time | [Startup and Loading](../user_guide/diffusion/startup_and_loading.md) | User-facing loading guide |
|
| Reduce diffusion model startup time | [Startup and Loading](../user_guide/diffusion/startup_and_loading.md) | User-facing loading guide |
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ outputs = omni.generate(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Access custom trajectory data
|
# Access custom trajectory data
|
||||||
output = outputs[0]
|
output = outputs[0].request_output
|
||||||
print(f"Trajectory timesteps shape: {output.metrics['trajectory_timesteps'].shape}")
|
print(f"Trajectory timesteps shape: {output.metrics['trajectory_timesteps'].shape}")
|
||||||
print(f"Trajectory latents shape: {output.latents.shape}")
|
print(f"Trajectory latents shape: {output.latents.shape}")
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
# Prefill-Decode (PD) Disaggregation (experimental)
|
|
||||||
|
|
||||||
!!! warning "Experimental design reference"
|
|
||||||
PD disaggregation is only partially integrated on the current `main` branch.
|
|
||||||
The runtime contains PD detection and routing scaffolding, but there is no
|
|
||||||
supported deploy overlay or end-to-end validated launch recipe yet. Treat this
|
|
||||||
page as a description of the current design, not as production guidance.
|
|
||||||
|
|
||||||
Prefill-decode disaggregation splits the Qwen3-Omni Thinker into two logical
|
|
||||||
stages: a prefill worker that processes the prompt and produces KV cache, and a
|
|
||||||
decode worker that imports that cache and generates tokens. The Talker and
|
|
||||||
Code2Wav stages remain downstream; PD does not disaggregate the entire
|
|
||||||
Qwen3-Omni pipeline.
|
|
||||||
|
|
||||||
## Current design
|
|
||||||
|
|
||||||
### Runtime topology
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
client["Client request"] --> orchestrator["Omni Orchestrator"]
|
|
||||||
orchestrator -->|"original prompt<br/>max_tokens = 1"| prefill["Thinker prefill<br/>KV producer"]
|
|
||||||
prefill -->|"KV blocks"| connector[("vLLM KV connector")]
|
|
||||||
prefill -->|"KV routing metadata"| orchestrator
|
|
||||||
orchestrator -->|"original prompt<br/>remote KV parameters"| decode["Thinker decode<br/>KV consumer"]
|
|
||||||
connector -->|"remote KV load"| decode
|
|
||||||
decode -->|"generated text"| client
|
|
||||||
```
|
|
||||||
|
|
||||||
The orchestrator owns the request lifecycle, while the vLLM KV connector owns
|
|
||||||
the bulk KV transfer. The original prompt is submitted to both Thinker stages:
|
|
||||||
the decode worker still needs the request tokens and multimodal features to
|
|
||||||
construct its request, while the imported KV cache prevents it from repeating
|
|
||||||
the completed prefill computation.
|
|
||||||
|
|
||||||
For the full Qwen3-Omni pipeline, splitting the Thinker adds one logical stage:
|
|
||||||
|
|
||||||
| Stage | Role | Output or handoff |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 0 | Thinker prefill | Saves prompt KV and prompt-side multimodal output |
|
|
||||||
| 1 | Thinker decode | Loads remote KV, generates text, and produces decode-side conditioning |
|
|
||||||
| 2 | Talker | Converts combined Thinker conditioning into codec codes |
|
|
||||||
| 3 | Code2Wav | Converts codec codes into audio |
|
|
||||||
|
|
||||||
Text remains a final output of the decode stage, and audio remains a final
|
|
||||||
output of Code2Wav.
|
|
||||||
|
|
||||||
### Request lifecycle
|
|
||||||
|
|
||||||
1. At startup, PD detection looks for one stage marked `is_prefill_only` and
|
|
||||||
one downstream stage marked `is_decode_only`. The decode stage must list the
|
|
||||||
prefill stage as an input source.
|
|
||||||
2. The orchestrator clones the Thinker sampling parameters for prefill, forces
|
|
||||||
`max_tokens=1`, clears stop conditions, and sets producer-side
|
|
||||||
`kv_transfer_params`. This makes prefill finish by length after exporting KV.
|
|
||||||
3. When prefill finishes, the orchestrator captures its KV routing metadata and
|
|
||||||
Qwen multimodal output under the original request ID.
|
|
||||||
4. The orchestrator submits the original prompt to decode with consumer-side
|
|
||||||
`kv_transfer_params`. Those parameters identify the transfer, prefill engine,
|
|
||||||
bootstrap endpoint, and, when required by the connector, remote request.
|
|
||||||
5. The decode engine imports the KV cache and continues generation. The normal
|
|
||||||
Omni stage path then carries Thinker conditioning through Talker and
|
|
||||||
Code2Wav.
|
|
||||||
|
|
||||||
### Control and data paths
|
|
||||||
|
|
||||||
PD uses two different connector layers. They solve different handoffs and are
|
|
||||||
not interchangeable.
|
|
||||||
|
|
||||||
| Path | Payload | Owner |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| PD control path | Request ID, original prompt, `transfer_id`, bootstrap address, engine ID, and connector-specific request metadata | Omni Orchestrator |
|
|
||||||
| PD data path | Thinker attention KV blocks | vLLM KV connector |
|
|
||||||
| Downstream model path | Thinker hidden states and embeddings, Talker codec codes, and audio tensors | vLLM-Omni stage connectors and processors |
|
|
||||||
|
|
||||||
### Detection and validation
|
|
||||||
|
|
||||||
The current runtime accepts one logical prefill/decode pair per pipeline. Each
|
|
||||||
stage may use tensor parallelism, but both sides of the pair must satisfy these
|
|
||||||
invariants:
|
|
||||||
|
|
||||||
- both stages define `kv_transfer_config`
|
|
||||||
- prefill uses `kv_producer` or `kv_both`
|
|
||||||
- decode uses `kv_consumer` or `kv_both`
|
|
||||||
- both stages use the same KV connector
|
|
||||||
- `kv_buffer_device` and `kv_buffer_size` agree when set on both stages
|
|
||||||
- both stages use the same `tensor_parallel_size`
|
|
||||||
|
|
||||||
These checks happen before request processing so an inconsistent PD topology
|
|
||||||
fails during engine initialization.
|
|
||||||
|
|
||||||
## What is implemented today
|
|
||||||
|
|
||||||
| Area | Current state |
|
|
||||||
| --- | --- |
|
|
||||||
| Pair detection and topology validation | Implemented in the shared PD helper |
|
|
||||||
| Prefill sampling preparation | Implemented for sync and async entrypoints |
|
|
||||||
| Prefill-to-decode routing | Implemented in the current Orchestrator |
|
|
||||||
| Deploy-based configuration | Not yet migrated; the supported `stages:` deploy schema cannot currently expand Qwen3-Omni into the four-stage PD topology |
|
|
||||||
| Qwen3 Thinker-to-Talker handoff | Bridge state and embedding-merge helpers exist, but the merge is not wired into the live stage processor |
|
|
||||||
| Connector compatibility | Incomplete; the Orchestrator currently requires `remote_request_id`, while the pinned Mooncake connector completes prefill without returning that metadata |
|
|
||||||
| Validation coverage | PD entrypoint tests and the PD deploy overlay in online end-to-end tests are temporarily disabled during migration |
|
|
||||||
| Performance | No non-regression or throughput/latency result is established for the current path |
|
|
||||||
|
|
||||||
Because these pieces are still converging, the previous legacy `stage_args` YAML
|
|
||||||
and `vllm serve` command have been removed. The serve CLI now accepts the
|
|
||||||
deploy-based `stages:` schema, so retaining the legacy recipe would imply a
|
|
||||||
launch path that current `main` rejects.
|
|
||||||
|
|
||||||
## Configuration contract under development
|
|
||||||
|
|
||||||
The intended deploy integration starts from the
|
|
||||||
[default Qwen3-Omni deploy config](gh-file:vllm_omni/deploy/qwen3_omni_moe.yaml)
|
|
||||||
and expands its frozen three-stage pipeline into the four stages shown above.
|
|
||||||
A complete integration must:
|
|
||||||
|
|
||||||
- split the Thinker definition without duplicating the Talker or Code2Wav model
|
|
||||||
contracts
|
|
||||||
- remap downstream stage IDs, input sources, and stage connectors
|
|
||||||
- inject producer and consumer `kv_transfer_config` values into the two Thinker
|
|
||||||
stages
|
|
||||||
- allocate separate device sets for prefill and decode
|
|
||||||
- preserve both final outputs: decode text and Code2Wav audio
|
|
||||||
- provide connector-specific routing metadata without hard-coding one connector
|
|
||||||
contract
|
|
||||||
|
|
||||||
The required number of GPUs depends on tensor-parallel sizes and whether Talker
|
|
||||||
and Code2Wav share a device. A basic TP=1 layout uses one device for prefill, one
|
|
||||||
for decode, and one for the two downstream audio stages.
|
|
||||||
|
|
||||||
## Implementation map
|
|
||||||
|
|
||||||
- PD detection, validation, and sampling-parameter preparation:
|
|
||||||
[`PDDisaggregationMixin`](gh-file:vllm_omni/entrypoints/pd_utils.py)
|
|
||||||
- startup detection and connector metadata:
|
|
||||||
[`AsyncOmniEngine`](gh-file:vllm_omni/engine/async_omni_engine.py)
|
|
||||||
- request-scoped prefill-to-decode routing:
|
|
||||||
[`Orchestrator`](gh-file:vllm_omni/engine/orchestrator.py)
|
|
||||||
- Qwen3 downstream conditioning helpers:
|
|
||||||
[Qwen3-Omni stage input processor](gh-file:vllm_omni/model_executor/stage_input_processors/qwen3_omni.py)
|
|
||||||
@@ -47,7 +47,7 @@ if __name__ == "__main__":
|
|||||||
omni = Omni(model="Tongyi-MAI/Z-Image-Turbo")
|
omni = Omni(model="Tongyi-MAI/Z-Image-Turbo")
|
||||||
prompt = "a cup of coffee on the table"
|
prompt = "a cup of coffee on the table"
|
||||||
outputs = omni.generate(prompt)
|
outputs = omni.generate(prompt)
|
||||||
images = outputs[0].images
|
images = outputs[0].request_output.images
|
||||||
images[0].save("coffee.png")
|
images[0].save("coffee.png")
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -75,7 +75,8 @@ if __name__ == "__main__":
|
|||||||
]
|
]
|
||||||
omni_outputs = omni.generate(prompts)
|
omni_outputs = omni.generate(prompts)
|
||||||
for i_prompt, prompt_output in enumerate(omni_outputs):
|
for i_prompt, prompt_output in enumerate(omni_outputs):
|
||||||
this_images = prompt_output.images
|
this_request_output = prompt_output.request_output
|
||||||
|
this_images = this_request_output.images
|
||||||
for i_image, image in enumerate(this_images):
|
for i_image, image in enumerate(this_images):
|
||||||
image.save(f"p{i_prompt}-img{i_image}.jpg")
|
image.save(f"p{i_prompt}-img{i_image}.jpg")
|
||||||
print("saved to", f"p{i_prompt}-img{i_image}.jpg")
|
print("saved to", f"p{i_prompt}-img{i_image}.jpg")
|
||||||
|
|||||||
@@ -37,23 +37,15 @@ th {
|
|||||||
| `Krea2Pipeline` | Krea 2 (Raw + Turbo) | `krea/Krea-2-Raw`, `krea/Krea-2-Turbo` | ✅︎ | | | | — |
|
| `Krea2Pipeline` | Krea 2 (Raw + Turbo) | `krea/Krea-2-Raw`, `krea/Krea-2-Turbo` | ✅︎ | | | | — |
|
||||||
| `WanPipeline` | Wan2.1-T2V, Wan2.2-T2V, Wan2.2-TI2V | `Wan-AI/Wan2.1-T2V-1.3B-Diffusers`, `Wan-AI/Wan2.1-T2V-14B-Diffusers`, `Wan-AI/Wan2.2-T2V-A14B-Diffusers`, `Wan-AI/Wan2.2-TI2V-5B-Diffusers` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `WanPipeline` | Wan2.1-T2V, Wan2.2-T2V, Wan2.2-TI2V | `Wan-AI/Wan2.1-T2V-1.3B-Diffusers`, `Wan-AI/Wan2.1-T2V-14B-Diffusers`, `Wan-AI/Wan2.2-T2V-A14B-Diffusers`, `Wan-AI/Wan2.2-TI2V-5B-Diffusers` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `WanImageToVideoPipeline` | Wan2.2-I2V | `Wan-AI/Wan2.2-I2V-A14B-Diffusers` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `WanImageToVideoPipeline` | Wan2.2-I2V | `Wan-AI/Wan2.2-I2V-A14B-Diffusers` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `LingBotWorldCausalDMDPipeline` | LingBot-World 2.0 v1 (experimental) | `robbyant/lingbot-world-v2-14b-causal-fast-diffusers` | Experimental | | | | — |
|
|
||||||
| `Cosmos3OmniDiffusersPipeline` | Cosmos3 T2I, T2V, I2V, V2V, T2V with sound, action policy | `nvidia/Cosmos3-Nano`, `nvidia/Cosmos3-Super` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `Cosmos3OmniDiffusersPipeline` | Cosmos3 T2I, T2V, I2V, V2V, T2V with sound, action policy | `nvidia/Cosmos3-Nano`, `nvidia/Cosmos3-Super` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `Wan22S2VPipeline` | Wan2.2-S2V | `Wan-AI/Wan2.2-S2V-14B` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `Wan22S2VPipeline` | Wan2.2-S2V | `Wan-AI/Wan2.2-S2V-14B` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `Wan22VACEPipeline` | Wan2.1-VACE | `Wan-AI/Wan2.1-VACE-1.3B-diffusers`, `Wan-AI/Wan2.1-VACE-14B-diffusers` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `Wan22VACEPipeline` | Wan2.1-VACE | `Wan-AI/Wan2.1-VACE-1.3B-diffusers`, `Wan-AI/Wan2.1-VACE-14B-diffusers` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `Wan22VACEPipeline` | Wan2.2-VACE | `Pyros13/Wan2.2-VACE-Fun-A14B-Diffusers` | ✅︎ | | | | — |
|
| `Wan22VACEPipeline` | Wan2.2-VACE | `Pyros13/Wan2.2-VACE-Fun-A14B-Diffusers` | ✅︎ | | | | — |
|
||||||
| `LTX2Pipeline` | LTX-2 / LTX-2.3 one-stage T2V and I2V | `Lightricks/LTX-2`, `diffusers/LTX-2.3-Diffusers` | ✅︎ | ✅︎ | | | — |
|
| `LTX2Pipeline` | LTX-2 / LTX-2.3 one-stage T2V and I2V | `Lightricks/LTX-2`, `diffusers/LTX-2.3-Diffusers` | ✅︎ | ✅︎ | | | — |
|
||||||
| `LTX2TwoStagePipeline` | LTX-2 / LTX-2.3 ordinary two-stage T2V and I2V | `Lightricks/LTX-2`, `diffusers/LTX-2.3-Diffusers` + matching Lightricks LoRA and upsampler | ✅︎ | ✅︎ | | | — |
|
| `LTX2DistilledPipeline` | LTX-2 distilled two-stage T2V and I2V | `rootonchair/LTX-2-19b-distilled` | ✅︎ | ✅︎ | | | — |
|
||||||
| `LTX2DistilledOneStagePipeline` | LTX-2 / LTX-2.3 merged-distilled one-stage T2V and I2V | `rootonchair/LTX-2-19b-distilled`, `diffusers/LTX-2.3-Distilled-Diffusers` | ✅︎ | ✅︎ | | | — |
|
|
||||||
| `LTX2DistilledTwoStagePipeline` | LTX-2 / LTX-2.3 merged-distilled two-stage T2V and I2V | `rootonchair/LTX-2-19b-distilled`, `diffusers/LTX-2.3-Distilled-Diffusers` + matching Lightricks upsampler | ✅︎ | ✅︎ | | | — |
|
|
||||||
| `LTX2Pipeline` | LTX-2.5 Full/SFT one-stage T2V and I2V | `Lightricks/LTX-2.5-Diffusers` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/LTX/LTX-2.5.md) |
|
|
||||||
| `LTX2TwoStagePipeline` | LTX-2.5 Full/SFT two-stage T2V and I2V | `Lightricks/LTX-2.5-Diffusers` + `Lightricks/LTX-2.5` LoRA | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/LTX/LTX-2.5.md) |
|
|
||||||
| `LTX2DistilledOneStagePipeline` | LTX-2.5 merged-distilled one-stage T2V and I2V | `Lightricks/LTX-2.5-Diffusers` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/LTX/LTX-2.5.md) |
|
|
||||||
| `LTX2DistilledTwoStagePipeline` | LTX-2.5 merged-distilled two-stage T2V and I2V | `Lightricks/LTX-2.5-Diffusers` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/LTX/LTX-2.5.md) |
|
|
||||||
| `LingBotVideoPipeline` | LingBot-Video dense and MoE T2I, T2V, TI2V | `robbyant/lingbot-video-dense-1.3b`, `robbyant/lingbot-video-moe-30b-a3b` | ✅︎ | | | | — |
|
| `LingBotVideoPipeline` | LingBot-Video dense and MoE T2I, T2V, TI2V | `robbyant/lingbot-video-dense-1.3b`, `robbyant/lingbot-video-moe-30b-a3b` | ✅︎ | | | | — |
|
||||||
| `MiniMaxH3Pipeline` | MiniMax H3 T2VA, first/last-keyframe FL2VA, and mixed-reference Ref2VA | `MiniMaxAI/MiniMax-H3` | ✅︎ | ✅︎ | | | [Published](https://recipes.vllm.ai/MiniMaxAI/MiniMax-H3) |
|
| `MiniMaxH3Pipeline` | MiniMax H3 T2VA, first/last-keyframe FL2VA, and mixed-reference Ref2VA | `MiniMaxAI/MiniMax-H3` | ✅︎ | ✅︎ | | | [Published](https://recipes.vllm.ai/MiniMaxAI/MiniMax-H3) |
|
||||||
| `DreamZeroPipeline` | DreamZero-DROID | `GEAR-Dreams/DreamZero-DROID` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `DreamZeroPipeline` | DreamZero-DROID | `GEAR-Dreams/DreamZero-DROID` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `Pi0Pipeline` | π0 (Pi-Zero) VLA | `lerobot/pi0_base` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/lerobot/Pi0.md) |
|
|
||||||
| `HeliosPipeline`, `HeliosPyramidPipeline` | Helios | `BestWishYsh/Helios-Base`, `BestWishYsh/Helios-Mid`, `BestWishYsh/Helios-Distilled` | ✅︎ | ✅︎ | ✅︎ | | — |
|
| `HeliosPipeline`, `HeliosPyramidPipeline` | Helios | `BestWishYsh/Helios-Base`, `BestWishYsh/Helios-Mid`, `BestWishYsh/Helios-Distilled` | ✅︎ | ✅︎ | ✅︎ | | — |
|
||||||
| `MagiHumanPipeline` | MagiHuman | `SII-GAIR/daVinci-MagiHuman-Base-1080p` | ✅︎ | ✅︎ | | | — |
|
| `MagiHumanPipeline` | MagiHuman | `SII-GAIR/daVinci-MagiHuman-Base-1080p` | ✅︎ | ✅︎ | | | — |
|
||||||
| `OvisImagePipeline` | Ovis-Image | `OvisAI/Ovis-Image` | ✅︎ | ✅︎ | | ✅︎ | — |
|
| `OvisImagePipeline` | Ovis-Image | `OvisAI/Ovis-Image` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||||
@@ -66,7 +58,6 @@ th {
|
|||||||
| `CosyVoice3Model` | CosyVoice3 | `FunAudioLLM/Fun-CosyVoice3-0.5B-2512` | ✅︎ | ✅︎ | | ✅︎ | — |
|
| `CosyVoice3Model` | CosyVoice3 | `FunAudioLLM/Fun-CosyVoice3-0.5B-2512` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||||
| `OmniVoiceModel` | OmniVoice | `k2-fsa/OmniVoice` | ✅︎ | | | | — |
|
| `OmniVoiceModel` | OmniVoice | `k2-fsa/OmniVoice` | ✅︎ | | | | — |
|
||||||
| `VoxCPM2TalkerForConditionalGeneration` | VoxCPM2 | `openbmb/VoxCPM2` | ✅︎ | | | | — |
|
| `VoxCPM2TalkerForConditionalGeneration` | VoxCPM2 | `openbmb/VoxCPM2` | ✅︎ | | | | — |
|
||||||
| `DotsTTSForConditionalGeneration` | dots.tts | `rednote-hilab/dots.tts-soar` | ✅︎ | | | | — |
|
|
||||||
| `MammothModa2ForConditionalGeneration` | MammothModa2-Preview | `bytedance-research/MammothModa2-Preview` | ✅︎ | ✅︎ | | | — |
|
| `MammothModa2ForConditionalGeneration` | MammothModa2-Preview | `bytedance-research/MammothModa2-Preview` | ✅︎ | ✅︎ | | | — |
|
||||||
| `MammothModa2ForConditionalGeneration` | MammothModa2-Dev (AR-only image understanding) | `bytedance-research/MammothModa2-Dev` | ✅︎ | | | | — |
|
| `MammothModa2ForConditionalGeneration` | MammothModa2-Dev (AR-only image understanding) | `bytedance-research/MammothModa2-Dev` | ✅︎ | | | | — |
|
||||||
| `Flux2KleinPipeline` | FLUX.2-klein | `black-forest-labs/FLUX.2-klein-4B`, `black-forest-labs/FLUX.2-klein-9B` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `Flux2KleinPipeline` | FLUX.2-klein | `black-forest-labs/FLUX.2-klein-4B`, `black-forest-labs/FLUX.2-klein-9B` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
@@ -78,7 +69,6 @@ th {
|
|||||||
| `SoulXSingerPipeline` | SoulX-Singer (SVS) | `Soul-AILab/SoulX-Singer` | ✅︎ | | | | — |
|
| `SoulXSingerPipeline` | SoulX-Singer (SVS) | `Soul-AILab/SoulX-Singer` | ✅︎ | | | | — |
|
||||||
| `SoulXSingerSVCPipeline` | SoulX-Singer-SVC | `Soul-AILab/SoulX-Singer` (`model-svc.pt`) | ✅︎ | | | | — |
|
| `SoulXSingerSVCPipeline` | SoulX-Singer-SVC | `Soul-AILab/SoulX-Singer` (`model-svc.pt`) | ✅︎ | | | | — |
|
||||||
| `AudioXPipeline` | AudioX | `zhangj1an/AudioX` | ✅︎ | ✅︎ | | | — |
|
| `AudioXPipeline` | AudioX | `zhangj1an/AudioX` | ✅︎ | ✅︎ | | | — |
|
||||||
| `MiniMaxMusic3ForConditionalGeneration` | MiniMax Music 3 (text-to-music) | `MiniMaxAI/MiniMax-Music3` | ✅︎ | | | | — |
|
|
||||||
| `Qwen3TTSForConditionalGeneration` | Qwen3-TTS-12Hz-1.7B-CustomVoice | `Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice` | ✅︎ | | | | [Published](https://recipes.vllm.ai/Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice) |
|
| `Qwen3TTSForConditionalGeneration` | Qwen3-TTS-12Hz-1.7B-CustomVoice | `Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice` | ✅︎ | | | | [Published](https://recipes.vllm.ai/Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice) |
|
||||||
| `Qwen3TTSForConditionalGeneration` | Qwen3-TTS-12Hz-1.7B-VoiceDesign | `Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `Qwen3TTSForConditionalGeneration` | Qwen3-TTS-12Hz-1.7B-VoiceDesign | `Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `Qwen3TTSForConditionalGeneration` | Qwen3-TTS-12Hz-1.7B-Base | `Qwen/Qwen3-TTS-12Hz-1.7B-Base` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `Qwen3TTSForConditionalGeneration` | Qwen3-TTS-12Hz-1.7B-Base | `Qwen/Qwen3-TTS-12Hz-1.7B-Base` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
@@ -106,7 +96,6 @@ th {
|
|||||||
|`DyninOmniForConditionalGeneration` | Dynin-Omni | `snu-aidas/Dynin-Omni` | ✅︎ | | | | — |
|
|`DyninOmniForConditionalGeneration` | Dynin-Omni | `snu-aidas/Dynin-Omni` | ✅︎ | | | | — |
|
||||||
| `MiniCPMO45OmniForConditionalGeneration` | MiniCPM-o 4.5 | `openbmb/MiniCPM-o-4_5` | ✅︎ | | ✅︎ | | — |
|
| `MiniCPMO45OmniForConditionalGeneration` | MiniCPM-o 4.5 | `openbmb/MiniCPM-o-4_5` | ✅︎ | | ✅︎ | | — |
|
||||||
| `ErnieImagePipeline` | ERNIE-Image | `baidu/ERNIE-Image`, `baidu/ERNIE-Image-Turbo` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `ErnieImagePipeline` | ERNIE-Image | `baidu/ERNIE-Image`, `baidu/ERNIE-Image-Turbo` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `GepardTalkerForConditionalGeneration` | Gepard-1.0 | `nineninesix/gepard-1.0` | ✅︎ | | | | — |
|
|
||||||
|`HiDreamImagePipeline` | HiDream-I1-Full | `HiDream-ai/HiDream-I1-Full` | ✅︎ | ✅︎ | | | — |
|
|`HiDreamImagePipeline` | HiDream-I1-Full | `HiDream-ai/HiDream-I1-Full` | ✅︎ | ✅︎ | | | — |
|
||||||
|
|
||||||
The `Recipe` column links the corresponding deployment recipe. `Published` is
|
The `Recipe` column links the corresponding deployment recipe. `Published` is
|
||||||
@@ -120,10 +109,6 @@ support metadata until their recipe is audited.
|
|||||||
|
|
||||||
✅︎ indicates the model is supported on that backend. Empty cells mean not listed as supported on that backend.
|
✅︎ indicates the model is supported on that backend. Empty cells mean not listed as supported on that backend.
|
||||||
|
|
||||||
See the [LingBot-World 2.0 recipe](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Robbyant/LingBot-World-2.0.md)
|
|
||||||
for offline and experimental realtime usage, supported controls, validation,
|
|
||||||
and current limitations.
|
|
||||||
|
|
||||||
<sup>H3</sup> MiniMax H3 AMD GPU support is validated on gfx942 (MI300X) and
|
<sup>H3</sup> MiniMax H3 AMD GPU support is validated on gfx942 (MI300X) and
|
||||||
gfx950 (MI350) in BF16, using the AITER `FLASH_ATTN` diffusion attention backend.
|
gfx950 (MI350) in BF16, using the AITER `FLASH_ATTN` diffusion attention backend.
|
||||||
Other AMD SKUs (e.g. MI325X) are not listed until their
|
Other AMD SKUs (e.g. MI325X) are not listed until their
|
||||||
|
|||||||
@@ -141,20 +141,7 @@ The response shape depends on the streaming parameters:
|
|||||||
|
|
||||||
**Non-streaming (default).** With `stream=false` and no `stream_format`, returns the
|
**Non-streaming (default).** With `stream=false` and no `stream_format`, returns the
|
||||||
complete clip as binary audio data with an appropriate `Content-Type` header (e.g.
|
complete clip as binary audio data with an appropriate `Content-Type` header (e.g.
|
||||||
`audio/wav`). Because the raw-bytes body has no JSON carrier, successful
|
`audio/wav`). The raw-bytes body has no JSON carrier, so no `usage` is reported.
|
||||||
non-streaming responses from non-diffusion speech servers report usage through
|
|
||||||
response headers:
|
|
||||||
|
|
||||||
| Header | Description |
|
|
||||||
| --- | --- |
|
|
||||||
| `x-vllm-omni-input-tokens` | Total input tokens (`text_tokens` + `audio_tokens`). |
|
|
||||||
| `x-vllm-omni-output-tokens` | Generated codec/audio tokens. |
|
|
||||||
| `x-vllm-omni-total-tokens` | `input_tokens` + `output_tokens`. |
|
|
||||||
| `x-vllm-omni-input-text-tokens` | Tokens from the synthesized text (`input` plus `instructions`). |
|
|
||||||
| `x-vllm-omni-input-audio-tokens` | Reference-audio codec frames, non-zero only for in-context voice cloning. |
|
|
||||||
|
|
||||||
Diffusion-mode speech servers route through a separate response path and do not
|
|
||||||
emit these headers.
|
|
||||||
|
|
||||||
**Raw audio stream** (`stream_format="audio"`). Streams raw audio bytes (PCM or
|
**Raw audio stream** (`stream_format="audio"`). Streams raw audio bytes (PCM or
|
||||||
WAV) as they are decoded.
|
WAV) as they are decoded.
|
||||||
|
|||||||
@@ -167,14 +167,6 @@ transfer options such as `control_path`, `control_guidance`,
|
|||||||
`num_conditional_frames`, `show_control_condition`, and `show_input`; see the
|
`num_conditional_frames`, `show_control_condition`, and `show_input`; see the
|
||||||
Cosmos3 recipe for complete examples.
|
Cosmos3 recipe for complete examples.
|
||||||
|
|
||||||
HTTP redirects for `image_reference.image_url` follow vLLM's
|
|
||||||
`VLLM_MEDIA_URL_ALLOW_REDIRECTS` setting. Before starting the server, set it to
|
|
||||||
`1` (the default) to allow redirects or `0` to reject them. A redirect target
|
|
||||||
can differ from the original host, and vLLM-Omni does not yet fully implement
|
|
||||||
upstream vLLM's media URL allowlist protection. Deployments should therefore
|
|
||||||
treat remote media URLs as untrusted and choose this setting as part of their
|
|
||||||
URL access policy.
|
|
||||||
|
|
||||||
### Speech-to-Video
|
### Speech-to-Video
|
||||||
|
|
||||||
For models that support audio-driven generation (e.g., Wan2.2-S2V), pass both
|
For models that support audio-driven generation (e.g., Wan2.2-S2V), pass both
|
||||||
|
|||||||
@@ -1,73 +1,63 @@
|
|||||||
# Diffusion Attention Backends
|
# Diffusion Attention Backends
|
||||||
|
|
||||||
Use this page to select and configure a diffusion attention backend. Backend-
|
This document describes the diffusion attention backends available in vLLM-Omni, how to select them globally and per-role, the per-platform defaults, and how to use SageAttention.
|
||||||
specific installation, tuning, and compatibility details live in separate
|
|
||||||
guides so that the selection contract stays easy to scan.
|
|
||||||
|
|
||||||
Diffusion attention backend selection applies to DiT attention in image and
|
## Overview
|
||||||
video generation models. It does **not** change autoregressive LLM attention,
|
|
||||||
which uses vLLM's attention selector.
|
|
||||||
|
|
||||||
For the internal selector, registry, and platform contract, see
|
Diffusion attention backend selection is resolved in `vllm_omni.diffusion.attention.selector`. It looks up the backend from a structured `AttentionConfig` carried on `OmniDiffusionConfig` and falls back to the platform default when nothing is configured.
|
||||||
[Attention Backend Selection](../../design/feature/attention_backend_selection.md).
|
|
||||||
|
|
||||||
## Choose a guide
|
This backend is used by diffusion attention layers such as the DiT attention in video and image generation models. It does **not** affect autoregressive (LLM) attention paths — those go through vLLM's own attention backend selector.
|
||||||
|
|
||||||
| Need | Guide |
|
The full set of backends and their platform defaults is in the **Backend Options** and **Platform Defaults** sections below. If no attention backend is configured, vLLM-Omni asks the current platform to choose the default.
|
||||||
|
|
||||||
|
## Backend Options
|
||||||
|
|
||||||
|
| Value | Notes |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Select a conservative or platform-native dense kernel | [Dense Backends](attention_backends/dense_backends.md) |
|
| `TRTLLM_ATTN` | FlashInfer's trtllm-gen FMHA (TensorRT-LLM's generated kernels, vendored by FlashInfer). Dense BF16, GQA native, `head_dim=128`. Datacenter Blackwell only (sm_100 / sm_103). Packed paths can provide `cu_seqlens` directly. Supports optional **Skip-Softmax** sparse attention — see below. Requires `flashinfer`. |
|
||||||
| Use FlashInfer trtllm-gen FMHA, Skip-Softmax, or TRTLLM SAGE quantization | [TRTLLM Attention](attention_backends/trtllm.md) |
|
| `FLASH_ATTN` | Wraps FlashAttention 4 on Blackwell when `flash-attn-4` is installed, then falls back to FlashAttention 3/2. Default on Hopper / Ada / Ampere when a compatible FlashAttention package is installed. |
|
||||||
| Install and use SageAttention 2.2 or SageAttention3 | [SageAttention](attention_backends/sage.md) |
|
| `CUDNN_ATTN` | Pins `sdpa_kernel([CUDNN_ATTENTION])`. Default on Blackwell (sm_10x / sm_12x) with cuDNN ≥ 9.5. Wins on mask-heavy DiTs (HunyuanVideo-1.5: 2× e2e vs SDPA). |
|
||||||
| Match training or rollout kernels loaded from Hugging Face | [Hugging Face Hub Backends](attention_backends/huggingface_hub.md) |
|
| `FLASHINFER_ATTN` | Uses FlashInfer's batch-prefill wrapper and supports mixed Q/K and V input dtypes through backend-specific configuration. Used as Blackwell fallback when cuDNN is unavailable. Requires `flashinfer` >= 0.6.16rc1 for mixed-dtype configurations. |
|
||||||
| Use block-sparse video attention on Ascend NPU | [RainFusion](attention_backends/rainfusion.md) |
|
| `TORCH_SDPA` | PyTorch `scaled_dot_product_attention` with the default backend dispatcher. Most conservative; always available. |
|
||||||
|
| `SAGE_ATTN` | SageAttention 2.2 — INT8-quantized attention with FP16 accumulation. Lossy but typically visually indistinguishable on diffusion outputs. Requires `sageattention`. |
|
||||||
|
| `SAGE_ATTN_3` | Requires `sageattn3` from `SageAttention/sageattention3_blackwell`. CUDA only, intended for Blackwell GPUs, with GQA/MQA requests falling back to PyTorch SDPA. |
|
||||||
|
| `FLASH_ATTN_HUB` | FlashAttention 2 from HuggingFace `kernels` library. Useful for train/rollout alignment. |
|
||||||
|
| `FLASH_ATTN_3_HUB` | FlashAttention 3 from HuggingFace `kernels` library. CUDA Hopper (sm_90+) only; falls back to `FLASH_ATTN_HUB` on older GPUs. |
|
||||||
|
| `RAINFUSION_ATTN` | MindIE-SD **RainFusion** block-sparse video attention — see [below](#rainfusion_attn-backend-and-block-sparse-video-attention). Ascend NPU only; requires `mindiesd`. Delegates to `FLASH_ATTN` for anything that is not a packed video sequence. |
|
||||||
|
|
||||||
## Backend options
|
|
||||||
|
|
||||||
| Value | Family | Primary use | Detail |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `TORCH_SDPA` | Dense | Conservative reference; always available | [Dense Backends](attention_backends/dense_backends.md#torch_sdpa) |
|
|
||||||
| `FLASH_ATTN` | Dense | FlashAttention 4/3/2 depending on the installed package and GPU | [Dense Backends](attention_backends/dense_backends.md#flash_attn) |
|
|
||||||
| `CUDNN_ATTN` | Dense | Mask-heavy DiTs on Blackwell with cuDNN 9.5 or newer | [Dense Backends](attention_backends/dense_backends.md#cudnn_attn) |
|
|
||||||
| `FLASHINFER_ATTN` | Dense or quantized | FlashInfer batch prefill; optional mixed Q/K and V dtypes | [Dense Backends](attention_backends/dense_backends.md#flashinfer_attn) |
|
|
||||||
| `TRTLLM_ATTN` | Dense, sparse, or quantized | Datacenter Blackwell with `head_dim=128` and compatible packed paths | [TRTLLM Attention](attention_backends/trtllm.md) |
|
|
||||||
| `SAGE_ATTN` | Quantized | SageAttention 2.2 INT8 attention | [SageAttention](attention_backends/sage.md#sage_attn) |
|
|
||||||
| `SAGE_ATTN_3` | Quantized | SageAttention3 on Blackwell | [SageAttention](attention_backends/sage.md#sage_attn_3) |
|
|
||||||
| `FLASH_ATTN_HUB` | Hub kernel | FlashAttention 2 from Hugging Face `kernels` | [Hugging Face Hub Backends](attention_backends/huggingface_hub.md) |
|
|
||||||
| `FLASH_ATTN_3_HUB` | Hub kernel | FlashAttention 3 from Hugging Face `kernels` on Hopper or newer | [Hugging Face Hub Backends](attention_backends/huggingface_hub.md) |
|
|
||||||
| `RAINFUSION_ATTN` | Block sparse | MindIE-SD RainFusion video attention on Ascend NPU | [RainFusion](attention_backends/rainfusion.md) |
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Backend selection is resolved in this order:
|
Diffusion attention backends can be configured three ways, in priority order:
|
||||||
|
|
||||||
1. `--diffusion-attention-config` per-role configuration.
|
1. **`--diffusion-attention-config`** — structured per-role config (highest priority).
|
||||||
2. `--diffusion-attention-backend` or `DIFFUSION_ATTENTION_BACKEND` as a
|
2. **`--diffusion-attention-backend` / `DIFFUSION_ATTENTION_BACKEND` env var** — global shorthand that sets the default backend.
|
||||||
global default.
|
3. **Platform default** — used when nothing is configured.
|
||||||
3. The current platform's default.
|
|
||||||
|
|
||||||
`--diffusion-attention-backend` is shorthand for
|
`--diffusion-attention-backend` is shorthand for `--diffusion-attention-config.default.backend`. It may be combined with `--diffusion-attention-config.per_role.*` overrides, but is mutually exclusive with `--diffusion-attention-config.default.backend`.
|
||||||
`--diffusion-attention-config.default.backend`. Do not pass it together with an
|
|
||||||
explicit `default.backend` in the structured configuration.
|
|
||||||
|
|
||||||
### Global default
|
### Global default
|
||||||
|
|
||||||
```bash
|
Set the default backend for every diffusion attention layer:
|
||||||
vllm-omni serve <model> --diffusion-attention-backend FLASH_ATTN
|
|
||||||
|
|
||||||
# Backwards-compatible environment variable
|
```bash
|
||||||
export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
|
# CLI flag
|
||||||
|
vllm-omni serve <model> --diffusion-attention-backend SAGE_ATTN
|
||||||
|
|
||||||
|
# Environment variable (also recognized for backwards compatibility)
|
||||||
|
export DIFFUSION_ATTENTION_BACKEND=SAGE_ATTN
|
||||||
```
|
```
|
||||||
|
|
||||||
### Per-role configuration
|
### Per-role configuration
|
||||||
|
|
||||||
Roles are declared by each diffusion model. Common categories are `self` and
|
Roles are free-form strings declared by each diffusion model. The two common categories are `"self"` and `"cross"`; model-specific roles (e.g. `"ltx2.audio_to_video"`) may also be declared. A role string is matched in this order:
|
||||||
`cross`; a model may also use a more specific role such as
|
|
||||||
`ltx2.audio_to_video`. Resolution order is:
|
|
||||||
|
|
||||||
1. Exact `per_role[role]` match.
|
1. Exact `per_role[role]` match
|
||||||
2. Category `per_role[role_category]` match.
|
2. `per_role[role_category]` fallback (e.g. `"ltx2.audio_to_video"` → `"cross"`)
|
||||||
3. `default`.
|
3. `default`
|
||||||
4. Platform default.
|
4. Platform default
|
||||||
|
|
||||||
|
Use vLLM-style dotted flags or one JSON blob:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Dotted flags
|
# Dotted flags
|
||||||
@@ -75,118 +65,430 @@ vllm-omni serve <model> \
|
|||||||
--diffusion-attention-config.default.backend FLASH_ATTN \
|
--diffusion-attention-config.default.backend FLASH_ATTN \
|
||||||
--diffusion-attention-config.per_role.cross.backend TORCH_SDPA
|
--diffusion-attention-config.per_role.cross.backend TORCH_SDPA
|
||||||
|
|
||||||
# Equivalent JSON
|
# JSON
|
||||||
vllm-omni serve <model> \
|
vllm-omni serve <model> \
|
||||||
--diffusion-attention-config \
|
--diffusion-attention-config '{"default":{"backend":"FLASH_ATTN"},"per_role":{"cross":{"backend":"TORCH_SDPA"}}}'
|
||||||
'{"default":{"backend":"FLASH_ATTN"},"per_role":{"cross":{"backend":"TORCH_SDPA"}}}'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Python API
|
A backend that needs configuration exposes it as a typed field on the spec:
|
||||||
|
`TRTLLM_ATTN`'s Skip-Softmax (see [below](#trtllm_attn-backend-and-skip-softmax)) and
|
||||||
|
`RAINFUSION_ATTN`'s RainFusion block (see [below](#rainfusion_attn-backend-and-block-sparse-video-attention)):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--diffusion-attention-config.default.backend TRTLLM_ATTN \
|
||||||
|
--diffusion-attention-config.default.skip_softmax.target_sparsity 0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
### Programmatic API
|
||||||
|
|
||||||
|
When constructing `OmniDiffusionConfig` directly:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from vllm_omni.diffusion.data import (
|
from vllm_omni.diffusion.data import AttentionConfig, AttentionSpec, AttnQuantSpec, OmniDiffusionConfig
|
||||||
AttentionConfig,
|
|
||||||
AttentionSpec,
|
|
||||||
OmniDiffusionConfig,
|
|
||||||
)
|
|
||||||
|
|
||||||
config = OmniDiffusionConfig(
|
config = OmniDiffusionConfig(
|
||||||
diffusion_attention_config=AttentionConfig(
|
diffusion_attention_config=AttentionConfig(
|
||||||
default=AttentionSpec(backend="FLASH_ATTN"),
|
default=AttentionSpec(backend="FLASH_ATTN"),
|
||||||
per_role={"cross": AttentionSpec(backend="TORCH_SDPA")},
|
per_role={
|
||||||
|
"cross": AttentionSpec(backend="TORCH_SDPA"),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
...,
|
...,
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Backend-specific typed blocks are documented with their consumers:
|
A plain dict is also accepted and normalized to `AttentionConfig`.
|
||||||
|
|
||||||
- `quant`: [FlashInfer](attention_backends/dense_backends.md#flashinfer-quantized-attention)
|
#### FlashInfer QK16/V8 quantized attention
|
||||||
and [TRTLLM SAGE](attention_backends/trtllm.md#sage-quantization).
|
|
||||||
- `skip_softmax`: [TRTLLM Skip-Softmax](attention_backends/trtllm.md#skip-softmax).
|
|
||||||
- `block_sparse`: [RainFusion](attention_backends/rainfusion.md#configuration).
|
|
||||||
|
|
||||||
## Platform defaults
|
`FLASHINFER_ATTN` accepts quantization options through `AttentionSpec.quant`. For QK16/V8:
|
||||||
|
|
||||||
|
```python
|
||||||
|
config = OmniDiffusionConfig(
|
||||||
|
diffusion_attention_config=AttentionConfig(
|
||||||
|
default=AttentionSpec(
|
||||||
|
backend="FLASHINFER_ATTN",
|
||||||
|
quant=AttnQuantSpec(
|
||||||
|
dtype_qk="bfloat16",
|
||||||
|
dtype_vo="fp8_e4m3",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
`dtype_qk` configures the Q and K input dtypes. `dtype_vo` configures the V input dtype.
|
||||||
|
|
||||||
|
## Platform Defaults
|
||||||
|
|
||||||
### Blackwell (sm_100 / sm_103 / sm_120 / sm_121)
|
### Blackwell (sm_100 / sm_103 / sm_120 / sm_121)
|
||||||
|
|
||||||
The CUDA auto-route preference is:
|
Auto-route preference, in order:
|
||||||
|
|
||||||
1. `TRTLLM_ATTN` on datacenter Blackwell (sm_100/sm_103) when FlashInfer is
|
1. `TRTLLM_ATTN` — on **datacenter** Blackwell (sm_100 / sm_103) when `flashinfer` is installed, the model's `head_dim` is 128, **and the model declares a compatible packed/mask-free path**
|
||||||
available, `head_dim=128`, and the model declares a compatible packed or
|
2. `CUDNN_ATTN` — when cuDNN ≥ 9.5 is available (ships in PyTorch 2.5+ wheels)
|
||||||
mask-free path.
|
3. `FLASHINFER_ATTN` — when `flashinfer` is installed but cuDNN < 9.5
|
||||||
2. `CUDNN_ATTN` when cuDNN 9.5 or newer is available.
|
4. `FLASH_ATTN` — when `flash-attn` is installed with the Blackwell CUTE kernel
|
||||||
3. `FLASHINFER_ATTN` when FlashInfer is available but cuDNN is too old.
|
5. `TORCH_SDPA` — last resort
|
||||||
4. `FLASH_ATTN` when a compatible package is installed.
|
|
||||||
5. `TORCH_SDPA`.
|
|
||||||
|
|
||||||
`TRTLLM_ATTN` is not auto-selected on workstation Blackwell
|
`TRTLLM_ATTN` is skipped on workstation Blackwell (sm_120 / sm_121) and for any `head_dim != 128`, so those GPUs keep the `CUDNN_ATTN` route described below.
|
||||||
(sm_120/sm_121), for other head dimensions, or for paths that require an
|
|
||||||
unsupported mask.
|
|
||||||
|
|
||||||
### Hopper, Ada, and Ampere
|
`TRTLLM_ATTN` outranks `CUDNN_ATTN` on datacenter Blackwell for compatible packed/mask-free pipelines. Workstation Blackwell (sm_120 / sm_121) and pipelines that require attention masks retain their normal fallback.
|
||||||
|
|
||||||
The CUDA auto-route uses `FLASH_ATTN` when available and otherwise falls back
|
The startup log line `Defaulting to diffusion attention backend CUDNN_ATTN (Blackwell sm_120, cuDNN 91002)` confirms the route.
|
||||||
to `TORCH_SDPA`. `CUDNN_ATTN` and `FLASHINFER_ATTN` remain explicit options.
|
|
||||||
|
|
||||||
Other platforms validate an explicit backend and choose their own default
|
**Why CUDNN_ATTN by default**: on mask-heavy diffusion models (HunyuanVideo-1.5, Qwen-Image), cuDNN's pinned FMHA kernel sidesteps a PyTorch SDPA dispatch quirk where the unpinned dispatcher picks `EFFICIENT_ATTENTION` (~25 ms) for masked calls instead of cuDNN (~11 ms). The pin gives 2× e2e on HV-1.5 with no regression on lighter models.
|
||||||
through the platform implementation. Check the startup log to confirm the
|
|
||||||
resolved backend.
|
|
||||||
|
|
||||||
## Choosing a backend manually
|
### Hopper (sm_90) / Ada (sm_89) / Ampere (sm_80–sm_86)
|
||||||
|
|
||||||
Override the platform default when you need:
|
Auto-route preference:
|
||||||
|
|
||||||
- a correctness reference (`TORCH_SDPA`);
|
1. `FLASH_ATTN` — when `flash-attn` is installed
|
||||||
- a backend-specific workaround;
|
2. `TORCH_SDPA` — fallback
|
||||||
- training/rollout kernel alignment (Hub backends); or
|
|
||||||
- an explicitly validated sparse or quantized speedup.
|
|
||||||
|
|
||||||
The startup log prints the resolved backend and whether it came from explicit
|
`CUDNN_ATTN` and `FLASHINFER_ATTN` are still selectable via env var on these GPUs but are not in the auto-route — FlashAttention 2 is the well-tuned path on pre-Blackwell hardware.
|
||||||
configuration or platform defaulting. If no resolution message appears, check
|
|
||||||
earlier logs for diffusion-stage initialization failures.
|
|
||||||
|
|
||||||
## Reference benchmark
|
## TRTLLM_ATTN Backend and Skip-Softmax
|
||||||
|
|
||||||
The following BF16 results were measured on an sm_120 RTX Pro 6000 Blackwell
|
`TRTLLM_ATTN` runs FlashInfer's trtllm-gen FMHA. Dense it is a BF16 backend like the others; on top of
|
||||||
with the same prompt and seed across runs. Treat them as reference results, not
|
that it can enable **Skip-Softmax**, a sparse-attention mode that trades a little fidelity for speed
|
||||||
portable guarantees.
|
(algorithm: [design doc](../../design/feature/skip_softmax.md)).
|
||||||
|
|
||||||
| Model | Shape | `TORCH_SDPA` | `CUDNN_ATTN` | `FLASHINFER_ATTN` |
|
Enable it through the typed `skip_softmax` block on the attention spec:
|
||||||
| --- | --- | ---: | ---: | ---: |
|
|
||||||
|
| Key | Valid values | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `target_sparsity` | finite, `[0, 1]` | Operating point on a calibrated curve. Needs a calibration for the model. |
|
||||||
|
| `threshold` | finite, `≥ 0` | Direct threshold, no calibration needed. Mutually exclusive with `target_sparsity`. |
|
||||||
|
| `disabled_until_timestep` | finite, `[0, 1]` | Holds early, high-noise steps dense; skip turns on once the normalized timestep `t ≤ D`. |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vllm-omni serve Wan-AI/Wan2.2-T2V-A14B-Diffusers \
|
||||||
|
--diffusion-attention-config '{"default": {"backend": "TRTLLM_ATTN",
|
||||||
|
"skip_softmax": {"target_sparsity": 0.65, "disabled_until_timestep": 0.86}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Programmatically the same block is a typed `SkipSoftmaxSpec` (values validated at construction):
|
||||||
|
|
||||||
|
```python
|
||||||
|
from vllm_omni.diffusion.data import AttentionConfig, AttentionSpec, SkipSoftmaxSpec
|
||||||
|
|
||||||
|
AttentionConfig(
|
||||||
|
default=AttentionSpec(
|
||||||
|
backend="TRTLLM_ATTN",
|
||||||
|
skip_softmax=SkipSoftmaxSpec(target_sparsity=0.65, disabled_until_timestep=0.86),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Start at `target_sparsity=0.65, disabled_until_timestep=0.86`** and raise `target_sparsity` only
|
||||||
|
as far as your quality bar allows — the output diverges from dense as it climbs. The gain grows with
|
||||||
|
sequence length, since Skip-Softmax only accelerates attention. Dense BF16 is `TRTLLM_ATTN` with no
|
||||||
|
`skip_softmax` block.
|
||||||
|
|
||||||
|
Requires datacenter Blackwell with `head_dim == 128`; elsewhere, or without FlashInfer, selecting
|
||||||
|
`TRTLLM_ATTN` raises rather than silently degrading.
|
||||||
|
|
||||||
|
## TRTLLM_ATTN SAGE Quantization
|
||||||
|
|
||||||
|
On top of dense/Skip-Softmax, `TRTLLM_ATTN` can run **SAGE** (SageAttention) — the trtllm-gen
|
||||||
|
FMHA kernel with per-block quantized Q/K and per-channel fp8 V, so the two attention matmuls run
|
||||||
|
in low precision. Enable it through the typed `quant` block on the attention spec:
|
||||||
|
|
||||||
|
| Key | Valid values | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `dtype_qk` | `int8`, `fp8_e4m3` | Quantization dtype for Q and K. Absent ⇒ dense (SAGE off). |
|
||||||
|
| `q_block_size` | `1`, `4`, `16` | Per-token block size for Q scales (default `1`). |
|
||||||
|
| `k_block_size` | `1`, `4`, `16` | Per-token block size for K scales (default `16`). |
|
||||||
|
|
||||||
|
V is always quantized per-channel to fp8_e4m3 and K-smoothing is applied inside the routine, so
|
||||||
|
neither is a user knob. Only block sizes `1`, `4`, `16` have compiled kernels.
|
||||||
|
|
||||||
|
The `quant` block is a shared `AttnQuantSpec` consumed by more than one backend; each reads the
|
||||||
|
fields that apply and rejects the rest. `TRTLLM_ATTN` (SAGE) reads `dtype_qk` (`int8` / `fp8_e4m3`),
|
||||||
|
`q_block_size`, and `k_block_size`; `FLASHINFER_ATTN` reads `dtype_qk` (`float16` / `bfloat16`),
|
||||||
|
`dtype_vo`, and `flashinfer_backend` (see its section). A `dtype_qk` from the wrong set raises.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vllm-omni serve Wan-AI/Wan2.2-T2V-A14B-Diffusers \
|
||||||
|
--diffusion-attention-config '{"default": {"backend": "TRTLLM_ATTN",
|
||||||
|
"quant": {"dtype_qk": "fp8_e4m3", "q_block_size": 1, "k_block_size": 16}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
from vllm_omni.diffusion.data import AttentionConfig, AttentionSpec, AttnQuantSpec
|
||||||
|
|
||||||
|
AttentionConfig(
|
||||||
|
default=AttentionSpec(
|
||||||
|
backend="TRTLLM_ATTN",
|
||||||
|
quant=AttnQuantSpec(dtype_qk="fp8_e4m3", q_block_size=1, k_block_size=16),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Requirements.** Needs FlashInfer ≥ 0.6.16rc1. Kernel
|
||||||
|
availability is arch-dependent: `fp8_e4m3` QK has kernels on both **SM100** (B200) and **SM103**
|
||||||
|
(B300); `int8` QK kernels are compiled for **SM100 only**.
|
||||||
|
|
||||||
|
## RAINFUSION_ATTN Backend and Block-Sparse Video Attention
|
||||||
|
|
||||||
|
`RAINFUSION_ATTN` runs MindIE-SD's RainFusion (`rf_v2`) kernel on Ascend NPU. It pools each
|
||||||
|
128-token block of keys, ranks them per query block, and computes attention against only the
|
||||||
|
top-scoring ones. Video tokens are rearranged into `(t, h, w)` order first, so the kept blocks are
|
||||||
|
spatiotemporal neighbours rather than arbitrary rows of the packed sequence.
|
||||||
|
|
||||||
|
Only the video segment is sparsified. The prefix rows (text, visual conditions, audio) and the
|
||||||
|
first-frame blocks are always kept dense, which is why the realized sparsity is lower than the
|
||||||
|
nominal `sparsity` you configure. Anything the kernel cannot handle — a warmup denoise step, an
|
||||||
|
exempt layer, a sequence with no published video geometry, a video segment under 32 blocks —
|
||||||
|
delegates to `FLASH_ATTN`, so a model can select this backend unconditionally.
|
||||||
|
|
||||||
|
Enable it through the typed `block_sparse` block on the attention spec, shared by every
|
||||||
|
block-sparse backend:
|
||||||
|
|
||||||
|
| Key | Valid values | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `sparsity` | finite, `[0, 1]` | Nominal fraction of key blocks dropped per query block. `0` disables sparsity. Defaults to `0.8`. |
|
||||||
|
| `start_step` | `≥ 0` | Keep the first N denoise steps dense. Layout is decided early, so these steps dominate structural fidelity. |
|
||||||
|
| `skip_layers` | index selector, e.g. `"0-3,38"` | DiT blocks that always stay dense. |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vllm-omni serve MiniMaxAI/MiniMax-H3 \
|
||||||
|
--diffusion-attention-config '{"default": {"backend": "RAINFUSION_ATTN",
|
||||||
|
"block_sparse": {"sparsity": 0.8, "start_step": 0}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Programmatically the same block is a typed `BlockSparseSpec` (values validated at construction):
|
||||||
|
|
||||||
|
```python
|
||||||
|
from vllm_omni.diffusion.data import AttentionConfig, AttentionSpec, BlockSparseSpec
|
||||||
|
|
||||||
|
AttentionConfig(
|
||||||
|
default=AttentionSpec(
|
||||||
|
backend="RAINFUSION_ATTN",
|
||||||
|
block_sparse=BlockSparseSpec(sparsity=0.8, start_step=0, skip_layers="0-1"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tune in the order `start_step` → `sparsity` → `skip_layers`.** Raise `start_step` first: it is the
|
||||||
|
cheapest way to recover structure, because the early high-noise steps decide global layout while the
|
||||||
|
later steps only refine texture. Then walk `sparsity` up to your quality bar. Reach for
|
||||||
|
`skip_layers` last, once an A/B against dense at the same seed points at specific blocks.
|
||||||
|
|
||||||
|
Requires Ascend NPU with `mindiesd`; selecting it on any other platform raises. It is also
|
||||||
|
incompatible with ring sequence parallelism, since `rf_v2` needs the whole key sequence to rank
|
||||||
|
blocks — use Ulysses SP (`ring_degree=1`).
|
||||||
|
|
||||||
|
### Which geometries run sparse
|
||||||
|
|
||||||
|
MindIE-SD rf_v2 handles arbitrary video grids by spatially rearranging video first and, when
|
||||||
|
necessary, promoting a real-video suffix to its always-kept prefix segment. It never pads: rf_v2
|
||||||
|
does not consume an attention mask for padded keys. The remaining video rows stay sparse, while the
|
||||||
|
promoted rows remain visible to every query and key.
|
||||||
|
|
||||||
|
For example, MiniMax-H3 at 1344x768 has grid `(62, 24, 42)`: its 62496 video rows leave a 32-row
|
||||||
|
128-block residue, and its two non-8-wide latent columns form a 2928-row rearranged suffix.
|
||||||
|
MindIE-SD promotes 2976 rows, leaving 59520 sparse rows (`465 × 128`). This keeps the block mask
|
||||||
|
and kernel tiling aligned, at the cost of retaining more blocks.
|
||||||
|
|
||||||
|
8x8-aligned spatial grids remain preferable for performance. A latent `h` or `w` not divisible by 8
|
||||||
|
creates an always-kept residual suffix and lowers realized sparsity. "Multiple of 8" on the latent
|
||||||
|
grid means **width and height that are multiples of 256**. This path requires a MindIE-SD release
|
||||||
|
that implements protected video tails for rf_v2.
|
||||||
|
|
||||||
|
## End-to-End Benchmark (BF16, sm_120 RTX Pro 6000 Blackwell)
|
||||||
|
|
||||||
|
Same prompt and seed across runs. `Total generation time` from `text_to_video.py` / `text_to_image.py`.
|
||||||
|
|
||||||
|
| Model | Shape | TORCH_SDPA | CUDNN_ATTN | FLASHINFER_ATTN |
|
||||||
|
|---|---|---|---|---|
|
||||||
| HunyuanVideo-1.5 (T2V) | 480p / 33f / 50 steps | 147.05 s | **73.02 s** | 127.84 s |
|
| HunyuanVideo-1.5 (T2V) | 480p / 33f / 50 steps | 147.05 s | **73.02 s** | 127.84 s |
|
||||||
| Wan 2.2 14B (T2V) | 480p / 33f / 40 steps | 117.75 s | 117.17 s | **115.07 s** |
|
| Wan 2.2 14B (T2V) | 480p / 33f / 40 steps | 117.75 s | 117.17 s | **115.07 s** |
|
||||||
| Qwen-Image (T2I) | 1024² / 50 steps | 17.41 s | **15.14 s** | 16.02 s |
|
| Qwen-Image (T2I) | 1024² / 50 steps | 17.41 s | **15.14 s** | 16.02 s |
|
||||||
| FLUX.2-dev (T2I) | 1024² / 50 steps, TP=2 | 53.62 s | **53.30 s** | 54.94 s |
|
| FLUX.2-dev (T2I) | 1024² / 50 steps, TP=2 | 53.62 s | **53.30 s** | 54.94 s |
|
||||||
|
|
||||||
Mask-heavy DiTs favored `CUDNN_ATTN`; lighter-mask or TP-saturated workloads
|
Pattern: mask-heavy DiTs (HV-1.5, Qwen-Image) favor `CUDNN_ATTN`; lighter-mask DiTs and TP-saturated configs (Wan 2.2, FLUX.2 TP=2) tie within noise.
|
||||||
were close enough that users should benchmark their exact model and shape.
|
|
||||||
|
|
||||||
## Compatibility anchors
|
## Known Limitations
|
||||||
|
|
||||||
The following headings preserve links to sections that moved into dedicated
|
### LTX-2.0: `CUDNN_ATTN` crashes under torch.compile
|
||||||
guides.
|
|
||||||
|
|
||||||
## TRTLLM_ATTN Backend and Skip-Softmax
|
LTX-2's audio attention has a symbolic head_dim under torch.compile tracing. cuDNN's SDPA backend selector rejects symbolic dims and Dynamo aborts compilation. Tracked in [#3121](https://github.com/vllm-project/vllm-omni/issues/3121).
|
||||||
|
|
||||||
See [TRTLLM Attention](attention_backends/trtllm.md#skip-softmax).
|
**Workaround**: explicitly select `FLASHINFER_ATTN` or `TORCH_SDPA` for LTX-2.0:
|
||||||
|
|
||||||
## TRTLLM_ATTN SAGE Quantization
|
```bash
|
||||||
|
DIFFUSION_ATTENTION_BACKEND=FLASHINFER_ATTN python examples/offline_inference/text_to_video/text_to_video.py \
|
||||||
|
--model Lightricks/LTX-2 ...
|
||||||
|
```
|
||||||
|
|
||||||
See [TRTLLM Attention](attention_backends/trtllm.md#sage-quantization).
|
### FlashAttention-4 on Blackwell
|
||||||
|
|
||||||
## RAINFUSION_ATTN Backend and Block-Sparse Video Attention
|
Install the optional CUDA 13 extra to use the CuTe-based FA4 path:
|
||||||
|
|
||||||
See [RainFusion](attention_backends/rainfusion.md).
|
```bash
|
||||||
|
pip install 'vllm-omni[fa4]'
|
||||||
|
```
|
||||||
|
|
||||||
|
`FLASH_ATTN` prefers `flash_attn.cute` on Blackwell and falls back to FA3/FA2
|
||||||
|
when it is unavailable. Version `4.0.0b18` is required; earlier beta
|
||||||
|
wheels had known JIT failures on Blackwell.
|
||||||
|
|
||||||
|
## Choosing a Backend Manually
|
||||||
|
|
||||||
|
### When to override the default
|
||||||
|
|
||||||
|
- **Quality validation**: compare a new backend against `TORCH_SDPA` as the reference, since SDPA's default dispatcher is the most extensively tested.
|
||||||
|
- **Lossy speedup hunting**: try `SAGE_ATTN` (INT8 quantized) on diffusion outputs — typically indistinguishable visually but always validate.
|
||||||
|
- **Workaround for known issues**: see Known Limitations above.
|
||||||
|
|
||||||
|
### Verifying which backend is in use
|
||||||
|
|
||||||
|
The startup log prints one of:
|
||||||
|
|
||||||
|
```
|
||||||
|
Using diffusion attention backend 'CUDNN_ATTN' # explicit override
|
||||||
|
Defaulting to diffusion attention backend CUDNN_ATTN ... # auto-route
|
||||||
|
Defaulting to diffusion attention backend SDPA # nothing else available
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't see one of these, the model didn't reach diffusion stage init — check earlier logs for failures.
|
||||||
|
|
||||||
## SageAttention Installation
|
## SageAttention Installation
|
||||||
|
|
||||||
See [SageAttention](attention_backends/sage.md#installation).
|
vLLM-Omni expects SageAttention to be installed into the same Python environment as vLLM-Omni.
|
||||||
|
|
||||||
|
Build from source:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/thu-ml/SageAttention.git
|
||||||
|
cd SageAttention
|
||||||
|
|
||||||
|
export EXT_PARALLEL=4 NVCC_APPEND_FLAGS="--threads 8" MAX_JOBS=32
|
||||||
|
pip install . --no-build-isolation
|
||||||
|
```
|
||||||
|
|
||||||
|
Quick check:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -c "import sageattention; print(sageattention.__file__)"
|
||||||
|
```
|
||||||
|
|
||||||
## SageAttention3 Installation
|
## SageAttention3 Installation
|
||||||
|
|
||||||
See [SageAttention](attention_backends/sage.md#sageattention3-installation).
|
vLLM-Omni expects SageAttention3 to be installed into the same Python environment as vLLM-Omni.
|
||||||
|
|
||||||
|
Build from source:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/thu-ml/SageAttention.git
|
||||||
|
cd SageAttention/sageattention3_blackwell
|
||||||
|
python setup.py install
|
||||||
|
```
|
||||||
|
|
||||||
|
Quick check:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -c "import sageattn3; print(sageattn3.__file__)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `SAGE_ATTN_3` is only selected on CUDA when `sageattn3` is importable and the GPU is Blackwell-class.
|
||||||
|
- SageAttention3's Blackwell kernel assumes `Hq == Hkv`. In vLLM-Omni, GQA/MQA diffusion requests fall back to PyTorch SDPA for correctness.
|
||||||
|
|
||||||
## HuggingFace Kernels Hub Backends
|
## HuggingFace Kernels Hub Backends
|
||||||
|
|
||||||
See [Hugging Face Hub Backends](attention_backends/huggingface_hub.md).
|
To achieve perfect numerical consistency between **training** (typically using HuggingFace Diffusers with Hub kernels) and **serving/rollout** (using vLLM-Omni), you can use the Hub-based attention backends. This eliminates numerical drift / sampling divergence caused by executing different local kernel versions during rollout.
|
||||||
|
|
||||||
|
The following backend options are supported:
|
||||||
|
- `FLASH_ATTN_HUB` (HuggingFace `kernels-community/flash-attn2`)
|
||||||
|
- `FLASH_ATTN_3_HUB` (HuggingFace `kernels-community/flash-attn3`, Hopper sm_90+ only)
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
To use these backends, you must install the `kernels` library:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install kernels==0.14.1
|
||||||
|
```
|
||||||
|
|
||||||
|
If the `kernels` library is not available in the environment, vLLM-Omni will log a warning and fall back gracefully to the corresponding local backend implementations (`FLASH_ATTN`). On CUDA GPUs below Hopper (compute capability < 9.0), `FLASH_ATTN_3_HUB` falls back to `FLASH_ATTN_HUB`.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Select a Hub backend using the global CLI flag or environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Environment variable
|
||||||
|
export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN_HUB
|
||||||
|
|
||||||
|
# CLI flag
|
||||||
|
vllm-omni serve <model> --diffusion-attention-backend FLASH_ATTN_HUB
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Default (auto-route)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python examples/offline_inference/text_to_video/text_to_video.py \
|
||||||
|
--model hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v \
|
||||||
|
--prompt "A dog running across a field of golden wheat." \
|
||||||
|
--height 480 --width 832 --num-frames 33 \
|
||||||
|
--num-inference-steps 50 --seed 42 --guidance-scale 6.0 \
|
||||||
|
--output hv15.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
On Blackwell this picks `CUDNN_ATTN` automatically. Check the log for the `Defaulting to ...` line.
|
||||||
|
|
||||||
|
### Explicit backend selection
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DIFFUSION_ATTENTION_BACKEND=FLASHINFER_ATTN python examples/offline_inference/text_to_video/text_to_video.py \
|
||||||
|
--model Lightricks/LTX-2 \
|
||||||
|
--prompt "A dog running across a field of golden wheat." \
|
||||||
|
--height 480 --width 832 --num-frames 33 \
|
||||||
|
--num-inference-steps 40 --seed 42 --guidance-scale 4.0 \
|
||||||
|
--output ltx2.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
### SageAttention (lossy)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DIFFUSION_ATTENTION_BACKEND=SAGE_ATTN python examples/offline_inference/text_to_video/text_to_video.py \
|
||||||
|
--model hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v \
|
||||||
|
--prompt "A dog running across a field of golden wheat." \
|
||||||
|
--height 480 --width 832 --num-frames 33 \
|
||||||
|
--num-inference-steps 30 --seed 42 --guidance-scale 6.0 \
|
||||||
|
--tensor-parallel-size 2 \
|
||||||
|
--output hv15_sage.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
Example: Wan2.2 TI2V 5B
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DIFFUSION_ATTENTION_BACKEND=SAGE_ATTN python examples/offline_inference/text_to_video/text_to_video.py \
|
||||||
|
--model Wan-AI/Wan2.2-TI2V-5B-Diffusers \
|
||||||
|
--prompt "A dog running across a field of golden wheat." \
|
||||||
|
--height 704 --width 1280 --num-frames 49 \
|
||||||
|
--num-inference-steps 30 --seed 42 --guidance-scale 5.0 \
|
||||||
|
--tensor-parallel-size 2 \
|
||||||
|
--output outputs/wan22_sage.mp4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enable SageAttention3
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DIFFUSION_ATTENTION_BACKEND=SAGE_ATTN_3 python examples/offline_inference/text_to_video/text_to_video.py \
|
||||||
|
--model hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v \
|
||||||
|
--prompt "A dog running across a field of golden wheat." \
|
||||||
|
--height 480 --width 832 --num-frames 33 \
|
||||||
|
--num-inference-steps 30 --seed 42 --guidance-scale 6.0 \
|
||||||
|
--tensor-parallel-size 2 \
|
||||||
|
--output outputs/hv15_sage3.mp4
|
||||||
|
```
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
# Dense Attention Backends
|
|
||||||
|
|
||||||
Use these backends when you want dense attention without backend-specific
|
|
||||||
sparsity. Start with `TORCH_SDPA` for correctness comparisons, then benchmark
|
|
||||||
the fastest compatible kernel for your model, shape, and hardware.
|
|
||||||
|
|
||||||
For selection precedence and per-role configuration, see the
|
|
||||||
[attention backend overview](../attention_backends.md).
|
|
||||||
|
|
||||||
## `TORCH_SDPA`
|
|
||||||
|
|
||||||
`TORCH_SDPA` calls PyTorch `scaled_dot_product_attention` and lets PyTorch's
|
|
||||||
dispatcher choose the implementation. It is always available and is the most
|
|
||||||
conservative reference when validating another backend.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm-omni serve <model> --diffusion-attention-backend TORCH_SDPA
|
|
||||||
```
|
|
||||||
|
|
||||||
## `FLASH_ATTN`
|
|
||||||
|
|
||||||
`FLASH_ATTN` uses the installed FlashAttention implementation. On Blackwell it
|
|
||||||
prefers the CuTe-based FlashAttention 4 path and falls back to
|
|
||||||
FlashAttention 3 or 2 when that path is unavailable. On Hopper, Ada, and
|
|
||||||
Ampere it is the preferred automatic route when a compatible package is
|
|
||||||
installed.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm-omni serve <model> --diffusion-attention-backend FLASH_ATTN
|
|
||||||
```
|
|
||||||
|
|
||||||
### FlashAttention 4 on Blackwell
|
|
||||||
|
|
||||||
Install the optional CUDA 13 extra:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install 'vllm-omni[fa4]'
|
|
||||||
```
|
|
||||||
|
|
||||||
Version `4.0.0b18` is required; earlier beta wheels had known JIT failures on
|
|
||||||
Blackwell. If the CuTe path is unavailable, the backend falls back to the
|
|
||||||
compatible FlashAttention 3 or 2 path.
|
|
||||||
|
|
||||||
## `CUDNN_ATTN`
|
|
||||||
|
|
||||||
`CUDNN_ATTN` pins PyTorch SDPA to `CUDNN_ATTENTION`. It is particularly useful
|
|
||||||
for mask-heavy DiTs and is automatically preferred on Blackwell when cuDNN
|
|
||||||
9.5 or newer is available and the higher-priority TRTLLM route is not
|
|
||||||
compatible.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm-omni serve <model> --diffusion-attention-backend CUDNN_ATTN
|
|
||||||
```
|
|
||||||
|
|
||||||
### LTX-2.0 limitation
|
|
||||||
|
|
||||||
LTX-2 audio attention has a symbolic head dimension during `torch.compile`
|
|
||||||
tracing. The cuDNN SDPA selector rejects that symbolic dimension and Dynamo
|
|
||||||
aborts compilation. This is tracked in
|
|
||||||
[issue #3121](https://github.com/vllm-project/vllm-omni/issues/3121).
|
|
||||||
|
|
||||||
Use `FLASHINFER_ATTN` or `TORCH_SDPA` as a workaround:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
DIFFUSION_ATTENTION_BACKEND=FLASHINFER_ATTN \
|
|
||||||
python examples/offline_inference/text_to_video/text_to_video.py \
|
|
||||||
--model Lightricks/LTX-2 ...
|
|
||||||
```
|
|
||||||
|
|
||||||
## `FLASHINFER_ATTN`
|
|
||||||
|
|
||||||
`FLASHINFER_ATTN` uses FlashInfer's batch-prefill wrapper. It is an explicit
|
|
||||||
option on CUDA platforms and an automatic Blackwell fallback when FlashInfer
|
|
||||||
is installed but cuDNN is too old for `CUDNN_ATTN`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm-omni serve <model> --diffusion-attention-backend FLASHINFER_ATTN
|
|
||||||
```
|
|
||||||
|
|
||||||
### FlashInfer quantized attention
|
|
||||||
|
|
||||||
The backend accepts an `AttentionSpec.quant` block. For QK16/V8, keep Q and K
|
|
||||||
in FP16 or BF16 and use FP8 E4M3 for V:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from vllm_omni.diffusion.data import (
|
|
||||||
AttentionConfig,
|
|
||||||
AttentionSpec,
|
|
||||||
AttnQuantSpec,
|
|
||||||
OmniDiffusionConfig,
|
|
||||||
)
|
|
||||||
|
|
||||||
config = OmniDiffusionConfig(
|
|
||||||
diffusion_attention_config=AttentionConfig(
|
|
||||||
default=AttentionSpec(
|
|
||||||
backend="FLASHINFER_ATTN",
|
|
||||||
quant=AttnQuantSpec(
|
|
||||||
dtype_qk="bfloat16",
|
|
||||||
dtype_vo="fp8_e4m3",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
...,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
`dtype_qk` controls Q and K; `dtype_vo` controls V. Mixed-dtype
|
|
||||||
configurations require FlashInfer 0.6.16rc1 or newer. The shared quantization
|
|
||||||
schema is also consumed by TRTLLM, but each backend validates its own allowed
|
|
||||||
fields and values; see [TRTLLM SAGE quantization](trtllm.md#sage-quantization).
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# Hugging Face Hub Attention Backends
|
|
||||||
|
|
||||||
Hub backends load the same published kernels commonly used with Hugging Face
|
|
||||||
Diffusers. They are useful when training and serving must use matching kernel
|
|
||||||
implementations to reduce numerical drift and sampling divergence.
|
|
||||||
|
|
||||||
| Backend | Kernel | Platform |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `FLASH_ATTN_HUB` | `kernels-community/flash-attn2` | Compatible CUDA GPUs |
|
|
||||||
| `FLASH_ATTN_3_HUB` | `kernels-community/flash-attn3` | Hopper sm_90 or newer |
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install kernels==0.14.1
|
|
||||||
```
|
|
||||||
|
|
||||||
If `kernels` is unavailable, vLLM-Omni warns and falls back to the
|
|
||||||
corresponding local FlashAttention implementation. On CUDA devices older than
|
|
||||||
Hopper, `FLASH_ATTN_3_HUB` falls back to `FLASH_ATTN_HUB`.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN_HUB
|
|
||||||
|
|
||||||
# Equivalent CLI selection
|
|
||||||
vllm-omni serve <model> --diffusion-attention-backend FLASH_ATTN_HUB
|
|
||||||
```
|
|
||||||
|
|
||||||
For per-role kernel alignment, configure the Hub backend on only the relevant
|
|
||||||
role; see the [attention backend overview](../attention_backends.md#per-role-configuration).
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
# RainFusion Attention
|
|
||||||
|
|
||||||
`RAINFUSION_ATTN` runs MindIE-SD RainFusion (`rf_v2`) block-sparse video
|
|
||||||
attention on Ascend NPU. It pools 128-token key blocks, ranks them per query
|
|
||||||
block, and attends to the highest-scoring blocks after arranging video tokens
|
|
||||||
in `(t, h, w)` order.
|
|
||||||
|
|
||||||
Only the video segment is sparse. Prefix rows such as text, visual conditions,
|
|
||||||
and audio, plus first-frame blocks, remain dense. Unsupported calls—including
|
|
||||||
warmup steps, skipped layers, missing video geometry, and video segments below
|
|
||||||
32 blocks—delegate to `FLASH_ATTN`, so compatible models can select RainFusion
|
|
||||||
globally.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
| Key | Valid values | Meaning |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `sparsity` | finite, `[0, 1]` | Nominal dropped-key-block fraction; default `0.8`; `0` disables sparsity |
|
|
||||||
| `start_step` | integer, `>= 0` | Number of early denoise steps kept dense |
|
|
||||||
| `skip_layers` | selector such as `"0-3,38"` | DiT blocks kept dense |
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm-omni serve MiniMaxAI/MiniMax-H3 \
|
|
||||||
--diffusion-attention-config '{"default":{"backend":"RAINFUSION_ATTN",\
|
|
||||||
"block_sparse":{"sparsity":0.8,"start_step":0}}}'
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
from vllm_omni.diffusion.data import AttentionConfig, AttentionSpec, BlockSparseSpec
|
|
||||||
|
|
||||||
config = AttentionConfig(
|
|
||||||
default=AttentionSpec(
|
|
||||||
backend="RAINFUSION_ATTN",
|
|
||||||
block_sparse=BlockSparseSpec(
|
|
||||||
sparsity=0.8,
|
|
||||||
start_step=0,
|
|
||||||
skip_layers="0-1",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Tune `start_step`, then `sparsity`, then `skip_layers`. Increasing the dense
|
|
||||||
early-step window is usually the cheapest way to recover global structure;
|
|
||||||
use layer exclusions only after a same-seed dense comparison identifies
|
|
||||||
sensitive blocks.
|
|
||||||
|
|
||||||
## Requirements and compatibility
|
|
||||||
|
|
||||||
RainFusion requires Ascend NPU and `mindiesd`. Selecting it on another
|
|
||||||
platform raises. It is incompatible with ring sequence parallelism because
|
|
||||||
the kernel needs the complete key sequence for ranking; use Ulysses sequence
|
|
||||||
parallelism with `ring_degree=1`.
|
|
||||||
|
|
||||||
## Geometry handling
|
|
||||||
|
|
||||||
RainFusion handles arbitrary video grids by rearranging video spatially and,
|
|
||||||
when necessary, promoting a real-video suffix to the always-kept prefix. It
|
|
||||||
does not pad because `rf_v2` does not consume a padding attention mask.
|
|
||||||
|
|
||||||
For MiniMax-H3 at 1344x768, grid `(62, 24, 42)` produces 62,496 video rows.
|
|
||||||
The implementation promotes 2,976 rows and leaves 59,520 sparse rows
|
|
||||||
(`465 x 128`) so the mask and kernel tiling remain aligned.
|
|
||||||
|
|
||||||
Spatial grids aligned to 8x8 are still preferable. A latent height or width
|
|
||||||
not divisible by 8 creates an always-kept suffix and reduces realized
|
|
||||||
sparsity. At the image level, that means width and height divisible by 256.
|
|
||||||
Protected video tails require a compatible MindIE-SD release.
|
|
||||||
|
|
||||||
For common configuration and selector behavior, see the
|
|
||||||
[attention backend overview](../attention_backends.md) and the
|
|
||||||
[backend selection design](../../../design/feature/attention_backend_selection.md).
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
# SageAttention
|
|
||||||
|
|
||||||
SageAttention backends provide lossy low-precision attention for diffusion
|
|
||||||
models. Validate output quality against `TORCH_SDPA` at the same seed before
|
|
||||||
using either backend in production.
|
|
||||||
|
|
||||||
## `SAGE_ATTN`
|
|
||||||
|
|
||||||
`SAGE_ATTN` uses SageAttention 2.2 with INT8-quantized attention and FP16
|
|
||||||
accumulation.
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
Install SageAttention into the same environment as vLLM-Omni:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/thu-ml/SageAttention.git
|
|
||||||
cd SageAttention
|
|
||||||
export EXT_PARALLEL=4 NVCC_APPEND_FLAGS="--threads 8" MAX_JOBS=32
|
|
||||||
pip install . --no-build-isolation
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify the installation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python -c "import sageattention; print(sageattention.__file__)"
|
|
||||||
```
|
|
||||||
|
|
||||||
Select it globally:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm-omni serve <model> --diffusion-attention-backend SAGE_ATTN
|
|
||||||
```
|
|
||||||
|
|
||||||
## `SAGE_ATTN_3`
|
|
||||||
|
|
||||||
`SAGE_ATTN_3` uses the SageAttention3 Blackwell implementation.
|
|
||||||
|
|
||||||
### SageAttention3 installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/thu-ml/SageAttention.git
|
|
||||||
cd SageAttention/sageattention3_blackwell
|
|
||||||
python setup.py install
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify the installation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python -c "import sageattn3; print(sageattn3.__file__)"
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm-omni serve <model> --diffusion-attention-backend SAGE_ATTN_3
|
|
||||||
```
|
|
||||||
|
|
||||||
`SAGE_ATTN_3` requires CUDA, an importable `sageattn3`, and a Blackwell-class
|
|
||||||
GPU. Its kernel assumes the query-head count equals the key/value-head count.
|
|
||||||
GQA and MQA diffusion calls therefore fall back to PyTorch SDPA for
|
|
||||||
correctness.
|
|
||||||
|
|
||||||
For common configuration and platform routing, see the
|
|
||||||
[attention backend overview](../attention_backends.md).
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
# TRTLLM Attention
|
|
||||||
|
|
||||||
`TRTLLM_ATTN` runs FlashInfer's trtllm-gen FMHA kernels. It supports dense
|
|
||||||
BF16 attention plus optional Skip-Softmax sparsity or SAGE quantization.
|
|
||||||
|
|
||||||
For common selection and per-role configuration, see the
|
|
||||||
[attention backend overview](../attention_backends.md).
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
Selecting `TRTLLM_ATTN` requires:
|
|
||||||
|
|
||||||
- datacenter Blackwell, sm_100 or sm_103;
|
|
||||||
- `head_dim=128`;
|
|
||||||
- FlashInfer; and
|
|
||||||
- a model-declared packed or mask-free compatible path.
|
|
||||||
|
|
||||||
It is not supported on workstation Blackwell (sm_120/sm_121). An incompatible
|
|
||||||
explicit selection raises instead of silently changing the backend.
|
|
||||||
|
|
||||||
## Dense mode
|
|
||||||
|
|
||||||
Select `TRTLLM_ATTN` without `skip_softmax` or `quant` to run dense BF16:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm-omni serve <model> --diffusion-attention-backend TRTLLM_ATTN
|
|
||||||
```
|
|
||||||
|
|
||||||
## Skip-Softmax
|
|
||||||
|
|
||||||
Skip-Softmax trades some fidelity for speed by skipping low-contribution
|
|
||||||
attention work. See the [feature design](../../../design/feature/skip_softmax.md)
|
|
||||||
for the algorithm and implementation contract.
|
|
||||||
|
|
||||||
| Key | Valid values | Meaning |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `target_sparsity` | finite, `[0, 1]` | Point on a model-calibrated curve |
|
|
||||||
| `threshold` | finite, `>= 0` | Direct threshold; mutually exclusive with `target_sparsity` |
|
|
||||||
| `disabled_until_timestep` | finite, `[0, 1]` | Keep early high-noise steps dense until normalized `t <= D` |
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm-omni serve Wan-AI/Wan2.2-T2V-A14B-Diffusers \
|
|
||||||
--diffusion-attention-config '{"default":{"backend":"TRTLLM_ATTN",\
|
|
||||||
"skip_softmax":{"target_sparsity":0.65,"disabled_until_timestep":0.86}}}'
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
from vllm_omni.diffusion.data import AttentionConfig, AttentionSpec, SkipSoftmaxSpec
|
|
||||||
|
|
||||||
config = AttentionConfig(
|
|
||||||
default=AttentionSpec(
|
|
||||||
backend="TRTLLM_ATTN",
|
|
||||||
skip_softmax=SkipSoftmaxSpec(
|
|
||||||
target_sparsity=0.65,
|
|
||||||
disabled_until_timestep=0.86,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Start with `target_sparsity=0.65` and `disabled_until_timestep=0.86`. Increase
|
|
||||||
sparsity only after comparing quality against dense output at the same seed.
|
|
||||||
The benefit grows with sequence length because only attention is accelerated.
|
|
||||||
|
|
||||||
## SAGE quantization
|
|
||||||
|
|
||||||
TRTLLM SAGE quantizes Q/K per block and V per channel so both attention
|
|
||||||
matrix multiplications use low precision.
|
|
||||||
|
|
||||||
| Key | Valid values | Meaning |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `dtype_qk` | `int8`, `fp8_e4m3` | Q/K quantization dtype; absent means dense |
|
|
||||||
| `q_block_size` | `1`, `4`, `16` | Q scale block size; default `1` |
|
|
||||||
| `k_block_size` | `1`, `4`, `16` | K scale block size; default `16` |
|
|
||||||
|
|
||||||
V is always quantized per channel to FP8 E4M3 and K-smoothing is internal.
|
|
||||||
Only block sizes with compiled kernels are accepted.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm-omni serve Wan-AI/Wan2.2-T2V-A14B-Diffusers \
|
|
||||||
--diffusion-attention-config '{"default":{"backend":"TRTLLM_ATTN",\
|
|
||||||
"quant":{"dtype_qk":"fp8_e4m3","q_block_size":1,"k_block_size":16}}}'
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
from vllm_omni.diffusion.data import AttentionConfig, AttentionSpec, AttnQuantSpec
|
|
||||||
|
|
||||||
config = AttentionConfig(
|
|
||||||
default=AttentionSpec(
|
|
||||||
backend="TRTLLM_ATTN",
|
|
||||||
quant=AttnQuantSpec(
|
|
||||||
dtype_qk="fp8_e4m3",
|
|
||||||
q_block_size=1,
|
|
||||||
k_block_size=16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
This path requires FlashInfer 0.6.16rc1 or newer. FP8 Q/K kernels are
|
|
||||||
available on sm_100 and sm_103; INT8 Q/K kernels are compiled for sm_100 only.
|
|
||||||
The shared `AttnQuantSpec` is backend typed: values intended for
|
|
||||||
`FLASHINFER_ATTN` are rejected here and vice versa.
|
|
||||||
@@ -1,75 +1,389 @@
|
|||||||
# CPU Offloading for Diffusion Models
|
# CPU Offloading for Diffusion Models
|
||||||
|
|
||||||
vLLM-Omni provides three CPU-offload strategies for diffusion models. Choose
|
## Overview
|
||||||
the coarsest strategy that meets your memory target: finer-grained streaming
|
|
||||||
saves more device memory but adds more transfer and setup overhead.
|
|
||||||
|
|
||||||
For the shared factory, discovery, and lifecycle contract, see the
|
vLLM-Omni provides two offloading strategies to reduce GPU memory usage for diffusion models:
|
||||||
[CPU Offloading design](../../design/feature/offloader/README.md).
|
|
||||||
|
|
||||||
## Choose a strategy
|
1. **Model-level (Sequential) Offloading**: Mutual exclusion between DiT model and encoder - only one is on GPU at a time.
|
||||||
|
2. **Layerwise (Blockwise) Offloading**: Keeps only one transformer block on GPU at a time with compute-memory overlap.
|
||||||
|
|
||||||
| Strategy | Device residency | Parallel scope | Guide |
|
Both strategies use pinned memory for faster CPU-GPU transfers. The strategies are **mutually exclusive** for now - if both are enabled, layerwise takes priority.
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| Model-level (sequential) | One pipeline component group at a time | Single device | [Model-Level Offloading](offloader/cpu_offload.md) |
|
|
||||||
| Layerwise (blockwise) | One transformer block, with next-block prefetch | Single device | [Layerwise Offloading](offloader/layerwise_offload.md) |
|
|
||||||
| Distributed layerwise | Fixed two-block device buffer; optional host-weight sharding and AllGather | Multiple GPU/NPU ranks | [Distributed Layerwise Offloading](offloader/distributed_layerwise_offload.md) |
|
|
||||||
|
|
||||||
All strategies use pinned host memory for faster transfers where applicable.
|
|
||||||
Configuration priority is:
|
|
||||||
|
|
||||||
1. Distributed layerwise offloading.
|
|
||||||
2. Layerwise offloading.
|
|
||||||
3. Model-level offloading.
|
|
||||||
|
|
||||||
Treat the flags as mutually exclusive. If more than one is enabled, the
|
|
||||||
higher-priority strategy is selected.
|
|
||||||
|
|
||||||
## Quick selection
|
|
||||||
|
|
||||||
- Use [model-level offloading](offloader/cpu_offload.md) when swapping whole
|
|
||||||
encoders and DiTs is enough to fit and phase-boundary transfers are
|
|
||||||
acceptable.
|
|
||||||
- Use [layerwise offloading](offloader/layerwise_offload.md) for compute-heavy video
|
|
||||||
DiTs where block transfers can overlap computation.
|
|
||||||
- Use [distributed layerwise offloading](offloader/distributed_layerwise_offload.md)
|
|
||||||
when a multi-rank deployment also needs bounded device residency and,
|
|
||||||
optionally, sharded host weights.
|
|
||||||
|
|
||||||
## Supported models
|
|
||||||
|
|
||||||
| Architecture | Example models | DiT class | Model-level | Layerwise | Distributed layerwise | Layerwise block attributes |
|
|
||||||
| --- | --- | --- | :---: | :---: | :---: | --- |
|
|
||||||
| Flux2Pipeline | `black-forest-labs/FLUX.2-dev` | `Flux2Transformer2DModel` | yes | yes | — | `transformer_blocks`, `single_transformer_blocks` |
|
|
||||||
| LongCatImagePipeline | `meituan-longcat/LongCat-Image` | `LongCatImageTransformer2DModel` | — | yes | — | `transformer_blocks`, `single_transformer_blocks` |
|
|
||||||
| NextStep11Pipeline | `stepfun-ai/NextStep-1.1` | `NextStepModel` | — | yes | — | `layers` |
|
|
||||||
| OvisImagePipeline | `AIDC-AI/Ovis-Image-7B` | `OvisImageTransformer2DModel` | — | yes | — | `transformer` |
|
|
||||||
| QwenImagePipeline | `Qwen/Qwen-Image` | `QwenImageTransformer2DModel` | yes | yes | — | `transformer_blocks` |
|
|
||||||
| StableDiffusionXLPipeline | `stabilityai/stable-diffusion-xl-base-1.0` | `SDXLUNet2DConditionModel` | yes | yes | — | `down_blocks`, `up_blocks` |
|
|
||||||
| StableDiffusion3Pipeline | `stabilityai/stable-diffusion-3.5-medium` | `SD3Transformer2DModel` | — | yes | — | `transformer_blocks` |
|
|
||||||
| Wan22I2VPipeline | `Wan-AI/Wan2.2-I2V-A14B-Diffusers` | `WanTransformer3DModel` | yes | yes | — | `blocks` |
|
|
||||||
| Wan22Pipeline | `Wan-AI/Wan2.2-T2V-A14B-Diffusers` | `WanTransformer3DModel` | yes | yes | — | `blocks` |
|
|
||||||
| SoulXSingerPipeline / SoulXSingerSVCPipeline | `Soul-AILab/SoulX-Singer` | `DiffLlama` | yes | yes | — | `layers` |
|
|
||||||
| BagelPipeline | `ByteDance-Seed/BAGEL-7B-MoT` | `Qwen2MoTModel` | — | yes | — | `layers`, customized modules |
|
|
||||||
| Cosmos3OmniDiffusersPipeline | `nvidia/Cosmos3-Nano`, `nvidia/Cosmos3-Super` | `Cosmos3VFMTransformer`, `Cosmos3LanguageModel` | yes | yes | yes | `layers`, `gen_layers` |
|
|
||||||
|
|
||||||
Model-level support requires discoverable DiT and encoder components.
|
|
||||||
Layerwise support requires transformer block topology. Distributed support
|
|
||||||
reuses that topology but still requires validation for the model, checkpoint,
|
|
||||||
and parallel configuration.
|
|
||||||
|
|
||||||
## Compatibility anchors
|
|
||||||
|
|
||||||
These headings preserve links to sections that moved into dedicated guides.
|
|
||||||
|
|
||||||
## Model-level (Sequential) Offloading
|
## Model-level (Sequential) Offloading
|
||||||
|
|
||||||
See [Model-Level Offloading](offloader/cpu_offload.md).
|
### How It Works
|
||||||
|
|
||||||
|
Model-level offloading implements mutual exclusion between DiT transformer and encoder modules using pre forward hooks:
|
||||||
|
|
||||||
|
- **When encoders run**: DiT transformer is offloaded to CPU
|
||||||
|
- **When DiT runs**: Encoders are offloaded to CPU, if more than one dit models, only one loaded on GPU, others get offloaded to CPU.
|
||||||
|
- **VAE**: Stays resident on GPU
|
||||||
|
|
||||||
|
Before each module's forward pass, the hook automatically moves it to GPU while offloading the other module group to CPU. Transfers use pinned memory for speed.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
**Python API:**
|
||||||
|
```python
|
||||||
|
from vllm_omni import Omni
|
||||||
|
|
||||||
|
m = Omni(model="Wan-AI/Wan2.2-T2V-A14B-Diffusers", enable_cpu_offload=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
**CLI:**
|
||||||
|
```bash
|
||||||
|
vllm serve Wan-AI/Wan2.2-T2V-A14B-Diffusers --omni --enable-cpu-offload
|
||||||
|
```
|
||||||
|
|
||||||
|
### To Support a Model
|
||||||
|
|
||||||
|
Implement the `SupportsComponentDiscovery` protocol to declare which
|
||||||
|
submodules serve as pipeline components (used by offloading, HSDP
|
||||||
|
sharding, and other framework features):
|
||||||
|
|
||||||
|
```python
|
||||||
|
from typing import ClassVar
|
||||||
|
from vllm_omni.diffusion.models.interface import SupportsComponentDiscovery
|
||||||
|
|
||||||
|
class MyPipeline(nn.Module, SupportsComponentDiscovery):
|
||||||
|
_dit_modules: ClassVar[list[str]] = ["transformer"]
|
||||||
|
_encoder_modules: ClassVar[list[str]] = ["text_encoder", "vision_model"]
|
||||||
|
_vae_modules: ClassVar[list[str]] = ["vae"]
|
||||||
|
_resident_modules: ClassVar[list[str]] = [] # optional
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.transformer = ... # DiT — stays on GPU during denoising
|
||||||
|
self.text_encoder = ... # Encoder — offloaded to CPU during denoising
|
||||||
|
self.vision_model = ... # Encoder — offloaded to CPU during denoising
|
||||||
|
self.vae = ... # VAE — always on GPU
|
||||||
|
```
|
||||||
|
|
||||||
|
- `_dit_modules`: attribute names of denoising submodules (kept on GPU
|
||||||
|
during the diffusion loop).
|
||||||
|
- `_encoder_modules`: attribute names of encoder/vision submodules
|
||||||
|
(offloaded to CPU during the diffusion loop).
|
||||||
|
- `_vae_modules`: attribute names of VAE(s) (always kept on GPU, not
|
||||||
|
part of the mutual exclusion hooks).
|
||||||
|
- `_resident_modules`: attribute names of small submodules that must
|
||||||
|
stay on GPU during layerwise offloading (e.g. embedders, connectors).
|
||||||
|
Optional — defaults to `[]`.
|
||||||
|
|
||||||
|
All attribute names support dotted paths for nested submodules
|
||||||
|
(e.g. `"pipe.transformer"`, `"bagel.time_embedder"`).
|
||||||
|
|
||||||
|
Both DiT and encoder lists are needed because the offload hooks use
|
||||||
|
mutual exclusion: when one group runs, the other moves to CPU.
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
- Cold start latency increases
|
||||||
|
- Adds overhead from CPU-GPU transfers between encoder and denoising phases
|
||||||
|
- Support single GPU only for now
|
||||||
|
|
||||||
|
|
||||||
|
### Component offloading for split models (e.g. Cosmos3)
|
||||||
|
|
||||||
|
Some models split their transformer into mutually-exclusive *components* that run
|
||||||
|
in different phases of a single forward pass rather than as separate pipeline
|
||||||
|
components -- e.g. Cosmos3's understanding (reasoner) component runs once per
|
||||||
|
generation while the generation (generator) component runs every denoising step.
|
||||||
|
Such models have no separate text encoder to swap against, so the transformer
|
||||||
|
owns a small model-local offload path and wraps each phase with
|
||||||
|
`with self._offload_context(name):`
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Cosmos3VFMTransformer(nn.Module):
|
||||||
|
def forward(self, ...):
|
||||||
|
with self._offload_context("reasoner"):
|
||||||
|
... # understanding pass, runs once
|
||||||
|
with self._offload_context("generator"):
|
||||||
|
... # denoising pass, runs every step
|
||||||
|
```
|
||||||
|
|
||||||
|
Model-level offloading then keeps exactly one component GPU-resident at a time
|
||||||
|
(the other on CPU), reusing the same `SequentialOffloadHook` `.to()` movers. The
|
||||||
|
pipeline opts in by exposing `enable_omni_model_cpu_offload` (which drives the
|
||||||
|
transformer's `enable_model_cpu_offload` and pins the VAE). Layerwise offloading
|
||||||
|
works for these models too -- each component declares its own block container via
|
||||||
|
`_layerwise_offload_blocks_attrs`.
|
||||||
|
|
||||||
|
|
||||||
## Layerwise (Blockwise) Offloading
|
## Layerwise (Blockwise) Offloading
|
||||||
|
|
||||||
See [Layerwise Offloading](offloader/layerwise_offload.md).
|
### How It Works
|
||||||
|
|
||||||
|
Layerwise offloading keeps only one transformer block on GPU at a time.
|
||||||
|
|
||||||
|
As each block completes, the next block is prefetched to GPU while the current block is freed. The pre and forward hooks utilized by layerwise offloading apply a separate CUDA stream (`copy_stream`) to overlap weight transfer with computation, and retain flattened tensors in pinned CPU memory for block parameters re-materialization. Encoders, VAE, and non-block DiT modules (embeddings, norms) always stay on GPU.
|
||||||
|
|
||||||
|
**Execution Flow:**
|
||||||
|
|
||||||
|
| Block | Pre-forward Hook | Forward | Post-forward Hook |
|
||||||
|
|-------|------------------|---------|-------------------|
|
||||||
|
| block-0 | Prefetch block-1 (async) | Compute block-0 | Free block-0 |
|
||||||
|
| block-1 | Prefetch block-2 (async) | Compute block-1 | Free block-1 |
|
||||||
|
| ... | ... | ... | ... |
|
||||||
|
| block-(n-1) | **Prefetch block-0** (async) | Compute block-(n-1) | Free block-(n-1) |
|
||||||
|
|
||||||
|
Each transformer block has a `LayerwiseOffloadHook` that prefetches the next block before forward and frees the current block after forward.
|
||||||
|
|
||||||
|
Layerwise offloading is primarily recommended for large **video generation models** where the compute cost per block is high enough to effectively overlap with memory prefetch operations. For example, Wan2.2 T2V and I2V pipelines.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
**Python API:**
|
||||||
|
```python
|
||||||
|
from vllm_omni import Omni
|
||||||
|
|
||||||
|
# Text-to-video
|
||||||
|
m = Omni(model="Wan-AI/Wan2.2-T2V-A14B-Diffusers", enable_layerwise_offload=True)
|
||||||
|
|
||||||
|
# Or image-to-video
|
||||||
|
m = Omni(model="Wan-AI/Wan2.2-I2V-A14B-Diffusers", enable_layerwise_offload=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
**CLI:**
|
||||||
|
```bash
|
||||||
|
# Text-to-video
|
||||||
|
vllm serve Wan-AI/Wan2.2-T2V-A14B-Diffusers --omni --enable-layerwise-offload
|
||||||
|
|
||||||
|
# Or image-to-video
|
||||||
|
vllm serve Wan-AI/Wan2.2-I2V-A14B-Diffusers --omni --enable-layerwise-offload
|
||||||
|
```
|
||||||
|
|
||||||
|
### To Support a Model
|
||||||
|
|
||||||
|
Models must define the blocks attribute name for layerwise offloading:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class WanTransformer3DModel(nn.Module):
|
||||||
|
_layerwise_offload_blocks_attrs = ["blocks"] # Attribute names containing transformer blocks
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.blocks = nn.ModuleList([...]) # Transformer blocks
|
||||||
|
```
|
||||||
|
|
||||||
|
For models with multiple block types:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Flux2Transformer2DModel(nn.Module):
|
||||||
|
_layerwise_offload_blocks_attrs = ["transformer_blocks", "single_transformer_blocks"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
- Cold start latency increases because offloaded components must be moved to CPU
|
||||||
|
during setup; layerwise offload may add extra weight consolidation and pinning
|
||||||
|
work.
|
||||||
|
- Performance depends on compute cost and H2D bandwidth as well
|
||||||
|
- Support single GPU only for now
|
||||||
|
|
||||||
|
|
||||||
## Distributed Layerwise Offloading
|
## Distributed Layerwise Offloading
|
||||||
|
|
||||||
See [Distributed Layerwise Offloading](offloader/distributed_layerwise_offload.md).
|
### How It Works
|
||||||
|
|
||||||
|
Distributed layerwise offloading extends single-GPU layerwise offloading to
|
||||||
|
multi-device deployments. In the default mode, each rank stores only
|
||||||
|
**1/dp_size** of the model weights in host memory; full layer weights are
|
||||||
|
reconstructed at runtime via **AllGather** on a dedicated communication stream,
|
||||||
|
overlapped with computation via a fixed double-buffer scheme. The
|
||||||
|
`--dlo-no-use-allgather` mode instead streams standard-loader rank-local
|
||||||
|
weights independently and does not shard weights across ranks.
|
||||||
|
|
||||||
|
For the implementation design and compatibility matrix, see
|
||||||
|
[Distributed Layerwise Offload](../../design/feature/distributed_layerwise_offload.md).
|
||||||
|
|
||||||
|
**Key features:**
|
||||||
|
- **Weight sharding + AllGather**: each rank stores 1/dp_size of weights,
|
||||||
|
reconstructed per-layer via `all_gather_into_tensor`
|
||||||
|
- **Fixed double-buffer**: exactly 2 transformer blocks on each device at any
|
||||||
|
time, regardless of model size
|
||||||
|
- **DP multi-concurrency**: N concurrent requests processed in parallel
|
||||||
|
(AllGather only gathers weight shards, which are request-independent)
|
||||||
|
- **mmap weight loading**: weights loaded as mmap views pointing to shared OS
|
||||||
|
page cache, eliminating O(dp_size × model_size) RSS during model creation
|
||||||
|
- **Platform-agnostic**: works on NVIDIA GPU (CUDA/NCCL) and Ascend NPU
|
||||||
|
(CANN/HCCL) via vLLM-Omni's platform abstraction
|
||||||
|
|
||||||
|
**Execution Flow (per device):**
|
||||||
|
|
||||||
|
```
|
||||||
|
Compute Stream: [Layer N] [Layer N+1] [Layer N+2]
|
||||||
|
H2D Stream: [H2D Shard N+1] [H2D Shard N+2]
|
||||||
|
AllGather: [AG N+1] [AG N+2]
|
||||||
|
Slot Usage: Slot0: Layer N Slot1: Layer N+1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
**CLI:**
|
||||||
|
```bash
|
||||||
|
# 4× GPU/NPU with AllGather (recommended)
|
||||||
|
vllm serve /path/to/model --omni \
|
||||||
|
--enable-distributed-layerwise-offload \
|
||||||
|
--data-parallel-size 4
|
||||||
|
|
||||||
|
# Without DLO AllGather (each rank streams standard-loader rank-local weights)
|
||||||
|
vllm serve /path/to/model --omni \
|
||||||
|
--enable-distributed-layerwise-offload \
|
||||||
|
--data-parallel-size 4 \
|
||||||
|
--dlo-no-use-allgather
|
||||||
|
|
||||||
|
# With SP instead of DP (long sequences)
|
||||||
|
vllm serve /path/to/model --omni \
|
||||||
|
--enable-distributed-layerwise-offload \
|
||||||
|
--usp 4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Python API:**
|
||||||
|
```python
|
||||||
|
from vllm_omni import Omni
|
||||||
|
|
||||||
|
m = Omni(
|
||||||
|
model="/path/to/model",
|
||||||
|
enable_distributed_layerwise_offload=True,
|
||||||
|
dlo_use_allgather=True, # default
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### CLI Flags
|
||||||
|
|
||||||
|
| Flag | Description | Default |
|
||||||
|
|------|-------------|---------|
|
||||||
|
| `--enable-distributed-layerwise-offload` | Enable DLO | `false` |
|
||||||
|
| `--data-parallel-size N` | Number of DP ranks; DLO weight-sharding group when AllGather is enabled | `1` |
|
||||||
|
| `--dlo-use-allgather` | Shard + AllGather (saves CPU, requires concurrent requests) | `true` |
|
||||||
|
| `--dlo-no-use-allgather` | Use standard-loader rank-local weights; no DLO sharding or AllGather | `false` |
|
||||||
|
|
||||||
|
### How model weights are loaded (mmap path)
|
||||||
|
|
||||||
|
When DLO + AllGather is active, the offloader:
|
||||||
|
|
||||||
|
1. **Saves non-persistent buffers** (e.g. RoPE `inv_freq`, timestep `freqs`)
|
||||||
|
from the normally-created transformer
|
||||||
|
2. **Converts to meta device** via `to_empty(device="meta")`, releasing random
|
||||||
|
initialization weights
|
||||||
|
3. **Loads checkpoint weights as mmap views** via `safe_open().get_tensor()`,
|
||||||
|
which return views into the OS page cache (shared across all ranks, 0 RSS)
|
||||||
|
4. **Calls `post_load_weights()`** to apply model-specific dtype conversions
|
||||||
|
(e.g. Cosmos3's `time_embedder` → FP32)
|
||||||
|
5. **Restores non-persistent buffers** from saved copies
|
||||||
|
|
||||||
|
This approach requires **zero model-specific code changes** — no pipeline or
|
||||||
|
transformer modifications are needed.
|
||||||
|
|
||||||
|
### OffloadPlan (declarative topology metadata)
|
||||||
|
|
||||||
|
Models can optionally declare an `OffloadPlan` class variable to provide
|
||||||
|
topology metadata (block attribute names, submodules to offload) without
|
||||||
|
any offload-specific logic:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from vllm_omni.diffusion.offloader import OffloadPlan
|
||||||
|
|
||||||
|
class MyPipeline(nn.Module):
|
||||||
|
_dit_modules = ["transformer"]
|
||||||
|
_offload_plan = OffloadPlan(
|
||||||
|
block_attrs={"transformer": ("blocks",)},
|
||||||
|
offload_submodules={"context_encoder": "layers"},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
When not declared, the offloader falls back to `_layerwise_offload_blocks_attrs`
|
||||||
|
and heuristic attribute search. This is backward-compatible — existing models
|
||||||
|
work without any changes.
|
||||||
|
|
||||||
|
### DP Multi-concurrency
|
||||||
|
|
||||||
|
When `--data-parallel-size > 1` and AllGather is enabled, the scheduler batches
|
||||||
|
up to `dp_size` requests per denoise step. Each DP rank processes a different
|
||||||
|
request while AllGather synchronizes only weight shards (request-independent).
|
||||||
|
|
||||||
|
**Requirements for concurrent requests:**
|
||||||
|
- `num_inference_steps` must be specified explicitly (None is not allowed)
|
||||||
|
- All concurrent requests must have the same `num_inference_steps` value
|
||||||
|
- These constraints exist because AllGather is a collective that requires
|
||||||
|
every rank to participate at each denoise step
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
- Online quantization (FP8) is incompatible with mmap loading — falls back to
|
||||||
|
regular loading only when the model is outside the DLO+AllGather mmap path;
|
||||||
|
online quantization with DLO+AllGather is rejected. Use
|
||||||
|
`--dlo-no-use-allgather` or disable online quantization.
|
||||||
|
- Tensor Parallel > 1 is rejected in the DLO+AllGather mmap path because TP-aware
|
||||||
|
weight-loader callbacks are bypassed. The no-AllGather path retains the
|
||||||
|
standard TP loader and is experimental.
|
||||||
|
- HSDP + AllGather is rejected because it would double-shard parameters. HSDP
|
||||||
|
with no-AllGather is accepted at configuration level, but has limited
|
||||||
|
end-to-end validation.
|
||||||
|
- `num_inference_steps=None` is not allowed in DP multi-concurrency mode
|
||||||
|
|
||||||
|
**Module Discovery**
|
||||||
|
|
||||||
|
The offloader discovers pipeline components in two ways:
|
||||||
|
|
||||||
|
1. **Protocol-based** (preferred): If the pipeline implements
|
||||||
|
`SupportsComponentDiscovery`, its `_dit_modules`, `_encoder_modules`,
|
||||||
|
`_vae_modules`, and `_resident_modules` class variables are used
|
||||||
|
directly. All attribute names support dotted paths (e.g.
|
||||||
|
`"pipe.transformer"`, `"bagel.time_embedder"`) for nested submodules.
|
||||||
|
|
||||||
|
2. **Fallback attribute scan**: Otherwise, the offloader scans for
|
||||||
|
well-known attribute names:
|
||||||
|
- **DiT modules**: `transformer`, `transformer_2`, `dit`, `sr_dit`, `language_model`, `transformer_blocks`, `model`
|
||||||
|
- **Encoders**: `text_encoder`, `text_encoder_2`, `text_encoder_3`, `image_encoder`
|
||||||
|
- **VAE**: `vae`, `audio_vae`
|
||||||
|
|
||||||
|
**Hook System**
|
||||||
|
|
||||||
|
Both strategies use vLLM-Omni's hook registry system (`HookRegistry` and `ModelHook`) to register pre/post forward callbacks on modules, enabling automatic swapping without modifying model code.
|
||||||
|
|
||||||
|
**Backend Architecture**
|
||||||
|
|
||||||
|
```
|
||||||
|
OffloadBackend (base class)
|
||||||
|
├── ModelLevelOffloadBackend → uses SequentialOffloadHook (.to() swap)
|
||||||
|
│ (delegates to a pipeline's enable_omni_model_cpu_offload
|
||||||
|
│ for split models like Cosmos3)
|
||||||
|
├── LayerWiseOffloadBackend → uses LayerwiseOffloadHook
|
||||||
|
│ (single-GPU, full weights on host)
|
||||||
|
└── DistributedLayerwiseOffloadBackend → uses DistributedLayerwiseOffloadHook
|
||||||
|
(multi-GPU, sharded AllGather or rank-local mode)
|
||||||
|
```
|
||||||
|
|
||||||
|
Factory function `get_offload_backend()` selects the appropriate backend based on
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
For split models, `ModelLevelOffloadBackend.enable()` detects a pipeline's
|
||||||
|
`enable_omni_model_cpu_offload` hook and delegates to it; Cosmos3 then swaps its
|
||||||
|
reasoner/generator components inside the model forward pass.
|
||||||
|
|
||||||
|
|
||||||
|
## Supported Models
|
||||||
|
|
||||||
|
| Architecture | Example Models | DiT Class | Model-Level Offload | Layerwise Offload | Distributed Layerwise Offload | Blocks Attrs (Layerwise specific) |
|
||||||
|
|--------------|----------------|-----------|---------------------|-------------------|-------------------------------|-----------------------------------|
|
||||||
|
| Flux2Pipeline | `black-forest-labs/FLUX.2-dev` | `Flux2Transformer2DModel` | ✓ | ✓ | - | `"transformer_blocks"`, `"single_transformer_blocks"` |
|
||||||
|
| LongCatImagePipeline | `meituan-longcat/LongCat-Image` | `LongCatImageTransformer2DModel` | - | ✓ | - | `"transformer_blocks"`, `"single_transformer_blocks"` |
|
||||||
|
| NextStep11Pipeline | `stepfun-ai/NextStep-1.1` | `NextStepModel` | - | ✓ | - | `"layers"` |
|
||||||
|
| OvisImagePipeline | `AIDC-AI/Ovis-Image-7B` | `OvisImageTransformer2DModel` | - | ✓ | - | `"transformer"` |
|
||||||
|
| QwenImagePipeline | `Qwen/Qwen-Image` | `QwenImageTransformer2DModel` | ✓ | ✓ | - | `"transformer_blocks"` |
|
||||||
|
| StableDiffusionXLPipeline | `stabilityai/stable-diffusion-xl-base-1.0` | `SDXLUNet2DConditionModel` | ✓ | ✓ | - | `"down_blocks"`, `"up_blocks"` |
|
||||||
|
| StableDiffusion3Pipeline | `stabilityai/stable-diffusion-3.5-medium` | `SD3Transformer2DModel` | - | ✓ | - | `"transformer_blocks"` |
|
||||||
|
| Wan22I2VPipeline | `Wan-AI/Wan2.2-I2V-A14B-Diffusers` | `WanTransformer3DModel` | ✓ | ✓ | - | `"blocks"` |
|
||||||
|
| Wan22Pipeline | `Wan-AI/Wan2.2-T2V-A14B-Diffusers` | `WanTransformer3DModel` | ✓ | ✓ | - | `"blocks"` |
|
||||||
|
| SoulXSingerPipeline / SoulXSingerSVCPipeline | `Soul-AILab/SoulX-Singer` | `DiffLlama` (`cfm_decoder.model.diff_estimator`) | ✓ | ✓ | - | `"layers"` |
|
||||||
|
| BagelPipeline | `ByteDance-Seed/BAGEL-7B-MoT` | `Qwen2MoTModel` | - | ✓ | - | `"layers"`, `"customized modules"` |
|
||||||
|
| Cosmos3OmniDiffusersPipeline | `nvidia/Cosmos3-Nano`, `nvidia/Cosmos3-Super` | `Cosmos3VFMTransformer`, `Cosmos3LanguageModel` | ✓ | ✓ | ✓ | `"layers"`, `"gen_layers"` |
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Model-Level Offloading is expected to be supported by all common diffusion models (DiT and encoders) naturally
|
||||||
|
- Layerwise Offloading requires DiT class to define `_layerwise_offload_blocks_attrs` pointing to transformer blocks
|
||||||
|
- Distributed Layerwise Offloading reuses the layerwise block topology, but the
|
||||||
|
model and parallelism combination still needs validation. AllGather mode
|
||||||
|
additionally requires a compatible checkpoint/mmap layout; no-AllGather mode
|
||||||
|
retains the standard loader's rank-local tensors. See the
|
||||||
|
[DLO design](../../design/feature/distributed_layerwise_offload.md) and
|
||||||
|
[Cosmos3 DistOffload recipe](https://github.com/vllm-project/vllm-omni/blob/main/recipes/cosmos3/Cosmos3-DistOffload.md)
|
||||||
|
for usage examples.
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
# Model-Level Offloading
|
|
||||||
|
|
||||||
Model-level, or sequential, offloading keeps only the pipeline component group
|
|
||||||
currently executing on the accelerator. It is the simplest offload strategy
|
|
||||||
and is selected with `--enable-cpu-offload`.
|
|
||||||
|
|
||||||
## How it works
|
|
||||||
|
|
||||||
Pre-forward hooks enforce mutual exclusion between DiT and encoder modules:
|
|
||||||
|
|
||||||
- before an encoder runs, the DiT moves to CPU;
|
|
||||||
- before a DiT runs, encoders and other DiTs move to CPU; and
|
|
||||||
- VAE modules remain on the accelerator.
|
|
||||||
|
|
||||||
Pinned host memory reduces transfer overhead. Transfers occur at phase
|
|
||||||
boundaries, so cold-start and encoder-to-denoiser transitions become slower.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```python
|
|
||||||
from vllm_omni import Omni
|
|
||||||
|
|
||||||
omni = Omni(
|
|
||||||
model="Wan-AI/Wan2.2-T2V-A14B-Diffusers",
|
|
||||||
enable_cpu_offload=True,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm serve Wan-AI/Wan2.2-T2V-A14B-Diffusers \
|
|
||||||
--omni --enable-cpu-offload
|
|
||||||
```
|
|
||||||
|
|
||||||
## Model integration
|
|
||||||
|
|
||||||
Pipelines should implement `SupportsComponentDiscovery`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from typing import ClassVar
|
|
||||||
|
|
||||||
from vllm_omni.diffusion.models.interface import SupportsComponentDiscovery
|
|
||||||
|
|
||||||
|
|
||||||
class MyPipeline(nn.Module, SupportsComponentDiscovery):
|
|
||||||
_dit_modules: ClassVar[list[str]] = ["transformer"]
|
|
||||||
_encoder_modules: ClassVar[list[str]] = ["text_encoder", "vision_model"]
|
|
||||||
_vae_modules: ClassVar[list[str]] = ["vae"]
|
|
||||||
_resident_modules: ClassVar[list[str]] = []
|
|
||||||
```
|
|
||||||
|
|
||||||
All entries may be dotted paths. DiT and encoder lists are both required for
|
|
||||||
mutual exclusion. VAE modules are pinned but not swapped; resident modules are
|
|
||||||
small modules that must stay on the accelerator for layerwise paths.
|
|
||||||
|
|
||||||
## Split-model components
|
|
||||||
|
|
||||||
Some models, such as Cosmos3, split one transformer into mutually exclusive
|
|
||||||
components that run in different phases. The pipeline exposes
|
|
||||||
`enable_omni_model_cpu_offload`, and the backend delegates to the model-local
|
|
||||||
contexts:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class Cosmos3VFMTransformer(nn.Module):
|
|
||||||
def forward(self, ...):
|
|
||||||
with self._offload_context("reasoner"):
|
|
||||||
...
|
|
||||||
with self._offload_context("generator"):
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
This preserves the same invariant—exactly one component is device resident—
|
|
||||||
while reusing sequential `.to()` movers.
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
- Single device only.
|
|
||||||
- Higher cold-start latency.
|
|
||||||
- Transfers between encoder and denoising phases add latency.
|
|
||||||
|
|
||||||
See the [model-level design](../../../design/feature/offloader/cpu_offload.md)
|
|
||||||
for lifecycle and extension invariants.
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
# Distributed Layerwise Offloading
|
|
||||||
|
|
||||||
Distributed layerwise offloading (DLO) streams DiT blocks through two device
|
|
||||||
buffers instead of keeping the complete DiT in HBM. It supports sharded host
|
|
||||||
weights with AllGather or independently streamed rank-local weights. The
|
|
||||||
no-AllGather path can optionally share node-local host weights through mmap.
|
|
||||||
|
|
||||||
See the [shared DLO component architecture](../../../design/feature/offloader/distributed_layerwise_offload.md#architecture)
|
|
||||||
for ownership and lifecycle coordination. The mode-specific internals are
|
|
||||||
documented separately in the
|
|
||||||
[AllGather design](../../../design/feature/offloader/distributed_layerwise_offload.md#allgather-design)
|
|
||||||
and
|
|
||||||
[no-AllGather design](../../../design/feature/offloader/distributed_layerwise_offload.md#no-allgather-design).
|
|
||||||
|
|
||||||
## Choose a mode
|
|
||||||
|
|
||||||
| Mode | Use when | Host weights | Runtime synchronization |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| DLO AllGather (default) | DP ranks execute the same block path in lockstep | About `1 / dp_size` per rank | DLO weight AllGather |
|
|
||||||
| no-AllGather | Ranks or engines must schedule independently | Complete rank-local layout | No DLO weight collective |
|
|
||||||
| no-AllGather + host weight cache | Equivalent independent workers share one node | Shared final mmap layout per TP coordinate | No DLO weight collective |
|
|
||||||
| host weight cache + host registration | The platform supports registration and recurrent staging is too expensive | Shared registered mmap layout | No DLO weight collective |
|
|
||||||
|
|
||||||
AllGather is normally the best choice for synchronized DP. Host weight cache mode
|
|
||||||
targets independently scheduled replicas, especially repeated TP engines on
|
|
||||||
one node.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Sharded host weights with DLO AllGather
|
|
||||||
vllm serve /path/to/model --omni \
|
|
||||||
--enable-distributed-layerwise-offload \
|
|
||||||
--data-parallel-size 4
|
|
||||||
|
|
||||||
# Independently streamed rank-local weights
|
|
||||||
vllm serve /path/to/model --omni \
|
|
||||||
--enable-distributed-layerwise-offload \
|
|
||||||
--dlo-no-use-allgather
|
|
||||||
|
|
||||||
# Share final host weights; prefer registered direct H2D under the pinned-memory policy
|
|
||||||
vllm serve /path/to/model --omni \
|
|
||||||
--enable-distributed-layerwise-offload \
|
|
||||||
--dlo-no-use-allgather \
|
|
||||||
--dlo-use-host-weight-cache
|
|
||||||
|
|
||||||
# Apply an 80 GiB per-worker safety ceiling to the same registration attempt
|
|
||||||
vllm serve /path/to/model --omni \
|
|
||||||
--enable-distributed-layerwise-offload \
|
|
||||||
--tensor-parallel-size 2 \
|
|
||||||
--dlo-no-use-allgather \
|
|
||||||
--dlo-use-host-weight-cache \
|
|
||||||
--dlo-host-weight-cache-pin-limit-gib 80
|
|
||||||
```
|
|
||||||
|
|
||||||
## Relevant options
|
|
||||||
|
|
||||||
| Flag | Meaning | Default |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `--enable-distributed-layerwise-offload` | Enable DLO | `false` |
|
|
||||||
| `--dlo-no-use-allgather` | Stream complete rank-local blocks independently | `false` |
|
|
||||||
| `--dlo-use-host-weight-cache` | Use the final-layout cache and prefer registration when pinned memory is enabled | `false` |
|
|
||||||
| `--dlo-host-weight-cache-pin-limit-gib GIB` | Optional per-worker registration ceiling; zero means no additional ceiling | `0` |
|
|
||||||
| `--dlo-resident-layers N` | Keep eligible leading DiT blocks resident in HBM | `0` |
|
|
||||||
|
|
||||||
No-AllGather DLO automatically uses a compatible checkpoint mmap plan. When
|
|
||||||
checkpoint mmap is unavailable, it preserves ordinary pinned loader weights by
|
|
||||||
default. `--dlo-use-host-weight-cache` explicitly selects the transform-complete
|
|
||||||
final-layout cache. Under the default `pin_cpu_memory=True` policy, DLO attempts
|
|
||||||
to register the complete immutable mapping for direct H2D. Setting
|
|
||||||
`pin_cpu_memory=False` through structured or programmatic configuration retains
|
|
||||||
the staged mmap path instead. A positive pin limit is only an optional safety
|
|
||||||
ceiling and does not select the cache. The cache uses
|
|
||||||
`~/.cache/vllm-omni/dlo-host-weights`. Programmatic configuration may override
|
|
||||||
`dlo_host_weight_cache_dir` and the writer lock timeout; these advanced storage
|
|
||||||
controls are intentionally not separate CLI flags.
|
|
||||||
|
|
||||||
## Operational notes
|
|
||||||
|
|
||||||
- All workers that should share must see the same local, disk-backed cache
|
|
||||||
directory. Do not use tmpfs or a cross-node filesystem for this version.
|
|
||||||
- Cache entries are immutable and validated before use. A cache or registration
|
|
||||||
failure keeps the ordinary loader weights or falls back to bounded staging.
|
|
||||||
- The bounded-staging fallback is a memory-first mode: recurrent pageable-mmap to
|
|
||||||
pinned-slot copies can reduce throughput, especially across concurrent TP
|
|
||||||
engines. Use it intentionally by disabling pinned host memory, or when the
|
|
||||||
active platform cannot register the immutable mapping.
|
|
||||||
- When a positive registration ceiling is configured, it must cover the
|
|
||||||
complete page-aligned mapping reported in the worker log. Registration is
|
|
||||||
all-or-nothing. Zero applies no additional ceiling.
|
|
||||||
- CUDA is the first registration backend and requires read-only host
|
|
||||||
registration support for immutable cache mappings. Platforms or devices
|
|
||||||
without that capability continue to use bounded staging; pinned-memory policy
|
|
||||||
requests direct H2D but does not guarantee it.
|
|
||||||
- Registration is process-local but does not duplicate the underlying file
|
|
||||||
pages. Each worker must still satisfy its platform and OS page-locking limits.
|
|
||||||
- Shutdown unregisters host ranges before closing their mmap handles.
|
|
||||||
- Host weight cache v1 has no automatic eviction. Stop all users of an entry
|
|
||||||
before deleting it.
|
|
||||||
|
|
||||||
## Scheduling constraints
|
|
||||||
|
|
||||||
AllGather ranks must request blocks in the same collective order. Concurrent DP
|
|
||||||
requests may use different prompts, but they must follow the same denoising and
|
|
||||||
block-execution path and set the same explicit `num_inference_steps`.
|
|
||||||
|
|
||||||
No-AllGather workers do not have this DLO lockstep requirement. Ordinary TP or
|
|
||||||
SP model collectives still synchronize ranks within each engine.
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
- Direct checkpoint mmap currently requires TP1. The host weight cache can share
|
|
||||||
final ordinary-loader layouts between matching TP coordinates at TP1 or TP>1.
|
|
||||||
- Per-tensor online FP8 linears use the ordinary loader and can run with either
|
|
||||||
DLO transfer path. Other online quantization methods require no-AllGather
|
|
||||||
until their runtime layouts are validated.
|
|
||||||
- Host weight cache v1 rejects quantized, non-contiguous, aliased/tied,
|
|
||||||
device-only, HSDP, expert-parallel, CFG-parallel, and PP layouts.
|
|
||||||
- HSDP plus DLO AllGather is unsupported, and host weight cache v1 rejects HSDP.
|
|
||||||
- The host weight cache changes steady-state host backing, not startup loading:
|
|
||||||
each worker still performs ordinary loading and full content validation.
|
|
||||||
- Skip-load-on-hit, cache eviction, and cross-node sharing remain follow-up
|
|
||||||
work in
|
|
||||||
[RFC #6195](https://github.com/vllm-project/vllm-omni/issues/6195).
|
|
||||||
|
|
||||||
See the [Cosmos3 DistOffload recipe](https://github.com/vllm-project/vllm-omni/blob/main/recipes/cosmos3/Cosmos3-DistOffload.md)
|
|
||||||
for an end-to-end example.
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
# Layerwise Offloading
|
|
||||||
|
|
||||||
Layerwise, or blockwise, offloading keeps one transformer block on the
|
|
||||||
accelerator and prefetches the next block while the current block computes.
|
|
||||||
It is best suited to compute-heavy video DiTs whose block execution can hide
|
|
||||||
host-to-device transfers.
|
|
||||||
|
|
||||||
## Execution flow
|
|
||||||
|
|
||||||
Each block has a pre-forward and post-forward hook. Parameters are consolidated
|
|
||||||
in pinned host tensors and rematerialized for execution on a dedicated copy
|
|
||||||
stream.
|
|
||||||
|
|
||||||
| Block | Pre-forward hook | Forward | Post-forward hook |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| block 0 | Prefetch block 1 | Compute block 0 | Free block 0 |
|
|
||||||
| block 1 | Prefetch block 2 | Compute block 1 | Free block 1 |
|
|
||||||
| ... | ... | ... | ... |
|
|
||||||
| last block | Prefetch block 0 | Compute last block | Free last block |
|
|
||||||
|
|
||||||
Encoders, VAE modules, and non-block DiT modules such as embeddings and norms
|
|
||||||
remain device resident.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```python
|
|
||||||
from vllm_omni import Omni
|
|
||||||
|
|
||||||
omni = Omni(
|
|
||||||
model="Wan-AI/Wan2.2-T2V-A14B-Diffusers",
|
|
||||||
enable_layerwise_offload=True,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm serve Wan-AI/Wan2.2-T2V-A14B-Diffusers \
|
|
||||||
--omni --enable-layerwise-offload
|
|
||||||
```
|
|
||||||
|
|
||||||
## Model integration
|
|
||||||
|
|
||||||
Transformer classes declare containers of executable blocks:
|
|
||||||
|
|
||||||
```python
|
|
||||||
class WanTransformer3DModel(nn.Module):
|
|
||||||
_layerwise_offload_blocks_attrs = ["blocks"]
|
|
||||||
|
|
||||||
|
|
||||||
class Flux2Transformer2DModel(nn.Module):
|
|
||||||
_layerwise_offload_blocks_attrs = [
|
|
||||||
"transformer_blocks",
|
|
||||||
"single_transformer_blocks",
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
See the [layerwise design](../../../design/feature/offloader/layerwise_offload.md) for
|
|
||||||
the discovery and hook invariants. `OffloadPlan` is a separate declarative
|
|
||||||
topology path for distributed layerwise offload.
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
- Single device only.
|
|
||||||
- Setup consolidates and pins block parameters, increasing cold-start time.
|
|
||||||
- Performance depends on block compute time and host-to-device bandwidth;
|
|
||||||
lightweight blocks may not hide transfers.
|
|
||||||
@@ -64,41 +64,6 @@ omni = Omni(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Combined with Online FP8 Quantization
|
|
||||||
|
|
||||||
HSDP can be combined with online FP8 quantization for diffusion models that
|
|
||||||
support both features. HSDP shards the quantized weights across GPUs to reduce
|
|
||||||
the per-GPU weight memory footprint.
|
|
||||||
|
|
||||||
```python
|
|
||||||
from vllm_omni import Omni
|
|
||||||
from vllm_omni.diffusion.data import DiffusionParallelConfig
|
|
||||||
|
|
||||||
omni = Omni(
|
|
||||||
model="<your-model>",
|
|
||||||
quantization="fp8",
|
|
||||||
parallel_config=DiffusionParallelConfig(
|
|
||||||
use_hsdp=True,
|
|
||||||
hsdp_shard_size=2,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
For online serving:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm serve <your-model> \
|
|
||||||
--omni \
|
|
||||||
--quantization fp8 \
|
|
||||||
--use-hsdp \
|
|
||||||
--hsdp-shard-size 2
|
|
||||||
```
|
|
||||||
|
|
||||||
The model must support both HSDP and online FP8 quantization. HSDP remains
|
|
||||||
incompatible with Tensor Parallelism. See the
|
|
||||||
[FP8 Quantization Guide](../../quantization/fp8.md) for supported models and
|
|
||||||
hardware.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Example Script
|
## Example Script
|
||||||
@@ -182,4 +147,3 @@ For detailed instructions on adding HSDP support to new models, see the [HSDP Co
|
|||||||
1. ✅ **Enable HSDP** - Set `use_hsdp=True` and `hsdp_shard_size` to reduce per-GPU memory for large models
|
1. ✅ **Enable HSDP** - Set `use_hsdp=True` and `hsdp_shard_size` to reduce per-GPU memory for large models
|
||||||
2. ✅ **Combine with SP** - Use together with `ulysses_degree` for video models requiring both memory reduction and sequence parallelism
|
2. ✅ **Combine with SP** - Use together with `ulysses_degree` for video models requiring both memory reduction and sequence parallelism
|
||||||
3. ⚠️ **Incompatible with TP** - `tensor_parallel_size` must be 1 when HSDP is enabled
|
3. ⚠️ **Incompatible with TP** - `tensor_parallel_size` must be 1 when HSDP is enabled
|
||||||
4. ✅ **Combine with online FP8** - Use `quantization="fp8"` or `--quantization fp8` to reduce the sharded weight memory footprint
|
|
||||||
@@ -10,7 +10,7 @@ This guide covers the parallelism methods in vLLM-Omni for speeding up diffusion
|
|||||||
| **[Sequence Parallelism](sequence_parallel.md)** | Splits sequence dimension across GPUs (Ulysses-SP, Ring-Attention, or hybrid) for high-resolution images and videos |
|
| **[Sequence Parallelism](sequence_parallel.md)** | Splits sequence dimension across GPUs (Ulysses-SP, Ring-Attention, or hybrid) for high-resolution images and videos |
|
||||||
| **[CFG-Parallel](cfg_parallel.md)** | Runs CFG positive/negative branches on separate GPUs for ~1.8x speedup on guided generation |
|
| **[CFG-Parallel](cfg_parallel.md)** | Runs CFG positive/negative branches on separate GPUs for ~1.8x speedup on guided generation |
|
||||||
| **[Pipeline Parallelism](pipeline_parallel.md)** | Splits the denoising transformer block-wise across sequential GPU stages to reduce per-GPU model memory |
|
| **[Pipeline Parallelism](pipeline_parallel.md)** | Splits the denoising transformer block-wise across sequential GPU stages to reduce per-GPU model memory |
|
||||||
| **[VAE Parallelism](vae_parallelism.md)** | Distributes VAE decode spatially across GPUs to reduce peak VAE memory |
|
| **[VAE Parallel](vae_parallel.md)** | Distributes VAE decode spatially across GPUs to reduce peak VAE memory |
|
||||||
| **[HSDP](hsdp.md)** | Shards full model weights via PyTorch FSDP2 to enable large-model inference on memory-constrained GPUs |
|
| **[HSDP](hsdp.md)** | Shards full model weights via PyTorch FSDP2 to enable large-model inference on memory-constrained GPUs |
|
||||||
| **[Expert Parallelism](expert_parallel.md)** | Shards MoE expert blocks across GPUs for MoE models (e.g. HunyuanImage3.0) |
|
| **[Expert Parallelism](expert_parallel.md)** | Shards MoE expert blocks across GPUs for MoE models (e.g. HunyuanImage3.0) |
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -1,4 +1,4 @@
|
|||||||
# VAE Parallelism Guide
|
# VAE Parallel Guide
|
||||||
|
|
||||||
|
|
||||||
## Table of Content
|
## Table of Content
|
||||||
@@ -113,7 +113,7 @@ In `DiffusionParallelConfig`:
|
|||||||
| Parameter | Type | Default | Description |
|
| Parameter | Type | Default | Description |
|
||||||
|-----------|------|---------|-------------|
|
|-----------|------|---------|-------------|
|
||||||
| `vae_patch_parallel_size` | int | 1 | Number of GPUs for VAE patch/tile parallelism. Set to 2 or higher to enable. Should typically match `tensor_parallel_size` as they share the same process group. |
|
| `vae_patch_parallel_size` | int | 1 | Number of GPUs for VAE patch/tile parallelism. Set to 2 or higher to enable. Should typically match `tensor_parallel_size` as they share the same process group. |
|
||||||
| `vae_parallel_mode` | str | `"tile"` | VAE parallel decode strategy: `"tile"` (default tile/patch parallel decode), `"spatial_shard_height"`, or `"spatial_shard_width"` (spatially-sharded decode, Wan only). See [Spatially-Sharded Decode](#spatially-sharded-decode-wan). |
|
| `vae_parallel_mode` | str | `"tile"` | VAE parallel decode strategy: `"tile"`, `"auto"` (Wan request-shape selection), `"spatial_shard_height"`, or `"spatial_shard_width"`. Spatial sharding is currently Wan-only. See [Spatially-Sharded Decode](#spatially-sharded-decode-wan). |
|
||||||
|
|
||||||
Additional requirements:
|
Additional requirements:
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ Additional requirements:
|
|||||||
|
|
||||||
## Spatially-Sharded Decode (Wan)
|
## Spatially-Sharded Decode (Wan)
|
||||||
|
|
||||||
The default `vae_parallel_mode="tile"` distributes whole tiles across ranks. For the **Wan** VAE there is an alternative decode strategy, **spatially-sharded decode**, selected via `vae_parallel_mode="spatial_shard_height"` or `vae_parallel_mode="spatial_shard_width"`.
|
The default `vae_parallel_mode="tile"` keeps patch/tile parallel decode unchanged. For Wan, set `vae_parallel_mode="auto"` to select spatial-shard decode only after a request is already large enough to enter Diffusers' tiled-decode path and the VAE uses the full DiT decode group. Auto mode splits the longer eligible spatial axis to reduce halo traffic. Direct-decode requests and partial groups fall back to tile decode. Use `"spatial_shard_height"` or `"spatial_shard_width"` to force an axis.
|
||||||
|
|
||||||
Instead of assigning independent tiles to ranks, spatial-shard decode shards the decoder feature maps along the height (`spatial_shard_height`) or width (`spatial_shard_width`) dimension and exchanges halo rows/columns between neighboring ranks around the spatial convolutions. This keeps the receptive field correct across shard boundaries, so the result matches the single-GPU decode within numerical tolerance.
|
Instead of assigning independent tiles to ranks, spatial-shard decode shards the decoder feature maps along the height (`spatial_shard_height`) or width (`spatial_shard_width`) dimension and exchanges halo rows/columns between neighboring ranks around the spatial convolutions. This keeps the receptive field correct across shard boundaries, so the result matches the single-GPU decode within numerical tolerance.
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ omni = Omni(
|
|||||||
parallel_config=DiffusionParallelConfig(
|
parallel_config=DiffusionParallelConfig(
|
||||||
tensor_parallel_size=2,
|
tensor_parallel_size=2,
|
||||||
vae_patch_parallel_size=2, # must match the DiT group size
|
vae_patch_parallel_size=2, # must match the DiT group size
|
||||||
vae_parallel_mode="spatial_shard_width", # or "spatial_shard_height"
|
vae_parallel_mode="auto", # opt in to Wan request-shape selection
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
@@ -152,14 +152,14 @@ Or from the CLI / serving entrypoint:
|
|||||||
vllm serve Wan-AI/Wan2.1-T2V-1.3B-Diffusers --omni \
|
vllm serve Wan-AI/Wan2.1-T2V-1.3B-Diffusers --omni \
|
||||||
--tensor-parallel-size 2 \
|
--tensor-parallel-size 2 \
|
||||||
--vae-patch-parallel-size 2 \
|
--vae-patch-parallel-size 2 \
|
||||||
--vae-parallel-mode spatial_shard_width
|
--vae-parallel-mode auto
|
||||||
```
|
```
|
||||||
|
|
||||||
**Constraints and behavior:**
|
**Constraints and behavior:**
|
||||||
|
|
||||||
- Spatial-shard decode is **decode-only** and currently implemented for the **Wan** VAE. Other models ignore `spatial_shard_*` modes.
|
- Spatial-shard decode is **decode-only** and currently implemented for the **Wan** VAE. Other VAE families retain their existing mode handling and may reject explicit Wan spatial modes.
|
||||||
- It requires `vae_patch_parallel_size` to **match the DiT process group size**. If it does not, the VAE logs a warning and **falls back to tile-parallel decode** at runtime.
|
- It requires `vae_patch_parallel_size` to **match the DiT process group size**. `auto` quietly falls back to tile decode for a partial group; an explicitly forced spatial mode logs a warning.
|
||||||
- `spatial_shard_height` and `spatial_shard_width` are mutually exclusive for a given VAE instance (the decoder is patched in place for a single split dimension).
|
- The decoder's context-aware wrappers retain the direct path, so one process can alternate small/large and portrait/landscape requests safely.
|
||||||
|
|
||||||
For end-to-end latency/throughput, launch serving with the desired `vae_parallel_mode` and use the existing diffusion serving benchmark:
|
For end-to-end latency/throughput, launch serving with the desired `vae_parallel_mode` and use the existing diffusion serving benchmark:
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ Memory optimization methods help reduce GPU memory usage, enabling inference on
|
|||||||
|--------|-------------|----------|
|
|--------|-------------|----------|
|
||||||
| **[CPU Offload](diffusion/cpu_offload.md)** | Offloads model components to CPU memory | Limited VRAM, large models on consumer GPUs |
|
| **[CPU Offload](diffusion/cpu_offload.md)** | Offloads model components to CPU memory | Limited VRAM, large models on consumer GPUs |
|
||||||
| **[Quantization](quantization/overview.md)** | Reduces transformer stages from BF16 to FP8/INT8/etc. | Limited VRAM, minimal accuracy loss |
|
| **[Quantization](quantization/overview.md)** | Reduces transformer stages from BF16 to FP8/INT8/etc. | Limited VRAM, minimal accuracy loss |
|
||||||
| **[VAE Parallelism](diffusion/parallelism/vae_parallelism.md)** | Distributes VAE decode work across GPUs | High-resolution generation with reduced VAE memory peak |
|
| **[VAE Parallel](diffusion/parallelism/vae_parallel.md)** | Distributes VAE decode work across GPUs | High-resolution generation with reduced VAE memory peak |
|
||||||
|
|
||||||
### Extensions
|
### Extensions
|
||||||
|
|
||||||
@@ -106,10 +106,7 @@ The following tables show which models support each feature:
|
|||||||
|
|
||||||
> Notes:
|
> Notes:
|
||||||
|
|
||||||
> 1. CPU Offload has three strategies: model-level, layerwise, and distributed
|
> 1. CPU Offload has two methods: Module-wise (default for models with DiT + text encoder) and Layerwise. The tables below show **Layerwise support** only. Split models like Cosmos3 (no separate text encoder) swap their reasoner/generator components for module-wise offload; see the [CPU Offload Guide](diffusion/cpu_offload.md).
|
||||||
> layerwise. The tables below show **layerwise support** only. Split models
|
|
||||||
> like Cosmos3 swap their reasoner/generator components for model-level
|
|
||||||
> offload; see the [CPU Offload Guide](diffusion/cpu_offload.md).
|
|
||||||
> 2. The **💾Quantization** column is collapsed for readability. See [Quantization](quantization/overview.md) for per-method and per-model support details.
|
> 2. The **💾Quantization** column is collapsed for readability. See [Quantization](quantization/overview.md) for per-method and per-model support details.
|
||||||
|
|
||||||
### ImageGen
|
### ImageGen
|
||||||
@@ -161,7 +158,6 @@ The following tables show which models support each feature:
|
|||||||
| **Wan2.1-VACE** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
|
| **Wan2.1-VACE** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
|
||||||
| **LTX-2** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
|
| **LTX-2** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
|
||||||
| **LTX-2.3** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
|
| **LTX-2.3** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
|
||||||
| **LTX-2.5** | ❌ | ❓ (one-stage) | ❓ (Ulysses only) | ✅ (Full only) | ❓ | ❌ | ✅ | ✅ | ✅ (decode) | ❓ (FP8) | ❌ |
|
|
||||||
| **Helios** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅* |
|
| **Helios** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅* |
|
||||||
| **HunyuanVideo-1.5 T2V I2V** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (encode/decode) | ✅ | ❌ |
|
| **HunyuanVideo-1.5 T2V I2V** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (encode/decode) | ✅ | ❌ |
|
||||||
| **DreamID-Omni** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
| **DreamID-Omni** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||||
@@ -175,7 +171,7 @@ The following tables show which models support each feature:
|
|||||||
**Frame Interpolation Support**
|
**Frame Interpolation Support**
|
||||||
|
|
||||||
- **Supported**: Wan2.2 text-to-video, image-to-video, and TI2V pipelines
|
- **Supported**: Wan2.2 text-to-video, image-to-video, and TI2V pipelines
|
||||||
- **Not supported**: Wan2.1-VACE, LTX-2, LTX-2.3, LTX-2.5, Helios, HunyuanVideo-1.5, DreamID-Omni
|
- **Not supported**: Wan2.1-VACE, LTX-2, LTX-2.3, Helios, HunyuanVideo-1.5, DreamID-Omni
|
||||||
|
|
||||||
### AudioGen
|
### AudioGen
|
||||||
|
|
||||||
@@ -218,7 +214,7 @@ The following tables show which models support each feature:
|
|||||||
3. CPU Offloading (Layerwise) and CPU Offloading (Module-wise) are not compatible.
|
3. CPU Offloading (Layerwise) and CPU Offloading (Module-wise) are not compatible.
|
||||||
4. The CPU Offloading (Layerwise) row describes local layerwise offload.
|
4. The CPU Offloading (Layerwise) row describes local layerwise offload.
|
||||||
Multi-device Distributed Layerwise Offload has a separate topology and
|
Multi-device Distributed Layerwise Offload has a separate topology and
|
||||||
compatibility matrix in the [Distributed Layerwise Offloading guide](diffusion/offloader/distributed_layerwise_offload.md).
|
compatibility matrix in the [CPU Offloading guide](diffusion/cpu_offload.md#distributed-layerwise-offloading).
|
||||||
5. The compatibility matrix uses FP8 as the representative quantization method.
|
5. The compatibility matrix uses FP8 as the representative quantization method.
|
||||||
6. Step Execution is not compatible with any diffusion cache backend. LoRA is supported, but each scheduled batch must use a single adapter (requests with different `lora_request` or `lora_scale` are kept in separate batches).
|
6. Step Execution is not compatible with any diffusion cache backend. LoRA is supported, but each scheduled batch must use a single adapter (requests with different `lora_request` or `lora_scale` are kept in separate batches).
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ if __name__ == "__main__":
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
outputs[0].images[0].save("output.png")
|
outputs[0].request_output.images[0].save("output.png")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Image-to-Image (Image Editing)
|
## Image-to-Image (Image Editing)
|
||||||
@@ -72,7 +72,7 @@ if __name__ == "__main__":
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
outputs[0].images[0].save("output.png")
|
outputs[0].request_output.images[0].save("output.png")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Generation Parameters
|
## Generation Parameters
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ if __name__ == "__main__":
|
|||||||
omni = Omni(model="Qwen/Qwen-Image")
|
omni = Omni(model="Qwen/Qwen-Image")
|
||||||
prompt = "a cup of coffee on the table"
|
prompt = "a cup of coffee on the table"
|
||||||
outputs = omni.generate(prompt)
|
outputs = omni.generate(prompt)
|
||||||
images = outputs[0].images
|
images = outputs[0].request_output.images
|
||||||
images[0].save("coffee.png")
|
images[0].save("coffee.png")
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ if __name__ == "__main__":
|
|||||||
]
|
]
|
||||||
outputs = omni.generate(prompts)
|
outputs = omni.generate(prompts)
|
||||||
for i, output in enumerate(outputs):
|
for i, output in enumerate(outputs):
|
||||||
output.images[0].save(f"{i}.jpg")
|
output.request_output.images[0].save(f"{i}.jpg")
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
@@ -207,7 +207,7 @@ if __name__ == "__main__":
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
for i, output in enumerate(outputs):
|
for i, output in enumerate(outputs):
|
||||||
output.images[0].save(f"{i}.jpg")
|
output.request_output.images[0].save(f"{i}.jpg")
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also pass a negative prompt via the CLI argument `--negative-prompt`:
|
You can also pass a negative prompt via the CLI argument `--negative-prompt`:
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ More failure modes (for example, network jitter and network interruption) will b
|
|||||||
|
|
||||||
## Source of Conclusions
|
## Source of Conclusions
|
||||||
|
|
||||||
The behaviors and conclusions above are summarized from current fault injection validation results on **`Qwen3-Omni`**, **`Wan2.2`**, and **`HunyuanImage-3.0-Instruct`** (DiT-only, `/v1/images/generations`). Automated coverage lives under `tests/dfx/reliability/` (`test_reliability_qwen3_omni.py`, `test_reliability_wan22.py`, `test_reliability_hunyuan_image.py`) and runs weekly via `.buildkite/cuda/test-weekly.yml`.
|
The behaviors and conclusions above are summarized from current fault injection validation results on **`Qwen3-Omni`**, **`Wan2.2`**, **`HunyuanImage-3.0-Instruct`** (DiT-only, `/v1/images/generations`), and **`VoxCPM2`** (`/v1/audio/speech`). Automated coverage lives under `tests/dfx/reliability/` (`test_reliability_qwen3_omni.py`, `test_reliability_wan22.py`, `test_reliability_hunyuan_image.py`, `test_reliability_voxcpm2.py`) and runs weekly via `.buildkite/cuda/test-weekly.yml`.
|
||||||
|
|
||||||
### Example: `SIGTERM` fault injection log (Qwen3-Omni)
|
### Example: `SIGTERM` fault injection log (Qwen3-Omni)
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ vllm serve Qwen/Qwen-Image --omni --port 8091 \
|
|||||||
|
|
||||||
- **Tensor Parallelism — Text Encoder Not Sharded**: TP currently only shards the DiT blocks. Each TP rank retains a **full copy of the text encoder weights**, leading to significant GPU memory overhead proportional to TP degree. Tracked in [Issue #771](https://github.com/vllm-project/vllm-omni/issues/771).
|
- **Tensor Parallelism — Text Encoder Not Sharded**: TP currently only shards the DiT blocks. Each TP rank retains a **full copy of the text encoder weights**, leading to significant GPU memory overhead proportional to TP degree. Tracked in [Issue #771](https://github.com/vllm-project/vllm-omni/issues/771).
|
||||||
|
|
||||||
- **CPU Offloading — Strategies Are Mutually Exclusive**: Distributed layerwise, layerwise, and model-level offload should not be enabled together. Selection priority is distributed layerwise, then layerwise, then model-level.
|
- **CPU Offloading — Two Modes Are Mutually Exclusive**: Model-level offload (`enable_cpu_offload`) and layerwise offload (`enable_layerwise_offload`) cannot be used simultaneously. If both are set, layerwise takes priority and model-level is silently ignored.
|
||||||
|
|
||||||
- **CPU Offloading — VAE stays on GPU**: Both offloading strategies keep the VAE on GPU at all times. For high-resolution generation, VAE decode can still cause OOM. Mitigate by combining with `vae_use_tiling=True` or VAE Patch Parallelism.
|
- **CPU Offloading — VAE stays on GPU**: Both offloading strategies keep the VAE on GPU at all times. For high-resolution generation, VAE decode can still cause OOM. Mitigate by combining with `vae_use_tiling=True` or VAE Patch Parallelism.
|
||||||
|
|
||||||
|
|||||||
@@ -80,14 +80,12 @@ warmup_quack_fp8([(14040, 2048, 6144), (14040, 2048, 2048)])
|
|||||||
|
|
||||||
## Model Type Support
|
## Model Type Support
|
||||||
|
|
||||||
### Diffusion Models
|
### Diffusion Model (Qwen-Image, Wan2.2)
|
||||||
|
|
||||||
| Model | HF models | Online | Pre-calibrated | Recommendation | `ignored_layers` | Text-Encoder quantization |
|
| Model | HF models | Online | Pre-calibrated | Recommendation | `ignored_layers` | Text-Encoder quantization |
|
||||||
|-------|-----------|:-------:|:------:|----------------|------------------|------------------|
|
|-------|-----------|:-------:|:------:|----------------|------------------|------------------|
|
||||||
| Qwen-Image | `Qwen/Qwen-Image`, `Qwen/Qwen-Image-2512` | Yes | Yes | Skip sensitive image-stream MLPs when quality regresses | `img_mlp` | |
|
| Qwen-Image | `Qwen/Qwen-Image`, `Qwen/Qwen-Image-2512` | Yes | Yes | Skip sensitive image-stream MLPs when quality regresses | `img_mlp` | |
|
||||||
| Wan2.2 | Wan2.2 diffusion pipelines | Not validated | Not validated | Validate against BF16 before documenting as supported | TBD | |
|
| Wan2.2 | Wan2.2 diffusion pipelines | Not validated | Not validated | Validate against BF16 before documenting as supported | TBD | |
|
||||||
| LTX-2 | `Lightricks/LTX-2`, `rootonchair/LTX-2-19b-distilled` | Yes | Not validated | Transformer only; use dynamic phase LoRA for ordinary two-stage | None | |
|
|
||||||
| LTX-2.3 | `diffusers/LTX-2.3-Diffusers`, `diffusers/LTX-2.3-Distilled-Diffusers` | Yes | Not validated | Transformer only; use dynamic phase LoRA for ordinary two-stage | None | |
|
|
||||||
| Z-Image | `Tongyi-MAI/Z-Image-Turbo` | Yes | Yes | All layers | None | ✅︎ |
|
| Z-Image | `Tongyi-MAI/Z-Image-Turbo` | Yes | Yes | All layers | None | ✅︎ |
|
||||||
| FLUX.1 | `black-forest-labs/FLUX.1-dev`, `black-forest-labs/FLUX.1-schnell` | Yes | Yes | All layers | None | |
|
| FLUX.1 | `black-forest-labs/FLUX.1-dev`, `black-forest-labs/FLUX.1-schnell` | Yes | Yes | All layers | None | |
|
||||||
| FLUX.2-dev | `black-forest-labs/FLUX.2-dev` | Yes | Not validated | All layers | None | ✅︎ |
|
| FLUX.2-dev | `black-forest-labs/FLUX.2-dev` | Yes | Not validated | All layers | None | ✅︎ |
|
||||||
@@ -95,7 +93,7 @@ warmup_quack_fp8([(14040, 2048, 6144), (14040, 2048, 2048)])
|
|||||||
| HunyuanImage-3.0 | `tencent/HunyuanImage-3.0`, `tencent/HunyuanImage-3.0-Instruct` | Yes | Yes | All layers; use the Hunyuan stage config for multi-stage runs | None | |
|
| HunyuanImage-3.0 | `tencent/HunyuanImage-3.0`, `tencent/HunyuanImage-3.0-Instruct` | Yes | Yes | All layers; use the Hunyuan stage config for multi-stage runs | None | |
|
||||||
| HunyuanVideo-1.5 | `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v`, `720p_t2v`, `480p_i2v` | Yes | Yes | All layers | None | |
|
| HunyuanVideo-1.5 | `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v`, `720p_t2v`, `480p_i2v` | Yes | Yes | All layers | None | |
|
||||||
| Cosmos3 | `nvidia/Cosmos3-Nano`, `nvidia/Cosmos3-Super` | Yes | Not validated | All layers | None | |
|
| Cosmos3 | `nvidia/Cosmos3-Nano`, `nvidia/Cosmos3-Super` | Yes | Not validated | All layers | None | |
|
||||||
| MiniMax-H3 | `MiniMaxAI/MiniMax-H3` (`FL2VA` / `Ref2VA`) | Yes | Not validated | `quantization="fp8"` quantizes eligible DiT and text-encoder linears; mixed-precision input/output heads stay FP32 | None | ✅︎ |
|
| MiniMax H3 | `MiniMaxAI/MiniMax-H3` | Yes | Not validated | DiT linears except patch, timestep, and final projections; not compatible with layerwise offload | None | |
|
||||||
|
|
||||||
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
||||||
|
|
||||||
@@ -138,39 +136,6 @@ outputs = omni.generate(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Global and per-component scope
|
|
||||||
|
|
||||||
A plain, global quantization configuration is passed unchanged to every
|
|
||||||
quantization-aware component constructed by a pipeline. This includes an
|
|
||||||
eligible encoder when that encoder is implemented with vLLM quantizable
|
|
||||||
layers; it does not rewrite arbitrary `torch.nn` modules. A structured
|
|
||||||
component map is the only way to narrow that scope. Pipeline integrations that
|
|
||||||
do not yet expose an encoder through the quantization factory remain DiT-only.
|
|
||||||
|
|
||||||
For a pipeline that exposes both a transformer and a quantization-aware text
|
|
||||||
encoder, the scope is:
|
|
||||||
|
|
||||||
| Configuration | Transformer | Text encoder | Components without supported quantizable layers |
|
|
||||||
|---------------|-------------|--------------|-------------------------------------------------|
|
|
||||||
| `quantization="fp8"` | FP8 | FP8 | checkpoint precision |
|
|
||||||
| `{"transformer": {"method": "fp8"}}` | FP8 | checkpoint precision | checkpoint precision |
|
|
||||||
| `{"text_encoder": {"method": "fp8"}}` | checkpoint precision | FP8 | checkpoint precision |
|
|
||||||
|
|
||||||
Use `quantization_config` for component-selective Python configuration. For
|
|
||||||
example, quantize only the text decoder:
|
|
||||||
|
|
||||||
```python
|
|
||||||
omni = Omni(
|
|
||||||
model="<your-model>",
|
|
||||||
quantization_config={"text_encoder": {"method": "fp8"}},
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Component keys are runtime prefixes exposed by the pipeline integration; common
|
|
||||||
keys include `transformer` and `text_encoder`. Entries may be combined, and
|
|
||||||
`ignored_layers` can keep named eligible layers in checkpoint precision. Check
|
|
||||||
the model recipe for supported components and their runtime prefixes.
|
|
||||||
|
|
||||||
CLI:
|
CLI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -179,24 +144,6 @@ python text_to_image.py --model <your-model> --quantization fp8 --ignored-layers
|
|||||||
vllm serve <your-model> --omni --quantization fp8
|
vllm serve <your-model> --omni --quantization fp8
|
||||||
```
|
```
|
||||||
|
|
||||||
### Distributed layerwise offload
|
|
||||||
|
|
||||||
Per-tensor online FP8 linears can be combined with DLO's default AllGather path:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm serve <your-model> \
|
|
||||||
--omni \
|
|
||||||
--quantization fp8 \
|
|
||||||
--enable-distributed-layerwise-offload
|
|
||||||
```
|
|
||||||
|
|
||||||
Direct checkpoint mmap is not used for this combination. Each rank runs the
|
|
||||||
ordinary loader to create the final FP8 weights and scales before DLO shards
|
|
||||||
them, so startup temporarily materializes the complete FP8 model in host memory
|
|
||||||
on every rank. Runtime DLO host residency is sharded normally. Use
|
|
||||||
`--dlo-no-use-allgather` when the synchronized AllGather request-wave contract
|
|
||||||
does not fit the workload.
|
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
| Parameter | Type | Default | Description |
|
| Parameter | Type | Default | Description |
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from vllm.assets.video import VideoAsset, video_to_ndarrays
|
|||||||
from vllm.multimodal.media.audio import load_audio
|
from vllm.multimodal.media.audio import load_audio
|
||||||
from vllm.utils.argparse_utils import FlexibleArgumentParser
|
from vllm.utils.argparse_utils import FlexibleArgumentParser
|
||||||
|
|
||||||
|
from vllm_omni.engine.arg_utils import nullify_stage_engine_defaults
|
||||||
from vllm_omni.entrypoints.omni import Omni
|
from vllm_omni.entrypoints.omni import Omni
|
||||||
from vllm_omni.model_executor.stage_input_processors.aura_omni import (
|
from vllm_omni.model_executor.stage_input_processors.aura_omni import (
|
||||||
DEFAULT_QWEN3_TTS_REF_TEXT,
|
DEFAULT_QWEN3_TTS_REF_TEXT,
|
||||||
@@ -227,6 +228,7 @@ def parse_args():
|
|||||||
parser.add_argument("--init-timeout", type=int, default=2000, help="Overall initialization timeout.")
|
parser.add_argument("--init-timeout", type=int, default=2000, help="Overall initialization timeout.")
|
||||||
parser.add_argument("--stage-init-timeout", type=int, default=2000, help="Per-stage initialization timeout.")
|
parser.add_argument("--stage-init-timeout", type=int, default=2000, help="Per-stage initialization timeout.")
|
||||||
parser.add_argument("--log-stats", action="store_true", default=False, help="Enable stats logging.")
|
parser.add_argument("--log-stats", action="store_true", default=False, help="Enable stats logging.")
|
||||||
|
nullify_stage_engine_defaults(parser)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,288 +0,0 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
||||||
"""Generate a LingBot-World v2 video from an image and camera trajectory.
|
|
||||||
|
|
||||||
The official checkpoint is licensed separately under CC BY-NC-SA and is
|
|
||||||
restricted to non-commercial use. This vLLM-Omni integration remains
|
|
||||||
Apache-2.0 licensed.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
python examples/offline_inference/diffusion/lingbot_world_v2.py \
|
|
||||||
--prompt "The camera moves slowly forward through the scene." \
|
|
||||||
--image /path/to/first_frame.png \
|
|
||||||
--action-dir /path/to/actions/forward \
|
|
||||||
--output lingbot_world_v2.mp4
|
|
||||||
|
|
||||||
``--action-dir`` must contain ``poses.npy`` and ``intrinsics.npy``. The
|
|
||||||
example configures its parent as the trusted action root and sends the
|
|
||||||
contained directory through ``extra_args["action_path"]``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import math
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
_MODEL = "robbyant/lingbot-world-v2-14b-causal-fast-diffusers"
|
|
||||||
_NUM_INFERENCE_STEPS = 4
|
|
||||||
_MAX_SEQUENCE_LENGTH = 512
|
|
||||||
_MAX_PIXEL_AREA = 480 * 832
|
|
||||||
_MAX_RAW_FRAMES = 117
|
|
||||||
_MAX_SOURCE_CAMERA_FRAMES = 4096
|
|
||||||
_TEMPORAL_COMPRESSION = 4
|
|
||||||
_LATENT_FRAMES_PER_BLOCK = 3
|
|
||||||
_SPATIAL_ALIGNMENT = 16
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class LingBotPaths:
|
|
||||||
image: Path
|
|
||||||
action_dir: Path
|
|
||||||
action_root: Path
|
|
||||||
action_relative: Path
|
|
||||||
camera_frames: int
|
|
||||||
output: Path
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_camera_arrays(action_dir: Path) -> int:
|
|
||||||
"""Validate the bounded camera contract before starting the engine."""
|
|
||||||
|
|
||||||
try:
|
|
||||||
poses = np.load(action_dir / "poses.npy", mmap_mode="r", allow_pickle=False)
|
|
||||||
intrinsics = np.load(action_dir / "intrinsics.npy", mmap_mode="r", allow_pickle=False)
|
|
||||||
except (OSError, TypeError, ValueError):
|
|
||||||
raise ValueError("Camera files must be valid numeric poses.npy and intrinsics.npy arrays.") from None
|
|
||||||
if poses.ndim != 3 or poses.shape[1:] != (4, 4):
|
|
||||||
raise ValueError("poses.npy must have shape [frames, 4, 4].")
|
|
||||||
if intrinsics.ndim != 2 or intrinsics.shape[1:] != (4,):
|
|
||||||
raise ValueError("intrinsics.npy must have shape [frames, 4].")
|
|
||||||
if poses.shape[0] != intrinsics.shape[0]:
|
|
||||||
raise ValueError("poses.npy and intrinsics.npy must contain the same frame count.")
|
|
||||||
if not 1 <= poses.shape[0] <= _MAX_SOURCE_CAMERA_FRAMES:
|
|
||||||
raise ValueError("Camera arrays must contain between 1 and 4096 source frames.")
|
|
||||||
if poses.dtype.kind not in "fiu" or intrinsics.dtype.kind not in "fiu":
|
|
||||||
raise ValueError("Camera arrays must contain real numeric values.")
|
|
||||||
if not np.isfinite(poses).all() or not np.isfinite(intrinsics).all():
|
|
||||||
raise ValueError("Camera arrays must contain only finite values.")
|
|
||||||
return int(poses.shape[0])
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Generate a LingBot-World v2 video from a first-frame image and camera trajectory."
|
|
||||||
)
|
|
||||||
parser.add_argument("--model", default=_MODEL, help="Official Hugging Face model ID or local checkpoint path.")
|
|
||||||
parser.add_argument("--prompt", required=True, help="Text description of the scene and desired motion.")
|
|
||||||
parser.add_argument("--image", required=True, help="Path to the first-frame image.")
|
|
||||||
parser.add_argument(
|
|
||||||
"--action-dir",
|
|
||||||
required=True,
|
|
||||||
help="Camera action directory containing poses.npy and intrinsics.npy.",
|
|
||||||
)
|
|
||||||
parser.add_argument("--height", type=int, default=480, help="Output height; must be divisible by 16.")
|
|
||||||
parser.add_argument("--width", type=int, default=832, help="Output width; must be divisible by 16.")
|
|
||||||
parser.add_argument(
|
|
||||||
"--num-frames",
|
|
||||||
type=int,
|
|
||||||
default=9,
|
|
||||||
help="Raw output frame count. Nine frames are one three-latent-frame causal block.",
|
|
||||||
)
|
|
||||||
parser.add_argument("--seed", type=int, default=42, help="Random seed.")
|
|
||||||
parser.add_argument(
|
|
||||||
"--tensor-parallel-size",
|
|
||||||
type=int,
|
|
||||||
default=1,
|
|
||||||
help="Number of GPUs used for tensor parallelism inside the DiT.",
|
|
||||||
)
|
|
||||||
parser.add_argument("--flow-shift", type=float, default=5.0, help="Positive FlowUniPC scheduler shift.")
|
|
||||||
parser.add_argument("--fps", type=int, default=16, help="Frames per second in the exported MP4.")
|
|
||||||
parser.add_argument("--output", default="lingbot_world_v2.mp4", help="Output MP4 path.")
|
|
||||||
parser.add_argument(
|
|
||||||
"--enforce-eager",
|
|
||||||
action="store_true",
|
|
||||||
help="Disable compilation and force eager execution.",
|
|
||||||
)
|
|
||||||
return parser.parse_args(argv)
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_cli_paths(args: argparse.Namespace) -> LingBotPaths:
|
|
||||||
"""Canonicalize local inputs and construct a safe trusted-root mapping."""
|
|
||||||
|
|
||||||
image = Path(args.image).expanduser().resolve()
|
|
||||||
if not image.is_file():
|
|
||||||
raise ValueError("--image must point to an existing file.")
|
|
||||||
|
|
||||||
action_dir = Path(args.action_dir).expanduser().resolve()
|
|
||||||
if not action_dir.is_dir():
|
|
||||||
raise ValueError("--action-dir must point to an existing directory.")
|
|
||||||
if not all((action_dir / name).is_file() for name in ("poses.npy", "intrinsics.npy")):
|
|
||||||
raise ValueError("--action-dir must contain poses.npy and intrinsics.npy.")
|
|
||||||
camera_frames = _validate_camera_arrays(action_dir)
|
|
||||||
|
|
||||||
action_root = action_dir.parent.resolve()
|
|
||||||
try:
|
|
||||||
action_relative = action_dir.relative_to(action_root)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError("--action-dir must be contained by its canonical trusted action root.") from None
|
|
||||||
if not action_relative.parts:
|
|
||||||
raise ValueError("--action-dir must name a directory contained by a trusted action root.")
|
|
||||||
|
|
||||||
output = Path(args.output).expanduser().resolve()
|
|
||||||
return LingBotPaths(
|
|
||||||
image=image,
|
|
||||||
action_dir=action_dir,
|
|
||||||
action_root=action_root,
|
|
||||||
action_relative=action_relative,
|
|
||||||
camera_frames=camera_frames,
|
|
||||||
output=output,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _positive_finite(value: float, flag: str) -> float:
|
|
||||||
result = float(value)
|
|
||||||
if not math.isfinite(result) or result <= 0:
|
|
||||||
raise ValueError(f"{flag} must be a positive finite number.")
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def build_omni_kwargs(
|
|
||||||
args: argparse.Namespace,
|
|
||||||
paths: LingBotPaths,
|
|
||||||
*,
|
|
||||||
parallel_config: Any,
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""Build Omni configuration without overriding checkpoint class discovery."""
|
|
||||||
|
|
||||||
if args.tensor_parallel_size <= 0:
|
|
||||||
raise ValueError("--tensor-parallel-size must be a positive integer.")
|
|
||||||
flow_shift = _positive_finite(args.flow_shift, "--flow-shift")
|
|
||||||
model_path = Path(args.model).expanduser()
|
|
||||||
model = str(model_path.resolve()) if model_path.exists() else args.model
|
|
||||||
return {
|
|
||||||
"model": model,
|
|
||||||
"flow_shift": flow_shift,
|
|
||||||
"parallel_config": parallel_config,
|
|
||||||
"enforce_eager": args.enforce_eager,
|
|
||||||
"model_config": {"lingbot_action_root": str(paths.action_root)},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def build_request(
|
|
||||||
args: argparse.Namespace,
|
|
||||||
paths: LingBotPaths,
|
|
||||||
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
||||||
"""Build the prompt and fixed v1 causal-DMD sampling contract."""
|
|
||||||
|
|
||||||
prompt_text = args.prompt.strip()
|
|
||||||
if not prompt_text:
|
|
||||||
raise ValueError("--prompt must contain non-whitespace text.")
|
|
||||||
if args.height <= 0 or args.width <= 0:
|
|
||||||
raise ValueError("--height and --width must be positive integers.")
|
|
||||||
if args.height % _SPATIAL_ALIGNMENT or args.width % _SPATIAL_ALIGNMENT:
|
|
||||||
raise ValueError("--height and --width must each be divisible by 16.")
|
|
||||||
if args.height * args.width > _MAX_PIXEL_AREA:
|
|
||||||
raise ValueError("--height * --width must not exceed 480 * 832 pixels.")
|
|
||||||
if args.num_frames <= 0:
|
|
||||||
raise ValueError("--num-frames must be a positive integer.")
|
|
||||||
if args.num_frames > _MAX_RAW_FRAMES:
|
|
||||||
raise ValueError("--num-frames must not exceed 117 raw frames.")
|
|
||||||
if args.num_frames > paths.camera_frames:
|
|
||||||
raise ValueError("Camera arrays must contain at least --num-frames frames.")
|
|
||||||
if (args.num_frames - 1) % _TEMPORAL_COMPRESSION:
|
|
||||||
raise ValueError("--num-frames must satisfy (num_frames - 1) divisible by 4.")
|
|
||||||
latent_frames = (args.num_frames - 1) // _TEMPORAL_COMPRESSION + 1
|
|
||||||
if latent_frames % _LATENT_FRAMES_PER_BLOCK:
|
|
||||||
raise ValueError("--num-frames must map to whole three-frame latent blocks.")
|
|
||||||
if args.fps <= 0:
|
|
||||||
raise ValueError("--fps must be a positive integer.")
|
|
||||||
flow_shift = _positive_finite(args.flow_shift, "--flow-shift")
|
|
||||||
|
|
||||||
prompt = {
|
|
||||||
"prompt": prompt_text,
|
|
||||||
"multi_modal_data": {"image": str(paths.image)},
|
|
||||||
}
|
|
||||||
sampling_kwargs = {
|
|
||||||
"height": args.height,
|
|
||||||
"width": args.width,
|
|
||||||
"num_frames": args.num_frames,
|
|
||||||
"num_inference_steps": _NUM_INFERENCE_STEPS,
|
|
||||||
"max_sequence_length": _MAX_SEQUENCE_LENGTH,
|
|
||||||
"seed": args.seed,
|
|
||||||
"fps": args.fps,
|
|
||||||
"extra_args": {
|
|
||||||
"action_path": paths.action_relative.as_posix(),
|
|
||||||
"flow_shift": flow_shift,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return prompt, sampling_kwargs
|
|
||||||
|
|
||||||
|
|
||||||
def extract_video_array(outputs: Any) -> np.ndarray:
|
|
||||||
"""Unwrap one standard Omni diffusion video into ``[frames, H, W, C]``."""
|
|
||||||
|
|
||||||
if not isinstance(outputs, list) or len(outputs) != 1:
|
|
||||||
raise ValueError("Expected exactly one Omni output for the single LingBot request.")
|
|
||||||
output = outputs[0]
|
|
||||||
request_output = getattr(output, "request_output", None)
|
|
||||||
if request_output is not None and getattr(request_output, "images", None):
|
|
||||||
output = request_output
|
|
||||||
|
|
||||||
images = getattr(output, "images", None)
|
|
||||||
if not images:
|
|
||||||
multimodal_output = getattr(output, "multimodal_output", None) or {}
|
|
||||||
images = multimodal_output.get("video")
|
|
||||||
if images is None:
|
|
||||||
raise ValueError("No video frames found in Omni output.")
|
|
||||||
if isinstance(images, list) and len(images) == 1:
|
|
||||||
images = images[0]
|
|
||||||
|
|
||||||
video: np.ndarray = np.asarray(images)
|
|
||||||
if video.ndim == 5:
|
|
||||||
if video.shape[0] != 1:
|
|
||||||
raise ValueError(f"Expected one generated video, got batch shape {video.shape}.")
|
|
||||||
video = video[0]
|
|
||||||
if video.ndim != 4 or video.shape[-1] not in (3, 4):
|
|
||||||
raise ValueError(f"Expected video shape [frames, height, width, channels], got {video.shape}.")
|
|
||||||
return video
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> Path:
|
|
||||||
args = parse_args(argv)
|
|
||||||
paths = resolve_cli_paths(args)
|
|
||||||
prompt, sampling_kwargs = build_request(args, paths)
|
|
||||||
paths.output.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
# Keep vLLM imports below pure CLI/path validation so ``--help`` and the
|
|
||||||
# helper tests work on machines without the native vLLM runtime.
|
|
||||||
from diffusers.utils import export_to_video
|
|
||||||
|
|
||||||
from vllm_omni.diffusion.data import DiffusionParallelConfig
|
|
||||||
from vllm_omni.entrypoints.omni import Omni
|
|
||||||
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
|
||||||
|
|
||||||
parallel_config = DiffusionParallelConfig(tensor_parallel_size=args.tensor_parallel_size)
|
|
||||||
omni_kwargs = build_omni_kwargs(args, paths, parallel_config=parallel_config)
|
|
||||||
omni = Omni(**omni_kwargs)
|
|
||||||
try:
|
|
||||||
outputs = omni.generate(
|
|
||||||
prompt,
|
|
||||||
OmniDiffusionSamplingParams(**sampling_kwargs),
|
|
||||||
use_tqdm=False,
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
omni.close()
|
|
||||||
|
|
||||||
frames = extract_video_array(outputs)
|
|
||||||
export_to_video(frames, str(paths.output), fps=args.fps)
|
|
||||||
print(f"Saved {frames.shape[0]} frames to {paths.output}")
|
|
||||||
return paths.output
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
||||||
"""Run LingBot-World 2.0 as an in-process realtime AR-Diffusion session.
|
|
||||||
|
|
||||||
This example exercises the internal session API. It is not a public HTTP or
|
|
||||||
WebSocket protocol. Each JSONL event advances the world by one three-latent
|
|
||||||
frame AR block and writes the latent plus its identity metadata. The current
|
|
||||||
scoped implementation supports at most ten ticks per generation epoch because
|
|
||||||
its image condition is bounded to 117 pixel frames; reset or create a session
|
|
||||||
to start a new world.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import asyncio
|
|
||||||
import json
|
|
||||||
import math
|
|
||||||
import time
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
_MODEL = "robbyant/lingbot-world-v2-14b-causal-fast-diffusers"
|
|
||||||
_CAMERA_ACTION_SCHEMA = "lingbot.camera_actions.v1"
|
|
||||||
_FRAMES_PER_BLOCK = 3
|
|
||||||
# ((117 pixel frames - 1) / VAE temporal factor 4 + 1) / 3 latent frames.
|
|
||||||
_MAX_REALTIME_TICKS = 10
|
|
||||||
|
|
||||||
|
|
||||||
def _camera_event_data(frames: list[list[str]]) -> dict[str, Any]:
|
|
||||||
"""Build the event-side script consumed by LingBotCameraControlReducer."""
|
|
||||||
return {"mode": "script", "frames": frames}
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
||||||
parser = argparse.ArgumentParser(description="Run an in-process realtime LingBot-World 2.0 session.")
|
|
||||||
parser.add_argument("--model", default=_MODEL, help="Hugging Face model ID or local checkpoint path.")
|
|
||||||
parser.add_argument("--image", required=True, help="Initial RGB image.")
|
|
||||||
parser.add_argument("--prompt", required=True, help="Initial scene prompt.")
|
|
||||||
parser.add_argument(
|
|
||||||
"--events",
|
|
||||||
required=True,
|
|
||||||
help="JSONL file with one prompt/action event per AR block; at most 10 events.",
|
|
||||||
)
|
|
||||||
parser.add_argument("--output-dir", required=True, help="Directory for chunk latents and metadata.")
|
|
||||||
parser.add_argument("--session-id", default="lingbot-world", help="Persistent world session identifier.")
|
|
||||||
parser.add_argument("--height", type=int, default=480)
|
|
||||||
parser.add_argument("--width", type=int, default=832)
|
|
||||||
parser.add_argument("--seed", type=int, default=42)
|
|
||||||
parser.add_argument("--gpu-memory-fraction", type=float, default=0.1)
|
|
||||||
parser.add_argument("--tensor-parallel-size", type=int, default=1)
|
|
||||||
parser.add_argument("--enforce-eager", action="store_true")
|
|
||||||
return parser.parse_args(argv)
|
|
||||||
|
|
||||||
|
|
||||||
def _load_events(path: Path) -> list[dict[str, Any]]:
|
|
||||||
events: list[dict[str, Any]] = []
|
|
||||||
for line_number, raw_line in enumerate(path.read_text().splitlines(), start=1):
|
|
||||||
line = raw_line.strip()
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
value = json.loads(line)
|
|
||||||
except json.JSONDecodeError as exc:
|
|
||||||
raise ValueError(f"events line {line_number} is not valid JSON: {exc.msg}.") from None
|
|
||||||
if not isinstance(value, dict):
|
|
||||||
raise ValueError(f"events line {line_number} must be a JSON object.")
|
|
||||||
event_id = value.get("event_id")
|
|
||||||
if isinstance(event_id, bool) or not isinstance(event_id, int) or event_id < 0:
|
|
||||||
raise ValueError(f"events line {line_number} requires a non-negative integer event_id.")
|
|
||||||
prompt = value.get("prompt")
|
|
||||||
if prompt is not None and (not isinstance(prompt, str) or not prompt.strip()):
|
|
||||||
raise ValueError(f"events line {line_number} prompt must be a non-empty string.")
|
|
||||||
frames = value.get("frames")
|
|
||||||
if frames is not None:
|
|
||||||
if not isinstance(frames, list) or len(frames) != _FRAMES_PER_BLOCK:
|
|
||||||
raise ValueError(f"events line {line_number} frames must contain exactly three lists.")
|
|
||||||
for frame in frames:
|
|
||||||
if not isinstance(frame, list) or any(not isinstance(action, str) for action in frame):
|
|
||||||
raise ValueError(f"events line {line_number} frames must contain only action strings.")
|
|
||||||
if prompt is None and frames is None:
|
|
||||||
raise ValueError(f"events line {line_number} must update prompt and/or frames.")
|
|
||||||
events.append({"event_id": event_id, "prompt": prompt, "frames": frames})
|
|
||||||
if not events:
|
|
||||||
raise ValueError("events file must contain at least one event.")
|
|
||||||
if len(events) > _MAX_REALTIME_TICKS:
|
|
||||||
raise ValueError(
|
|
||||||
"events file must contain at most "
|
|
||||||
f"{_MAX_REALTIME_TICKS} events because the current LingBot realtime "
|
|
||||||
"image-condition horizon is 117 pixel frames."
|
|
||||||
)
|
|
||||||
event_ids = [event["event_id"] for event in events]
|
|
||||||
if event_ids != sorted(set(event_ids)):
|
|
||||||
raise ValueError("event_id values must be unique and strictly increasing.")
|
|
||||||
return events
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_args(args: argparse.Namespace) -> tuple[Path, Path, Path]:
|
|
||||||
image = Path(args.image).expanduser().resolve()
|
|
||||||
events = Path(args.events).expanduser().resolve()
|
|
||||||
output_dir = Path(args.output_dir).expanduser().resolve()
|
|
||||||
if not image.is_file():
|
|
||||||
raise ValueError("--image must point to an existing file.")
|
|
||||||
if not events.is_file():
|
|
||||||
raise ValueError("--events must point to an existing JSONL file.")
|
|
||||||
if not args.prompt.strip():
|
|
||||||
raise ValueError("--prompt must contain non-whitespace text.")
|
|
||||||
if args.height <= 0 or args.width <= 0 or args.height % 16 or args.width % 16:
|
|
||||||
raise ValueError("--height and --width must be positive multiples of 16.")
|
|
||||||
if args.tensor_parallel_size <= 0:
|
|
||||||
raise ValueError("--tensor-parallel-size must be positive.")
|
|
||||||
if not math.isfinite(args.gpu_memory_fraction) or not 0 < args.gpu_memory_fraction <= 1:
|
|
||||||
raise ValueError("--gpu-memory-fraction must be in (0, 1].")
|
|
||||||
return image, events, output_dir
|
|
||||||
|
|
||||||
|
|
||||||
async def run(argv: Sequence[str] | None = None) -> Path:
|
|
||||||
args = parse_args(argv)
|
|
||||||
image, events_path, output_dir = _validate_args(args)
|
|
||||||
events = _load_events(events_path)
|
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
# Imports stay below pure input validation so --help and helper tests do not
|
|
||||||
# require a CUDA-enabled vLLM installation.
|
|
||||||
import torch
|
|
||||||
|
|
||||||
from vllm_omni.diffusion.data import DiffusionParallelConfig
|
|
||||||
from vllm_omni.diffusion.models.lingbot_world.actions import LingBotCameraControlReducer
|
|
||||||
from vllm_omni.entrypoints.async_omni import AsyncOmni
|
|
||||||
from vllm_omni.experimental.ar_diffusion.consumer import ARDiffusionOmniTickConsumer
|
|
||||||
from vllm_omni.experimental.ar_diffusion.session import (
|
|
||||||
ARDiffusionSessionEvent,
|
|
||||||
ARDiffusionSessionManager,
|
|
||||||
ARDiffusionWorkerLifecycle,
|
|
||||||
)
|
|
||||||
from vllm_omni.experimental.ar_diffusion.tick_protocol import ARDiffusionControlInput
|
|
||||||
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
|
||||||
|
|
||||||
engine = AsyncOmni(
|
|
||||||
model=args.model,
|
|
||||||
engine_backend="vllm_omni.experimental.ar_diffusion.engine.ARDiffusionEngine",
|
|
||||||
enforce_eager=args.enforce_eager,
|
|
||||||
parallel_config=DiffusionParallelConfig(tensor_parallel_size=args.tensor_parallel_size),
|
|
||||||
max_num_seqs=1,
|
|
||||||
model_config={
|
|
||||||
"ar_diffusion_height": args.height,
|
|
||||||
"ar_diffusion_width": args.width,
|
|
||||||
"ar_diffusion_kv_config": {
|
|
||||||
"gpu_memory_fraction": args.gpu_memory_fraction,
|
|
||||||
"warmup_cudagraph": True,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
sampling = OmniDiffusionSamplingParams(
|
|
||||||
height=args.height,
|
|
||||||
width=args.width,
|
|
||||||
num_frames=9,
|
|
||||||
num_inference_steps=4,
|
|
||||||
max_sequence_length=512,
|
|
||||||
seed=args.seed,
|
|
||||||
output_type="latent",
|
|
||||||
extra_args={"flow_shift": 5.0},
|
|
||||||
)
|
|
||||||
consumer = ARDiffusionOmniTickConsumer(
|
|
||||||
engine,
|
|
||||||
prompt_provider=lambda tick: {
|
|
||||||
"prompt": tick.prompt,
|
|
||||||
"multi_modal_data": {"image": str(image)},
|
|
||||||
},
|
|
||||||
sampling_params_list=[sampling],
|
|
||||||
diffusion_stage_id=0,
|
|
||||||
)
|
|
||||||
manager = ARDiffusionSessionManager(
|
|
||||||
tick_consumer=consumer,
|
|
||||||
lifecycle=ARDiffusionWorkerLifecycle(engine, stage_ids=[0], timeout=180.0),
|
|
||||||
max_pending_events=32,
|
|
||||||
control_reducer_factory=LingBotCameraControlReducer,
|
|
||||||
)
|
|
||||||
session = await manager.create_session(args.session_id)
|
|
||||||
measurements: list[dict[str, Any]] = []
|
|
||||||
try:
|
|
||||||
for chunk_index, event in enumerate(events):
|
|
||||||
controls = ()
|
|
||||||
if event["frames"] is not None:
|
|
||||||
controls = (
|
|
||||||
ARDiffusionControlInput(
|
|
||||||
track="camera",
|
|
||||||
schema=_CAMERA_ACTION_SCHEMA,
|
|
||||||
data=_camera_event_data(event["frames"]),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
await session.accept_event(
|
|
||||||
ARDiffusionSessionEvent(
|
|
||||||
event_id=event["event_id"],
|
|
||||||
prompt=event["prompt"]
|
|
||||||
if event["prompt"] is not None
|
|
||||||
else (args.prompt if chunk_index == 0 else None),
|
|
||||||
controls=controls,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
started = time.perf_counter()
|
|
||||||
output = await session.next_chunk()
|
|
||||||
elapsed = time.perf_counter() - started
|
|
||||||
if len(output.images) != 1 or not isinstance(output.images[0], torch.Tensor):
|
|
||||||
raise RuntimeError("Expected one latent tensor from each realtime LingBot chunk.")
|
|
||||||
latent = output.images[0].detach().float().cpu()
|
|
||||||
metadata = consumer.chunk_metadata(output).to_dict()
|
|
||||||
torch.save(latent, output_dir / f"chunk_{chunk_index:03d}.pt")
|
|
||||||
(output_dir / f"chunk_{chunk_index:03d}.json").write_text(
|
|
||||||
json.dumps(metadata, indent=2, sort_keys=True) + "\n"
|
|
||||||
)
|
|
||||||
measurements.append(
|
|
||||||
{
|
|
||||||
"chunk_index": chunk_index,
|
|
||||||
"latency_seconds": elapsed,
|
|
||||||
"shape": list(latent.shape),
|
|
||||||
"finite": bool(torch.isfinite(latent).all()),
|
|
||||||
"metadata": metadata,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
print(json.dumps(measurements[-1], sort_keys=True), flush=True)
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
await manager.close_session(args.session_id)
|
|
||||||
finally:
|
|
||||||
engine.shutdown()
|
|
||||||
|
|
||||||
summary_path = output_dir / "summary.json"
|
|
||||||
summary_path.write_text(json.dumps({"chunks": measurements}, indent=2, sort_keys=True) + "\n")
|
|
||||||
return summary_path
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Sequence[str] | None = None) -> Path:
|
|
||||||
return asyncio.run(run(argv))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -96,7 +96,7 @@ if __name__ == "__main__":
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
from diffusers.utils import export_to_video
|
from diffusers.utils import export_to_video
|
||||||
frames = outputs[0].images
|
frames = outputs[0].request_output.images
|
||||||
export_to_video(frames, "quick_test_i2v.mp4", fps=16)
|
export_to_video(frames, "quick_test_i2v.mp4", fps=16)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ from vllm_omni.model_extras import (
|
|||||||
from vllm_omni.model_extras import (
|
from vllm_omni.model_extras import (
|
||||||
get_extra_body_params,
|
get_extra_body_params,
|
||||||
get_model_class_name,
|
get_model_class_name,
|
||||||
should_preserve_reference_image_size,
|
|
||||||
)
|
)
|
||||||
from vllm_omni.outputs import OmniRequestOutput
|
from vllm_omni.outputs import OmniRequestOutput
|
||||||
from vllm_omni.platforms import current_omni_platform
|
from vllm_omni.platforms import current_omni_platform
|
||||||
@@ -455,13 +454,7 @@ def main():
|
|||||||
|
|
||||||
media_inputs: dict[str, Any] = {}
|
media_inputs: dict[str, Any] = {}
|
||||||
if image is not None:
|
if image is not None:
|
||||||
preserve_image_size = should_preserve_reference_image_size(
|
media_inputs["image"] = image.resize((width, height), PIL.Image.Resampling.LANCZOS)
|
||||||
model_class_name,
|
|
||||||
model=args.model,
|
|
||||||
)
|
|
||||||
media_inputs["image"] = (
|
|
||||||
image if preserve_image_size else image.resize((width, height), PIL.Image.Resampling.LANCZOS)
|
|
||||||
)
|
|
||||||
if last_image is not None:
|
if last_image is not None:
|
||||||
media_inputs["last_image"] = last_image.resize((width, height), PIL.Image.Resampling.LANCZOS)
|
media_inputs["last_image"] = last_image.resize((width, height), PIL.Image.Resampling.LANCZOS)
|
||||||
if mask_image is not None:
|
if mask_image is not None:
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ def parse_args():
|
|||||||
# ``max_num_seqs=1`` …) are passed as flat kwargs to ``Omni`` below
|
# ``max_num_seqs=1`` …) are passed as flat kwargs to ``Omni`` below
|
||||||
# and ``create_default_diffusion`` materializes the stage config.
|
# and ``create_default_diffusion`` materializes the stage config.
|
||||||
|
|
||||||
|
from vllm_omni.engine.arg_utils import nullify_stage_engine_defaults
|
||||||
|
|
||||||
|
nullify_stage_engine_defaults(p)
|
||||||
return p.parse_args()
|
return p.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ def main(args):
|
|||||||
print(f"use_tts (tts_bos): {use_tts}")
|
print(f"use_tts (tts_bos): {use_tts}")
|
||||||
|
|
||||||
for stage_outputs in omni_generator:
|
for stage_outputs in omni_generator:
|
||||||
output = stage_outputs
|
output = stage_outputs.request_output
|
||||||
if stage_outputs.final_output_type == "text":
|
if stage_outputs.final_output_type == "text":
|
||||||
request_id = output.request_id
|
request_id = output.request_id
|
||||||
text_output = output.outputs[0].text
|
text_output = output.outputs[0].text
|
||||||
|
|||||||
+22
-57
@@ -1,32 +1,21 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||||
"""Validate MiniMax-H3 request-mode and DLO lifecycle behavior.
|
"""Reproduce MiniMax-H3 request-mode and DLO/DP2 lifecycle behavior.
|
||||||
|
|
||||||
The DLO modes accept any positive data-parallel size. With AllGather enabled,
|
|
||||||
the script submits one complete DP wave and verifies recovery after an invalid
|
|
||||||
wave. It also checks that every mode shuts down all worker processes cleanly.
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
VLLM_WORKER_MULTIPROC_METHOD=spawn \
|
VLLM_WORKER_MULTIPROC_METHOD=spawn \
|
||||||
VLLM_OMNI_VIDEO_SYNC_TIMEOUT=14400 \
|
VLLM_OMNI_VIDEO_SYNC_TIMEOUT=14400 \
|
||||||
VLLM_OMNI_DLO_DP_WAVE_TIMEOUT=600 \
|
VLLM_OMNI_DLO_DP_WAVE_TIMEOUT=600 \
|
||||||
CUDA_VISIBLE_DEVICES=0,1,2,3 \
|
|
||||||
python examples/offline_inference/minimax_h3/dlo_lifecycle.py \
|
|
||||||
--model /path/to/MiniMax-H3/FL2VA --mode dlo --dp-size 4
|
|
||||||
|
|
||||||
# Rank-local mmap storage, with no DLO collective or DP wave scheduling.
|
|
||||||
VLLM_WORKER_MULTIPROC_METHOD=spawn \
|
|
||||||
VLLM_OMNI_VIDEO_SYNC_TIMEOUT=14400 \
|
|
||||||
CUDA_VISIBLE_DEVICES=0,1 \
|
CUDA_VISIBLE_DEVICES=0,1 \
|
||||||
python examples/offline_inference/minimax_h3/dlo_lifecycle.py \
|
python examples/offline_inference/minimax_h3/dlo_dp2_lifecycle.py \
|
||||||
--model /path/to/MiniMax-H3/FL2VA --mode dlo-no-allgather --dp-size 2
|
--model /path/to/MiniMax-H3/FL2VA --mode dlo-dp2
|
||||||
|
|
||||||
VLLM_WORKER_MULTIPROC_METHOD=spawn \
|
VLLM_WORKER_MULTIPROC_METHOD=spawn \
|
||||||
VLLM_OMNI_VIDEO_SYNC_TIMEOUT=14400 \
|
VLLM_OMNI_VIDEO_SYNC_TIMEOUT=14400 \
|
||||||
CUDA_VISIBLE_DEVICES=0,1 \
|
CUDA_VISIBLE_DEVICES=0,1 \
|
||||||
python examples/offline_inference/minimax_h3/dlo_lifecycle.py \
|
python examples/offline_inference/minimax_h3/dlo_dp2_lifecycle.py \
|
||||||
--model /path/to/MiniMax-H3/FL2VA --mode request --tp-size 2
|
--model /path/to/MiniMax-H3/FL2VA --mode request
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -51,33 +40,14 @@ DEFAULT_PROMPTS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def positive_int(value: str) -> int:
|
|
||||||
parsed = int(value)
|
|
||||||
if parsed < 1:
|
|
||||||
raise argparse.ArgumentTypeError("must be a positive integer")
|
|
||||||
return parsed
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args() -> argparse.Namespace:
|
def parse_args() -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument("--model", required=True, help="Path to MiniMax-H3/FL2VA")
|
parser.add_argument("--model", required=True, help="Path to MiniMax-H3/FL2VA")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--mode",
|
"--mode",
|
||||||
choices=("dlo", "dlo-no-allgather", "request"),
|
choices=("dlo-dp2", "request"),
|
||||||
required=True,
|
required=True,
|
||||||
help="DLO (with or without AllGather) or ordinary non-DLO request mode",
|
help="DLO AllGather DP2 or ordinary non-DLO TP2 request mode",
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--dp-size",
|
|
||||||
type=positive_int,
|
|
||||||
default=2,
|
|
||||||
help="DLO data-parallel size (for example 1, 2, 4, or 8; default: 2)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--tp-size",
|
|
||||||
type=positive_int,
|
|
||||||
default=2,
|
|
||||||
help="Tensor-parallel size used only by request mode (default: 2)",
|
|
||||||
)
|
)
|
||||||
parser.add_argument("--steps", type=int, default=2)
|
parser.add_argument("--steps", type=int, default=2)
|
||||||
parser.add_argument("--duration", type=float, default=5.0)
|
parser.add_argument("--duration", type=float, default=5.0)
|
||||||
@@ -90,11 +60,10 @@ def parse_args() -> argparse.Namespace:
|
|||||||
|
|
||||||
|
|
||||||
def engine_kwargs(args: argparse.Namespace) -> dict[str, Any]:
|
def engine_kwargs(args: argparse.Namespace) -> dict[str, Any]:
|
||||||
is_dlo = args.mode in {"dlo", "dlo-no-allgather"}
|
|
||||||
common: dict[str, Any] = {
|
common: dict[str, Any] = {
|
||||||
"model": args.model,
|
"model": args.model,
|
||||||
"trust_remote_code": True,
|
"trust_remote_code": True,
|
||||||
"num_gpus": args.dp_size if is_dlo else args.tp_size,
|
"num_gpus": 2,
|
||||||
"usp": 1,
|
"usp": 1,
|
||||||
"ring": 1,
|
"ring": 1,
|
||||||
"vae_parallel_mode": "tile",
|
"vae_parallel_mode": "tile",
|
||||||
@@ -105,22 +74,22 @@ def engine_kwargs(args: argparse.Namespace) -> dict[str, Any]:
|
|||||||
"stage_init_timeout": args.init_timeout,
|
"stage_init_timeout": args.init_timeout,
|
||||||
"init_timeout": args.init_timeout,
|
"init_timeout": args.init_timeout,
|
||||||
}
|
}
|
||||||
if is_dlo:
|
if args.mode == "dlo-dp2":
|
||||||
common.update(
|
common.update(
|
||||||
tensor_parallel_size=1,
|
tensor_parallel_size=1,
|
||||||
data_parallel_size=args.dp_size,
|
data_parallel_size=2,
|
||||||
text_encoder_tp_size=1,
|
text_encoder_tp_size=1,
|
||||||
vae_patch_parallel_size=1,
|
vae_patch_parallel_size=1,
|
||||||
enable_distributed_layerwise_offload=True,
|
enable_distributed_layerwise_offload=True,
|
||||||
dlo_use_allgather=args.mode == "dlo",
|
dlo_use_allgather=True,
|
||||||
dlo_resident_layers=0,
|
dlo_resident_layers=0,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
common.update(
|
common.update(
|
||||||
tensor_parallel_size=args.tp_size,
|
tensor_parallel_size=2,
|
||||||
data_parallel_size=1,
|
data_parallel_size=1,
|
||||||
text_encoder_tp_size=args.tp_size,
|
text_encoder_tp_size=2,
|
||||||
vae_patch_parallel_size=args.tp_size,
|
vae_patch_parallel_size=2,
|
||||||
enable_distributed_layerwise_offload=False,
|
enable_distributed_layerwise_offload=False,
|
||||||
)
|
)
|
||||||
return common
|
return common
|
||||||
@@ -203,8 +172,7 @@ async def run(args: argparse.Namespace) -> dict[str, Any]:
|
|||||||
"engine_kwargs": engine_kwargs(args),
|
"engine_kwargs": engine_kwargs(args),
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
uses_dp_wave = args.mode == "dlo" and args.dp_size > 1
|
if args.mode == "dlo-dp2":
|
||||||
if uses_dp_wave:
|
|
||||||
started = time.perf_counter()
|
started = time.perf_counter()
|
||||||
asymmetric = await asyncio.gather(
|
asymmetric = await asyncio.gather(
|
||||||
generate_one(
|
generate_one(
|
||||||
@@ -214,15 +182,12 @@ async def run(args: argparse.Namespace) -> dict[str, Any]:
|
|||||||
prompt="",
|
prompt="",
|
||||||
seed=1001,
|
seed=1001,
|
||||||
),
|
),
|
||||||
*(
|
|
||||||
generate_one(
|
generate_one(
|
||||||
engine,
|
engine,
|
||||||
args,
|
args,
|
||||||
request_id=f"invalid-peer-{index}",
|
request_id="invalid-peer",
|
||||||
prompt=DEFAULT_PROMPTS[index % len(DEFAULT_PROMPTS)],
|
prompt=DEFAULT_PROMPTS[0],
|
||||||
seed=1001 + index,
|
seed=1002,
|
||||||
)
|
|
||||||
for index in range(1, args.dp_size)
|
|
||||||
),
|
),
|
||||||
return_exceptions=True,
|
return_exceptions=True,
|
||||||
)
|
)
|
||||||
@@ -230,18 +195,18 @@ async def run(args: argparse.Namespace) -> dict[str, Any]:
|
|||||||
summary["asymmetric_errors"] = [
|
summary["asymmetric_errors"] = [
|
||||||
f"{type(result).__name__}: {result}" for result in asymmetric if isinstance(result, BaseException)
|
f"{type(result).__name__}: {result}" for result in asymmetric if isinstance(result, BaseException)
|
||||||
]
|
]
|
||||||
if len(summary["asymmetric_errors"]) != args.dp_size:
|
if len(summary["asymmetric_errors"]) != 2:
|
||||||
raise RuntimeError("Every request in the asymmetric DP wave must fail before dispatch")
|
raise RuntimeError("Both requests in the asymmetric DP wave must fail before dispatch")
|
||||||
|
|
||||||
started = time.perf_counter()
|
started = time.perf_counter()
|
||||||
request_count = args.dp_size if uses_dp_wave else 1
|
request_count = 2 if args.mode == "dlo-dp2" else 1
|
||||||
outputs = await asyncio.gather(
|
outputs = await asyncio.gather(
|
||||||
*(
|
*(
|
||||||
generate_one(
|
generate_one(
|
||||||
engine,
|
engine,
|
||||||
args,
|
args,
|
||||||
request_id=f"recovery-{index}",
|
request_id=f"recovery-{index}",
|
||||||
prompt=DEFAULT_PROMPTS[index % len(DEFAULT_PROMPTS)],
|
prompt=DEFAULT_PROMPTS[index],
|
||||||
seed=2000 + index,
|
seed=2000 + index,
|
||||||
)
|
)
|
||||||
for index in range(request_count)
|
for index in range(request_count)
|
||||||
@@ -67,7 +67,7 @@ def main() -> None:
|
|||||||
parser.add_argument("--output-dir", default="results/nemotron_voicechat")
|
parser.add_argument("--output-dir", default="results/nemotron_voicechat")
|
||||||
parser.add_argument("--system-prompt", default=DEFAULT_SYSTEM_PROMPT)
|
parser.add_argument("--system-prompt", default=DEFAULT_SYSTEM_PROMPT)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--deploy-config",
|
"--stage-configs-path",
|
||||||
default=None,
|
default=None,
|
||||||
help=(
|
help=(
|
||||||
"Deploy yaml override. Pass vllm_omni/deploy/nemotron_labs_voicechat_streaming.yaml "
|
"Deploy yaml override. Pass vllm_omni/deploy/nemotron_labs_voicechat_streaming.yaml "
|
||||||
@@ -134,8 +134,8 @@ def main() -> None:
|
|||||||
]
|
]
|
||||||
|
|
||||||
omni_kwargs: dict = {}
|
omni_kwargs: dict = {}
|
||||||
if args.deploy_config:
|
if args.stage_configs_path:
|
||||||
omni_kwargs["deploy_config"] = args.deploy_config
|
omni_kwargs["stage_configs_path"] = args.stage_configs_path
|
||||||
omni = Omni(model=str(ckpt), trust_remote_code=True, **omni_kwargs)
|
omni = Omni(model=str(ckpt), trust_remote_code=True, **omni_kwargs)
|
||||||
outputs = omni.generate(inputs, sampling_params_list)
|
outputs = omni.generate(inputs, sampling_params_list)
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ if __name__ == "__main__":
|
|||||||
omni = Omni(model="Qwen/Qwen-Image")
|
omni = Omni(model="Qwen/Qwen-Image")
|
||||||
prompt = "a cup of coffee on the table"
|
prompt = "a cup of coffee on the table"
|
||||||
outputs = omni.generate(prompt)
|
outputs = omni.generate(prompt)
|
||||||
images = outputs[0].images
|
images = outputs[0].request_output.images
|
||||||
images[0].save("coffee.png")
|
images[0].save("coffee.png")
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -265,7 +265,7 @@ if __name__ == "__main__":
|
|||||||
]
|
]
|
||||||
outputs = omni.generate(prompts)
|
outputs = omni.generate(prompts)
|
||||||
for i, output in enumerate(outputs):
|
for i, output in enumerate(outputs):
|
||||||
output.images[0].save(f"{i}.jpg")
|
output.request_output.images[0].save(f"{i}.jpg")
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
@@ -301,7 +301,7 @@ if __name__ == "__main__":
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
for i, output in enumerate(outputs):
|
for i, output in enumerate(outputs):
|
||||||
output.images[0].save(f"{i}.jpg")
|
output.request_output.images[0].save(f"{i}.jpg")
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also pass a negative prompt via the CLI argument `--negative-prompt`:
|
You can also pass a negative prompt via the CLI argument `--negative-prompt`:
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ list of supported architectures across all modalities, see
|
|||||||
|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|
|
||||||
| CosyVoice3 | `FunAudioLLM/Fun-CosyVoice3-0.5B-2512` | 2 (talker + code2wav) | ✓ | ✓ | — | 24 kHz |
|
| CosyVoice3 | `FunAudioLLM/Fun-CosyVoice3-0.5B-2512` | 2 (talker + code2wav) | ✓ | ✓ | — | 24 kHz |
|
||||||
| Fish Speech S2 Pro | `fishaudio/s2-pro` | dual-AR | ✓ | ✓ | — | 44.1 kHz |
|
| Fish Speech S2 Pro | `fishaudio/s2-pro` | dual-AR | ✓ | ✓ | — | 44.1 kHz |
|
||||||
| Gepard-1.0 | `nineninesix/gepard-1.0` | single (native AR + NanoCodec) | — (zero-shot; cloning WIP) | — (serving WIP) | zero-shot | 22.05 kHz |
|
|
||||||
| GLM-TTS | `zai-org/GLM-TTS` | 2 (AR + DiT) | ✓ (required) | ✓ | — | 24 kHz |
|
| GLM-TTS | `zai-org/GLM-TTS` | 2 (AR + DiT) | ✓ (required) | ✓ | — | 24 kHz |
|
||||||
| Ming-omni-tts | `inclusionAI/Ming-omni-tts-0.5B` | 2 (AR + audio VAE) | ✓ | ✓ | style / IP / dialect / TTA / podcast | 44.1 kHz |
|
| Ming-omni-tts | `inclusionAI/Ming-omni-tts-0.5B` | 2 (AR + audio VAE) | ✓ | ✓ | style / IP / dialect / TTA / podcast | 44.1 kHz |
|
||||||
| Ming-flash-omni-TTS | `Jonathan1909/Ming-flash-omni-2.0` | single (talker only) | — (caption-controlled) | — | style / IP / basic captions | 44.1 kHz |
|
| Ming-flash-omni-TTS | `Jonathan1909/Ming-flash-omni-2.0` | single (talker only) | — (caption-controlled) | — | style / IP / basic captions | 44.1 kHz |
|
||||||
@@ -24,7 +23,6 @@ list of supported architectures across all modalities, see
|
|||||||
| OmniVoice | `k2-fsa/OmniVoice` | 2 (gen + dec) | ✓ | — | voice design, language hint | 24 kHz |
|
| OmniVoice | `k2-fsa/OmniVoice` | 2 (gen + dec) | ✓ | — | voice design, language hint | 24 kHz |
|
||||||
| Qwen3-TTS | `Qwen/Qwen3-TTS-12Hz-1.7B-{CustomVoice,VoiceDesign,Base}` | 2 (talker + code2wav) | ✓ (Base) | ✓ | 3 task variants | 24 kHz |
|
| Qwen3-TTS | `Qwen/Qwen3-TTS-12Hz-1.7B-{CustomVoice,VoiceDesign,Base}` | 2 (talker + code2wav) | ✓ (Base) | ✓ | 3 task variants | 24 kHz |
|
||||||
| VoxCPM2 | `openbmb/VoxCPM2` | single (native AR) | ✓ | ✓ (online) | continuation | 48 kHz |
|
| VoxCPM2 | `openbmb/VoxCPM2` | single (native AR) | ✓ | ✓ (online) | continuation | 48 kHz |
|
||||||
| dots.tts | `rednote-hilab/dots.tts-soar` | single (native AR) | — (not wired yet) | — | — | 48 kHz |
|
|
||||||
| IndexTTS-2 | `IndexTeam/IndexTTS-2` | 2 (AR talker + S2Mel DiT + BigVGAN) | ✓ (required) | — | emotion control (`--emo-audio`, `--emo-text`, `--emo-vector`) | 22.05 kHz |
|
| IndexTTS-2 | `IndexTeam/IndexTTS-2` | 2 (AR talker + S2Mel DiT + BigVGAN) | ✓ (required) | — | emotion control (`--emo-audio`, `--emo-text`, `--emo-vector`) | 22.05 kHz |
|
||||||
| IndexTTS-2.5 | native `checkpoints/` bundle | 2 (AR talker + EnhancedCodec + S2Mel DiT + BigVGAN) | ✓ (required) | — | multilingual (`--lang`) + emotion control | 22.05 kHz |
|
| IndexTTS-2.5 | native `checkpoints/` bundle | 2 (AR talker + EnhancedCodec + S2Mel DiT + BigVGAN) | ✓ (required) | — | multilingual (`--lang`) + emotion control | 22.05 kHz |
|
||||||
| Voxtral TTS | `mistralai/Voxtral-4B-TTS-2603` | varies | ✓ | ✓ | voice presets | 24 kHz |
|
| Voxtral TTS | `mistralai/Voxtral-4B-TTS-2603` | varies | ✓ | ✓ | voice presets | 24 kHz |
|
||||||
@@ -396,58 +394,6 @@ python examples/offline_inference/text_to_speech/qwen3_tts/end2end.py \
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Gepard-1.0
|
|
||||||
|
|
||||||
Single-stage native AR TTS at 22.05 kHz. Pipeline: `Qwen3.5 backbone → 32 FSQ codebook heads (one frame/step) → NeMo NanoCodec`. Backbone runs under vLLM paged attention; the 32-head sampling + learned embedding feedback ride the native-AR runner hooks. `enforce_eager` (CUDA graph is a perf follow-up).
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
The NanoCodec decoder needs NeMo (NVIDIA Open Model License), installed separately:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uv pip install "nemo_toolkit[tts]==2.7.3" \
|
|
||||||
--overrides <(printf '%s\n' "transformers>=5.5.3" "huggingface-hub>=1.0")
|
|
||||||
```
|
|
||||||
|
|
||||||
The overrides are what make that command work. `nemo_toolkit[tts]` declares
|
|
||||||
`transformers~=4.57.0`, so a plain install downgrades the version
|
|
||||||
`requirements/common.txt` pins and takes `huggingface-hub` back to 0.x with it — after
|
|
||||||
which `import vllm` fails. Both packages are named here rather than left to resolve on
|
|
||||||
their own: transformers 5.x happens to require `huggingface-hub` 1.x, but relying on
|
|
||||||
that would make the second repair an accident of the first. `--constraint` cannot be
|
|
||||||
used instead, since it narrows a version range rather than overriding a dependency a
|
|
||||||
package declares, and the two sets never intersect. NeMo 2.7.3 decodes correctly
|
|
||||||
against transformers 5.x, so nothing is lost by holding it there.
|
|
||||||
|
|
||||||
(The same clash is why this is documented here rather than declared in
|
|
||||||
`pyproject.toml`: an extra resolves together with the base dependencies, so
|
|
||||||
`vllm-omni[gepard]` would be unsatisfiable rather than opt-in. SoulX-Singer English
|
|
||||||
SVS documents `nemo_toolkit[asr]` the same way.)
|
|
||||||
|
|
||||||
On a host whose CUDA toolkit cannot build kernels for the GPU — no `nvcc`/`ninja`, or a
|
|
||||||
consumer Blackwell (`sm_120`) card — also set `VLLM_USE_FLASHINFER_SAMPLER=0`. vLLM's
|
|
||||||
sampler JIT-compiles through FlashInfer on first use, and where that build fails the engine
|
|
||||||
dies inside `profile_run` long after every import has succeeded, which reads as a model
|
|
||||||
failure rather than an environment one. The PyTorch sampler it falls back to costs nothing
|
|
||||||
here.
|
|
||||||
|
|
||||||
### Quick start (zero-shot, default voice)
|
|
||||||
```bash
|
|
||||||
python examples/offline_inference/text_to_speech/gepard/end2end.py \
|
|
||||||
--text "Hello, this is a Gepard demo."
|
|
||||||
```
|
|
||||||
|
|
||||||
### Voice cloning
|
|
||||||
Not yet — PR1 is zero-shot only (the learned `null_prefix` default voice). Reference-audio cloning (`ref_compressor` speaker prefix) is a follow-up PR.
|
|
||||||
|
|
||||||
### Notes
|
|
||||||
- Output: 22.05 kHz mono WAV.
|
|
||||||
- First run downloads two checkpoints, not one: the model itself, and the NanoCodec decoder it names in `codec_id` (`nvidia/nemo-nano-codec-22khz-1.89kbps-21.5fps` by default). Both are fetched automatically; expect the first startup to be correspondingly slower.
|
|
||||||
- Offline only for now; the `/v1/audio/speech` adapter is a follow-up PR.
|
|
||||||
- Deploy config: `vllm_omni/deploy/gepard.yaml` (the example's default; copy it and pass `--deploy-config` to change it).
|
|
||||||
- Generation length and reproducibility are stage settings, not script flags. One output token is one audio frame, so `max_tokens` in the YAML is the frame budget; `seed` makes the in-model 32-head sampling reproducible. The script deliberately passes no `SamplingParams`: one supplied by a caller replaces the stage defaults wholesale rather than merging, which would drop the pipeline's stop token and run every request to `max_tokens`.
|
|
||||||
- Text length: short texts are repeated internally to match the training layout (the checkpoint's `text_repetition` block); the upper bound is the stage's `max_model_len`, enforced by the engine. Empty text is rejected rather than voiced.
|
|
||||||
- `VLLM_GEPARD_GREEDY=1` swaps the 32-head Gumbel-max sampling for argmax, for reproducible comparisons that do not depend on a seed.
|
|
||||||
|
|
||||||
## VoxCPM2
|
## VoxCPM2
|
||||||
|
|
||||||
Single-stage native AR TTS at 48 kHz. Pipeline: `feat_encoder → MiniCPM4 → FSQ → residual_lm → LocDiT → AudioVAE`.
|
Single-stage native AR TTS at 48 kHz. Pipeline: `feat_encoder → MiniCPM4 → FSQ → residual_lm → LocDiT → AudioVAE`.
|
||||||
@@ -484,31 +430,6 @@ Streaming is exposed through the online OpenAI Speech API (`stream=true`). See [
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## dots.tts
|
|
||||||
|
|
||||||
Single-stage native AR TTS at 48 kHz (rednote-hilab). Pipeline: `Qwen2.5-1.5B base LM → DiT (10-step Euler flow matching) → patch_encoder AR loopback → AudioVAE (streaming decode)`. Same "vLLM-native base LM + side-path computation" pattern as VoxCPM2, with a plain Qwen2 backbone instead of MiniCPM4 and no FSQ / residual LM stage.
|
|
||||||
|
|
||||||
### Quick start
|
|
||||||
```bash
|
|
||||||
python examples/offline_inference/text_to_speech/dots_tts/end2end.py \
|
|
||||||
--model rednote-hilab/dots.tts-soar \
|
|
||||||
--text "Hello, this is a test of dots TTS running on vLLM Omni."
|
|
||||||
```
|
|
||||||
|
|
||||||
### Voice cloning
|
|
||||||
Not wired in this release — generation is zero-shot only. The CAM++ x-vector speaker encoder weights load, but `end2end.py` has no `--ref-audio`/`--ref-text` flags yet.
|
|
||||||
|
|
||||||
### Streaming
|
|
||||||
The AudioVAE decoder has an internal streaming path (`init_stream_state` / `stream_step` / `stream_flush`) used to avoid boundary artifacts between 160 ms patches, but it is not yet exposed through an online serving endpoint or example.
|
|
||||||
|
|
||||||
### Notes
|
|
||||||
- Output: 48 kHz mono WAV.
|
|
||||||
- Deploy config: `vllm_omni/deploy/dots_tts.yaml` (auto-loaded by HF `model_type`).
|
|
||||||
- Checkpoints: `rednote-hilab/dots.tts-soar` is the validated default. `dots.tts-base` shares the same architecture but is unvalidated in this repo. `dots.tts-mf` (MeanFlow, 2-4 step) is not supported yet.
|
|
||||||
- Known limitation: no CUDA graph capture and no batched side-path computation yet, so concurrent requests do not currently scale (each request's DiT Euler steps run serially). See `recipes/rednote-hilab/dots.tts.md` for details and the roadmap.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## IndexTTS-2 and IndexTTS-2.5
|
## IndexTTS-2 and IndexTTS-2.5
|
||||||
|
|
||||||
Both versions use a 2-stage TTS pipeline and produce 22.05 kHz mono audio. Stage 0 is a GPT AR talker. IndexTTS-2 Stage 1 uses RepCodec semantic embeddings, GPT latent, S2Mel CFM/DiT, and BigVGAN. IndexTTS-2.5 replaces RepCodec with EnhancedCodec and uses the official code-only path (`use_gpt_latent=false`). Every request requires reference audio for zero-shot voice cloning.
|
Both versions use a 2-stage TTS pipeline and produce 22.05 kHz mono audio. Stage 0 is a GPT AR talker. IndexTTS-2 Stage 1 uses RepCodec semantic embeddings, GPT latent, S2Mel CFM/DiT, and BigVGAN. IndexTTS-2.5 replaces RepCodec with EnhancedCodec and uses the official code-only path (`use_gpt_latent=false`). Every request requires reference audio for zero-shot voice cloning.
|
||||||
|
|||||||
@@ -1,125 +0,0 @@
|
|||||||
"""Offline dots.tts inference example (single-stage native AR pipeline).
|
|
||||||
|
|
||||||
Uses the single-stage native AR config (vllm_omni/deploy/dots_tts.yaml).
|
|
||||||
Reference checkpoint: rednote-hilab/dots.tts-soar.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import soundfile as sf
|
|
||||||
import torch
|
|
||||||
|
|
||||||
from vllm_omni import Omni
|
|
||||||
from vllm_omni.utils.tracking_parser import TrackingArgumentParser
|
|
||||||
|
|
||||||
REPO_ROOT = Path(__file__).resolve().parents[4]
|
|
||||||
SAMPLE_RATE = 48_000
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
|
||||||
parser = TrackingArgumentParser(description="Offline dots.tts native AR inference")
|
|
||||||
parser.add_argument(
|
|
||||||
"--model",
|
|
||||||
type=str,
|
|
||||||
default="rednote-hilab/dots.tts-soar",
|
|
||||||
help="dots.tts model path or HuggingFace repo ID.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--text",
|
|
||||||
type=str,
|
|
||||||
default="Hello, this is a test of dots TTS running on vLLM Omni.",
|
|
||||||
help="Text to synthesize.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--output-dir",
|
|
||||||
type=str,
|
|
||||||
default="output_audio",
|
|
||||||
help="Directory for output WAV files.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--deploy-config",
|
|
||||||
type=str,
|
|
||||||
default=None,
|
|
||||||
help="Override the deploy config path. If unset, auto-loads "
|
|
||||||
"vllm_omni/deploy/dots_tts.yaml based on the HF model_type.",
|
|
||||||
)
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def extract_audio(multimodal_output: dict) -> torch.Tensor:
|
|
||||||
"""Extract the final complete audio tensor from multimodal output.
|
|
||||||
|
|
||||||
The output processor concatenates per-step delta tensors under
|
|
||||||
``model_outputs``. Falls back to ``audio`` for backwards compat.
|
|
||||||
"""
|
|
||||||
audio = multimodal_output.get("model_outputs")
|
|
||||||
if audio is None:
|
|
||||||
audio = multimodal_output.get("audio")
|
|
||||||
if audio is None:
|
|
||||||
raise ValueError(f"No audio key in multimodal_output: {list(multimodal_output.keys())}")
|
|
||||||
|
|
||||||
if isinstance(audio, list):
|
|
||||||
valid = [torch.as_tensor(a).float().cpu().reshape(-1) for a in audio if a is not None]
|
|
||||||
if not valid:
|
|
||||||
raise ValueError("Audio list is empty or all elements are None.")
|
|
||||||
return torch.cat(valid, dim=0) if len(valid) > 1 else valid[0]
|
|
||||||
|
|
||||||
return torch.as_tensor(audio).float().cpu().reshape(-1)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
args = parse_args()
|
|
||||||
|
|
||||||
output_dir = Path(args.output_dir)
|
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
engine = Omni(
|
|
||||||
model=args.model,
|
|
||||||
deploy_config=args.deploy_config,
|
|
||||||
)
|
|
||||||
|
|
||||||
from transformers import AutoTokenizer
|
|
||||||
|
|
||||||
from vllm_omni.model_executor.models.dots_tts.dots_tts_prompt import (
|
|
||||||
build_dots_tts_prompt,
|
|
||||||
)
|
|
||||||
|
|
||||||
tokenizer = AutoTokenizer.from_pretrained(args.model, trust_remote_code=True)
|
|
||||||
|
|
||||||
prompt = build_dots_tts_prompt(
|
|
||||||
tokenizer=tokenizer,
|
|
||||||
text=args.text,
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"Model : {args.model}")
|
|
||||||
print(f"Text : {args.text}")
|
|
||||||
print(f"Output dir : {output_dir}")
|
|
||||||
print(f"Prompt len : {len(prompt['prompt_token_ids'])} tokens")
|
|
||||||
|
|
||||||
t_start = time.perf_counter()
|
|
||||||
outputs = engine.generate([prompt])
|
|
||||||
elapsed = time.perf_counter() - t_start
|
|
||||||
|
|
||||||
request_output = outputs[0]
|
|
||||||
mm = request_output.outputs[0].multimodal_output
|
|
||||||
audio = extract_audio(mm)
|
|
||||||
|
|
||||||
duration = audio.numel() / SAMPLE_RATE
|
|
||||||
rtf = elapsed / duration if duration > 0 else float("inf")
|
|
||||||
|
|
||||||
output_path = output_dir / "output.wav"
|
|
||||||
sf.write(str(output_path), audio.numpy(), SAMPLE_RATE, format="WAV")
|
|
||||||
|
|
||||||
print(f"Saved : {output_path}")
|
|
||||||
print(f"Duration : {duration:.2f}s")
|
|
||||||
print(f"Inference : {elapsed:.2f}s")
|
|
||||||
print(f"RTF : {rtf:.3f}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
|
||||||
main()
|
|
||||||
@@ -38,6 +38,17 @@ from vllm_omni.utils.tracking_parser import TrackingArgumentParser
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_MODEL = "fishaudio/s2-pro"
|
DEFAULT_MODEL = "fishaudio/s2-pro"
|
||||||
|
DEFAULT_DEPLOY_CONFIG = os.path.join(
|
||||||
|
os.path.dirname(__file__),
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"vllm_omni",
|
||||||
|
"model_executor",
|
||||||
|
"stage_configs",
|
||||||
|
"fish_speech_s2_pro.yaml",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_prompt(
|
def build_prompt(
|
||||||
@@ -141,8 +152,7 @@ def _build_omni_kwargs(args, model_name: str) -> dict:
|
|||||||
log_stats=args.log_stats,
|
log_stats=args.log_stats,
|
||||||
stage_init_timeout=args.stage_init_timeout,
|
stage_init_timeout=args.stage_init_timeout,
|
||||||
)
|
)
|
||||||
if args.deploy_config:
|
omni_kwargs["deploy_config"] = args.deploy_config or DEFAULT_DEPLOY_CONFIG
|
||||||
omni_kwargs["deploy_config"] = args.deploy_config
|
|
||||||
return omni_kwargs
|
return omni_kwargs
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
||||||
"""Offline inference example for Gepard-1.0 TTS via vLLM-Omni.
|
|
||||||
|
|
||||||
Single-stage native-AR pipeline: a Qwen3.5 backbone samples one 32-code FSQ
|
|
||||||
frame per step; the NeMo NanoCodec decodes the frames to a 22.05 kHz mono
|
|
||||||
waveform. Zero-shot: the default learned voice, no reference audio.
|
|
||||||
|
|
||||||
Generation length and seed are stage settings rather than flags here. One
|
|
||||||
output token is one audio frame, so ``max_tokens`` in the deploy YAML is the
|
|
||||||
frame budget; pass a copy of the YAML via ``--deploy-config`` to change it.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python end2end.py --text "Hello, this is Gepard speaking."
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import soundfile as sf
|
|
||||||
import torch
|
|
||||||
|
|
||||||
import vllm_omni
|
|
||||||
from vllm_omni.utils.tracking_parser import TrackingArgumentParser
|
|
||||||
|
|
||||||
os.environ.setdefault("VLLM_WORKER_MULTIPROC_METHOD", "spawn")
|
|
||||||
|
|
||||||
from vllm_omni import Omni # noqa: E402
|
|
||||||
|
|
||||||
MODEL = "nineninesix/gepard-1.0"
|
|
||||||
SAMPLE_RATE = 22050
|
|
||||||
# Absolute: the loader hands the path straight to OmegaConf, which resolves a
|
|
||||||
# bare name against the current working directory, not the package.
|
|
||||||
DEFAULT_DEPLOY_CONFIG = str(Path(vllm_omni.__file__).resolve().parent / "deploy" / "gepard.yaml")
|
|
||||||
|
|
||||||
|
|
||||||
def build_request(text: str) -> dict:
|
|
||||||
"""Build an Omni request payload for Gepard (zero-shot, no ref audio).
|
|
||||||
|
|
||||||
The model only consumes the [speaker slots, SOT, text, EOT, SOS] layout, so
|
|
||||||
a bare text prompt would have no speaker slots and no SOS.
|
|
||||||
"""
|
|
||||||
from transformers import AutoTokenizer
|
|
||||||
|
|
||||||
from vllm_omni.model_executor.models.gepard.configuration_gepard import GepardConfig
|
|
||||||
from vllm_omni.model_executor.models.gepard.prompt import build_gepard_prompt_ids
|
|
||||||
|
|
||||||
# From the checkpoint's own sidecar, which is what the worker builds its
|
|
||||||
# config from too. A layout that disagrees with the trained one does not
|
|
||||||
# fail, it just degrades the speech.
|
|
||||||
cfg = GepardConfig.from_checkpoint(MODEL)
|
|
||||||
tokenizer = AutoTokenizer.from_pretrained(MODEL, trust_remote_code=True)
|
|
||||||
prompt_token_ids = build_gepard_prompt_ids(
|
|
||||||
tokenizer(text, add_special_tokens=False)["input_ids"],
|
|
||||||
config=cfg,
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"prompt_token_ids": prompt_token_ids,
|
|
||||||
"additional_information": {"text": [text]},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def save_audio(waveform: torch.Tensor, path: str, sample_rate: int = SAMPLE_RATE) -> None:
|
|
||||||
sf.write(path, waveform.float().numpy(), sample_rate)
|
|
||||||
seconds = waveform.numel() / sample_rate
|
|
||||||
print(
|
|
||||||
f" Saved {path} ({tuple(waveform.shape)}, {sample_rate} Hz, "
|
|
||||||
f"{seconds:.2f}s, peak {float(waveform.abs().max()):.3f})"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main(args) -> None:
|
|
||||||
omni = Omni(
|
|
||||||
model=MODEL,
|
|
||||||
deploy_config=args.deploy_config,
|
|
||||||
stage_init_timeout=args.stage_init_timeout,
|
|
||||||
init_timeout=args.init_timeout,
|
|
||||||
)
|
|
||||||
|
|
||||||
# No explicit SamplingParams: one supplied here would replace the stage
|
|
||||||
# defaults rather than merge over them, dropping the pipeline's stop token.
|
|
||||||
output_dir = Path(args.output_dir)
|
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
print(f"Synthesizing: {args.text!r}")
|
|
||||||
inputs = build_request(text=args.text)
|
|
||||||
|
|
||||||
for i, stage_outputs in enumerate(omni.generate(inputs)):
|
|
||||||
for j, out in enumerate(stage_outputs.outputs):
|
|
||||||
mm = out.multimodal_output
|
|
||||||
if mm is None:
|
|
||||||
print(f" [req {i}] No audio output.")
|
|
||||||
continue
|
|
||||||
# The consolidation path renames "model_outputs" to "audio".
|
|
||||||
audio = mm.get("audio")
|
|
||||||
if audio is None:
|
|
||||||
audio = mm.get("model_outputs")
|
|
||||||
if isinstance(audio, list):
|
|
||||||
audio = audio[0] if len(audio) else None
|
|
||||||
if audio is None:
|
|
||||||
print(f" [req {i}] No waveform in multimodal_output (keys: {list(mm)}).")
|
|
||||||
continue
|
|
||||||
sr_tensor = mm.get("sr")
|
|
||||||
sr = int(sr_tensor.item()) if hasattr(sr_tensor, "item") else SAMPLE_RATE
|
|
||||||
save_audio(audio.cpu(), str(output_dir / f"output_{i}_{j}.wav"), sr)
|
|
||||||
|
|
||||||
print("Done.")
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
|
||||||
parser = TrackingArgumentParser(description="Gepard-1.0 offline TTS inference (zero-shot)")
|
|
||||||
parser.add_argument("--text", default="Hello, this is Gepard speaking.", help="Text to synthesize.")
|
|
||||||
parser.add_argument(
|
|
||||||
"--output-dir",
|
|
||||||
default=os.path.join(
|
|
||||||
os.environ.get("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache")),
|
|
||||||
"gepard_output",
|
|
||||||
),
|
|
||||||
help="Directory for WAV outputs (default: ~/.cache/gepard_output).",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--deploy-config",
|
|
||||||
default=DEFAULT_DEPLOY_CONFIG,
|
|
||||||
help=(
|
|
||||||
"Path to the deploy YAML (default: the packaged vllm_omni/deploy/gepard.yaml). This must "
|
|
||||||
"stay set: without the YAML's `pipeline: gepard` pin the architectures fallback routes "
|
|
||||||
"Qwen3_5ForCausalLM to the diffusion registry. Copy it to change max_tokens or seed."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
# The upstream defaults are too tight for a cold start.
|
|
||||||
parser.add_argument("--stage-init-timeout", type=int, default=900)
|
|
||||||
parser.add_argument("--init-timeout", type=int, default=1800)
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main(parse_args())
|
|
||||||
@@ -32,10 +32,11 @@ from end2end import _estimate_prompt_len
|
|||||||
from vllm.utils.argparse_utils import FlexibleArgumentParser
|
from vllm.utils.argparse_utils import FlexibleArgumentParser
|
||||||
|
|
||||||
from vllm_omni import Omni
|
from vllm_omni import Omni
|
||||||
|
from vllm_omni.engine.arg_utils import nullify_stage_engine_defaults
|
||||||
from vllm_omni.utils.forced_aligner import align, build_forced_aligner_config
|
from vllm_omni.utils.forced_aligner import align, build_forced_aligner_config
|
||||||
|
|
||||||
|
|
||||||
def _default_deploy_config() -> str:
|
def _default_stage_config() -> str:
|
||||||
repo_root = Path(__file__).resolve().parents[4]
|
repo_root = Path(__file__).resolve().parents[4]
|
||||||
return str(repo_root / "vllm_omni" / "deploy" / "qwen3_tts.yaml")
|
return str(repo_root / "vllm_omni" / "deploy" / "qwen3_tts.yaml")
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ def main(args: Any) -> None:
|
|||||||
"output_dir",
|
"output_dir",
|
||||||
):
|
):
|
||||||
omni_kwargs.pop(key, None)
|
omni_kwargs.pop(key, None)
|
||||||
omni_kwargs["deploy_config"] = args.deploy_config or _default_deploy_config()
|
omni_kwargs["stage_configs_path"] = args.stage_configs_path or _default_stage_config()
|
||||||
omni_kwargs["log_stats"] = args.log_stats
|
omni_kwargs["log_stats"] = args.log_stats
|
||||||
omni = Omni(**omni_kwargs)
|
omni = Omni(**omni_kwargs)
|
||||||
|
|
||||||
@@ -155,7 +156,7 @@ def parse_args() -> Any:
|
|||||||
default=None,
|
default=None,
|
||||||
help="Optional YAML file for forced aligner settings (incl. gpu_memory_utilization)",
|
help="Optional YAML file for forced aligner settings (incl. gpu_memory_utilization)",
|
||||||
)
|
)
|
||||||
parser.add_argument("--deploy-config", default=None, help="Qwen3-TTS deploy YAML")
|
parser.add_argument("--stage-configs-path", default=None, help="Qwen3-TTS deploy YAML")
|
||||||
parser.add_argument("--text", default="Hello world.", help="Text to synthesize and align")
|
parser.add_argument("--text", default="Hello world.", help="Text to synthesize and align")
|
||||||
parser.add_argument("--language", default="English", help="Qwen3-TTS language field")
|
parser.add_argument("--language", default="English", help="Qwen3-TTS language field")
|
||||||
parser.add_argument("--speaker", default="Vivian", help="CustomVoice speaker name")
|
parser.add_argument("--speaker", default="Vivian", help="CustomVoice speaker name")
|
||||||
@@ -163,6 +164,7 @@ def parse_args() -> Any:
|
|||||||
parser.add_argument("--max-new-tokens", type=int, default=2048, help="TTS max_new_tokens")
|
parser.add_argument("--max-new-tokens", type=int, default=2048, help="TTS max_new_tokens")
|
||||||
parser.add_argument("--output-dir", default="output_audio", help="Directory for WAV and JSON sidecar")
|
parser.add_argument("--output-dir", default="output_audio", help="Directory for WAV and JSON sidecar")
|
||||||
parser.add_argument("--log-stats", action="store_true", default=False, help="Enable vLLM-Omni stats logging")
|
parser.add_argument("--log-stats", action="store_true", default=False, help="Enable vLLM-Omni stats logging")
|
||||||
|
nullify_stage_engine_defaults(parser)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -570,8 +570,6 @@ def main():
|
|||||||
num_frames=args.num_frames,
|
num_frames=args.num_frames,
|
||||||
extra_args=extra_args,
|
extra_args=extra_args,
|
||||||
)
|
)
|
||||||
if args.frame_rate is not None:
|
|
||||||
sampling_kwargs["frame_rate"] = args.frame_rate
|
|
||||||
if args.guidance_scale_high is not None:
|
if args.guidance_scale_high is not None:
|
||||||
sampling_kwargs["guidance_scale_2"] = args.guidance_scale_high
|
sampling_kwargs["guidance_scale_2"] = args.guidance_scale_high
|
||||||
|
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
# π0 (Pi-Zero) VLA — OpenPI realtime serving
|
|
||||||
|
|
||||||
[π0](https://www.physicalintelligence.company/blog/pi0) is a Vision-Language-Action
|
|
||||||
model from Physical Intelligence: multi-camera images + a language instruction +
|
|
||||||
robot proprioceptive state → a continuous action chunk via flow-matching denoising
|
|
||||||
(it does **not** emit text tokens). This example serves π0 over the OpenPI realtime
|
|
||||||
websocket protocol at `/v1/realtime/robot/openpi`.
|
|
||||||
|
|
||||||
## Install extras
|
|
||||||
|
|
||||||
The core `pip install -e .` does not include the OpenPI client used here:
|
|
||||||
|
|
||||||
- `openpi-client` (from the [openpi](https://github.com/Physical-Intelligence/openpi)
|
|
||||||
repo: `pip install -e packages/openpi-client`), `websockets`, `msgpack`, `msgpack-numpy`
|
|
||||||
|
|
||||||
## Weights
|
|
||||||
|
|
||||||
π0 uses the LeRobot `lerobot/pi0_base` checkpoint (HF, ~13 GB). Either let the server
|
|
||||||
download it (`MODEL=lerobot/pi0_base`) or point at a local copy
|
|
||||||
(`MODEL=/path/to/pi0_base`).
|
|
||||||
|
|
||||||
## Run the server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm serve lerobot/pi0_base --omni --port 8000 \
|
|
||||||
--served-model-name pi0 \
|
|
||||||
--deploy-config vllm_omni/deploy/pi0.yaml \
|
|
||||||
--enforce-eager --disable-log-stats
|
|
||||||
```
|
|
||||||
|
|
||||||
The deploy config (`vllm_omni/deploy/pi0.yaml`) declares a single diffusion stage
|
|
||||||
(`Pi0Pipeline`), float32, `max_num_seqs: 1`, and the `policy_server_config` handshake
|
|
||||||
metadata (3 cameras, `joint_position`, action horizon 50, action dim 32).
|
|
||||||
|
|
||||||
### Commonly adjusted `model_config` keys
|
|
||||||
|
|
||||||
These live under `stages[0].model_config` in `vllm_omni/deploy/pi0.yaml`:
|
|
||||||
|
|
||||||
| Key | Default | Meaning |
|
|
||||||
|---|---|---|
|
|
||||||
| `chunk_size` | `50` | Action-chunk length (timesteps) the model predicts per inference. |
|
|
||||||
| `num_inference_steps` | `10` | Flow-matching Euler denoising steps. |
|
|
||||||
| `max_action_dim` | `32` | Action dimensionality (state/action are padded to this). |
|
|
||||||
| `max_state_dim` | `32` | Proprioceptive-state dimensionality (zero-padded to this). |
|
|
||||||
| `image_resolution` | `[224, 224]` | Per-camera input size (square; SigLIP). |
|
|
||||||
| `tokenizer_max_length` | `48` | Max PaliGemma prompt tokens. |
|
|
||||||
| `max_cameras` | `3` | Camera slots the model attends to (real + `-1`-padded). |
|
|
||||||
| `image_feature_keys` | 3 `observation.images.*` keys | Camera order the model attends to. |
|
|
||||||
| `image_key_map` | `{}` | Map raw obs camera keys → `image_feature_keys` (empty = verbatim). |
|
|
||||||
|
|
||||||
The `policy_server_config` block below them is the OpenPI handshake metadata
|
|
||||||
advertised to the client; keep its `action_horizon` / `action_dim` /
|
|
||||||
`image_resolution` in sync with the `model_config` values above. The e2e test
|
|
||||||
`tests/e2e/online_serving/test_pi0_expansion.py::test_pi0_openpi_online` connects to a
|
|
||||||
live server and asserts the advertised metadata matches these `pi0.yaml` values.
|
|
||||||
|
|
||||||
## Run the client
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python examples/online_serving/pi0/openpi_client.py --host 127.0.0.1 --port 8000 \
|
|
||||||
--prompt "pick up the red block and place it in the bin"
|
|
||||||
```
|
|
||||||
|
|
||||||
It connects, prints the server metadata, sends robot observations, and prints the
|
|
||||||
returned `[action_horizon, action_dim] = [50, 32]` action chunks. Replace the blank
|
|
||||||
cameras / zero state in `_make_dummy_obs` with real frames (HWC uint8) and
|
|
||||||
proprioceptive state to drive a robot.
|
|
||||||
|
|
||||||
### Observation format
|
|
||||||
|
|
||||||
The client sends a flat dict per inference:
|
|
||||||
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"observation.images.base_0_rgb": np.uint8[H, W, 3],
|
|
||||||
"observation.images.left_wrist_0_rgb": np.uint8[H, W, 3],
|
|
||||||
"observation.images.right_wrist_0_rgb":np.uint8[H, W, 3],
|
|
||||||
"state": np.float32[state_dim], # zero-padded to max_state_dim=32 server-side
|
|
||||||
"prompt": "pick up the red block",
|
|
||||||
"session_id": "<uuid>", # accepted but ignored (π0 is stateless)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Camera keys must match the server's `image_feature_keys` (the checkpoint's
|
|
||||||
`input_features` order). If your robot uses different camera names, set
|
|
||||||
`model_config.image_key_map` in `pi0.yaml` to map raw obs keys → feature keys.
|
|
||||||
|
|
||||||
## Correctness
|
|
||||||
|
|
||||||
π0's flow-matching kernel is bit-for-bit matched to the LeRobot `PI0Policy` reference
|
|
||||||
(`max|Δ| = 7.15e-07`, CPU/float32, fixed noise; see
|
|
||||||
`tests/diffusion/models/pi0/test_pi0_parity.py::test_pi0_vllm_omni_vs_lerobot`) and is
|
|
||||||
version-stable across transformers releases (the version-stability checks in
|
|
||||||
`tests/diffusion/models/pi0/test_pi0_units.py`). An OpenPI websocket online-serving e2e
|
|
||||||
lives in `tests/e2e/online_serving/test_pi0_expansion.py::test_pi0_openpi_online`.
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
- **Normalization stats**: `lerobot/pi0_base` uses identity normalization, which is
|
|
||||||
fully supported (state passes through, actions are returned in the model's space).
|
|
||||||
Per-dataset `norm_stats` declared in a checkpoint's `config.json` are honored, but
|
|
||||||
stats stored only in LeRobot's `policy_preprocessor.json` companion safetensors are
|
|
||||||
not yet loaded — a fine-tuned checkpoint that relies on those would need that bridge
|
|
||||||
added before its actions are in real-world units.
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
||||||
"""Minimal OpenPI client for the π0 (Pi-Zero) VLA policy server.
|
|
||||||
|
|
||||||
Connects to a running π0 server (see README.md), sends a single robot
|
|
||||||
observation (3 cameras + state + language instruction), and prints the returned
|
|
||||||
continuous action chunk ``[action_horizon, action_dim]``.
|
|
||||||
|
|
||||||
The server speaks the OpenPI websocket protocol:
|
|
||||||
connect -> server sends msgpack(policy_server_config metadata)
|
|
||||||
infer -> client sends msgpack(obs), server replies msgpack(actions ndarray)
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python openpi_client.py --host 127.0.0.1 --port 8000 \
|
|
||||||
--prompt "pick up the red block"
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import uuid
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Avoid shadowing the installed ``openpi_client`` package with this dir.
|
|
||||||
example_dir = str(Path(__file__).resolve().parent)
|
|
||||||
removed = sys.path and sys.path[0] == example_dir
|
|
||||||
if removed:
|
|
||||||
sys.path.pop(0)
|
|
||||||
try:
|
|
||||||
from openpi_client.websocket_client_policy import WebsocketClientPolicy
|
|
||||||
finally:
|
|
||||||
if removed:
|
|
||||||
sys.path.insert(0, example_dir)
|
|
||||||
except ImportError as exc: # pragma: no cover - runtime dependency guard
|
|
||||||
raise ImportError("π0 OpenPI example requires `openpi-client`.") from exc
|
|
||||||
|
|
||||||
logger = logging.getLogger("pi0_openpi_client")
|
|
||||||
|
|
||||||
DEFAULT_HOST = "127.0.0.1"
|
|
||||||
DEFAULT_PORT = 8000
|
|
||||||
DEFAULT_PATH = "/v1/realtime/robot/openpi"
|
|
||||||
DEFAULT_PROMPT = "pick up the red block and place it in the bin"
|
|
||||||
|
|
||||||
# π0 / pi0_base camera identities (must match the server's image_feature_keys).
|
|
||||||
CAMERA_KEYS = (
|
|
||||||
"observation.images.base_0_rgb",
|
|
||||||
"observation.images.left_wrist_0_rgb",
|
|
||||||
"observation.images.right_wrist_0_rgb",
|
|
||||||
)
|
|
||||||
STATE_DIM = 32
|
|
||||||
|
|
||||||
|
|
||||||
def _make_dummy_obs(prompt: str, session_id: str, image_size: int = 224) -> dict[str, Any]:
|
|
||||||
"""Build a single π0 observation with blank cameras + zero state.
|
|
||||||
|
|
||||||
Replace the zeros with real camera frames (HWC uint8) and proprioceptive
|
|
||||||
state to drive an actual robot.
|
|
||||||
"""
|
|
||||||
obs: dict[str, Any] = {cam: np.zeros((image_size, image_size, 3), dtype=np.uint8) for cam in CAMERA_KEYS}
|
|
||||||
obs["state"] = np.zeros(STATE_DIM, dtype=np.float32)
|
|
||||||
obs["prompt"] = prompt
|
|
||||||
obs["session_id"] = session_id
|
|
||||||
return obs
|
|
||||||
|
|
||||||
|
|
||||||
def _ws_uri(host: str, port: int, path: str) -> str:
|
|
||||||
"""Build the full websocket URI. WebsocketClientPolicy takes the whole URI as
|
|
||||||
``host`` (it has no separate path arg)."""
|
|
||||||
if host.startswith(("ws://", "wss://")):
|
|
||||||
return host
|
|
||||||
return f"ws://{host}:{port}{path}"
|
|
||||||
|
|
||||||
|
|
||||||
def _as_action_array(response: Any) -> np.ndarray:
|
|
||||||
if isinstance(response, dict) and response.get("type") == "error":
|
|
||||||
raise RuntimeError(f"Inference failed: {response.get('message', response)}")
|
|
||||||
return np.asarray(response, dtype=np.float32)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
parser = argparse.ArgumentParser(description="π0 OpenPI client")
|
|
||||||
parser.add_argument("--host", default=DEFAULT_HOST)
|
|
||||||
parser.add_argument("--port", type=int, default=DEFAULT_PORT)
|
|
||||||
parser.add_argument("--path", default=DEFAULT_PATH)
|
|
||||||
parser.add_argument("--prompt", default=DEFAULT_PROMPT)
|
|
||||||
parser.add_argument("--num-steps", type=int, default=2, help="Inference observations to send.")
|
|
||||||
args = parser.parse_args()
|
|
||||||
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
||||||
|
|
||||||
policy = WebsocketClientPolicy(host=_ws_uri(args.host, args.port, args.path))
|
|
||||||
metadata = dict(policy.get_server_metadata())
|
|
||||||
logger.info("Server metadata: %s", metadata)
|
|
||||||
|
|
||||||
session_id = str(uuid.uuid4())
|
|
||||||
for step in range(args.num_steps):
|
|
||||||
obs = _make_dummy_obs(args.prompt, session_id)
|
|
||||||
actions = _as_action_array(policy.infer(obs))
|
|
||||||
logger.info(
|
|
||||||
"[step %d] actions shape=%s mean=%.4f std=%.4f",
|
|
||||||
step,
|
|
||||||
actions.shape,
|
|
||||||
float(actions.mean()),
|
|
||||||
float(actions.std()),
|
|
||||||
)
|
|
||||||
if not np.isfinite(actions).all():
|
|
||||||
raise RuntimeError("Server returned non-finite actions.")
|
|
||||||
logger.info("Done — π0 OpenPI round-trip OK.")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main())
|
|
||||||
@@ -79,13 +79,8 @@ plugins:
|
|||||||
user_guide/diffusion/step_execution.md: user_guide/diffusion/execution_modes.md
|
user_guide/diffusion/step_execution.md: user_guide/diffusion/execution_modes.md
|
||||||
user_guide/diffusion/cpu_offload_diffusion.md: user_guide/diffusion/cpu_offload.md
|
user_guide/diffusion/cpu_offload_diffusion.md: user_guide/diffusion/cpu_offload.md
|
||||||
user_guide/quantization/index.md: user_guide/quantization/overview.md
|
user_guide/quantization/index.md: user_guide/quantization/overview.md
|
||||||
configuration/pd_disaggregation.md: features/pd_disaggregation.md
|
|
||||||
design/feature/diffusion_request_level_batching.md: design/feature/diffusion_continuous_batching.md
|
design/feature/diffusion_request_level_batching.md: design/feature/diffusion_continuous_batching.md
|
||||||
design/feature/diffusion_step_execution.md: design/feature/diffusion_continuous_batching.md
|
design/feature/diffusion_step_execution.md: design/feature/diffusion_continuous_batching.md
|
||||||
design/feature/cpu_offloading.md: design/feature/offloader/README.md
|
|
||||||
design/feature/model_level_offload.md: design/feature/offloader/cpu_offload.md
|
|
||||||
design/feature/layerwise_offload.md: design/feature/offloader/layerwise_offload.md
|
|
||||||
design/feature/distributed_layerwise_offload.md: design/feature/offloader/distributed_layerwise_offload.md
|
|
||||||
- glightbox
|
- glightbox
|
||||||
- git-revision-date-localized:
|
- git-revision-date-localized:
|
||||||
# exclude files
|
# exclude files
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ dev = [
|
|||||||
"pytest-asyncio==1.4.0",
|
"pytest-asyncio==1.4.0",
|
||||||
"pytest-cov==7.1.0",
|
"pytest-cov==7.1.0",
|
||||||
"pytest-mock==3.15.1",
|
"pytest-mock==3.15.1",
|
||||||
"pytest-shard==0.1.2",
|
|
||||||
"websockets==16.1",
|
"websockets==16.1",
|
||||||
"pytest-subtests==0.15.0",
|
"pytest-subtests==0.15.0",
|
||||||
"datasets==3.6.0",
|
"datasets==3.6.0",
|
||||||
@@ -302,21 +301,6 @@ filterwarnings = [
|
|||||||
"ignore:.*does not have '__bases__' attribute.*:UserWarning",
|
"ignore:.*does not have '__bases__' attribute.*:UserWarning",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.coverage.run]
|
|
||||||
core = "sysmon"
|
|
||||||
source = ["vllm_omni"]
|
|
||||||
# Online-serving tests launch the server via subprocess.Popen and stop it with
|
|
||||||
# SIGTERM. pytest-cov no longer instruments subprocesses implicitly, and a
|
|
||||||
# SIGTERM-ed process discards its data, so without patch/sigterm a coverage run
|
|
||||||
# over an online test measures only the pytest parent.
|
|
||||||
patch = ["subprocess"]
|
|
||||||
concurrency = ["multiprocessing", "thread"]
|
|
||||||
sigterm = true
|
|
||||||
|
|
||||||
[tool.coverage.report]
|
|
||||||
show_missing = true
|
|
||||||
skip_covered = true
|
|
||||||
|
|
||||||
[tool.typos.default]
|
[tool.typos.default]
|
||||||
extend-ignore-identifiers-re = [
|
extend-ignore-identifiers-re = [
|
||||||
".*_thw",
|
".*_thw",
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ MusicGen-style delay pattern).
|
|||||||
**Online serving:**
|
**Online serving:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vllm serve bosonai/higgs-audio-v3-tts-4b \
|
vllm-omni serve bosonai/higgs-audio-v3-tts-4b \
|
||||||
--host 0.0.0.0 --port 8095 \
|
--host 0.0.0.0 --port 8095 \
|
||||||
--trust-remote-code --omni
|
--trust-remote-code --omni
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,175 +0,0 @@
|
|||||||
# LTX-2.5
|
|
||||||
|
|
||||||
> Text-to-video and first-frame image-to-video generation with synchronized audio
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
vLLM-Omni supports the gated
|
|
||||||
[`Lightricks/LTX-2.5-Diffusers`](https://huggingface.co/Lightricks/LTX-2.5-Diffusers)
|
|
||||||
checkpoint for T2V and first-frame I2V. Every output includes synchronized
|
|
||||||
48 kHz stereo audio.
|
|
||||||
|
|
||||||
The raw [`Lightricks/LTX-2.5`](https://huggingface.co/Lightricks/LTX-2.5)
|
|
||||||
repository is not directly loadable with `--model`. It supplies the official
|
|
||||||
upsampler and LoRA sidecars used by the Full/SFT two-stage pipeline, so accept
|
|
||||||
both model licenses and authenticate before first use.
|
|
||||||
|
|
||||||
## Choose a pipeline
|
|
||||||
|
|
||||||
| Pipeline | Mode | Default size | Schedule |
|
|
||||||
|---|---|---:|---:|
|
|
||||||
| `LTX2Pipeline` | Full/SFT one-stage | 960x544 | 30 steps |
|
|
||||||
| `LTX2TwoStagePipeline` | Full/SFT two-stage | 1920x1088 | 30 + 3 steps |
|
|
||||||
| `LTX2DistilledOneStagePipeline` | Distilled one-stage | 960x544 | 8 steps |
|
|
||||||
| `LTX2DistilledTwoStagePipeline` | Distilled two-stage | 1920x1088 | 8 + 3 steps |
|
|
||||||
|
|
||||||
Both two-stage pipelines generate at half resolution, apply the official x2
|
|
||||||
latent upsampler, and run a three-step refinement stage. Select the class with
|
|
||||||
`--model-class-name`; no `--task-type` flag is required. Supplying one initial
|
|
||||||
image selects I2V, while omitting it selects T2V.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
```bash
|
|
||||||
hf auth login
|
|
||||||
export MODEL=Lightricks/LTX-2.5-Diffusers
|
|
||||||
```
|
|
||||||
|
|
||||||
Install matching vLLM and vLLM-Omni versions, and ensure `ffmpeg` and
|
|
||||||
`ffprobe` are on `PATH`. I2V requires PyAV backed by an FFmpeg build with the
|
|
||||||
`libx264` encoder.
|
|
||||||
|
|
||||||
## Hardware
|
|
||||||
|
|
||||||
| GPU | Status | Recommended scope |
|
|
||||||
|---|---|---|
|
|
||||||
| NVIDIA B300 | Verified | All four canonical pipelines |
|
|
||||||
| NVIDIA B200 or H200 | Capacity-based recommendation; not yet verified | All four canonical pipelines |
|
|
||||||
| NVIDIA GB200 or GB300 | Capacity-based recommendation; not yet verified | All four canonical pipelines |
|
|
||||||
| NVIDIA H100 80 GB | FP8 recipe | Distilled one-stage at 960x544 |
|
|
||||||
|
|
||||||
The 1920x1088 two-stage examples require about 114 GB of peak GPU memory.
|
|
||||||
80 GB GPUs do not have enough safety margin for the canonical two-stage
|
|
||||||
configuration; use a larger GPU or reduce the output configuration.
|
|
||||||
|
|
||||||
### H100 80 GB FP8
|
|
||||||
|
|
||||||
Use the distilled one-stage pipeline with FP8 and cuDNN attention:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm serve Lightricks/LTX-2.5-Diffusers \
|
|
||||||
--omni \
|
|
||||||
--model-class-name LTX2DistilledOneStagePipeline \
|
|
||||||
--quantization fp8 \
|
|
||||||
--diffusion-attention-backend CUDNN_ATTN \
|
|
||||||
--host 0.0.0.0 \
|
|
||||||
--port 8000
|
|
||||||
```
|
|
||||||
|
|
||||||
Use the T2V or I2V request below after the server is ready.
|
|
||||||
|
|
||||||
## Offline inference
|
|
||||||
|
|
||||||
Choose values from the pipeline table. For example, the distilled two-stage
|
|
||||||
path uses:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export PIPELINE=LTX2DistilledTwoStagePipeline
|
|
||||||
export WIDTH=1920
|
|
||||||
export HEIGHT=1088
|
|
||||||
export STEPS=8
|
|
||||||
```
|
|
||||||
|
|
||||||
### Text-to-video
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python examples/offline_inference/text_to_video/text_to_video.py \
|
|
||||||
--model "${MODEL}" \
|
|
||||||
--model-class-name "${PIPELINE}" \
|
|
||||||
--prompt "A cinematic shot of a red fox walking through a snowy forest at dawn, the camera tracking alongside, snow crunching underfoot." \
|
|
||||||
--width "${WIDTH}" \
|
|
||||||
--height "${HEIGHT}" \
|
|
||||||
--num-frames 121 \
|
|
||||||
--num-inference-steps "${STEPS}" \
|
|
||||||
--frame-rate 24 \
|
|
||||||
--fps 24 \
|
|
||||||
--seed 42 \
|
|
||||||
--output ltx25-t2v.mp4
|
|
||||||
```
|
|
||||||
|
|
||||||
### First-frame image-to-video
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python examples/offline_inference/image_to_video/image_to_video.py \
|
|
||||||
--model "${MODEL}" \
|
|
||||||
--model-class-name "${PIPELINE}" \
|
|
||||||
--image /absolute/path/to/first-frame.png \
|
|
||||||
--prompt "The red fox walks forward while the camera tracks alongside." \
|
|
||||||
--width "${WIDTH}" \
|
|
||||||
--height "${HEIGHT}" \
|
|
||||||
--num-frames 121 \
|
|
||||||
--num-inference-steps "${STEPS}" \
|
|
||||||
--frame-rate 24 \
|
|
||||||
--fps 24 \
|
|
||||||
--seed 42 \
|
|
||||||
--output ltx25-i2v.mp4
|
|
||||||
```
|
|
||||||
|
|
||||||
LTX-2.5 uses the official CRF-18 first-frame conditioning path by default.
|
|
||||||
|
|
||||||
## Online serving
|
|
||||||
|
|
||||||
Start one server for the selected pipeline:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm serve "${MODEL}" \
|
|
||||||
--omni \
|
|
||||||
--model-class-name "${PIPELINE}" \
|
|
||||||
--host 0.0.0.0 \
|
|
||||||
--port 8000 \
|
|
||||||
--stage-init-timeout 900
|
|
||||||
```
|
|
||||||
|
|
||||||
T2V request:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -sS --fail-with-body \
|
|
||||||
-X POST http://127.0.0.1:8000/v1/videos/sync \
|
|
||||||
-F 'prompt=A cinematic shot of a red fox walking through a snowy forest at dawn, the camera tracking alongside, snow crunching underfoot.' \
|
|
||||||
-o ltx25-online-t2v.mp4
|
|
||||||
```
|
|
||||||
|
|
||||||
For I2V, add exactly one first frame:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export FIRST_FRAME=/absolute/path/to/first-frame.png
|
|
||||||
|
|
||||||
curl -sS --fail-with-body \
|
|
||||||
-X POST http://127.0.0.1:8000/v1/videos/sync \
|
|
||||||
-F "input_reference=@${FIRST_FRAME};type=image/png" \
|
|
||||||
-F 'prompt=The red fox walks forward while the camera tracks alongside.' \
|
|
||||||
-o ltx25-online-i2v.mp4
|
|
||||||
```
|
|
||||||
|
|
||||||
Restart the server after changing `PIPELINE` because each class selects a
|
|
||||||
different weight layout and execution topology.
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
|
|
||||||
- Online `width`, `height`, `num_frames`, `fps`, and `num_inference_steps`
|
|
||||||
are optional and use the selected pipeline defaults when omitted. `seed` is
|
|
||||||
optional and random when omitted.
|
|
||||||
- `num_frames` must be `8k+1` when overridden.
|
|
||||||
- One-stage width and height must be divisible by 32; two-stage final
|
|
||||||
dimensions must be divisible by 64.
|
|
||||||
- Full/SFT pipelines accept negative prompts. Distilled pipelines are
|
|
||||||
positive-only and reject negative prompts.
|
|
||||||
- I2V accepts exactly one initial image per prompt.
|
|
||||||
- The supported `--model` value is `Lightricks/LTX-2.5-Diffusers`.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [LTX-2.5-Diffusers model card](https://huggingface.co/Lightricks/LTX-2.5-Diffusers)
|
|
||||||
- [Official LTX-2 repository](https://github.com/Lightricks/LTX-2)
|
|
||||||
- [Generic T2V example](../../examples/offline_inference/text_to_video/text_to_video.py)
|
|
||||||
- [Generic I2V example](../../examples/offline_inference/image_to_video/image_to_video.py)
|
|
||||||
+49
-80
@@ -4,22 +4,11 @@
|
|||||||
|
|
||||||
## Pipelines
|
## Pipelines
|
||||||
|
|
||||||
| `--model-class-name` | Task | Required checkpoint repositories |
|
| Model | Checkpoint | Task | `--model-class-name` | Batching |
|
||||||
|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `LTX2Pipeline` | LTX-2 one-stage T2V/I2V | `Lightricks/LTX-2` |
|
| LTX-2 | `Lightricks/LTX-2` | One-stage T2V/I2V | `LTX2Pipeline` | Yes |
|
||||||
| `LTX2TwoStagePipeline` | LTX-2 ordinary two-stage T2V/I2V | `Lightricks/LTX-2` |
|
| LTX-2 distilled | `rootonchair/LTX-2-19b-distilled` | Two-stage T2V/I2V | `LTX2DistilledPipeline` | No |
|
||||||
| `LTX2DistilledOneStagePipeline` | LTX-2 merged-distilled one-stage T2V/I2V | `rootonchair/LTX-2-19b-distilled` |
|
| LTX-2.3 | `diffusers/LTX-2.3-Diffusers` | One-stage T2V/I2V | `LTX2Pipeline` | Yes |
|
||||||
| `LTX2DistilledTwoStagePipeline` | LTX-2 merged-distilled two-stage T2V/I2V | `rootonchair/LTX-2-19b-distilled` |
|
|
||||||
| `LTX2Pipeline` | LTX-2.3 one-stage T2V/I2V | `diffusers/LTX-2.3-Diffusers` |
|
|
||||||
| `LTX2TwoStagePipeline` | LTX-2.3 ordinary two-stage T2V/I2V | `diffusers/LTX-2.3-Diffusers`<br>`Lightricks/LTX-2.3` |
|
|
||||||
| `LTX2DistilledOneStagePipeline` | LTX-2.3 merged-distilled one-stage T2V/I2V | `diffusers/LTX-2.3-Distilled-Diffusers` |
|
|
||||||
| `LTX2DistilledTwoStagePipeline` | LTX-2.3 merged-distilled two-stage T2V/I2V | `diffusers/LTX-2.3-Distilled-Diffusers`<br>`Lightricks/LTX-2.3` |
|
|
||||||
|
|
||||||
Repositories in the table are download units. A full pipeline repository
|
|
||||||
contains the Transformer, text encoder, connectors, VAEs, vocoder, scheduler,
|
|
||||||
and tokenizer; an additional repository supplies LoRA or upsampler sidecars.
|
|
||||||
The runtime first searches the model root for sidecars under their official
|
|
||||||
filenames, then downloads them from the matching Lightricks Hub repository.
|
|
||||||
|
|
||||||
`LTX2Pipeline` is the unified one-stage entry. Checkpoint metadata selects the
|
`LTX2Pipeline` is the unified one-stage entry. Checkpoint metadata selects the
|
||||||
LTX-2 or LTX-2.3 profile; omitting an image selects T2V, while one initial
|
LTX-2 or LTX-2.3 profile; omitting an image selects T2V, while one initial
|
||||||
@@ -27,13 +16,10 @@ image selects I2V. Both one-stage repositories declare this class, so
|
|||||||
`--model-class-name` is optional. LTX-2.3 requires the Diffusers checkpoint;
|
`--model-class-name` is optional. LTX-2.3 requires the Diffusers checkpoint;
|
||||||
the raw `Lightricks/LTX-2.3` safetensors repository is not directly loadable.
|
the raw `Lightricks/LTX-2.3` safetensors repository is not directly loadable.
|
||||||
|
|
||||||
`LTX2TwoStagePipeline` samples the regular model at half resolution, upsamples,
|
`LTX2DistilledPipeline` is the unified distilled entry: omit `image` for T2V
|
||||||
then refines with the distilled LoRA. `LTX2DistilledOneStagePipeline` uses a
|
or provide one initial image for I2V. As in the official LTX pipelines,
|
||||||
merged distilled Transformer without upsampling, while
|
distilled mode is selected explicitly rather than inferred from the checkpoint
|
||||||
`LTX2DistilledTwoStagePipeline` uses it in both stages. All entries support
|
name or metadata; always pass `--model-class-name LTX2DistilledPipeline`.
|
||||||
T2V and I2V; select their class explicitly. The deprecated
|
|
||||||
`LTX2DistilledPipeline` name remains an alias for
|
|
||||||
`LTX2DistilledTwoStagePipeline`.
|
|
||||||
|
|
||||||
## API Migration
|
## API Migration
|
||||||
|
|
||||||
@@ -57,8 +43,8 @@ The consolidation also removes these registry names without aliases:
|
|||||||
| `LTX23Pipeline` | `LTX2Pipeline`; checkpoint metadata selects LTX-2.3 |
|
| `LTX23Pipeline` | `LTX2Pipeline`; checkpoint metadata selects LTX-2.3 |
|
||||||
| `LTX2ImageToVideoPipeline` | `LTX2Pipeline` with `image=` |
|
| `LTX2ImageToVideoPipeline` | `LTX2Pipeline` with `image=` |
|
||||||
| `LTX23ImageToVideoPipeline` | `LTX2Pipeline` with `image=`; checkpoint metadata selects LTX-2.3 |
|
| `LTX23ImageToVideoPipeline` | `LTX2Pipeline` with `image=`; checkpoint metadata selects LTX-2.3 |
|
||||||
| `LTX2TwoStagesPipeline` | `LTX2DistilledTwoStagePipeline` |
|
| `LTX2TwoStagesPipeline` | `LTX2DistilledPipeline` |
|
||||||
| `LTX2ImageToVideoTwoStagesPipeline` | `LTX2DistilledTwoStagePipeline` with `image=` |
|
| `LTX2ImageToVideoTwoStagesPipeline` | `LTX2DistilledPipeline` with `image=` |
|
||||||
|
|
||||||
Passing any second positional argument now raises `TypeError`. These changes
|
Passing any second positional argument now raises `TypeError`. These changes
|
||||||
affect direct Python callers and explicit `--model-class-name` overrides;
|
affect direct Python callers and explicit `--model-class-name` overrides;
|
||||||
@@ -82,28 +68,24 @@ The model recipe also supplies the default negative prompt. Top-level
|
|||||||
API defaults `num_frames` to `1`, so set it explicitly; the offline LTX scripts
|
API defaults `num_frames` to `1`, so set it explicitly; the offline LTX scripts
|
||||||
default to `121`.
|
default to `121`.
|
||||||
|
|
||||||
## Two-Stage Defaults
|
## Distilled Two-Stage Defaults
|
||||||
|
|
||||||
| Parameter | Ordinary | Full-distilled |
|
| Parameter | Value |
|
||||||
|---|---:|---:|
|
|---|---:|
|
||||||
| Final width × height | 1536 × 1024 | 1536 × 1024 |
|
| Final width × height | 1536 × 1024 |
|
||||||
| Stage 1 width × height | 768 × 512 | 768 × 512 |
|
| Stage 1 width × height | 768 × 512 |
|
||||||
| Frames / frame rate | 121 / 24 | 121 / 24 |
|
| Frames / frame rate | 121 / 24 |
|
||||||
| Stage 1 / Stage 2 steps | 40 (LTX-2) or 30 (LTX-2.3) / 3 | 8 / 3 |
|
| Stage 1 / Stage 2 denoise steps | 8 / 3 |
|
||||||
| Guidance | Stage 1 guided; Stage 2 positive-only | Fixed positive-only |
|
| Guidance | Fixed positive-only |
|
||||||
|
|
||||||
API dimensions are final dimensions and must be divisible by 64. All LTX
|
The dimensions passed to the API are the final output dimensions and must be
|
||||||
requests require `num_frames = 8k+1`. Ordinary Stage 1 uses the LTX-2 or
|
divisible by 64. Both one- and two-stage requests require `num_frames = 8k+1`.
|
||||||
LTX-2.3 one-stage defaults shown above. Distilled schedules are fixed, so
|
The distilled recipe fixes both sigma schedules; `num_inference_steps` must be
|
||||||
`num_inference_steps`, when supplied, must be `8`. Both entries reject custom
|
`8` when supplied.
|
||||||
sigmas and input latents.
|
|
||||||
|
|
||||||
Ordinary two-stage uses layer-fused LoRA for an unquantized BF16 Transformer
|
|
||||||
and automatically switches to dynamic LoRA when quantization is enabled.
|
|
||||||
|
|
||||||
## Serving
|
## Serving
|
||||||
|
|
||||||
Start either one-stage checkpoint:
|
Start either checkpoint:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vllm serve Lightricks/LTX-2 --omni --stage-init-timeout 600
|
vllm serve Lightricks/LTX-2 --omni --stage-init-timeout 600
|
||||||
@@ -113,19 +95,11 @@ vllm serve Lightricks/LTX-2 --omni --stage-init-timeout 600
|
|||||||
vllm serve diffusers/LTX-2.3-Diffusers --omni --stage-init-timeout 600
|
vllm serve diffusers/LTX-2.3-Diffusers --omni --stage-init-timeout 600
|
||||||
```
|
```
|
||||||
|
|
||||||
Start a two-stage checkpoint with its explicit class:
|
Start the distilled checkpoint with its two-stage entry:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vllm serve rootonchair/LTX-2-19b-distilled --omni \
|
vllm serve rootonchair/LTX-2-19b-distilled --omni \
|
||||||
--model-class-name LTX2DistilledTwoStagePipeline --stage-init-timeout 600
|
--model-class-name LTX2DistilledPipeline --stage-init-timeout 600
|
||||||
# LTX-2.3 full-distilled; the v1.1 x2 upsampler is resolved when absent
|
|
||||||
vllm serve diffusers/LTX-2.3-Distilled-Diffusers --omni \
|
|
||||||
--model-class-name LTX2DistilledTwoStagePipeline --stage-init-timeout 600
|
|
||||||
# Ordinary LTX-2.3 two-stage
|
|
||||||
vllm serve diffusers/LTX-2.3-Diffusers --omni \
|
|
||||||
--model-class-name LTX2TwoStagePipeline \
|
|
||||||
--enable-layerwise-offload \
|
|
||||||
--stage-init-timeout 600
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The same server handles T2V and I2V. A T2V request using the selected recipe's
|
The same server handles T2V and I2V. A T2V request using the selected recipe's
|
||||||
@@ -161,9 +135,8 @@ together with `input_reference`.
|
|||||||
|
|
||||||
## Guidance
|
## Guidance
|
||||||
|
|
||||||
One-stage and ordinary Stage 1 support independent video/audio CFG,
|
LTX supports independent video/audio CFG, spatio-temporal guidance (STG),
|
||||||
spatio-temporal guidance (STG), cross-modality guidance, and rescaling.
|
cross-modality guidance, and rescaling:
|
||||||
Distilled stages and ordinary Stage 2 are fixed positive-only.
|
|
||||||
|
|
||||||
| Parameter | Default | Effect | Alias |
|
| Parameter | Default | Effect | Alias |
|
||||||
|---|---:|---|---|
|
|---|---:|---|---|
|
||||||
@@ -199,9 +172,9 @@ complete guidance plan in parallel. Despite the legacy `cfg` name, it covers
|
|||||||
text CFG, STG, and cross-modality guidance passes; guidance rescaling is
|
text CFG, STG, and cross-modality guidance passes; guidance rescaling is
|
||||||
applied after all pass predictions are gathered.
|
applied after all pass predictions are gathered.
|
||||||
|
|
||||||
The default one-stage recipe and ordinary Stage 1 have four Transformer passes
|
The default one-stage recipe has four Transformer passes per denoise step:
|
||||||
per denoise step: `cond`, `uncond`, `ptb` (STG), and `mod`
|
`cond`, `uncond`, `ptb` (STG), and `mod` (cross-modality). The useful balanced
|
||||||
(cross-modality). The useful balanced configurations are therefore:
|
configurations are therefore:
|
||||||
|
|
||||||
| `--cfg-parallel-size` | Passes per rank | Guidance-slot utilization | Notes |
|
| `--cfg-parallel-size` | Passes per rank | Guidance-slot utilization | Notes |
|
||||||
|---:|---:|---:|---|
|
|---:|---:|---:|---|
|
||||||
@@ -222,8 +195,9 @@ vllm serve diffusers/LTX-2.3-Diffusers --omni \
|
|||||||
```
|
```
|
||||||
|
|
||||||
The total device count is the product of `cfg_parallel_size` and the other
|
The total device count is the product of `cfg_parallel_size` and the other
|
||||||
configured parallel dimensions. Positive-only distilled phases do not benefit
|
configured parallel dimensions. Distilled pipelines use positive-only
|
||||||
from `cfg_parallel_size > 1`.
|
guidance, so `cfg_parallel_size > 1` does not provide guidance speedup for
|
||||||
|
them.
|
||||||
|
|
||||||
### Python API
|
### Python API
|
||||||
|
|
||||||
@@ -268,22 +242,22 @@ noted below.
|
|||||||
| `image` | image or batch, `None` | Direct value wins over request images; no image selects T2V. I2V accepts one image per prompt, and a batch cannot mix T2V/I2V. |
|
| `image` | image or batch, `None` | Direct value wins over request images; no image selects T2V. I2V accepts one image per prompt, and a batch cannot mix T2V/I2V. |
|
||||||
| `prompt` | string or list, `None` | Positive-text fallback; request prompts win. Mutually exclusive with `prompt_embeds`. |
|
| `prompt` | string or list, `None` | Positive-text fallback; request prompts win. Mutually exclusive with `prompt_embeds`. |
|
||||||
| `negative_prompt` | string or list, `None` | Fallback after request values and before the recipe default; mutually exclusive with negative embeddings. |
|
| `negative_prompt` | string or list, `None` | Fallback after request values and before the recipe default; mutually exclusive with negative embeddings. |
|
||||||
| `height` | `int`, `None` | Request → direct value → recipe default; divisible by 32 one-stage or 64 two-stage. |
|
| `height` | `int`, `None` | Request → direct value → recipe default; divisible by 32 one-stage or 64 distilled. |
|
||||||
| `width` | `int`, `None` | Same precedence and alignment as `height`. |
|
| `width` | `int`, `None` | Same precedence and alignment as `height`. |
|
||||||
| `num_frames` | `int`, `None` | Request → direct value → recipe default; must be `8k+1` and also determines audio duration with `frame_rate`. |
|
| `num_frames` | `int`, `None` | Request → direct value → recipe default; must be `8k+1` and also determines audio duration with `frame_rate`. |
|
||||||
| `frame_rate` | `float`, `None` | Request `frame_rate` → request `fps` → direct value → recipe default. |
|
| `frame_rate` | `float`, `None` | Request `frame_rate` → request `fps` → direct value → recipe default. |
|
||||||
| `num_inference_steps` | `int`, `None` | Request → direct value → recipe default; minimum 2 one-stage and ordinary Stage 1, fixed at 8 for distilled Stage 1. Custom one-stage `sigmas` determine actual steps. |
|
| `num_inference_steps` | `int`, `None` | Request → direct value → recipe default; minimum 2 one-stage, fixed at 8 for distilled Stage 1. Custom one-stage `sigmas` determine actual steps. |
|
||||||
| `sigmas` | list of float, `None` | One-stage only. Request values win; every request in a fused batch must use the same schedule. |
|
| `sigmas` | list of float, `None` | One-stage only. Request values win; every request in a fused batch must use the same schedule. |
|
||||||
| `timesteps` | list of int, `None` | Compatibility slot; LTX accepts only `None`. Use `sigmas`. |
|
| `timesteps` | list of int, `None` | Compatibility slot; LTX accepts only `None`. Use `sigmas`. |
|
||||||
| `guidance_scale` | `float`, `None` | One-stage and ordinary Stage 1 common video/audio CFG fallback; an explicit request value wins. Distilled phases use fixed positive-only guidance. |
|
| `guidance_scale` | `float`, `None` | One-stage common video/audio CFG fallback; an explicit request value wins. Distilled two-stage uses fixed positive-only guidance. |
|
||||||
| `guidance_rescale` | `float`, `None` | Accepts only `None` or `0.0`; use the modality rescale fields. |
|
| `guidance_rescale` | `float`, `None` | Accepts only `None` or `0.0`; use the modality rescale fields. |
|
||||||
| `noise_scale` | `float`, `0.0` | Compatibility slot; LTX accepts only `0.0`. |
|
| `noise_scale` | `float`, `0.0` | Compatibility slot; LTX accepts only `0.0`. |
|
||||||
| `num_videos_per_prompt` | `int`, `1` | Output-count fallback; positive request `num_outputs_per_prompt` wins. |
|
| `num_videos_per_prompt` | `int`, `1` | Output-count fallback; positive request `num_outputs_per_prompt` wins. |
|
||||||
| `generator` | generator or list, `None` | Explicit RNG; otherwise request generators/seeds are collated. Lists must match the effective output batch. |
|
| `generator` | generator or list, `None` | Explicit RNG; otherwise request generators/seeds are collated. Lists must match the effective output batch. |
|
||||||
| `latents` | tensor, `None` | One-stage only. Request tensors win; packed `[B, S, C]` and validated unpacked video layouts are accepted. |
|
| `latents` | tensor, `None` | Request tensors win. One-stage accepts packed `[B, S, C]` and validated unpacked video layouts. |
|
||||||
| `audio_latents` | tensor, `None` | One-stage only. Request tensors win and are collated. |
|
| `audio_latents` | tensor, `None` | One-stage initial audio latents; request tensors win and are collated. |
|
||||||
| `prompt_embeds` | tensor, `None` | Precomputed positive conditioning; requires `prompt_attention_mask` and cannot accompany `prompt`. |
|
| `prompt_embeds` | tensor, `None` | Precomputed positive conditioning; requires `prompt_attention_mask` and cannot accompany `prompt`. |
|
||||||
| `negative_prompt_embeds` | tensor, `None` | One-stage and ordinary Stage 1 conditioning; requires its mask and cannot accompany `negative_prompt`. |
|
| `negative_prompt_embeds` | tensor, `None` | One-stage precomputed negative conditioning; requires its mask and cannot accompany `negative_prompt`. |
|
||||||
| `prompt_attention_mask` | tensor, `None` | Mask for positive embeddings; request `prompt_attention_mask` or `attention_mask` wins. |
|
| `prompt_attention_mask` | tensor, `None` | Mask for positive embeddings; request `prompt_attention_mask` or `attention_mask` wins. |
|
||||||
| `negative_prompt_attention_mask` | tensor, `None` | Mask for negative embeddings; request negative mask fields win. |
|
| `negative_prompt_attention_mask` | tensor, `None` | Mask for negative embeddings; request negative mask fields win. |
|
||||||
| `decode_timestep` | float or list, `0.0` | Video-VAE decode timestep; request value wins. Lists may match 1, prompt batch, or output batch. |
|
| `decode_timestep` | float or list, `0.0` | Video-VAE decode timestep; request value wins. Lists may match 1, prompt batch, or output batch. |
|
||||||
@@ -299,13 +273,13 @@ request prompt payload; LTX guidance fields live in sampling `extra_args`.
|
|||||||
|
|
||||||
### Recipe-Specific Request Capabilities
|
### Recipe-Specific Request Capabilities
|
||||||
|
|
||||||
| Override | One-stage | Ordinary two-stage | Distilled two-stage |
|
| Override | One-stage | Distilled two-stage |
|
||||||
|---|---|---|---|
|
|---|---|---|
|
||||||
| Guidance | Supported | Stage 1 only; Stage 2 is positive-only | Fixed positive-only |
|
| Guidance | Supported | Fixed positive-only |
|
||||||
| Negative prompt/embeddings | Supported | Supported by Stage 1 | Rejected |
|
| Negative prompt/embeddings | Supported | Rejected |
|
||||||
| `num_inference_steps` | Supported | Controls Stage 1; Stage 2 uses 3 | Fixed at 8 for Stage 1; Stage 2 uses 3 |
|
| `num_inference_steps` | Supported | Fixed at 8 for Stage 1; Stage 2 uses 3 |
|
||||||
| Custom `sigmas` | Supported | Rejected | Rejected; both phases use fixed schedules |
|
| Custom `sigmas` | Supported | Rejected; both phases use fixed schedules |
|
||||||
| Video/audio latents | Supported | Rejected | Rejected |
|
| Video/audio latents | Supported | Rejected |
|
||||||
|
|
||||||
These capability checks apply equally to direct `forward` keywords and
|
These capability checks apply equally to direct `forward` keywords and
|
||||||
values in `OmniDiffusionSamplingParams`; unsupported values fail instead of
|
values in `OmniDiffusionSamplingParams`; unsupported values fail instead of
|
||||||
@@ -335,10 +309,6 @@ bundled offline CLI do not currently expose `sigmas`.
|
|||||||
`--max-num-seqs 1`.
|
`--max-num-seqs 1`.
|
||||||
- `--cfg-parallel-size` shards the complete LTX guidance plan, including STG,
|
- `--cfg-parallel-size` shards the complete LTX guidance plan, including STG,
|
||||||
modality guidance, and rescale-compatible prediction gathering.
|
modality guidance, and rescale-compatible prediction gathering.
|
||||||
- Sequence parallelism may pad audio latents. Pure Ulysses masks the padding;
|
|
||||||
Ring cannot, so audio length must be SP-divisible. Use `ring_degree=1` or a
|
|
||||||
divisible request shape.
|
|
||||||
- Cache-DiT is one-stage only; multi-stage configurations are rejected.
|
|
||||||
|
|
||||||
## Operational Notes
|
## Operational Notes
|
||||||
|
|
||||||
@@ -353,15 +323,14 @@ bundled offline CLI do not currently expose `sigmas`.
|
|||||||
request parameter.
|
request parameter.
|
||||||
- For benchmarks, use `tests/dfx/perf/tests/test_ltx2_vllm_omni.json` with
|
- For benchmarks, use `tests/dfx/perf/tests/test_ltx2_vllm_omni.json` with
|
||||||
`tests/dfx/perf/scripts/run_diffusion_benchmark.py`.
|
`tests/dfx/perf/scripts/run_diffusion_benchmark.py`.
|
||||||
- Ordinary and full-distilled two-stage T2V/I2V are supported; HQ execution
|
- Current two-stage support is limited to the distilled checkpoint; official
|
||||||
remains out of scope.
|
LTX two-stage/HQ execution remains out of scope.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- <https://huggingface.co/Lightricks/LTX-2>
|
- <https://huggingface.co/Lightricks/LTX-2>
|
||||||
- <https://huggingface.co/Lightricks/LTX-2.3>
|
- <https://huggingface.co/Lightricks/LTX-2.3>
|
||||||
- <https://huggingface.co/diffusers/LTX-2.3-Diffusers>
|
- <https://huggingface.co/diffusers/LTX-2.3-Diffusers>
|
||||||
- <https://huggingface.co/diffusers/LTX-2.3-Distilled-Diffusers>
|
|
||||||
- [Online video generation](../../docs/user_guide/examples/online_serving/text_to_video.md)
|
- [Online video generation](../../docs/user_guide/examples/online_serving/text_to_video.md)
|
||||||
- [Diffusion execution modes](../../docs/user_guide/diffusion/execution_modes.md)
|
- [Diffusion execution modes](../../docs/user_guide/diffusion/execution_modes.md)
|
||||||
- [T2V offline example](../../examples/offline_inference/text_to_video/text_to_video.md)
|
- [T2V offline example](../../examples/offline_inference/text_to_video/text_to_video.md)
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ MammothModa2 generation parameters as a JSON object through `--extra-body`:
|
|||||||
```bash
|
```bash
|
||||||
python examples/offline_inference/text_to_image/text_to_image.py \
|
python examples/offline_inference/text_to_image/text_to_image.py \
|
||||||
--model ./MammothModa2-Preview \
|
--model ./MammothModa2-Preview \
|
||||||
--deploy-config vllm_omni/deploy/mammoth_moda2.yaml \
|
--stage-configs-path vllm_omni/deploy/mammoth_moda2.yaml \
|
||||||
--prompt "A stylish woman riding a motorcycle in NYC, movie poster style" \
|
--prompt "A stylish woman riding a motorcycle in NYC, movie poster style" \
|
||||||
--height 1024 \
|
--height 1024 \
|
||||||
--width 1024 \
|
--width 1024 \
|
||||||
@@ -132,44 +132,6 @@ ls -lh mammoth_t2i.png
|
|||||||
python -c "from PIL import Image; print(Image.open('mammoth_t2i.png').size)"
|
python -c "from PIL import Image; print(Image.open('mammoth_t2i.png').size)"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1x AMD MI300X, MammothModa2 Preview
|
|
||||||
|
|
||||||
#### Environment
|
|
||||||
|
|
||||||
- OS: Linux 6.8.0-134-generic, x86_64
|
|
||||||
- Container: official ROCm image built from `docker/Dockerfile.rocm`
|
|
||||||
- Python: 3.12.13
|
|
||||||
- PyTorch: 2.11.0+gitd0c8b1f
|
|
||||||
- Driver / runtime: AMD 6.19.14.31400000 / ROCm 7.2.53211
|
|
||||||
- GPU: one AMD Instinct MI300X, `gfx942:sramecc+:xnack-`, 191.69 GiB visible HBM
|
|
||||||
- vLLM version: 0.27.0+rocm723
|
|
||||||
- vLLM Omni version or commit: `73e1368c7bb940efe1a025859c9d6c8eeeb2e3f0`
|
|
||||||
- Installed vLLM Omni package metadata: `0.27.0rc2.dev44+g55abdade9.rocm`
|
|
||||||
|
|
||||||
#### Offline Commands
|
|
||||||
|
|
||||||
The checked run used the committed stage split, with `gpu_memory_utilization` set to 0.5 for AR and 0.3 for DiT:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 examples/offline_inference/text_to_image/text_to_image.py \
|
|
||||||
--model bytedance-research/MammothModa2-Preview \
|
|
||||||
--deploy-config vllm_omni/deploy/mammoth_moda2.yaml \
|
|
||||||
--prompt "A stylish woman riding a motorcycle in NYC, movie poster style" \
|
|
||||||
--height 1024 \
|
|
||||||
--width 1024 \
|
|
||||||
--seed 42 \
|
|
||||||
--extra-body '{"text_guidance_scale": 4.0, "cfg_range": [0.0, 1.0], "num_inference_steps": 50}' \
|
|
||||||
--enable-diffusion-pipeline-profiler \
|
|
||||||
--log-stats \
|
|
||||||
--output mammoth_t2i.png
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Verification
|
|
||||||
|
|
||||||
The first request took 85.224 seconds. The AR stage generated 4,161 visual tokens in 72.996 seconds, and the DiT stage took 12.163 seconds. AR weight loading used 21.4 GiB and took 8.250 seconds. DiT weight loading used 5.49 GiB and took 1.824 seconds. The largest one second whole device memory sample was 106.57 GiB, including the AR KV cache reserved by the 0.5 memory setting.
|
|
||||||
|
|
||||||
The output was a valid 1024 by 1024 RGB PNG.
|
|
||||||
|
|
||||||
## MammothModa2-Dev unified inference
|
## MammothModa2-Dev unified inference
|
||||||
|
|
||||||
MammothModa2-Dev uses a Qwen3-VL AR backbone, while MammothModa2-Preview uses
|
MammothModa2-Dev uses a Qwen3-VL AR backbone, while MammothModa2-Preview uses
|
||||||
|
|||||||
@@ -1,195 +0,0 @@
|
|||||||
# MiniMax-H3 on a single NPU 950PR
|
|
||||||
|
|
||||||
This recipe runs MiniMax-H3 with online INT8 quantization on one NPU 950PR
|
|
||||||
NPU (128 GB HiBL 1.0 HBM). It covers the single-card T2VA configuration at
|
|
||||||
1024x576. For the eight-card Atlas 800I A3 BF16 route at 768P, see
|
|
||||||
[MiniMax-H3-NPU.md](MiniMax-H3-NPU.md).
|
|
||||||
|
|
||||||
## Capacity requirements
|
|
||||||
|
|
||||||
| Resource | Requirement |
|
|
||||||
| --- | ---: |
|
|
||||||
| NPU | 1x NPU 950PR |
|
|
||||||
| NPU HBM | 128 GiB (131,072 MiB reported by `npu-smi`) |
|
|
||||||
| Observed HBM high-water mark | 118,442 MiB (90.4% of capacity) |
|
|
||||||
| Checkpoint storage | 135 GiB per partition, local disk strongly preferred |
|
|
||||||
| Container shared memory | 8 GiB, or a writable local filesystem for spill |
|
|
||||||
|
|
||||||
The observed high-water mark leaves about 12.3 GiB of headroom, so this card is
|
|
||||||
sized for exactly one resident task partition at 1024x576. Do not expect a
|
|
||||||
larger output shape or concurrency greater than one to fit.
|
|
||||||
|
|
||||||
Because a single 950PR holds the whole partition in HBM, this route does not
|
|
||||||
need layerwise offload, and the 200 GiB system-RAM floor carried by the 72 GiB
|
|
||||||
GPU recipes does not apply. The validated run completed inside a container
|
|
||||||
limited to 32 GiB of RAM. Passing `--enable-layerwise-offload` on this card is
|
|
||||||
actively harmful: it stages weights in non-reclaimable host memory and the
|
|
||||||
container's OOM killer terminates the server with exit code -9.
|
|
||||||
|
|
||||||
|
|
||||||
## Environment
|
|
||||||
|
|
||||||
- Host architecture: x86_64
|
|
||||||
- driver: 25.7.rc1.6 (hal 7.35.23)
|
|
||||||
- firmware: 9.0.0.105.229
|
|
||||||
- CANN toolkit: 9.1.0 (`/usr/local//cann-9.1.0`)
|
|
||||||
- npu-smi: 25.7.rc1.6
|
|
||||||
- Python: 3.12.13
|
|
||||||
- PyTorch: 2.10.0+cpu
|
|
||||||
- torch_npu: 2.10.0.post2
|
|
||||||
- vLLM: 0.26.0
|
|
||||||
- vLLM-Omni: 0.26.1.dev103+g584d78c67.npu (commit `584d78c6`)
|
|
||||||
|
|
||||||
Install vLLM-Omni from a checkout with MiniMax-H3 support:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uv venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
uv pip install -e .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Start a server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export MODEL=/path/to/MiniMax-H3/FL2VA
|
|
||||||
export PORT=8000
|
|
||||||
export VLLM_WORKER_MULTIPROC_METHOD=spawn
|
|
||||||
export VLLM_OMNI_VIDEO_SYNC_TIMEOUT=14400
|
|
||||||
|
|
||||||
vllm serve "${MODEL}" \
|
|
||||||
--omni \
|
|
||||||
--host 0.0.0.0 \
|
|
||||||
--port "${PORT}" \
|
|
||||||
--trust-remote-code \
|
|
||||||
--num-gpus 1 \
|
|
||||||
--tensor-parallel-size 1 \
|
|
||||||
--usp 1 \
|
|
||||||
--ring 1 \
|
|
||||||
--text-encoder-tp-size 1 \
|
|
||||||
--vae-patch-parallel-size 1 \
|
|
||||||
--vae-parallel-mode tile \
|
|
||||||
--vae-use-tiling \
|
|
||||||
--init-timeout 14400 \
|
|
||||||
--stage-init-timeout 14400 \
|
|
||||||
--quantization int8
|
|
||||||
```
|
|
||||||
|
|
||||||
Do not pass `--diffusion-attention-backend CUDNN_ATTN`. cuDNN attention has no
|
|
||||||
NPU implementation and fails at the first denoise step. Leave the backend
|
|
||||||
unset: it resolves to `TORCH_SDPA`, or to `FLASH_ATTN` when MindIE-SD is
|
|
||||||
installed (see [MiniMax-H3-NPU.md § Environment](MiniMax-H3-NPU.md#environment)).
|
|
||||||
|
|
||||||
H3 is CFG-distilled, so `--cfg-parallel-size` must remain 1.
|
|
||||||
|
|
||||||
## Validated evidence
|
|
||||||
|
|
||||||
Measured on one NPU 950PR with the server command above plus
|
|
||||||
`--enable-diffusion-pipeline-profiler`, generating a 5 s 1024x576 T2VA clip from
|
|
||||||
the request in [§ T2VA request example](#t2va-request-example). MiniMax-H3
|
|
||||||
requested 60 denoise steps and executed 59 denoise updates, so per-step latency
|
|
||||||
is `denoise / 59`. `ffprobe` confirms every returned MP4 is 1024x576, 24 fps,
|
|
||||||
124 video frames, 5.175 s, with a 32 kHz audio track of 165,600 samples.
|
|
||||||
|
|
||||||
Five requests were issued back to back with no idle time between them. The card
|
|
||||||
is measurably faster on the first request than in steady state, so both regimes
|
|
||||||
are reported.
|
|
||||||
|
|
||||||
| Stage | First request, cold card | Thermal steady state |
|
|
||||||
| --- | ---: | ---: |
|
|
||||||
| End-to-end request | 462.13 s | 506.38 s |
|
|
||||||
| Text encode | 0.55 s | 0.05 s |
|
|
||||||
| Denoise (59 updates) | 429.29 s | 471.74 s |
|
|
||||||
| Per denoise update | 7,276 ms | 7,996 ms |
|
|
||||||
| VAE decode | 8.43 s | 8.14 s |
|
|
||||||
| Worker-to-server handoff | 24 s | 27 s |
|
|
||||||
| MP4 muxing | 0.57 s | 0.50 s |
|
|
||||||
| Server-reported `denoise_step_latency_ms` | 7,702 ms | 8,440 ms |
|
|
||||||
| Peak HBM (`npu-smi`) | 118,442 MiB | 118,442 MiB |
|
|
||||||
| Average NPU power | 566.9 W | 556.9 W |
|
|
||||||
| Peak NPU temperature | 104 C | 104 C |
|
|
||||||
|
|
||||||
Sample counts differ per row. The cold column is a single request. In the steady
|
|
||||||
column, text encode, denoise and VAE decode are means over the four subsequent
|
|
||||||
requests — denoise spanned 470.37 to 473.21 s, a 0.60% spread — while
|
|
||||||
end-to-end, handoff and muxing are means over the two of those four that
|
|
||||||
survived the handoff timeout described above.
|
|
||||||
|
|
||||||
**Report the steady-state column.** The cold card completes denoise 9.9% faster
|
|
||||||
while drawing only 1.8% more average power, and both regimes reach the same
|
|
||||||
104 C ceiling, so the first request is buying a short window of higher clocks
|
|
||||||
before the die saturates. Any benchmark that issues a single request against an
|
|
||||||
idle card will overstate throughput by about 10%.
|
|
||||||
|
|
||||||
**Do not read `denoise_step_latency_ms` as a per-step time.** The server divides
|
|
||||||
the entire stage wall time by the 60 *requested* steps, so it absorbs text
|
|
||||||
encoding, VAE decode and the 837 MiB handoff. All three successful requests
|
|
||||||
satisfy `denoise_step_latency_ms == e2e_stage_wall_time_ms / 60` exactly. The
|
|
||||||
per-update figure in the table is `diffuse / 59` from the pipeline profiler.
|
|
||||||
|
|
||||||
Denoise accounts for 93% of the steady-state request and the handoff accounts
|
|
||||||
for 5.3%, so keeping the transfer in shared memory should bring end-to-end down
|
|
||||||
to roughly 480 s.
|
|
||||||
|
|
||||||
Peak HBM is sampled externally with `npu-smi` at a 3 s interval. It is a
|
|
||||||
reserved high-water mark rather than live allocation: it reads 111,272 MiB after
|
|
||||||
weight loading and before the first request, and does not fall back after
|
|
||||||
requests complete. Re-measure for longer outputs, a different output shape, or
|
|
||||||
concurrency greater than one.
|
|
||||||
|
|
||||||
### Startup
|
|
||||||
|
|
||||||
Weight loading took **31 min 50 s** on the validated host (first log line
|
|
||||||
08:04:59, `Application startup complete` 08:36:49), with the checkpoint on a
|
|
||||||
shared GlusterFS network mount. This is why the server command sets
|
|
||||||
`--init-timeout 14400` and `--stage-init-timeout 14400`; the stock 600 s and
|
|
||||||
1800 s timeouts both abort mid-load with `TimeoutError` and exit code 143.
|
|
||||||
Reduce both timeouts when the partition is staged on local disk.
|
|
||||||
|
|
||||||
## T2VA request example
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export API_URL="http://127.0.0.1:${PORT}/v1/videos/sync"
|
|
||||||
|
|
||||||
curl -sS --max-time 1800 -X POST "${API_URL}" \
|
|
||||||
-F 'prompt=At night, three cats march into a bedroom playing tiny brass instruments, then abruptly file out, with synchronized room ambience.' \
|
|
||||||
-F 'width=1024' \
|
|
||||||
-F 'height=576' \
|
|
||||||
-F 'aspect_ratio=16:9' \
|
|
||||||
-F 'fps=24' \
|
|
||||||
-F 'num_inference_steps=60' \
|
|
||||||
-F 'flow_shift=12' \
|
|
||||||
-F 'seed=1101' \
|
|
||||||
-F 'extra_params={"task":"t2va","duration":5,"audio_flow_shift":3.0}' \
|
|
||||||
-o t2va.mp4
|
|
||||||
```
|
|
||||||
|
|
||||||
## Known limitations
|
|
||||||
|
|
||||||
- INT8 online quantization is validated for T2VA on this card. Use BF16 for
|
|
||||||
FL2VA and Ref2VA, or re-measure before relying on it.
|
|
||||||
- Single-card serving loads one task partition at a time. For Ref2VA, stop the
|
|
||||||
server and restart it against the `Ref2VA` directory.
|
|
||||||
- Sustained load drives the die to 104 C and costs about 10% of denoise
|
|
||||||
throughput relative to a cold card. Warm the server with at least one full
|
|
||||||
request before measuring, and treat steady state as the reportable number.
|
|
||||||
- Requests can fail during result handoff on hosts where the IPC spill path is
|
|
||||||
slow. See [§ Result handoff and shared
|
|
||||||
memory](#result-handoff-and-shared-memory).
|
|
||||||
- Loading the checkpoint from a network mount takes over 30 minutes and forces
|
|
||||||
very large init timeouts. Stage the partition on local disk when possible.
|
|
||||||
- The configuration measured here is 1024x576 at 60 steps, which differs from
|
|
||||||
the 1344x768 at 50 steps used by the GPU recipes in this directory. Denoise
|
|
||||||
cost scales superlinearly with token count, so the numbers are not directly
|
|
||||||
comparable across recipes.
|
|
||||||
- The image ships a `triton` package whose backend is not built
|
|
||||||
(`No module named 'triton._C.libtriton.'`). vLLM logs this as an error
|
|
||||||
at startup and disables Triton; the diffusion path does not need it and the
|
|
||||||
run completes normally.
|
|
||||||
|
|
||||||
## Additional resources
|
|
||||||
|
|
||||||
- [MiniMax-H3.md](MiniMax-H3.md) — full GPU guide
|
|
||||||
- [MiniMax-H3-NPU.md](MiniMax-H3-NPU.md) — eight-card Atlas 800I A3 BF16 guide
|
|
||||||
- [Int8 quantization](../../docs/user_guide/quantization/int8.md)
|
|
||||||
- [Supported models](../../docs/models/supported_models.md)
|
|
||||||
- [Video API](../../docs/serving/videos_api.md)
|
|
||||||
@@ -357,19 +357,12 @@ No restart is needed: `task=fl2va` routes to `FL2VA/transformer`, while
|
|||||||
|
|
||||||
### Online FP8 quantization
|
### Online FP8 quantization
|
||||||
|
|
||||||
MiniMax H3 supports online FP8 quantization of both the DiT and the Qwen3-VL
|
MiniMax H3 supports load-time FP8 quantization of the DiT. The checkpoint
|
||||||
text decoder. The checkpoint remains BF16 on disk; vLLM-Omni creates FP8
|
remains BF16 on disk; vLLM-Omni quantizes eligible weights while loading and
|
||||||
weights at runtime and uses dynamic activation scaling during inference. By
|
uses dynamic activation scaling during inference. By default, attention and
|
||||||
default, `--quantization fp8` quantizes eligible attention and MLP linears in
|
MLP linears in the token refiner and main DiT blocks, the condition
|
||||||
the text decoder, token refiner, and main DiT blocks, as well as the DiT
|
projection, and all AdaLN projections use FP8. Patch, timestep, and final
|
||||||
condition and AdaLN projections. The Qwen vision tower, embeddings, norms,
|
projections remain FP32; the text encoder and VAEs are unchanged.
|
||||||
RoPE, both VAEs, and the model's FP32 patch, timestep, and output projections
|
|
||||||
keep checkpoint precision.
|
|
||||||
|
|
||||||
To select a component, use `--diffusion-quantization-config` with
|
|
||||||
`{"transformer":{"method":"fp8"}}` for DiT-only FP8 or
|
|
||||||
`{"text_encoder":{"method":"fp8"}}` for text-decoder-only FP8. The two
|
|
||||||
entries can be combined. The shorthand below enables both components.
|
|
||||||
|
|
||||||
Add this option to an existing H3 server command:
|
Add this option to an existing H3 server command:
|
||||||
|
|
||||||
@@ -377,28 +370,6 @@ Add this option to an existing H3 server command:
|
|||||||
--quantization fp8
|
--quantization fp8
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Single 96 GB GPU, no-offload capacity check
|
|
||||||
|
|
||||||
Use the FL2VA-only partition for this capacity test. Loading the combined
|
|
||||||
service would also load the Ref2VA DiT and would test a different memory
|
|
||||||
budget. A no-offload capacity check should contain none of
|
|
||||||
`--enable-cpu-offload`, `--enable-layerwise-offload`, or
|
|
||||||
`--enable-distributed-layerwise-offload`. VAE tiling changes decode placement
|
|
||||||
but does not offload model weights to the CPU.
|
|
||||||
|
|
||||||
The run passes the capacity check when the server initializes, the request
|
|
||||||
finishes without CUDA OOM or Xid errors, `peak_used_mib` remains below the
|
|
||||||
card's reported `memory_total_mib`, and `ffprobe` reports H.264 video plus
|
|
||||||
32 kHz stereo AAC audio. Report the measured headroom rather than assuming
|
|
||||||
that every nominal 96 GB SKU exposes the same MiB total.
|
|
||||||
|
|
||||||
As a capacity proxy only, the same five-second case on one B300 measured a
|
|
||||||
92,946 MiB whole-device first-request peak and a 92,146 MiB worker peak. Its
|
|
||||||
encode, diffuse, decode, and client wall times were 8.664 s, 134.504 s,
|
|
||||||
6.016 s, and 151.583 s. These numbers suggest that a 96 GB card may fit, but
|
|
||||||
they are not an RTX PRO 6000 validation: kernels, allocator behavior, and
|
|
||||||
reported device capacity differ across GPUs.
|
|
||||||
|
|
||||||
Use `ignored_layers` to keep any otherwise eligible linear in BF16. H3
|
Use `ignored_layers` to keep any otherwise eligible linear in BF16. H3
|
||||||
resolves the `transformer` component before constructing the DiT, so names do
|
resolves the `transformer` component before constructing the DiT, so names do
|
||||||
not start with `transformer.`. Entries are exact runtime linear prefixes; a
|
not start with `transformer.`. Entries are exact runtime linear prefixes; a
|
||||||
@@ -415,11 +386,10 @@ For example, keep the first main block's attention projections in BF16 with:
|
|||||||
'{"transformer":{"method":"fp8","ignored_layers":["blocks.0.attn.qkv_proj","blocks.0.attn.out_proj"]}}'
|
'{"transformer":{"method":"fp8","ignored_layers":["blocks.0.attn.qkv_proj","blocks.0.attn.out_proj"]}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
The structured option replaces `--quantization fp8`. Online FP8 can be used
|
The structured option replaces `--quantization fp8`. Online FP8 is currently
|
||||||
with H3 layerwise offload and with both DLO transfer paths. The default
|
incompatible with H3 layerwise offload because the offload path produces a
|
||||||
AllGather path uses the ordinary loader to finalize FP8 weights and scales
|
weight stride rejected by the Cutlass FP8 kernel. Use resident FP8 with tensor
|
||||||
before sharding them across ranks. `--dlo-no-use-allgather` instead retains
|
parallelism and VAE tiling instead.
|
||||||
complete rank-local tensors and avoids the synchronized request-wave contract.
|
|
||||||
|
|
||||||
## AMD ROCm (gfx942 / gfx950)
|
## AMD ROCm (gfx942 / gfx950)
|
||||||
|
|
||||||
@@ -780,13 +750,6 @@ audio spectral cosine 0.9589 (minimum 0.80), and audio RMS ratio 0.9342. The
|
|||||||
resident per-GPU peak was 68.52 GiB for BF16 and 53.51 GiB for FP8, a 22%
|
resident per-GPU peak was 68.52 GiB for BF16 and 53.51 GiB for FP8, a 22%
|
||||||
reduction.
|
reduction.
|
||||||
|
|
||||||
For direct human inspection, see the external
|
|
||||||
[BF16 versus global-FP8 comparison](https://lishunyang12.github.io/vllm-omni-rankings/scripts/minimax_h3_global_fp8_vs_bf16/),
|
|
||||||
which includes matched five-second T2VA, I2VA, and Ref2VA videos. The
|
|
||||||
[comparison sources](https://github.com/lishunyang12/vllm-omni-rankings/tree/main/scripts/minimax_h3_global_fp8_vs_bf16)
|
|
||||||
also record per-task fidelity metrics and provenance without storing generated
|
|
||||||
media in this repository.
|
|
||||||
|
|
||||||
## TeaCache acceleration
|
## TeaCache acceleration
|
||||||
|
|
||||||
TeaCache reuses DiT block residuals across denoising steps when consecutive
|
TeaCache reuses DiT block residuals across denoising steps when consecutive
|
||||||
@@ -871,9 +834,7 @@ vllm serve "${MODEL_ROOT}/FL2VA" \
|
|||||||
H3 native `tile` mode only.
|
H3 native `tile` mode only.
|
||||||
- A U2 x Ring2 hybrid currently fails with an attention-mask length mismatch; use
|
- A U2 x Ring2 hybrid currently fails with an attention-mask length mismatch; use
|
||||||
pure Ulysses.
|
pure Ulysses.
|
||||||
- Online FP8 with DLO AllGather temporarily materializes the complete FP8 model
|
- Online FP8 is not compatible with layerwise offload.
|
||||||
in host memory on every rank during startup before retaining only each rank's
|
|
||||||
shard. Size startup host memory for that transient peak.
|
|
||||||
- TeaCache and Cache-DiT cannot be enabled on the same server.
|
- TeaCache and Cache-DiT cannot be enabled on the same server.
|
||||||
- Image+audio Ref2VA accepts exactly one image and one audio reference.
|
- Image+audio Ref2VA accepts exactly one image and one audio reference.
|
||||||
- Video Ref2VA accepts one or more video files, but not an additional standalone
|
- Video Ref2VA accepts one or more video files, but not an additional standalone
|
||||||
|
|||||||
@@ -1,173 +0,0 @@
|
|||||||
# MiniMax Music 3
|
|
||||||
|
|
||||||
> Text-to-music: lyrics plus a style caption in, a 32 kHz stereo song out
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
- Vendor: MiniMax
|
|
||||||
- Model: `MiniMaxAI/MiniMax-Music3`
|
|
||||||
- Task: Text-to-music generation (vocals and instrumental together)
|
|
||||||
- Mode: Online serving through the OpenAI-compatible `/v1/audio/speech` API
|
|
||||||
- Maintainer: @linyueqian
|
|
||||||
|
|
||||||
## When to use this recipe
|
|
||||||
|
|
||||||
Use it to serve `MiniMaxAI/MiniMax-Music3` for full-song generation up to six
|
|
||||||
minutes. This is not a TTS model with a musical voice: there is no reference
|
|
||||||
speaker, no `voice`, and no `temperature`. What you control is the lyrics, the
|
|
||||||
caption, the seed, and the length.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- Model card: [MiniMaxAI/MiniMax-Music3](https://huggingface.co/MiniMaxAI/MiniMax-Music3)
|
|
||||||
- Demo samples: [MiniMax Music 3 demo](https://minimax-ai.github.io/music3-demo/)
|
|
||||||
- Deploy config: `vllm_omni/deploy/minimax_music3.yaml` (single GPU),
|
|
||||||
`vllm_omni/deploy/minimax_music3_2gpu.yaml` (split across two)
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
| Component | Spec |
|
|
||||||
|---|---|
|
|
||||||
| Backbone | Qwen3 decoder, 36 layers, hidden 4096, GQA 32/8, vocab 200k |
|
|
||||||
| Depth decoder | 4 layers, hidden 4096, 16 heads; 7 residual codebooks of 1024 |
|
|
||||||
| Frame | 8 codebooks; `c0` in the backbone vocabulary, `c1..c7` in the depth decoder |
|
|
||||||
| AR guidance | Classifier-free, scale 1.5, `c0` masked to the conditioned branch's top 50 |
|
|
||||||
| Acoustic stage | Flow-matching transformer (36 layers, dim 2048) plus a DAC-style decoder, 512x upsampling |
|
|
||||||
| Solver | Euler, 30 steps, acoustic guidance scale 1.7 |
|
|
||||||
| Window | 200 frames per acoustic chunk, 100-frame hop |
|
|
||||||
| Frame rate | 25 frames per second |
|
|
||||||
| Context length | 10,240 tokens |
|
|
||||||
| Output | 32 kHz stereo WAV |
|
|
||||||
|
|
||||||
Generation is two-stage. The backbone predicts one audio frame per decode step,
|
|
||||||
each frame eight RVQ codebooks deep. Every 200 frames are handed to the
|
|
||||||
flow-matching transformer, which solves a latent that the decoder turns into
|
|
||||||
waveform.
|
|
||||||
|
|
||||||
**Guidance doubles the rows.** Every request decodes twice: one row sees the
|
|
||||||
real prompt, the other sees the same prompt with the caption-and-lyrics span
|
|
||||||
replaced by `<|audio_cfg|>`. Both rows hold their own KV cache for the whole
|
|
||||||
song, so a request costs twice the KV its length suggests. Size the pool for
|
|
||||||
rows, not requests.
|
|
||||||
|
|
||||||
## Hardware Support
|
|
||||||
|
|
||||||
## GPU
|
|
||||||
|
|
||||||
### 1x H200 141GB
|
|
||||||
|
|
||||||
#### Environment
|
|
||||||
|
|
||||||
- OS: Linux
|
|
||||||
- Python: 3.12
|
|
||||||
- CUDA: 12.9
|
|
||||||
- vLLM version: 0.27.0
|
|
||||||
- vLLM-Omni version or commit: branch `feat/minimax-music3`
|
|
||||||
|
|
||||||
#### Command
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm serve MiniMaxAI/MiniMax-Music3 \
|
|
||||||
--host 0.0.0.0 --port 8000 \
|
|
||||||
--trust-remote-code --omni
|
|
||||||
```
|
|
||||||
|
|
||||||
The default deploy config `vllm_omni/deploy/minimax_music3.yaml` is discovered
|
|
||||||
automatically from the checkpoint's `model_type=minimax_music3`. It colocates
|
|
||||||
both stages on device 0.
|
|
||||||
|
|
||||||
#### Verification
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:8000/v1/audio/speech \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"model": "MiniMaxAI/MiniMax-Music3",
|
|
||||||
"input": "[Verse]\nWalking down the empty street at midnight\nStreetlights flicker like a broken dream\n[Chorus]\nAnd I keep on walking\nTill the morning finds me",
|
|
||||||
"instructions": "A melancholic lo-fi hip-hop track at 85 BPM in F minor: mellow Rhodes piano riff, soft vinyl crackle, dusty boom-bap drums with a laid-back swing, warm upright bass. Intimate bedroom production, gentle tape saturation, no bright cymbals.",
|
|
||||||
"seed": 1,
|
|
||||||
"max_new_tokens": 750
|
|
||||||
}' \
|
|
||||||
--output song.wav
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: a 30 second 32 kHz stereo WAV. `max_new_tokens` counts audio frames
|
|
||||||
at 25 per second, so 750 frames is at most 30 seconds. It is a cap, not a
|
|
||||||
target: the model ends the song itself when it emits the audio-end token, and a
|
|
||||||
response shorter than the cap is the model finishing rather than a truncation.
|
|
||||||
|
|
||||||
#### Notes
|
|
||||||
|
|
||||||
- **Memory:** stage 0 takes `gpu_memory_utilization: 0.6` for the bf16 backbone
|
|
||||||
and its KV pool; stage 1 takes 0.2 for the transformer and decoder weights,
|
|
||||||
which sit outside any KV budget.
|
|
||||||
- **Key flags:** none required. Guidance, the solver step count and the window
|
|
||||||
geometry are fixed by the checkpoint and are not request parameters.
|
|
||||||
- **Known limitations:**
|
|
||||||
- The external API is non-streaming; `stream: true` is rejected.
|
|
||||||
- `max_model_len` is 10,240, so a maximum-length prompt (5,000 tokens) and a
|
|
||||||
maximum-length song (9,000 frames) cannot both fit. Long captions shorten
|
|
||||||
the maximum song. This is a property of the checkpoint.
|
|
||||||
- The acoustic stage runs in float32. bfloat16 is accepted but measurably
|
|
||||||
degrades the solver.
|
|
||||||
|
|
||||||
### 2x H200 141GB
|
|
||||||
|
|
||||||
Use `minimax_music3_2gpu.yaml` to put the acoustic stage on the second device:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm serve MiniMaxAI/MiniMax-Music3 \
|
|
||||||
--host 0.0.0.0 --port 8000 \
|
|
||||||
--trust-remote-code --omni \
|
|
||||||
--deploy-config minimax_music3_2gpu.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
Only the placement differs; both layouts run the acoustic stage in float32.
|
|
||||||
|
|
||||||
## Writing the request
|
|
||||||
|
|
||||||
Two fields carry the request, and both are required.
|
|
||||||
|
|
||||||
- `input` is the **lyrics**. Structure tags (`[Verse]`, `[Chorus]`, `[Bridge]`,
|
|
||||||
`[Outro]`) steer the arrangement and are part of the prompt contract.
|
|
||||||
**Put a tag on its own line.** Normalization keeps only the tags on a line
|
|
||||||
that starts with one and drops the rest of that line, so lyrics written next
|
|
||||||
to a tag are silently lost:
|
|
||||||
|
|
||||||
```text
|
|
||||||
"[Verse]\nWalking down the street" -> [start] [verse] Walking down the street
|
|
||||||
"[Verse] Walking down the street" -> [start] [verse]
|
|
||||||
```
|
|
||||||
|
|
||||||
- `instructions` is the **caption** describing genre, instrumentation, tempo,
|
|
||||||
mood and production. It is the strongest control available: vague captions
|
|
||||||
give generic arrangements. Long structured captions work well, since the
|
|
||||||
model was trained on descriptions covering global attributes, emotional
|
|
||||||
progression, vocal detail and arrangement.
|
|
||||||
|
|
||||||
A request is deterministic in its seed: the same lyrics, caption, seed and
|
|
||||||
length return the same audio. Changing only the seed gives another take of the
|
|
||||||
same song.
|
|
||||||
|
|
||||||
## Request parameters
|
|
||||||
|
|
||||||
| Parameter | Type | Default | Description |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `input` | string | required | Lyrics, non-empty. Tags on their own lines |
|
|
||||||
| `instructions` | string | required | Caption, non-empty |
|
|
||||||
| `seed` | int | `0` | Non-negative. Fixes the output for a given request |
|
|
||||||
| `max_new_tokens` | int | `9000` | Cap on audio frames, 25 per second. Maximum 9,000 |
|
|
||||||
| `response_format` | string | `"wav"` | Output container |
|
|
||||||
|
|
||||||
## Parameters the model rejects
|
|
||||||
|
|
||||||
The request is refused rather than silently ignored, so a mistake is visible
|
|
||||||
immediately.
|
|
||||||
|
|
||||||
| Parameter | Reason |
|
|
||||||
|---|---|
|
|
||||||
| `temperature`, `top_p`, `top_k`, `repetition_penalty` | Sampling is fixed: guidance at 1.5, then a seeded top-k 50 draw |
|
|
||||||
| `voice` | There is no speaker to select; the vocal comes from the caption |
|
|
||||||
| `ref_audio`, `ref_text`, `language`, `task_type` | No reference-audio conditioning and no language tag |
|
|
||||||
| `speed` | Only `1.0`. Tempo belongs in the caption, e.g. "at 92 BPM" |
|
|
||||||
| `stream: true` | The external API is non-streaming |
|
|
||||||
@@ -51,7 +51,7 @@ NemotronH (~3B active parameters). Pick the pipeline by task:
|
|||||||
`model_type` auto-resolves `audex_tts.yaml`):
|
`model_type` auto-resolves `audex_tts.yaml`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vllm serve nvidia/Nemotron-Labs-Audex-2B \
|
vllm-omni serve nvidia/Nemotron-Labs-Audex-2B \
|
||||||
--host 0.0.0.0 --port 8097 \
|
--host 0.0.0.0 --port 8097 \
|
||||||
--trust-remote-code --omni
|
--trust-remote-code --omni
|
||||||
# other modes: add --stage-configs-path vllm_omni/deploy/audex_{tta,thinker_only,s2s}.yaml
|
# other modes: add --stage-configs-path vllm_omni/deploy/audex_{tta,thinker_only,s2s}.yaml
|
||||||
@@ -109,7 +109,7 @@ The 30B REQUIRES its explicit deploy yaml (the shared HF `model_type`
|
|||||||
auto-resolves to the 2B-tuned config otherwise):
|
auto-resolves to the 2B-tuned config otherwise):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vllm serve nvidia/Nemotron-Labs-Audex-30B-A3B \
|
vllm-omni serve nvidia/Nemotron-Labs-Audex-30B-A3B \
|
||||||
--host 0.0.0.0 --port 8097 \
|
--host 0.0.0.0 --port 8097 \
|
||||||
--trust-remote-code --omni \
|
--trust-remote-code --omni \
|
||||||
--stage-configs-path vllm_omni/deploy/audex_tts_30b.yaml
|
--stage-configs-path vllm_omni/deploy/audex_tts_30b.yaml
|
||||||
|
|||||||
@@ -71,35 +71,6 @@ single-GPU entry point; `minicpmo_4_5_2gpu.yaml` is the recommended
|
|||||||
two-GPU profile. The removed fused two-stage implementation is not retained as
|
two-GPU profile. The removed fused two-stage implementation is not retained as
|
||||||
a fallback because it would duplicate state machines and correctness paths.
|
a fallback because it would duplicate state machines and correctness paths.
|
||||||
|
|
||||||
### Graph execution
|
|
||||||
|
|
||||||
Graph boundaries follow each stage's state model:
|
|
||||||
|
|
||||||
| Stage | Graph mode on Ascend | Eager boundary |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 0 Thinker | vLLM `PIECEWISE` | multimodal preprocessing and output routing |
|
|
||||||
| 1 Talker | vLLM `PIECEWISE` | conditioning preprocessing, codec sampling, request-state updates |
|
|
||||||
| 2 Code2Wav | inner exact-shape NPUGraph for the CFM DiT estimator | encoder, timestep embedding, HiFT/RNG, request parsing, stream-state commit |
|
|
||||||
|
|
||||||
Stage 2 keeps `enforce_eager: true` for the generation runner because its
|
|
||||||
Python request metadata and per-request cache dictionaries are not valid
|
|
||||||
outer-graph inputs. The backend captures only the deterministic CFM DiT
|
|
||||||
estimator, with an exact graph key for each tensor shape. Timestep embedding
|
|
||||||
stays eager because the upstream implementation creates a CPU frequency tensor;
|
|
||||||
HiFT stays eager because it generates random phase. The graph cache stores up
|
|
||||||
to 32 entries by default, after which unseen shapes run eagerly.
|
|
||||||
|
|
||||||
An ACL graph capture failure is fatal to that Stage-2 process because older
|
|
||||||
torch-npu releases can leave allocator and RNG capture state invalid. Restart
|
|
||||||
the service after a capture failure. To run without capture, set
|
|
||||||
`code2wav_enable_npu_graph: false` under the Stage-2
|
|
||||||
`platforms.npu.stages[].additional_config` block before startup. Tune the cache
|
|
||||||
limit there with `code2wav_max_npu_graphs`. Graph mode also requires
|
|
||||||
`ASCEND_LAUNCH_BLOCKING` to be unset or set to `0`.
|
|
||||||
|
|
||||||
The inner NPUGraph is independent of the outer runner setting, so do not use a
|
|
||||||
global `--enforce-eager` override when Stage 0/1 `PIECEWISE` replay is desired.
|
|
||||||
|
|
||||||
## GPU
|
## GPU
|
||||||
|
|
||||||
### 1 x GPU (default — single command)
|
### 1 x GPU (default — single command)
|
||||||
@@ -240,9 +211,9 @@ speech output (TTS)"** checkbox on / off.
|
|||||||
model processes still share one CUDA device.
|
model processes still share one CUDA device.
|
||||||
- `--trust-remote-code` is required — the HF repo ships a custom
|
- `--trust-remote-code` is required — the HF repo ships a custom
|
||||||
`MiniCPMO` config / model class.
|
`MiniCPMO` config / model class.
|
||||||
- Stage 0 Thinker and Stage 1 Talker enable vLLM CUDA Graphs. Stage 2 keeps its
|
- Stage 0 Thinker and Stage 1 Talker enable vLLM CUDA Graphs. Stage 2 remains
|
||||||
request orchestration eager while using inner CFM and HiFT CUDA Graphs. On
|
eager because its request-owned Flow/HiFT caches and variable chunk/cache
|
||||||
Ascend, the exact-shape CFM DiT estimator instead uses inner NPUGraph replay.
|
shapes are not yet exposed through a static exact-shape graph wrapper.
|
||||||
- All default stages use `max_num_seqs: 4` to reduce cross-process GPU
|
- All default stages use `max_num_seqs: 4` to reduce cross-process GPU
|
||||||
contention. Talker AR
|
contention. Talker AR
|
||||||
state and Code2Wav caches are request-owned; Code2Wav batches only
|
state and Code2Wav caches are request-owned; Code2Wav batches only
|
||||||
@@ -253,8 +224,8 @@ speech output (TTS)"** checkbox on / off.
|
|||||||
- `StageRequestStats.batch_size` is a request-scoped placeholder, not the
|
- `StageRequestStats.batch_size` is a request-scoped placeholder, not the
|
||||||
scheduler's execution batch.
|
scheduler's execution batch.
|
||||||
- Single-GPU co-location trades throughput for hardware density: Stage 0/1
|
- Single-GPU co-location trades throughput for hardware density: Stage 0/1
|
||||||
CUDA Graph replay and Stage 2 vocoder kernels compete across three CUDA
|
CUDA Graph replay and eager Stage 2 vocoder kernels compete across three
|
||||||
contexts. Use the 8x4090 config or a custom multi-GPU mapping for
|
CUDA contexts. Use the 8x4090 config or a custom multi-GPU mapping for
|
||||||
throughput-sensitive serving.
|
throughput-sensitive serving.
|
||||||
|
|
||||||
### 8 x RTX 4090 24GB (consumer-GPU layout)
|
### 8 x RTX 4090 24GB (consumer-GPU layout)
|
||||||
|
|||||||
@@ -252,53 +252,3 @@ curl -X POST http://localhost:8091/v1/audio/speech \
|
|||||||
- stage_id: 1
|
- stage_id: 1
|
||||||
gpu_memory_utilization: 0.15
|
gpu_memory_utilization: 0.15
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1x AMD MI300X, 1.7B checkpoints
|
|
||||||
|
|
||||||
#### Environment
|
|
||||||
|
|
||||||
- OS: Linux 6.8.0-134-generic, x86_64
|
|
||||||
- Container: official ROCm image built from `docker/Dockerfile.rocm`
|
|
||||||
- Python: 3.12.13
|
|
||||||
- PyTorch: 2.11.0+gitd0c8b1f
|
|
||||||
- Driver / runtime: AMD 6.19.14.31400000 / ROCm 7.2.53211
|
|
||||||
- GPU: one AMD Instinct MI300X, `gfx942:sramecc+:xnack-`, 191.69 GiB visible HBM
|
|
||||||
- vLLM version: 0.27.0+rocm723
|
|
||||||
- vLLM Omni version or commit: `3fecb6953ca8dc51210cc0421ef24552267a41ef`
|
|
||||||
- Installed vLLM Omni package metadata: `0.27.0rc2.dev44+g55abdade9.rocm`
|
|
||||||
- ONNX Runtime: onnxruntime-rocm 1.22.2.post3 with `ROCMExecutionProvider`
|
|
||||||
- transformers: 5.15.0
|
|
||||||
|
|
||||||
#### Command
|
|
||||||
|
|
||||||
The MI300X tests also worked without `enforce_eager`. To use that setup, remove `enforce_eager: true` from stage 1 under `platforms.rocm` in `vllm_omni/deploy/qwen3_tts.yaml`.
|
|
||||||
|
|
||||||
Set this environment variable before you start Qwen3-TTS:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export MIOPEN_FIND_MODE=FAST
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Verification
|
|
||||||
|
|
||||||
Each row records one offline request with one prompt and batch size one. Request time is the logged `e2e_wall_time_ms`, and stage 1 time is the logged `e2e_stage_1_wall_time_ms`.
|
|
||||||
|
|
||||||
| Checkpoint | Code2Wav configuration | `MIOPEN_FIND_MODE` | Request time | Stage 1 time | Output duration | Real time factor | Maximum sampled device memory |
|
|
||||||
| --- | --- | --- | ---: | ---: | ---: | ---: | ---: |
|
|
||||||
| CustomVoice | Eager | Unset | 1.683 s | 1.613 s | 5.68 s | 0.30 | 60.91 GiB |
|
|
||||||
| CustomVoice | Graphs allowed | `FAST` | 1.326 s | 1.259 s | 5.68 s | 0.23 | 62.19 GiB |
|
|
||||||
| VoiceDesign | Eager | Unset | 1.195 s | 1.180 s | 4.40 s | 0.27 | 60.91 GiB |
|
|
||||||
| VoiceDesign | Graphs allowed | `FAST` | 1.127 s | 1.054 s | 4.40 s | 0.26 | 62.21 GiB |
|
|
||||||
| Base | Eager | Unset | 13.330 s | 13.263 s | 4.32 s | 3.09 | 61.74 GiB |
|
|
||||||
| Base | Graphs allowed | `FAST` | 14.314 s | 14.245 s | 4.80 s | 2.98 | 62.84 GiB |
|
|
||||||
|
|
||||||
All six processes finished successfully, and each output passed the 24 kHz mono WAV checks.
|
|
||||||
|
|
||||||
#### Notes
|
|
||||||
|
|
||||||
- The current ROCm config keeps Code2Wav in eager mode.
|
|
||||||
- The tested alternative allows graphs and sets [`MIOPEN_FIND_MODE=FAST`](https://rocm.docs.amd.com/projects/MIOpen/en/develop/reference/env_variables.html). MIOpen uses a saved kernel choice when available and uses its immediate fallback otherwise.
|
|
||||||
- CustomVoice used a graph for one of four logged Code2Wav batches. VoiceDesign used a graph for two of four batches. Base used no graphs for its four logged batches.
|
|
||||||
- The recorded request time with graphs allowed was lower for CustomVoice and VoiceDesign and higher for Base. The Base runs generated different amounts of audio, so their request times are not a direct comparison.
|
|
||||||
- Each setup was tested with one request, so the timing is not a performance benchmark.
|
|
||||||
- GPU memory was sampled once per second with `rocm-smi`.
|
|
||||||
+3
-6
@@ -49,19 +49,16 @@ recipes/
|
|||||||
| [`Helios/Helios.md`](./Helios/Helios.md) | Text-to-video, image-to-video, and video-to-video generation | 1x NVIDIA H20 |
|
| [`Helios/Helios.md`](./Helios/Helios.md) | Text-to-video, image-to-video, and video-to-video generation | 1x NVIDIA H20 |
|
||||||
| [`inclusionAI/Ming-flash-omni-2.0.md`](./inclusionAI/Ming-flash-omni-2.0.md) | Online serving for multimodal chat + standalone TTS | 4x H100 / 1x H100 80GB |
|
| [`inclusionAI/Ming-flash-omni-2.0.md`](./inclusionAI/Ming-flash-omni-2.0.md) | Online serving for multimodal chat + standalone TTS | 4x H100 / 1x H100 80GB |
|
||||||
| [`inclusionAI/Ming-omni-tts.md`](./inclusionAI/Ming-omni-tts.md) | Offline + online dense Ming TTS/audio generation | 1x H100 80GB / 1x AMD MI300X (ROCm 7.2) |
|
| [`inclusionAI/Ming-omni-tts.md`](./inclusionAI/Ming-omni-tts.md) | Offline + online dense Ming TTS/audio generation | 1x H100 80GB / 1x AMD MI300X (ROCm 7.2) |
|
||||||
| [`k2-fsa/OmniVoice.md`](./k2-fsa/OmniVoice.md) | Offline and online multilingual text-to-speech | 1x AMD MI300X (ROCm 7.2) |
|
|
||||||
| [`IndexTeam/IndexTTS-2.md`](./IndexTeam/IndexTTS-2.md) | Online serving for voice-cloned TTS with optional emotion control | 1x L4 24GB or larger CUDA GPU |
|
| [`IndexTeam/IndexTTS-2.md`](./IndexTeam/IndexTTS-2.md) | Online serving for voice-cloned TTS with optional emotion control | 1x L4 24GB or larger CUDA GPU |
|
||||||
| [`IndexTeam/IndexTTS-2_5.md`](./IndexTeam/IndexTTS-2_5.md) | Online + offline multilingual voice-cloned TTS with native speed and emotion control | 1x NVIDIA H20 96GB |
|
| [`IndexTeam/IndexTTS-2_5.md`](./IndexTeam/IndexTTS-2_5.md) | Online + offline multilingual voice-cloned TTS with native speed and emotion control | 1x NVIDIA H20 96GB |
|
||||||
| [`MiniMaxAI/MiniMax-H3.md`](./MiniMaxAI/MiniMax-H3.md) | T2VA, FL2VA, image+audio Ref2VA, and multi-video Ref2VA serving | 1x GPU with CPU offload / 4x B300 |
|
| [`MiniMaxAI/MiniMax-H3.md`](./MiniMaxAI/MiniMax-H3.md) | T2VA, FL2VA, image+audio Ref2VA, and multi-video Ref2VA serving | 1x GPU with CPU offload / 4x B300 |
|
||||||
| [`MiniMaxAI/MiniMax-H3-MUSA.md`](./MiniMaxAI/MiniMax-H3-MUSA.md) | MiniMax H3 T2VA, FL2VA, and Ref2VA on MUSA | 4x MTT S5000 (TP4 Ref2VA profile) |
|
| [`MiniMaxAI/MiniMax-H3-MUSA.md`](./MiniMaxAI/MiniMax-H3-MUSA.md) | MiniMax H3 T2VA, FL2VA, and Ref2VA on MUSA | 4x MTT S5000 (TP4 Ref2VA profile) |
|
||||||
| [`krea/Krea-2.md`](./krea/Krea-2.md) | Text-to-image (Turbo + Raw), offline + online, with LoRA | 1x H100 80GB |
|
| [`krea/Krea-2.md`](./krea/Krea-2.md) | Text-to-image (Turbo + Raw), offline + online, with LoRA | 1x H100 80GB |
|
||||||
| [`LTX/LTX-2.md`](./LTX/LTX-2.md) | LTX-2/LTX-2.3 text-to-video and image-to-video with synchronized audio | H200 141GB / 96GB-class GPU |
|
| [`LTX/LTX-2.md`](./LTX/LTX-2.md) | LTX-2/LTX-2.3 text-to-video and image-to-video with synchronized audio | H200 141GB / 96GB-class GPU |
|
||||||
| [`LTX/LTX-2.5.md`](./LTX/LTX-2.5.md) | LTX-2.5-Diffusers: Full/SFT one-stage and distilled two-stage T2V/I2V with synchronized audio | NVIDIA B300; cuDNN-qualified |
|
|
||||||
| [`MammothModa2/MammothModa2.md`](./MammothModa2/MammothModa2.md) | Preview and Dev text-to-image (AR → DiT); Dev text/image understanding | Preview: 1x L40S 48GB / 1x ≥40GB GPU; Dev: 1x NVIDIA GPU with sufficient cache headroom |
|
| [`MammothModa2/MammothModa2.md`](./MammothModa2/MammothModa2.md) | Preview and Dev text-to-image (AR → DiT); Dev text/image understanding | Preview: 1x L40S 48GB / 1x ≥40GB GPU; Dev: 1x NVIDIA GPU with sufficient cache headroom |
|
||||||
| [`meituan-longcat/LongCat-Video-Avatar-1.5.md`](./meituan-longcat/LongCat-Video-Avatar-1.5.md) | Audio-driven avatar video generation (AT2V / AI2V, single- and multi-speaker, AVC continuation) | 1x H100 80GB |
|
| [`meituan-longcat/LongCat-Video-Avatar-1.5.md`](./meituan-longcat/LongCat-Video-Avatar-1.5.md) | Audio-driven avatar video generation (AT2V / AI2V, single- and multi-speaker, AVC continuation) | 1x H100 80GB |
|
||||||
| [`mistralai/Voxtral-TTS.md`](./mistralai/Voxtral-TTS.md) | Online serving for TTS | 1x RTX 4090 24GB |
|
| [`mistralai/Voxtral-TTS.md`](./mistralai/Voxtral-TTS.md) | Online serving for TTS | 1x RTX 4090 24GB |
|
||||||
| [`Robbyant/LingBot-Video.md`](./Robbyant/LingBot-Video.md) | Native dense and MoE text-to-video serving | Dense: 1x L20X; MoE: 1x L20X (~67.7 GiB peak) |
|
| [`Robbyant/LingBot-Video.md`](./Robbyant/LingBot-Video.md) | Native dense and MoE text-to-video serving | Dense: 1x L20X; MoE: 1x L20X (~67.7 GiB peak) |
|
||||||
| [`Robbyant/LingBot-World-2.0.md`](./Robbyant/LingBot-World-2.0.md) | Offline and experimental realtime interactive world generation | 1x H200/B200 or 2x B200 for TP=2 |
|
|
||||||
| [`cosmos3/Cosmos3-Nano.md`](./cosmos3/Cosmos3-Nano.md) | Text-to-image, text-to-video, image-to-video, video-to-video generation, text to video with sound, action policy | 1x H200 141GB / B300 |
|
| [`cosmos3/Cosmos3-Nano.md`](./cosmos3/Cosmos3-Nano.md) | Text-to-image, text-to-video, image-to-video, video-to-video generation, text to video with sound, action policy | 1x H200 141GB / B300 |
|
||||||
| [`cosmos3/Cosmos3-Edge.md`](./cosmos3/Cosmos3-Edge.md) | T2I / T2V / I2V + action (Physical-AI) generation on the Nemotron-based Edge transformer | 1x GPU (~8 GiB) |
|
| [`cosmos3/Cosmos3-Edge.md`](./cosmos3/Cosmos3-Edge.md) | T2I / T2V / I2V + action (Physical-AI) generation on the Nemotron-based Edge transformer | 1x GPU (~8 GiB) |
|
||||||
| [`cosmos3/Cosmos3-Super.md`](./cosmos3/Cosmos3-Super.md) | 64B T2I / T2V / I2V / V2V generation (+ optional audio) / Action policy | 8x H200/H100/A100 / 2x H200 / B300 |
|
| [`cosmos3/Cosmos3-Super.md`](./cosmos3/Cosmos3-Super.md) | 64B T2I / T2V / I2V / V2V generation (+ optional audio) / Action policy | 8x H200/H100/A100 / 2x H200 / B300 |
|
||||||
@@ -74,9 +71,9 @@ recipes/
|
|||||||
| [`Qwen/Qwen-Image-2512.md`](./Qwen/Qwen-Image-2512.md) | Text-to-image serving with step-wise continuous batching replay and ModelOpt FP8 / mixed FP8/NVFP4 | 1x A800 80GB / 2x B200 |
|
| [`Qwen/Qwen-Image-2512.md`](./Qwen/Qwen-Image-2512.md) | Text-to-image serving with step-wise continuous batching replay and ModelOpt FP8 / mixed FP8/NVFP4 | 1x A800 80GB / 2x B200 |
|
||||||
| [`Qwen/Qwen-Image-Edit.md`](./Qwen/Qwen-Image-Edit.md) | Text-guided single-image editing | 1x or 2x H200 141GB |
|
| [`Qwen/Qwen-Image-Edit.md`](./Qwen/Qwen-Image-Edit.md) | Text-guided single-image editing | 1x or 2x H200 141GB |
|
||||||
| [`Qwen/Qwen3-Omni.md`](./Qwen/Qwen3-Omni.md) | Online serving for multimodal chat | 1x A100 80GB |
|
| [`Qwen/Qwen3-Omni.md`](./Qwen/Qwen3-Omni.md) | Online serving for multimodal chat | 1x A100 80GB |
|
||||||
| [`Qwen/Qwen3-TTS.md`](./Qwen/Qwen3-TTS.md) | Text-to-speech serving (CustomVoice / VoiceDesign / Base) | 1x H100/A100 80GB / 1x AMD MI300X |
|
| [`Qwen/Qwen3-TTS.md`](./Qwen/Qwen3-TTS.md) | Text-to-speech serving (CustomVoice / VoiceDesign / Base) | 1x H100/A100 80GB |
|
||||||
| [`SenseNova/SenseNova-U1.md`](./SenseNova/SenseNova-U1.md) | Unified image generation and understanding | 1x or 2x H200 / 1x AMD MI300X |
|
| [`SenseNova/SenseNova-U1.md`](./SenseNova/SenseNova-U1.md) | Unified image generation and understanding | 1x H200 (144GB) |
|
||||||
| [`StabilityAI/Stable-Audio-Open.md`](./StabilityAI/Stable-Audio-Open.md) | Offline + online text-to-audio generation (Stable Audio Open) | 1x RTX 4090 24GB / 1x AMD MI300X |
|
| [`StabilityAI/Stable-Audio-Open.md`](./StabilityAI/Stable-Audio-Open.md) | Offline + online text-to-audio generation (Stable Audio Open) | 1x RTX 4090 24GB |
|
||||||
| [`Tencent/Covo-Audio-Chat.md`](./Tencent/Covo-Audio-Chat.md) | Online serving for audio chat | 1x A100 80GB |
|
| [`Tencent/Covo-Audio-Chat.md`](./Tencent/Covo-Audio-Chat.md) | Online serving for audio chat | 1x A100 80GB |
|
||||||
| [`Tencent/HunyuanImage-3.0-Instruct.md`](./Tencent/HunyuanImage-3.0-Instruct.md) | DiT-only text-to-image serving and benchmark, including ModelOpt mixed FP8/NVFP4 | 4x H100/H800 80GB / 2x B200 |
|
| [`Tencent/HunyuanImage-3.0-Instruct.md`](./Tencent/HunyuanImage-3.0-Instruct.md) | DiT-only text-to-image serving and benchmark, including ModelOpt mixed FP8/NVFP4 | 4x H100/H800 80GB / 2x B200 |
|
||||||
| [`Wan-AI/Wan2.2-T2V.md`](./Wan-AI/Wan2.2-T2V.md) | Text-to-video serving (Wan2.2 14B) with Skip-Softmax sparse attention | 1x datacenter Blackwell (B200/B300) |
|
| [`Wan-AI/Wan2.2-T2V.md`](./Wan-AI/Wan2.2-T2V.md) | Text-to-video serving (Wan2.2 14B) with Skip-Softmax sparse attention | 1x datacenter Blackwell (B200/B300) |
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
# LingBot-World 2.0
|
|
||||||
|
|
||||||
> Offline and experimental realtime interactive world generation
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
- Vendor: Robbyant
|
|
||||||
- Model: `robbyant/lingbot-world-v2-14b-causal-fast-diffusers`
|
|
||||||
- Task: image-conditioned interactive world generation
|
|
||||||
- Modes: offline trajectory replay and in-process realtime AR-Diffusion ticks
|
|
||||||
- Hardware validated: NVIDIA H200 and B200
|
|
||||||
- Maintainer: Community
|
|
||||||
|
|
||||||
The checkpoint is separately licensed under CC BY-NC-SA and restricted to
|
|
||||||
non-commercial use. The vLLM-Omni integration code remains Apache-2.0.
|
|
||||||
|
|
||||||
## Offline generation
|
|
||||||
|
|
||||||
The offline path consumes one source image and an action directory containing
|
|
||||||
`poses.npy` with shape `[frames, 4, 4]` and `intrinsics.npy` with shape
|
|
||||||
`[frames, 4]`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python examples/offline_inference/diffusion/lingbot_world_v2.py \
|
|
||||||
--prompt "The camera moves slowly forward through the scene." \
|
|
||||||
--image /path/to/first_frame.png \
|
|
||||||
--action-dir /path/to/actions/forward \
|
|
||||||
--num-frames 81 \
|
|
||||||
--output lingbot_world_v2.mp4
|
|
||||||
```
|
|
||||||
|
|
||||||
The current causal-fast checkpoint uses four DMD sampling steps. Raw frame
|
|
||||||
counts must be `9 + 12k`, up to 117 frames. Camera files may contain a
|
|
||||||
longer bounded source trajectory (the official example contains 269 frames);
|
|
||||||
the runtime consumes only the prefix needed by the request.
|
|
||||||
|
|
||||||
## Realtime in-process generation
|
|
||||||
|
|
||||||
The realtime example keeps AR-Diffusion KV and model-owned state across
|
|
||||||
requests. Each JSONL line describes the prompt and/or three latent-frame camera
|
|
||||||
actions applied at the next chunk boundary:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{"event_id":1,"prompt":"A road through a forest","frames":[["j"],[],[]]}
|
|
||||||
{"event_id":2,"frames":[["w"],["w"],["w"]]}
|
|
||||||
{"event_id":3,"prompt":"The road enters a snowy valley","frames":[[],[],[]]}
|
|
||||||
```
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python examples/offline_inference/diffusion/lingbot_world_v2_realtime.py \
|
|
||||||
--image /path/to/first_frame.png \
|
|
||||||
--events /path/to/events.jsonl \
|
|
||||||
--output-dir /tmp/lingbot-realtime \
|
|
||||||
--gpu-memory-fraction 0.6
|
|
||||||
```
|
|
||||||
|
|
||||||
The example writes one latent tensor and one metadata JSON file per chunk. It
|
|
||||||
exercises the same `ARDiffusionSessionManager -> ARDiffusionOmniTickConsumer
|
|
||||||
-> AsyncOmni -> ARDiffusionEngine` path used by a future HTTP or WebSocket
|
|
||||||
transport.
|
|
||||||
|
|
||||||
This PR intentionally does not define a public realtime HTTP/WebSocket schema.
|
|
||||||
An online serving client should be added together with the public transport
|
|
||||||
API rather than exposing LingBot-specific event fields from the generic model
|
|
||||||
runtime. The structured camera-interaction frontend and transport work is
|
|
||||||
tracked separately in
|
|
||||||
[vllm-project/vllm-omni#5527](https://github.com/vllm-project/vllm-omni/pull/5527).
|
|
||||||
|
|
||||||
## Realtime identity and controls
|
|
||||||
|
|
||||||
- `session_id` identifies the persistent world and its worker-owned state.
|
|
||||||
- `event_id` identifies a prompt/control update and remains monotonic across reset.
|
|
||||||
- `chunk_index` is contiguous from zero and restarts from zero after reset.
|
|
||||||
- `request_id` correlates one chunk snapshot with its output metadata.
|
|
||||||
- AsyncOmni uses a separate UUID-suffixed internal engine routing ID.
|
|
||||||
|
|
||||||
The generic runtime transports controls as opaque snapshots. LingBot's adapter
|
|
||||||
accepts:
|
|
||||||
|
|
||||||
- `lingbot.camera_actions.v1` for per-latent-frame key states such as `w`, `a`,
|
|
||||||
`s`, `d`, `i`, `j`, `k`, and `l`;
|
|
||||||
- `lingbot.camera_trajectory.v1` for explicit pose/intrinsics trajectories.
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
Real-checkpoint validation uses 480x832 output, four DMD steps, and seed 42.
|
|
||||||
The exercised matrix includes:
|
|
||||||
|
|
||||||
- TP=1 and TP=2 execution;
|
|
||||||
- two interleaved resident sessions;
|
|
||||||
- action input and prompt switching;
|
|
||||||
- seven contiguous chunks crossing the sink plus recent rolling window;
|
|
||||||
- direct versus paged replay;
|
|
||||||
- CUDA Graph execution;
|
|
||||||
- reset, close, failure cleanup, and exact metadata matching; and
|
|
||||||
- nine-frame VAE decode.
|
|
||||||
|
|
||||||
Official `generate.py` accuracy and performance numbers, together with
|
|
||||||
generation artifacts, are recorded in the PR validation section for the exact
|
|
||||||
tested commit.
|
|
||||||
|
|
||||||
## Current limitations
|
|
||||||
|
|
||||||
- Only the 14B causal-fast checkpoint is supported.
|
|
||||||
- The realtime control plane is internal; there is no public server transport yet.
|
|
||||||
- AR-Diffusion stages currently require one replica because session-affine
|
|
||||||
routing across replicas is not implemented.
|
|
||||||
- One AR block is generated per request and `max_num_seqs` must be one.
|
|
||||||
- Stateful streaming VAE decode is not implemented; the realtime example emits
|
|
||||||
latent chunks.
|
|
||||||
- SP/USP, pipeline/CFG parallelism, HSDP, VAE parallelism, quantization,
|
|
||||||
Cache-DiT, TeaCache, causal-pretrain, and the 1.3B checkpoint are not claimed.
|
|
||||||
- No AMD GPU, Ascend NPU, or Intel GPU support is claimed.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- Checkpoint: <https://huggingface.co/robbyant/lingbot-world-v2-14b-causal-fast-diffusers>
|
|
||||||
- Official implementation: <https://github.com/robbyant/lingbot-world-v2>
|
|
||||||
- Offline example: [`examples/offline_inference/diffusion/lingbot_world_v2.py`](../../examples/offline_inference/diffusion/lingbot_world_v2.py)
|
|
||||||
- Realtime design: [`docs/design/feature/realtime_ar_diffusion.md`](../../docs/design/feature/realtime_ar_diffusion.md)
|
|
||||||
@@ -178,48 +178,6 @@ with the expected 1536×2720 resolution.
|
|||||||
- The LLM transformer uses `QKVParallelLinear` and `MergedColumnParallelLinear`
|
- The LLM transformer uses `QKVParallelLinear` and `MergedColumnParallelLinear`
|
||||||
for fused QKV and gate/up projections with TP support.
|
for fused QKV and gate/up projections with TP support.
|
||||||
|
|
||||||
### 1x AMD MI300X 192GB
|
|
||||||
|
|
||||||
#### Environment
|
|
||||||
|
|
||||||
- OS: Linux 6.8.0-134-generic, x86_64
|
|
||||||
- Container: official ROCm image built from `docker/Dockerfile.rocm`
|
|
||||||
- Python: 3.12.13
|
|
||||||
- PyTorch: 2.11.0+gitd0c8b1f
|
|
||||||
- Driver / runtime: AMD 6.19.14.31400000 / ROCm 7.2.53211
|
|
||||||
- GPU: one AMD Instinct MI300X, `gfx942:sramecc+:xnack-`, 191.69 GiB visible HBM
|
|
||||||
- vLLM version: 0.27.0+rocm723
|
|
||||||
- vLLM Omni version or commit: `a704c8759c96e123c0d7c89b11f120b1c0f120cf`
|
|
||||||
- Installed vLLM Omni package metadata: `0.27.0rc2.dev44+g55abdade9.rocm`
|
|
||||||
|
|
||||||
#### Command
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 examples/offline_inference/text_to_image/text_to_image.py \
|
|
||||||
--model SenseNova/SenseNova-U1-8B-MoT \
|
|
||||||
--prompt "Close portrait of an elderly woman by a farmhouse window, textured skin, gentle smile, warm natural light, emotional documentary look." \
|
|
||||||
--width 1536 \
|
|
||||||
--height 2720 \
|
|
||||||
--seed 42 \
|
|
||||||
--num-inference-steps 50 \
|
|
||||||
--cfg-scale 4.0 \
|
|
||||||
--extra-body '{"think": true, "cfg_norm": "none", "timestep_shift": 3.0, "t_eps": 0.02}' \
|
|
||||||
--enable-diffusion-pipeline-profiler \
|
|
||||||
--log-stats \
|
|
||||||
--output sensenova_u1_mi300x.png
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Verification
|
|
||||||
|
|
||||||
The command completed and wrote a valid 1536 by 2720 RGB PNG.
|
|
||||||
|
|
||||||
#### Notes
|
|
||||||
|
|
||||||
- Generation with 50 inference steps took 34.011 seconds.
|
|
||||||
- Model loading used 32.774 GiB and took 13.987 seconds.
|
|
||||||
- The internal profiler recorded 35.67 GB reserved and 35.10 GB allocated for the request.
|
|
||||||
- The highest one second whole device memory sample was 37.93 GiB.
|
|
||||||
|
|
||||||
## Online Serving
|
## Online Serving
|
||||||
|
|
||||||
SenseNova-U1 supports all four modalities via the OpenAI-compatible
|
SenseNova-U1 supports all four modalities via the OpenAI-compatible
|
||||||
|
|||||||
Loaded 100 of 504 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user