Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd82e41fff | ||
|
|
810112189b |
No files matched your search
@@ -8,7 +8,7 @@
|
|||||||
# NPU naming: {queue}_{resource_class}
|
# NPU naming: {queue}_{resource_class}
|
||||||
# a2b3_npu_1 — ascend-a2b3, resource_class npu-1 (A2/B3 CI image)
|
# a2b3_npu_1 — ascend-a2b3, resource_class npu-1 (A2/B3 CI image)
|
||||||
# a2b3_npu_4 — ascend-a2b3, resource_class npu-4
|
# a2b3_npu_4 — ascend-a2b3, resource_class npu-4
|
||||||
# a3_npu_2 — ascend-a3, resource_class npu-2
|
# a3_npu_2 — ascend-a3, resource_class npu-2 (A3 CI image tag …:$BUILDKITE_COMMIT-a3)
|
||||||
|
|
||||||
x-npu-k8s-plugins: &npu_k8s_plugins
|
x-npu-k8s-plugins: &npu_k8s_plugins
|
||||||
- kubernetes:
|
- kubernetes:
|
||||||
|
|||||||
@@ -139,11 +139,6 @@ steps:
|
|||||||
timeout_in_minutes: 50
|
timeout_in_minutes: 50
|
||||||
commands:
|
commands:
|
||||||
- pytest -s -v tests/e2e/offline_inference/test_minicpmo_4_5.py tests/e2e/online_serving/test_minicpmo_4_5.py -m 'advanced_model and cuda' --run-level 'advanced_model'
|
- pytest -s -v tests/e2e/offline_inference/test_minicpmo_4_5.py tests/e2e/online_serving/test_minicpmo_4_5.py -m 'advanced_model and cuda' --run-level 'advanced_model'
|
||||||
# h100_2, not h100_1: advanced_model asserts transcribe every audio
|
|
||||||
# response with Whisper, and tests/helpers/media.py only uses an
|
|
||||||
# accelerator for Whisper when a spare GPU exists. On a single card it
|
|
||||||
# grades on CPU: ~8-10 min per clip vs ~30 s, which alone overruns the
|
|
||||||
# 50-minute job timeout (build 2956).
|
|
||||||
mirror_hardwares: h100_2
|
mirror_hardwares: h100_2
|
||||||
|
|
||||||
- label: "Omni · MiniCPM-o 4.5 Duplex Test"
|
- label: "Omni · MiniCPM-o 4.5 Duplex Test"
|
||||||
@@ -153,10 +148,10 @@ steps:
|
|||||||
- tests/e2e/online_serving/test_minicpmo_4_5_duplex.py
|
- tests/e2e/online_serving/test_minicpmo_4_5_duplex.py
|
||||||
- tests/e2e/online_serving/test_minicpmo_4_5_duplex_expansion.py
|
- tests/e2e/online_serving/test_minicpmo_4_5_duplex_expansion.py
|
||||||
- tests/e2e/online_serving/test_minicpmo_realtime_duplex_drivers.py
|
- tests/e2e/online_serving/test_minicpmo_realtime_duplex_drivers.py
|
||||||
- tests/e2e/online_serving/helpers/minicpmo_realtime_duplex_scenarios.py
|
- tests/e2e/online_serving/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
|
||||||
- tests/e2e/online_serving/helpers/minicpmo_4_5_duplex.py
|
- tests/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/
|
||||||
@@ -169,7 +164,7 @@ steps:
|
|||||||
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_2
|
||||||
|
|
||||||
- label: "TTS · VoxCPM2 Test"
|
- label: "TTS · VoxCPM2 Test"
|
||||||
source_file_dependencies:
|
source_file_dependencies:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ steps:
|
|||||||
timeout_in_minutes: 50
|
timeout_in_minutes: 50
|
||||||
commands:
|
commands:
|
||||||
- pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex_expansion.py -m "full_model and cuda and H100 and omni" --run-level "full_model"
|
- pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex_expansion.py -m "full_model and cuda and H100 and omni" --run-level "full_model"
|
||||||
mirror_hardwares: h100_1
|
mirror_hardwares: h100_2
|
||||||
|
|
||||||
- label: ":full_moon: Omni · Doc Test with L4"
|
- label: ":full_moon: Omni · Doc Test with L4"
|
||||||
timeout_in_minutes: 90
|
timeout_in_minutes: 90
|
||||||
@@ -50,19 +50,6 @@ steps:
|
|||||||
exit $$EXIT
|
exit $$EXIT
|
||||||
mirror_hardwares: h100_2
|
mirror_hardwares: h100_2
|
||||||
|
|
||||||
- label: ":full_moon: Omni · MiniCPM-o 4.5 · Accuracy Test"
|
|
||||||
timeout_in_minutes: 180
|
|
||||||
commands:
|
|
||||||
- export SEED_TTS_WER_EVAL=1
|
|
||||||
- export SEED_TTS_EVAL_DEVICE=cuda:1
|
|
||||||
- |
|
|
||||||
set +e
|
|
||||||
pytest -s -v tests/e2e/accuracy/minicpmo_4_5/test_minicpmo_4_5.py -m "full_model and cuda" --run-level full_model
|
|
||||||
EXIT=$$?
|
|
||||||
buildkite-agent artifact upload "tests/e2e/accuracy/minicpmo_4_5/results/*.json"
|
|
||||||
exit $$EXIT
|
|
||||||
mirror_hardwares: h100_2
|
|
||||||
|
|
||||||
- label: ":full_moon: Omni · Perf Test · No Async Chunk"
|
- label: ":full_moon: Omni · Perf Test · No Async Chunk"
|
||||||
key: nightly-omni-performance-no-async-chunk
|
key: nightly-omni-performance-no-async-chunk
|
||||||
timeout_in_minutes: 300
|
timeout_in_minutes: 300
|
||||||
@@ -115,32 +102,6 @@ steps:
|
|||||||
exit $$EXIT
|
exit $$EXIT
|
||||||
mirror_hardwares: h100_3
|
mirror_hardwares: h100_3
|
||||||
|
|
||||||
- label: ":full_moon: Omni · MiniCPM-o 4.5 · Perf Test"
|
|
||||||
key: nightly-omni-performance-minicpmo-4-5
|
|
||||||
timeout_in_minutes: 180
|
|
||||||
commands:
|
|
||||||
- export BENCHMARK_DIR=tests/dfx/perf/results
|
|
||||||
- |
|
|
||||||
set +e
|
|
||||||
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5.json
|
|
||||||
EXIT=$$?
|
|
||||||
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
|
|
||||||
exit $$EXIT
|
|
||||||
mirror_hardwares: h100_1
|
|
||||||
|
|
||||||
- label: ":full_moon: Omni · MiniCPM-o 4.5 · Duplex Seed-TTS Perf Test"
|
|
||||||
key: nightly-omni-performance-minicpmo-4-5-duplex-seed-tts
|
|
||||||
timeout_in_minutes: 180
|
|
||||||
commands:
|
|
||||||
- export BENCHMARK_DIR=tests/dfx/perf/results
|
|
||||||
- |
|
|
||||||
set +e
|
|
||||||
pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5_duplex_seed_tts.json
|
|
||||||
EXIT=$$?
|
|
||||||
buildkite-agent artifact upload "tests/dfx/perf/results/*.json"
|
|
||||||
exit $$EXIT
|
|
||||||
mirror_hardwares: h100_1
|
|
||||||
|
|
||||||
- label: ":full_moon: Omni · Multi-Replica Startup Test with 4x H100"
|
- label: ":full_moon: Omni · Multi-Replica Startup Test with 4x H100"
|
||||||
timeout_in_minutes: 45
|
timeout_in_minutes: 45
|
||||||
commands:
|
commands:
|
||||||
@@ -345,7 +306,7 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
|
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
|
||||||
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
|
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
|
||||||
- export CACHE_DIT_VERSION=1.5.0
|
- export CACHE_DIT_VERSION=1.3.0
|
||||||
- |
|
- |
|
||||||
set +e
|
set +e
|
||||||
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_vllm_omni.json
|
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_vllm_omni.json
|
||||||
@@ -361,7 +322,7 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
|
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
|
||||||
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
|
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
|
||||||
- export CACHE_DIT_VERSION=1.5.0
|
- export CACHE_DIT_VERSION=1.3.0
|
||||||
- |
|
- |
|
||||||
set +e
|
set +e
|
||||||
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_edit_vllm_omni.json
|
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
|
||||||
@@ -377,7 +338,7 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
|
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
|
||||||
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
|
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
|
||||||
- export CACHE_DIT_VERSION=1.5.0
|
- export CACHE_DIT_VERSION=1.3.0
|
||||||
- |
|
- |
|
||||||
set +e
|
set +e
|
||||||
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_edit_2511_vllm_omni.json
|
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
|
||||||
@@ -393,7 +354,7 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
|
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
|
||||||
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
|
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
|
||||||
- export CACHE_DIT_VERSION=1.5.0
|
- export CACHE_DIT_VERSION=1.3.0
|
||||||
- |
|
- |
|
||||||
set +e
|
set +e
|
||||||
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_qwen_image_layered_vllm_omni.json
|
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
|
||||||
@@ -409,7 +370,7 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
|
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
|
||||||
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
|
- export DIFFUSION_ATTENTION_BACKEND=FLASH_ATTN
|
||||||
- export CACHE_DIT_VERSION=1.5.0
|
- export CACHE_DIT_VERSION=1.3.0
|
||||||
- |
|
- |
|
||||||
set +e
|
set +e
|
||||||
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_bagel_vllm_omni.json
|
pytest -s -v tests/dfx/perf/scripts/run_diffusion_benchmark.py --test-config-file tests/dfx/perf/tests/test_bagel_vllm_omni.json
|
||||||
@@ -466,10 +427,10 @@ steps:
|
|||||||
- pytest -s -v tests/e2e/accuracy/test_diffusers_backend_similarity.py -k '2v_matches_diffusers' -m full_model --run-level full_model
|
- pytest -s -v tests/e2e/accuracy/test_diffusers_backend_similarity.py -k '2v_matches_diffusers' -m full_model --run-level full_model
|
||||||
mirror_hardwares: h100_2
|
mirror_hardwares: h100_2
|
||||||
|
|
||||||
- label: ":full_moon: Diffusion X2V · MiniMax H3 I2VA/Ref2VA Accuracy Test"
|
- label: ":full_moon: Diffusion X2V · MiniMax H3 T2VA Accuracy Test"
|
||||||
timeout_in_minutes: 180
|
timeout_in_minutes: 180
|
||||||
commands:
|
commands:
|
||||||
- pytest -s -v tests/e2e/accuracy/minimax_h3/test_minimax_h3_i2va_ref2va_similarity.py -m full_model --run-level full_model
|
- pytest -s -v tests/e2e/accuracy/minimax_h3/test_minimax_h3_t2va_similarity.py -m full_model --run-level full_model
|
||||||
mirror_hardwares: h100_4
|
mirror_hardwares: h100_4
|
||||||
|
|
||||||
- label: ":full_moon: Diffusion X2V · Other Accuracy Test"
|
- label: ":full_moon: Diffusion X2V · Other Accuracy Test"
|
||||||
|
|||||||
@@ -128,21 +128,6 @@ steps:
|
|||||||
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
|
||||||
# vLLM 0.27 added a pre-flight capacity check in
|
|
||||||
# distributed/device_communicators/shm_broadcast.py. Without this
|
|
||||||
# the container gets Docker's 64 MiB default and every test in
|
|
||||||
# this step dies at engine start with
|
|
||||||
# RuntimeError: Insufficient space in /dev/shm:
|
|
||||||
# 240 MiB required, 64 MiB available
|
|
||||||
# (9 failed in 8.7s on build #2949). vLLM 0.26 had no such check
|
|
||||||
# and allocated regardless, so this step passed on main while
|
|
||||||
# being under-provisioned the whole time — 0.27 turned a silent
|
|
||||||
# risk into an explicit error.
|
|
||||||
#
|
|
||||||
# This is the only inline docker plugin block in .buildkite/cuda/;
|
|
||||||
# every other step inherits common/ci_mirror_hardwares.yml, which
|
|
||||||
# already sets 8gb. Matching that rather than inventing a value.
|
|
||||||
shm-size: "8gb"
|
|
||||||
environment:
|
environment:
|
||||||
- "HF_HOME=/fsx/hf_cache"
|
- "HF_HOME=/fsx/hf_cache"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -187,7 +172,7 @@ steps:
|
|||||||
timeout 20m bash -c "
|
timeout 20m bash -c "
|
||||||
pytest -s -v tests/e2e/offline_inference/test_minicpmo_4_5.py -m 'core_model' --run-level 'core_model'
|
pytest -s -v tests/e2e/offline_inference/test_minicpmo_4_5.py -m 'core_model' --run-level 'core_model'
|
||||||
"
|
"
|
||||||
mirror_hardwares: h100_1
|
mirror_hardwares: h100_2
|
||||||
|
|
||||||
- label: "Omni · MiniCPM-o 4.5 Online Test"
|
- label: "Omni · MiniCPM-o 4.5 Online Test"
|
||||||
source_file_dependencies:
|
source_file_dependencies:
|
||||||
@@ -198,7 +183,7 @@ steps:
|
|||||||
timeout 20m bash -c "
|
timeout 20m bash -c "
|
||||||
pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5.py -m 'core_model' --run-level 'core_model'
|
pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5.py -m 'core_model' --run-level 'core_model'
|
||||||
"
|
"
|
||||||
mirror_hardwares: h100_1
|
mirror_hardwares: h100_2
|
||||||
|
|
||||||
- label: "Omni · MiniCPM-o 4.5 Duplex Test"
|
- label: "Omni · MiniCPM-o 4.5 Duplex Test"
|
||||||
source_file_dependencies:
|
source_file_dependencies:
|
||||||
@@ -207,10 +192,10 @@ steps:
|
|||||||
- tests/e2e/online_serving/test_minicpmo_4_5_duplex.py
|
- tests/e2e/online_serving/test_minicpmo_4_5_duplex.py
|
||||||
- tests/e2e/online_serving/test_minicpmo_4_5_duplex_expansion.py
|
- tests/e2e/online_serving/test_minicpmo_4_5_duplex_expansion.py
|
||||||
- tests/e2e/online_serving/test_minicpmo_realtime_duplex_drivers.py
|
- tests/e2e/online_serving/test_minicpmo_realtime_duplex_drivers.py
|
||||||
- tests/e2e/online_serving/helpers/minicpmo_realtime_duplex_scenarios.py
|
- tests/e2e/online_serving/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
|
||||||
- tests/e2e/online_serving/helpers/minicpmo_4_5_duplex.py
|
- tests/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/
|
||||||
@@ -225,19 +210,7 @@ steps:
|
|||||||
timeout 20m bash -c "
|
timeout 20m bash -c "
|
||||||
pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex.py -m 'core_model and cuda' --run-level 'core_model'
|
pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex.py -m 'core_model and cuda' --run-level 'core_model'
|
||||||
"
|
"
|
||||||
mirror_hardwares: h100_1
|
mirror_hardwares: h100_2
|
||||||
|
|
||||||
- label: "Omni · MiniCPM-o 4.5 Perf Test"
|
|
||||||
timeout_in_minutes: 60
|
|
||||||
source_file_dependencies:
|
|
||||||
- tests/dfx/perf/tests/test_minicpmo_4_5_ready.json
|
|
||||||
- vllm_omni/deploy/minicpmo_4_5.yaml
|
|
||||||
- vllm_omni/model_executor/models/minicpmo_4_5/
|
|
||||||
- vllm_omni/model_executor/stage_input_processors/minicpmo_4_5_omni.py
|
|
||||||
commands:
|
|
||||||
- export BENCHMARK_DIR=tests/dfx/perf/results
|
|
||||||
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5_ready.json -m 'core_model and cuda'
|
|
||||||
mirror_hardwares: h100_1
|
|
||||||
|
|
||||||
- label: "TTS · VoxCPM2 Test"
|
- label: "TTS · VoxCPM2 Test"
|
||||||
source_file_dependencies:
|
source_file_dependencies:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ steps:
|
|||||||
DOCKER_BUILDKIT: "1"
|
DOCKER_BUILDKIT: "1"
|
||||||
# Buildkite will automatically replace this with the actual commit hash
|
# Buildkite will automatically replace this with the actual commit hash
|
||||||
VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}"
|
VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}"
|
||||||
VLLM_VERSION: "v0.27.0"
|
VLLM_VERSION: "v0.26.0"
|
||||||
priority: 100
|
priority: 100
|
||||||
timeout_in_minutes: 60
|
timeout_in_minutes: 60
|
||||||
soft_fail: false
|
soft_fail: false
|
||||||
@@ -34,23 +34,8 @@ docker_build() {
|
|||||||
docker build "${out[@]}" "$@" -f docker/Dockerfile.xpu .
|
docker build "${out[@]}" "$@" -f docker/Dockerfile.xpu .
|
||||||
}
|
}
|
||||||
|
|
||||||
build_vllm_base() (
|
|
||||||
local vllm_source_dir
|
|
||||||
vllm_source_dir=$(mktemp -d)
|
|
||||||
trap 'rm -rf "${vllm_source_dir}"' EXIT
|
|
||||||
|
|
||||||
git clone --depth 1 --branch "${VLLM_VERSION}" \
|
|
||||||
https://github.com/vllm-project/vllm "${vllm_source_dir}"
|
|
||||||
|
|
||||||
local out=("${export_args[@]/'{{IMAGE}}'/${base_image_name}}")
|
|
||||||
docker build "${out[@]}" \
|
|
||||||
--target vllm-openai \
|
|
||||||
-f "${vllm_source_dir}/docker/Dockerfile.xpu" \
|
|
||||||
"${vllm_source_dir}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if [ -z "$(docker images -q "${base_image_name}")" ]; then
|
if [ -z "$(docker images -q "${base_image_name}")" ]; then
|
||||||
build_vllm_base
|
docker_build "${base_image_name}" --target vllm-base --build-arg "VLLM_VERSION=${VLLM_VERSION}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Try building the docker image
|
# Try building the docker image
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ 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
|
||||||
- label: ":full_moon: Omni · MiniCPM-o 4.5 Test"
|
- label: ":robot: Omni · MiniCPM-o 4.5 Test"
|
||||||
mirror_hardwares: a3_npu_2
|
mirror_hardwares: a3_npu_2
|
||||||
env:
|
env:
|
||||||
VLLM_WORKER_MULTIPROC_METHOD: spawn
|
VLLM_WORKER_MULTIPROC_METHOD: spawn
|
||||||
@@ -47,34 +47,6 @@ steps:
|
|||||||
HF_TOKEN: "${HF_TOKEN}"
|
HF_TOKEN: "${HF_TOKEN}"
|
||||||
commands:
|
commands:
|
||||||
- pytest -s -v 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_expansion.py -m 'full_model' --run-level 'full_model'
|
- pytest -s -v 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_expansion.py -m 'full_model' --run-level 'full_model'
|
||||||
- label: ":full_moon: Omni · MiniCPM-o 4.5 · Duplex Test"
|
|
||||||
timeout_in_minutes: 180
|
|
||||||
mirror_hardwares: a3_npu_2
|
|
||||||
env:
|
|
||||||
VLLM_WORKER_MULTIPROC_METHOD: spawn
|
|
||||||
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
|
|
||||||
HF_TOKEN: "${HF_TOKEN}"
|
|
||||||
commands:
|
|
||||||
- >-
|
|
||||||
pytest -s -v
|
|
||||||
tests/e2e/online_serving/test_minicpmo_4_5_duplex.py
|
|
||||||
tests/e2e/online_serving/test_minicpmo_4_5_duplex_expansion.py
|
|
||||||
-m "npu and A3 and omni"
|
|
||||||
--run-level full_model
|
|
||||||
- label: ":full_moon: Omni · MiniCPM-o 4.5 · Accuracy Test"
|
|
||||||
timeout_in_minutes: 180
|
|
||||||
mirror_hardwares: a3_npu_2
|
|
||||||
env:
|
|
||||||
VLLM_WORKER_MULTIPROC_METHOD: spawn
|
|
||||||
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
|
|
||||||
HF_TOKEN: "${HF_TOKEN}"
|
|
||||||
commands:
|
|
||||||
- |
|
|
||||||
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
|
|
||||||
EXIT=$$?
|
|
||||||
buildkite-agent artifact upload "tests/e2e/accuracy/minicpmo_4_5/results/*.json"
|
|
||||||
exit $$EXIT
|
|
||||||
- group: ":card_index_dividers: TTS Model Test"
|
- group: ":card_index_dividers: TTS Model Test"
|
||||||
key: nightly-tts-test-group
|
key: nightly-tts-test-group
|
||||||
depends_on: upload-nightly-pipeline
|
depends_on: upload-nightly-pipeline
|
||||||
@@ -115,7 +87,7 @@ steps:
|
|||||||
DIFFUSION_ATTENTION_BACKEND: TORCH_SDPA
|
DIFFUSION_ATTENTION_BACKEND: TORCH_SDPA
|
||||||
commands:
|
commands:
|
||||||
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
|
- export DIFFUSION_BENCHMARK_DIR=tests/dfx/perf/results
|
||||||
- export CACHE_DIT_VERSION=1.5.0
|
- export CACHE_DIT_VERSION=1.3.0
|
||||||
- |
|
- |
|
||||||
set +e
|
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
|
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
|
||||||
|
|||||||
@@ -3,29 +3,22 @@ steps:
|
|||||||
key: ready-omni-group
|
key: ready-omni-group
|
||||||
depends_on: upload-ready-pipeline
|
depends_on: upload-ready-pipeline
|
||||||
steps:
|
steps:
|
||||||
- label: "Omni · MiniCPM-o 4.5 Test"
|
- label: ":robot: Omni · MiniCPM-o 4.5 Test"
|
||||||
mirror_hardwares: a3_npu_2
|
agents:
|
||||||
|
queue: "ascend-a3"
|
||||||
|
resource_class: "npu-2"
|
||||||
|
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-a3"
|
||||||
|
plugins:
|
||||||
|
- kubernetes:
|
||||||
|
podSpecPatch:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: swr-secret
|
||||||
env:
|
env:
|
||||||
VLLM_WORKER_MULTIPROC_METHOD: spawn
|
VLLM_WORKER_MULTIPROC_METHOD: spawn
|
||||||
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
|
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
|
||||||
|
HF_TOKEN: "${HF_TOKEN}"
|
||||||
commands:
|
commands:
|
||||||
- pytest -s -v tests/e2e/offline_inference/test_minicpmo_4_5.py tests/e2e/online_serving/test_minicpmo_4_5.py -m 'core_model and npu and A3 and omni' --run-level 'core_model'
|
- pytest -s -v tests/e2e/offline_inference/test_minicpmo_4_5.py tests/e2e/online_serving/test_minicpmo_4_5.py -m 'core_model' --run-level 'core_model'
|
||||||
- label: "Omni · MiniCPM-o 4.5 Duplex Test"
|
|
||||||
mirror_hardwares: a3_npu_2
|
|
||||||
env:
|
|
||||||
VLLM_WORKER_MULTIPROC_METHOD: spawn
|
|
||||||
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
|
|
||||||
commands:
|
|
||||||
- pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex.py -m 'core_model and npu and A3 and omni' --run-level 'core_model'
|
|
||||||
- label: "Omni · MiniCPM-o 4.5 Perf Test"
|
|
||||||
timeout_in_minutes: 60
|
|
||||||
mirror_hardwares: a3_npu_2
|
|
||||||
env:
|
|
||||||
VLLM_WORKER_MULTIPROC_METHOD: spawn
|
|
||||||
VLLM_ALLOW_LONG_MAX_MODEL_LEN: "1"
|
|
||||||
commands:
|
|
||||||
- export BENCHMARK_DIR=tests/dfx/perf/results
|
|
||||||
- pytest -s -v tests/dfx/perf/scripts/run_benchmark.py --test-config-file tests/dfx/perf/tests/test_minicpmo_4_5_ready.json -m 'core_model and npu and A3 and omni'
|
|
||||||
|
|
||||||
- group: ":card_index_dividers: TTS Model Test"
|
- group: ":card_index_dividers: TTS Model Test"
|
||||||
key: ready-tts-group
|
key: ready-tts-group
|
||||||
|
|||||||
@@ -40,11 +40,9 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- "bash .buildkite/release/scripts/publish-release-wheels-pypi.sh"
|
- "bash .buildkite/release/scripts/publish-release-wheels-pypi.sh"
|
||||||
|
|
||||||
# Auto-skip when NIGHTLY=1 (scheduled); otherwise require manual unblock.
|
|
||||||
- block: "Unblock to build release Docker images"
|
- block: "Unblock to build release Docker images"
|
||||||
depends_on: ~
|
depends_on: ~
|
||||||
key: block-build-release-images
|
key: block-build-release-images
|
||||||
if: build.env("NIGHTLY") != "1"
|
|
||||||
|
|
||||||
- group: "Build release Docker images"
|
- group: "Build release Docker images"
|
||||||
key: "build-release-images"
|
key: "build-release-images"
|
||||||
@@ -65,7 +63,7 @@ steps:
|
|||||||
--progress plain \
|
--progress plain \
|
||||||
-f docker/Dockerfile.cuda .
|
-f docker/Dockerfile.cuda .
|
||||||
- "docker push public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT-$(uname -m)"
|
- "docker push public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT-$(uname -m)"
|
||||||
# Fallback default tag (no arch suffix) if arm64 build fails.
|
# re-tag to default image tag and push, just in case arm64 build fails
|
||||||
- "docker tag public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT-$(uname -m) public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT"
|
- "docker tag public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT-$(uname -m) public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT"
|
||||||
- "docker push public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT"
|
- "docker push public.ecr.aws/q9t5s3a7/vllm-omni-release-repo:$BUILDKITE_COMMIT"
|
||||||
env:
|
env:
|
||||||
@@ -88,26 +86,6 @@ steps:
|
|||||||
env:
|
env:
|
||||||
DOCKER_BUILDKIT: "1"
|
DOCKER_BUILDKIT: "1"
|
||||||
|
|
||||||
# NIGHTLY=1 only: promote ECR images to DockerHub nightly tags, then prune.
|
|
||||||
- label: "Publish nightly multi-arch image to DockerHub"
|
|
||||||
depends_on:
|
|
||||||
- build-release-image-x86
|
|
||||||
- build-release-image-arm64
|
|
||||||
if: build.env("NIGHTLY") == "1"
|
|
||||||
key: publish-nightly-images-dockerhub
|
|
||||||
agents:
|
|
||||||
queue: small_cpu_queue_release
|
|
||||||
commands:
|
|
||||||
- "bash .buildkite/release/scripts/publish-release-images.sh nightly"
|
|
||||||
- "bash .buildkite/release/scripts/cleanup-nightly-builds.sh"
|
|
||||||
plugins:
|
|
||||||
- docker-login#v3.0.0:
|
|
||||||
username: vllmbot
|
|
||||||
password-env: DOCKERHUB_TOKEN
|
|
||||||
env:
|
|
||||||
DOCKER_BUILDKIT: "1"
|
|
||||||
DOCKERHUB_USERNAME: "vllmbot"
|
|
||||||
|
|
||||||
- block: "Publish release images to DockerHub"
|
- block: "Publish release images to DockerHub"
|
||||||
key: block-publish-release-images
|
key: block-publish-release-images
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
# Clean up old nightly builds from DockerHub (vllm/vllm-omni), keeping the
|
|
||||||
# newest 14 tags with the nightly- prefix.
|
|
||||||
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
TAG_PREFIX="nightly-"
|
|
||||||
REPO="vllm/vllm-omni"
|
|
||||||
REPO_API_URL="https://hub.docker.com/v2/repositories/${REPO}/tags"
|
|
||||||
|
|
||||||
echo "Cleaning up tags with prefix: $TAG_PREFIX in repository: $REPO"
|
|
||||||
|
|
||||||
if [ -z "$DOCKERHUB_TOKEN" ]; then
|
|
||||||
echo "Error: DOCKERHUB_TOKEN environment variable is not set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$DOCKERHUB_USERNAME" ]; then
|
|
||||||
echo "Error: DOCKERHUB_USERNAME environment variable is not set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Getting DockerHub bearer token..."
|
|
||||||
set +x
|
|
||||||
BEARER_TOKEN=$(curl -s -X POST \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d "{\"username\": \"$DOCKERHUB_USERNAME\", \"password\": \"$DOCKERHUB_TOKEN\"}" \
|
|
||||||
"https://hub.docker.com/v2/users/login" | jq -r '.token')
|
|
||||||
set -x
|
|
||||||
|
|
||||||
if [ -z "$BEARER_TOKEN" ] || [ "$BEARER_TOKEN" = "null" ]; then
|
|
||||||
echo "Error: Failed to get DockerHub bearer token"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
get_all_tags() {
|
|
||||||
local page=1
|
|
||||||
local all_tags=""
|
|
||||||
# Hub `name` is a partial match (not a strict prefix). Use it to narrow the
|
|
||||||
# list, then enforce startswith() client-side. Exhaust API pages; do not
|
|
||||||
# stop merely because a page has no matching tags after filtering.
|
|
||||||
local name_query
|
|
||||||
name_query=$(printf '%s' "$TAG_PREFIX" | jq -sRr @uri)
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
set +x
|
|
||||||
local response
|
|
||||||
response=$(curl -s -H "Authorization: Bearer $BEARER_TOKEN" \
|
|
||||||
"$REPO_API_URL?page=$page&page_size=100&name=${name_query}")
|
|
||||||
set -x
|
|
||||||
|
|
||||||
local page_count
|
|
||||||
page_count=$(echo "$response" | jq -r '.results | length')
|
|
||||||
if [ -z "$page_count" ] || [ "$page_count" = "0" ] || [ "$page_count" = "null" ]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
local tags
|
|
||||||
tags=$(echo "$response" | jq -r --arg prefix "$TAG_PREFIX" \
|
|
||||||
'.results[] | select(.name | startswith($prefix)) | "\(.last_updated)|\(.name)"')
|
|
||||||
|
|
||||||
if [ -n "$tags" ]; then
|
|
||||||
all_tags="$all_tags$tags"$'\n'
|
|
||||||
fi
|
|
||||||
page=$((page + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "$all_tags" | sort -r | cut -d'|' -f2
|
|
||||||
}
|
|
||||||
|
|
||||||
delete_tag() {
|
|
||||||
local tag_name="$1"
|
|
||||||
echo "Deleting tag: $tag_name"
|
|
||||||
|
|
||||||
local delete_url="https://hub.docker.com/v2/repositories/${REPO}/tags/$tag_name"
|
|
||||||
set +x
|
|
||||||
local response http_code body
|
|
||||||
response=$(curl -s -w "\n%{http_code}" -X DELETE \
|
|
||||||
-H "Authorization: Bearer $BEARER_TOKEN" "$delete_url")
|
|
||||||
set -x
|
|
||||||
http_code=$(printf '%s\n' "$response" | tail -n1)
|
|
||||||
body=$(printf '%s\n' "$response" | sed '$d')
|
|
||||||
|
|
||||||
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
|
|
||||||
echo "Successfully deleted tag: $tag_name (HTTP ${http_code})"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local detail=""
|
|
||||||
if echo "$body" | jq -e '.detail' > /dev/null 2>&1; then
|
|
||||||
detail=$(echo "$body" | jq -r '.detail')
|
|
||||||
elif [ -n "$body" ]; then
|
|
||||||
detail="$body"
|
|
||||||
else
|
|
||||||
detail="(empty response body)"
|
|
||||||
fi
|
|
||||||
echo "Error: Failed to delete tag $tag_name (HTTP ${http_code}): ${detail}"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Fetching all tags from DockerHub..."
|
|
||||||
all_tags=$(get_all_tags)
|
|
||||||
|
|
||||||
if [ -z "$all_tags" ]; then
|
|
||||||
echo "No tags found to clean up"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
total_tags=$(echo "$all_tags" | wc -l)
|
|
||||||
echo "Found $total_tags tags"
|
|
||||||
|
|
||||||
tags_to_keep=14
|
|
||||||
tags_to_delete=$((total_tags - tags_to_keep))
|
|
||||||
|
|
||||||
if [ $tags_to_delete -le 0 ]; then
|
|
||||||
echo "No tags need to be deleted (only $total_tags tags found, keeping $tags_to_keep)"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Will delete $tags_to_delete old tags, keeping the newest $tags_to_keep"
|
|
||||||
|
|
||||||
tags_to_delete_list=$(echo "$all_tags" | tail -n +$((tags_to_keep + 1)))
|
|
||||||
|
|
||||||
if [ -z "$tags_to_delete_list" ]; then
|
|
||||||
echo "No tags to delete"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Deleting old tags..."
|
|
||||||
delete_failures=0
|
|
||||||
while IFS= read -r tag; do
|
|
||||||
if [ -n "$tag" ]; then
|
|
||||||
if ! delete_tag "$tag"; then
|
|
||||||
delete_failures=$((delete_failures + 1))
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
fi
|
|
||||||
done <<< "$tags_to_delete_list"
|
|
||||||
|
|
||||||
if [ "$delete_failures" -gt 0 ]; then
|
|
||||||
echo "Cleanup finished with ${delete_failures} deletion failure(s)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Cleanup completed successfully"
|
|
||||||
@@ -1,87 +1,50 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
#
|
#
|
||||||
# Publish Docker images from ECR staging to DockerHub.
|
# Publish release Docker images from ECR to DockerHub.
|
||||||
#
|
# Pulls per-arch images, tags with `latest` and versioned tags, pushes them,
|
||||||
# Usage:
|
# then creates and pushes a multi-arch manifest.
|
||||||
# publish-release-images.sh # release mode (default)
|
|
||||||
# publish-release-images.sh release
|
|
||||||
# publish-release-images.sh nightly
|
|
||||||
#
|
|
||||||
# Release tags: latest, v<version>
|
|
||||||
# Nightly tags: nightly, nightly-<commit>
|
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
MODE="${1:-release}"
|
RELEASE_VERSION=$(buildkite-agent meta-data get release-version --default "" | sed 's/^v//')
|
||||||
|
if [ -z "${RELEASE_VERSION}" ]; then
|
||||||
|
echo "ERROR: release-version metadata not set"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
COMMIT="$BUILDKITE_COMMIT"
|
||||||
DOCKERHUB_REPO="vllm/vllm-omni"
|
DOCKERHUB_REPO="vllm/vllm-omni"
|
||||||
ECR_REPO="public.ecr.aws/q9t5s3a7/vllm-omni-release-repo"
|
ECR_REPO="public.ecr.aws/q9t5s3a7/vllm-omni-release-repo"
|
||||||
COMMIT="$BUILDKITE_COMMIT"
|
|
||||||
PRIMARY_TAGS=()
|
|
||||||
EXTRA_MANIFEST_TAGS=()
|
|
||||||
|
|
||||||
case "${MODE}" in
|
|
||||||
release)
|
|
||||||
RELEASE_VERSION=$(buildkite-agent meta-data get release-version --default "" | sed 's/^v//')
|
|
||||||
if [ -z "${RELEASE_VERSION}" ]; then
|
|
||||||
echo "ERROR: release-version metadata not set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
PRIMARY_TAGS=("latest" "v${RELEASE_VERSION}")
|
|
||||||
;;
|
|
||||||
nightly)
|
|
||||||
# Commit-pinned manifest reuses nightly-{x86_64,aarch64} arch images.
|
|
||||||
PRIMARY_TAGS=("nightly")
|
|
||||||
EXTRA_MANIFEST_TAGS=("nightly-${COMMIT}")
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: $0 {release|nightly}"
|
|
||||||
exit 2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo "Publishing ${MODE} images to ${DOCKERHUB_REPO}"
|
echo "Publishing release images v${RELEASE_VERSION}"
|
||||||
echo " Commit: ${COMMIT}"
|
echo " Commit: ${COMMIT}"
|
||||||
echo " Primary tags: ${PRIMARY_TAGS[*]}"
|
|
||||||
if [ ${#EXTRA_MANIFEST_TAGS[@]} -gt 0 ]; then
|
|
||||||
echo " Extra manifests: ${EXTRA_MANIFEST_TAGS[*]}"
|
|
||||||
fi
|
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
|
|
||||||
|
# Login to ECR to pull staging images
|
||||||
aws ecr-public get-login-password --region us-east-1 | \
|
aws ecr-public get-login-password --region us-east-1 | \
|
||||||
docker login --username AWS --password-stdin public.ecr.aws/q9t5s3a7
|
docker login --username AWS --password-stdin public.ecr.aws/q9t5s3a7
|
||||||
|
|
||||||
SRC_X86="${ECR_REPO}:${COMMIT}-x86_64"
|
docker pull "${ECR_REPO}:${COMMIT}-x86_64"
|
||||||
SRC_ARM="${ECR_REPO}:${COMMIT}-aarch64"
|
docker pull "${ECR_REPO}:${COMMIT}-aarch64"
|
||||||
|
|
||||||
docker pull "${SRC_X86}"
|
docker tag "${ECR_REPO}:${COMMIT}-x86_64" "${DOCKERHUB_REPO}:latest-x86_64"
|
||||||
docker pull "${SRC_ARM}"
|
docker tag "${ECR_REPO}:${COMMIT}-x86_64" "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-x86_64"
|
||||||
|
docker push "${DOCKERHUB_REPO}:latest-x86_64"
|
||||||
|
docker push "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-x86_64"
|
||||||
|
|
||||||
publish_manifest() {
|
docker tag "${ECR_REPO}:${COMMIT}-aarch64" "${DOCKERHUB_REPO}:latest-aarch64"
|
||||||
local tag="$1"
|
docker tag "${ECR_REPO}:${COMMIT}-aarch64" "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-aarch64"
|
||||||
local arch_tag_base="$2"
|
docker push "${DOCKERHUB_REPO}:latest-aarch64"
|
||||||
docker manifest rm "${DOCKERHUB_REPO}:${tag}" || true
|
docker push "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-aarch64"
|
||||||
docker manifest create \
|
|
||||||
"${DOCKERHUB_REPO}:${tag}" \
|
|
||||||
"${DOCKERHUB_REPO}:${arch_tag_base}-x86_64" \
|
|
||||||
"${DOCKERHUB_REPO}:${arch_tag_base}-aarch64"
|
|
||||||
docker manifest push "${DOCKERHUB_REPO}:${tag}"
|
|
||||||
}
|
|
||||||
|
|
||||||
for tag in "${PRIMARY_TAGS[@]}"; do
|
docker manifest rm "${DOCKERHUB_REPO}:latest" || true
|
||||||
docker tag "${SRC_X86}" "${DOCKERHUB_REPO}:${tag}-x86_64"
|
docker manifest rm "${DOCKERHUB_REPO}:v${RELEASE_VERSION}" || true
|
||||||
docker tag "${SRC_ARM}" "${DOCKERHUB_REPO}:${tag}-aarch64"
|
docker manifest create "${DOCKERHUB_REPO}:latest" "${DOCKERHUB_REPO}:latest-x86_64" "${DOCKERHUB_REPO}:latest-aarch64"
|
||||||
docker push "${DOCKERHUB_REPO}:${tag}-x86_64"
|
docker manifest create "${DOCKERHUB_REPO}:v${RELEASE_VERSION}" "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-x86_64" "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-aarch64"
|
||||||
docker push "${DOCKERHUB_REPO}:${tag}-aarch64"
|
docker manifest push "${DOCKERHUB_REPO}:latest"
|
||||||
publish_manifest "${tag}" "${tag}"
|
docker manifest push "${DOCKERHUB_REPO}:v${RELEASE_VERSION}"
|
||||||
done
|
|
||||||
|
|
||||||
ARCH_BASE="${PRIMARY_TAGS[0]}"
|
|
||||||
for tag in "${EXTRA_MANIFEST_TAGS[@]}"; do
|
|
||||||
publish_manifest "${tag}" "${ARCH_BASE}"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Successfully published ${MODE} images"
|
echo "Successfully published release images for v${RELEASE_VERSION}"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: precheck-pr
|
name: precheck-pr
|
||||||
description: Self-check your branch before creating a PR — catch dead code, prevent new model-specific Python examples, verify accuracy/perf claims, validate PR title format, and confirm merge readiness. Use when the user says "precheck", "self review", "pre-submit check", or "check my PR before I open it." Never posts to GitHub.
|
description: Self-check your branch before creating a PR — catch dead code, verify accuracy/perf claims, validate PR title format, and confirm merge readiness. Use when the user says "precheck", "self review", "pre-submit check", or "check my PR before I open it." Never posts to GitHub.
|
||||||
---
|
---
|
||||||
|
|
||||||
# PR Pre-Check
|
# PR Pre-Check
|
||||||
@@ -65,8 +65,6 @@ Ask: "Quick mode or full mode?" Then walk the checklist for the detected PR type
|
|||||||
|
|
||||||
**Also run the Code-Quality sweep on every PR**, regardless of type or mode: the five diff-scoped checks in [references/code-quality.md](references/code-quality.md) — kwargs fragility, broad-except swallow, `Any`/wrong type hints, hot-path `.clone()`/`deepcopy`, and event-loop blocking — plus the advisory conventions (log level, structured logging, synchronization, cleanup, dependencies, naming) in [checklists.md](references/checklists.md). These count only lines the PR *adds* — the pre-existing backlog across the repo is out of scope.
|
**Also run the Code-Quality sweep on every PR**, regardless of type or mode: the five diff-scoped checks in [references/code-quality.md](references/code-quality.md) — kwargs fragility, broad-except swallow, `Any`/wrong type hints, hot-path `.clone()`/`deepcopy`, and event-loop blocking — plus the advisory conventions (log level, structured logging, synchronization, cleanup, dependencies, naming) in [checklists.md](references/checklists.md). These count only lines the PR *adds* — the pre-existing backlog across the repo is out of scope.
|
||||||
|
|
||||||
**Also run the Examples-Policy check on every PR** using [references/examples-policy.md](references/examples-policy.md). Inspect only Python paths introduced by the diff. Treat a new model-, checkpoint-, vendor-, or family-specific Python example as blocking; do not report pre-existing example debt.
|
|
||||||
|
|
||||||
### Step 5: Print Report
|
### Step 5: Print Report
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -79,7 +77,6 @@ Pre-check report for <branch>
|
|||||||
───────────────── ──────
|
───────────────── ──────
|
||||||
PR title format ✓
|
PR title format ✓
|
||||||
Code quality ⚠ 1 broad except, 2 Any hints
|
Code quality ⚠ 1 broad except, 2 Any hints
|
||||||
Examples policy ✗ new model-specific Python example
|
|
||||||
PR desc integrity ✓
|
PR desc integrity ✓
|
||||||
Registry/config ✓
|
Registry/config ✓
|
||||||
Dead code ⚠ 2 warnings
|
Dead code ⚠ 2 warnings
|
||||||
|
|||||||
@@ -47,19 +47,6 @@ Diff-scoped sweep for five fragility patterns that are pervasive in the existing
|
|||||||
|
|
||||||
Roll the ⚠/✗ counts into the report as a single **Code quality** dimension row.
|
Roll the ⚠/✗ counts into the report as a single **Code quality** dimension row.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## All PRs: Examples Policy (run on every PR, quick & full)
|
|
||||||
|
|
||||||
Inspect Python files introduced under `examples/` using
|
|
||||||
[examples-policy.md](examples-policy.md). This is a diff-scoped ratchet: existing
|
|
||||||
model-specific examples may be edited or removed without triggering the check.
|
|
||||||
|
|
||||||
- [ ] **No new model-specific Python examples:** the diff adds, copies, or renames no Python file whose path or behavior is specific to one model, checkpoint, vendor, or model family. ✗ for any violation. Route inference through a shared task/protocol entrypoint, model request behavior through `vllm_omni/model_extras`, and commands through task docs or `recipes/`.
|
|
||||||
- [ ] **New generic example code is justified:** any new task-neutral Python file under `examples/` is an actual user-facing entrypoint rather than reusable production logic, a benchmark, an app, a setup tool, or a reproducer. ⚠ when the placement is questionable.
|
|
||||||
|
|
||||||
Roll the result into the report as a separate **Examples policy** dimension.
|
|
||||||
|
|
||||||
### Conventions (eyeball — no grep)
|
### Conventions (eyeball — no grep)
|
||||||
|
|
||||||
Judgment calls that don't grep cleanly — apply them while reading the diff, not as a mechanical check:
|
Judgment calls that don't grep cleanly — apply them while reading the diff, not as a mechanical check:
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
# Examples Python Policy
|
|
||||||
|
|
||||||
Prevent new model-specific Python examples without forcing cleanup of the
|
|
||||||
existing backlog. Run this check on every PR in quick and full modes.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
Set the merge base as in the main workflow, then list Python paths introduced
|
|
||||||
by the PR, including additions, copies, and renames:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
BASE="$(git merge-base HEAD origin/main)"
|
|
||||||
git diff --name-status --diff-filter=ACR --find-renames --find-copies \
|
|
||||||
"${BASE}"...HEAD -- 'examples/**/*.py'
|
|
||||||
```
|
|
||||||
|
|
||||||
Inspect destination paths for copies and renames. Existing model-specific files
|
|
||||||
that are only modified or deleted are grandfathered and must not be reported.
|
|
||||||
|
|
||||||
## Blocking classification
|
|
||||||
|
|
||||||
Mark ✗ when a newly introduced Python file does any of the following:
|
|
||||||
|
|
||||||
- Names or scopes a path to one model, checkpoint, vendor, or model family.
|
|
||||||
- Hard-codes a model identifier, model-specific prompt, request shape, output
|
|
||||||
conversion, or launch configuration behind an otherwise generic filename.
|
|
||||||
- Duplicates an existing task/protocol runner to demonstrate one model.
|
|
||||||
- Adds a per-model helper below a generic task hub such as `text_to_speech/`.
|
|
||||||
|
|
||||||
Do not infer safety from a generic filename alone. For example, a file named
|
|
||||||
`text_to_audio.py` is still model-specific if it only implements one model's
|
|
||||||
contract.
|
|
||||||
|
|
||||||
## Acceptable additions
|
|
||||||
|
|
||||||
A new Python example may pass when it is a genuinely model-neutral,
|
|
||||||
user-facing task or protocol entrypoint. It must accept model selection through
|
|
||||||
configuration and keep model-specific behavior out of the script.
|
|
||||||
|
|
||||||
Use ⚠ when the file is model-neutral but reusable logic appears misplaced under
|
|
||||||
`examples/`. Route code by purpose:
|
|
||||||
|
|
||||||
| Code purpose | Preferred location |
|
|
||||||
|---|---|
|
|
||||||
| Model prompt, defaults, request/output adaptation | `vllm_omni/model_extras/` or production model modules |
|
|
||||||
| Runnable model command and validation evidence | Task documentation or `recipes/` |
|
|
||||||
| UI or interactive application | `apps/` |
|
|
||||||
| Benchmark or evaluator | `benchmarks/` |
|
|
||||||
| Regression reproducer | `tests/` |
|
|
||||||
| Download, conversion, or setup utility | `tools/` |
|
|
||||||
|
|
||||||
If a model cannot use an existing shared runner, report the missing generic
|
|
||||||
capability. Do not solve the gap by adding a model-specific Python example.
|
|
||||||
|
|
||||||
## Report format
|
|
||||||
|
|
||||||
Add one row to the pre-check report:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Examples policy ✓ no new Python example paths
|
|
||||||
Examples policy ⚠ generic helper belongs in tools/
|
|
||||||
Examples policy ✗ examples/offline_inference/new_model/end2end.py is model-specific
|
|
||||||
Examples policy ✗ examples/offline_inference/x_to_y_model_name.py is model-specific
|
|
||||||
```
|
|
||||||
|
|
||||||
For a blocker, name the path, the model-specific behavior, and the appropriate
|
|
||||||
shared runner or destination. Do not propose deleting unrelated existing
|
|
||||||
examples as part of the PR.
|
|
||||||
+28
-38
@@ -1,9 +1,9 @@
|
|||||||
# Repository Skills for vLLM-Omni
|
# Claude Skills for vLLM-Omni
|
||||||
|
|
||||||
This directory contains repository-scale skills maintained for `vllm-omni`.
|
This directory contains Claude Code skills maintained for the `vllm-omni`
|
||||||
They capture repeatable workflows for common contributor and maintainer tasks
|
repository. These skills capture repeatable workflows for common contributor
|
||||||
such as model integration, CI-aligned testing, performance optimization, and
|
tasks such as model integration, pull request review, and release note
|
||||||
pull request review.
|
generation.
|
||||||
|
|
||||||
## Directory Structure
|
## Directory Structure
|
||||||
|
|
||||||
@@ -14,41 +14,31 @@ include:
|
|||||||
- `references/`: focused reference material used by the skill
|
- `references/`: focused reference material used by the skill
|
||||||
- `scripts/`: small helper scripts used by the skill
|
- `scripts/`: small helper scripts used by the skill
|
||||||
|
|
||||||
## Using the Skills
|
|
||||||
|
|
||||||
Coding agents that discover repository skills can invoke the relevant skill by
|
|
||||||
name. For other agents, point them to the skill's `SKILL.md` and ask them to
|
|
||||||
read it before changing code. Combine a domain skill with `vllm-omni-test` when
|
|
||||||
tests or CI coverage change, then use `precheck-pr` for the contributor's final
|
|
||||||
self-check.
|
|
||||||
|
|
||||||
## Available Skills
|
## Available Skills
|
||||||
|
|
||||||
- [`add-diffusion-model`](add-diffusion-model/SKILL.md): guides integration of
|
- `add-diffusion-model`: guides integration of a new diffusion model into
|
||||||
a new diffusion model into `vllm-omni`
|
`vllm-omni`
|
||||||
- [`add-tts-model`](add-tts-model/SKILL.md): covers integration of new TTS
|
- `diffusion-perf-opt`: guides diffusion model performance optimization,
|
||||||
models and related serving workflows
|
including profiling traces, parallel strategies, stage timing analysis, and
|
||||||
- [`diffusion-perf-opt`](diffusion-perf-opt/SKILL.md): guides diffusion model
|
benchmark-driven tuning
|
||||||
performance optimization, including profiling traces, parallel strategies,
|
- `quantization`: guides quantization method selection, model integration,
|
||||||
stage timing analysis, and benchmark-driven tuning
|
checkpoint loading, and quality/performance validation for vLLM-Omni
|
||||||
- [`precheck-pr`](precheck-pr/SKILL.md): self-checks a branch before creating a
|
- `add-omni-model`: covers addition of new omni-modality model support
|
||||||
PR by validating title format, dead code, accuracy and performance claims,
|
- `add-tts-model`: covers integration of new TTS models and related serving
|
||||||
and merge readiness
|
workflows
|
||||||
- [`quantization`](quantization/SKILL.md): guides quantization method selection,
|
- `generate-release-note`: helps prepare release notes for repository changes
|
||||||
model integration, checkpoint loading, and quality/performance validation
|
- `precheck-pr`: self-check a branch before creating a PR — validates PR title
|
||||||
for vLLM-Omni
|
format, catches dead code, verifies accuracy/perf claims, and confirms merge
|
||||||
- [`review-pr`](review-pr/SKILL.md): provides a frozen-snapshot, contract-aware
|
readiness
|
||||||
workflow for maintainers and reviewers; contributors should use
|
- `vllm-omni-test`: guides generation and execution of CI-aligned tests (L1–L4),
|
||||||
`precheck-pr` for self-review
|
pytest marker selection (`core_model` / `advanced_model` / `full_model`,
|
||||||
- [`vllm-omni-npu-model-runner-upgrade`](vllm-omni-npu-upgrade/SKILL.md):
|
`omni` / `tts` / `diffusion`), Buildkite wiring (`test-ready.yml`,
|
||||||
upgrades NPU model runners to align with the latest vllm-ascend
|
`test-merge.yml`, `test-nightly.yml`, `test-weekly.yml`), and copy-paste
|
||||||
`NPUModelRunner`
|
local plus CI-like `pytest` commands; see `references/test-routing.md` for
|
||||||
- [`vllm-omni-test`](vllm-omni-test/SKILL.md): guides generation and execution
|
level-to-command mapping
|
||||||
of CI-aligned tests (L1–L4), pytest marker selection (`core_model` /
|
- `review-pr`: provides a structured workflow for reviewing pull requests
|
||||||
`advanced_model` / `full_model`, `omni` / `tts` / `diffusion`), Buildkite
|
- `vllm-omni-npu-model-runner-upgrade`: upgrades NPU model runners to align with the
|
||||||
wiring (`test-ready.yml`, `test-merge.yml`, `test-nightly.yml`,
|
latest vllm-ascend NPUModelRunner
|
||||||
`test-weekly.yml`), and copy-paste local plus CI-like `pytest` commands; see
|
|
||||||
`references/test-routing.md` for level-to-command mapping
|
|
||||||
|
|
||||||
## Maintenance Guidelines
|
## Maintenance Guidelines
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ If docs and live code disagree, verify the code/tests and report the drift.
|
|||||||
| [test-quality-evaluation.md](references/checks/test-quality-evaluation.md) | Tests change, are absent for risky code, or may not exercise production behavior. |
|
| [test-quality-evaluation.md](references/checks/test-quality-evaluation.md) | Tests change, are absent for risky code, or may not exercise production behavior. |
|
||||||
| [tests-docs-checklist.md](references/checks/tests-docs-checklist.md) | Coverage, CI markers, examples, user docs, or PR evidence need review. |
|
| [tests-docs-checklist.md](references/checks/tests-docs-checklist.md) | Coverage, CI markers, examples, user docs, or PR evidence need review. |
|
||||||
| [verification.md](references/checks/verification.md) | Hardware, a server, or a runnable affected path is available for active verification. |
|
| [verification.md](references/checks/verification.md) | Hardware, a server, or a runnable affected path is available for active verification. |
|
||||||
| [examples-policy.md](../precheck-pr/references/examples-policy.md) | The PR adds, copies, or renames Python under `examples/`; apply the canonical policy shared with `precheck-pr`. |
|
|
||||||
|
|
||||||
### Delivery and reviewer coordination
|
### Delivery and reviewer coordination
|
||||||
|
|
||||||
@@ -153,13 +152,6 @@ contract they protect or use only the applicable evidence checks.
|
|||||||
Apply every category in [general-checks.md](references/process/general-checks.md) before
|
Apply every category in [general-checks.md](references/process/general-checks.md) before
|
||||||
lower-priority comments.
|
lower-priority comments.
|
||||||
|
|
||||||
If the diff census contains an added, copied, or renamed Python path under
|
|
||||||
`examples/`, read and apply the canonical
|
|
||||||
[examples policy](../precheck-pr/references/examples-policy.md). Treat a new
|
|
||||||
model-specific Python example as blocking. Do not flag model-specific example
|
|
||||||
debt that the PR only modifies or removes, and do not run the rest of the
|
|
||||||
author-oriented `precheck-pr` workflow.
|
|
||||||
|
|
||||||
For each changed value or behavior, trace:
|
For each changed value or behavior, trace:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
+2
-7
@@ -11,15 +11,10 @@ build:
|
|||||||
post_checkout:
|
post_checkout:
|
||||||
- git fetch --unshallow || true
|
- git fetch --unshallow || true
|
||||||
pre_install:
|
pre_install:
|
||||||
# `pre_install` runs before RTD upgrades pip, and the seeded pip 23.1
|
|
||||||
# rejects the `typing_extensions` wheel torch needs (fixed in pip 23.2).
|
|
||||||
- python -m pip install --upgrade pip
|
|
||||||
# Pre-install CPU torch so the subsequent `pip install .[docs]` does not
|
# Pre-install CPU torch so the subsequent `pip install .[docs]` does not
|
||||||
# pull ~2 GB of CUDA 13 libs (cuBLAS/cuDNN/NCCL/...) that exceed RTD's
|
# pull ~2 GB of CUDA 13 libs (cuBLAS/cuDNN/NCCL/...) that exceed RTD's
|
||||||
# 1 GB memory cap and time out the mkdocs build. Wheel-only: `--index-url`
|
# 1 GB memory cap and time out the mkdocs build.
|
||||||
# replaces PyPI, so an sdist fallback cannot reach its build deps. The
|
- python -m pip install --index-url https://download.pytorch.org/whl/cpu torch
|
||||||
# quotes keep YAML from reading `:all:` as a mapping key.
|
|
||||||
- "python -m pip install --only-binary=:all: --index-url https://download.pytorch.org/whl/cpu torch"
|
|
||||||
|
|
||||||
mkdocs:
|
mkdocs:
|
||||||
configuration: mkdocs.yml
|
configuration: mkdocs.yml
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ def _build_omni_kwargs(args, quantization=None):
|
|||||||
def _generate_image(omni, args, prompt, seed):
|
def _generate_image(omni, args, prompt, seed):
|
||||||
"""Generate a single image and return (PIL.Image, time_seconds, memory_gib)."""
|
"""Generate a single image and return (PIL.Image, time_seconds, memory_gib)."""
|
||||||
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
||||||
|
from vllm_omni.outputs import OmniRequestOutput
|
||||||
from vllm_omni.platforms import current_omni_platform
|
from vllm_omni.platforms import current_omni_platform
|
||||||
|
|
||||||
generator = torch.Generator(device=current_omni_platform.device_type).manual_seed(seed)
|
generator = torch.Generator(device=current_omni_platform.device_type).manual_seed(seed)
|
||||||
@@ -175,7 +176,7 @@ def _generate_image(omni, args, prompt, seed):
|
|||||||
elapsed = time.perf_counter() - start
|
elapsed = time.perf_counter() - start
|
||||||
peak_mem = torch.accelerator.max_memory_allocated() / (1024**3)
|
peak_mem = torch.accelerator.max_memory_allocated() / (1024**3)
|
||||||
|
|
||||||
req_out = outputs[0] if isinstance(outputs, list) else outputs
|
req_out = OmniRequestOutput.unwrap_result(outputs)
|
||||||
if not req_out.images:
|
if not req_out.images:
|
||||||
raise ValueError("Could not extract image output from result.")
|
raise ValueError("Could not extract image output from result.")
|
||||||
img = req_out.images[0]
|
img = req_out.images[0]
|
||||||
@@ -205,15 +206,15 @@ def _generate_video(omni, args, prompt, seed):
|
|||||||
elapsed = time.perf_counter() - start
|
elapsed = time.perf_counter() - start
|
||||||
peak_mem = torch.accelerator.max_memory_allocated() / (1024**3)
|
peak_mem = torch.accelerator.max_memory_allocated() / (1024**3)
|
||||||
|
|
||||||
if isinstance(outputs, list) and isinstance(outputs[0], OmniRequestOutput):
|
first = outputs[0]
|
||||||
first = outputs[0]
|
if hasattr(first, "request_output") and isinstance(first.request_output, list):
|
||||||
elif isinstance(outputs, OmniRequestOutput):
|
inner = first.request_output[0]
|
||||||
first = outputs
|
if isinstance(inner, OmniRequestOutput) and hasattr(inner, "images"):
|
||||||
else:
|
frames = inner.images[0] if inner.images else None
|
||||||
raise ValueError("Could not extract video frames from output.")
|
else:
|
||||||
|
frames = inner
|
||||||
if hasattr(first, "images"):
|
elif hasattr(first, "images") and first.images:
|
||||||
frames = first.images[0] if first.images else None
|
frames = first.images[0]
|
||||||
else:
|
else:
|
||||||
raise ValueError("Could not extract video frames from output.")
|
raise ValueError("Could not extract video frames from output.")
|
||||||
|
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ def benchmark(args: argparse.Namespace) -> None:
|
|||||||
output_idx = 0
|
output_idx = 0
|
||||||
for stage_outputs in omni.generate(all_prompts, sampling_params_list, py_generator=True):
|
for stage_outputs in omni.generate(all_prompts, sampling_params_list, py_generator=True):
|
||||||
if stage_outputs.final_output_type == "image":
|
if stage_outputs.final_output_type == "image":
|
||||||
request_output = stage_outputs
|
request_output = stage_outputs.request_output
|
||||||
request_id = getattr(request_output, "request_id", "")
|
request_id = getattr(request_output, "request_id", "")
|
||||||
|
|
||||||
images = getattr(request_output, "images", [])
|
images = getattr(request_output, "images", [])
|
||||||
|
|||||||
@@ -167,11 +167,9 @@ def main() -> None:
|
|||||||
]
|
]
|
||||||
native_cmd = [
|
native_cmd = [
|
||||||
sys.executable,
|
sys.executable,
|
||||||
"examples/offline_inference/text_to_video/text_to_video.py",
|
"examples/offline_inference/text_to_video/text_to_video_lingbot.py",
|
||||||
"--model",
|
"--model",
|
||||||
args.model,
|
args.model,
|
||||||
"--model-class-name",
|
|
||||||
"LingBotVideoPipeline",
|
|
||||||
"--prompt",
|
"--prompt",
|
||||||
args.prompt,
|
args.prompt,
|
||||||
"--output",
|
"--output",
|
||||||
|
|||||||
@@ -139,34 +139,6 @@ python benchmarks/tts/bench_tts.py \
|
|||||||
--output-dir ./results
|
--output-dir ./results
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Fixed IndexTTS 2.5 performance sweep
|
|
||||||
|
|
||||||
IndexTTS 2.5 is registered in `model_configs.yaml` like the other TTS models.
|
|
||||||
Use `--served-model-name` when the server was launched from a local native
|
|
||||||
bundle instead of the registry key. The command below reproduces the acceptance
|
|
||||||
workload: Seed-TTS Eval EN, `n=500`, concurrency `4/8/16/32`, five warmups, and
|
|
||||||
dataset seed 0.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python benchmarks/tts/bench_tts.py \
|
|
||||||
--model IndexTeam/IndexTTS-2.5 \
|
|
||||||
--served-model-name /path/to/indextts-2.5 \
|
|
||||||
--task voice_clone --locale en \
|
|
||||||
--dataset-path /path/to/seedtts_testset \
|
|
||||||
--host 127.0.0.1 --port 8092 \
|
|
||||||
--concurrency 4 8 16 32 \
|
|
||||||
--num-prompts 500 --num-warmups 5 \
|
|
||||||
--output-dir ./results/indextts2_5 \
|
|
||||||
-- \
|
|
||||||
--tokenizer /path/to/indextts-2.5/qwen0.6bemo4-merge \
|
|
||||||
--seed 0 --metric-percentiles 50,95,99 --disable-tqdm --save-detailed
|
|
||||||
```
|
|
||||||
|
|
||||||
The wrapper benchmarks an already-running server. For reproducible quality
|
|
||||||
comparisons, add `--request-seed 42`; production-performance runs should omit
|
|
||||||
it. The trailing `--seed 0` controls Seed-TTS row selection rather than model
|
|
||||||
sampling.
|
|
||||||
|
|
||||||
### 4. Plot a sweep
|
### 4. Plot a sweep
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -84,17 +84,12 @@ def build_bench_args(
|
|||||||
result_filename: str | None,
|
result_filename: str | None,
|
||||||
extra_cli_args: list[str],
|
extra_cli_args: list[str],
|
||||||
output_len: int | None = None,
|
output_len: int | None = None,
|
||||||
served_model_name: str | None = None,
|
|
||||||
num_warmups: int = 2,
|
|
||||||
request_seed: int | None = None,
|
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""Build the ``vllm bench serve --omni`` command for one (task, concurrency) run."""
|
"""Build the ``vllm bench serve --omni`` command for one (task, concurrency) run."""
|
||||||
dataset_name = _TASK_TO_DATASET[task]
|
dataset_name = _TASK_TO_DATASET[task]
|
||||||
backend: str = model_cfg["backend"]
|
backend: str = model_cfg["backend"]
|
||||||
endpoint: str = model_cfg["endpoint"]
|
endpoint: str = model_cfg["endpoint"]
|
||||||
task_extra_body: dict[str, Any] = dict((model_cfg.get("task_extra_body") or {}).get(task) or {})
|
task_extra_body: dict[str, Any] = (model_cfg.get("task_extra_body") or {}).get(task) or {}
|
||||||
if request_seed is not None:
|
|
||||||
task_extra_body["seed"] = request_seed
|
|
||||||
|
|
||||||
# Resolve dataset path
|
# Resolve dataset path
|
||||||
if dataset_path:
|
if dataset_path:
|
||||||
@@ -114,7 +109,7 @@ def build_bench_args(
|
|||||||
"--port",
|
"--port",
|
||||||
str(port),
|
str(port),
|
||||||
"--model",
|
"--model",
|
||||||
served_model_name or model,
|
model,
|
||||||
"--backend",
|
"--backend",
|
||||||
backend,
|
backend,
|
||||||
"--endpoint",
|
"--endpoint",
|
||||||
@@ -124,7 +119,7 @@ def build_bench_args(
|
|||||||
"--num-prompts",
|
"--num-prompts",
|
||||||
str(num_prompts),
|
str(num_prompts),
|
||||||
"--num-warmups",
|
"--num-warmups",
|
||||||
str(num_warmups),
|
"2",
|
||||||
"--percentile-metrics",
|
"--percentile-metrics",
|
||||||
"ttft,e2el,audio_rtf,audio_ttfp,audio_duration,audio_underrun",
|
"ttft,e2el,audio_rtf,audio_ttfp,audio_duration,audio_underrun",
|
||||||
]
|
]
|
||||||
@@ -157,8 +152,7 @@ def build_bench_args(
|
|||||||
if result_filename:
|
if result_filename:
|
||||||
cmd += ["--result-filename", result_filename]
|
cmd += ["--result-filename", result_filename]
|
||||||
|
|
||||||
passthrough = extra_cli_args[1:] if extra_cli_args[:1] == ["--"] else extra_cli_args
|
cmd += extra_cli_args
|
||||||
cmd += passthrough
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
@@ -231,12 +225,6 @@ def main() -> None:
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--model", required=True, help="HuggingFace model ID (e.g. Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice)"
|
"--model", required=True, help="HuggingFace model ID (e.g. Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice)"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--served-model-name",
|
|
||||||
default=None,
|
|
||||||
help="Model name/path accepted by the running server. Use this when --model selects a registry entry "
|
|
||||||
"but the server was launched from a local bundle path.",
|
|
||||||
)
|
|
||||||
parser.add_argument("--task", default="all", help="Task type: voice_clone | default_voice | voice_design | all")
|
parser.add_argument("--task", default="all", help="Task type: voice_clone | default_voice | voice_design | all")
|
||||||
parser.add_argument("--locale", default="en", choices=["en", "zh"])
|
parser.add_argument("--locale", default="en", choices=["en", "zh"])
|
||||||
parser.add_argument("--concurrency", type=int, nargs="+", default=[1, 4], metavar="N")
|
parser.add_argument("--concurrency", type=int, nargs="+", default=[1, 4], metavar="N")
|
||||||
@@ -248,13 +236,6 @@ def main() -> None:
|
|||||||
metavar="N",
|
metavar="N",
|
||||||
help="Number of prompts per run. If one value, applied to all concurrency levels.",
|
help="Number of prompts per run. If one value, applied to all concurrency levels.",
|
||||||
)
|
)
|
||||||
parser.add_argument("--num-warmups", type=int, default=2, help="Warmup requests before each measured run")
|
|
||||||
parser.add_argument(
|
|
||||||
"--request-seed",
|
|
||||||
type=int,
|
|
||||||
default=None,
|
|
||||||
help="Optional model sampling seed added to every request body for reproducible quality runs",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--dataset-path", default=None, help="Root of seed-tts-eval dataset (required for voice_clone/default_voice)"
|
"--dataset-path", default=None, help="Root of seed-tts-eval dataset (required for voice_clone/default_voice)"
|
||||||
)
|
)
|
||||||
@@ -275,9 +256,6 @@ def main() -> None:
|
|||||||
parser.add_argument("extra", nargs=argparse.REMAINDER, help="Extra args passed directly to vllm bench serve")
|
parser.add_argument("extra", nargs=argparse.REMAINDER, help="Extra args passed directly to vllm bench serve")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.num_warmups < 0:
|
|
||||||
parser.error("--num-warmups cannot be negative")
|
|
||||||
|
|
||||||
model_configs = load_model_configs(Path(args.model_configs))
|
model_configs = load_model_configs(Path(args.model_configs))
|
||||||
if args.model not in model_configs:
|
if args.model not in model_configs:
|
||||||
known = "\n ".join(model_configs.keys())
|
known = "\n ".join(model_configs.keys())
|
||||||
@@ -330,9 +308,6 @@ def main() -> None:
|
|||||||
result_filename=result_filename,
|
result_filename=result_filename,
|
||||||
extra_cli_args=args.extra or [],
|
extra_cli_args=args.extra or [],
|
||||||
output_len=args.output_len,
|
output_len=args.output_len,
|
||||||
served_model_name=args.served_model_name,
|
|
||||||
num_warmups=args.num_warmups,
|
|
||||||
request_seed=args.request_seed,
|
|
||||||
)
|
)
|
||||||
result = run_one_benchmark(cmd)
|
result = run_one_benchmark(cmd)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
|
|||||||
@@ -7,17 +7,6 @@
|
|||||||
# is tracked here.
|
# is tracked here.
|
||||||
|
|
||||||
models:
|
models:
|
||||||
IndexTeam/IndexTTS-2.5:
|
|
||||||
supported_tasks: [voice_clone]
|
|
||||||
backend: openai-audio-speech
|
|
||||||
endpoint: /v1/audio/speech
|
|
||||||
trust_remote_code: true
|
|
||||||
task_extra_body:
|
|
||||||
voice_clone:
|
|
||||||
extra_params:
|
|
||||||
lang: en
|
|
||||||
text_normalization: true
|
|
||||||
|
|
||||||
# -CustomVoice checkpoints lack speaker_encoder weights, so voice_clone is
|
# -CustomVoice checkpoints lack speaker_encoder weights, so voice_clone is
|
||||||
# NOT supported (an attempt raises ValueError from _extract_speaker_embedding
|
# NOT supported (an attempt raises ValueError from _extract_speaker_embedding
|
||||||
# at model runtime). Use -Base for voice_clone.
|
# at model runtime). Use -Base for voice_clone.
|
||||||
|
|||||||
+2
-19
@@ -1,24 +1,7 @@
|
|||||||
# Target vllm version: v0.27.0 (releases/v0.27.0 branch).
|
# Target vllm version: v0.26.0 (releases/v0.26.0 branch).
|
||||||
# Keep VLLM_BASE_TAG aligned with the target vLLM release on every rebase.
|
# Keep VLLM_BASE_TAG aligned with the target vLLM release on every rebase.
|
||||||
#
|
|
||||||
# Release-boundary form: there is no precompiled-wheel pin here, because the
|
|
||||||
# base image already ships exactly the target vLLM. Everything the runtime
|
|
||||||
# needs — torch, flashinfer, nvJitLink, numpy — comes from that image as one
|
|
||||||
# self-consistent set.
|
|
||||||
#
|
|
||||||
# This block reappears only when the rebase targets a vLLM commit that is NOT a
|
|
||||||
# released tag. During the 0.27 rebase, targeting v0.27.0rc1 on top of the
|
|
||||||
# v0.26.0 base image required reinstalling vLLM from wheels.vllm.ai and then
|
|
||||||
# hand-repairing the fallout: pinning torch ahead of flashinfer (installing it
|
|
||||||
# after bound flashinfer's TVM FFI runtime to the old torch and aborted engine
|
|
||||||
# core init), registering libnvJitLink.so.13 with ldconfig, and re-pinning numpy
|
|
||||||
# under numba's ceiling. All of it was compensation for a base image and a vLLM
|
|
||||||
# that disagreed. At a real release boundary they agree, so none of it is
|
|
||||||
# needed. If you pin an untagged commit again, restore the reinstall block —
|
|
||||||
# agent/tasks/20_update_wheel_url.sh fails the run rather than letting the
|
|
||||||
# Dockerfile silently ship the wrong vLLM.
|
|
||||||
ARG VLLM_BASE_IMAGE=vllm/vllm-openai
|
ARG VLLM_BASE_IMAGE=vllm/vllm-openai
|
||||||
ARG VLLM_BASE_TAG=v0.27.0
|
ARG VLLM_BASE_TAG=v0.26.0
|
||||||
|
|
||||||
# We use registry-based layer caching for the dependencies,
|
# We use registry-based layer caching for the dependencies,
|
||||||
# where the tag is determined based on the hash of the contents
|
# where the tag is determined based on the hash of the contents
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
|
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
|
||||||
ARG VLLM_ASCEND_TAG=v0.27.0
|
ARG VLLM_ASCEND_TAG=v0.26.0
|
||||||
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
|
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
|
||||||
|
|
||||||
# WORKDIR /vllm-workspace/vllm
|
# WORKDIR /vllm-workspace/vllm
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
|
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
|
||||||
ARG VLLM_ASCEND_TAG=v0.27.0-a3
|
ARG VLLM_ASCEND_TAG=v0.26.0-a3
|
||||||
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
|
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
|
||||||
|
|
||||||
# WORKDIR /vllm-workspace/vllm
|
# WORKDIR /vllm-workspace/vllm
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
|
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
|
||||||
ARG VLLM_ASCEND_TAG=v0.27.0
|
ARG VLLM_ASCEND_TAG=v0.26.0
|
||||||
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
|
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
|
||||||
|
|
||||||
ARG APP_DIR=/vllm-workspace/vllm-omni
|
ARG APP_DIR=/vllm-workspace/vllm-omni
|
||||||
@@ -45,7 +45,6 @@ RUN source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
|
|||||||
"hyperpyyaml>=1.2.0" \
|
"hyperpyyaml>=1.2.0" \
|
||||||
"onnx>=1.16.0" \
|
"onnx>=1.16.0" \
|
||||||
"librosa>=0.10.0" \
|
"librosa>=0.10.0" \
|
||||||
"datasets>=3.0.0" \
|
|
||||||
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
|
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
|
||||||
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
|
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
|
||||||
--default-timeout=60 && \
|
--default-timeout=60 && \
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
|
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
|
||||||
ARG VLLM_ASCEND_TAG=v0.27.0-a3
|
ARG VLLM_ASCEND_TAG=v0.26.0-a3
|
||||||
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
|
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
|
||||||
|
|
||||||
ARG APP_DIR=/vllm-workspace/vllm-omni
|
ARG APP_DIR=/vllm-workspace/vllm-omni
|
||||||
@@ -46,7 +46,6 @@ RUN source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
|
|||||||
"onnx>=1.16.0" \
|
"onnx>=1.16.0" \
|
||||||
"librosa>=0.10.0" \
|
"librosa>=0.10.0" \
|
||||||
"torchmetrics>=1.9.0" \
|
"torchmetrics>=1.9.0" \
|
||||||
"datasets>=3.0.0" \
|
|
||||||
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
|
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
|
||||||
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
|
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
|
||||||
--default-timeout=60 && \
|
--default-timeout=60 && \
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ ENV UV_EXTRA_INDEX_URL=${PIP_EXTRA_INDEX_URL}
|
|||||||
ENV UV_INDEX_STRATEGY="unsafe-best-match"
|
ENV UV_INDEX_STRATEGY="unsafe-best-match"
|
||||||
ENV UV_LINK_MODE="copy"
|
ENV UV_LINK_MODE="copy"
|
||||||
|
|
||||||
ARG VLLM_VERSION=v0.27.0
|
ARG VLLM_VERSION=v0.26.0
|
||||||
RUN git clone -b ${VLLM_VERSION} https://github.com/vllm-project/vllm
|
RUN git clone -b ${VLLM_VERSION} https://github.com/vllm-project/vllm
|
||||||
WORKDIR /workspace/vllm
|
WORKDIR /workspace/vllm
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ ENV VLLM_WORKER_MULTIPROC_METHOD=spawn
|
|||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
uv pip uninstall triton triton-xpu && \
|
uv pip uninstall triton triton-xpu && \
|
||||||
uv pip install triton-xpu==3.7.2 && \
|
uv pip install triton-xpu==3.7.1 && \
|
||||||
uv pip install --no-build-isolation --no-deps .
|
uv pip install --no-build-isolation --no-deps .
|
||||||
|
|
||||||
CMD ["/bin/bash"]
|
CMD ["/bin/bash"]
|
||||||
@@ -144,13 +144,12 @@ WORKDIR /workspace/vllm-omni
|
|||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
ENV VLLM_OMNI_TARGET_DEVICE=xpu
|
ENV VLLM_OMNI_TARGET_DEVICE=xpu
|
||||||
# Keep the upstream oneCCL packages paired with the XPU torch runtime.
|
|
||||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
uv pip install --no-build-isolation \
|
uv pip install --no-build-isolation \
|
||||||
--extra-index-url https://mirrors.ustc.edu.cn/pypi/simple/ \
|
--extra-index-url https://mirrors.ustc.edu.cn/pypi/simple/ \
|
||||||
".[dev]" && \
|
".[dev]" && \
|
||||||
uv pip uninstall triton && \
|
uv pip uninstall triton oneccl oneccl-devel && \
|
||||||
uv pip install triton-xpu==3.7.2 --reinstall
|
uv pip install triton-xpu==3.7.1 --reinstall
|
||||||
|
|
||||||
FROM vllm-omni AS vllm-omni-openai
|
FROM vllm-omni AS vllm-omni-openai
|
||||||
|
|
||||||
|
|||||||
+28
-35
@@ -40,12 +40,30 @@ nav:
|
|||||||
- Models:
|
- Models:
|
||||||
- models/supported_models.md
|
- models/supported_models.md
|
||||||
- Features:
|
- Features:
|
||||||
- Overview: features/README.md
|
- Sleep Mode: features/sleep_mode.md
|
||||||
- Runtime and Stage Execution:
|
- Diffusion Features:
|
||||||
- Execution Modes and Streaming: user_guide/diffusion/execution_modes.md
|
- Overview: user_guide/diffusion_features.md
|
||||||
- Sleep Mode: features/sleep_mode.md
|
- Feature Compatibility: user_guide/feature_compatibility.md
|
||||||
|
- Regional Compilation: user_guide/diffusion/regional_compilation.md
|
||||||
|
- Cache Acceleration:
|
||||||
|
- TeaCache: user_guide/diffusion/cache_acceleration/teacache.md
|
||||||
|
- Cache-DiT: user_guide/diffusion/cache_acceleration/cache_dit.md
|
||||||
|
- Attention Backends: user_guide/diffusion/attention_backends.md
|
||||||
|
- Frame Interpolation: user_guide/diffusion/frame_interpolation.md
|
||||||
|
- Parallelism:
|
||||||
|
- Overview: user_guide/diffusion/parallelism/overview.md
|
||||||
|
- CFG Parallel: user_guide/diffusion/parallelism/cfg_parallel.md
|
||||||
|
- Expert Parallel: user_guide/diffusion/parallelism/expert_parallel.md
|
||||||
|
- Hybrid Sharded Data Parallel: user_guide/diffusion/parallelism/hsdp.md
|
||||||
|
- Sequence Parallel: user_guide/diffusion/parallelism/sequence_parallel.md
|
||||||
|
- Tensor Parallel: user_guide/diffusion/parallelism/tensor_parallel.md
|
||||||
|
- VAE Parallelism: user_guide/diffusion/parallelism/vae_parallelism.md
|
||||||
|
- CPU Offloading: user_guide/diffusion/cpu_offload.md
|
||||||
|
- LoRA: user_guide/diffusion/lora.md
|
||||||
|
- Custom Pipeline: features/custom_pipeline.md
|
||||||
|
- Execution Modes: user_guide/diffusion/execution_modes.md
|
||||||
- Quantization:
|
- Quantization:
|
||||||
- Overview: user_guide/quantization/overview.md
|
- Quantization: user_guide/quantization/overview.md
|
||||||
- Online Quantization: user_guide/quantization/online.md
|
- Online Quantization: user_guide/quantization/online.md
|
||||||
- Quantized KV Cache: user_guide/quantization/quantized_kvcache.md
|
- Quantized KV Cache: user_guide/quantization/quantized_kvcache.md
|
||||||
- FP8 W8A8: user_guide/quantization/fp8.md
|
- FP8 W8A8: user_guide/quantization/fp8.md
|
||||||
@@ -54,33 +72,9 @@ nav:
|
|||||||
- MXFP8 W8A8: user_guide/quantization/mxfp8.md
|
- MXFP8 W8A8: user_guide/quantization/mxfp8.md
|
||||||
- MXFP4 W4A4: user_guide/quantization/mxfp4.md
|
- MXFP4 W4A4: user_guide/quantization/mxfp4.md
|
||||||
- ModelOpt: user_guide/quantization/modelopt.md
|
- ModelOpt: user_guide/quantization/modelopt.md
|
||||||
|
- GGUF: user_guide/quantization/gguf.md
|
||||||
- AutoRound: user_guide/quantization/autoround.md
|
- AutoRound: user_guide/quantization/autoround.md
|
||||||
- msModelSlim: user_guide/quantization/msmodelslim.md
|
- msModelSlim: user_guide/quantization/msmodelslim.md
|
||||||
- Diffusion Acceleration:
|
|
||||||
- Overview and Compatibility:
|
|
||||||
- Overview: user_guide/diffusion_features.md
|
|
||||||
- Feature Compatibility: user_guide/feature_compatibility.md
|
|
||||||
- CPU Offloading: user_guide/diffusion/cpu_offload.md
|
|
||||||
- Cache Acceleration:
|
|
||||||
- TeaCache: user_guide/diffusion/cache_acceleration/teacache.md
|
|
||||||
- Cache-DiT: user_guide/diffusion/cache_acceleration/cache_dit.md
|
|
||||||
- Parallelism:
|
|
||||||
- Overview: user_guide/diffusion/parallelism/overview.md
|
|
||||||
- CFG Parallel: user_guide/diffusion/parallelism/cfg_parallel.md
|
|
||||||
- Expert Parallel: user_guide/diffusion/parallelism/expert_parallel.md
|
|
||||||
- Hybrid Sharded Data Parallel: user_guide/diffusion/parallelism/hsdp.md
|
|
||||||
- Sequence Parallel: user_guide/diffusion/parallelism/sequence_parallel.md
|
|
||||||
- Tensor Parallel: user_guide/diffusion/parallelism/tensor_parallel.md
|
|
||||||
- VAE Parallel: user_guide/diffusion/parallelism/vae_parallel.md
|
|
||||||
- Attention Backends: user_guide/diffusion/attention_backends.md
|
|
||||||
- Regional Compilation: user_guide/diffusion/regional_compilation.md
|
|
||||||
- Frame Interpolation: user_guide/diffusion/frame_interpolation.md
|
|
||||||
- Startup and Loading: user_guide/diffusion/startup_and_loading.md
|
|
||||||
- LoRA: user_guide/diffusion/lora.md
|
|
||||||
- Experimental:
|
|
||||||
- Session State Manager: features/session_state_manager.md
|
|
||||||
- Integrations:
|
|
||||||
- Custom Diffusion Pipeline: features/custom_pipeline.md
|
|
||||||
- ComfyUI: features/comfyui.md
|
- ComfyUI: features/comfyui.md
|
||||||
- Fault Tolerance:
|
- Fault Tolerance:
|
||||||
- Failure Modes: user_guide/fault_injection_reliability_matrix.md
|
- Failure Modes: user_guide/fault_injection_reliability_matrix.md
|
||||||
@@ -114,7 +108,6 @@ nav:
|
|||||||
- Shared Memory Connector: design/feature/omni_connectors/shared_memory_connector.md
|
- Shared Memory Connector: design/feature/omni_connectors/shared_memory_connector.md
|
||||||
- Yuanrong Store Connector: design/feature/omni_connectors/yuanrong_connector.md
|
- Yuanrong Store Connector: design/feature/omni_connectors/yuanrong_connector.md
|
||||||
- Yuanrong Transfer Engine Connector: design/feature/omni_connectors/yuanrong_transfer_engine_connector.md
|
- Yuanrong Transfer Engine Connector: design/feature/omni_connectors/yuanrong_transfer_engine_connector.md
|
||||||
- Quantization: design/feature/quantization.md
|
|
||||||
- Diffusion Acceleration:
|
- Diffusion Acceleration:
|
||||||
- Parallelism:
|
- Parallelism:
|
||||||
- CFG-Parallel: design/feature/cfg_parallel.md
|
- CFG-Parallel: design/feature/cfg_parallel.md
|
||||||
@@ -123,9 +116,10 @@ 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 Parallel: design/feature/vae_parallel.md
|
- VAE Patch Parallelism: design/feature/vae_parallel.md
|
||||||
- Attention Optimization:
|
- Attention Backends:
|
||||||
- Skip-Softmax: design/feature/skip_softmax.md
|
- Skip-Softmax: design/feature/skip_softmax.md
|
||||||
|
- Quantization: design/feature/quantization.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
|
||||||
@@ -163,8 +157,7 @@ nav:
|
|||||||
- CLI Reference: cli
|
- CLI Reference: cli
|
||||||
- Community:
|
- Community:
|
||||||
- Governance: community/governance.md
|
- Governance: community/governance.md
|
||||||
- Contact Us: community/contact_us.md
|
- community/*
|
||||||
- Community Meetings: community/meetings.md
|
|
||||||
- Slack: https://slack.vllm.ai
|
- Slack: https://slack.vllm.ai
|
||||||
- Blog: https://blog.vllm.ai
|
- Blog: https://blog.vllm.ai
|
||||||
- Forum: https://discuss.vllm.ai
|
- Forum: https://discuss.vllm.ai
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
# Contact Us
|
# Contact Us
|
||||||
|
|
||||||
- For bugs and feature requests, search or open a GitHub [issue](https://github.com/vllm-project/vllm-omni/issues).
|
- For technical questions and feature requests, please use GitHub [Issues](https://github.com/vllm-project/vllm-omni/issues)
|
||||||
- For technical and usage questions, join the `sig-omni` channel in [vLLM Slack](https://slack.vllm.ai/) or use the [vLLM Forum](https://discuss.vllm.ai/).
|
- For coordinating contributions and development and discussing with other users and developers, please join `sig-omni` channel in our [Slack](https://slack.vllm.ai/) or use the [vLLM Forum](https://discuss.vllm.ai/)
|
||||||
- For coordinating contributions and development, use `sig-omni`, the forum, or join a [community meeting](meetings.md).
|
- For security disclosures, please use GitHub's [Security Advisories](https://github.com/vllm-project/vllm-omni/security/advisories) feature
|
||||||
|
|
||||||
## Security Disclosures
|
|
||||||
|
|
||||||
Do not share vulnerability details in public GitHub issues, pull requests, discussions, or Slack channels. To arrange a private report, send a direct message to one of the [lead maintainers](governance.md#lead-maintainers) in [vLLM Slack](https://slack.vllm.ai/). If you cannot send a direct message, you may post a detail-free request in `sig-omni` asking a lead maintainer to contact you privately; do not include any vulnerability details in that request.
|
|
||||||
@@ -52,11 +52,31 @@ Committers have write access and merge rights. They typically have deep expertis
|
|||||||
- [@ZJY0516](https://github.com/ZJY0516): Diffusion attention backend, kernel fusion, and CustomOp
|
- [@ZJY0516](https://github.com/ZJY0516): Diffusion attention backend, kernel fusion, and CustomOp
|
||||||
- [@yuanheng-zhao](https://github.com/yuanheng-zhao): Diffusion cache, offload, and Omni Support
|
- [@yuanheng-zhao](https://github.com/yuanheng-zhao): Diffusion cache, offload, and Omni Support
|
||||||
|
|
||||||
## Reviewer Routing
|
## Diffusion Workload Division
|
||||||
|
|
||||||
For path-based reviewer routing, consult [`.github/CODEOWNERS`](https://github.com/vllm-project/vllm-omni/blob/main/.github/CODEOWNERS). Detailed subsystem boundaries and safe-change guidance live in the [module design documents](../design/index.md#module-design-documents).
|
This section breaks down diffusion-related responsibilities. If you have PRs touching these areas, please ping the listed owners for review.
|
||||||
|
|
||||||
`CODEOWNERS` is operational routing metadata, not a governance or approval policy. Being listed does not grant committer status or merge rights, and it does not mean every listed reviewer must approve a change. The maintainer roster above is authoritative for project roles and merge rights.
|
### Runtime-related
|
||||||
|
|
||||||
|
- Diffusion models: @RuixiangMa, @wtomin, @xuechendi
|
||||||
|
|
||||||
|
### Optimization
|
||||||
|
|
||||||
|
- Parallel: @RuixiangMa, @wtomin
|
||||||
|
- Attention backend: @ZJY0516
|
||||||
|
- Cache: @yuanheng-zhao, @RuixiangMa, @SamitHuang
|
||||||
|
- Offload: @yuanheng-zhao
|
||||||
|
- Intel GPU support: @xuechendi
|
||||||
|
- Quantization: @lishunyang12, @david6666666
|
||||||
|
- Kernel fusion / communication-computation: @ZJY0516
|
||||||
|
|
||||||
|
### Docs & Test
|
||||||
|
|
||||||
|
- Diffusion docs: @RuixiangMa, @wtomin
|
||||||
|
|
||||||
|
## Meetings
|
||||||
|
|
||||||
|
Committers hold **bi-weekly meetings** to discuss future directions and collaborations of the project.
|
||||||
|
|
||||||
## Committer Nomination Process
|
## Committer Nomination Process
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
# Community Meetings
|
|
||||||
|
|
||||||
vLLM-Omni hosts developer-facing meetings for Chinese- and English-language audiences. Schedules and access details can change, so use these maintained documents as the source of truth:
|
|
||||||
|
|
||||||
- [Chinese-language meeting document](https://docs.google.com/document/d/1pdUBiS_7mdOUNDtdwy-9OUf7jsMWN324BIbps5olDME/edit?tab=t.0)
|
|
||||||
- [English-language meeting document](https://docs.google.com/document/d/19AlD08T8uJ7wqfs8VtYCi0iGMTwlTYVVlAxFZ3S7OIg/edit?tab=t.0)
|
|
||||||
|
|
||||||
Each document contains the current schedule, access details, agendas, and past meeting notes. For broader vLLM community events, see the [vLLM events calendar](https://vllm.ai/events/).
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Meetups
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Volunteers for Bugfix and CI
|
||||||
|
|
||||||
|
We encourage you to check current docs and [issues](https://github.com/vllm-project/vllm-omni/issues) to find possible solutions for your questions. If non of these can solve it, please propose an issue to describe your questions about bug or CI problems for developing.
|
||||||
|
|
||||||
|
If you have urgent need for locating and solving bugfix or CI problems, please find community volunteers below.
|
||||||
|
|
||||||
|
| Dec 4-Dec 12 | Dec 15-Dec 19 | Dec 22-Dec 26 | Dec 29- Jan 2, 2026| Jan 5-Jan 9 | Jan 12-Jan 16 |
|
||||||
|
|----------|----------|----------|----------|----------|----------|
|
||||||
|
| <a href="https://github.com/congw729">Conw729</a> | <a href="https://github.com/yinpeiqi">yinpeiqi</a> | <a href="https://github.com/tzhouam">tzhouam</a> | <a href="https://github.com/SamitHuang">SamitHuang</a> | <a href="https://github.com/gcanlin">gcanlin</a> | <a href="https://github.com/natureofnature">natureofnature</a> |
|
||||||
|
| <a href="https://github.com/david6666666">david6666666</a> | <a href="https://github.com/R2-Y">R2-Y</a> | <a href="https://github.com/hsliuustc0106">hsliuustc0106</a> | <a href="https://github.com/Gaohan123">Gaohan123</a> | <a href="https://github.com/ZJY0516">ZJY0516</a> | <a href="https://github.com/qibaoyuan">qibaoyuan</a> |
|
||||||
|
|
||||||
|
We kindly welcome more contributors to fix bugs and contribute new features!
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
Thank you for your interest in contributing to vLLM-Omni! This document provides guidelines and instructions for contributing.
|
Thank you for your interest in contributing to vLLM-Omni! This document provides guidelines and instructions for contributing.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
vLLM-Omni hosts developer-facing meetings for Chinese- and English-language audiences. See [Community Meetings](../community/meetings.md) for current schedules, access details, agendas, and past notes.
|
We host weekly developer-facing online meetings to discuss milestones and updates **every Tuesday at 19:30 PDT**. Meeting link as well as the past meeting notes can be found [here](https://tinyurl.com/vllm-omni-meeting).
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@@ -76,53 +76,12 @@ Please refer to the [test instructions](./ci/test_execution_guide.md) for detail
|
|||||||
!!! warning
|
!!! warning
|
||||||
Currently, not all unit tests pass when run on CPU platforms. If you don't have access to a GPU platform to run unit tests locally, rely on the continuous integration system to run the tests for now.
|
Currently, not all unit tests pass when run on CPU platforms. If you don't have access to a GPU platform to run unit tests locally, rely on the continuous integration system to run the tests for now.
|
||||||
|
|
||||||
### Using repository skills with coding agents
|
|
||||||
|
|
||||||
vLLM-Omni maintains [repository-scale skills](https://github.com/vllm-project/vllm-omni/tree/main/.claude/skills) for common coding, testing, performance, and review workflows. These skills capture repository-specific structure, validation requirements, and evidence standards that a general coding agent may not know.
|
|
||||||
|
|
||||||
If your coding agent discovers repository skills automatically, ask it to use the relevant skill by name. Otherwise, point it to the linked `SKILL.md` and ask it to read the file before changing code. Skills can be combined: use a domain skill for the implementation, `vllm-omni-test` for test and CI decisions, and `precheck-pr` before opening the pull request.
|
|
||||||
|
|
||||||
| Task | Repository skill |
|
|
||||||
| --- | --- |
|
|
||||||
| Add or extend a diffusion model | [`add-diffusion-model`](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/add-diffusion-model/SKILL.md) |
|
|
||||||
| Add or extend a text-to-speech model | [`add-tts-model`](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/add-tts-model/SKILL.md) |
|
|
||||||
| Add, select, or debug quantization | [`quantization`](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/quantization/SKILL.md) |
|
|
||||||
| Diagnose or optimize diffusion performance | [`diffusion-perf-opt`](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/diffusion-perf-opt/SKILL.md) |
|
|
||||||
| Upgrade an NPU model runner | [`vllm-omni-npu-model-runner-upgrade`](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/vllm-omni-npu-upgrade/SKILL.md) |
|
|
||||||
| Add a regression test, choose L1-L4 coverage, or wire Buildkite | [`vllm-omni-test`](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/vllm-omni-test/SKILL.md) |
|
|
||||||
| Self-check a branch before opening a pull request | [`precheck-pr`](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/precheck-pr/SKILL.md) |
|
|
||||||
| Perform a maintainer or reviewer review of an existing pull request or local branch | [`review-pr`](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/review-pr/SKILL.md) |
|
|
||||||
|
|
||||||
Use the following workflow when collaborating with a coding agent:
|
|
||||||
|
|
||||||
1. **Ground the task.** Provide the issue, RFC, or design document that defines the expected behavior. Ask the agent to state its assumptions and identify the affected module or feature contracts before editing code.
|
|
||||||
2. **Select the narrowest applicable skills.** Start with the domain skill that owns the change. Add `vllm-omni-test` whenever production behavior or tests change. For a bug fix, ask for the smallest reproducer and a regression test tied to the original symptom.
|
|
||||||
3. **Require executable evidence.** Ask the agent to run the narrowest relevant checks and report the exact commands and results. It must identify tests it could not run because of unavailable hardware, model weights, credentials, or dependencies; an unrun check is not a pass.
|
|
||||||
4. **Inspect the resulting diff.** Confirm that the implementation stays within the issue or RFC, does not overwrite unrelated work, and includes the required tests, documentation, and benchmark or accuracy evidence.
|
|
||||||
5. **Run the pre-submit workflow.** Use `precheck-pr`, then run the applicable local tests and pre-commit hooks before opening the pull request.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Use add-diffusion-model and vllm-omni-test to implement the linked issue.
|
|
||||||
Read the relevant design documents first, keep the change within the issue,
|
|
||||||
and report every test command, result, and hardware validation gap.
|
|
||||||
```
|
|
||||||
|
|
||||||
```text
|
|
||||||
Use vllm-omni-test to turn this bug reproducer into the smallest deterministic
|
|
||||||
regression test. Prefer L1 CPU coverage; if the failure requires real weights
|
|
||||||
or serving, explain the required L2/L3 environment and provide the exact command.
|
|
||||||
```
|
|
||||||
|
|
||||||
Repository skills guide the agent, but they do not replace contributor judgment, the accepted issue or RFC, required test evidence, or maintainer review. Review all generated changes before submitting them.
|
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
|
|
||||||
If you encounter a bug or have a feature request, please search existing issues first to see if it has already been reported. If not, please file a new issue, providing as much relevant information as possible.
|
If you encounter a bug or have a feature request, please search existing issues first to see if it has already been reported. If not, please file a new issue, providing as much relevant information as possible.
|
||||||
|
|
||||||
!!! important
|
!!! important
|
||||||
Do not report suspected security vulnerabilities through a public issue, pull request, discussion, or Slack channel. Follow the [security disclosure instructions](../community/contact_us.md#security-disclosures) to arrange a private report.
|
If you discover a security vulnerability, please report it by creating a GitHub issue with the `security` label.
|
||||||
|
|
||||||
## Pull Requests & Code Reviews
|
## Pull Requests & Code Reviews
|
||||||
|
|
||||||
|
|||||||
@@ -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 Parallel](feature/vae_parallel.md)
|
[Tensor Parallel](feature/tensor_parallel.md), and [VAE Patch Parallelism](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)
|
||||||
|
|||||||
@@ -1,57 +1,42 @@
|
|||||||
# VAE Parallel
|
# VAE Patch Parallelism
|
||||||
|
|
||||||
This document defines the VAE parallelism strategy contract and describes how to
|
This document describes how to add **VAE Patch Parallelism** support to a diffusion model.
|
||||||
add a strategy to a diffusion model. Tile/patch parallelism is the portable
|
We use **Qwen-Image** as the reference implementation for decode parallel, and **Wan2.2** for encode parallel.
|
||||||
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)
|
||||||
- [Strategy Selection Contract](#strategy-selection-contract)
|
- [Step-by-Step Implementation (Decode)](#step-by-step-implementation-decode)
|
||||||
- [Tile/Patch Implementation (Decode)](#tilepatch-implementation-decode)
|
- [Encode Parallel Implementation](#encode-parallel-implementation)
|
||||||
- [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 parallelism?
|
### What is Vae Patch parallel?
|
||||||
|
|
||||||
VAE parallelism distributes VAE **encoding** or **decoding** across multiple
|
**VAE Patch Parallelism** is an acceleration technique for both **encoding** and **decoding**. Instead of processing the entire tensor at once, the tensor is:
|
||||||
ranks. The implementation currently has two strategy families:
|
|
||||||
|
|
||||||
| Strategy | Unit of work | Communication | Current scope |
|
+ Split into multiple spatial tiles
|
||||||
|----------|--------------|---------------|---------------|
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
Both strategies preserve the model-specific receptive field and reconstruct the
|
+ Distributed across multiple ranks
|
||||||
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.
|
|
||||||
|
|
||||||
This approach can:
|
+ Encoded/Decoded in parallel
|
||||||
|
|
||||||
+ Distribute computation across multiple devices
|
+ Merged to reconstruct the final output
|
||||||
+ Reduce peak memory usage per device
|
|
||||||
+ Accelerate encoding or decoding latency
|
|
||||||
|
|
||||||
The actual benefit depends on shape, topology, communication cost, and VAE
|
This approach:
|
||||||
architecture. A strategy must not become the global default without parity and
|
|
||||||
performance evidence on its supported models.
|
+ Distributes computation across multiple devices
|
||||||
|
|
||||||
|
+ Reduces peak memory usage per device
|
||||||
|
|
||||||
|
+ Accelerates encoding/decoding latency
|
||||||
|
|
||||||
### When to Use Encode vs Decode Parallel
|
### When to Use Encode vs Decode Parallel
|
||||||
|
|
||||||
@@ -60,16 +45,8 @@ performance evidence on its supported models.
|
|||||||
| **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 and ownership
|
### Architecture
|
||||||
|
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:
|
||||||
|
|
||||||
@@ -99,11 +76,6 @@ 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.
|
||||||
@@ -120,34 +92,7 @@ Therefore:
|
|||||||
|
|
||||||
+ Merge must perform blending to avoid seams
|
+ Merge must perform blending to avoid seams
|
||||||
|
|
||||||
## Strategy Selection Contract
|
## Step-by-Step Implementation (Decode)
|
||||||
|
|
||||||
`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:
|
||||||
@@ -306,7 +251,7 @@ class YourModelPipeline(nn.Module):
|
|||||||
+ ).to(self.device)
|
+ ).to(self.device)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tile/Patch Encode Implementation
|
## Encode Parallel 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.
|
||||||
|
|
||||||
@@ -504,15 +449,9 @@ Complete examples in the codebase:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Spatially-Sharded Decode
|
## Spatially-Sharded Decode (Wan and QwenImage)
|
||||||
|
|
||||||
The tile-parallel executor above assigns independent spatial **tiles** to ranks.
|
The tile-parallel executor above assigns independent spatial **tiles** to ranks. The Wan VAE and distributed QwenImage backend additionally support a **spatially-sharded decode** backend. `DiffusionParallelConfig.vae_parallel_mode="auto"` is the default; explicit `"tile"`, `"spatial_shard_height"`, and `"spatial_shard_width"` modes remain available.
|
||||||
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
|
||||||
|
|
||||||
@@ -520,10 +459,11 @@ opt into request-shape selection with `"auto"`, or force an axis with
|
|||||||
|--------|--------------------------|----------------------|-----------------------------------------------|
|
|--------|--------------------------|----------------------|-----------------------------------------------|
|
||||||
| Unit of work | Independent overlapping tiles | Chooses tile or a spatial shard per request | 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 | Depends on the selected path | 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 | Depends on the selected path | 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 and trim padding; QwenImage then broadcasts |
|
||||||
| Scope | Decode + encode | Wan decode selection; encode remains tiled | Wan decode only |
|
| Activation memory | Bounded by tile size | Depends on the selected path | Grows with full output area divided across ranks |
|
||||||
|
| Scope | Decode + encode | Wan/QwenImage decode selection; encode remains tiled | 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 context-aware halo-exchanging variants so each rank only holds a shard of the activations but still sees the correct receptive field at shard boundaries. Wan and QwenImage share the padding, 2D convolution, attention, halo, and trim primitives in `wan_spatial_shard.py`; `qwenimage_spatial_shard.py` supplies a causal-convolution subclass that remains discoverable by Diffusers' QwenImage cache accounting.
|
||||||
|
|
||||||
### Wiring
|
### Wiring
|
||||||
|
|
||||||
@@ -534,68 +474,26 @@ serve.py (--vae-parallel-mode) / OmniEngineArgs
|
|||||||
-> DiffusionParallelConfig.vae_parallel_mode
|
-> DiffusionParallelConfig.vae_parallel_mode
|
||||||
-> registry.py: model.vae.set_parallel_size(vae_pp_size, mode=...)
|
-> registry.py: model.vae.set_parallel_size(vae_pp_size, mode=...)
|
||||||
-> DistributedVaeExecutor.parallel_mode
|
-> DistributedVaeExecutor.parallel_mode
|
||||||
-> DistributedAutoencoderKLWan.tiled_decode dispatch
|
-> capability-based pipeline preparation
|
||||||
|
-> DistributedAutoencoderKLWan/DistributedAutoencoderKLQwenImage.tiled_decode dispatch
|
||||||
```
|
```
|
||||||
|
|
||||||
`DistributedAutoencoderKLWan._spatial_shard_decode_enabled()` gates the path: it
|
The shared spatial policy gates the path: it requires distributed decode to be enabled, a 5D latent, a supported VAE capability, 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. This policy is cached only through normal kernel shape caches; it is reevaluated for every request.
|
||||||
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. Its wrappers consult a context-local split dimension and retain an exact direct fallback, allowing later requests to select tile, height, or width safely.
|
- 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`).
|
||||||
|
- QwenImage additionally covers cache discovery, compile compatibility, mixed tile/spatial requests, both axes, and all-rank result broadcast in `tests/diffusion/distributed/test_qwenimage_spatial_shard.py`.
|
||||||
## 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 parallel support to a diffusion model:
|
Adding VAE Patch Parallel support to diffusion model:
|
||||||
|
|
||||||
1. **Implement the distributed VAE** - Inherit from the base VAE class and
|
1. **Implement Distributed VAE** - Inherit from base VAE class and `DistributedVaeMixin`
|
||||||
`DistributedVaeMixin`.
|
2. **Decode Parallel** - Refactor `tiled_decode` into `tile_split`/`tile_exec`/`tile_merge`
|
||||||
2. **Keep tile as the baseline** - For tile decode, refactor `tiled_decode` into
|
3. **Encode Parallel** (optional) - Implement `encode_tile_split`/`encode_tile_exec`/`encode_tile_merge` for I2V models
|
||||||
`tile_split`/`tile_exec`/`tile_merge`.
|
4. **Change VAE model in pipeline** - Use the distributed version
|
||||||
3. **Add encode parallelism when needed** - Implement
|
5. **Test** - Verify numerical consistency with `vae_patch_parallel_size=1` vs `N`
|
||||||
`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.
|
|
||||||
+6
-14
@@ -11,10 +11,6 @@ in the active navigation.
|
|||||||
|
|
||||||
## Feature Design Documents
|
## Feature Design Documents
|
||||||
|
|
||||||
For user-facing configuration and current compatibility, see the
|
|
||||||
[Features overview](../features/README.md). A design document defines an
|
|
||||||
implementation contract; it is not, by itself, a general support claim.
|
|
||||||
|
|
||||||
### Runtime and stage execution
|
### Runtime and stage execution
|
||||||
|
|
||||||
- [Disaggregated Inference](feature/disaggregated_inference.md)
|
- [Disaggregated Inference](feature/disaggregated_inference.md)
|
||||||
@@ -34,10 +30,6 @@ implementation contract; it is not, by itself, a general support claim.
|
|||||||
- [Yuanrong Store Connector](feature/omni_connectors/yuanrong_connector.md)
|
- [Yuanrong Store Connector](feature/omni_connectors/yuanrong_connector.md)
|
||||||
- [Yuanrong Transfer Engine Connector](feature/omni_connectors/yuanrong_transfer_engine_connector.md)
|
- [Yuanrong Transfer Engine Connector](feature/omni_connectors/yuanrong_transfer_engine_connector.md)
|
||||||
|
|
||||||
### Quantization
|
|
||||||
|
|
||||||
- [Quantization](feature/quantization.md)
|
|
||||||
|
|
||||||
### Diffusion acceleration
|
### Diffusion acceleration
|
||||||
|
|
||||||
#### Parallelism
|
#### Parallelism
|
||||||
@@ -48,16 +40,16 @@ 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 Parallel](feature/vae_parallel.md)
|
- [VAE Patch Parallelism](feature/vae_parallel.md)
|
||||||
|
|
||||||
#### Attention optimization
|
#### Attention Backends
|
||||||
|
|
||||||
The [Diffusion Attention Backends](../user_guide/diffusion/attention_backends.md)
|
|
||||||
guide lists all selectable backends and their platform defaults. Skip-Softmax
|
|
||||||
is the backend-related optimization with a standalone feature-design contract:
|
|
||||||
|
|
||||||
- [Skip-Softmax](feature/skip_softmax.md)
|
- [Skip-Softmax](feature/skip_softmax.md)
|
||||||
|
|
||||||
|
#### Quantization
|
||||||
|
|
||||||
|
- [Quantization](feature/quantization.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)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Prometheus Metrics Design
|
# Prometheus Metrics Design
|
||||||
|
|
||||||
This document describes how vLLM-Omni exposes Prometheus metrics for multi-stage pipelines, the constraints that shaped the design, and how the pipeline-level metrics coexist with upstream vLLM per-engine metrics.
|
This document describes how vLLM-Omni exposes Prometheus metrics for multi-stage pipelines, the constraints that shaped the design, and how the pipeline-level metrics coexist with upstream vLLM per-engine metrics. This iteration covers the text and audio surface; diffusion / image / video families land in a follow-up.
|
||||||
|
|
||||||
## Objectives
|
## Objectives
|
||||||
|
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
# Features
|
|
||||||
|
|
||||||
Use this section to choose and configure vLLM-Omni runtime and optimization
|
|
||||||
features. The navigation follows the active
|
|
||||||
[Feature Design](../design/index.md#feature-design-documents) taxonomy whenever
|
|
||||||
an implementation contract has a user-facing workflow.
|
|
||||||
|
|
||||||
!!! note "User guides, design documents, and recipes"
|
|
||||||
|
|
||||||
User guides explain how to enable a feature and where it is supported.
|
|
||||||
Design documents define internal contracts and do not, by themselves,
|
|
||||||
imply general or production support. For model-specific launch flags and
|
|
||||||
hardware requirements, use the linked examples and validated recipes from
|
|
||||||
[Supported Models](../models/supported_models.md).
|
|
||||||
|
|
||||||
## Runtime and Stage Execution
|
|
||||||
|
|
||||||
| Goal | User guide | Related design contract |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
Some runtime designs are deliberately not promoted as standalone User Guide
|
|
||||||
features yet:
|
|
||||||
|
|
||||||
- [Disaggregated Inference](../design/feature/disaggregated_inference.md) and
|
|
||||||
[OmniConnector implementations](../design/index.md#communication) describe
|
|
||||||
topology and transport contracts. Practical deployment configuration remains
|
|
||||||
under [Pipeline and deploy configurations](../configuration/stage_configs.md).
|
|
||||||
- [Async Chunk](../design/feature/async_chunk.md) and
|
|
||||||
[Async Omni Output Materialization](../design/feature/omni_async_output_materialization.md)
|
|
||||||
are model- and pipeline-dependent. Use the selected model's deploy
|
|
||||||
configuration and recipe for the supported settings.
|
|
||||||
- [Automatic Prefix Caching](../design/feature/prefix_caching.md) remains a
|
|
||||||
design-level contract until its user-facing configuration and compatibility
|
|
||||||
surface is consolidated.
|
|
||||||
|
|
||||||
## Quantization
|
|
||||||
|
|
||||||
Quantization is a cross-model feature rather than a diffusion-only
|
|
||||||
optimization. The unified [`quantization_config` guide](../user_guide/quantization/overview.md)
|
|
||||||
covers diffusion-only models, multi-stage omni/TTS models, and multi-stage
|
|
||||||
diffusion models. Its [design contract](../design/feature/quantization.md)
|
|
||||||
defines the shared configuration and backend extension points.
|
|
||||||
|
|
||||||
## Diffusion Acceleration
|
|
||||||
|
|
||||||
| 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) |
|
|
||||||
| 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) |
|
|
||||||
| 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) | [Skip-Softmax](../design/feature/skip_softmax.md) |
|
|
||||||
| 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 |
|
|
||||||
| Reduce diffusion model startup time | [Startup and Loading](../user_guide/diffusion/startup_and_loading.md) | User-facing loading guide |
|
|
||||||
| Apply diffusion adapters | [LoRA](../user_guide/diffusion/lora.md) | User-facing extension guide |
|
|
||||||
|
|
||||||
The [Pipeline Parallelism guide](../user_guide/diffusion/parallelism/pipeline_parallel.md)
|
|
||||||
remains available by direct link, but it is not promoted in the primary User
|
|
||||||
Guide navigation until its user-facing support and placement are settled.
|
|
||||||
|
|
||||||
## Experimental
|
|
||||||
|
|
||||||
[Session State Manager](session_state_manager.md) is opt-in and experimental.
|
|
||||||
Its APIs and compatibility may change without notice.
|
|
||||||
@@ -51,7 +51,6 @@ th {
|
|||||||
| `OvisImagePipeline` | Ovis-Image | `OvisAI/Ovis-Image` | ✅︎ | ✅︎ | | ✅︎ | — |
|
| `OvisImagePipeline` | Ovis-Image | `OvisAI/Ovis-Image` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||||
| `LongcatImagePipeline` | LongCat-Image | `meituan-longcat/LongCat-Image` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `LongcatImagePipeline` | LongCat-Image | `meituan-longcat/LongCat-Image` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `LongCatImageEditPipeline` | LongCat-Image-Edit | `meituan-longcat/LongCat-Image-Edit` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `LongCatImageEditPipeline` | LongCat-Image-Edit | `meituan-longcat/LongCat-Image-Edit` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `LongCatVideoAvatarPipeline` | LongCat-Video-Avatar-1.5 A2V/AI2V (native single-speaker and multi-speaker AI2V/AVC) | `meituan-longcat/LongCat-Video-Avatar-1.5` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/meituan-longcat/LongCat-Video-Avatar-1.5.md) |
|
|
||||||
| `BooguImagePipeline` | Boogu-Image | `Boogu/Boogu-Image-0.1-Base`, `Boogu/Boogu-Image-0.1-Edit` | ✅︎ | | | | — |
|
| `BooguImagePipeline` | Boogu-Image | `Boogu/Boogu-Image-0.1-Base`, `Boogu/Boogu-Image-0.1-Edit` | ✅︎ | | | | — |
|
||||||
| `StableDiffusionXLPipeline` | Stable-Diffusion-XL | `stabilityai/stable-diffusion-xl-base-1.0` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
| `StableDiffusionXLPipeline` | Stable-Diffusion-XL | `stabilityai/stable-diffusion-xl-base-1.0` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
|
||||||
| `StableDiffusion3Pipeline` | Stable-Diffusion-3 | `stabilityai/stable-diffusion-3.5-medium` | ✅︎ | ✅︎ | | ✅︎ | — |
|
| `StableDiffusion3Pipeline` | Stable-Diffusion-3 | `stabilityai/stable-diffusion-3.5-medium` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||||
@@ -80,7 +79,6 @@ th {
|
|||||||
| `HiggsAudioV2ForConditionalGeneration` | Higgs-Audio v2 | `bosonai/higgs-audio-v2-generation-3B-base` | ✅︎ | | | | — |
|
| `HiggsAudioV2ForConditionalGeneration` | Higgs-Audio v2 | `bosonai/higgs-audio-v2-generation-3B-base` | ✅︎ | | | | — |
|
||||||
| `HiggsMultimodalQwen3ForConditionalGeneration` | Higgs-Audio v3 (TTS) | `bosonai/higgs-audio-v3-tts-4b` | ✅︎ | | | | [Published](https://recipes.vllm.ai/bosonai/higgs-audio-v3-tts-4b) |
|
| `HiggsMultimodalQwen3ForConditionalGeneration` | Higgs-Audio v3 (TTS) | `bosonai/higgs-audio-v3-tts-4b` | ✅︎ | | | | [Published](https://recipes.vllm.ai/bosonai/higgs-audio-v3-tts-4b) |
|
||||||
| `IndexTTS2TalkerForConditionalGeneration` | IndexTTS-2 | `IndexTeam/IndexTTS-2` | ✅︎ | | | | — |
|
| `IndexTTS2TalkerForConditionalGeneration` | IndexTTS-2 | `IndexTeam/IndexTTS-2` | ✅︎ | | | | — |
|
||||||
| `IndexTTS25TalkerForConditionalGeneration` | IndexTTS-2.5 | native `checkpoints/` bundle | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/IndexTeam/IndexTTS-2_5.md) |
|
|
||||||
| `NextStep11Pipeline` | NextStep-1.1 | `stepfun-ai/NextStep-1.1` | ✅︎ | ✅︎ | | ✅︎ | — |
|
| `NextStep11Pipeline` | NextStep-1.1 | `stepfun-ai/NextStep-1.1` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||||
| `MiMoAudioModel` | MiMo-Audio-7B-Instruct | `XiaomiMiMo/MiMo-Audio-7B-Instruct` | ✅︎ | ✅︎ | | | — |
|
| `MiMoAudioModel` | MiMo-Audio-7B-Instruct | `XiaomiMiMo/MiMo-Audio-7B-Instruct` | ✅︎ | ✅︎ | | | — |
|
||||||
| `MiMoV2ASRForCausalLM` | MiMo-V2.5-ASR | `XiaomiMiMo/MiMo-V2.5-ASR` | ✅︎ | ✅︎ | | | — |
|
| `MiMoV2ASRForCausalLM` | MiMo-V2.5-ASR | `XiaomiMiMo/MiMo-V2.5-ASR` | ✅︎ | ✅︎ | | | — |
|
||||||
|
|||||||
+3
-3
@@ -10,12 +10,12 @@ A: Refer to [GPU memory calculation and configuration](../configuration/gpu_memo
|
|||||||
|
|
||||||
> Q: I encounter some bugs or CI problems, which is urgent. How can I solve it?
|
> Q: I encounter some bugs or CI problems, which is urgent. How can I solve it?
|
||||||
|
|
||||||
A: Search the current [issues](https://github.com/vllm-project/vllm-omni/issues) first. If no existing issue matches a bug or CI failure, open a [bug report](https://github.com/vllm-project/vllm-omni/issues/new?template=400-bug-report.yml) with a minimal reproducer, environment details, and relevant logs. For technical or usage questions, use the `sig-omni` channel in [vLLM Slack](https://slack.vllm.ai/) or the [vLLM Forum](https://discuss.vllm.ai/).
|
A: At first, you can check current [issues](https://github.com/vllm-project/vllm-omni/issues) to find possible solutions. If none of these satisfy your demand and it is urgent, please find these [volunteers](https://docs.vllm.ai/projects/vllm-omni/en/latest/community/volunteers/) for help.
|
||||||
|
|
||||||
> Q: Does vLLM-Omni support AWQ or any other quantization?
|
> Q: Does vLLM-Omni support AWQ or any other quantization?
|
||||||
|
|
||||||
A: AWQ is available through the inherited vLLM quantization registry, and vLLM-Omni supports several additional methods. Actual support and validation vary by model, component, hardware, and checkpoint. See the [quantization overview](../user_guide/quantization/overview.md) for validated combinations and method-specific guides.
|
A: We plan to introduce GGUF FP8 prequantized models and online FP8 quantization in version 0.16.0. Support for other quantization types will follow in future releases. For details, please see our [Q1 quantization roadmap](https://github.com/vllm-project/vllm-omni/issues/1057).
|
||||||
|
|
||||||
> Q: Does vLLM-Omni support multimodal streaming input and output?
|
> Q: Does vLLM-Omni support multimodal streaming input and output?
|
||||||
|
|
||||||
A: Yes, for specific models and serving paths. Current capabilities include [streaming video input](../serving/video_stream_api.md), [streaming diffusion output](../user_guide/diffusion/execution_modes.md#streaming-output), and experimental full-duplex real-time audio described in the [project overview](../README.md). Check the model and API documentation because input and output support is model-specific.
|
A: Not yet. We already put it on the [Roadmap](https://github.com/vllm-project/vllm-omni/issues/165). Please stay tuned!
|
||||||
+26
-50
@@ -1,23 +1,23 @@
|
|||||||
# Production Metrics
|
# Production Metrics
|
||||||
|
|
||||||
vLLM-Omni exposes Prometheus metrics via the `/metrics` endpoint on the OpenAI-compatible API server.
|
vLLM-Omni exposes Prometheus metrics via the `/metrics` endpoint on the OpenAI-compatible API server. This page covers the text and audio surface; diffusion / image / video metrics are tracked in a follow-up PR.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vllm-omni serve Qwen/Qwen3-Omni-30B-A3B-Instruct --port 8000 --log-stats
|
vllm-omni serve Qwen/Qwen3-Omni-30B-A3B-Instruct --port 8000 --log-stats
|
||||||
curl http://localhost:8000/metrics
|
curl http://localhost:8000/metrics
|
||||||
```
|
```
|
||||||
|
|
||||||
**`--log-stats` is required to populate metric data.** Without the flag, the endpoint still returns `200 OK` but the upstream `vllm:*` wrap is not registered at all, and the 15 `vllm_omni:*` families are registered as placeholders with no data written to them. This keeps the runtime cost essentially zero for deployments that don't need monitoring. With the flag, all ~80 families populate.
|
**`--log-stats` is required to populate metric data.** Without the flag, the endpoint still returns `200 OK` but the upstream `vllm:*` wrap is not registered at all, and the 15 `vllm:omni_*` families are registered as placeholders with no data written to them. This keeps the runtime cost essentially zero for deployments that don't need monitoring. With the flag, all ~80 families populate.
|
||||||
|
|
||||||
## Metric Namespaces
|
## Metric Namespaces
|
||||||
|
|
||||||
| Prefix | Source | Present when |
|
| Prefix | Source | Present when |
|
||||||
|--------|--------|--------------|
|
|--------|--------|--------------|
|
||||||
| `vllm_omni:` | vLLM-Omni orchestrator / audio modality / cross-stage transfer | Pipeline-dependent |
|
| `vllm:omni_` | vLLM-Omni orchestrator / audio modality / cross-stage transfer | Pipeline-dependent |
|
||||||
| `vllm:` | Upstream vLLM engine, wrapped by `OmniPrometheusStatLogger` to expose `{stage, replica}` | Pipeline includes an LLM (AR) stage |
|
| `vllm:` | Upstream vLLM engine, wrapped by `OmniPrometheusStatLogger` to expose `{stage, replica}` | Pipeline includes an LLM (AR) stage |
|
||||||
| `http_` / `process_` | Uvicorn / Python runtime | Always |
|
| `http_` / `process_` | Uvicorn / Python runtime | Always |
|
||||||
|
|
||||||
## Pipeline-Level Metrics (`vllm_omni:`)
|
## Pipeline-Level Metrics (`vllm:omni_`)
|
||||||
|
|
||||||
Defined in `vllm_omni/metrics/prometheus.py`. Track request lifecycle across the full multi-stage pipeline.
|
Defined in `vllm_omni/metrics/prometheus.py`. Track request lifecycle across the full multi-stage pipeline.
|
||||||
|
|
||||||
@@ -25,66 +25,42 @@ Defined in `vllm_omni/metrics/prometheus.py`. Track request lifecycle across the
|
|||||||
|
|
||||||
| Metric | Type | Labels | Description |
|
| Metric | Type | Labels | Description |
|
||||||
|--------|------|--------|-------------|
|
|--------|------|--------|-------------|
|
||||||
| `vllm_omni:num_requests_running` | Gauge | `model_name` | Pipeline-global in-flight requests (dispatched to engine, not yet finalized) |
|
| `vllm:omni_num_requests_running` | Gauge | `model_name` | Pipeline-global in-flight requests (dispatched to engine, not yet finalized) |
|
||||||
| `vllm_omni:num_requests_waiting` | Gauge | `model_name` | Requests waiting in the Orchestrator queue |
|
| `vllm:omni_num_requests_waiting` | Gauge | `model_name` | Requests waiting in the Orchestrator queue |
|
||||||
| `vllm_omni:requests_success_total` | Counter | `model_name`, `finished_reason` | Total requests by completion reason. `finished_reason` ∈ {`stop`, `length`, `abort`, ...} mirroring upstream `vllm:request_success_total`; aborts cover client disconnect / cancellation paths in addition to upstream `FinishReason.ABORT` |
|
| `vllm:omni_requests_success_total` | Counter | `model_name`, `finished_reason` | Total requests by completion reason. `finished_reason` ∈ {`stop`, `length`, `abort`, ...} mirroring upstream `vllm:request_success_total`; aborts cover client disconnect / cancellation paths in addition to upstream `FinishReason.ABORT` |
|
||||||
|
|
||||||
### Latency
|
### Latency
|
||||||
|
|
||||||
| Metric | Type | Labels | Description |
|
| Metric | Type | Labels | Description |
|
||||||
|--------|------|--------|-------------|
|
|--------|------|--------|-------------|
|
||||||
| `vllm_omni:e2e_request_latency_s` | Histogram | `model_name` | Pipeline-global end-to-end request latency in seconds |
|
| `vllm:omni_e2e_request_latency_s` | Histogram | `model_name` | Pipeline-global end-to-end request latency in seconds |
|
||||||
|
|
||||||
## Audio Modality Metrics (`vllm_omni:`)
|
## Audio Modality Metrics (`vllm:omni_`)
|
||||||
|
|
||||||
Emitted at request finalize, except for `audio_ttfp_s` (streaming-hook at the first audio packet) and `audio_underrun_s` / `audio_continuity_ok_total` (streaming finalize, after the chunk stream is exhausted). All carry `{model_name, stage, replica}` plus the listed extra label.
|
Emitted at request finalize, except for `audio_ttfp_s` (streaming-hook at the first audio packet) and `audio_underrun_s` / `audio_continuity_ok_total` (streaming finalize, after the chunk stream is exhausted). All carry `{model_name, stage, replica}` plus the listed extra label.
|
||||||
|
|
||||||
| Metric | Type | Extra label | Description |
|
| Metric | Type | Extra label | Description |
|
||||||
|--------|------|-------------|-------------|
|
|--------|------|-------------|-------------|
|
||||||
| `vllm_omni:audio_ttfp_s` | Histogram | — | Time from request arrival to first audio packet/frame |
|
| `vllm:omni_audio_ttfp_s` | Histogram | — | Time from request arrival to first audio packet/frame |
|
||||||
| `vllm_omni:audio_duration_s` | Histogram | — | Audio content duration (`audio_frames / sample_rate`) |
|
| `vllm:omni_audio_duration_s` | Histogram | — | Audio content duration (`audio_frames / sample_rate`) |
|
||||||
| `vllm_omni:audio_rtf` | Histogram | — | Real-time factor (`stage_gen_time_s / audio_duration_s`); streaming TTS SLO red line `< 1`; uses `RTF_BUCKETS` |
|
| `vllm:omni_audio_rtf` | Histogram | — | Real-time factor (`stage_gen_time_s / audio_duration_s`); streaming TTS SLO red line `< 1`; uses `RTF_BUCKETS` |
|
||||||
| `vllm_omni:audio_frames_total` | Counter | — | Cumulative audio frame count; throughput via `rate()` |
|
| `vllm:omni_audio_frames_total` | Counter | — | Cumulative audio frame count; throughput via `rate()` |
|
||||||
| `vllm_omni:audio_underrun_s` | Histogram | — | Per-request worst-case player deficit; `> 0` indicates listener heard silent gaps |
|
| `vllm:omni_audio_underrun_s` | Histogram | — | Per-request worst-case player deficit; `> 0` indicates listener heard silent gaps |
|
||||||
| `vllm_omni:audio_continuity_ok_total` | Counter | `threshold_ms` | Incremented when the request's worst underrun stayed below `threshold_ms` |
|
| `vllm:omni_audio_continuity_ok_total` | Counter | `threshold_ms` | Incremented when the request's worst underrun stayed below `threshold_ms` |
|
||||||
| `vllm_omni:audio_skipped_requests_total` | Counter | `reason` | Silent-loss counter — code2wav rejected malformed codec input and returned `200 OK` with empty audio |
|
| `vllm:omni_audio_skipped_requests_total` | Counter | `reason` | Silent-loss counter — code2wav rejected malformed codec input and returned `200 OK` with empty audio |
|
||||||
|
|
||||||
The continuity math comes from `vllm_omni/benchmarks/audio_continuity.py::compute_continuity_stats` so the server-side observation aligns with the bench-side definition.
|
The continuity math comes from `vllm_omni/benchmarks/audio_continuity.py::compute_continuity_stats` so the server-side observation aligns with the bench-side definition.
|
||||||
|
|
||||||
## Diffusion Metrics (`vllm_omni:`)
|
## Cross-Stage Transfer Metrics (`vllm:omni_`)
|
||||||
|
|
||||||
Per-request timing breakdowns for diffusion (image/video) stages. Emitted at request finalize when `stage_metrics.diffusion_metrics` is present. All carry `{model_name, stage, replica}`.
|
|
||||||
|
|
||||||
| Metric | Type | Description |
|
|
||||||
|--------|------|-------------|
|
|
||||||
| `vllm_omni:diffusion_exec_s` | Histogram | DiT forward pass execution time per request in seconds |
|
|
||||||
| `vllm_omni:diffusion_exec_per_step_s` | Histogram | DiT forward pass execution time per denoising step in seconds (`exec_s / num_inference_steps`) |
|
|
||||||
| `vllm_omni:diffusion_preprocess_s` | Histogram | Diffusion input preprocessing time per request in seconds |
|
|
||||||
| `vllm_omni:diffusion_postprocess_s` | Histogram | Diffusion output postprocessing (VAE decode) time per request in seconds |
|
|
||||||
|
|
||||||
`diffusion_exec_s` uses `SECONDS_BUCKETS` (50 ms → 300 s); the other three use `SECONDS_FAST_BUCKETS` (1 ms → 60 s). `diffusion_exec_per_step_s` is skipped when `num_inference_steps` is not set.
|
|
||||||
|
|
||||||
### Example PromQL
|
|
||||||
|
|
||||||
```promql
|
|
||||||
# P99 DiT exec time by stage
|
|
||||||
histogram_quantile(0.99, rate(vllm_omni:diffusion_exec_s_bucket[5m]))
|
|
||||||
|
|
||||||
# Ratio of preprocess to total exec wall time
|
|
||||||
rate(vllm_omni:diffusion_preprocess_s_sum[5m])
|
|
||||||
/ rate(vllm_omni:diffusion_exec_s_sum[5m])
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cross-Stage Transfer Metrics (`vllm_omni:`)
|
|
||||||
|
|
||||||
Per-physical-transfer histograms tracking the data hop between adjacent stages. Labels `{model_name, from_stage, from_replica, to_stage, to_replica}` let dashboards attribute latency to specific replica edges. `from_replica` / `to_replica` are resolved from the orchestrator's sticky-routing binding (`stage_pool.get_bound_replica_id(request_id)`), so no extra plumbing through `TransferEdgeStats` is needed.
|
Per-physical-transfer histograms tracking the data hop between adjacent stages. Labels `{model_name, from_stage, from_replica, to_stage, to_replica}` let dashboards attribute latency to specific replica edges. `from_replica` / `to_replica` are resolved from the orchestrator's sticky-routing binding (`stage_pool.get_bound_replica_id(request_id)`), so no extra plumbing through `TransferEdgeStats` is needed.
|
||||||
|
|
||||||
| Metric | Type | Description |
|
| Metric | Type | Description |
|
||||||
|--------|------|-------------|
|
|--------|------|-------------|
|
||||||
| `vllm_omni:transfer_size_bytes` | Histogram | Per-transfer payload size in bytes |
|
| `vllm:omni_transfer_size_bytes` | Histogram | Per-transfer payload size in bytes |
|
||||||
| `vllm_omni:transfer_tx_s` | Histogram | Sender-side time (serialize + submit to connector) |
|
| `vllm:omni_transfer_tx_s` | Histogram | Sender-side time (serialize + submit to connector) |
|
||||||
| `vllm_omni:transfer_rx_s` | Histogram | Receiver-side time (recv + deserialize) |
|
| `vllm:omni_transfer_rx_s` | Histogram | Receiver-side time (recv + deserialize) |
|
||||||
| `vllm_omni:transfer_in_flight_s` | Histogram | Network in-flight time (TX done → RX recv start) |
|
| `vllm:omni_transfer_in_flight_s` | Histogram | Network in-flight time (TX done → RX recv start) |
|
||||||
|
|
||||||
## vLLM Engine Metrics (`vllm:`)
|
## vLLM Engine Metrics (`vllm:`)
|
||||||
|
|
||||||
@@ -107,9 +83,9 @@ For the full list of upstream metrics, see [the vLLM docs](https://github.com/vl
|
|||||||
|
|
||||||
| Metric group | Multi-stage LLM (Qwen3-Omni) |
|
| Metric group | Multi-stage LLM (Qwen3-Omni) |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `vllm_omni:` request tracking + latency | With `--log-stats` |
|
| `vllm:omni_` request tracking + latency | With `--log-stats` |
|
||||||
| `vllm_omni:` audio modality | With `--log-stats`, if pipeline has a talker stage |
|
| `vllm:omni_` audio modality | With `--log-stats`, if pipeline has a talker stage |
|
||||||
| `vllm_omni:` transfer | With `--log-stats`, if pipeline has ≥ 2 stages |
|
| `vllm:omni_` transfer | With `--log-stats`, if pipeline has ≥ 2 stages |
|
||||||
| `vllm:` engine metrics (per `(stage, replica)`) | With `--log-stats` |
|
| `vllm:` engine metrics (per `(stage, replica)`) | With `--log-stats` |
|
||||||
| `vllm:` MFU metrics | With `--log-stats --enable-mfu-metrics` |
|
| `vllm:` MFU metrics | With `--log-stats --enable-mfu-metrics` |
|
||||||
|
|
||||||
@@ -118,5 +94,5 @@ For the full list of upstream metrics, see [the vLLM docs](https://github.com/vl
|
|||||||
- All time-bearing metrics use the `_s` suffix (values in seconds). Buckets are `SECONDS_BUCKETS` for e2e / generation-style values and `SECONDS_FAST_BUCKETS` (1 ms → 60 s) for the fine-grained transfer and audio-underrun values.
|
- All time-bearing metrics use the `_s` suffix (values in seconds). Buckets are `SECONDS_BUCKETS` for e2e / generation-style values and `SECONDS_FAST_BUCKETS` (1 ms → 60 s) for the fine-grained transfer and audio-underrun values.
|
||||||
- Counters use the `_total` suffix (auto-appended by `prometheus_client`).
|
- Counters use the `_total` suffix (auto-appended by `prometheus_client`).
|
||||||
- Sizes use the `_bytes` suffix.
|
- Sizes use the `_bytes` suffix.
|
||||||
- All omni-specific families are prefixed `vllm_omni:`. The upstream `unregister_vllm_metrics()` function is monkey-patched (see `vllm_omni/patch.py`) to a scoped version that still strips upstream `vllm:*` collectors so multi-engine init within one process does not crash on duplicate registration, but preserves anything prefixed `vllm_omni:`.
|
- All omni-specific families are prefixed `vllm:omni_`. The upstream `unregister_vllm_metrics()` function is monkey-patched (see `vllm_omni/patch.py`) to a scoped version that still strips upstream `vllm:*` collectors so multi-engine init within one process does not crash on duplicate registration, but preserves anything prefixed `vllm:omni_` / `vllm_omni`.
|
||||||
- Text and audio first-output use distinct families (`vllm:time_to_first_token_seconds` reused from upstream for text; `vllm_omni:audio_ttfp_s` for audio) rather than a single metric with a `modality` label.
|
- Text and audio first-output use distinct families (`vllm:time_to_first_token_seconds` reused from upstream for text; `vllm:omni_audio_ttfp_s` for audio) rather than a single metric with a `modality` label.
|
||||||
@@ -1,18 +1,14 @@
|
|||||||
# LoRA (Low-Rank Adaptation) Guide
|
# LoRA (Low-Rank Adaptation) Guide
|
||||||
|
|
||||||
LoRA (Low-Rank Adaptation) enables fine-tuning diffusion models by adding trainable low-rank matrices to existing model weights. vLLM-Omni supports two LoRA backends: **PEFT** for PEFT-style adapters, and **Distill** usually for few-steps inference. **PEFT** backend allows you to customize model behavior without modifying the base model weights. **Distill** backend fuses LoRA weights into the base model at initialization.
|
LoRA (Low-Rank Adaptation) enables fine-tuning diffusion models by adding trainable low-rank matrices to existing model weights. vLLM-Omni currently supports PEFT-style LoRA adapters, allowing you to customize model behavior without modifying the base model weights.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
LoRA adapters are lightweight, model-specific fine-tuning weights that can be applied to diffusion models in two ways:
|
LoRA adapters are lightweight, model-specific fine-tuning weights that can be dynamically loaded and applied to diffusion models. vLLM-Omni uses a unified LoRA handling mechanism similar to vLLM with LRU cache management.
|
||||||
|
|
||||||
- **PEFT backend** (`--lora-backend peft`, default): Loads a PEFT-format adapter folder via `DiffusionLoRAManager`. Adapters are cached (LRU) and activated per request via `LoRARequest`. It uses a unified LoRA handling mechanisms similar to vLLM with LRU cache management.
|
|
||||||
- **Distill backend** (`--lora-backend distill`): Calls `pipeline.load_lora_weights` once at initialization to fuse one or more concrete checkpoint files directly into the base weights. Typically used for distilled few-step LoRAs (e.g. Lightning, LightX2V).
|
|
||||||
|
|
||||||
## LoRA Adapter Format
|
## LoRA Adapter Format
|
||||||
|
|
||||||
### PEFT (Parameter-Efficient Fine-Tuning) format (default)
|
LoRA adapters must be in **PEFT (Parameter-Efficient Fine-Tuning)** format. A typical LoRA adapter directory structure:
|
||||||
A typical PEFT-format LoRA adapter directory structure:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
lora_adapter/
|
lora_adapter/
|
||||||
@@ -29,20 +25,19 @@ The `adapter_config.json` file contains metadata about the LoRA adapter, includi
|
|||||||
|
|
||||||
### Offline Inference
|
### Offline Inference
|
||||||
|
|
||||||
#### PEFT backend: pre-loaded LoRA
|
#### Pre-loaded LoRA
|
||||||
|
|
||||||
Load a PEFT-format LoRA adapter at initialization. The adapter is pre-loaded into the cache and can be activated per request:
|
Load a LoRA adapter at initialization. This adapter is pre-loaded into the cache and can be activated by requests:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from vllm_omni import Omni
|
from vllm_omni import Omni
|
||||||
from vllm_omni.lora.request import LoRARequest
|
from vllm_omni.lora.request import LoRARequest
|
||||||
|
|
||||||
lora_path = "/path/to/lora_adapter"
|
lora_path="/path/to/lora_adapter"
|
||||||
|
|
||||||
omni = Omni(
|
omni = Omni(
|
||||||
model="stabilityai/stable-diffusion-3.5-medium",
|
model="stabilityai/stable-diffusion-3.5-medium",
|
||||||
lora_path=lora_path,
|
lora_path=lora_path
|
||||||
lora_backend="peft", # default, can be omitted
|
|
||||||
)
|
)
|
||||||
|
|
||||||
lora_request = LoRARequest(
|
lora_request = LoRARequest(
|
||||||
@@ -58,98 +53,6 @@ outputs = omni.generate(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Distill backend: fuse distilled LoRA at init
|
|
||||||
|
|
||||||
For distilled few-step LoRAs, pass `lora_backend="distill"` together with one or more concrete `.safetensors` files. The weights are fused into the base model once at init; subsequent `generate()` calls do not need a `LoRARequest`.
|
|
||||||
|
|
||||||
##### Supported pipelines
|
|
||||||
|
|
||||||
For Qwen-Image and Wan pipelines, the distill backend calls `pipeline.load_lora_weights(...)` during worker initialization.
|
|
||||||
|
|
||||||
| Pipeline | Supported distilled LoRA repo | Notes |
|
|
||||||
|----------|--------------------------------|-------|
|
|
||||||
| `QwenImagePipeline` | `lightx2v/Qwen-Image-2512-Lightning` | Used with Qwen-Image-2512 Lightning-style few-step inference. |
|
|
||||||
| `Wan22Pipeline` | `lightx2v/Wan2.1-Distill-Loras`, `lightx2v/Wan2.2-Distill-Loras` | Wan2.1 uses one LoRA file. For dual-transformer Wan2.2 MoE, pass high-noise then low-noise LoRA files. |
|
|
||||||
| `Wan22I2VPipeline` | `lightx2v/Wan2.2-Distill-Loras` | For dual-transformer Wan2.2 MoE, pass high-noise then low-noise LoRA files. |
|
|
||||||
|
|
||||||
Other diffusion pipelines are not currently listed as supporting distilled LoRA. Use the PEFT backend for request-time adapters, or bake converted weights into a local Diffusers directory before serving.
|
|
||||||
|
|
||||||
Single-file example (Qwen-Image-Lightning):
|
|
||||||
|
|
||||||
```python
|
|
||||||
from vllm_omni import Omni
|
|
||||||
|
|
||||||
omni = Omni(
|
|
||||||
model="Qwen/Qwen-Image-2512",
|
|
||||||
lora_path="/path/to/Qwen-Image-2512-Lightning.safetensors",
|
|
||||||
lora_backend="distill",
|
|
||||||
)
|
|
||||||
|
|
||||||
outputs = omni.generate(prompt="A piece of cheesecake")
|
|
||||||
```
|
|
||||||
|
|
||||||
Multi-file example (Wan2.2 MoE, high + low noise):
|
|
||||||
|
|
||||||
```python
|
|
||||||
from vllm_omni import Omni
|
|
||||||
|
|
||||||
omni = Omni(
|
|
||||||
model="Wan-AI/Wan2.2-T2V-A14B-Diffusers",
|
|
||||||
lora_path=[
|
|
||||||
"/path/to/wan2.2_high_noise_lora.safetensors", # -> transformer
|
|
||||||
"/path/to/wan2.2_low_noise_lora.safetensors", # -> transformer_2
|
|
||||||
],
|
|
||||||
lora_backend="distill",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
The CLI examples under `examples/offline_inference/` accept the same flags, e.g.:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python examples/offline_inference/text_to_video/text_to_video.py \
|
|
||||||
--model Wan-AI/Wan2.2-T2V-A14B-Diffusers \
|
|
||||||
--lora-backend distill \
|
|
||||||
--lora-path /path/to/high.safetensors /path/to/low.safetensors \
|
|
||||||
--prompt "A cat playing with yarn"
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Online serving
|
|
||||||
|
|
||||||
Distilled LoRAs can also be fused when an online diffusion server starts. The
|
|
||||||
adapter remains active for the lifetime of that server, so requests do not pass
|
|
||||||
a per-request `lora` field.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm serve Qwen/Qwen-Image-2512 \
|
|
||||||
--omni \
|
|
||||||
--port 8091 \
|
|
||||||
--lora-backend distill \
|
|
||||||
--lora-path /path/to/Qwen-Image-2512-Lightning-4steps.safetensors
|
|
||||||
```
|
|
||||||
|
|
||||||
Then send a normal generation request with the sampling settings expected by
|
|
||||||
the distilled checkpoint:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -s http://localhost:8091/v1/chat/completions \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"messages": [
|
|
||||||
{"role": "user", "content": "A Chinese female college student, around 20 years old, with a very short haircut that conveys a gentle, artistic vibe. Her hair naturally falls to partially cover her cheeks, projecting a tomboyish yet charming demeanor. She has cool-toned fair skin and delicate features, with a slightly shy yet subtly confident expression—her mouth crooked in a playful, youthful smirk. She wears an off-shoulder top, revealing one shoulder, with a well-proportioned figure. The image is framed as a close-up selfie: she dominates the foreground, while the background clearly shows her dormitory—a neatly made bed with white linens on the top bunk, a tidy study desk with organized stationery, and wooden cabinets and drawers. The photo is captured on a smartphone under soft, even ambient lighting, with natural tones, high clarity, and a bright, lively atmosphere full of youthful, everyday energy."}
|
|
||||||
],
|
|
||||||
"extra_body": {
|
|
||||||
"height": 1024,
|
|
||||||
"width": 1024,
|
|
||||||
"num_inference_steps": 4,
|
|
||||||
"true_cfg_scale": 1.0,
|
|
||||||
"seed": 42
|
|
||||||
}
|
|
||||||
}' | jq -r '.choices[0].message.content[0].image_url.url' | cut -d',' -f2 | base64 -d > qwen_image_distill_lora_output.png
|
|
||||||
```
|
|
||||||
|
|
||||||
For Wan2.2 MoE serving, pass the high-noise checkpoint first and the low-noise
|
|
||||||
checkpoint second to `--lora-path`.
|
|
||||||
|
|
||||||
!!! note "Server-side Path Requirement"
|
!!! note "Server-side Path Requirement"
|
||||||
The LoRA adapter path (`local_path`) must be readable on the **server** machine. If your client and server are on different machines, ensure the LoRA adapter is accessible via a shared mount or copied to the server.
|
The LoRA adapter path (`local_path`) must be readable on the **server** machine. If your client and server are on different machines, ensure the LoRA adapter is accessible via a shared mount or copied to the server.
|
||||||
|
|
||||||
@@ -238,7 +141,6 @@ Notes:
|
|||||||
|
|
||||||
- This route avoids runtime LoRA loading changes in vLLM-Omni when you choose to bake converted weights into a local Diffusers directory.
|
- This route avoids runtime LoRA loading changes in vLLM-Omni when you choose to bake converted weights into a local Diffusers directory.
|
||||||
- Output quality and speed depend on the replacement checkpoints and sampling params you choose.
|
- Output quality and speed depend on the replacement checkpoints and sampling params you choose.
|
||||||
- If you only need to fuse distilled LoRAs into a Wan2.2 checkpoint at load time (without the full LightX2V convert + assemble pipeline), you can instead pass them directly via `--lora-backend distill --lora-path <high>.safetensors <low>.safetensors`. See the [Distill backend](#distill-backend-fuse-distilled-lora-at-init) section above.
|
|
||||||
|
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|||||||
@@ -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 Parallel](vae_parallel.md)** | Distributes VAE decode spatially across GPUs to reduce peak VAE memory |
|
| **[VAE Parallelism](vae_parallelism.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) |
|
||||||
|
|
||||||
|
|||||||
@@ -164,12 +164,10 @@ In `DiffusionParallelConfig`:
|
|||||||
| `ulysses_degree` | int | 1 | Number of GPUs for Ulysses-SP. Uses all-to-all communication. |
|
| `ulysses_degree` | int | 1 | Number of GPUs for Ulysses-SP. Uses all-to-all communication. |
|
||||||
| `ring_degree` | int | 1 | Number of GPUs for Ring-Attention. Uses P2P ring communication. |
|
| `ring_degree` | int | 1 | Number of GPUs for Ring-Attention. Uses P2P ring communication. |
|
||||||
| `ulysses_mode` | str | `"default"` | Ulysses attention mode. Set to `"advanced_uaa"` to handle arbitrary sequence lengths and head counts without padding. |
|
| `ulysses_mode` | str | `"default"` | Ulysses attention mode. Set to `"advanced_uaa"` to handle arbitrary sequence lengths and head counts without padding. |
|
||||||
| `mask_sp_padding` | bool | `False` | When the sequence length is not divisible by the SP size, tokens are auto-padded with zeros. Set to `True` to mask those padding tokens (strict, but uses the slower varlen attention path); the default `False` leaves them unmasked, keeping the fast path with negligible numerical impact. |
|
|
||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
- Total sequence parallel size equals to `ulysses_degree × ring_degree`
|
- Total sequence parallel size equals to `ulysses_degree × ring_degree`
|
||||||
- Degrees must evenly divide the sequence length for optimal performance (or use `ulysses_mode="advanced_uaa"` for Ulysses-SP)
|
- Degrees must evenly divide the sequence length for optimal performance (or use `ulysses_mode="advanced_uaa"` for Ulysses-SP)
|
||||||
- `mask_sp_padding` is an experimental feature, currently only supported by `Wan2.2`, `Wan2.2 Vace`, `Qwen-Image`, `Flux 2`, and `HunyuanVideo 1.5`
|
|
||||||
|
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|||||||
+11
-7
@@ -1,4 +1,4 @@
|
|||||||
# VAE Parallel Guide
|
# VAE Parallelism Guide
|
||||||
|
|
||||||
|
|
||||||
## Table of Content
|
## Table of Content
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
VAE parallelism distributes VAE (Variational AutoEncoder) decode/encode work across multiple GPUs. This guide covers VAE patch/tile parallelism, which splits latent space into spatial tiles or patches, and Wan spatial-shard decode, which shards decoder feature maps along height or width.
|
VAE parallelism distributes VAE (Variational AutoEncoder) decode/encode work across multiple GPUs. This guide covers VAE patch/tile parallelism, which splits latent space into spatial tiles or patches, and Wan/QwenImage spatial-shard decode, which shards decoder feature maps along height or width.
|
||||||
|
|
||||||
This is particularly useful for:
|
This is particularly useful for:
|
||||||
- **High-resolution image generation** where VAE decode can become a memory bottleneck
|
- **High-resolution image generation** where VAE decode can become a memory bottleneck
|
||||||
@@ -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"`, `"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). |
|
| `vae_parallel_mode` | str | `"auto"` | VAE parallel decode strategy: `"auto"` (shape/model/topology-aware selection), `"tile"`, `"spatial_shard_height"`, or `"spatial_shard_width"`. Spatial sharding supports Wan and the distributed QwenImage backend. See [Spatially-Sharded Decode](#spatially-sharded-decode-wan-and-qwenimage). |
|
||||||
|
|
||||||
Additional requirements:
|
Additional requirements:
|
||||||
|
|
||||||
@@ -126,9 +126,9 @@ Additional requirements:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Spatially-Sharded Decode (Wan)
|
## Spatially-Sharded Decode (Wan and QwenImage)
|
||||||
|
|
||||||
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.
|
The default `vae_parallel_mode="auto"` selects spatial-shard decode only after a Wan or distributed QwenImage request is already large enough to enter Diffusers' tiled-decode path and the VAE uses the full DiT decode group. It splits the longer eligible spatial axis to reduce halo traffic. Direct-decode requests, partial groups, and other VAE families keep their existing path. Set `vae_parallel_mode="tile"` to force the previous behavior, or `"spatial_shard_height"` / `"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="auto", # opt in to Wan request-shape selection
|
vae_parallel_mode="auto", # default
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
@@ -157,9 +157,13 @@ vllm serve Wan-AI/Wan2.1-T2V-1.3B-Diffusers --omni \
|
|||||||
|
|
||||||
**Constraints and behavior:**
|
**Constraints and behavior:**
|
||||||
|
|
||||||
- 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.
|
- Spatial-shard decode is **decode-only** and implemented for the **Wan** VAE and the distributed **QwenImage** backend used by Qwen-Image and Krea2. Pipelines using a different VAE backend keep their existing behavior.
|
||||||
- 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.
|
- 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.
|
||||||
- The decoder's context-aware wrappers retain the direct path, so one process can alternate small/large and portrait/landscape requests safely.
|
- The decoder's context-aware wrappers retain the direct path, so one process can alternate small/large and portrait/landscape requests safely.
|
||||||
|
- Wan preserves its rank-0-only decode result, while QwenImage preserves its existing all-rank result by broadcasting after rank-0 assembly.
|
||||||
|
- Spatial sharding trades activation memory for latency as resolution grows. Use `vae_parallel_mode="tile"` when bounded VAE memory is more important than decode speed.
|
||||||
|
|
||||||
|
The automatic policy is adapted from [SGLang #28071](https://github.com/sgl-project/sglang/pull/28071) and narrowed to vLLM-Omni's parity-tested Wan and QwenImage backends.
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
# Diffusion Startup and Loading
|
|
||||||
|
|
||||||
Large diffusion models can take several minutes to load at startup. vLLM-Omni
|
|
||||||
loads safetensors shards in parallel to reduce this initialization time.
|
|
||||||
|
|
||||||
Multi-thread weight loading is enabled by default with four threads. No
|
|
||||||
configuration is needed for the default behavior.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
| Parameter | CLI flag | Default | Description |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `enable_multithread_weight_load` | `--disable-multithread-weight-load` | `True` | Pass the flag to disable multi-thread loading |
|
|
||||||
| `num_weight_load_threads` | `--num-weight-load-threads` | `4` | Number of parallel weight-loading threads |
|
|
||||||
|
|
||||||
!!! tip
|
|
||||||
|
|
||||||
The default balances startup speed and disk I/O contention. Fast NVMe
|
|
||||||
storage may benefit from more threads, while network storage or hard disks
|
|
||||||
may not.
|
|
||||||
|
|
||||||
## Online Serving
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Default: multi-thread loading with four threads
|
|
||||||
vllm serve Qwen/Qwen-Image --omni --port 8091
|
|
||||||
|
|
||||||
# Increase the thread count
|
|
||||||
vllm serve Wan-AI/Wan2.2-I2V-A14B-Diffusers --omni \
|
|
||||||
--num-weight-load-threads 8
|
|
||||||
|
|
||||||
# Disable multi-thread loading
|
|
||||||
vllm serve Qwen/Qwen-Image --omni --disable-multithread-weight-load
|
|
||||||
```
|
|
||||||
|
|
||||||
## Offline Inference
|
|
||||||
|
|
||||||
```python
|
|
||||||
from vllm_omni import Omni
|
|
||||||
|
|
||||||
# Default: multi-thread loading with four threads
|
|
||||||
omni = Omni(model="Qwen/Qwen-Image")
|
|
||||||
|
|
||||||
# Increase the thread count
|
|
||||||
omni = Omni(
|
|
||||||
model="Wan-AI/Wan2.2-I2V-A14B-Diffusers",
|
|
||||||
num_weight_load_threads=8,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Reference Benchmarks
|
|
||||||
|
|
||||||
The following measurements were collected on NVIDIA H800 hardware. Treat them
|
|
||||||
as reference results rather than a guarantee for other storage or hardware
|
|
||||||
configurations.
|
|
||||||
|
|
||||||
| Model | Sequential loading | Multi-thread loading | Speedup |
|
|
||||||
| --- | ---: | ---: | ---: |
|
|
||||||
| **Qwen/Qwen-Image** (53.7 GiB) | 168 s | 27 s | **6.2x** |
|
|
||||||
| **Wan-AI/Wan2.2-I2V-A14B-Diffusers** (64.5 GiB) | 283 s | 56 s | **5.1x** |
|
|
||||||
@@ -49,7 +49,7 @@ Parallelism methods distribute computation across GPUs without quality loss (mat
|
|||||||
|
|
||||||
| Method | Description | Best For |
|
| Method | Description | Best For |
|
||||||
|--------|-------------|----------|
|
|--------|-------------|----------|
|
||||||
| **[Multi-Thread Weight Loading](diffusion/startup_and_loading.md)** | Loads safetensors shards in parallel using a thread pool | All diffusion models; reduces startup from minutes to seconds |
|
| **[Multi-Thread Weight Loading](#multi-thread-weight-loading)** | Loads safetensors shards in parallel using a thread pool | All diffusion models; reduces startup from minutes to seconds |
|
||||||
|
|
||||||
**Note:** Some acceleration methods can be combined together for optimized performance. See [Feature Compatibility Table](#feature-compatibility) and [Feature Compatibility Tutorial](feature_compatibility.md) for detailed configuration examples.
|
**Note:** Some acceleration methods can be combined together for optimized performance. See [Feature Compatibility Table](#feature-compatibility) and [Feature Compatibility Tutorial](feature_compatibility.md) for detailed configuration examples.
|
||||||
|
|
||||||
@@ -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 Parallel](diffusion/parallelism/vae_parallel.md)** | Distributes VAE decode work across GPUs | High-resolution generation with reduced VAE memory peak |
|
| **[VAE Parallelism](diffusion/parallelism/vae_parallelism.md)** | Distributes VAE decode work across GPUs | High-resolution generation with reduced VAE memory peak |
|
||||||
|
|
||||||
### Extensions
|
### Extensions
|
||||||
|
|
||||||
@@ -100,9 +100,7 @@ The following tables show which models support each feature:
|
|||||||
|
|
||||||
- **🔀SP (Ulysses & Ring)**: Includes both Ulysses-SP and Ring-Attention methods
|
- **🔀SP (Ulysses & Ring)**: Includes both Ulysses-SP and Ring-Attention methods
|
||||||
- ✅ = Fully supported
|
- ✅ = Fully supported
|
||||||
- ✅* = Supported with the constraint listed below the table
|
|
||||||
- ❌ = Not supported
|
- ❌ = Not supported
|
||||||
- ❓ = Not verified; not recommended
|
|
||||||
|
|
||||||
> Notes:
|
> Notes:
|
||||||
|
|
||||||
@@ -121,7 +119,7 @@ The following tables show which models support each feature:
|
|||||||
| **FLUX.2-dev** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
| **FLUX.2-dev** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||||
| **GLM-Image** | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
| **GLM-Image** | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||||
| **Hidream-I1-Full** | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
| **Hidream-I1-Full** | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||||
| **HunyuanImage3** | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅* |
|
| **HunyuanImage3** | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
|
||||||
| **Krea 2** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
|
| **Krea 2** | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
|
||||||
| **LongCat-Image** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
| **LongCat-Image** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||||
| **LongCat-Image-Edit** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
| **LongCat-Image-Edit** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||||
@@ -147,7 +145,6 @@ The following tables show which models support each feature:
|
|||||||
> 2. `Tongyi-MAI/Z-Image-Turbo` and `SII-GAIR/daVinci-MagiHuman-Base-1080p` are distilled models with minimal NFEs; CFG-Parallel is not necessary.
|
> 2. `Tongyi-MAI/Z-Image-Turbo` and `SII-GAIR/daVinci-MagiHuman-Base-1080p` are distilled models with minimal NFEs; CFG-Parallel is not necessary.
|
||||||
> 3. Cosmos3 T2I uses `Cosmos3OmniDiffusersPipeline` with `modalities=["image"]`. Model-level CPU offload swaps the nested UND reasoner and GEN generator pathways; layerwise offload remains available for blockwise GEN/UND offload.
|
> 3. Cosmos3 T2I uses `Cosmos3OmniDiffusersPipeline` with `modalities=["image"]`. Model-level CPU offload swaps the nested UND reasoner and GEN generator pathways; layerwise offload remains available for blockwise GEN/UND offload.
|
||||||
> 4. Krea 2 currently supports single-GPU inference plus LoRA, Cache-DiT, HSDP, CPU/layerwise offload, and VAE-patch-parallel (decode). TP/SP/CFG-Parallel are not yet wired. The few-step distilled (Turbo) checkpoint uses `is_distilled=true` (fixed timestep shift `mu=1.15`); generate at 2048x2048 by default with `num_inference_steps≈8` and `guidance_scale=0`. The Raw checkpoint uses 1024x1024, `num_inference_steps=28`, and `guidance_scale=4.5`.
|
> 4. Krea 2 currently supports single-GPU inference plus LoRA, Cache-DiT, HSDP, CPU/layerwise offload, and VAE-patch-parallel (decode). TP/SP/CFG-Parallel are not yet wired. The few-step distilled (Turbo) checkpoint uses `is_distilled=true` (fixed timestep shift `mu=1.15`); generate at 2048x2048 by default with `num_inference_steps≈8` and `guidance_scale=0`. The Raw checkpoint uses 1024x1024, `num_inference_steps=28`, and `guidance_scale=4.5`.
|
||||||
> 5. HunyuanImage3 supports step execution. Multi-request step execution requires `TORCH_SDPA`; see [Diffusion Execution Modes](diffusion/execution_modes.md#step-execution).
|
|
||||||
|
|
||||||
### VideoGen
|
### VideoGen
|
||||||
|
|
||||||
@@ -158,16 +155,12 @@ 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) | ❌ | ❌ |
|
||||||
| **Helios** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅* |
|
| **Helios** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||||
| **HunyuanVideo-1.5 T2V I2V** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (encode/decode) | ✅ | ❌ |
|
| **HunyuanVideo-1.5 T2V I2V** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (encode/decode) | ✅ | ❌ |
|
||||||
| **DreamID-Omni** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
| **DreamID-Omni** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||||
| **Cosmos3** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (encode/decode) | ✅ | ❌ |
|
| **Cosmos3** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (encode/decode) | ✅ | ❌ |
|
||||||
| **LongCat-Video-Avatar-1.5** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
||||||
| **MiniMax-H3** | ✅ (FL2VA) | ✅ | ✅ | ❌ | ✅ (DiT/TE) | ❌ | ✅ | ✅ | ✅ (tile) | ✅ (DiT) | ❌ |
|
| **MiniMax-H3** | ✅ (FL2VA) | ✅ | ✅ | ❌ | ✅ (DiT/TE) | ❌ | ✅ | ✅ | ✅ (tile) | ✅ (DiT) | ❌ |
|
||||||
|
|
||||||
> **Step execution note:** Helios supports single-request step execution only;
|
|
||||||
> use `max_num_seqs=1`.
|
|
||||||
|
|
||||||
**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
|
||||||
@@ -196,7 +189,7 @@ The following tables show which models support each feature:
|
|||||||
| **🔀Ring-Attn** | ✅ | ✅ | ✅ | | | | | | | | | | | |
|
| **🔀Ring-Attn** | ✅ | ✅ | ✅ | | | | | | | | | | | |
|
||||||
| **🔀CFG-Parallel** | ✅ | ✅ | ✅ | ✅ | | | | | | | | | | |
|
| **🔀CFG-Parallel** | ✅ | ✅ | ✅ | ✅ | | | | | | | | | | |
|
||||||
| **🔀Tensor Parallel** | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | | | | | |
|
| **🔀Tensor Parallel** | ✅ | ✅ | ✅ | ✅ | ✅ | | | | | | | | | |
|
||||||
| **🔀HSDP** | ❓ | ❓ | ✅ | ❓ | ✅ | ❌ | | | | | | | | |
|
| **🔀HSDP** | ❓ | ❓ | ❓ | ❓ | ❓ | ❌ | | | | | | | | |
|
||||||
| **🔀Expert Parallel** | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | | | | | | | |
|
| **🔀Expert Parallel** | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | | | | | | | |
|
||||||
| **💾CPU Offloading (Layerwise)** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | | | | | |
|
| **💾CPU Offloading (Layerwise)** | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | | | | | |
|
||||||
| **💾CPU Offloading (Module-wise)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❓ | ❓ | ❌ | | | | | |
|
| **💾CPU Offloading (Module-wise)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❓ | ❓ | ❌ | | | | | |
|
||||||
@@ -207,43 +200,97 @@ The following tables show which models support each feature:
|
|||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
|
|
||||||
1. HSDP can be combined with Ulysses-SP or CFG-Parallel. Tensor Parallel
|
1. Tensor Parallel and HSDP are not compatible.
|
||||||
and HSDP are not compatible; other HSDP combinations in the table
|
|
||||||
remain unverified.
|
|
||||||
2. TeaCache and Cache-DiT are not compatible.
|
2. TeaCache and Cache-DiT are not compatible.
|
||||||
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. CPU Offloading (Layerwise) supports single-card for now.
|
||||||
Multi-device Distributed Layerwise Offload has a separate topology and
|
5. Using FP8-Quant as an example of qunatization methods.
|
||||||
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.
|
|
||||||
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).
|
||||||
|
|
||||||
|
|
||||||
## Multi-Thread Weight Loading
|
## Multi-Thread Weight Loading
|
||||||
|
|
||||||
The loading guide now lives at [Diffusion Startup and
|
Large diffusion models can take several minutes to load weights at startup (e.g., ~3 min for Qwen-Image, ~5 min for Wan2.2 I2V 14B). Multi-thread weight loading speeds up this process by loading safetensors shards in parallel using a thread pool instead of sequentially.
|
||||||
Loading](diffusion/startup_and_loading.md). This heading remains so existing
|
|
||||||
links to this section continue to work.
|
This optimization is **enabled by default** with 4 threads. No configuration is needed for the default behavior.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
| Parameter | CLI Flag | Default | Description |
|
||||||
|
|-----------|----------|---------|-------------|
|
||||||
|
| `enable_multithread_weight_load` | `--disable-multithread-weight-load` | `True` (enabled) | Pass the flag to disable multi-thread loading |
|
||||||
|
| `num_weight_load_threads` | `--num-weight-load-threads` | `4` | Number of threads for parallel weight loading |
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
The default of 4 threads balances speed and disk I/O contention. On fast NVMe storage you may benefit from more threads (e.g., 8). On HDD or network storage, the default of 4 avoids saturating I/O bandwidth.
|
||||||
|
|
||||||
|
### Online Serving
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Default (multi-thread enabled, 4 threads)
|
||||||
|
vllm serve Qwen/Qwen-Image --omni --port 8091
|
||||||
|
|
||||||
|
# Custom thread count
|
||||||
|
vllm serve Wan-AI/Wan2.2-I2V-A14B-Diffusers --omni --num-weight-load-threads 8
|
||||||
|
|
||||||
|
# Disable multi-thread loading
|
||||||
|
vllm serve Qwen/Qwen-Image --omni --disable-multithread-weight-load
|
||||||
|
```
|
||||||
|
|
||||||
|
### Offline Inference
|
||||||
|
|
||||||
|
```python
|
||||||
|
from vllm_omni import Omni
|
||||||
|
|
||||||
|
# Default (multi-thread enabled, 4 threads)
|
||||||
|
omni = Omni(model="Qwen/Qwen-Image")
|
||||||
|
|
||||||
|
# Custom thread count
|
||||||
|
omni = Omni(
|
||||||
|
model="Wan-AI/Wan2.2-I2V-A14B-Diffusers",
|
||||||
|
num_weight_load_threads=8,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Benchmarks
|
||||||
|
|
||||||
|
Measured on NVIDIA H800:
|
||||||
|
|
||||||
|
| Model | Before | After | Speedup |
|
||||||
|
|-------|--------|-------|---------|
|
||||||
|
| **Qwen/Qwen-Image** (53.7 GiB) | 168s | 27s | **6.2x** |
|
||||||
|
| **Wan-AI/Wan2.2-I2V-A14B-Diffusers** (64.5 GiB) | 283s | 56s | **5.1x** |
|
||||||
|
|
||||||
## Learn More
|
## Learn More
|
||||||
|
|
||||||
The Diffusion Acceleration navigation groups the remaining guides as follows:
|
**Cache Acceleration:**
|
||||||
|
|
||||||
| Area | Guide |
|
- **[TeaCache Configuration Guide](diffusion/cache_acceleration/teacache.md)** - Parameter tuning, performance tips, troubleshooting
|
||||||
| --- | --- |
|
- **[Cache-DiT Advanced Guide](diffusion/cache_acceleration/cache_dit.md)** - DBCache, TaylorSeer, SCM techniques and optimization
|
||||||
| Compatibility | [Feature Compatibility](feature_compatibility.md) |
|
|
||||||
| CPU offloading | [CPU Offloading](diffusion/cpu_offload.md) |
|
|
||||||
| Cache acceleration | [TeaCache](diffusion/cache_acceleration/teacache.md), [Cache-DiT](diffusion/cache_acceleration/cache_dit.md) |
|
|
||||||
| Parallelism | [Parallelism Overview](diffusion/parallelism/overview.md) |
|
|
||||||
| Attention | [Attention Backends](diffusion/attention_backends.md) |
|
|
||||||
| Compilation | [Regional Compilation](diffusion/regional_compilation.md) |
|
|
||||||
| Video extension | [Frame Interpolation](diffusion/frame_interpolation.md) |
|
|
||||||
| Startup | [Startup and Loading](diffusion/startup_and_loading.md) |
|
|
||||||
| Adapters | [LoRA](diffusion/lora.md) |
|
|
||||||
|
|
||||||
Related cross-model and runtime features are documented separately:
|
**Parallelism Methods:**
|
||||||
|
|
||||||
- [Quantization](quantization/overview.md) covers diffusion-only models,
|
- **[Parallelism Overview](diffusion/parallelism/overview.md)** - Tensor Parallelism, Sequence Parallelism, CFG Parallelism, Pipeline Parallelism, HSDP, and Expert Parallelism
|
||||||
multi-stage omni/TTS models, and multi-stage diffusion models.
|
|
||||||
- [Execution Modes and Streaming](diffusion/execution_modes.md) covers the
|
**Memory Optimization:**
|
||||||
diffusion runtime, including batching, step execution, and streaming output.
|
|
||||||
|
- **[CPU Offload Guide](diffusion/cpu_offload.md)** - Offload model components to CPU, reduce GPU memory usage
|
||||||
|
- **[VAE Parallelism Guide](diffusion/parallelism/vae_parallelism.md)** - Distribute VAE decode work across GPUs for high-resolution images and videos
|
||||||
|
- **[Quantization](quantization/overview.md)** - Quantization methods for diffusion, multi-stage omni/TTS, and multi-stage diffusion models
|
||||||
|
|
||||||
|
**Extensions:**
|
||||||
|
|
||||||
|
- **[LoRA Inference Guide](diffusion/lora.md)** - Low-Rank Adaptation for style customization and fine-tuning
|
||||||
|
- **[Frame Interpolation Guide](diffusion/frame_interpolation.md)** - Worker-side post-generation video frame interpolation for smoother motion
|
||||||
|
|
||||||
|
**Execution Modes:**
|
||||||
|
|
||||||
|
- **[Diffusion Execution Modes](diffusion/execution_modes.md)** - Configure request batching, step execution, continuous batching, and streaming output
|
||||||
|
|
||||||
|
**Startup Optimization:**
|
||||||
|
|
||||||
|
- **[Multi-Thread Weight Loading](#multi-thread-weight-loading)** - Speed up model startup by loading safetensors shards in parallel
|
||||||
|
|
||||||
|
**Advanced Topics:**
|
||||||
|
|
||||||
|
- **[Feature Compatibility](feature_compatibility.md)** - How to combine multiple features for maximum performance
|
||||||
@@ -88,9 +88,8 @@ python text_to_image.py \
|
|||||||
| `--vae-use-tiling` | flag | off | Enable VAE tiling for memory optimization |
|
| `--vae-use-tiling` | flag | off | Enable VAE tiling for memory optimization |
|
||||||
| `--cfg-parallel-size` | int | `1` | Set to `2` to enable CFG Parallel |
|
| `--cfg-parallel-size` | int | `1` | Set to `2` to enable CFG Parallel |
|
||||||
| `--enable-cpu-offload` | flag | off | Enable CPU offloading for diffusion models |
|
| `--enable-cpu-offload` | flag | off | Enable CPU offloading for diffusion models |
|
||||||
| `--lora-path` | str | — | Path to PEFT LoRA adapter folder or checkpoint file |
|
| `--lora-path` | str | — | Path to PEFT LoRA adapter folder |
|
||||||
| `--lora-scale` | float | `1.0` | Scale factor for LoRA weights |
|
| `--lora-scale` | float | `1.0` | Scale factor for LoRA weights |
|
||||||
| `--lora-backend` | str |`"peft"`| LoRA backend for loading LoRA adapters. Default: peft. Available options: peft, distill |
|
|
||||||
|
|
||||||
**NextStep-1.1 specific arguments:**
|
**NextStep-1.1 specific arguments:**
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ For backend selection and SageAttention usage, see the [Diffusion Attention Back
|
|||||||
| Model | Default Resolution | Default Frames | Default Steps | Guidance | VRAM (BF16) |
|
| Model | Default Resolution | Default Frames | Default Steps | Guidance | VRAM (BF16) |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| `Wan-AI/Wan2.2-T2V-A14B-Diffusers` | 720x1280 | 81 | 40 | 4.0 | ~60 GiB |
|
| `Wan-AI/Wan2.2-T2V-A14B-Diffusers` | 720x1280 | 81 | 40 | 4.0 | ~60 GiB |
|
||||||
| `robbyant/lingbot-video-dense-1.3b` / `robbyant/lingbot-video-moe-30b-a3b` | 192x320 | 9 | 2 | 3.0 | ~68 GiB (MoE smoke) |
|
|
||||||
| `Lightricks/LTX-2` | 512x768 | 121 | 40 | video 3.0 / audio 7.0 | Model-dependent |
|
| `Lightricks/LTX-2` | 512x768 | 121 | 40 | video 3.0 / audio 7.0 | Model-dependent |
|
||||||
| `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v` | 480x832 | 121 | 50 | 6.0 | 1×A100 80GB |
|
| `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v` | 480x832 | 121 | 50 | 6.0 | 1×A100 80GB |
|
||||||
| `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-720p_t2v` | 720x1280 | 121 | 50 | 6.0 | FP8 + VAE tiling required |
|
| `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-720p_t2v` | 720x1280 | 121 | 50 | 6.0 | FP8 + VAE tiling required |
|
||||||
@@ -36,25 +35,6 @@ python text_to_video.py \
|
|||||||
--output t2v_out.mp4
|
--output t2v_out.mp4
|
||||||
```
|
```
|
||||||
|
|
||||||
### LingBot-Video
|
|
||||||
|
|
||||||
The shared runner recognizes both official LingBot checkpoint IDs and selects
|
|
||||||
the native `LingBotVideoPipeline` automatically:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python text_to_video.py \
|
|
||||||
--model robbyant/lingbot-video-dense-1.3b \
|
|
||||||
--prompt "a robotic arm picks up a red block" \
|
|
||||||
--height 192 --width 320 --num-frames 9 --num-inference-steps 2 \
|
|
||||||
--guidance-scale 3.0 --flow-shift 3.0 --fps 24 \
|
|
||||||
--output lingbot_t2v.mp4
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `robbyant/lingbot-video-moe-30b-a3b` for the MoE checkpoint. For local
|
|
||||||
paths without `lingbot` in the directory name, add
|
|
||||||
`--model-class-name LingBotVideoPipeline`. Model-only options use
|
|
||||||
`--extra-body`, for example `'{"batch_cfg": true, "output_type": "np"}'`.
|
|
||||||
|
|
||||||
### LTX-2
|
### LTX-2
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -145,9 +125,6 @@ python text_to_video.py \
|
|||||||
- `--audio-sample-rate`: fallback audio sample rate when the pipeline returns audio.
|
- `--audio-sample-rate`: fallback audio sample rate when the pipeline returns audio.
|
||||||
- `--quantization`: quantization method (`fp8` for FP8, `gguf` for GGUF).
|
- `--quantization`: quantization method (`fp8` for FP8, `gguf` for GGUF).
|
||||||
- `--flow-shift`: scheduler flow_shift parameter.
|
- `--flow-shift`: scheduler flow_shift parameter.
|
||||||
- `--lora-path`: path to PEFT LoRA adapter folder or checkpoint file.
|
|
||||||
- `--lora-scale`: scale factor for LoRA weights.
|
|
||||||
- `--lora-backend`: backend for loading LoRA adapters. Default: peft. Available options: peft, distill.
|
|
||||||
|
|
||||||
### Wan2.2-specific
|
### Wan2.2-specific
|
||||||
|
|
||||||
|
|||||||
@@ -12,20 +12,20 @@ For the internal architecture and backend extension points, see the
|
|||||||
|
|
||||||
| Mode | Guide | Description | Methods |
|
| Mode | Guide | Description | Methods |
|
||||||
|------|-------|-------------|---------|
|
|------|-------|-------------|---------|
|
||||||
| Online quantization | [Online Quantization](online.md) | vLLM-Omni computes quantized weights and scales while loading the model. | FP8 W8A8, Int8 W8A8, BitsAndBytes W4, MXFP8 W8A8, MXFP4 W4A4 |
|
| Online quantization | [Online Quantization](online.md) | vLLM-Omni computes quantized weights and scales while loading the model. | FP8 W8A8, Int8 W8A8, MXFP8 W8A8, MXFP4 W4A4 |
|
||||||
| Runtime attention quantization | [Quantized KV Cache](quantized_kvcache.md) | vLLM-Omni dynamically quantizes eligible diffusion Flash Attention tensors during inference. | FP8 FA |
|
| Runtime attention quantization | [Quantized KV Cache](quantized_kvcache.md) | vLLM-Omni dynamically quantizes eligible diffusion Flash Attention tensors during inference. | FP8 FA |
|
||||||
| Pre-quantized checkpoints | Method-specific guides | The checkpoint or an offline quantizer provides quantized weights and scales before serving. | ModelOpt, AutoRound, msModelSlim, serialized Int8, offline MXFP8, offline MXFP4 DualScale |
|
| Pre-quantized checkpoints | Method-specific guides | The checkpoint or an offline quantizer provides quantized weights and scales before serving. | ModelOpt, GGUF, AutoRound, msModelSlim, serialized Int8, offline MXFP8, offline MXFP4 DualScale |
|
||||||
|
|
||||||
## Hardware Support
|
## Hardware Support
|
||||||
|
|
||||||
| Device | FP8 W8A8 | Int8 W8A8 | BitsAndBytes W4 | ModelOpt | MXFP8 W8A8 | MXFP4 W4A4 | AutoRound | msModelSlim |
|
| Device | FP8 W8A8 | Int8 W8A8 | ModelOpt | MXFP8 W8A8 | MXFP4 W4A4 | GGUF | AutoRound | msModelSlim |
|
||||||
|--------|----------|-----------|------------------|----------|------------|------------|-----------|-------------|
|
|--------|----------|-----------|----------|------------|------------|------|-----------|-------------|
|
||||||
| NVIDIA Blackwell GPU (SM 100+) | ✅ | ✅ | ✅ | ✅ | ⭕ | ⭕ | ✅ | ❌ |
|
| NVIDIA Blackwell GPU (SM 100+) | ✅ | ✅ | ✅ | ⭕ | ⭕ | ✅ | ✅ | ❌ |
|
||||||
| NVIDIA Ada/Hopper GPU (SM 89+) | ✅ | ✅ | ✅ | ✅ | ⭕ | ⭕ | ✅ | ❌ |
|
| NVIDIA Ada/Hopper GPU (SM 89+) | ✅ | ✅ | ✅ | ⭕ | ⭕ | ✅ | ✅ | ❌ |
|
||||||
| NVIDIA Ampere GPU (SM 80+) | ✅ | ✅ | ✅ | ⭕ | ⭕ | ⭕ | ✅ | ❌ |
|
| NVIDIA Ampere GPU (SM 80+) | ✅ | ✅ | ⭕ | ⭕ | ⭕ | ✅ | ✅ | ❌ |
|
||||||
| AMD ROCm | ⭕ | ⭕ | ❌ | ⭕ | ⭕ | ✅ | ⭕ | ❌ |
|
| AMD ROCm | ⭕ | ⭕ | ⭕ | ⭕ | ✅ | ⭕ | ⭕ | ❌ |
|
||||||
| Intel XPU | ⭕ | ⭕ | ❌ | ⭕ | ⭕ | ⭕ | ✅ | ❌ |
|
| Intel XPU | ⭕ | ⭕ | ⭕ | ⭕ | ⭕ | ⭕ | ✅ | ❌ |
|
||||||
| Ascend NPU | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
|
| Ascend NPU | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ |
|
||||||
|
|
||||||
Legend: `✅` supported, `❌` unsupported, `⭕` not verified in this
|
Legend: `✅` supported, `❌` unsupported, `⭕` not verified in this
|
||||||
guide. FP8 on Ampere may use a weight-only path where available.
|
guide. FP8 on Ampere may use a weight-only path where available.
|
||||||
@@ -43,10 +43,10 @@ otherwise.
|
|||||||
|--------|-------|------|----------------|--------|
|
|--------|-------|------|----------------|--------|
|
||||||
| FP8 W8A8 | [FP8](fp8.md) | Online W8A8 or checkpoint FP8 | Qwen-Image; Wan2.2 is not validated | Validated for Qwen-Image family and other DiT models |
|
| FP8 W8A8 | [FP8](fp8.md) | Online W8A8 or checkpoint FP8 | Qwen-Image; Wan2.2 is not validated | Validated for Qwen-Image family and other DiT models |
|
||||||
| Int8 W8A8 | [Int8](int8.md) | Online or serialized W8A8 | Qwen-Image; Wan2.2 is not validated | Validated for Qwen-Image and Z-Image |
|
| Int8 W8A8 | [Int8](int8.md) | Online or serialized W8A8 | Qwen-Image; Wan2.2 is not validated | Validated for Qwen-Image and Z-Image |
|
||||||
| BitsAndBytes W4 | [BitsAndBytes](bitsandbytes.md) | Online W4 weight-only | Z-Image-Turbo; Qwen-Image/Wan2.2 not validated | Validated for Z-Image-Turbo |
|
|
||||||
| ModelOpt | [ModelOpt](modelopt.md) | Pre-quantized FP8 checkpoints | Qwen-Image, Z-Image, FLUX.2, HunyuanImage-3.0 | Validated for ModelOpt FP8 diffusion checkpoints |
|
| ModelOpt | [ModelOpt](modelopt.md) | Pre-quantized FP8 checkpoints | Qwen-Image, Z-Image, FLUX.2, HunyuanImage-3.0 | Validated for ModelOpt FP8 diffusion checkpoints |
|
||||||
| MXFP8 W8A8 | [MXFP8](mxfp8.md) | Online W8A8 or offline pre-quantized | Wan2.2-T2V-A14B, I2V-A14B, TI2V-5B | Ascend NPU only; validated for Wan2.2 |
|
| MXFP8 W8A8 | [MXFP8](mxfp8.md) | Online W8A8 or offline pre-quantized | Wan2.2-T2V-A14B, I2V-A14B, TI2V-5B | Ascend NPU only; validated for Wan2.2 |
|
||||||
| MXFP4 W4A4 | [MXFP4](mxfp4.md) | `mxfp4`: online single-scale only; `mxfp4_dualscale`: online or offline dual-scale (offline recommended) | Wan2.2-T2V-A14B, I2V-A14B | Ascend NPU only; validated for Wan2.2 A14B cascade models; TI2V-5B not supported; offline `mxfp4_dualscale` uses calibrated `mul_scale` for best accuracy |
|
| MXFP4 W4A4 | [MXFP4](mxfp4.md) | `mxfp4`: online single-scale only; `mxfp4_dualscale`: online or offline dual-scale (offline recommended) | Wan2.2-T2V-A14B, I2V-A14B | Ascend NPU only; validated for Wan2.2 A14B cascade models; TI2V-5B not supported; offline `mxfp4_dualscale` uses calibrated `mul_scale` for best accuracy |
|
||||||
|
| GGUF | [GGUF](gguf.md) | Pre-quantized transformer weights | Qwen-Image | Validated where a model-specific GGUF adapter exists |
|
||||||
| AutoRound | [AutoRound](autoround.md) | Pre-quantized W4A16 checkpoints | FLUX.1-dev; Qwen-Image/Wan2.2 not validated | Checkpoint-driven |
|
| AutoRound | [AutoRound](autoround.md) | Pre-quantized W4A16 checkpoints | FLUX.1-dev; Qwen-Image/Wan2.2 not validated | Checkpoint-driven |
|
||||||
| msModelSlim | [msModelSlim](msmodelslim.md) | Pre-quantized Ascend checkpoints | Wan2.2 recipe; HunyuanImage-3.0 inference target | Ascend/NPU path |
|
| msModelSlim | [msModelSlim](msmodelslim.md) | Pre-quantized Ascend checkpoints | Wan2.2 recipe; HunyuanImage-3.0 inference target | Ascend/NPU path |
|
||||||
|
|
||||||
@@ -61,9 +61,9 @@ in BF16 unless the model guide explicitly adds support.
|
|||||||
|--------|-------|-------|----------------|--------|
|
|--------|-------|-------|----------------|--------|
|
||||||
| ModelOpt | [ModelOpt](modelopt.md) | Thinker or language-model checkpoint config | Qwen3-Omni thinker | ModelOpt checkpoint path |
|
| ModelOpt | [ModelOpt](modelopt.md) | Thinker or language-model checkpoint config | Qwen3-Omni thinker | ModelOpt checkpoint path |
|
||||||
| Int8 | [Int8](int8.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
| Int8 | [Int8](int8.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
||||||
| BitsAndBytes | [BitsAndBytes](bitsandbytes.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
|
||||||
| MXFP8 | [MXFP8](mxfp8.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
| MXFP8 | [MXFP8](mxfp8.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
||||||
| MXFP4 | [MXFP4](mxfp4.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
| MXFP4 | [MXFP4](mxfp4.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
||||||
|
| GGUF | [GGUF](gguf.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
||||||
| AutoRound | [AutoRound](autoround.md) | Thinker or language-model checkpoint config | Qwen2.5-Omni, Qwen3-Omni | Supported through AutoRound checkpoints |
|
| AutoRound | [AutoRound](autoround.md) | Thinker or language-model checkpoint config | Qwen2.5-Omni, Qwen3-Omni | Supported through AutoRound checkpoints |
|
||||||
| msModelSlim | [msModelSlim](msmodelslim.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
| msModelSlim | [msModelSlim](msmodelslim.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
||||||
|
|
||||||
@@ -76,10 +76,10 @@ attached to the intended stage rather than applied globally.
|
|||||||
|--------|-------|-------|----------------|--------|
|
|--------|-------|-------|----------------|--------|
|
||||||
| FP8 | [FP8](fp8.md) | Stage-specific DiT or transformer module | BAGEL, GLM-Image | Requires model-specific validation |
|
| FP8 | [FP8](fp8.md) | Stage-specific DiT or transformer module | BAGEL, GLM-Image | Requires model-specific validation |
|
||||||
| Int8 | [Int8](int8.md) | Stage-specific DiT or transformer module | BAGEL, GLM-Image | Requires model-specific validation |
|
| Int8 | [Int8](int8.md) | Stage-specific DiT or transformer module | BAGEL, GLM-Image | Requires model-specific validation |
|
||||||
| BitsAndBytes | [BitsAndBytes](bitsandbytes.md) | Stage-specific transformer or DiT module | BAGEL, GLM-Image | Not validated |
|
|
||||||
| ModelOpt | [ModelOpt](modelopt.md) | Checkpoint-defined diffusion stage | BAGEL, GLM-Image | Requires model-specific validation |
|
| ModelOpt | [ModelOpt](modelopt.md) | Checkpoint-defined diffusion stage | BAGEL, GLM-Image | Requires model-specific validation |
|
||||||
| MXFP8 | [MXFP8](mxfp8.md) | Stage-specific DiT or transformer module | BAGEL, GLM-Image | Not validated |
|
| MXFP8 | [MXFP8](mxfp8.md) | Stage-specific DiT or transformer module | BAGEL, GLM-Image | Not validated |
|
||||||
| MXFP4 | [MXFP4](mxfp4.md) | Stage-specific DiT or transformer module | BAGEL, GLM-Image | Not validated |
|
| MXFP4 | [MXFP4](mxfp4.md) | Stage-specific DiT or transformer module | BAGEL, GLM-Image | Not validated |
|
||||||
|
| GGUF | [GGUF](gguf.md) | Stage-specific transformer weights | BAGEL, GLM-Image | No validated adapter listed |
|
||||||
| AutoRound | [AutoRound](autoround.md) | Checkpoint-defined stage | BAGEL, GLM-Image | No validated checkpoint listed |
|
| AutoRound | [AutoRound](autoround.md) | Checkpoint-defined stage | BAGEL, GLM-Image | No validated checkpoint listed |
|
||||||
| msModelSlim | [msModelSlim](msmodelslim.md) | Ascend-generated stage weights | GLM-Image | Requires model-specific adaptation |
|
| msModelSlim | [msModelSlim](msmodelslim.md) | Ascend-generated stage weights | GLM-Image | Requires model-specific adaptation |
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ config = build_quant_config({
|
|||||||
|
|
||||||
| Component | Default quantized? | Notes |
|
| Component | Default quantized? | Notes |
|
||||||
|-----------|--------------------|-------|
|
|-----------|--------------------|-------|
|
||||||
| Diffusion transformer | Yes | Primary target for FP8, Int8, BitsAndBytes, ModelOpt, MXFP8, MXFP4, AutoRound, and msModelSlim |
|
| Diffusion transformer | Yes | Primary target for FP8, Int8, ModelOpt, MXFP8, MXFP4, GGUF, AutoRound, and msModelSlim |
|
||||||
| Text encoder | No | Keep BF16 unless a method-specific guide documents support |
|
| Text encoder | No | Keep BF16 unless a method-specific guide documents support |
|
||||||
| VAE | No | Keep BF16; storage-only paths are method-specific |
|
| VAE | No | Keep BF16; storage-only paths are method-specific |
|
||||||
| Scheduler/tokenizer | No | Loaded from the base model repository |
|
| Scheduler/tokenizer | No | Loaded from the base model repository |
|
||||||
@@ -139,8 +139,8 @@ from vllm_omni.quantization import build_quant_config
|
|||||||
|
|
||||||
build_quant_config("fp8")
|
build_quant_config("fp8")
|
||||||
build_quant_config({"method": "fp8", "activation_scheme": "static"})
|
build_quant_config({"method": "fp8", "activation_scheme": "static"})
|
||||||
build_quant_config("bitsandbytes")
|
|
||||||
build_quant_config("auto-round", bits=4, group_size=128)
|
build_quant_config("auto-round", bits=4, group_size=128)
|
||||||
|
build_quant_config({"method": "gguf", "gguf_model": "/path/to/model.gguf"})
|
||||||
build_quant_config({"transformer": {"method": "fp8"}, "vae": None})
|
build_quant_config({"transformer": {"method": "fp8"}, "vae": None})
|
||||||
build_quant_config(None)
|
build_quant_config(None)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ def build_sampling_params() -> list[SamplingParams]:
|
|||||||
|
|
||||||
|
|
||||||
def save_stage_output(stage_outputs, output_dir: str) -> None:
|
def save_stage_output(stage_outputs, output_dir: str) -> None:
|
||||||
output = stage_outputs
|
output = stage_outputs.request_output
|
||||||
request_id = output.request_id
|
request_id = output.request_id
|
||||||
if stage_outputs.final_output_type == "text":
|
if stage_outputs.final_output_type == "text":
|
||||||
text_output = output.outputs[0].text
|
text_output = output.outputs[0].text
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ def main(args):
|
|||||||
|
|
||||||
omni = Omni(
|
omni = Omni(
|
||||||
model=args.model_name,
|
model=args.model_name,
|
||||||
deploy_config=args.deploy_config,
|
stage_configs_path=args.stage_configs_path,
|
||||||
log_stats=args.log_stats,
|
log_stats=args.log_stats,
|
||||||
stage_init_timeout=args.stage_init_timeout,
|
stage_init_timeout=args.stage_init_timeout,
|
||||||
batch_timeout=args.batch_timeout,
|
batch_timeout=args.batch_timeout,
|
||||||
@@ -104,7 +104,7 @@ def main(args):
|
|||||||
os.makedirs(output_dir, exist_ok=True)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
for stage_outputs in omni_outputs:
|
for stage_outputs in omni_outputs:
|
||||||
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
|
||||||
@@ -167,10 +167,10 @@ def parse_args():
|
|||||||
help="Sampling rate for audio loading (default: 16000).",
|
help="Sampling rate for audio loading (default: 16000).",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--deploy-config",
|
"--stage-configs-path",
|
||||||
type=str,
|
type=str,
|
||||||
default=None,
|
default=None,
|
||||||
help="Path to a deploy config YAML file.",
|
help="Path to stage configs YAML file.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--log-stats",
|
"--log-stats",
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ async def main():
|
|||||||
if not outputs:
|
if not outputs:
|
||||||
raise ValueError("No output produced from omni.generate()")
|
raise ValueError("No output produced from omni.generate()")
|
||||||
|
|
||||||
first_out = outputs[0]
|
first_out = outputs[0].request_output
|
||||||
req_out: OmniRequestOutput = first_out
|
req_out: OmniRequestOutput = first_out
|
||||||
|
|
||||||
# Verify trajectory data (from custom pipeline)
|
# Verify trajectory data (from custom pipeline)
|
||||||
|
|||||||
@@ -741,7 +741,7 @@ def make_mmu_prompt(
|
|||||||
|
|
||||||
def iter_mm_outputs(outputs: list[Any]):
|
def iter_mm_outputs(outputs: list[Any]):
|
||||||
for omni_out in outputs:
|
for omni_out in outputs:
|
||||||
req_out = omni_out
|
req_out = getattr(omni_out, "request_output", None)
|
||||||
req_list = req_out if isinstance(req_out, list) else [req_out]
|
req_list = req_out if isinstance(req_out, list) else [req_out]
|
||||||
for item in req_list:
|
for item in req_list:
|
||||||
if item is None:
|
if item is None:
|
||||||
@@ -876,7 +876,7 @@ def parse_args(repo_root: Path) -> argparse.Namespace:
|
|||||||
parser.add_argument("--task", type=str, required=True, choices=TASK_CHOICES)
|
parser.add_argument("--task", type=str, required=True, choices=TASK_CHOICES)
|
||||||
parser.add_argument("--model", type=str, required=True, help="HF repo id or local model directory.")
|
parser.add_argument("--model", type=str, required=True, help="HF repo id or local model directory.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--deploy-config",
|
"--stage-config-path",
|
||||||
type=str,
|
type=str,
|
||||||
default=str(repo_root / "vllm_omni/deploy/dynin_omni.yaml"),
|
default=str(repo_root / "vllm_omni/deploy/dynin_omni.yaml"),
|
||||||
help="Path to deploy config yaml.",
|
help="Path to deploy config yaml.",
|
||||||
@@ -1394,8 +1394,8 @@ def main() -> None:
|
|||||||
|
|
||||||
from vllm_omni.entrypoints.omni import Omni
|
from vllm_omni.entrypoints.omni import Omni
|
||||||
|
|
||||||
deploy_config_path = str(Path(args.deploy_config).expanduser())
|
stage_config_path = str(Path(args.stage_config_path).expanduser())
|
||||||
omni = Omni(model=model_source, deploy_config=deploy_config_path, dtype=args.dtype)
|
omni = Omni(model=model_source, stage_configs_path=stage_config_path, dtype=args.dtype)
|
||||||
sampling_params_list = [
|
sampling_params_list = [
|
||||||
SamplingParams(max_tokens=int(args.max_tokens_per_stage), temperature=0.0, top_p=1.0, detokenize=False)
|
SamplingParams(max_tokens=int(args.max_tokens_per_stage), temperature=0.0, top_p=1.0, detokenize=False)
|
||||||
for _ in range(omni.num_stages)
|
for _ in range(omni.num_stages)
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ def parse_args():
|
|||||||
)
|
)
|
||||||
parser.add_argument("--sys-type", type=str, default=None, help="Override system prompt type.")
|
parser.add_argument("--sys-type", type=str, default=None, help="Override system prompt type.")
|
||||||
parser.add_argument("--deploy-config", type=str, default=None, help="Custom deploy YAML path.")
|
parser.add_argument("--deploy-config", type=str, default=None, help="Custom deploy YAML path.")
|
||||||
|
parser.add_argument("--stage-configs-path", type=str, default=None, help="Custom legacy stage config YAML path.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--stream",
|
"--stream",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -145,7 +146,13 @@ def main():
|
|||||||
else:
|
else:
|
||||||
bot_task = args.bot_task
|
bot_task = args.bot_task
|
||||||
|
|
||||||
deploy_config = args.deploy_config or _MODALITY_DEFAULT_DEPLOY_CONFIG[args.modality]
|
if args.deploy_config is not None and args.stage_configs_path is not None:
|
||||||
|
raise ValueError("--deploy-config and --stage-configs-path are mutually exclusive.")
|
||||||
|
|
||||||
|
deploy_config = args.deploy_config
|
||||||
|
stage_configs_path = args.stage_configs_path
|
||||||
|
if deploy_config is None and stage_configs_path is None:
|
||||||
|
deploy_config = _MODALITY_DEFAULT_DEPLOY_CONFIG[args.modality]
|
||||||
|
|
||||||
omni_kwargs = {
|
omni_kwargs = {
|
||||||
"model": args.model,
|
"model": args.model,
|
||||||
@@ -161,7 +168,10 @@ def main():
|
|||||||
|
|
||||||
if additional_config is not None:
|
if additional_config is not None:
|
||||||
omni_kwargs["additional_config"] = additional_config
|
omni_kwargs["additional_config"] = additional_config
|
||||||
omni_kwargs["deploy_config"] = deploy_config
|
if deploy_config is not None:
|
||||||
|
omni_kwargs["deploy_config"] = deploy_config
|
||||||
|
else:
|
||||||
|
omni_kwargs["stage_configs_path"] = stage_configs_path
|
||||||
|
|
||||||
omni = Omni(**omni_kwargs)
|
omni = Omni(**omni_kwargs)
|
||||||
|
|
||||||
@@ -262,7 +272,10 @@ def main():
|
|||||||
print(f" Modality: {args.modality}")
|
print(f" Modality: {args.modality}")
|
||||||
print(f" Prompt task: {task}")
|
print(f" Prompt task: {task}")
|
||||||
print(f" Bot task: {bot_task}")
|
print(f" Bot task: {bot_task}")
|
||||||
print(f" Deploy config: {deploy_config}")
|
if deploy_config is not None:
|
||||||
|
print(f" Deploy config: {deploy_config}")
|
||||||
|
else:
|
||||||
|
print(f" Stage config: {stage_configs_path}")
|
||||||
print(f" Num stages: {omni.num_stages}")
|
print(f" Num stages: {omni.num_stages}")
|
||||||
if args.modality in ("text2img", "img2img"):
|
if args.modality in ("text2img", "img2img"):
|
||||||
print(f" Inference steps: {args.steps}")
|
print(f" Inference steps: {args.steps}")
|
||||||
@@ -290,7 +303,7 @@ def main():
|
|||||||
)
|
)
|
||||||
img_idx = 0
|
img_idx = 0
|
||||||
for req_output in omni_outputs:
|
for req_output in omni_outputs:
|
||||||
ro = req_output
|
ro = getattr(req_output, "request_output", None)
|
||||||
stage_id = getattr(req_output, "stage_id", None)
|
stage_id = getattr(req_output, "stage_id", None)
|
||||||
|
|
||||||
# AR stage text — each CompletionOutput.text is already a delta when
|
# AR stage text — each CompletionOutput.text is already a delta when
|
||||||
|
|||||||
@@ -621,7 +621,7 @@ def main():
|
|||||||
images = getattr(output, "images", None)
|
images = getattr(output, "images", None)
|
||||||
if images:
|
if images:
|
||||||
break
|
break
|
||||||
req_out = output
|
req_out = getattr(output, "request_output", None)
|
||||||
images = getattr(req_out, "images", None) if req_out is not None else None
|
images = getattr(req_out, "images", None) if req_out is not None else None
|
||||||
if images:
|
if images:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -60,12 +60,8 @@ from vllm_omni.diffusion.data import DiffusionParallelConfig
|
|||||||
from vllm_omni.diffusion.utils.param_utils import apply_declared_extra_args
|
from vllm_omni.diffusion.utils.param_utils import apply_declared_extra_args
|
||||||
from vllm_omni.entrypoints.omni import Omni
|
from vllm_omni.entrypoints.omni import Omni
|
||||||
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
||||||
from vllm_omni.lora.request import LoRARequest
|
|
||||||
from vllm_omni.lora.utils import stable_lora_int_id
|
|
||||||
from vllm_omni.model_extras import (
|
|
||||||
build_image_to_video_prompt as build_model_image_to_video_prompt,
|
|
||||||
)
|
|
||||||
from vllm_omni.model_extras import (
|
from vllm_omni.model_extras import (
|
||||||
|
build_image_to_video_prompt,
|
||||||
get_extra_body_params,
|
get_extra_body_params,
|
||||||
get_model_class_name,
|
get_model_class_name,
|
||||||
)
|
)
|
||||||
@@ -87,22 +83,6 @@ def parse_json_object(value: str, flag_name: str = "argument") -> dict[str, Any]
|
|||||||
parse_profiler_config = functools.partial(parse_json_object, flag_name="--profiler-config")
|
parse_profiler_config = functools.partial(parse_json_object, flag_name="--profiler-config")
|
||||||
|
|
||||||
|
|
||||||
def build_image_to_video_prompt(
|
|
||||||
prompt: str,
|
|
||||||
negative_prompt: str | None,
|
|
||||||
media_inputs: dict[str, Any],
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
"""Build the canonical request envelope for the shared I2V example."""
|
|
||||||
result: dict[str, Any] = {
|
|
||||||
"prompt": prompt,
|
|
||||||
"modalities": ["video"],
|
|
||||||
"multi_modal_data": media_inputs,
|
|
||||||
}
|
|
||||||
if negative_prompt is not None:
|
|
||||||
result["negative_prompt"] = negative_prompt
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args() -> argparse.Namespace:
|
def parse_args() -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description=(
|
description=(
|
||||||
@@ -314,30 +294,6 @@ def parse_args() -> argparse.Namespace:
|
|||||||
default=None,
|
default=None,
|
||||||
help='JSON profiler config for torch/cuda profiling, e.g. \'{"profiler":"torch","torch_profiler_dir":"./perf"}\'.',
|
help='JSON profiler config for torch/cuda profiling, e.g. \'{"profiler":"torch","torch_profiler_dir":"./perf"}\'.',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--lora-path",
|
|
||||||
type=str,
|
|
||||||
nargs="+",
|
|
||||||
default=None,
|
|
||||||
help="Path to a LoRA adapter folder (PEFT format) or concrete LoRA checkpoint files. "
|
|
||||||
"For Wan2.2 MoE models, pass the high-noise checkpoint first and the low-noise checkpoint second.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--lora-scale",
|
|
||||||
type=float,
|
|
||||||
default=1.0,
|
|
||||||
help="Scale factor for PEFT LoRA weights (default: 1.0).",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--lora-backend",
|
|
||||||
type=str,
|
|
||||||
default="peft",
|
|
||||||
choices=["peft", "distill"],
|
|
||||||
help=(
|
|
||||||
"LoRA backend. 'peft' loads a PEFT-format adapter for request-time activation; "
|
|
||||||
"'distill' fuses one or more concrete LoRA checkpoint files at initialization."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--extra-body",
|
"--extra-body",
|
||||||
type=functools.partial(parse_json_object, flag_name="--extra-body"),
|
type=functools.partial(parse_json_object, flag_name="--extra-body"),
|
||||||
@@ -531,12 +487,6 @@ def main():
|
|||||||
omni_kwargs["flow_shift"] = flow_shift
|
omni_kwargs["flow_shift"] = flow_shift
|
||||||
if args.quantization is not None:
|
if args.quantization is not None:
|
||||||
omni_kwargs["quantization"] = args.quantization
|
omni_kwargs["quantization"] = args.quantization
|
||||||
if args.lora_path is not None:
|
|
||||||
lora_path = args.lora_path
|
|
||||||
if len(lora_path) == 1:
|
|
||||||
lora_path = lora_path[0]
|
|
||||||
omni_kwargs["lora_path"] = lora_path
|
|
||||||
omni_kwargs["lora_backend"] = args.lora_backend
|
|
||||||
# Cosmos3 loads its (gated) guardrail models at build time, so the guardrails
|
# Cosmos3 loads its (gated) guardrail models at build time, so the guardrails
|
||||||
# gate is an engine-level config (offline analog of the server's --no-guardrails).
|
# gate is an engine-level config (offline analog of the server's --no-guardrails).
|
||||||
if args.extra_body and "guardrails" in args.extra_body:
|
if args.extra_body and "guardrails" in args.extra_body:
|
||||||
@@ -567,39 +517,19 @@ def main():
|
|||||||
print(f" Video size: {width}x{height}")
|
print(f" Video size: {width}x{height}")
|
||||||
print(f"{'=' * 60}\n")
|
print(f"{'=' * 60}\n")
|
||||||
|
|
||||||
lora_request = None
|
|
||||||
if args.lora_path and args.lora_backend == "peft":
|
|
||||||
if len(args.lora_path) != 1:
|
|
||||||
raise ValueError("Only one LoRA path is expected for PEFT backend.")
|
|
||||||
|
|
||||||
lora_path = args.lora_path[0]
|
|
||||||
lora_request = LoRARequest(
|
|
||||||
lora_name=Path(lora_path).stem,
|
|
||||||
lora_int_id=stable_lora_int_id(lora_path),
|
|
||||||
lora_path=lora_path,
|
|
||||||
)
|
|
||||||
|
|
||||||
negative_prompt = args.negative_prompt
|
negative_prompt = args.negative_prompt
|
||||||
if negative_prompt is None and not is_ltx2:
|
if negative_prompt is None and not is_ltx2:
|
||||||
# Preserve the historical empty-prompt behavior for non-LTX examples.
|
# Preserve the historical empty-prompt behavior for non-LTX examples.
|
||||||
negative_prompt = ""
|
negative_prompt = ""
|
||||||
prompt_dict = build_image_to_video_prompt(
|
prompt_dict = build_image_to_video_prompt(
|
||||||
|
model_class_name=model_class_name,
|
||||||
prompt=args.prompt,
|
prompt=args.prompt,
|
||||||
negative_prompt=negative_prompt,
|
negative_prompt=negative_prompt,
|
||||||
media_inputs=media_inputs,
|
media_inputs=media_inputs,
|
||||||
)
|
|
||||||
prompt_dict = build_model_image_to_video_prompt(
|
|
||||||
model_class_name=model_class_name,
|
|
||||||
prompt=prompt_dict,
|
|
||||||
height=height,
|
height=height,
|
||||||
width=width,
|
width=width,
|
||||||
num_frames=num_frames,
|
num_frames=num_frames,
|
||||||
)
|
)
|
||||||
sampling_extra_args = {"sample_solver": args.sample_solver}
|
|
||||||
if lora_request is not None:
|
|
||||||
sampling_extra_args["lora_request"] = lora_request
|
|
||||||
sampling_extra_args["lora_scale"] = args.lora_scale
|
|
||||||
|
|
||||||
sampling_params = OmniDiffusionSamplingParams(
|
sampling_params = OmniDiffusionSamplingParams(
|
||||||
height=height,
|
height=height,
|
||||||
width=width,
|
width=width,
|
||||||
@@ -610,7 +540,7 @@ def main():
|
|||||||
num_inference_steps=num_inference_steps,
|
num_inference_steps=num_inference_steps,
|
||||||
num_frames=num_frames,
|
num_frames=num_frames,
|
||||||
frame_rate=frame_rate,
|
frame_rate=frame_rate,
|
||||||
extra_args=sampling_extra_args,
|
extra_args={"sample_solver": args.sample_solver},
|
||||||
)
|
)
|
||||||
if flow_shift is not None:
|
if flow_shift is not None:
|
||||||
sampling_params.extra_args["flow_shift"] = flow_shift
|
sampling_params.extra_args["flow_shift"] = flow_shift
|
||||||
@@ -646,8 +576,8 @@ def main():
|
|||||||
if frames.multimodal_output and "audio" in frames.multimodal_output:
|
if frames.multimodal_output and "audio" in frames.multimodal_output:
|
||||||
audio = frames.multimodal_output["audio"]
|
audio = frames.multimodal_output["audio"]
|
||||||
audio_sample_rate = frames.multimodal_output.get("audio_sample_rate", audio_sample_rate)
|
audio_sample_rate = frames.multimodal_output.get("audio_sample_rate", audio_sample_rate)
|
||||||
if frames.is_pipeline_output and frames is not None:
|
if frames.is_pipeline_output and frames.request_output is not None:
|
||||||
inner_output = frames
|
inner_output = frames.request_output
|
||||||
if isinstance(inner_output, OmniRequestOutput):
|
if isinstance(inner_output, OmniRequestOutput):
|
||||||
if inner_output.multimodal_output and "audio" in inner_output.multimodal_output:
|
if inner_output.multimodal_output and "audio" in inner_output.multimodal_output:
|
||||||
audio = inner_output.multimodal_output["audio"]
|
audio = inner_output.multimodal_output["audio"]
|
||||||
|
|||||||
@@ -1,319 +0,0 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
import wave
|
|
||||||
from functools import lru_cache
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import imageio
|
|
||||||
import numpy as np
|
|
||||||
import torch
|
|
||||||
|
|
||||||
from vllm_omni.diffusion.data import DiffusionParallelConfig
|
|
||||||
from vllm_omni.diffusion.models.longcat_video.pipeline_longcat_video_avatar import (
|
|
||||||
prepare_longcat_video_avatar_model_for_omni,
|
|
||||||
)
|
|
||||||
from vllm_omni.entrypoints.omni import Omni
|
|
||||||
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
|
||||||
from vllm_omni.outputs import OmniRequestOutput
|
|
||||||
from vllm_omni.platforms import current_omni_platform
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
|
||||||
parser = argparse.ArgumentParser(description="Generate LongCat-Video-Avatar A2V/AI2V video.")
|
|
||||||
parser.add_argument("--model", default="meituan-longcat/LongCat-Video-Avatar-1.5")
|
|
||||||
parser.add_argument("--base-model-dir", default=None, help="Optional local LongCat-Video base model directory.")
|
|
||||||
parser.add_argument("--stage", choices=["at2v", "ai2v"], default="at2v")
|
|
||||||
parser.add_argument("--prompt", default="A person speaks calmly while facing the camera.")
|
|
||||||
parser.add_argument("--negative-prompt", default="low quality, blurry, watermark, text")
|
|
||||||
parser.add_argument("--audio", default=None, help="Input speech audio path.")
|
|
||||||
parser.add_argument("--image", default=None, help="Input reference image path for AI2V.")
|
|
||||||
parser.add_argument("--input-json", default=None, help="Official LongCat Avatar JSON case for AI2V.")
|
|
||||||
parser.add_argument("--output", default="longcat_avatar_output.mp4")
|
|
||||||
parser.add_argument("--resolution", choices=["480p", "720p"], default="480p")
|
|
||||||
parser.add_argument("--num-frames", type=int, default=93)
|
|
||||||
parser.add_argument("--num-segments", default="1", help="Number of AVC segments, or 'auto' to cover full audio.")
|
|
||||||
parser.add_argument("--num-cond-frames", type=int, default=13)
|
|
||||||
parser.add_argument("--ref-img-index", type=int, default=10)
|
|
||||||
parser.add_argument("--mask-frame-range", type=int, default=3)
|
|
||||||
parser.add_argument("--fps", type=int, default=25)
|
|
||||||
parser.add_argument("--num-inference-steps", type=int, default=8)
|
|
||||||
parser.add_argument("--seed", type=int, default=42)
|
|
||||||
parser.add_argument("--use-distill", action=argparse.BooleanOptionalAction, default=True)
|
|
||||||
parser.add_argument("--use-int8", action=argparse.BooleanOptionalAction, default=True)
|
|
||||||
parser.add_argument(
|
|
||||||
"--build-components-on-gpu",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
help="Build large Avatar components directly on GPU for faster startup. Requires more peak VRAM.",
|
|
||||||
)
|
|
||||||
parser.add_argument("--use-kv-cache", action=argparse.BooleanOptionalAction, default=True)
|
|
||||||
parser.add_argument("--offload-kv-cache", action=argparse.BooleanOptionalAction, default=False)
|
|
||||||
parser.add_argument("--enforce-eager", action="store_true")
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def _extract_frames(output: Any):
|
|
||||||
if isinstance(output, list):
|
|
||||||
output = output[0] if output else None
|
|
||||||
if isinstance(output, OmniRequestOutput):
|
|
||||||
if not output.images:
|
|
||||||
raise ValueError("No video frames found in OmniRequestOutput.")
|
|
||||||
frames = output.images[0] if len(output.images) == 1 else output.images
|
|
||||||
if isinstance(frames, tuple) and len(frames) == 2:
|
|
||||||
frames = frames[0]
|
|
||||||
if isinstance(frames, dict):
|
|
||||||
frames = frames.get("frames") or frames.get("video")
|
|
||||||
else:
|
|
||||||
frames = output
|
|
||||||
if frames is None:
|
|
||||||
raise ValueError("No video frames found in output.")
|
|
||||||
return frames
|
|
||||||
|
|
||||||
|
|
||||||
def _frames_to_uint8(frames):
|
|
||||||
frames_np = np.asarray([np.asarray(frame) for frame in frames])
|
|
||||||
if frames_np.ndim == 5:
|
|
||||||
frames_np = frames_np[0]
|
|
||||||
if np.issubdtype(frames_np.dtype, np.floating):
|
|
||||||
frames_np = (np.clip(frames_np, 0.0, 1.0) * 255).round()
|
|
||||||
return np.clip(frames_np, 0, 255).astype(np.uint8)
|
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1)
|
|
||||||
def _ffmpeg_exe() -> str:
|
|
||||||
try:
|
|
||||||
import imageio_ffmpeg
|
|
||||||
|
|
||||||
return imageio_ffmpeg.get_ffmpeg_exe()
|
|
||||||
except Exception as exc:
|
|
||||||
raise RuntimeError("LongCat Avatar example requires the Python imageio[ffmpeg] dependency.") from exc
|
|
||||||
|
|
||||||
|
|
||||||
def _wav_duration(path: Path) -> float:
|
|
||||||
with wave.open(str(path), "rb") as audio:
|
|
||||||
return audio.getnframes() / float(audio.getframerate())
|
|
||||||
|
|
||||||
|
|
||||||
def _save_video_with_audio(frames, output_path: Path, audio_path: str, fps: int) -> None:
|
|
||||||
frames_np = _frames_to_uint8(frames)
|
|
||||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with tempfile.TemporaryDirectory(prefix="longcat_avatar_export_") as tmp:
|
|
||||||
tmp_dir = Path(tmp)
|
|
||||||
temp_video = tmp_dir / "video.mp4"
|
|
||||||
crop_audio = tmp_dir / "audio.wav"
|
|
||||||
crop_video = tmp_dir / "video_cropped.mp4"
|
|
||||||
writer = imageio.get_writer(str(temp_video), fps=fps, quality=5)
|
|
||||||
try:
|
|
||||||
for frame in frames_np:
|
|
||||||
writer.append_data(frame)
|
|
||||||
finally:
|
|
||||||
writer.close()
|
|
||||||
|
|
||||||
duration = len(frames_np) / fps
|
|
||||||
ffmpeg = _ffmpeg_exe()
|
|
||||||
subprocess.run([ffmpeg, "-y", "-i", audio_path, "-t", f"{duration}", str(crop_audio)], check=True)
|
|
||||||
audio_duration = _wav_duration(crop_audio)
|
|
||||||
subprocess.run(
|
|
||||||
[
|
|
||||||
ffmpeg,
|
|
||||||
"-y",
|
|
||||||
"-i",
|
|
||||||
str(temp_video),
|
|
||||||
"-t",
|
|
||||||
f"{audio_duration}",
|
|
||||||
"-c:v",
|
|
||||||
"copy",
|
|
||||||
str(crop_video),
|
|
||||||
],
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
subprocess.run(
|
|
||||||
[
|
|
||||||
ffmpeg,
|
|
||||||
"-y",
|
|
||||||
"-i",
|
|
||||||
str(crop_video),
|
|
||||||
"-i",
|
|
||||||
str(crop_audio),
|
|
||||||
"-c:v",
|
|
||||||
"libx264",
|
|
||||||
"-c:a",
|
|
||||||
"aac",
|
|
||||||
"-shortest",
|
|
||||||
str(output_path),
|
|
||||||
],
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _infer_asset_root_from_input_json(input_json: Path) -> Path:
|
|
||||||
resolved = input_json.resolve()
|
|
||||||
parts = resolved.parts
|
|
||||||
if "assets" not in parts:
|
|
||||||
return resolved.parent
|
|
||||||
asset_idx = parts.index("assets")
|
|
||||||
return Path(*parts[:asset_idx])
|
|
||||||
|
|
||||||
|
|
||||||
def _resolve_case_path(path: str, asset_root: Path) -> str:
|
|
||||||
raw_path = Path(path)
|
|
||||||
if raw_path.is_absolute():
|
|
||||||
return str(raw_path)
|
|
||||||
candidate = asset_root / raw_path
|
|
||||||
return str(candidate if candidate.exists() else raw_path)
|
|
||||||
|
|
||||||
|
|
||||||
def _speaker_sort_key(name: str) -> tuple[int, str]:
|
|
||||||
match = re.search(r"(\d+)$", str(name))
|
|
||||||
return (int(match.group(1)) if match else 10_000, str(name))
|
|
||||||
|
|
||||||
|
|
||||||
def _load_input_case(input_json: str) -> tuple[dict[str, Any], Path]:
|
|
||||||
input_json_path = Path(input_json)
|
|
||||||
with input_json_path.open("r", encoding="utf-8") as f:
|
|
||||||
case = json.load(f)
|
|
||||||
return case, _infer_asset_root_from_input_json(input_json_path)
|
|
||||||
|
|
||||||
|
|
||||||
def _create_merged_audio_for_case(
|
|
||||||
audio_paths: dict[str, str],
|
|
||||||
audio_type: str,
|
|
||||||
output_path: Path,
|
|
||||||
) -> str:
|
|
||||||
sorted_paths = [path for _, path in sorted(audio_paths.items(), key=lambda item: _speaker_sort_key(str(item[0])))]
|
|
||||||
if len(sorted_paths) == 1:
|
|
||||||
return sorted_paths[0]
|
|
||||||
inputs = [arg for path in sorted_paths for arg in ("-i", path)]
|
|
||||||
audio_type = audio_type.lower()
|
|
||||||
if audio_type == "para":
|
|
||||||
filter_complex = f"amix=inputs={len(sorted_paths)}:duration=longest:normalize=0"
|
|
||||||
elif audio_type == "add":
|
|
||||||
filter_complex = "".join(f"[{idx}:a]" for idx in range(len(sorted_paths)))
|
|
||||||
filter_complex += f"concat=n={len(sorted_paths)}:v=0:a=1"
|
|
||||||
else:
|
|
||||||
raise NotImplementedError(f"Unsupported LongCat Avatar audio_type {audio_type!r}.")
|
|
||||||
subprocess.run([_ffmpeg_exe(), "-y", *inputs, "-filter_complex", filter_complex, str(output_path)], check=True)
|
|
||||||
return str(output_path)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
args = parse_args()
|
|
||||||
input_case = None
|
|
||||||
input_asset_root = None
|
|
||||||
audio_for_mux = args.audio
|
|
||||||
if args.input_json:
|
|
||||||
input_case, input_asset_root = _load_input_case(args.input_json)
|
|
||||||
args.stage = "ai2v"
|
|
||||||
args.prompt = input_case.get("prompt") or args.prompt
|
|
||||||
args.image = _resolve_case_path(input_case["cond_image"], input_asset_root)
|
|
||||||
args.audio = None
|
|
||||||
if not args.input_json and not args.audio:
|
|
||||||
raise ValueError("--audio is required unless --input-json is provided.")
|
|
||||||
if args.stage == "ai2v" and not args.image:
|
|
||||||
raise ValueError("--image is required when --stage=ai2v.")
|
|
||||||
model_for_omni = prepare_longcat_video_avatar_model_for_omni(args.model, args.use_int8)
|
|
||||||
|
|
||||||
additional_config = {
|
|
||||||
"model_type": "avatar-v1.5",
|
|
||||||
"resolution": args.resolution,
|
|
||||||
"use_distill": args.use_distill,
|
|
||||||
"use_int8": args.use_int8,
|
|
||||||
"build_components_on_gpu": args.build_components_on_gpu,
|
|
||||||
}
|
|
||||||
if args.base_model_dir:
|
|
||||||
additional_config["base_model_dir"] = args.base_model_dir
|
|
||||||
|
|
||||||
omni = Omni(
|
|
||||||
model=model_for_omni,
|
|
||||||
model_class_name="LongCatVideoAvatarPipeline",
|
|
||||||
dtype="bfloat16",
|
|
||||||
enforce_eager=args.enforce_eager,
|
|
||||||
additional_config=additional_config,
|
|
||||||
parallel_config=DiffusionParallelConfig(
|
|
||||||
ulysses_degree=1,
|
|
||||||
ring_degree=1,
|
|
||||||
cfg_parallel_size=1,
|
|
||||||
tensor_parallel_size=1,
|
|
||||||
vae_patch_parallel_size=1,
|
|
||||||
pipeline_parallel_size=1,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
audio_input: str | dict[str, str] | None = args.audio
|
|
||||||
additional_information = {"stage": args.stage, "resolution": args.resolution}
|
|
||||||
if input_case is not None and input_asset_root is not None:
|
|
||||||
audio_input = {
|
|
||||||
speaker: _resolve_case_path(path, input_asset_root)
|
|
||||||
for speaker, path in sorted(
|
|
||||||
input_case["cond_audio"].items(),
|
|
||||||
key=lambda item: _speaker_sort_key(str(item[0])),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
additional_information.update(
|
|
||||||
{
|
|
||||||
"input_json": args.input_json,
|
|
||||||
"audio_type": input_case.get("audio_type", "para"),
|
|
||||||
"bbox": input_case.get("bbox", {}),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
prompt = {
|
|
||||||
"prompt": args.prompt,
|
|
||||||
"negative_prompt": args.negative_prompt,
|
|
||||||
"multi_modal_data": {"audio": audio_input},
|
|
||||||
"additional_information": additional_information,
|
|
||||||
}
|
|
||||||
if args.stage == "ai2v":
|
|
||||||
prompt["multi_modal_data"]["image"] = args.image
|
|
||||||
|
|
||||||
try:
|
|
||||||
output = omni.generate(
|
|
||||||
prompt,
|
|
||||||
OmniDiffusionSamplingParams(
|
|
||||||
generator=torch.Generator(device=current_omni_platform.device_type).manual_seed(args.seed),
|
|
||||||
seed=args.seed,
|
|
||||||
guidance_scale=1.0 if args.use_distill else 4.0,
|
|
||||||
guidance_scale_2=1.0 if args.use_distill else 4.0,
|
|
||||||
num_inference_steps=args.num_inference_steps,
|
|
||||||
num_frames=args.num_frames,
|
|
||||||
fps=args.fps,
|
|
||||||
extra_args={
|
|
||||||
"stage": args.stage,
|
|
||||||
"resolution": args.resolution,
|
|
||||||
"save_fps": args.fps,
|
|
||||||
"use_distill": args.use_distill,
|
|
||||||
"use_int8": args.use_int8,
|
|
||||||
"input_json": args.input_json,
|
|
||||||
"num_segments": args.num_segments,
|
|
||||||
"num_cond_frames": args.num_cond_frames,
|
|
||||||
"ref_img_index": args.ref_img_index,
|
|
||||||
"mask_frame_range": args.mask_frame_range,
|
|
||||||
"use_kv_cache": args.use_kv_cache,
|
|
||||||
"offload_kv_cache": args.offload_kv_cache,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
use_tqdm=True,
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
omni.close()
|
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory(prefix="longcat_avatar_audio_mux_") as tmp:
|
|
||||||
if input_case is not None and isinstance(audio_input, dict):
|
|
||||||
audio_for_mux = _create_merged_audio_for_case(
|
|
||||||
audio_input,
|
|
||||||
input_case.get("audio_type", "para"),
|
|
||||||
Path(tmp) / "merged_audio.wav",
|
|
||||||
)
|
|
||||||
_save_video_with_audio(_extract_frames(output), Path(args.output), audio_for_mux, args.fps)
|
|
||||||
print(f"Saved video to {args.output}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
"""
|
|
||||||
Offline inference example: MammothModa2 image summarization (single AR stage).
|
|
||||||
|
|
||||||
Example:
|
|
||||||
uv run python examples/offline_inference/mammothmodal2_preview/run_mammothmoda2_image_summarize.py \
|
|
||||||
--model path/to/MammothModa2-Preview \
|
|
||||||
--deploy-config vllm_omni/deploy/mammoth_moda2_ar.yaml \
|
|
||||||
--image /path/to/input.jpg \
|
|
||||||
--question "Please summarize the content of this image."
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
from vllm import SamplingParams
|
|
||||||
from vllm.multimodal.image import convert_image_mode
|
|
||||||
|
|
||||||
from vllm_omni import Omni
|
|
||||||
|
|
||||||
DEFAULT_SYSTEM = "You are a helpful assistant."
|
|
||||||
DEFAULT_QUESTION = "Please summarize the content of this image."
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args() -> argparse.Namespace:
|
|
||||||
parser = argparse.ArgumentParser(description="MammothModa2 image summarization (offline, AR only).")
|
|
||||||
parser.add_argument("--model", type=str, required=True, help="Path to model directory or model id.")
|
|
||||||
parser.add_argument("--deploy-config", type=str, required=True, help="Path to deploy config yaml.")
|
|
||||||
parser.add_argument("--image", type=str, required=True, help="Path to input image.")
|
|
||||||
parser.add_argument("--question", type=str, default=DEFAULT_QUESTION, help="Question/instruction for the model.")
|
|
||||||
parser.add_argument("--system", type=str, default=DEFAULT_SYSTEM, help="System prompt.")
|
|
||||||
parser.add_argument(
|
|
||||||
"--max-tokens",
|
|
||||||
type=int,
|
|
||||||
default=512,
|
|
||||||
help="Max new tokens to generate.",
|
|
||||||
)
|
|
||||||
parser.add_argument("--temperature", type=float, default=0.2)
|
|
||||||
parser.add_argument("--top-p", type=float, default=0.9)
|
|
||||||
parser.add_argument("--seed", type=int, default=42)
|
|
||||||
parser.add_argument("--trust-remote-code", action="store_true")
|
|
||||||
parser.add_argument(
|
|
||||||
"--enable-diffusion-pipeline-profiler",
|
|
||||||
action="store_true",
|
|
||||||
help="Enable diffusion pipeline profiler to display stage durations.",
|
|
||||||
)
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def build_prompt(system: str, question: str) -> str:
|
|
||||||
return (
|
|
||||||
f"<|im_start|>system\n{system}<|im_end|>\n"
|
|
||||||
"<|im_start|>user\n"
|
|
||||||
"<|vision_start|><|image_pad|><|vision_end|>"
|
|
||||||
f"{question}<|im_end|>\n"
|
|
||||||
"<|im_start|>assistant\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
args = parse_args()
|
|
||||||
|
|
||||||
if not os.path.exists(args.image):
|
|
||||||
raise FileNotFoundError(f"Image file not found: {args.image}")
|
|
||||||
|
|
||||||
pil_image = Image.open(args.image)
|
|
||||||
image_data = convert_image_mode(pil_image, "RGB")
|
|
||||||
prompt = build_prompt(args.system, args.question)
|
|
||||||
|
|
||||||
omni = Omni(
|
|
||||||
model=args.model,
|
|
||||||
deploy_config=args.deploy_config,
|
|
||||||
trust_remote_code=args.trust_remote_code,
|
|
||||||
enable_diffusion_pipeline_profiler=args.enable_diffusion_pipeline_profiler,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
sp = SamplingParams(
|
|
||||||
temperature=float(args.temperature),
|
|
||||||
top_p=float(args.top_p),
|
|
||||||
top_k=-1,
|
|
||||||
max_tokens=int(args.max_tokens),
|
|
||||||
seed=int(args.seed),
|
|
||||||
detokenize=True,
|
|
||||||
)
|
|
||||||
# NOTE: omni.generate() returns a Generator[OmniRequestOutput, None, None].
|
|
||||||
# Consume it inside the try block so the worker isn't closed early.
|
|
||||||
outputs = list(
|
|
||||||
omni.generate(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"prompt": prompt,
|
|
||||||
"multi_modal_data": {"image": image_data},
|
|
||||||
"additional_information": {"omni_task": ["chat"]},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[sp],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
omni.close()
|
|
||||||
|
|
||||||
lines: list[str] = []
|
|
||||||
for stage_outputs in outputs:
|
|
||||||
ro = stage_outputs
|
|
||||||
text = ro.outputs[0].text if getattr(ro, "outputs", None) else str(ro)
|
|
||||||
lines.append(f"request_id: {getattr(ro, 'request_id', 'unknown')}\n")
|
|
||||||
lines.append("answer:\n")
|
|
||||||
lines.append(text.strip() + "\n")
|
|
||||||
lines.append("\n")
|
|
||||||
|
|
||||||
print("\n".join(lines))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -297,7 +297,7 @@ def main(args):
|
|||||||
os.makedirs(output_dir, exist_ok=True)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
for stage_outputs in omni_outputs:
|
for stage_outputs in omni_outputs:
|
||||||
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
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ def main(args):
|
|||||||
omni.start_profile(stages=args.profiler_stages)
|
omni.start_profile(stages=args.profiler_stages)
|
||||||
|
|
||||||
for stage_outputs in omni.generate(prompts, sampling_params_list):
|
for stage_outputs in omni.generate(prompts, sampling_params_list):
|
||||||
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
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ def parse_args():
|
|||||||
default=None,
|
default=None,
|
||||||
help="Path to a .txt file with one prompt per line (preferred).",
|
help="Path to a .txt file with one prompt per line (preferred).",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--stage-configs-path",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Path to a stage configs file (deprecated; prefer --deploy-config).",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--deploy-config",
|
"--deploy-config",
|
||||||
type=str,
|
type=str,
|
||||||
|
|||||||
@@ -1,229 +0,0 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
||||||
"""Offline speech-to-speech with NVIDIA-NemotronLabs-VoiceChat-11B.
|
|
||||||
|
|
||||||
Feeds a 16 kHz user utterance (plus a spoken-style system prompt) through the
|
|
||||||
3-stage nemotron_voicechat pipeline and writes the agent's reply as text and a
|
|
||||||
22.05 kHz WAV.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python end2end.py \
|
|
||||||
--checkpoint /path/to/NVIDIA-NemotronLabs-VoiceChat-11B \
|
|
||||||
--wav /path/to/user_question_16k.wav \
|
|
||||||
--output-dir results/nemotron_voicechat
|
|
||||||
|
|
||||||
The system prompt defaults to the NeMo reference default. The text tokenizer
|
|
||||||
resolves from the ``nvidia/NVIDIA-Nemotron-Nano-9B-v2`` HF id (or the
|
|
||||||
``NEMOTRON_VOICECHAT_LLM_PATH`` env var for air-gapped runs).
|
|
||||||
|
|
||||||
Sizing the input WAV: the timeline is frame-locked, so the reply budget IS the
|
|
||||||
input duration — the acoustic channel trails the text channel, and a reply
|
|
||||||
that has not finished when the frames run out is truncated SILENTLY (no error;
|
|
||||||
ASR of the WAV will just disagree with the emitted text). Leave generous
|
|
||||||
trailing silence after the question (a question ending at ~4.5 s truncated in
|
|
||||||
an 8 s WAV but completed cleanly in a 16 s one). The script warns when the
|
|
||||||
text channel is still emitting non-PAD tokens near the last frame.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import soundfile as sf
|
|
||||||
|
|
||||||
# Warn if the text channel is still speaking within this many final frames
|
|
||||||
# (12 frames = ~1 s) — the frame-locked reply likely got cut off.
|
|
||||||
TRUNCATION_GUARD_FRAMES = 12
|
|
||||||
|
|
||||||
# The NeMo reference offline script's default system prompt.
|
|
||||||
DEFAULT_SYSTEM_PROMPT = (
|
|
||||||
"You are an AI voice assistant developed by NVIDIA. "
|
|
||||||
"Your name is NVIDIA Voice Chat. "
|
|
||||||
"Answer in a spoken, conversational style rather than a written one. "
|
|
||||||
"Do not repeat the same sentence over and over again. "
|
|
||||||
"Start the conversation by greeting the user."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def load_wav_16k_mono(path: str) -> np.ndarray:
|
|
||||||
wav, sr = sf.read(path, dtype="float32")
|
|
||||||
if wav.ndim > 1:
|
|
||||||
wav = wav.mean(axis=1)
|
|
||||||
if sr != 16000:
|
|
||||||
from vllm.multimodal.audio import resample_audio_scipy
|
|
||||||
|
|
||||||
wav = resample_audio_scipy(wav, orig_sr=sr, target_sr=16000)
|
|
||||||
return wav.astype(np.float32)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
|
||||||
parser.add_argument("--checkpoint", required=True, help="NVIDIA-NemotronLabs-VoiceChat-11B directory")
|
|
||||||
parser.add_argument("--wav", required=True, help="user speech (any sr; resampled to 16 kHz mono)")
|
|
||||||
parser.add_argument("--output-dir", default="results/nemotron_voicechat")
|
|
||||||
parser.add_argument("--system-prompt", default=DEFAULT_SYSTEM_PROMPT)
|
|
||||||
parser.add_argument(
|
|
||||||
"--stage-configs-path",
|
|
||||||
default=None,
|
|
||||||
help=(
|
|
||||||
"Deploy yaml override. Pass vllm_omni/deploy/nemotron_labs_voicechat_streaming.yaml "
|
|
||||||
"for the async-chunk streaming pipeline (stages overlap; audio starts decoding "
|
|
||||||
"before the thinker finishes). Default: the bundled non-streaming yaml (bit-exact "
|
|
||||||
"parity path)."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if not Path(args.wav).is_file():
|
|
||||||
raise FileNotFoundError(f"Input WAV not found: {args.wav}")
|
|
||||||
ckpt = Path(args.checkpoint)
|
|
||||||
raw_cfg = json.loads((ckpt / "config.json").read_text())
|
|
||||||
stt_cfg = raw_cfg["model"]["stt"]["model"]
|
|
||||||
|
|
||||||
from transformers import AutoTokenizer
|
|
||||||
from vllm.sampling_params import SamplingParams
|
|
||||||
|
|
||||||
from vllm_omni.entrypoints.omni import Omni
|
|
||||||
from vllm_omni.model_executor.models.nemotron_voicechat.nemotron_voicechat_thinker import (
|
|
||||||
compute_acoustic_frame_count,
|
|
||||||
)
|
|
||||||
|
|
||||||
tok_ref = os.environ.get("NEMOTRON_VOICECHAT_LLM_PATH") or stt_cfg.get(
|
|
||||||
"pretrained_llm", "nvidia/NVIDIA-Nemotron-Nano-9B-v2"
|
|
||||||
)
|
|
||||||
tokenizer = AutoTokenizer.from_pretrained(tok_ref, trust_remote_code=False)
|
|
||||||
bos_id = tokenizer.convert_tokens_to_ids(stt_cfg.get("bos_token", "<s>"))
|
|
||||||
eos_id = tokenizer.convert_tokens_to_ids(stt_cfg.get("eos_token", "</s>"))
|
|
||||||
pad_id = tokenizer.convert_tokens_to_ids(stt_cfg.get("pad_token", "<SPECIAL_12>"))
|
|
||||||
# NeMo encode_system_prompt: [bos] + tokens + [eos].
|
|
||||||
prompt_ids = [bos_id] + tokenizer.encode(args.system_prompt, add_special_tokens=False) + [eos_id]
|
|
||||||
|
|
||||||
wav = load_wav_16k_mono(args.wav)
|
|
||||||
n_frames = compute_acoustic_frame_count(stt_cfg, int(wav.shape[0]))
|
|
||||||
print(f"input: {wav.shape[0]} samples @16kHz -> {n_frames} acoustic frames; prompt {len(prompt_ids)} tokens")
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
# vLLM prompt = system prompt + one placeholder position (acoustic
|
|
||||||
# frame 0); the thinker replaces every position with fused embeddings.
|
|
||||||
"prompt_token_ids": prompt_ids + [pad_id],
|
|
||||||
"additional_information": {
|
|
||||||
# Plain float list: raw ndarrays do not survive the engine-core
|
|
||||||
# message serialization.
|
|
||||||
"nvc_audio": wav.astype(np.float32).tolist(),
|
|
||||||
"nvc_sr": 16000,
|
|
||||||
"nvc_prompt_token_ids": prompt_ids,
|
|
||||||
"nvc_expected_frames": n_frames,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
sampling_params_list = [
|
|
||||||
# thinker: greedy, frame-locked generation length. NO min_tokens: the
|
|
||||||
# tokenizer's eos_token is <SPECIAL_12> — the frame-locked PAD/silence
|
|
||||||
# token — and vLLM's min_tokens masks "EOS" to -inf, which would forbid
|
|
||||||
# silence and force the agent to babble through the whole utterance.
|
|
||||||
SamplingParams(temperature=0.0, max_tokens=n_frames, ignore_eos=True, seed=0),
|
|
||||||
# talker: placeholder loop, stops on the stage's stop token. 16383:
|
|
||||||
# the 1-token placeholder prompt + max_tokens must fit the stage's
|
|
||||||
# max_model_len (16384).
|
|
||||||
SamplingParams(temperature=0.0, max_tokens=16383, stop_token_ids=[1], detokenize=False, seed=0),
|
|
||||||
# code2wav: single decode step.
|
|
||||||
SamplingParams(temperature=0.0, max_tokens=1, detokenize=False, seed=0),
|
|
||||||
]
|
|
||||||
|
|
||||||
omni_kwargs: dict = {}
|
|
||||||
if args.stage_configs_path:
|
|
||||||
omni_kwargs["stage_configs_path"] = args.stage_configs_path
|
|
||||||
omni = Omni(model=str(ckpt), trust_remote_code=True, **omni_kwargs)
|
|
||||||
outputs = omni.generate(inputs, sampling_params_list)
|
|
||||||
|
|
||||||
out_dir = Path(args.output_dir)
|
|
||||||
out_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
stem = Path(args.wav).stem
|
|
||||||
|
|
||||||
agent_text = None
|
|
||||||
audio = None
|
|
||||||
sr = 22050
|
|
||||||
for stage_outputs in outputs:
|
|
||||||
# One entry per final stage (text from the thinker, audio from code2wav);
|
|
||||||
# mirrors the qwen2_5_omni offline example's collection loop.
|
|
||||||
output = getattr(stage_outputs, "request_output", stage_outputs)
|
|
||||||
final_type = getattr(stage_outputs, "final_output_type", None)
|
|
||||||
completions = getattr(output, "outputs", None) or []
|
|
||||||
if not completions:
|
|
||||||
continue
|
|
||||||
completion = completions[0]
|
|
||||||
if final_type in (None, "text"):
|
|
||||||
text = getattr(completion, "text", None)
|
|
||||||
if text:
|
|
||||||
agent_text = text
|
|
||||||
token_ids = getattr(completion, "token_ids", None)
|
|
||||||
if token_ids:
|
|
||||||
(out_dir / f"{stem}_text_tokens.json").write_text(json.dumps(list(map(int, token_ids))))
|
|
||||||
# Frame lock: the reply budget IS the input length. If the text
|
|
||||||
# channel is still mid-sentence when the frames run out, the
|
|
||||||
# spoken answer is silently truncated — warn instead.
|
|
||||||
tail = list(map(int, token_ids))[-TRUNCATION_GUARD_FRAMES:]
|
|
||||||
if any(t != pad_id for t in tail):
|
|
||||||
print(
|
|
||||||
f"WARNING: the text channel emits non-PAD tokens within the last "
|
|
||||||
f"{TRUNCATION_GUARD_FRAMES} frames ({TRUNCATION_GUARD_FRAMES * 80} ms) — the reply "
|
|
||||||
"likely ran out of acoustic frames and is cut off mid-sentence. "
|
|
||||||
"Pad the input WAV with more trailing silence and rerun."
|
|
||||||
)
|
|
||||||
# Best-effort: dump the function-channel debug timeline if the
|
|
||||||
# engine surfaced the request info on this output.
|
|
||||||
info = getattr(output, "additional_information", None)
|
|
||||||
func_timeline = info.get("nvc_function_tokens") if isinstance(info, dict) else None
|
|
||||||
if func_timeline is not None:
|
|
||||||
values = func_timeline.tolist() if hasattr(func_timeline, "tolist") else list(func_timeline)
|
|
||||||
(out_dir / f"{stem}_function_tokens.json").write_text(json.dumps([int(v) for v in values]))
|
|
||||||
print(f"function-channel timeline: {len(values)} entries")
|
|
||||||
if final_type in (None, "audio"):
|
|
||||||
# multimodal_output is a MultimodalPayload (Mapping) — duck-type it.
|
|
||||||
mm = getattr(completion, "multimodal_output", None)
|
|
||||||
get = getattr(mm, "get", None)
|
|
||||||
if not callable(get):
|
|
||||||
continue
|
|
||||||
candidate = None
|
|
||||||
for key in ("audio", "model_outputs", "wav", "waveform"):
|
|
||||||
value = get(key)
|
|
||||||
if value is not None:
|
|
||||||
candidate = value
|
|
||||||
break
|
|
||||||
if isinstance(candidate, list):
|
|
||||||
candidate = candidate[0] if candidate else None
|
|
||||||
if candidate is not None:
|
|
||||||
arr = np.asarray(
|
|
||||||
candidate.float().cpu() if hasattr(candidate, "cpu") else candidate, dtype=np.float32
|
|
||||||
).reshape(-1)
|
|
||||||
if arr.size > 0:
|
|
||||||
audio = arr
|
|
||||||
sr_val = get("sr")
|
|
||||||
if sr_val is not None:
|
|
||||||
sr_item = sr_val[0] if isinstance(sr_val, list) else sr_val
|
|
||||||
sr = int(sr_item.item() if hasattr(sr_item, "item") else sr_item)
|
|
||||||
|
|
||||||
if agent_text is not None:
|
|
||||||
# The frame-locked text channel emits PAD tokens on silent frames;
|
|
||||||
# strip them (NeMo's post-inference detokenizer does the same).
|
|
||||||
pad_token = stt_cfg.get("pad_token", "<SPECIAL_12>")
|
|
||||||
agent_text = " ".join(agent_text.replace(pad_token, " ").split())
|
|
||||||
text_path = out_dir / f"{stem}_output.txt"
|
|
||||||
text_path.write_text(agent_text + "\n")
|
|
||||||
print(f"agent text -> {text_path}\n{agent_text}")
|
|
||||||
else:
|
|
||||||
print("WARNING: no agent text in outputs")
|
|
||||||
|
|
||||||
if audio is not None and audio.size > 0:
|
|
||||||
wav_path = out_dir / f"{stem}_output.wav"
|
|
||||||
sf.write(wav_path, audio, sr)
|
|
||||||
print(f"agent audio -> {wav_path} ({audio.size / sr:.2f}s @ {sr} Hz)")
|
|
||||||
else:
|
|
||||||
raise RuntimeError("No audio produced by the code2wav stage.")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -392,7 +392,7 @@ def main(args):
|
|||||||
total_requests = len(prompts)
|
total_requests = len(prompts)
|
||||||
processed_count = 0
|
processed_count = 0
|
||||||
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
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ def main(args):
|
|||||||
print(f"query type: {args.query_type}")
|
print(f"query type: {args.query_type}")
|
||||||
|
|
||||||
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
|
||||||
@@ -484,10 +484,10 @@ def parse_args():
|
|||||||
help="Path to a .txt file with one prompt per line (preferred).",
|
help="Path to a .txt file with one prompt per line (preferred).",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--deploy-config",
|
"--stage-configs-path",
|
||||||
type=str,
|
type=str,
|
||||||
default=None,
|
default=None,
|
||||||
help="Path to a deploy config YAML.",
|
help="Path to a stage configs file.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--video-path",
|
"--video-path",
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ async def run_single_request(
|
|||||||
sampling_params_list=sampling_params_list,
|
sampling_params_list=sampling_params_list,
|
||||||
output_modalities=output_modalities,
|
output_modalities=output_modalities,
|
||||||
):
|
):
|
||||||
output = omni_output
|
output = omni_output.request_output
|
||||||
if omni_output.final_output_type == "text":
|
if omni_output.final_output_type == "text":
|
||||||
if stage_0_first_output_ts is None:
|
if stage_0_first_output_ts is None:
|
||||||
stage_0_first_output_ts = time.perf_counter()
|
stage_0_first_output_ts = time.perf_counter()
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import torch
|
|||||||
from vllm_omni.diffusion.data import DiffusionParallelConfig
|
from vllm_omni.diffusion.data import DiffusionParallelConfig
|
||||||
from vllm_omni.entrypoints.omni import Omni
|
from vllm_omni.entrypoints.omni import Omni
|
||||||
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
||||||
|
from vllm_omni.outputs import OmniRequestOutput
|
||||||
from vllm_omni.platforms import current_omni_platform
|
from vllm_omni.platforms import current_omni_platform
|
||||||
|
|
||||||
|
|
||||||
@@ -351,8 +352,8 @@ def main():
|
|||||||
else:
|
else:
|
||||||
print("[Profiler] No valid profiling data returned.")
|
print("[Profiler] No valid profiling data returned.")
|
||||||
|
|
||||||
# omni.generate() returns a list for sync calls; unwrap single-result list.
|
# Extract output from result
|
||||||
output = result[0] if isinstance(result, list) else result
|
output = OmniRequestOutput.unwrap_result(result)
|
||||||
|
|
||||||
if not output.images:
|
if not output.images:
|
||||||
raise ValueError("No video frames found in OmniRequestOutput.")
|
raise ValueError("No video frames found in OmniRequestOutput.")
|
||||||
|
|||||||
@@ -232,8 +232,15 @@ def main(args):
|
|||||||
raise ValueError(f"Unknown query type: {args.query_type}")
|
raise ValueError(f"Unknown query type: {args.query_type}")
|
||||||
|
|
||||||
# Initialize vLLM-Omni with Step-Audio2
|
# Initialize vLLM-Omni with Step-Audio2
|
||||||
# Resolve an explicit deploy config or use the bundled task default.
|
# Resolve deploy config path. Keep explicit stage_configs_path for legacy
|
||||||
if args.deploy_config:
|
# custom configs, but use bundled deploy configs by default.
|
||||||
|
deploy_config_path = None
|
||||||
|
stage_config_path = None
|
||||||
|
if args.stage_configs_path and args.deploy_config:
|
||||||
|
raise ValueError("--stage-configs-path and --deploy-config are mutually exclusive")
|
||||||
|
if args.stage_configs_path:
|
||||||
|
stage_config_path = args.stage_configs_path
|
||||||
|
elif args.deploy_config:
|
||||||
deploy_config_path = args.deploy_config
|
deploy_config_path = args.deploy_config
|
||||||
else:
|
else:
|
||||||
configs_dir = Path(__file__).parent.parent.parent.parent / "vllm_omni/deploy"
|
configs_dir = Path(__file__).parent.parent.parent.parent / "vllm_omni/deploy"
|
||||||
@@ -255,7 +262,10 @@ def main(args):
|
|||||||
"ray_address": args.ray_address,
|
"ray_address": args.ray_address,
|
||||||
"trust_remote_code": True,
|
"trust_remote_code": True,
|
||||||
}
|
}
|
||||||
omni_kwargs["deploy_config"] = deploy_config_path
|
if stage_config_path is not None:
|
||||||
|
omni_kwargs["stage_configs_path"] = stage_config_path
|
||||||
|
else:
|
||||||
|
omni_kwargs["deploy_config"] = deploy_config_path
|
||||||
|
|
||||||
omni_llm = Omni(**omni_kwargs)
|
omni_llm = Omni(**omni_kwargs)
|
||||||
|
|
||||||
@@ -335,7 +345,7 @@ def main(args):
|
|||||||
# Process outputs from each stage
|
# Process outputs from each stage
|
||||||
for stage_idx, stage_outputs in enumerate(omni_outputs):
|
for stage_idx, stage_outputs in enumerate(omni_outputs):
|
||||||
# request_output may be a single object or a list
|
# request_output may be a single object or a list
|
||||||
raw_output = stage_outputs
|
raw_output = stage_outputs.request_output
|
||||||
outputs_list = raw_output if isinstance(raw_output, list) else [raw_output]
|
outputs_list = raw_output if isinstance(raw_output, list) else [raw_output]
|
||||||
|
|
||||||
if stage_outputs.final_output_type == "text":
|
if stage_outputs.final_output_type == "text":
|
||||||
@@ -408,6 +418,12 @@ def parse_args():
|
|||||||
" - HuggingFace ID: stepfun-ai/Step-Audio-2-mini"
|
" - HuggingFace ID: stepfun-ai/Step-Audio-2-mini"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--stage-configs-path",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Path to a legacy stage config YAML file (default: use bundled deploy config)",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--deploy-config",
|
"--deploy-config",
|
||||||
type=str,
|
type=str,
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ from vllm_omni.diffusion.utils.param_utils import apply_declared_extra_args
|
|||||||
from vllm_omni.entrypoints.omni import Omni
|
from vllm_omni.entrypoints.omni import Omni
|
||||||
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
||||||
from vllm_omni.model_extras import get_extra_body_params, get_model_class_name
|
from vllm_omni.model_extras import get_extra_body_params, get_model_class_name
|
||||||
from vllm_omni.outputs import OmniRequestOutput
|
|
||||||
from vllm_omni.platforms import current_omni_platform
|
from vllm_omni.platforms import current_omni_platform
|
||||||
|
|
||||||
AUDIOX_TASKS = ("t2a", "t2m", "v2a", "v2m", "tv2a", "tv2m")
|
AUDIOX_TASKS = ("t2a", "t2m", "v2a", "v2m", "tv2a", "tv2m")
|
||||||
@@ -389,9 +388,9 @@ def main():
|
|||||||
raise ValueError("No output generated from omni.generate()")
|
raise ValueError("No output generated from omni.generate()")
|
||||||
|
|
||||||
output = outputs[0]
|
output = outputs[0]
|
||||||
if not isinstance(output, OmniRequestOutput) or not output:
|
if not hasattr(output, "request_output") or not output.request_output:
|
||||||
raise ValueError("No request_output found in OmniRequestOutput")
|
raise ValueError("No request_output found in OmniRequestOutput")
|
||||||
request_output = output
|
request_output = output.request_output
|
||||||
if not hasattr(request_output, "multimodal_output"):
|
if not hasattr(request_output, "multimodal_output"):
|
||||||
raise ValueError("No multimodal_output found in request_output")
|
raise ValueError("No multimodal_output found in request_output")
|
||||||
|
|
||||||
|
|||||||
@@ -57,9 +57,8 @@ python examples/offline_inference/text_to_image/text_to_image.py \
|
|||||||
--output lingbot_t2i.png
|
--output lingbot_t2i.png
|
||||||
```
|
```
|
||||||
|
|
||||||
The shared T2I entry point selects the `image` output modality. The LingBot
|
The LingBot prompt builder selects the `image` output modality and requests one
|
||||||
pipeline validates that T2I requests produce one frame and do not include an
|
frame. Passing an input image is not supported in LingBot T2I mode.
|
||||||
input image.
|
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@@ -110,9 +109,8 @@ python text_to_image.py \
|
|||||||
| `--ring-degree` | int | `1` | Ring sequence parallel degree for hybrid Ulysses + Ring inference |
|
| `--ring-degree` | int | `1` | Ring sequence parallel degree for hybrid Ulysses + Ring inference |
|
||||||
| `--ulysses-mode` | str | `"strict"` | Ulysses SP mode: `"strict"` or `"advanced_uaa"` |
|
| `--ulysses-mode` | str | `"strict"` | Ulysses SP mode: `"strict"` or `"advanced_uaa"` |
|
||||||
| `--enable-cpu-offload` | flag | off | Enable CPU offloading for diffusion models |
|
| `--enable-cpu-offload` | flag | off | Enable CPU offloading for diffusion models |
|
||||||
| `--lora-path` | str | — | Path to PEFT LoRA adapter folder or checkpoint file |
|
| `--lora-path` | str | — | Path to PEFT LoRA adapter folder |
|
||||||
| `--lora-scale` | float | `1.0` | Scale factor for LoRA weights |
|
| `--lora-scale` | float | `1.0` | Scale factor for LoRA weights |
|
||||||
| `--lora-backend` | str |`"peft"`| LoRA backend for loading LoRA adapters. Default: peft. Available options: peft, distill |
|
|
||||||
| `--use-system-prompt` | str | `None` | System prompt preset: `en_unified`, `en_vanilla`, `en_recaption`, `en_think_recaption`, `dynamic`, `None`, or custom text. Recommended: `en_unified`. Only for HunyuanImage-3.0.|
|
| `--use-system-prompt` | str | `None` | System prompt preset: `en_unified`, `en_vanilla`, `en_recaption`, `en_think_recaption`, `dynamic`, `None`, or custom text. Recommended: `en_unified`. Only for HunyuanImage-3.0.|
|
||||||
| `--system-prompt` | str | `None` | Custom system prompt text. Only used when `--use-system-prompt` is set to `custom`. Only for HunyuanImage-3.0.|
|
| `--system-prompt` | str | `None` | Custom system prompt text. Only used when `--use-system-prompt` is set to `custom`. Only for HunyuanImage-3.0.|
|
||||||
| `--auxiliary-text-encoder` | str | `None` | Supplementary auxiliary text encoder parameters model name or path (especially for Hidream-l1-full). |
|
| `--auxiliary-text-encoder` | str | `None` | Supplementary auxiliary text encoder parameters model name or path (especially for Hidream-l1-full). |
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ def build_demo(args: argparse.Namespace) -> gr.Blocks:
|
|||||||
)
|
)
|
||||||
images_outputs = []
|
images_outputs = []
|
||||||
for output in outputs:
|
for output in outputs:
|
||||||
req_out = output
|
req_out = output.request_output
|
||||||
if not isinstance(req_out, OmniRequestOutput) or not hasattr(req_out, "images"):
|
if not isinstance(req_out, OmniRequestOutput) or not hasattr(req_out, "images"):
|
||||||
raise ValueError("Invalid request_output structure or missing 'images' key")
|
raise ValueError("Invalid request_output structure or missing 'images' key")
|
||||||
images = req_out.images
|
images = req_out.images
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import time
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import torch
|
import torch
|
||||||
from diffusers.utils import numpy_to_pil
|
|
||||||
|
|
||||||
from vllm_omni.diffusion.data import logger
|
from vllm_omni.diffusion.data import logger
|
||||||
from vllm_omni.diffusion.utils.image_output import extract_images_from_outputs
|
from vllm_omni.diffusion.utils.image_output import extract_images_from_outputs
|
||||||
@@ -21,9 +19,7 @@ from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
|||||||
from vllm_omni.lora.request import LoRARequest
|
from vllm_omni.lora.request import LoRARequest
|
||||||
from vllm_omni.lora.utils import stable_lora_int_id
|
from vllm_omni.lora.utils import stable_lora_int_id
|
||||||
from vllm_omni.model_extras import (
|
from vllm_omni.model_extras import (
|
||||||
build_text_to_image_prompt as build_model_text_to_image_prompt,
|
build_text_to_image_prompt,
|
||||||
)
|
|
||||||
from vllm_omni.model_extras import (
|
|
||||||
get_extra_body_params,
|
get_extra_body_params,
|
||||||
get_model_class_name,
|
get_model_class_name,
|
||||||
should_init_extra_args_for_non_diffusion_stages,
|
should_init_extra_args_for_non_diffusion_stages,
|
||||||
@@ -58,28 +54,6 @@ def parse_json_object(value: str, flag_name: str = "argument") -> dict[str, Any]
|
|||||||
parse_profiler_config = functools.partial(parse_json_object, flag_name="--profiler-config")
|
parse_profiler_config = functools.partial(parse_json_object, flag_name="--profiler-config")
|
||||||
|
|
||||||
|
|
||||||
def build_text_to_image_prompt(prompt: str, negative_prompt: str | None) -> dict[str, Any]:
|
|
||||||
"""Build the canonical request envelope for the shared T2I example."""
|
|
||||||
result: dict[str, Any] = {
|
|
||||||
"prompt": prompt,
|
|
||||||
"modalities": ["image"],
|
|
||||||
}
|
|
||||||
if negative_prompt is not None:
|
|
||||||
result["negative_prompt"] = negative_prompt
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_images_for_save(images: list[Any]) -> list[Any]:
|
|
||||||
"""Convert NumPy diffusion outputs to PIL images before saving."""
|
|
||||||
normalized = []
|
|
||||||
for image in images:
|
|
||||||
if isinstance(image, np.ndarray):
|
|
||||||
normalized.extend(numpy_to_pil(image))
|
|
||||||
else:
|
|
||||||
normalized.append(image)
|
|
||||||
return normalized
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args() -> argparse.Namespace:
|
def parse_args() -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(description="Generate an image with supported diffusion models.")
|
parser = argparse.ArgumentParser(description="Generate an image with supported diffusion models.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -92,6 +66,12 @@ def parse_args() -> argparse.Namespace:
|
|||||||
"meituan-longcat/LongCat-Image, OvisAI/Ovis-Image, "
|
"meituan-longcat/LongCat-Image, OvisAI/Ovis-Image, "
|
||||||
"stabilityai/stable-diffusion-3.5-medium, Tongyi-MAI/Z-Image-Turbo and etc.",
|
"stabilityai/stable-diffusion-3.5-medium, Tongyi-MAI/Z-Image-Turbo and etc.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--stage-configs-path",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="[Deprecated] Path to a legacy stage_args-format YAML. Prefer --deploy-config.",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--deploy-config",
|
"--deploy-config",
|
||||||
type=str,
|
type=str,
|
||||||
@@ -267,7 +247,6 @@ def parse_args() -> argparse.Namespace:
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--lora-path",
|
"--lora-path",
|
||||||
type=str,
|
type=str,
|
||||||
nargs="+",
|
|
||||||
default=None,
|
default=None,
|
||||||
help="Path to LoRA adapter folder (PEFT format). Loaded at initialization and used for generation.",
|
help="Path to LoRA adapter folder (PEFT format). Loaded at initialization and used for generation.",
|
||||||
)
|
)
|
||||||
@@ -277,15 +256,6 @@ def parse_args() -> argparse.Namespace:
|
|||||||
default=1.0,
|
default=1.0,
|
||||||
help="Scale factor for LoRA weights (default: 1.0).",
|
help="Scale factor for LoRA weights (default: 1.0).",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--lora-backend",
|
|
||||||
type=str,
|
|
||||||
default="peft",
|
|
||||||
choices=["peft", "distill"],
|
|
||||||
help="LoRA backend for loading LoRA adapters. Default: peft"
|
|
||||||
"'peft' loads a PEFT-format adapter folder, used e.g. for RL"
|
|
||||||
"'distill' fuses one or more concrete LoRA checkpoint files, used e.g. for distilled few-step LoRAs",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--vae-patch-parallel-size",
|
"--vae-patch-parallel-size",
|
||||||
type=int,
|
type=int,
|
||||||
@@ -421,12 +391,8 @@ def main():
|
|||||||
# Prepare LoRA kwargs for Omni initialization
|
# Prepare LoRA kwargs for Omni initialization
|
||||||
lora_args: dict[str, Any] = {}
|
lora_args: dict[str, Any] = {}
|
||||||
if args.lora_path:
|
if args.lora_path:
|
||||||
lora_path = args.lora_path
|
lora_args["lora_path"] = args.lora_path
|
||||||
if len(lora_path) == 1:
|
print(f"Using LoRA from: {args.lora_path}")
|
||||||
lora_path = lora_path[0]
|
|
||||||
lora_args["lora_path"] = lora_path
|
|
||||||
lora_args["lora_backend"] = args.lora_backend
|
|
||||||
print(f"Using LoRA from: {lora_path} with backend: {args.lora_backend}")
|
|
||||||
|
|
||||||
# Build quantization kwargs: use quantization_config dict when
|
# Build quantization kwargs: use quantization_config dict when
|
||||||
# ignored_layers is specified so the list flows through OmniDiffusionConfig
|
# ignored_layers is specified so the list flows through OmniDiffusionConfig
|
||||||
@@ -469,6 +435,8 @@ def main():
|
|||||||
omni_kwargs["tensor_parallel_size"] = args.tensor_parallel_size
|
omni_kwargs["tensor_parallel_size"] = args.tensor_parallel_size
|
||||||
if args.enforce_eager is not None:
|
if args.enforce_eager is not None:
|
||||||
omni_kwargs["enforce_eager"] = args.enforce_eager
|
omni_kwargs["enforce_eager"] = args.enforce_eager
|
||||||
|
if args.stage_configs_path:
|
||||||
|
omni_kwargs["stage_configs_path"] = args.stage_configs_path
|
||||||
if args.deploy_config:
|
if args.deploy_config:
|
||||||
omni_kwargs["deploy_config"] = args.deploy_config
|
omni_kwargs["deploy_config"] = args.deploy_config
|
||||||
if use_nextstep:
|
if use_nextstep:
|
||||||
@@ -507,33 +475,26 @@ def main():
|
|||||||
print(f" Image size: {args.width}x{args.height}")
|
print(f" Image size: {args.width}x{args.height}")
|
||||||
if args.lora_path:
|
if args.lora_path:
|
||||||
print(f" LoRA: scale={args.lora_scale}")
|
print(f" LoRA: scale={args.lora_scale}")
|
||||||
if args.deploy_config:
|
if args.stage_configs_path:
|
||||||
print(f" deploy-config: {args.deploy_config}")
|
print(f" stage-configs-path: {args.stage_configs_path}")
|
||||||
print(f"{'=' * 60}\n")
|
print(f"{'=' * 60}\n")
|
||||||
|
|
||||||
# Build LoRA request when --lora-path is set
|
# Build LoRA request when --lora-path is set
|
||||||
lora_request = None
|
lora_request = None
|
||||||
if args.lora_path and args.lora_backend == "peft":
|
if args.lora_path:
|
||||||
if len(args.lora_path) != 1:
|
lora_request_id = stable_lora_int_id(args.lora_path)
|
||||||
raise ValueError("Only one LoRA path is expected for PEFT backend.")
|
|
||||||
|
|
||||||
lora_path = args.lora_path[0]
|
|
||||||
lora_request_id = stable_lora_int_id(lora_path)
|
|
||||||
lora_request = LoRARequest(
|
lora_request = LoRARequest(
|
||||||
lora_name=Path(lora_path).stem,
|
lora_name=Path(args.lora_path).stem,
|
||||||
lora_int_id=lora_request_id,
|
lora_int_id=lora_request_id,
|
||||||
lora_path=lora_path,
|
lora_path=args.lora_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
generation_start = time.perf_counter()
|
generation_start = time.perf_counter()
|
||||||
|
|
||||||
prompt_dict = build_text_to_image_prompt(
|
prompt_dict = build_text_to_image_prompt(
|
||||||
|
model_class_name=model_class_name,
|
||||||
prompt=args.prompt,
|
prompt=args.prompt,
|
||||||
negative_prompt=args.negative_prompt,
|
negative_prompt=args.negative_prompt,
|
||||||
)
|
|
||||||
prompt_dict = build_model_text_to_image_prompt(
|
|
||||||
model_class_name=model_class_name,
|
|
||||||
prompt=prompt_dict,
|
|
||||||
height=args.height,
|
height=args.height,
|
||||||
width=args.width,
|
width=args.width,
|
||||||
)
|
)
|
||||||
@@ -648,7 +609,7 @@ def main():
|
|||||||
images = getattr(output, "images", None)
|
images = getattr(output, "images", None)
|
||||||
if images:
|
if images:
|
||||||
break
|
break
|
||||||
req_out = output
|
req_out = getattr(output, "request_output", None)
|
||||||
images = getattr(req_out, "images", None) if req_out is not None else None
|
images = getattr(req_out, "images", None) if req_out is not None else None
|
||||||
if images:
|
if images:
|
||||||
break
|
break
|
||||||
@@ -658,7 +619,6 @@ def main():
|
|||||||
|
|
||||||
if not images:
|
if not images:
|
||||||
raise ValueError("No images found in request_output")
|
raise ValueError("No images found in request_output")
|
||||||
images = _normalize_images_for_save(images)
|
|
||||||
|
|
||||||
output_path = Path(args.output)
|
output_path = Path(args.output)
|
||||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ list of supported architectures across all modalities, see
|
|||||||
| 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 |
|
||||||
| 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 |
|
|
||||||
| Voxtral TTS | `mistralai/Voxtral-4B-TTS-2603` | varies | ✓ | ✓ | voice presets | 24 kHz |
|
| Voxtral TTS | `mistralai/Voxtral-4B-TTS-2603` | varies | ✓ | ✓ | voice presets | 24 kHz |
|
||||||
|
|
||||||
## Common Quick Start
|
## Common Quick Start
|
||||||
@@ -372,10 +371,18 @@ python examples/offline_inference/text_to_speech/qwen3_tts/end2end.py \
|
|||||||
Streaming requires `async_chunk: true` in the stage config.
|
Streaming requires `async_chunk: true` in the stage config.
|
||||||
|
|
||||||
### Word Timestamps
|
### Word Timestamps
|
||||||
Word-level timestamps are currently a serving-path feature: launch
|
Generate a WAV offline and a JSON sidecar with word-level timestamps from
|
||||||
`vllm-omni serve` with `--forced-aligner` and request `word_timestamps`
|
`Qwen/Qwen3-ForcedAligner-0.6B`:
|
||||||
(see `examples/online_serving/text_to_speech/README.md`). An offline
|
```bash
|
||||||
example is not provided in this release.
|
python examples/offline_inference/text_to_speech/qwen3_tts/word_timestamps.py \
|
||||||
|
--model Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice \
|
||||||
|
--forced-aligner Qwen/Qwen3-ForcedAligner-0.6B \
|
||||||
|
--text "Hello world." \
|
||||||
|
--output-dir /tmp/qwen3_tts_timestamps
|
||||||
|
```
|
||||||
|
The script writes `qwen3_tts_word_timestamps.wav` and
|
||||||
|
`qwen3_tts_word_timestamps.json`. On machines without a local CUDA toolkit,
|
||||||
|
set `VLLM_USE_FLASHINFER_SAMPLER=0` to avoid FlashInfer sampler JIT.
|
||||||
|
|
||||||
### Batched decoding
|
### Batched decoding
|
||||||
The Code2Wav stage supports batched decoding through the SpeechTokenizer. Pass multiple prompts via `--txt-prompts` and set `--batch-size` accordingly. To raise `max_num_seqs` on either stage, point `--stage-configs-path` at a stage configs YAML with the desired values (see `vllm_omni/model_executor/stage_configs/` for templates):
|
The Code2Wav stage supports batched decoding through the SpeechTokenizer. Pass multiple prompts via `--txt-prompts` and set `--batch-size` accordingly. To raise `max_num_seqs` on either stage, point `--stage-configs-path` at a stage configs YAML with the desired values (see `vllm_omni/model_executor/stage_configs/` for templates):
|
||||||
@@ -430,14 +437,11 @@ Streaming is exposed through the online OpenAI Speech API (`stream=true`). See [
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## IndexTTS-2 and IndexTTS-2.5
|
## IndexTTS-2
|
||||||
|
|
||||||
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.
|
2-stage TTS pipeline (GPT AR talker + S2Mel CFM DiT + BigVGAN vocoder) at 22.05 kHz. Every request requires reference audio for zero-shot voice cloning. Supports emotion conditioning via audio, text, or 8-dim vector.
|
||||||
|
|
||||||
### Quick start
|
### Quick start
|
||||||
|
|
||||||
IndexTTS-2:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python examples/offline_inference/text_to_speech/indextts2/end2end.py \
|
python examples/offline_inference/text_to_speech/indextts2/end2end.py \
|
||||||
--model IndexTeam/IndexTTS-2 \
|
--model IndexTeam/IndexTTS-2 \
|
||||||
@@ -445,24 +449,6 @@ python examples/offline_inference/text_to_speech/indextts2/end2end.py \
|
|||||||
--ref-audio /path/to/reference.wav
|
--ref-audio /path/to/reference.wav
|
||||||
```
|
```
|
||||||
|
|
||||||
IndexTTS-2 uses its existing frontend and does not require the IndexTTS-2.5 text dependencies. Before running IndexTTS-2.5, install its optional frontend dependencies:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install 'vllm-omni[indextts2]'
|
|
||||||
```
|
|
||||||
|
|
||||||
IndexTTS-2.5 with a native repository/lab bundle whose model root contains `checkpoints/`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python examples/offline_inference/text_to_speech/indextts2/end2end.py \
|
|
||||||
--model /path/to/indextts-2.5 \
|
|
||||||
--model-version 2.5 \
|
|
||||||
--lang ja \
|
|
||||||
--text "こんにちは、音声合成のテストです。" \
|
|
||||||
--ref-audio /path/to/reference.wav
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### Emotion control
|
### Emotion control
|
||||||
```bash
|
```bash
|
||||||
# Emotion from reference audio
|
# Emotion from reference audio
|
||||||
@@ -488,13 +474,10 @@ python examples/offline_inference/text_to_speech/indextts2/end2end.py \
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Notes
|
### Notes
|
||||||
- `--ref-audio` is **required** — neither version provides text-only synthesis.
|
- `--ref-audio` is **required** — IndexTTS-2 does not support text-only synthesis.
|
||||||
- IndexTTS-2.5 Stage 0 (AR Talker) generates mel codes from text + reference audio using plain vLLM sampling. It does not reproduce the official default `num_beams=3` beam search. For parity comparisons, run upstream with `num_beams=1`; output quality can differ from the official beam-search result.
|
- Stage 0 (AR Talker): GPT-2 generates mel codes from text + reference audio.
|
||||||
- IndexTTS-2.5 accepts language codes such as `zh`, `en`, `zhen` (mixed Chinese/English), `ja`, and `yue`. `Mandarin` is a vLLM-Omni convenience alias for `zh`. Japanese (`ja`) uses `fugashi` tokenization and produces audio, but it does not automatically expand numbers, dates, or percentages; write those inputs as readable Japanese text before calling the model. Use `--no-text-normalization` only when the input is already normalized.
|
- Stage 1 (S2Mel + BigVGAN): CFM DiT converts mel codes to waveform at 22.05 kHz.
|
||||||
- A seed controls Stage 0 AR sampling and per-request CFM noise. Different concurrent batch compositions do not guarantee a bit-identical waveform.
|
- Deploy config: `vllm_omni/deploy/indextts2.yaml`. Stage 1 runs with `enforce_eager: true` (DiT has dynamic shapes).
|
||||||
- Stage 1 (semantic codec + S2Mel + BigVGAN): CFM/DiT converts the complete semantic-code sequence to waveform.
|
|
||||||
- Deploy configs: `vllm_omni/deploy/indextts2.yaml` and `indextts2_5.yaml`.
|
|
||||||
- `async_chunk=false` is intentional for IndexTTS-2.5 correctness: S2Mel consumes the completed semantic sequence.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ def run_e2e():
|
|||||||
print(f"Received {len(outputs)} outputs.")
|
print(f"Received {len(outputs)} outputs.")
|
||||||
for i, output in enumerate(outputs):
|
for i, output in enumerate(outputs):
|
||||||
try:
|
try:
|
||||||
ro = output
|
ro = output.request_output
|
||||||
if ro is None:
|
if ro is None:
|
||||||
print("No request_output found.")
|
print("No request_output found.")
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ 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(
|
DEFAULT_STAGE_CONFIG = os.path.join(
|
||||||
os.path.dirname(__file__),
|
os.path.dirname(__file__),
|
||||||
"..",
|
"..",
|
||||||
"..",
|
"..",
|
||||||
@@ -152,7 +152,10 @@ 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,
|
||||||
)
|
)
|
||||||
omni_kwargs["deploy_config"] = args.deploy_config or DEFAULT_DEPLOY_CONFIG
|
if args.deploy_config:
|
||||||
|
omni_kwargs["deploy_config"] = args.deploy_config
|
||||||
|
else:
|
||||||
|
omni_kwargs["stage_configs_path"] = args.stage_configs_path or DEFAULT_STAGE_CONFIG
|
||||||
return omni_kwargs
|
return omni_kwargs
|
||||||
|
|
||||||
|
|
||||||
@@ -176,7 +179,7 @@ def main(args):
|
|||||||
|
|
||||||
t_start = time.perf_counter()
|
t_start = time.perf_counter()
|
||||||
for stage_outputs in omni.generate(inputs):
|
for stage_outputs in omni.generate(inputs):
|
||||||
request_output = stage_outputs
|
request_output = stage_outputs.request_output
|
||||||
if request_output is None or not request_output.outputs:
|
if request_output is None or not request_output.outputs:
|
||||||
continue
|
continue
|
||||||
_save_wav(
|
_save_wav(
|
||||||
@@ -212,7 +215,7 @@ async def main_streaming(args):
|
|||||||
sample_rate = None
|
sample_rate = None
|
||||||
|
|
||||||
async for stage_output in omni.generate(prompt, request_id=request_id):
|
async for stage_output in omni.generate(prompt, request_id=request_id):
|
||||||
mm = stage_output.outputs[0].multimodal_output
|
mm = stage_output.request_output.outputs[0].multimodal_output
|
||||||
if not stage_output.finished:
|
if not stage_output.finished:
|
||||||
t_now = time.perf_counter()
|
t_now = time.perf_counter()
|
||||||
sample_rate, n = _collect_audio_chunk(mm, all_audio_chunks, sample_rate)
|
sample_rate, n = _collect_audio_chunk(mm, all_audio_chunks, sample_rate)
|
||||||
@@ -275,6 +278,12 @@ def parse_args():
|
|||||||
default=None,
|
default=None,
|
||||||
help="Deploy config YAML path (under vllm_omni/deploy/).",
|
help="Deploy config YAML path (under vllm_omni/deploy/).",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--stage-configs-path",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Path to stage configs YAML (legacy, prefer --deploy-config).",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--output-dir",
|
"--output-dir",
|
||||||
default="output_audio",
|
default="output_audio",
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ def main(args):
|
|||||||
|
|
||||||
omni = Omni(
|
omni = Omni(
|
||||||
model=args.model,
|
model=args.model,
|
||||||
deploy_config=deploy_config_path,
|
stage_configs_path=deploy_config_path,
|
||||||
log_stats=args.log_stats,
|
log_stats=args.log_stats,
|
||||||
stage_init_timeout=args.stage_init_timeout,
|
stage_init_timeout=args.stage_init_timeout,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
# 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
|
||||||
"""Offline inference example for IndexTTS 2.0 and 2.5 via vLLM-Omni.
|
"""Offline inference example for IndexTTS2 via vLLM-Omni.
|
||||||
|
|
||||||
Two-stage pipeline: GPT AR (Stage 0) → semantic codec + S2Mel + BigVGAN
|
Two-stage pipeline: GPT AR (Stage 0) → S2Mel + BigVGAN (Stage 1).
|
||||||
(Stage 1).
|
|
||||||
Output is 22050 Hz mono WAV.
|
Output is 22050 Hz mono WAV.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python end2end.py \
|
python end2end.py \
|
||||||
--model /path/to/indextts-2.5 \
|
--model /path/to/IndexTeam/IndexTTS-2 \
|
||||||
--model-version 2.5 \
|
|
||||||
--lang zh \
|
|
||||||
--text "你好,这是一个语音合成测试。" \
|
--text "你好,这是一个语音合成测试。" \
|
||||||
--ref-audio /path/to/ref.wav
|
--ref-audio /path/to/ref.wav
|
||||||
|
|
||||||
@@ -50,10 +47,6 @@ def build_request(
|
|||||||
emo_alpha: float | None = None,
|
emo_alpha: float | None = None,
|
||||||
use_emo_text: bool = False,
|
use_emo_text: bool = False,
|
||||||
use_random: bool = False,
|
use_random: bool = False,
|
||||||
model_type: str = "indextts2",
|
|
||||||
lang: str = "zh",
|
|
||||||
text_normalization: bool = True,
|
|
||||||
tokenizer_file: str | None = None,
|
|
||||||
) -> dict:
|
) -> dict:
|
||||||
additional: dict = {"text": [text]}
|
additional: dict = {"text": [text]}
|
||||||
if ref_audio_path:
|
if ref_audio_path:
|
||||||
@@ -70,44 +63,12 @@ def build_request(
|
|||||||
additional["use_emo_text"] = [True]
|
additional["use_emo_text"] = [True]
|
||||||
if use_random:
|
if use_random:
|
||||||
additional["use_random"] = [True]
|
additional["use_random"] = [True]
|
||||||
if model_type == "indextts2_5":
|
|
||||||
additional["lang"] = [lang]
|
|
||||||
additional["text_normalization"] = [text_normalization]
|
|
||||||
prompt_kwargs = {
|
|
||||||
"model_type": model_type,
|
|
||||||
"lang": lang,
|
|
||||||
"text_normalization": text_normalization,
|
|
||||||
}
|
|
||||||
if tokenizer_file is not None:
|
|
||||||
prompt_kwargs["tokenizer_file"] = tokenizer_file
|
|
||||||
return {
|
return {
|
||||||
"prompt_token_ids": build_indextts2_prefill_prompt_ids(
|
"prompt_token_ids": build_indextts2_prefill_prompt_ids(model, text),
|
||||||
model,
|
|
||||||
text,
|
|
||||||
**prompt_kwargs,
|
|
||||||
),
|
|
||||||
"additional_information": additional,
|
"additional_information": additional,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _get_stage0_tokenizer_file(omni: Omni) -> str | None:
|
|
||||||
"""Read the effective tokenizer override from the resolved Stage 0 config."""
|
|
||||||
stage_configs = omni.stage_configs
|
|
||||||
if not stage_configs:
|
|
||||||
return None
|
|
||||||
engine_args = getattr(stage_configs[0], "engine_args", None)
|
|
||||||
if engine_args is None:
|
|
||||||
return None
|
|
||||||
hf_overrides = getattr(engine_args, "hf_overrides", None)
|
|
||||||
if hf_overrides is None and isinstance(engine_args, dict):
|
|
||||||
hf_overrides = engine_args.get("hf_overrides")
|
|
||||||
if hf_overrides is None:
|
|
||||||
return None
|
|
||||||
if hasattr(hf_overrides, "get"):
|
|
||||||
return hf_overrides.get("tokenizer_file")
|
|
||||||
return getattr(hf_overrides, "tokenizer_file", None)
|
|
||||||
|
|
||||||
|
|
||||||
def save_audio(waveform: torch.Tensor, path: str, sample_rate: int = 22050) -> None:
|
def save_audio(waveform: torch.Tensor, path: str, sample_rate: int = 22050) -> None:
|
||||||
audio_np = waveform.float().numpy()
|
audio_np = waveform.float().numpy()
|
||||||
sf.write(path, audio_np, sample_rate)
|
sf.write(path, audio_np, sample_rate)
|
||||||
@@ -134,14 +95,9 @@ def extract_audio(mm: dict) -> tuple[torch.Tensor | None, int]:
|
|||||||
|
|
||||||
|
|
||||||
def main(args) -> None:
|
def main(args) -> None:
|
||||||
model_type = "indextts2_5" if args.model_version == "2.5" else "indextts2"
|
|
||||||
deploy_config = args.deploy_config
|
|
||||||
if deploy_config is None and args.model_version == "2.5":
|
|
||||||
repo_root = Path(__file__).resolve().parents[4]
|
|
||||||
deploy_config = str(repo_root / "vllm_omni" / "deploy" / "indextts2_5.yaml")
|
|
||||||
omni = Omni(
|
omni = Omni(
|
||||||
model=args.model,
|
model=args.model,
|
||||||
deploy_config=deploy_config,
|
deploy_config=args.deploy_config,
|
||||||
stage_init_timeout=args.stage_init_timeout,
|
stage_init_timeout=args.stage_init_timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -170,7 +126,7 @@ def main(args) -> None:
|
|||||||
print(f"Synthesizing: {args.text!r}")
|
print(f"Synthesizing: {args.text!r}")
|
||||||
if args.ref_audio:
|
if args.ref_audio:
|
||||||
print(f" ref_audio: {args.ref_audio}")
|
print(f" ref_audio: {args.ref_audio}")
|
||||||
request_kwargs = dict(
|
inputs = build_request(
|
||||||
model=args.model,
|
model=args.model,
|
||||||
text=args.text,
|
text=args.text,
|
||||||
ref_audio_path=args.ref_audio,
|
ref_audio_path=args.ref_audio,
|
||||||
@@ -180,17 +136,7 @@ def main(args) -> None:
|
|||||||
emo_alpha=args.emo_alpha,
|
emo_alpha=args.emo_alpha,
|
||||||
use_emo_text=args.use_emo_text,
|
use_emo_text=args.use_emo_text,
|
||||||
use_random=args.use_random,
|
use_random=args.use_random,
|
||||||
model_type=model_type,
|
|
||||||
lang=args.lang,
|
|
||||||
text_normalization=args.text_normalization,
|
|
||||||
)
|
)
|
||||||
if model_type == "indextts2_5":
|
|
||||||
tokenizer_file = _get_stage0_tokenizer_file(omni)
|
|
||||||
if tokenizer_file is not None:
|
|
||||||
request_kwargs["tokenizer_file"] = tokenizer_file
|
|
||||||
inputs = build_request(**request_kwargs)
|
|
||||||
if model_type == "indextts2_5":
|
|
||||||
inputs["additional_information"]["duration_factor"] = [1.0 / getattr(args, "speed", 1.0)]
|
|
||||||
|
|
||||||
for i, omni_out in enumerate(omni.generate(inputs, sampling_params_list=sampling_params)):
|
for i, omni_out in enumerate(omni.generate(inputs, sampling_params_list=sampling_params)):
|
||||||
mm = omni_out.multimodal_output
|
mm = omni_out.multimodal_output
|
||||||
@@ -208,37 +154,8 @@ def main(args) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
parser = FlexibleArgumentParser(description="IndexTTS 2.0/2.5 offline inference")
|
parser = FlexibleArgumentParser(description="IndexTTS2 offline inference")
|
||||||
parser.add_argument(
|
parser.add_argument("--model", required=True, help="HF model path for IndexTTS2.")
|
||||||
"--model",
|
|
||||||
required=True,
|
|
||||||
help="HF model path or native bundle containing checkpoints/.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--model-version",
|
|
||||||
choices=("2.0", "2.5"),
|
|
||||||
default="2.0",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--lang",
|
|
||||||
default="zh",
|
|
||||||
help=(
|
|
||||||
"IndexTTS 2.5 language code, for example zh/en/zhen/ja/yue; "
|
|
||||||
"zhen is mixed Chinese/English and Mandarin is a vLLM-Omni alias for zh."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--no-text-normalization",
|
|
||||||
action="store_false",
|
|
||||||
dest="text_normalization",
|
|
||||||
help="Disable IndexTTS 2.5 text normalization.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--speed",
|
|
||||||
type=float,
|
|
||||||
default=1.0,
|
|
||||||
help="IndexTTS 2.5 native synthesis speed in [0.5, 2.0]; 2.0 is faster.",
|
|
||||||
)
|
|
||||||
parser.add_argument("--text", default="你好,这是IndexTTS2语音合成测试。")
|
parser.add_argument("--text", default="你好,这是IndexTTS2语音合成测试。")
|
||||||
parser.add_argument("--ref-audio", required=True, help="Reference audio for voice cloning.")
|
parser.add_argument("--ref-audio", required=True, help="Reference audio for voice cloning.")
|
||||||
parser.add_argument("--emo-audio", default=None, help="Emotion reference audio.")
|
parser.add_argument("--emo-audio", default=None, help="Emotion reference audio.")
|
||||||
@@ -253,15 +170,7 @@ def parse_args():
|
|||||||
parser.add_argument("--emo-alpha", type=float, default=None, help="Emotion weight in [0, 1].")
|
parser.add_argument("--emo-alpha", type=float, default=None, help="Emotion weight in [0, 1].")
|
||||||
parser.add_argument("--use-emo-text", action="store_true", help="Infer emotion vector from emo-text or text.")
|
parser.add_argument("--use-emo-text", action="store_true", help="Infer emotion vector from emo-text or text.")
|
||||||
parser.add_argument("--use-random", action="store_true", help="Use random emotion prototypes.")
|
parser.add_argument("--use-random", action="store_true", help="Use random emotion prototypes.")
|
||||||
parser.add_argument(
|
parser.add_argument("--seed", type=int, default=None)
|
||||||
"--seed",
|
|
||||||
type=int,
|
|
||||||
default=None,
|
|
||||||
help=(
|
|
||||||
"Seed Stage 0 AR sampling and per-request CFM noise; changing the "
|
|
||||||
"concurrent batch composition may still change the final waveform."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--output-dir",
|
"--output-dir",
|
||||||
default=os.path.join(
|
default=os.path.join(
|
||||||
@@ -271,10 +180,7 @@ def parse_args():
|
|||||||
)
|
)
|
||||||
parser.add_argument("--deploy-config", default=None)
|
parser.add_argument("--deploy-config", default=None)
|
||||||
parser.add_argument("--stage-init-timeout", type=int, default=600)
|
parser.add_argument("--stage-init-timeout", type=int, default=600)
|
||||||
args = parser.parse_args()
|
return parser.parse_args()
|
||||||
if args.model_version == "2.5" and not 0.5 <= args.speed <= 2.0:
|
|
||||||
parser.error("IndexTTS 2.5 --speed must be between 0.5 and 2.0")
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ def main(args) -> None:
|
|||||||
params_list = sampling_params
|
params_list = sampling_params
|
||||||
|
|
||||||
for stage_outputs in omni.generate(inputs, params_list):
|
for stage_outputs in omni.generate(inputs, params_list):
|
||||||
for i, req_output in enumerate(stage_outputs):
|
for i, req_output in enumerate(stage_outputs.request_output):
|
||||||
for j, out in enumerate(req_output.outputs):
|
for j, out in enumerate(req_output.outputs):
|
||||||
mm = out.multimodal_output
|
mm = out.multimodal_output
|
||||||
if mm is None:
|
if mm is None:
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ def run_e2e():
|
|||||||
print(f"Received {len(outputs)} outputs.")
|
print(f"Received {len(outputs)} outputs.")
|
||||||
for i, output in enumerate(outputs):
|
for i, output in enumerate(outputs):
|
||||||
try:
|
try:
|
||||||
ro = output
|
ro = output.request_output
|
||||||
if ro is None:
|
if ro is None:
|
||||||
print("No request_output found.")
|
print("No request_output found.")
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -391,7 +391,7 @@ def main(args):
|
|||||||
for batch_start in range(0, len(inputs), batch_size):
|
for batch_start in range(0, len(inputs), batch_size):
|
||||||
batch = inputs[batch_start : batch_start + batch_size]
|
batch = inputs[batch_start : batch_start + batch_size]
|
||||||
for stage_outputs in omni.generate(batch):
|
for stage_outputs in omni.generate(batch):
|
||||||
output = stage_outputs
|
output = stage_outputs.request_output
|
||||||
_save_wav(output_dir, output.request_id, output.outputs[0].multimodal_output)
|
_save_wav(output_dir, output.request_id, output.outputs[0].multimodal_output)
|
||||||
|
|
||||||
|
|
||||||
@@ -412,7 +412,7 @@ async def main_streaming(args):
|
|||||||
t_prev = t_start
|
t_prev = t_start
|
||||||
chunk_idx = 0
|
chunk_idx = 0
|
||||||
async for stage_output in omni.generate(prompt, request_id=request_id):
|
async for stage_output in omni.generate(prompt, request_id=request_id):
|
||||||
mm = stage_output.outputs[0].multimodal_output
|
mm = stage_output.request_output.outputs[0].multimodal_output
|
||||||
if not stage_output.finished:
|
if not stage_output.finished:
|
||||||
t_now = time.perf_counter()
|
t_now = time.perf_counter()
|
||||||
audio = mm.get("audio")
|
audio = mm.get("audio")
|
||||||
@@ -490,10 +490,10 @@ def parse_args():
|
|||||||
help="Path to a .txt file with one prompt per line (preferred).",
|
help="Path to a .txt file with one prompt per line (preferred).",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--deploy-config",
|
"--stage-configs-path",
|
||||||
type=str,
|
type=str,
|
||||||
default=None,
|
default=None,
|
||||||
help="Path to a deploy config YAML.",
|
help="Path to a stage configs file.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--audio-path",
|
"--audio-path",
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ def main(args: Any) -> None:
|
|||||||
prompt = _build_custom_voice_input(args)
|
prompt = _build_custom_voice_input(args)
|
||||||
final_output = None
|
final_output = None
|
||||||
for stage_outputs in omni.generate([prompt]):
|
for stage_outputs in omni.generate([prompt]):
|
||||||
final_output = stage_outputs
|
final_output = stage_outputs.request_output
|
||||||
if final_output is None:
|
if final_output is None:
|
||||||
raise RuntimeError("Qwen3-TTS did not produce an output.")
|
raise RuntimeError("Qwen3-TTS did not produce an output.")
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def _stage_durations_from_output(outputs) -> dict[str, float]:
|
|||||||
durations = getattr(omni_out, "stage_durations", None) or {}
|
durations = getattr(omni_out, "stage_durations", None) or {}
|
||||||
if durations:
|
if durations:
|
||||||
return dict(durations)
|
return dict(durations)
|
||||||
ro = omni_out
|
ro = getattr(omni_out, "request_output", None)
|
||||||
if ro is not None:
|
if ro is not None:
|
||||||
return dict(getattr(ro, "stage_durations", None) or {})
|
return dict(getattr(ro, "stage_durations", None) or {})
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ def extract_audio(outputs) -> tuple[np.ndarray, int]:
|
|||||||
out = outputs[0]
|
out = outputs[0]
|
||||||
mm = getattr(out, "multimodal_output", None)
|
mm = getattr(out, "multimodal_output", None)
|
||||||
if mm is None:
|
if mm is None:
|
||||||
ro = out
|
ro = getattr(out, "request_output", None)
|
||||||
if ro is not None:
|
if ro is not None:
|
||||||
mm = getattr(ro, "multimodal_output", None)
|
mm = getattr(ro, "multimodal_output", None)
|
||||||
if mm is None and ro.outputs:
|
if mm is None and ro.outputs:
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ A unified script for text-to-video generation. Supports multiple models with mod
|
|||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| `Wan-AI/Wan2.1-VACE-1.3B-diffusers` | 480x832 | 81 | 30 | 5.0 | ~20 GiB (RTX 5090, VAE tiling) |
|
| `Wan-AI/Wan2.1-VACE-1.3B-diffusers` | 480x832 | 81 | 30 | 5.0 | ~20 GiB (RTX 5090, VAE tiling) |
|
||||||
| `Wan-AI/Wan2.2-T2V-A14B-Diffusers` | 720x1280 | 81 | 40 | 4.0 | ~60 GiB |
|
| `Wan-AI/Wan2.2-T2V-A14B-Diffusers` | 720x1280 | 81 | 40 | 4.0 | ~60 GiB |
|
||||||
| `robbyant/lingbot-video-dense-1.3b` / `robbyant/lingbot-video-moe-30b-a3b` | 192x320 | 9 | 2 | 3.0 | ~68 GiB (MoE smoke) |
|
|
||||||
| `Lightricks/LTX-2` | 512x768 | 121 | 40 | video 3.0 / audio 7.0 | Model-dependent |
|
| `Lightricks/LTX-2` | 512x768 | 121 | 40 | video 3.0 / audio 7.0 | Model-dependent |
|
||||||
| `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v` | 480x832 | 121 | 50 | 6.0 | 1×A100 80GB |
|
| `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v` | 480x832 | 121 | 50 | 6.0 | 1×A100 80GB |
|
||||||
| `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-720p_t2v` | 720x1280 | 121 | 50 | 6.0 | FP8 + VAE tiling required |
|
| `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-720p_t2v` | 720x1280 | 121 | 50 | 6.0 | FP8 + VAE tiling required |
|
||||||
@@ -58,31 +57,18 @@ python text_to_video.py \
|
|||||||
|
|
||||||
### LingBot-Video
|
### LingBot-Video
|
||||||
|
|
||||||
The shared runner recognizes the official dense and MoE checkpoint IDs, selects
|
The dedicated LingBot example explicitly selects the video output modality:
|
||||||
`LingBotVideoPipeline`, and builds the canonical video request envelope:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python text_to_video.py \
|
python examples/offline_inference/text_to_video/text_to_video_lingbot.py \
|
||||||
--model robbyant/lingbot-video-dense-1.3b \
|
--model robbyant/lingbot-video-dense-1.3b \
|
||||||
--prompt "a robotic arm picks up a red block" \
|
|
||||||
--height 192 --width 320 --num-frames 9 --num-inference-steps 2 \
|
--height 192 --width 320 --num-frames 9 --num-inference-steps 2 \
|
||||||
--guidance-scale 3.0 --flow-shift 3.0 --fps 24 \
|
|
||||||
--output lingbot_t2v.mp4
|
--output lingbot_t2v.mp4
|
||||||
```
|
```
|
||||||
|
|
||||||
Use the same command with `robbyant/lingbot-video-moe-30b-a3b` for the MoE
|
Use the same command with `robbyant/lingbot-video-moe-30b-a3b` for the MoE
|
||||||
checkpoint. Requested frame counts are rounded upward to the causal
|
checkpoint. Requested frame counts are rounded upward to the causal
|
||||||
VAE's `4n+1` grid. For a local checkpoint path whose name does not contain
|
VAE's `4n+1` grid.
|
||||||
`lingbot`, pass `--model-class-name LingBotVideoPipeline`.
|
|
||||||
|
|
||||||
LingBot-only options such as `batch_cfg` and `output_type` use the shared
|
|
||||||
model-extra channel, for example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python text_to_video.py \
|
|
||||||
--model robbyant/lingbot-video-dense-1.3b \
|
|
||||||
--extra-body '{"batch_cfg": true, "output_type": "np"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### LTX-2
|
### LTX-2
|
||||||
|
|
||||||
@@ -232,9 +218,6 @@ python text_to_video.py \
|
|||||||
- `--audio-sample-rate`: fallback audio sample rate when the pipeline returns audio.
|
- `--audio-sample-rate`: fallback audio sample rate when the pipeline returns audio.
|
||||||
- `--quantization`: quantization method (such as `fp8` for FP8).
|
- `--quantization`: quantization method (such as `fp8` for FP8).
|
||||||
- `--flow-shift`: scheduler flow_shift parameter.
|
- `--flow-shift`: scheduler flow_shift parameter.
|
||||||
- `--lora-path`: path to PEFT LoRA adapter folder or checkpoint file.
|
|
||||||
- `--lora-scale`: scale factor for LoRA weights.
|
|
||||||
- `--lora-backend`: backend for loading LoRA adapters. Default: peft. Available options: peft, distill.
|
|
||||||
- `--extra-body`: JSON object of model-specific generation params, filtered against the model's declared `extra_body_params` (see [`vllm_omni/model_extras`](../../../vllm_omni/model_extras)). Used by Cosmos3 (see above).
|
- `--extra-body`: JSON object of model-specific generation params, filtered against the model's declared `extra_body_params` (see [`vllm_omni/model_extras`](../../../vllm_omni/model_extras)). Used by Cosmos3 (see above).
|
||||||
|
|
||||||
### Wan2.2-specific
|
### Wan2.2-specific
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ from vllm_omni.diffusion.data import DiffusionParallelConfig
|
|||||||
from vllm_omni.diffusion.utils.param_utils import apply_declared_extra_args
|
from vllm_omni.diffusion.utils.param_utils import apply_declared_extra_args
|
||||||
from vllm_omni.entrypoints.omni import Omni
|
from vllm_omni.entrypoints.omni import Omni
|
||||||
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
||||||
from vllm_omni.lora.request import LoRARequest
|
from vllm_omni.model_extras import get_extra_body_params, get_model_class_name
|
||||||
from vllm_omni.lora.utils import stable_lora_int_id
|
|
||||||
from vllm_omni.model_extras import get_extra_body_params, get_model_class_name, get_output_tensor_range
|
|
||||||
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
|
||||||
|
|
||||||
@@ -80,17 +78,6 @@ _MODEL_PRESETS = {
|
|||||||
"fps": 16,
|
"fps": 16,
|
||||||
"output": "helios_output.mp4",
|
"output": "helios_output.mp4",
|
||||||
},
|
},
|
||||||
"lingbot": {
|
|
||||||
"model_class_name": "LingBotVideoPipeline",
|
|
||||||
"height": 192,
|
|
||||||
"width": 320,
|
|
||||||
"num_frames": 9,
|
|
||||||
"num_inference_steps": 2,
|
|
||||||
"guidance_scale": 3.0,
|
|
||||||
"fps": 24,
|
|
||||||
"flow_shift": 3.0,
|
|
||||||
"output": "lingbot_video_output.mp4",
|
|
||||||
},
|
|
||||||
"ltx2": {
|
"ltx2": {
|
||||||
"height": 512,
|
"height": 512,
|
||||||
"width": 768,
|
"width": 768,
|
||||||
@@ -121,51 +108,27 @@ _MODEL_PRESETS = {
|
|||||||
def _detect_preset(model: str, model_class_name: str | None = None) -> dict:
|
def _detect_preset(model: str, model_class_name: str | None = None) -> dict:
|
||||||
model_lower = model.lower()
|
model_lower = model.lower()
|
||||||
class_lower = (model_class_name or "").lower()
|
class_lower = (model_class_name or "").lower()
|
||||||
if "lingbot" in model_lower or "lingbotvideo" in class_lower:
|
if "distilled" in class_lower or "distilled" in model_lower:
|
||||||
return _MODEL_PRESETS["lingbot"]
|
return _MODEL_PRESETS["ltx2_distilled"]
|
||||||
if "ltx" in class_lower or "ltx" in model_lower:
|
if "ltx23" in class_lower or "ltx-2.3" in model_lower or "ltx_2.3" in model_lower:
|
||||||
if "distilled" in class_lower or "distilled" in model_lower:
|
return _MODEL_PRESETS["ltx23"]
|
||||||
return _MODEL_PRESETS["ltx2_distilled"]
|
if "ltx2" in class_lower or "ltx-2" in model_lower or "ltx_2" in model_lower:
|
||||||
if "ltx23" in class_lower or "ltx-2.3" in model_lower or "ltx_2.3" in model_lower:
|
|
||||||
return _MODEL_PRESETS["ltx23"]
|
|
||||||
return _MODEL_PRESETS["ltx2"]
|
return _MODEL_PRESETS["ltx2"]
|
||||||
if "vace" in model_lower or "vace" in class_lower:
|
if "vace" in model_lower:
|
||||||
return _MODEL_PRESETS["vace"]
|
return _MODEL_PRESETS["vace"]
|
||||||
# Edge must be matched before the generic cosmos branch (its "cosmos" substring would
|
# Edge must be matched before the generic cosmos branch (its "cosmos" substring would
|
||||||
# otherwise pick up the Nano/Super 720p / gs 6.0 / flow_shift 10.0 preset).
|
# otherwise pick up the Nano/Super 720p / gs 6.0 / flow_shift 10.0 preset).
|
||||||
if ("cosmos" in model_lower or "cosmos" in class_lower) and ("edge" in model_lower or "edge" in class_lower):
|
if ("cosmos" in model_lower or "cosmos" in class_lower) and ("edge" in model_lower or "edge" in class_lower):
|
||||||
return _MODEL_PRESETS["cosmos3_edge"]
|
return _MODEL_PRESETS["cosmos3_edge"]
|
||||||
if "cosmos" in model_lower or "cosmos" in class_lower:
|
if "cosmos" in model_lower:
|
||||||
return _MODEL_PRESETS["cosmos"]
|
return _MODEL_PRESETS["cosmos"]
|
||||||
if "hunyuan" in model_lower or "hunyuan" in class_lower:
|
if "hunyuan" in model_lower:
|
||||||
return _MODEL_PRESETS["hunyuan"]
|
return _MODEL_PRESETS["hunyuan"]
|
||||||
if "helios" in model_lower or "helios" in class_lower:
|
if "helios" in model_lower:
|
||||||
return _MODEL_PRESETS["helios"]
|
return _MODEL_PRESETS["helios"]
|
||||||
return _MODEL_PRESETS["wan"]
|
return _MODEL_PRESETS["wan"]
|
||||||
|
|
||||||
|
|
||||||
def build_text_to_video_prompt(prompt: str, negative_prompt: str | None) -> dict[str, Any]:
|
|
||||||
"""Build the canonical request envelope for the shared T2V example."""
|
|
||||||
result: dict[str, Any] = {
|
|
||||||
"prompt": prompt,
|
|
||||||
"modalities": ["video"],
|
|
||||||
}
|
|
||||||
if negative_prompt is not None:
|
|
||||||
result["negative_prompt"] = negative_prompt
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_float_tensor(tensor: torch.Tensor, source_range: str) -> torch.Tensor:
|
|
||||||
"""Normalize decoded frames according to the pipeline's output contract."""
|
|
||||||
if not tensor.is_floating_point():
|
|
||||||
return tensor
|
|
||||||
if source_range == "negative_one_to_one":
|
|
||||||
return tensor.clamp(-1, 1) * 0.5 + 0.5
|
|
||||||
if source_range == "zero_to_one":
|
|
||||||
return tensor.clamp(0, 1)
|
|
||||||
raise ValueError(f"Unsupported floating-point tensor range: {source_range!r}")
|
|
||||||
|
|
||||||
|
|
||||||
def parse_profiler_config(value: str) -> dict[str, Any]:
|
def parse_profiler_config(value: str) -> dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
config = json.loads(value)
|
config = json.loads(value)
|
||||||
@@ -195,7 +158,7 @@ def parse_extra_body(value: str) -> dict[str, Any]:
|
|||||||
def parse_args() -> argparse.Namespace:
|
def parse_args() -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Generate a video from a text prompt. "
|
description="Generate a video from a text prompt. "
|
||||||
"Supports Wan2.2, HunyuanVideo-1.5, Helios, LingBot-Video, and other text-to-video models."
|
"Supports Wan2.2, HunyuanVideo-1.5, Helios, and other text-to-video models."
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--model",
|
"--model",
|
||||||
@@ -371,29 +334,6 @@ def parse_args() -> argparse.Namespace:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Enable expert parallelism for MoE layers.",
|
help="Enable expert parallelism for MoE layers.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--lora-path",
|
|
||||||
type=str,
|
|
||||||
nargs="+",
|
|
||||||
default=None,
|
|
||||||
help="Path to LoRA adapter folder (PEFT format) or concrete LoRA checkpoint files. Loaded at initialization and used for generation."
|
|
||||||
"Note: for Wan2.2 MoE models, two checkpoints positionally mapped to high-noise and low-noise modules.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--lora-scale",
|
|
||||||
type=float,
|
|
||||||
default=1.0,
|
|
||||||
help="Scale factor for LoRA weights (default: 1.0).",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--lora-backend",
|
|
||||||
type=str,
|
|
||||||
default="peft",
|
|
||||||
choices=["peft", "distill"],
|
|
||||||
help="LoRA backend for loading LoRA adapters. Default: peft"
|
|
||||||
"'peft' loads a PEFT-format adapter folder, used e.g. for RL"
|
|
||||||
"'distill' fuses one or more concrete LoRA checkpoint files, used e.g. for distilled few-step LoRAs",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--use-hsdp",
|
"--use-hsdp",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -425,7 +365,7 @@ def _extract_peak_memory_mb(result: Any) -> float:
|
|||||||
return 0.0
|
return 0.0
|
||||||
val = getattr(result, "peak_memory_mb", 0.0)
|
val = getattr(result, "peak_memory_mb", 0.0)
|
||||||
if not val:
|
if not val:
|
||||||
inner = result
|
inner = getattr(result, "request_output", None)
|
||||||
if isinstance(inner, list):
|
if isinstance(inner, list):
|
||||||
inner = inner[0] if inner else None
|
inner = inner[0] if inner else None
|
||||||
val = getattr(inner, "peak_memory_mb", 0.0)
|
val = getattr(inner, "peak_memory_mb", 0.0)
|
||||||
@@ -443,7 +383,6 @@ def main():
|
|||||||
for key, default_val in preset.items():
|
for key, default_val in preset.items():
|
||||||
if getattr(args, key.replace("-", "_"), None) is None:
|
if getattr(args, key.replace("-", "_"), None) is None:
|
||||||
setattr(args, key.replace("-", "_"), default_val)
|
setattr(args, key.replace("-", "_"), default_val)
|
||||||
model_class_name = args.model_class_name
|
|
||||||
|
|
||||||
generator = torch.Generator(device=current_omni_platform.device_type).manual_seed(args.seed)
|
generator = torch.Generator(device=current_omni_platform.device_type).manual_seed(args.seed)
|
||||||
# Cache-dit config (Wan2.2 only)
|
# Cache-dit config (Wan2.2 only)
|
||||||
@@ -501,12 +440,6 @@ def main():
|
|||||||
omni_kwargs["cache_backend"] = args.cache_backend
|
omni_kwargs["cache_backend"] = args.cache_backend
|
||||||
omni_kwargs["cache_config"] = cache_config
|
omni_kwargs["cache_config"] = cache_config
|
||||||
omni_kwargs["enable_cache_dit_summary"] = args.enable_cache_dit_summary
|
omni_kwargs["enable_cache_dit_summary"] = args.enable_cache_dit_summary
|
||||||
if args.lora_path is not None:
|
|
||||||
lora_path = args.lora_path
|
|
||||||
if len(lora_path) == 1:
|
|
||||||
lora_path = lora_path[0]
|
|
||||||
omni_kwargs["lora_path"] = lora_path
|
|
||||||
omni_kwargs["lora_backend"] = args.lora_backend
|
|
||||||
|
|
||||||
# Cosmos3 loads its (gated) guardrail models at build time, so the guardrails
|
# Cosmos3 loads its (gated) guardrail models at build time, so the guardrails
|
||||||
# gate is an engine-level config (offline analog of the server's --no-guardrails).
|
# gate is an engine-level config (offline analog of the server's --no-guardrails).
|
||||||
@@ -516,7 +449,6 @@ def main():
|
|||||||
omni = Omni(**omni_kwargs)
|
omni = Omni(**omni_kwargs)
|
||||||
model_class_name = get_model_class_name(omni) or model_class_name
|
model_class_name = get_model_class_name(omni) or model_class_name
|
||||||
declared_extra_body_params = get_extra_body_params(model_class_name)
|
declared_extra_body_params = get_extra_body_params(model_class_name)
|
||||||
output_tensor_range = get_output_tensor_range(model_class_name)
|
|
||||||
|
|
||||||
if profiler_enabled:
|
if profiler_enabled:
|
||||||
print("[Profiler] Starting profiling...")
|
print("[Profiler] Starting profiling...")
|
||||||
@@ -537,29 +469,12 @@ def main():
|
|||||||
print(f" Video size: {args.width}x{args.height}")
|
print(f" Video size: {args.width}x{args.height}")
|
||||||
print(f"{'=' * 60}\n")
|
print(f"{'=' * 60}\n")
|
||||||
|
|
||||||
lora_request = None
|
prompt_dict = {"prompt": args.prompt}
|
||||||
if args.lora_path and args.lora_backend == "peft":
|
if args.negative_prompt is not None:
|
||||||
if len(args.lora_path) != 1:
|
prompt_dict["negative_prompt"] = args.negative_prompt
|
||||||
raise ValueError("Only one LoRA path is expected for PEFT backend.")
|
elif preset not in (_MODEL_PRESETS["ltx2"], _MODEL_PRESETS["ltx23"]):
|
||||||
|
|
||||||
lora_path = args.lora_path[0]
|
|
||||||
lora_request_id = stable_lora_int_id(lora_path)
|
|
||||||
lora_request = LoRARequest(
|
|
||||||
lora_name=Path(lora_path).stem,
|
|
||||||
lora_int_id=lora_request_id,
|
|
||||||
lora_path=lora_path,
|
|
||||||
)
|
|
||||||
|
|
||||||
negative_prompt = args.negative_prompt
|
|
||||||
if negative_prompt is None and all(preset is not _MODEL_PRESETS[name] for name in ("lingbot", "ltx2", "ltx23")):
|
|
||||||
# Preserve the historical empty-prompt behavior for non-LTX examples.
|
# Preserve the historical empty-prompt behavior for non-LTX examples.
|
||||||
negative_prompt = ""
|
prompt_dict["negative_prompt"] = ""
|
||||||
prompt_dict = build_text_to_video_prompt(args.prompt, negative_prompt)
|
|
||||||
|
|
||||||
extra_args = {}
|
|
||||||
if lora_request:
|
|
||||||
extra_args["lora_request"] = lora_request
|
|
||||||
extra_args["lora_scale"] = args.lora_scale
|
|
||||||
|
|
||||||
sampling_kwargs = dict(
|
sampling_kwargs = dict(
|
||||||
height=args.height,
|
height=args.height,
|
||||||
@@ -568,7 +483,6 @@ def main():
|
|||||||
guidance_scale=args.guidance_scale,
|
guidance_scale=args.guidance_scale,
|
||||||
num_inference_steps=args.num_inference_steps,
|
num_inference_steps=args.num_inference_steps,
|
||||||
num_frames=args.num_frames,
|
num_frames=args.num_frames,
|
||||||
extra_args=extra_args,
|
|
||||||
)
|
)
|
||||||
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
|
||||||
@@ -611,8 +525,8 @@ def main():
|
|||||||
if frames.multimodal_output and "audio" in frames.multimodal_output:
|
if frames.multimodal_output and "audio" in frames.multimodal_output:
|
||||||
audio = frames.multimodal_output["audio"]
|
audio = frames.multimodal_output["audio"]
|
||||||
audio_sample_rate = frames.multimodal_output.get("audio_sample_rate", audio_sample_rate)
|
audio_sample_rate = frames.multimodal_output.get("audio_sample_rate", audio_sample_rate)
|
||||||
if frames.is_pipeline_output and frames is not None:
|
if frames.is_pipeline_output and frames.request_output is not None:
|
||||||
inner_output = frames
|
inner_output = frames.request_output
|
||||||
if isinstance(inner_output, OmniRequestOutput):
|
if isinstance(inner_output, OmniRequestOutput):
|
||||||
if inner_output.multimodal_output and "audio" in inner_output.multimodal_output:
|
if inner_output.multimodal_output and "audio" in inner_output.multimodal_output:
|
||||||
audio = inner_output.multimodal_output["audio"]
|
audio = inner_output.multimodal_output["audio"]
|
||||||
@@ -666,7 +580,8 @@ def main():
|
|||||||
frame_tensor = frame_tensor[0]
|
frame_tensor = frame_tensor[0]
|
||||||
if frame_tensor.dim() == 3 and frame_tensor.shape[0] in (3, 4):
|
if frame_tensor.dim() == 3 and frame_tensor.shape[0] in (3, 4):
|
||||||
frame_tensor = frame_tensor.permute(1, 2, 0)
|
frame_tensor = frame_tensor.permute(1, 2, 0)
|
||||||
frame_tensor = _normalize_float_tensor(frame_tensor, output_tensor_range)
|
if frame_tensor.is_floating_point():
|
||||||
|
frame_tensor = frame_tensor.clamp(-1, 1) * 0.5 + 0.5
|
||||||
return frame_tensor.float().numpy()
|
return frame_tensor.float().numpy()
|
||||||
if isinstance(frame, np.ndarray):
|
if isinstance(frame, np.ndarray):
|
||||||
frame_array = frame
|
frame_array = frame
|
||||||
@@ -718,7 +633,8 @@ def main():
|
|||||||
video_tensor = video_tensor[0]
|
video_tensor = video_tensor[0]
|
||||||
elif video_tensor.dim() == 4 and video_tensor.shape[0] in (3, 4):
|
elif video_tensor.dim() == 4 and video_tensor.shape[0] in (3, 4):
|
||||||
video_tensor = video_tensor.permute(1, 2, 3, 0)
|
video_tensor = video_tensor.permute(1, 2, 3, 0)
|
||||||
video_tensor = _normalize_float_tensor(video_tensor, output_tensor_range)
|
if video_tensor.is_floating_point():
|
||||||
|
video_tensor = video_tensor.clamp(-1, 1) * 0.5 + 0.5
|
||||||
video_array = video_tensor.float().numpy()
|
video_array = video_tensor.float().numpy()
|
||||||
elif isinstance(frames, np.ndarray):
|
elif isinstance(frames, np.ndarray):
|
||||||
video_array = frames
|
video_array = frames
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from vllm_omni.diffusion.data import DiffusionParallelConfig
|
||||||
|
from vllm_omni.entrypoints.omni import Omni
|
||||||
|
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
||||||
|
from vllm_omni.outputs import OmniRequestOutput
|
||||||
|
from vllm_omni.platforms import current_omni_platform
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Generate text-to-video with a LingBot-Video checkpoint.")
|
||||||
|
parser.add_argument("--model", default="robbyant/lingbot-video-dense-1.3b")
|
||||||
|
parser.add_argument("--prompt", default="a robotic arm picks up a red block")
|
||||||
|
parser.add_argument("--negative-prompt", default=None)
|
||||||
|
parser.add_argument("--output", default="lingbot_video_output.mp4")
|
||||||
|
parser.add_argument("--height", type=int, default=192)
|
||||||
|
parser.add_argument("--width", type=int, default=320)
|
||||||
|
parser.add_argument("--num-frames", type=int, default=9)
|
||||||
|
parser.add_argument("--num-inference-steps", type=int, default=2)
|
||||||
|
parser.add_argument("--guidance-scale", type=float, default=3.0)
|
||||||
|
parser.add_argument("--flow-shift", type=float, default=3.0)
|
||||||
|
parser.add_argument("--seed", type=int, default=42)
|
||||||
|
parser.add_argument("--fps", type=int, default=24)
|
||||||
|
parser.add_argument("--output-type", choices=["pt", "np"], default="pt")
|
||||||
|
parser.add_argument("--batch-cfg", action="store_true")
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def _peak_memory_mb(output: Any) -> float:
|
||||||
|
result = output[0] if isinstance(output, list) and output else output
|
||||||
|
return float(getattr(result, "peak_memory_mb", 0.0) or 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_video(output: Any) -> Any:
|
||||||
|
if isinstance(output, list):
|
||||||
|
output = output[0] if output else None
|
||||||
|
if isinstance(output, OmniRequestOutput):
|
||||||
|
if output.final_output_type != "image":
|
||||||
|
raise ValueError(f"Unexpected output type {output.final_output_type!r}.")
|
||||||
|
if not output.images:
|
||||||
|
raise ValueError("No video frames found in OmniRequestOutput.")
|
||||||
|
return output.images[0] if len(output.images) == 1 else output.images
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def _to_frame_array(video: Any) -> np.ndarray:
|
||||||
|
if isinstance(video, torch.Tensor):
|
||||||
|
tensor = video.detach().cpu()
|
||||||
|
if tensor.dim() == 5:
|
||||||
|
tensor = tensor[0]
|
||||||
|
if tensor.dim() == 4 and tensor.shape[-1] not in (3, 4):
|
||||||
|
if tensor.shape[0] in (3, 4):
|
||||||
|
tensor = tensor.permute(1, 2, 3, 0)
|
||||||
|
elif tensor.shape[1] in (3, 4):
|
||||||
|
tensor = tensor.permute(0, 2, 3, 1)
|
||||||
|
if tensor.is_floating_point():
|
||||||
|
if float(tensor.min()) < 0.0:
|
||||||
|
tensor = tensor.clamp(-1, 1) * 0.5 + 0.5
|
||||||
|
else:
|
||||||
|
tensor = tensor.clamp(0, 1)
|
||||||
|
return tensor.float().numpy()
|
||||||
|
if isinstance(video, np.ndarray):
|
||||||
|
array = video[0] if video.ndim == 5 else video
|
||||||
|
if np.issubdtype(array.dtype, np.integer):
|
||||||
|
return array.astype(np.float32) / 255.0
|
||||||
|
return array.astype(np.float32, copy=False)
|
||||||
|
if isinstance(video, list):
|
||||||
|
return np.stack([_to_frame_array(frame) for frame in video], axis=0)
|
||||||
|
return np.asarray(video, dtype=np.float32)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = parse_args()
|
||||||
|
generator = torch.Generator(device=current_omni_platform.device_type).manual_seed(args.seed)
|
||||||
|
omni = Omni(
|
||||||
|
model=args.model,
|
||||||
|
model_class_name="LingBotVideoPipeline",
|
||||||
|
flow_shift=args.flow_shift,
|
||||||
|
parallel_config=DiffusionParallelConfig(),
|
||||||
|
)
|
||||||
|
|
||||||
|
prompt: dict[str, Any] = {
|
||||||
|
"prompt": args.prompt,
|
||||||
|
"modalities": ["video"],
|
||||||
|
"num_frames": args.num_frames,
|
||||||
|
}
|
||||||
|
if args.negative_prompt is not None:
|
||||||
|
prompt["negative_prompt"] = args.negative_prompt
|
||||||
|
|
||||||
|
sampling_params = OmniDiffusionSamplingParams(
|
||||||
|
height=args.height,
|
||||||
|
width=args.width,
|
||||||
|
num_frames=args.num_frames,
|
||||||
|
num_inference_steps=args.num_inference_steps,
|
||||||
|
guidance_scale=args.guidance_scale,
|
||||||
|
generator=generator,
|
||||||
|
output_type=args.output_type,
|
||||||
|
extra_args={"batch_cfg": args.batch_cfg, "shift": args.flow_shift},
|
||||||
|
)
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
output = omni.generate(prompt, sampling_params)
|
||||||
|
elapsed = time.perf_counter() - start
|
||||||
|
print(f"Total generation time: {elapsed:.4f} seconds ({elapsed * 1000:.2f} ms)")
|
||||||
|
peak_mb = _peak_memory_mb(output)
|
||||||
|
if peak_mb:
|
||||||
|
print(f"Worker peak GPU memory (reserved): {peak_mb:.2f} MiB ({peak_mb / 1024:.2f} GiB)")
|
||||||
|
|
||||||
|
video = _to_frame_array(_extract_video(output))
|
||||||
|
output_path = Path(args.output)
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
from diffusers.utils import export_to_video
|
||||||
|
|
||||||
|
export_to_video(list(video), str(output_path), fps=args.fps)
|
||||||
|
print(f"Saved generated video to {output_path}")
|
||||||
|
omni.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -138,7 +138,7 @@ def _extract_peak_memory_mb(result: Any) -> float:
|
|||||||
return 0.0
|
return 0.0
|
||||||
val = getattr(result, "peak_memory_mb", 0.0)
|
val = getattr(result, "peak_memory_mb", 0.0)
|
||||||
if not val:
|
if not val:
|
||||||
inner = result
|
inner = getattr(result, "request_output", None)
|
||||||
if isinstance(inner, list):
|
if isinstance(inner, list):
|
||||||
inner = inner[0] if inner else None
|
inner = inner[0] if inner else None
|
||||||
val = getattr(inner, "peak_memory_mb", 0.0)
|
val = getattr(inner, "peak_memory_mb", 0.0)
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
# Image-To-Video
|
# Image-To-Video
|
||||||
|
|
||||||
This example demonstrates online image-to-video generation with vLLM-Omni.
|
This example demonstrates how to deploy the Wan2.2 image-to-video model for online video generation using vLLM-Omni.
|
||||||
The startup script defaults to Wan2.2, but `MODEL` can select any supported
|
|
||||||
image-to-video model. The existing curl helper remains a concrete Wan2.2
|
|
||||||
request through the standard Videos API.
|
|
||||||
|
|
||||||
## Start Server
|
## Start Server
|
||||||
|
|
||||||
@@ -23,7 +20,9 @@ bash run_server.sh
|
|||||||
|
|
||||||
The script allows overriding:
|
The script allows overriding:
|
||||||
- `MODEL` (default: `Wan-AI/Wan2.2-I2V-A14B-Diffusers`)
|
- `MODEL` (default: `Wan-AI/Wan2.2-I2V-A14B-Diffusers`)
|
||||||
- `PORT` (default: `8099`)
|
- `PORT` (default: `8091`)
|
||||||
|
- `BOUNDARY_RATIO` (default: `0.875`)
|
||||||
|
- `FLOW_SHIFT` (default: `12.0`)
|
||||||
- `CACHE_BACKEND` (default: `none`)
|
- `CACHE_BACKEND` (default: `none`)
|
||||||
- `ENABLE_CACHE_DIT_SUMMARY` (default: `0`)
|
- `ENABLE_CACHE_DIT_SUMMARY` (default: `0`)
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Shared image-to-video server start script
|
# Wan2.2 image-to-video server start script
|
||||||
|
|
||||||
MODEL="${MODEL:-Wan-AI/Wan2.2-I2V-A14B-Diffusers}"
|
MODEL="${MODEL:-Wan-AI/Wan2.2-I2V-A14B-Diffusers}"
|
||||||
PORT="${PORT:-8099}"
|
PORT="${PORT:-8099}"
|
||||||
CACHE_BACKEND="${CACHE_BACKEND:-none}"
|
CACHE_BACKEND="${CACHE_BACKEND:-none}"
|
||||||
ENABLE_CACHE_DIT_SUMMARY="${ENABLE_CACHE_DIT_SUMMARY:-0}"
|
ENABLE_CACHE_DIT_SUMMARY="${ENABLE_CACHE_DIT_SUMMARY:-0}"
|
||||||
|
|
||||||
echo "Starting image-to-video server..."
|
echo "Starting Wan2.2 I2V server..."
|
||||||
echo "Model: $MODEL"
|
echo "Model: $MODEL"
|
||||||
echo "Port: $PORT"
|
echo "Port: $PORT"
|
||||||
echo "Cache backend: $CACHE_BACKEND"
|
echo "Cache backend: $CACHE_BACKEND"
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ including `librosa`.
|
|||||||
|
|
||||||
The deploy config auto-loads via `--omni`.
|
The deploy config auto-loads via `--omni`.
|
||||||
The default `vllm_omni/deploy/minicpmo_4_5.yaml` keeps all three stages on
|
The default `vllm_omni/deploy/minicpmo_4_5.yaml` keeps all three stages on
|
||||||
logical device 0 with memory budgets of 55%, 15%, and 18%, leaving headroom
|
logical device 0 with memory budgets of 55%, 15%, and 15%, leaving headroom
|
||||||
for runtime kernels such as the HiFi-GAN vocoder's cuDNN workspace. The
|
for runtime kernels such as the HiFi-GAN vocoder's cuDNN workspace. The
|
||||||
profile admits at most four sequences per stage and bounds Talker context
|
profile admits at most four sequences per stage and bounds startup video
|
||||||
to 4096 tokens. For throughput,
|
profiling to 32 frames per video. For throughput,
|
||||||
`minicpmo_4_5_2gpu.yaml` gives the Thinker
|
`minicpmo_4_5_2gpu.yaml` gives the Thinker
|
||||||
GPU 0 (90%) and colocates the Talker (55%) and Code2Wav (35%) on GPU 1. That
|
GPU 0 (90%) and colocates the Talker (55%) and Code2Wav (35%) on GPU 1. That
|
||||||
profile admits at most four concurrent sequences per stage.
|
profile admits at most four concurrent sequences per stage.
|
||||||
|
|||||||
@@ -196,7 +196,6 @@ async def run_demo(args: argparse.Namespace) -> dict[str, object]:
|
|||||||
args.model,
|
args.model,
|
||||||
ref_audio=_ref_audio_data_url(args.ref_audio),
|
ref_audio=_ref_audio_data_url(args.ref_audio),
|
||||||
session_id=args.session_id,
|
session_id=args.session_id,
|
||||||
temperature=args.temperature,
|
|
||||||
timeout_s=args.timeout_s,
|
timeout_s=args.timeout_s,
|
||||||
)
|
)
|
||||||
stream_event_cursor = len(client.events.events)
|
stream_event_cursor = len(client.events.events)
|
||||||
@@ -384,12 +383,6 @@ def parse_args() -> argparse.Namespace:
|
|||||||
parser.add_argument("--output-dir", default="/tmp/minicpmo_realtime_duplex_demo")
|
parser.add_argument("--output-dir", default="/tmp/minicpmo_realtime_duplex_demo")
|
||||||
parser.add_argument("--chunk-ms", type=int, default=200)
|
parser.add_argument("--chunk-ms", type=int, default=200)
|
||||||
parser.add_argument("--timeout-s", type=float, default=60.0)
|
parser.add_argument("--timeout-s", type=float, default=60.0)
|
||||||
parser.add_argument(
|
|
||||||
"--temperature",
|
|
||||||
type=float,
|
|
||||||
default=None,
|
|
||||||
help="Stage0 sampling temperature; response-required soft-interrupt uses 0.0.",
|
|
||||||
)
|
|
||||||
parser.add_argument("--no-realtime-pacing", action="store_true")
|
parser.add_argument("--no-realtime-pacing", action="store_true")
|
||||||
parser.add_argument("--require-audio", action="store_true")
|
parser.add_argument("--require-audio", action="store_true")
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
# Text-To-Image
|
# Text-To-Image
|
||||||
|
|
||||||
This example demonstrates online text-to-image generation with vLLM-Omni. The
|
This example demonstrates how to deploy Qwen-Image model for online image generation service using vLLM-Omni.
|
||||||
startup script and Python clients default to Qwen-Image, but `MODEL` can select
|
|
||||||
any supported text-to-image model. The existing curl helper remains a concrete
|
|
||||||
Qwen-Image request through the standard Images API.
|
|
||||||
|
|
||||||
## Start Server
|
## Start Server
|
||||||
|
|
||||||
@@ -118,23 +115,7 @@ python gradio_demo.py
|
|||||||
|
|
||||||
## LoRA
|
## LoRA
|
||||||
|
|
||||||
This example supports both startup-time distilled LoRAs and request-time
|
This example supports Peft-compatible LoRA (Low-Rank Adaptation) adapters for diffusion models. The LoRA adapter path must be readable on the **server** machine (usually a local path or a mounted directory).
|
||||||
PEFT-compatible adapters. The LoRA path must be readable on the **server**
|
|
||||||
machine (usually a local path or a mounted directory).
|
|
||||||
|
|
||||||
### Start Server with a Distilled LoRA
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vllm serve Qwen/Qwen-Image-2512 \
|
|
||||||
--omni \
|
|
||||||
--port 8091 \
|
|
||||||
--lora-backend distill \
|
|
||||||
--lora-path /path/to/Qwen-Image-2512-Lightning-4steps.safetensors
|
|
||||||
```
|
|
||||||
|
|
||||||
The distilled LoRA is fused once during server initialization and applies to
|
|
||||||
every request. Use the checkpoint's few-step sampling settings, for example
|
|
||||||
`num_inference_steps=4` and `true_cfg_scale=1.0`.
|
|
||||||
|
|
||||||
### Using Python Client with LoRA
|
### Using Python Client with LoRA
|
||||||
|
|
||||||
@@ -245,8 +226,8 @@ count, use `size` and `n` rather than `height`, `width`, or
|
|||||||
| `height` | int | None | Image height in pixels |
|
| `height` | int | None | Image height in pixels |
|
||||||
| `width` | int | None | Image width in pixels |
|
| `width` | int | None | Image width in pixels |
|
||||||
| `size` | str | None | Image size (e.g., "1024x1024") |
|
| `size` | str | None | Image size (e.g., "1024x1024") |
|
||||||
| `num_inference_steps` | int | model | Number of denoising steps |
|
| `num_inference_steps` | int | 50 | Number of denoising steps |
|
||||||
| `true_cfg_scale` | float | model | True CFG scale when supported |
|
| `true_cfg_scale` | float | 4.0 | Qwen-Image CFG scale |
|
||||||
| `seed` | int | None | Random seed (reproducible) |
|
| `seed` | int | None | Random seed (reproducible) |
|
||||||
| `negative_prompt` | str | None | Negative prompt |
|
| `negative_prompt` | str | None | Negative prompt |
|
||||||
| `num_outputs_per_prompt` | int | 1 | Number of images to generate |
|
| `num_outputs_per_prompt` | int | 1 | Number of images to generate |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Shared text-to-image Gradio demo for online serving.
|
Qwen-Image Gradio Demo for online serving.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python gradio_demo.py [--server http://localhost:8091] [--port 7860]
|
python gradio_demo.py [--server http://localhost:8091] [--port 7860]
|
||||||
@@ -77,9 +77,9 @@ def generate_image(
|
|||||||
def create_demo(server_url: str):
|
def create_demo(server_url: str):
|
||||||
"""Create Gradio demo interface."""
|
"""Create Gradio demo interface."""
|
||||||
|
|
||||||
with gr.Blocks(title="Text-to-Image Demo") as demo:
|
with gr.Blocks(title="Qwen-Image Demo") as demo:
|
||||||
gr.Markdown("# Text-to-Image Online Generation")
|
gr.Markdown("# Qwen-Image Online Generation")
|
||||||
gr.Markdown("Generate images using the model configured on the server")
|
gr.Markdown("Generate images using Qwen-Image model")
|
||||||
|
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
with gr.Column(scale=1):
|
with gr.Column(scale=1):
|
||||||
@@ -115,6 +115,7 @@ def create_demo(server_url: str):
|
|||||||
label="Inference Steps",
|
label="Inference Steps",
|
||||||
minimum=10,
|
minimum=10,
|
||||||
maximum=100,
|
maximum=100,
|
||||||
|
# Default steps aligned with run_curl_text_to_image.sh to 100
|
||||||
value=100,
|
value=100,
|
||||||
step=5,
|
step=5,
|
||||||
)
|
)
|
||||||
@@ -172,7 +173,7 @@ def create_demo(server_url: str):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Shared text-to-image Gradio demo")
|
parser = argparse.ArgumentParser(description="Qwen-Image Gradio Demo")
|
||||||
parser.add_argument("--server", default="http://localhost:8091", help="Server URL")
|
parser.add_argument("--server", default="http://localhost:8091", help="Server URL")
|
||||||
parser.add_argument("--port", type=int, default=7860, help="Gradio port")
|
parser.add_argument("--port", type=int, default=7860, help="Gradio port")
|
||||||
parser.add_argument("--share", action="store_true", help="Create public link")
|
parser.add_argument("--share", action="store_true", help="Create public link")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Shared OpenAI-compatible text-to-image generation client.
|
Qwen-Image OpenAI-compatible image generation client.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python openai_chat_client.py --prompt "A beautiful landscape" --output output.png
|
python openai_chat_client.py --prompt "A beautiful landscape" --output output.png
|
||||||
@@ -39,7 +39,7 @@ def generate_image(
|
|||||||
height: Image height in pixels
|
height: Image height in pixels
|
||||||
width: Image width in pixels
|
width: Image width in pixels
|
||||||
steps: Number of diffusion steps
|
steps: Number of diffusion steps
|
||||||
true_cfg_scale: True CFG scale for models that support it
|
true_cfg_scale: Qwen-Image CFG scale
|
||||||
seed: Random seed
|
seed: Random seed
|
||||||
negative_prompt: Negative prompt
|
negative_prompt: Negative prompt
|
||||||
num_outputs_per_prompt: Number of images to generate
|
num_outputs_per_prompt: Number of images to generate
|
||||||
@@ -70,7 +70,7 @@ def generate_image(
|
|||||||
payload["num_inference_steps"] = steps
|
payload["num_inference_steps"] = steps
|
||||||
if true_cfg_scale is not None:
|
if true_cfg_scale is not None:
|
||||||
payload["true_cfg_scale"] = true_cfg_scale
|
payload["true_cfg_scale"] = true_cfg_scale
|
||||||
if negative_prompt is not None:
|
if negative_prompt:
|
||||||
payload["negative_prompt"] = negative_prompt
|
payload["negative_prompt"] = negative_prompt
|
||||||
if seed is not None:
|
if seed is not None:
|
||||||
payload["seed"] = seed
|
payload["seed"] = seed
|
||||||
@@ -115,15 +115,15 @@ def generate_image(
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Shared text-to-image client")
|
parser = argparse.ArgumentParser(description="Qwen-Image chat client")
|
||||||
parser.add_argument("--prompt", "-p", default="a cup of coffee on the table", help="Text prompt")
|
parser.add_argument("--prompt", "-p", default="a cup of coffee on the table", help="Text prompt")
|
||||||
parser.add_argument("--output", "-o", default="text_to_image_output.png", help="Output file")
|
parser.add_argument("--output", "-o", default="qwen_image_output.png", help="Output file")
|
||||||
parser.add_argument("--server", "-s", default="http://localhost:8091", help="Server URL")
|
parser.add_argument("--server", "-s", default="http://localhost:8091", help="Server URL")
|
||||||
parser.add_argument("--height", type=int, help="Image height; defaults to the model setting")
|
parser.add_argument("--height", type=int, default=1024, help="Image height")
|
||||||
parser.add_argument("--width", type=int, help="Image width; defaults to the model setting")
|
parser.add_argument("--width", type=int, default=1024, help="Image width")
|
||||||
parser.add_argument("--steps", type=int, help="Inference steps; defaults to the model setting")
|
parser.add_argument("--steps", type=int, default=50, help="Inference steps")
|
||||||
parser.add_argument("--cfg-scale", type=float, help="True CFG scale when supported")
|
parser.add_argument("--cfg-scale", type=float, default=4.0, help="True CFG scale")
|
||||||
parser.add_argument("--seed", type=int, help="Random seed")
|
parser.add_argument("--seed", type=int, default=0, help="Random seed")
|
||||||
parser.add_argument("--negative", help="Negative prompt")
|
parser.add_argument("--negative", help="Negative prompt")
|
||||||
|
|
||||||
parser.add_argument("--lora-path", default=None, help="Server-local LoRA adapter folder (PEFT format)")
|
parser.add_argument("--lora-path", default=None, help="Server-local LoRA adapter folder (PEFT format)")
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Shared text-to-image online serving startup script
|
# Qwen-Image online serving startup script
|
||||||
|
|
||||||
MODEL="${MODEL:-Qwen/Qwen-Image}"
|
MODEL="${MODEL:-Qwen/Qwen-Image}"
|
||||||
PORT="${PORT:-8091}"
|
PORT="${PORT:-8091}"
|
||||||
|
|
||||||
echo "Starting text-to-image server..."
|
echo "Starting Qwen-Image server..."
|
||||||
echo "Model: $MODEL"
|
echo "Model: $MODEL"
|
||||||
echo "Port: $PORT"
|
echo "Port: $PORT"
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ For the full list of supported architectures across all modalities, see
|
|||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| Fish Speech S2 Pro | `fishaudio/s2-pro` | ✓ (`ref_audio`+`ref_text`) | ✓ (PCM stream) | — | ✓ |
|
| Fish Speech S2 Pro | `fishaudio/s2-pro` | ✓ (`ref_audio`+`ref_text`) | ✓ (PCM stream) | — | ✓ |
|
||||||
| GLM-TTS | `zai-org/GLM-TTS` | ✓ (`ref_audio`+`ref_text`, required) | ✓ (PCM stream) | — | ✓ |
|
| GLM-TTS | `zai-org/GLM-TTS` | ✓ (`ref_audio`+`ref_text`, required) | ✓ (PCM stream) | — | ✓ |
|
||||||
| IndexTTS-2 | `IndexTeam/IndexTTS-2` | ✓ (`ref_audio` or uploaded `voice`) | `stream=true` response, non-chunk | uploaded audio voice only; no presets | — |
|
| IndexTTS-2 | `IndexTeam/IndexTTS-2` | ✓ (`ref_audio` or uploaded `voice`) | compat only, non-chunk | uploaded audio voice only; no presets | — |
|
||||||
| IndexTTS-2.5 | native `checkpoints/` bundle | ✓ (`ref_audio` or uploaded `voice`) | `stream=true` response, non-chunk | uploaded audio voice only; no presets | — |
|
|
||||||
| Ming-omni-tts | `inclusionAI/Ming-omni-tts-0.5B` | ✓ (`ref_audio` / `speaker_embedding`) | ✓ (PCM stream) | IP labels + structured `instructions` | — |
|
| Ming-omni-tts | `inclusionAI/Ming-omni-tts-0.5B` | ✓ (`ref_audio` / `speaker_embedding`) | ✓ (PCM stream) | IP labels + structured `instructions` | — |
|
||||||
| Ming-flash-omni-TTS | `Jonathan1909/Ming-flash-omni-2.0` | — (caption-controlled) | — | caption fields (`instructions`) | — |
|
| Ming-flash-omni-TTS | `Jonathan1909/Ming-flash-omni-2.0` | — (caption-controlled) | — | caption fields (`instructions`) | — |
|
||||||
| MOSS-TTS-Nano | `OpenMOSS-Team/MOSS-TTS-Nano` | ✓ (`ref_audio` required) | ✓ (PCM stream) | — | ✓ |
|
| MOSS-TTS-Nano | `OpenMOSS-Team/MOSS-TTS-Nano` | ✓ (`ref_audio` required) | ✓ (PCM stream) | — | ✓ |
|
||||||
@@ -139,29 +138,15 @@ bash examples/online_serving/text_to_speech/glm_tts/run_gradio_demo.sh
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## IndexTTS-2 and IndexTTS-2.5
|
## IndexTTS-2
|
||||||
|
|
||||||
2-stage TTS at 22.05 kHz. Requests use `ref_audio` for voice cloning, or an uploaded audio `voice` from `/v1/audio/voices`. IndexTTS-2.5 uses a multilingual tokenizer, CAMPPlus speaker projection, EnhancedCodec, and code-only Stage 0→1 transfer by default. Both versions support emotion conditioning via `emo_audio`, `emo_text`, or `emo_vector` passed in `extra_params`.
|
2-stage TTS (GPT AR + S2Mel CFM DiT + BigVGAN) at 22.05 kHz. Requests use `ref_audio` for voice cloning, or an uploaded audio `voice` from `/v1/audio/voices`. Supports emotion conditioning via `emo_audio`, `emo_text`, or `emo_vector` passed in `extra_params`.
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
IndexTTS-2 uses its existing frontend and does not require the IndexTTS-2.5 text dependencies. Before launching IndexTTS-2.5, install its optional frontend dependencies:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install 'vllm-omni[indextts2]'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Launch
|
### Launch
|
||||||
```bash
|
```bash
|
||||||
vllm serve IndexTeam/IndexTTS-2 --omni --trust-remote-code --port 8092
|
vllm serve IndexTeam/IndexTTS-2 --omni --trust-remote-code --port 8092
|
||||||
# or, to pass the bundled deploy config explicitly:
|
# or, to pass the bundled deploy config explicitly:
|
||||||
bash examples/online_serving/text_to_speech/indextts2/run_server.sh
|
bash examples/online_serving/text_to_speech/indextts2/run_server.sh
|
||||||
|
|
||||||
# IndexTTS-2.5 default: use_gpt_latent=false
|
|
||||||
MODEL_VERSION=2.5 \
|
|
||||||
MODEL=/path/to/indextts-2.5 \
|
|
||||||
bash examples/online_serving/text_to_speech/indextts2/run_server.sh
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sending requests
|
### Sending requests
|
||||||
@@ -176,35 +161,14 @@ python examples/online_serving/text_to_speech/indextts2/speech_client.py \
|
|||||||
--text "今天心情很好!" \
|
--text "今天心情很好!" \
|
||||||
--ref-audio /path/to/ref.wav \
|
--ref-audio /path/to/ref.wav \
|
||||||
--emo-audio /path/to/happy.wav
|
--emo-audio /path/to/happy.wav
|
||||||
|
|
||||||
# IndexTTS-2.5 Japanese request
|
|
||||||
python examples/online_serving/text_to_speech/indextts2/speech_client.py \
|
|
||||||
--model-version 2.5 \
|
|
||||||
--model /path/to/indextts-2.5 \
|
|
||||||
--lang ja \
|
|
||||||
--text "こんにちは、音声合成のテストです。" \
|
|
||||||
--ref-audio /path/to/ref.wav
|
|
||||||
|
|
||||||
# IndexTTS-2.5 native speed control
|
|
||||||
python examples/online_serving/text_to_speech/indextts2/speech_client.py \
|
|
||||||
--model-version 2.5 \
|
|
||||||
--model /path/to/indextts-2.5 \
|
|
||||||
--speed 2.0 \
|
|
||||||
--text "这是两倍速度的语音合成测试。" \
|
|
||||||
--ref-audio /path/to/ref.wav
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Notes
|
### Notes
|
||||||
- Output: 22.05 kHz mono WAV.
|
- Output: 22.05 kHz mono WAV.
|
||||||
- Provide `ref_audio` on the documented raw request path, or pass `voice` only when it names an uploaded audio voice; neither version provides a built-in text-only preset voice.
|
- Provide `ref_audio` on the documented raw request path, or pass `voice` only when it names an uploaded audio voice; IndexTTS-2 does not provide a built-in text-only preset voice.
|
||||||
- IndexTTS-2.5 request controls `lang` and `text_normalization` are carried in `extra_params`; the client does this for `--model-version 2.5`.
|
|
||||||
- IndexTTS-2.5 accepts the public `speed` request field in `[0.5, 2.0]`: `2.0` generates shorter, faster speech and `0.5` generates longer, slower speech. This is native Stage 1 duration control, so serving does not apply the generic playback-speed adjustment a second time. IndexTTS-2 does not use this control.
|
|
||||||
- IndexTTS-2.5 accepts language codes such as `zh`, `en`, `zhen` (mixed Chinese/English), `ja`, and `yue`. `Mandarin` is a vLLM-Omni convenience alias for `zh`. Japanese (`ja`) uses `fugashi` tokenization and produces audio, but it does not automatically expand numbers, dates, or percentages; callers should first write those inputs as readable Japanese text.
|
|
||||||
- A request `seed` controls Stage 0 AR sampling and per-request CFM noise. Different concurrent batch compositions do not guarantee a bit-identical waveform.
|
|
||||||
- IndexTTS-2.5 Stage 0 uses plain vLLM sampling and does not reproduce the official default `num_beams=3` beam search. For parity comparisons, run upstream with `num_beams=1`; output quality can differ from the official beam-search result.
|
|
||||||
- Emotion params (`emo_audio`, `emo_text`, `emo_vector`, `emo_alpha`, `use_emo_text`, `use_random`) are passed via the `extra_params` field. Official precedence is `use_emo_text` > `emo_vector` > `emo_audio` > same emotion as the speaker reference.
|
- Emotion params (`emo_audio`, `emo_text`, `emo_vector`, `emo_alpha`, `use_emo_text`, `use_random`) are passed via the `extra_params` field. Official precedence is `use_emo_text` > `emo_vector` > `emo_audio` > same emotion as the speaker reference.
|
||||||
- IndexTTS-2.5 uses `vllm_omni/deploy/indextts2_5.yaml` with the official code-only Stage 0 to Stage 1 contract.
|
- `stream=true` is accepted as an OpenAI-compatible response path, but IndexTTS-2 is not async-chunk streaming; audio is produced after S2Mel receives the full mel-code sequence.
|
||||||
- For IndexTTS-2.5, set `MODEL` to the local native bundle; asset discovery accepts its nested `checkpoints/` layout.
|
- Deploy config: `vllm_omni/deploy/indextts2.yaml` (auto-loaded).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -605,13 +569,6 @@ python qwen3_tts/streaming_speech_client.py \
|
|||||||
The client writes one PCM file per sentence and a matching
|
The client writes one PCM file per sentence and a matching
|
||||||
`sentence_XXX_timestamps.json` sidecar.
|
`sentence_XXX_timestamps.json` sidecar.
|
||||||
|
|
||||||
Non-streaming requests can also ask for timestamps: pass
|
|
||||||
`"word_timestamps": true` to `POST /v1/audio/speech` and read the
|
|
||||||
`X-Word-Timestamps` response header (JSON list of `{word, start_ms, end_ms}`,
|
|
||||||
ASCII-escaped). Past 4 KB the header is replaced by
|
|
||||||
`X-Word-Timestamps-Omitted: oversize; bytes=<n>; limit=4096` and the audio
|
|
||||||
still returns — use the WebSocket path for long transcripts.
|
|
||||||
|
|
||||||
To *see* the alignment instead of reading a JSON sidecar, run the
|
To *see* the alignment instead of reading a JSON sidecar, run the
|
||||||
word-timestamp Gradio demo (server must be launched with `--forced-aligner`):
|
word-timestamp Gradio demo (server must be launched with `--forced-aligner`):
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -1,37 +1,20 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Launch vLLM-Omni server for IndexTTS 2.0 or 2.5.
|
# Launch vLLM-Omni server for IndexTTS2
|
||||||
#
|
#
|
||||||
# Usage from repository root:
|
# Usage from repository root:
|
||||||
# examples/online_serving/text_to_speech/indextts2/run_server.sh
|
# examples/online_serving/text_to_speech/indextts2/run_server.sh
|
||||||
# MODEL_VERSION=2.5 MODEL=/path/to/native/bundle \
|
# CUDA_VISIBLE_DEVICES=0 PORT=8092 MODEL=/path/to/IndexTeam/IndexTTS-2 examples/online_serving/text_to_speech/indextts2/run_server.sh
|
||||||
# examples/online_serving/text_to_speech/indextts2/run_server.sh
|
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
ROOT_DIR="$(cd -- "$SCRIPT_DIR/../../../.." && pwd)"
|
ROOT_DIR="$(cd -- "$SCRIPT_DIR/../../../.." && pwd)"
|
||||||
|
|
||||||
MODEL_VERSION="${MODEL_VERSION:-2.0}"
|
MODEL="${MODEL:-IndexTeam/IndexTTS-2}"
|
||||||
PORT="${PORT:-8092}"
|
PORT="${PORT:-8092}"
|
||||||
|
DEPLOY_CONFIG="${DEPLOY_CONFIG:-$ROOT_DIR/vllm_omni/deploy/indextts2.yaml}"
|
||||||
|
|
||||||
if [[ "$MODEL_VERSION" == "2.5" ]]; then
|
echo "Starting IndexTTS2 server with model: $MODEL"
|
||||||
if [[ -z "${MODEL:-}" ]]; then
|
|
||||||
echo "Usage: MODEL_VERSION=2.5 MODEL=/path/to/native/bundle bash $0" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
DEFAULT_DEPLOY_CONFIG="$ROOT_DIR/vllm_omni/deploy/indextts2_5.yaml"
|
|
||||||
elif [[ "$MODEL_VERSION" == "2.0" ]]; then
|
|
||||||
MODEL="${MODEL:-IndexTeam/IndexTTS-2}"
|
|
||||||
DEFAULT_DEPLOY_CONFIG="$ROOT_DIR/vllm_omni/deploy/indextts2.yaml"
|
|
||||||
else
|
|
||||||
echo "MODEL_VERSION must be 2.0 or 2.5" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
DEPLOY_CONFIG="${DEPLOY_CONFIG:-$DEFAULT_DEPLOY_CONFIG}"
|
|
||||||
|
|
||||||
echo "Starting IndexTTS $MODEL_VERSION server with model: $MODEL"
|
|
||||||
echo "Deploy config: $DEPLOY_CONFIG"
|
|
||||||
|
|
||||||
FLASHINFER_DISABLE_VERSION_CHECK=1 \
|
FLASHINFER_DISABLE_VERSION_CHECK=1 \
|
||||||
vllm serve "$MODEL" \
|
vllm serve "$MODEL" \
|
||||||
|
|||||||
Loaded 100 of 488 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user