Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00dab2ac95 | ||
|
|
c98517ce7f | ||
|
|
d77a35a32b | ||
|
|
877beeaa49 | ||
|
|
8b8d3f56db | ||
|
|
c57178c0e1 | ||
|
|
9a59475015 | ||
|
|
12f8168179 | ||
|
|
72a02b492a | ||
|
|
5358f5db01 | ||
|
|
e677360168 | ||
|
|
208c6776a8 | ||
|
|
fcf1085395 | ||
|
|
38a2500bdf | ||
|
|
7cd826d28c | ||
|
|
31384cb39c | ||
|
|
5be7541d01 | ||
|
|
9b0df3909a | ||
|
|
a15b5b4bcd | ||
|
|
3c3989ed04 | ||
|
|
854bff7fe5 | ||
|
|
b792578d89 | ||
|
|
bdb1d996be | ||
|
|
8b0a6779d4 | ||
|
|
58007c0084 | ||
|
|
1ff2753469 | ||
|
|
4697bd1be0 | ||
|
|
2ce15ab7a0 | ||
|
|
584d78c673 | ||
|
|
89015d5cad | ||
|
|
348cf95a89 | ||
|
|
22dd96ec5c | ||
|
|
e04210d690 | ||
|
|
4ab1b4ff9c | ||
|
|
06e733d3cd | ||
|
|
af3faa6c1e | ||
|
|
322600bfb2 | ||
|
|
c4a14091c9 | ||
|
|
6d4e4ffc7c | ||
|
|
f97af75d1d | ||
|
|
73a312d5b6 | ||
|
|
1a56953d7a | ||
|
|
1f65814fab | ||
|
|
4f9b65074d |
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 CI image tag …:$BUILDKITE_COMMIT-a3)
|
# a3_npu_2 — ascend-a3, resource_class npu-2
|
||||||
|
|
||||||
x-npu-k8s-plugins: &npu_k8s_plugins
|
x-npu-k8s-plugins: &npu_k8s_plugins
|
||||||
- kubernetes:
|
- kubernetes:
|
||||||
|
|||||||
@@ -139,6 +139,11 @@ 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"
|
||||||
@@ -148,10 +153,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/minicpmo_realtime_duplex_scenarios.py
|
- tests/e2e/online_serving/helpers/minicpmo_realtime_duplex_scenarios.py
|
||||||
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_multi_session.py
|
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_multi_session.py
|
||||||
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_soft_interrupt.py
|
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_soft_interrupt.py
|
||||||
- tests/helpers/minicpmo_4_5_duplex.py
|
- tests/e2e/online_serving/helpers/minicpmo_4_5_duplex.py
|
||||||
- vllm_omni/deploy/minicpmo_4_5_duplex.yaml
|
- vllm_omni/deploy/minicpmo_4_5_duplex.yaml
|
||||||
- vllm_omni/experimental/fullduplex/
|
- vllm_omni/experimental/fullduplex/
|
||||||
- vllm_omni/model_executor/models/minicpmo_4_5/
|
- vllm_omni/model_executor/models/minicpmo_4_5/
|
||||||
@@ -164,7 +169,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_2
|
mirror_hardwares: h100_1
|
||||||
|
|
||||||
- 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_2
|
mirror_hardwares: h100_1
|
||||||
|
|
||||||
- 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,6 +50,19 @@ 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
|
||||||
@@ -102,6 +115,32 @@ 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:
|
||||||
@@ -306,7 +345,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.3.0
|
- export CACHE_DIT_VERSION=1.5.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
|
||||||
@@ -322,7 +361,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.3.0
|
- export CACHE_DIT_VERSION=1.5.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
|
||||||
@@ -338,7 +377,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.3.0
|
- export CACHE_DIT_VERSION=1.5.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
|
||||||
@@ -354,7 +393,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.3.0
|
- export CACHE_DIT_VERSION=1.5.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
|
||||||
@@ -370,7 +409,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.3.0
|
- export CACHE_DIT_VERSION=1.5.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
|
||||||
@@ -427,10 +466,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 T2VA Accuracy Test"
|
- label: ":full_moon: Diffusion X2V · MiniMax H3 I2VA/Ref2VA Accuracy Test"
|
||||||
timeout_in_minutes: 180
|
timeout_in_minutes: 180
|
||||||
commands:
|
commands:
|
||||||
- pytest -s -v tests/e2e/accuracy/minimax_h3/test_minimax_h3_t2va_similarity.py -m full_model --run-level full_model
|
- pytest -s -v tests/e2e/accuracy/minimax_h3/test_minimax_h3_i2va_ref2va_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,6 +128,21 @@ 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:
|
||||||
@@ -172,7 +187,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_2
|
mirror_hardwares: h100_1
|
||||||
|
|
||||||
- label: "Omni · MiniCPM-o 4.5 Online Test"
|
- label: "Omni · MiniCPM-o 4.5 Online Test"
|
||||||
source_file_dependencies:
|
source_file_dependencies:
|
||||||
@@ -183,7 +198,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_2
|
mirror_hardwares: h100_1
|
||||||
|
|
||||||
- label: "Omni · MiniCPM-o 4.5 Duplex Test"
|
- label: "Omni · MiniCPM-o 4.5 Duplex Test"
|
||||||
source_file_dependencies:
|
source_file_dependencies:
|
||||||
@@ -192,10 +207,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/minicpmo_realtime_duplex_scenarios.py
|
- tests/e2e/online_serving/helpers/minicpmo_realtime_duplex_scenarios.py
|
||||||
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_multi_session.py
|
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_multi_session.py
|
||||||
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_soft_interrupt.py
|
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_soft_interrupt.py
|
||||||
- tests/helpers/minicpmo_4_5_duplex.py
|
- tests/e2e/online_serving/helpers/minicpmo_4_5_duplex.py
|
||||||
- vllm_omni/deploy/minicpmo_4_5_duplex.yaml
|
- vllm_omni/deploy/minicpmo_4_5_duplex.yaml
|
||||||
- vllm_omni/experimental/fullduplex/
|
- vllm_omni/experimental/fullduplex/
|
||||||
- vllm_omni/model_executor/models/minicpmo_4_5/
|
- vllm_omni/model_executor/models/minicpmo_4_5/
|
||||||
@@ -210,7 +225,19 @@ 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_2
|
mirror_hardwares: h100_1
|
||||||
|
|
||||||
|
- 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.26.0"
|
VLLM_VERSION: "v0.27.0"
|
||||||
priority: 100
|
priority: 100
|
||||||
timeout_in_minutes: 60
|
timeout_in_minutes: 60
|
||||||
soft_fail: false
|
soft_fail: false
|
||||||
@@ -34,8 +34,23 @@ 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
|
||||||
docker_build "${base_image_name}" --target vllm-base --build-arg "VLLM_VERSION=${VLLM_VERSION}"
|
build_vllm_base
|
||||||
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: ":robot: Omni · MiniCPM-o 4.5 Test"
|
- label: ":full_moon: 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,6 +47,34 @@ 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
|
||||||
@@ -87,7 +115,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.3.0
|
- export CACHE_DIT_VERSION=1.5.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,22 +3,29 @@ steps:
|
|||||||
key: ready-omni-group
|
key: ready-omni-group
|
||||||
depends_on: upload-ready-pipeline
|
depends_on: upload-ready-pipeline
|
||||||
steps:
|
steps:
|
||||||
- label: ":robot: Omni · MiniCPM-o 4.5 Test"
|
- label: "Omni · MiniCPM-o 4.5 Test"
|
||||||
agents:
|
mirror_hardwares: a3_npu_2
|
||||||
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' --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 and npu and A3 and omni' --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,9 +40,11 @@ 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"
|
||||||
@@ -63,7 +65,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)"
|
||||||
# re-tag to default image tag and push, just in case arm64 build fails
|
# Fallback default tag (no arch suffix) if 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:
|
||||||
@@ -86,6 +88,26 @@ 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:
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
#!/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,50 +1,87 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
#
|
#
|
||||||
# Publish release Docker images from ECR to DockerHub.
|
# Publish Docker images from ECR staging to DockerHub.
|
||||||
# Pulls per-arch images, tags with `latest` and versioned tags, pushes them,
|
#
|
||||||
# then creates and pushes a multi-arch manifest.
|
# Usage:
|
||||||
|
# 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
|
||||||
|
|
||||||
RELEASE_VERSION=$(buildkite-agent meta-data get release-version --default "" | sed 's/^v//')
|
MODE="${1:-release}"
|
||||||
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 release images v${RELEASE_VERSION}"
|
echo "Publishing ${MODE} images to ${DOCKERHUB_REPO}"
|
||||||
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
|
||||||
|
|
||||||
docker pull "${ECR_REPO}:${COMMIT}-x86_64"
|
SRC_X86="${ECR_REPO}:${COMMIT}-x86_64"
|
||||||
docker pull "${ECR_REPO}:${COMMIT}-aarch64"
|
SRC_ARM="${ECR_REPO}:${COMMIT}-aarch64"
|
||||||
|
|
||||||
docker tag "${ECR_REPO}:${COMMIT}-x86_64" "${DOCKERHUB_REPO}:latest-x86_64"
|
docker pull "${SRC_X86}"
|
||||||
docker tag "${ECR_REPO}:${COMMIT}-x86_64" "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-x86_64"
|
docker pull "${SRC_ARM}"
|
||||||
docker push "${DOCKERHUB_REPO}:latest-x86_64"
|
|
||||||
docker push "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-x86_64"
|
|
||||||
|
|
||||||
docker tag "${ECR_REPO}:${COMMIT}-aarch64" "${DOCKERHUB_REPO}:latest-aarch64"
|
publish_manifest() {
|
||||||
docker tag "${ECR_REPO}:${COMMIT}-aarch64" "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-aarch64"
|
local tag="$1"
|
||||||
docker push "${DOCKERHUB_REPO}:latest-aarch64"
|
local arch_tag_base="$2"
|
||||||
docker push "${DOCKERHUB_REPO}:v${RELEASE_VERSION}-aarch64"
|
docker manifest rm "${DOCKERHUB_REPO}:${tag}" || true
|
||||||
|
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}"
|
||||||
|
}
|
||||||
|
|
||||||
docker manifest rm "${DOCKERHUB_REPO}:latest" || true
|
for tag in "${PRIMARY_TAGS[@]}"; do
|
||||||
docker manifest rm "${DOCKERHUB_REPO}:v${RELEASE_VERSION}" || true
|
docker tag "${SRC_X86}" "${DOCKERHUB_REPO}:${tag}-x86_64"
|
||||||
docker manifest create "${DOCKERHUB_REPO}:latest" "${DOCKERHUB_REPO}:latest-x86_64" "${DOCKERHUB_REPO}:latest-aarch64"
|
docker tag "${SRC_ARM}" "${DOCKERHUB_REPO}:${tag}-aarch64"
|
||||||
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}-x86_64"
|
||||||
docker manifest push "${DOCKERHUB_REPO}:latest"
|
docker push "${DOCKERHUB_REPO}:${tag}-aarch64"
|
||||||
docker manifest push "${DOCKERHUB_REPO}:v${RELEASE_VERSION}"
|
publish_manifest "${tag}" "${tag}"
|
||||||
|
done
|
||||||
|
|
||||||
|
ARCH_BASE="${PRIMARY_TAGS[0]}"
|
||||||
|
for tag in "${EXTRA_MANIFEST_TAGS[@]}"; do
|
||||||
|
publish_manifest "${tag}" "${ARCH_BASE}"
|
||||||
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Successfully published release images for v${RELEASE_VERSION}"
|
echo "Successfully published ${MODE} images"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: precheck-pr
|
name: precheck-pr
|
||||||
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.
|
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.
|
||||||
---
|
---
|
||||||
|
|
||||||
# PR Pre-Check
|
# PR Pre-Check
|
||||||
@@ -65,6 +65,8 @@ 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
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -77,6 +79,7 @@ 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,6 +47,19 @@ 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:
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# 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.
|
||||||
+38
-28
@@ -1,9 +1,9 @@
|
|||||||
# Claude Skills for vLLM-Omni
|
# Repository Skills for vLLM-Omni
|
||||||
|
|
||||||
This directory contains Claude Code skills maintained for the `vllm-omni`
|
This directory contains repository-scale skills maintained for `vllm-omni`.
|
||||||
repository. These skills capture repeatable workflows for common contributor
|
They capture repeatable workflows for common contributor and maintainer tasks
|
||||||
tasks such as model integration, pull request review, and release note
|
such as model integration, CI-aligned testing, performance optimization, and
|
||||||
generation.
|
pull request review.
|
||||||
|
|
||||||
## Directory Structure
|
## Directory Structure
|
||||||
|
|
||||||
@@ -14,31 +14,41 @@ 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`: guides integration of a new diffusion model into
|
- [`add-diffusion-model`](add-diffusion-model/SKILL.md): guides integration of
|
||||||
`vllm-omni`
|
a new diffusion model into `vllm-omni`
|
||||||
- `diffusion-perf-opt`: guides diffusion model performance optimization,
|
- [`add-tts-model`](add-tts-model/SKILL.md): covers integration of new TTS
|
||||||
including profiling traces, parallel strategies, stage timing analysis, and
|
models and related serving workflows
|
||||||
benchmark-driven tuning
|
- [`diffusion-perf-opt`](diffusion-perf-opt/SKILL.md): guides diffusion model
|
||||||
- `quantization`: guides quantization method selection, model integration,
|
performance optimization, including profiling traces, parallel strategies,
|
||||||
checkpoint loading, and quality/performance validation for vLLM-Omni
|
stage timing analysis, and benchmark-driven tuning
|
||||||
- `add-omni-model`: covers addition of new omni-modality model support
|
- [`precheck-pr`](precheck-pr/SKILL.md): self-checks a branch before creating a
|
||||||
- `add-tts-model`: covers integration of new TTS models and related serving
|
PR by validating title format, dead code, accuracy and performance claims,
|
||||||
workflows
|
and merge readiness
|
||||||
- `generate-release-note`: helps prepare release notes for repository changes
|
- [`quantization`](quantization/SKILL.md): guides quantization method selection,
|
||||||
- `precheck-pr`: self-check a branch before creating a PR — validates PR title
|
model integration, checkpoint loading, and quality/performance validation
|
||||||
format, catches dead code, verifies accuracy/perf claims, and confirms merge
|
for vLLM-Omni
|
||||||
readiness
|
- [`review-pr`](review-pr/SKILL.md): provides a frozen-snapshot, contract-aware
|
||||||
- `vllm-omni-test`: guides generation and execution of CI-aligned tests (L1–L4),
|
workflow for maintainers and reviewers; contributors should use
|
||||||
pytest marker selection (`core_model` / `advanced_model` / `full_model`,
|
`precheck-pr` for self-review
|
||||||
`omni` / `tts` / `diffusion`), Buildkite wiring (`test-ready.yml`,
|
- [`vllm-omni-npu-model-runner-upgrade`](vllm-omni-npu-upgrade/SKILL.md):
|
||||||
`test-merge.yml`, `test-nightly.yml`, `test-weekly.yml`), and copy-paste
|
upgrades NPU model runners to align with the latest vllm-ascend
|
||||||
local plus CI-like `pytest` commands; see `references/test-routing.md` for
|
`NPUModelRunner`
|
||||||
level-to-command mapping
|
- [`vllm-omni-test`](vllm-omni-test/SKILL.md): guides generation and execution
|
||||||
- `review-pr`: provides a structured workflow for reviewing pull requests
|
of CI-aligned tests (L1–L4), pytest marker selection (`core_model` /
|
||||||
- `vllm-omni-npu-model-runner-upgrade`: upgrades NPU model runners to align with the
|
`advanced_model` / `full_model`, `omni` / `tts` / `diffusion`), Buildkite
|
||||||
latest vllm-ascend NPUModelRunner
|
wiring (`test-ready.yml`, `test-merge.yml`, `test-nightly.yml`,
|
||||||
|
`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,6 +97,7 @@ 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
|
||||||
|
|
||||||
@@ -152,6 +153,13 @@ 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
|
||||||
|
|||||||
+7
-2
@@ -11,10 +11,15 @@ 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.
|
# 1 GB memory cap and time out the mkdocs build. Wheel-only: `--index-url`
|
||||||
- python -m pip install --index-url https://download.pytorch.org/whl/cpu torch
|
# replaces PyPI, so an sdist fallback cannot reach its build deps. The
|
||||||
|
# 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,7 +158,6 @@ 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)
|
||||||
@@ -176,7 +175,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 = OmniRequestOutput.unwrap_result(outputs)
|
req_out = outputs[0] if isinstance(outputs, list) else 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]
|
||||||
@@ -206,15 +205,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)
|
||||||
|
|
||||||
first = outputs[0]
|
if isinstance(outputs, list) and isinstance(outputs[0], OmniRequestOutput):
|
||||||
if hasattr(first, "request_output") and isinstance(first.request_output, list):
|
first = outputs[0]
|
||||||
inner = first.request_output[0]
|
elif isinstance(outputs, OmniRequestOutput):
|
||||||
if isinstance(inner, OmniRequestOutput) and hasattr(inner, "images"):
|
first = outputs
|
||||||
frames = inner.images[0] if inner.images else None
|
else:
|
||||||
else:
|
raise ValueError("Could not extract video frames from output.")
|
||||||
frames = inner
|
|
||||||
elif hasattr(first, "images") and first.images:
|
if hasattr(first, "images"):
|
||||||
frames = first.images[0]
|
frames = first.images[0] if first.images else None
|
||||||
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
|
request_output = stage_outputs
|
||||||
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,9 +167,11 @@ def main() -> None:
|
|||||||
]
|
]
|
||||||
native_cmd = [
|
native_cmd = [
|
||||||
sys.executable,
|
sys.executable,
|
||||||
"examples/offline_inference/text_to_video/text_to_video_lingbot.py",
|
"examples/offline_inference/text_to_video/text_to_video.py",
|
||||||
"--model",
|
"--model",
|
||||||
args.model,
|
args.model,
|
||||||
|
"--model-class-name",
|
||||||
|
"LingBotVideoPipeline",
|
||||||
"--prompt",
|
"--prompt",
|
||||||
args.prompt,
|
args.prompt,
|
||||||
"--output",
|
"--output",
|
||||||
|
|||||||
@@ -139,6 +139,34 @@ 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,12 +84,17 @@ 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] = (model_cfg.get("task_extra_body") or {}).get(task) or {}
|
task_extra_body: dict[str, Any] = dict((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:
|
||||||
@@ -109,7 +114,7 @@ def build_bench_args(
|
|||||||
"--port",
|
"--port",
|
||||||
str(port),
|
str(port),
|
||||||
"--model",
|
"--model",
|
||||||
model,
|
served_model_name or model,
|
||||||
"--backend",
|
"--backend",
|
||||||
backend,
|
backend,
|
||||||
"--endpoint",
|
"--endpoint",
|
||||||
@@ -119,7 +124,7 @@ def build_bench_args(
|
|||||||
"--num-prompts",
|
"--num-prompts",
|
||||||
str(num_prompts),
|
str(num_prompts),
|
||||||
"--num-warmups",
|
"--num-warmups",
|
||||||
"2",
|
str(num_warmups),
|
||||||
"--percentile-metrics",
|
"--percentile-metrics",
|
||||||
"ttft,e2el,audio_rtf,audio_ttfp,audio_duration,audio_underrun",
|
"ttft,e2el,audio_rtf,audio_ttfp,audio_duration,audio_underrun",
|
||||||
]
|
]
|
||||||
@@ -152,7 +157,8 @@ def build_bench_args(
|
|||||||
if result_filename:
|
if result_filename:
|
||||||
cmd += ["--result-filename", result_filename]
|
cmd += ["--result-filename", result_filename]
|
||||||
|
|
||||||
cmd += extra_cli_args
|
passthrough = extra_cli_args[1:] if extra_cli_args[:1] == ["--"] else extra_cli_args
|
||||||
|
cmd += passthrough
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
@@ -225,6 +231,12 @@ 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")
|
||||||
@@ -236,6 +248,13 @@ 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)"
|
||||||
)
|
)
|
||||||
@@ -256,6 +275,9 @@ 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())
|
||||||
@@ -308,6 +330,9 @@ 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,6 +7,17 @@
|
|||||||
# 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.
|
||||||
|
|||||||
+19
-2
@@ -1,7 +1,24 @@
|
|||||||
# Target vllm version: v0.26.0 (releases/v0.26.0 branch).
|
# Target vllm version: v0.27.0 (releases/v0.27.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.26.0
|
ARG VLLM_BASE_TAG=v0.27.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
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ 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 && \
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ 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.26.0
|
ARG VLLM_VERSION=v0.27.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.1 && \
|
uv pip install triton-xpu==3.7.2 && \
|
||||||
uv pip install --no-build-isolation --no-deps .
|
uv pip install --no-build-isolation --no-deps .
|
||||||
|
|
||||||
CMD ["/bin/bash"]
|
CMD ["/bin/bash"]
|
||||||
@@ -144,12 +144,13 @@ 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 oneccl oneccl-devel && \
|
uv pip uninstall triton && \
|
||||||
uv pip install triton-xpu==3.7.1 --reinstall
|
uv pip install triton-xpu==3.7.2 --reinstall
|
||||||
|
|
||||||
FROM vllm-omni AS vllm-omni-openai
|
FROM vllm-omni AS vllm-omni-openai
|
||||||
|
|
||||||
|
|||||||
+32
-26
@@ -40,30 +40,12 @@ nav:
|
|||||||
- Models:
|
- Models:
|
||||||
- models/supported_models.md
|
- models/supported_models.md
|
||||||
- Features:
|
- Features:
|
||||||
- Sleep Mode: features/sleep_mode.md
|
- Overview: features/README.md
|
||||||
- Diffusion Features:
|
- Runtime and Stage Execution:
|
||||||
- Overview: user_guide/diffusion_features.md
|
- Execution Modes and Streaming: user_guide/diffusion/execution_modes.md
|
||||||
- Feature Compatibility: user_guide/feature_compatibility.md
|
- Sleep Mode: features/sleep_mode.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:
|
||||||
- Quantization: user_guide/quantization/overview.md
|
- Overview: 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
|
||||||
@@ -72,9 +54,33 @@ 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 Parallelism: user_guide/diffusion/parallelism/vae_parallelism.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
|
||||||
@@ -108,6 +114,7 @@ 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
|
||||||
@@ -117,9 +124,8 @@ nav:
|
|||||||
- Sequence Parallel: design/feature/sequence_parallel.md
|
- Sequence Parallel: design/feature/sequence_parallel.md
|
||||||
- Tensor Parallel: design/feature/tensor_parallel.md
|
- Tensor Parallel: design/feature/tensor_parallel.md
|
||||||
- VAE Patch Parallelism: design/feature/vae_parallel.md
|
- VAE Patch Parallelism: design/feature/vae_parallel.md
|
||||||
- Attention Backends:
|
- Attention Optimization:
|
||||||
- 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
|
||||||
|
|||||||
@@ -76,6 +76,47 @@ 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.
|
||||||
|
|||||||
+13
-5
@@ -11,6 +11,10 @@ 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)
|
||||||
@@ -30,6 +34,10 @@ in the active navigation.
|
|||||||
- [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
|
||||||
@@ -42,14 +50,14 @@ in the active navigation.
|
|||||||
- [Tensor Parallel](feature/tensor_parallel.md)
|
- [Tensor Parallel](feature/tensor_parallel.md)
|
||||||
- [VAE Patch Parallelism](feature/vae_parallel.md)
|
- [VAE Patch Parallelism](feature/vae_parallel.md)
|
||||||
|
|
||||||
#### Attention Backends
|
#### Attention optimization
|
||||||
|
|
||||||
|
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 iteration covers the text and audio surface; diffusion / image / video families land in a follow-up.
|
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.
|
||||||
|
|
||||||
## Objectives
|
## Objectives
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# 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,6 +51,7 @@ 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` | ✅︎ | ✅︎ | | ✅︎ | — |
|
||||||
@@ -79,6 +80,7 @@ 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` | ✅︎ | ✅︎ | | | — |
|
||||||
|
|||||||
+4
-1
@@ -14,7 +14,10 @@ A: At first, you can check current [issues](https://github.com/vllm-project/vllm
|
|||||||
|
|
||||||
> Q: Does vLLM-Omni support AWQ or any other quantization?
|
> Q: Does vLLM-Omni support AWQ or any other quantization?
|
||||||
|
|
||||||
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).
|
A: AWQ is not currently listed as a validated vLLM-Omni path. Quantization
|
||||||
|
support depends on the model, pipeline stage, checkpoint format, and hardware
|
||||||
|
backend. See the current [quantization support matrix](../user_guide/quantization/overview.md)
|
||||||
|
for online methods, pre-quantized checkpoints, and model-level validation.
|
||||||
|
|
||||||
> Q: Does vLLM-Omni support multimodal streaming input and output?
|
> Q: Does vLLM-Omni support multimodal streaming input and output?
|
||||||
|
|
||||||
|
|||||||
+50
-26
@@ -1,23 +1,23 @@
|
|||||||
# Production Metrics
|
# Production Metrics
|
||||||
|
|
||||||
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.
|
vLLM-Omni exposes Prometheus metrics via the `/metrics` endpoint on the OpenAI-compatible API server.
|
||||||
|
|
||||||
```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,42 +25,66 @@ 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.
|
||||||
|
|
||||||
## Cross-Stage Transfer Metrics (`vllm:omni_`)
|
## Diffusion 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:`)
|
||||||
|
|
||||||
@@ -83,9 +107,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` |
|
||||||
|
|
||||||
@@ -94,5 +118,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_` / `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:`.
|
||||||
- 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,14 +1,18 @@
|
|||||||
# 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 currently supports PEFT-style LoRA adapters, allowing you to customize model behavior without modifying the base model weights.
|
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.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
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.
|
LoRA adapters are lightweight, model-specific fine-tuning weights that can be applied to diffusion models in two ways:
|
||||||
|
|
||||||
|
- **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
|
||||||
|
|
||||||
LoRA adapters must be in **PEFT (Parameter-Efficient Fine-Tuning)** format. A typical LoRA adapter directory structure:
|
### PEFT (Parameter-Efficient Fine-Tuning) format (default)
|
||||||
|
A typical PEFT-format LoRA adapter directory structure:
|
||||||
|
|
||||||
```
|
```
|
||||||
lora_adapter/
|
lora_adapter/
|
||||||
@@ -25,19 +29,20 @@ The `adapter_config.json` file contains metadata about the LoRA adapter, includi
|
|||||||
|
|
||||||
### Offline Inference
|
### Offline Inference
|
||||||
|
|
||||||
#### Pre-loaded LoRA
|
#### PEFT backend: pre-loaded LoRA
|
||||||
|
|
||||||
Load a LoRA adapter at initialization. This adapter is pre-loaded into the cache and can be activated by requests:
|
Load a PEFT-format LoRA adapter at initialization. The adapter is pre-loaded into the cache and can be activated per request:
|
||||||
|
|
||||||
```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(
|
||||||
@@ -53,6 +58,98 @@ 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.
|
||||||
|
|
||||||
@@ -141,6 +238,7 @@ 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
|
||||||
|
|||||||
@@ -164,10 +164,12 @@ 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
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# 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](#multi-thread-weight-loading)** | Loads safetensors shards in parallel using a thread pool | All diffusion models; reduces startup from minutes to seconds |
|
| **[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 |
|
||||||
|
|
||||||
**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.
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ current support.
|
|||||||
|--------|--------------|-------------|----------|
|
|--------|--------------|-------------|----------|
|
||||||
| **[FP8](quantization/fp8.md)** | `quantization="fp8"` | FP8 W8A8 on validated transformer stages | Memory reduction, inference speedup |
|
| **[FP8](quantization/fp8.md)** | `quantization="fp8"` | FP8 W8A8 on validated transformer stages | Memory reduction, inference speedup |
|
||||||
| **[INT8](quantization/int8.md)** | `quantization="int8"` | INT8 W8A8 on validated transformer stages | Memory reduction, broad GPU compatibility |
|
| **[INT8](quantization/int8.md)** | `quantization="int8"` | INT8 W8A8 on validated transformer stages | Memory reduction, broad GPU compatibility |
|
||||||
| **[GGUF](quantization/gguf.md)** | `quantization="gguf"` | Native GGUF transformer-only weights (Q4, Q8, etc.) | Memory reduction on consumer GPUs |
|
| **[BitsAndBytes W4](quantization/bitsandbytes.md)** | `quantization="bitsandbytes"` | Online NF4/FP4 weight-only quantization on validated CUDA transformer stages | Reducing transformer weight memory without preparing a separate checkpoint |
|
||||||
|
|
||||||
## Supported Models
|
## Supported Models
|
||||||
|
|
||||||
@@ -100,7 +100,9 @@ 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:
|
||||||
|
|
||||||
@@ -119,7 +121,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** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||||
@@ -145,6 +147,7 @@ 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
|
||||||
|
|
||||||
@@ -155,12 +158,16 @@ 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
|
||||||
@@ -189,7 +196,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)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❓ | ❓ | ❌ | | | | | |
|
||||||
@@ -200,97 +207,43 @@ The following tables show which models support each feature:
|
|||||||
|
|
||||||
!!! info
|
!!! info
|
||||||
|
|
||||||
1. Tensor Parallel and HSDP are not compatible.
|
1. HSDP can be combined with Ulysses-SP or CFG-Parallel. Tensor Parallel
|
||||||
|
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. CPU Offloading (Layerwise) supports single-card for now.
|
4. The CPU Offloading (Layerwise) row describes local layerwise offload.
|
||||||
5. Using FP8-Quant as an example of qunatization methods.
|
Multi-device Distributed Layerwise Offload has a separate topology and
|
||||||
|
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
|
||||||
|
|
||||||
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.
|
The loading guide now lives at [Diffusion Startup and
|
||||||
|
Loading](diffusion/startup_and_loading.md). This heading remains so existing
|
||||||
This optimization is **enabled by default** with 4 threads. No configuration is needed for the default behavior.
|
links to this section continue to work.
|
||||||
|
|
||||||
### 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
|
||||||
|
|
||||||
**Cache Acceleration:**
|
The Diffusion Acceleration navigation groups the remaining guides as follows:
|
||||||
|
|
||||||
- **[TeaCache Configuration Guide](diffusion/cache_acceleration/teacache.md)** - Parameter tuning, performance tips, troubleshooting
|
| Area | Guide |
|
||||||
- **[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) |
|
||||||
|
|
||||||
**Parallelism Methods:**
|
Related cross-model and runtime features are documented separately:
|
||||||
|
|
||||||
- **[Parallelism Overview](diffusion/parallelism/overview.md)** - Tensor Parallelism, Sequence Parallelism, CFG Parallelism, Pipeline Parallelism, HSDP, and Expert Parallelism
|
- [Quantization](quantization/overview.md) covers diffusion-only models,
|
||||||
|
multi-stage omni/TTS models, and multi-stage diffusion models.
|
||||||
**Memory Optimization:**
|
- [Execution Modes and Streaming](diffusion/execution_modes.md) covers the
|
||||||
|
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,8 +88,9 @@ 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 |
|
| `--lora-path` | str | — | Path to PEFT LoRA adapter folder or checkpoint file |
|
||||||
| `--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,6 +12,7 @@ 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 |
|
||||||
@@ -35,6 +36,25 @@ 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
|
||||||
@@ -123,8 +143,12 @@ python text_to_video.py \
|
|||||||
- `--enable-layerwise-offload`: enable layerwise offloading on DiT modules.
|
- `--enable-layerwise-offload`: enable layerwise offloading on DiT modules.
|
||||||
- `--frame-rate`: generation FPS for pipelines that require it (e.g., LTX2).
|
- `--frame-rate`: generation FPS for pipelines that require it (e.g., LTX2).
|
||||||
- `--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`: online quantization method, for example `fp8`; supported
|
||||||
|
pre-quantized checkpoints are normally auto-detected from checkpoint metadata.
|
||||||
- `--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
|
||||||
|
|
||||||
|
|||||||
@@ -90,8 +90,8 @@ vllm serve "Qwen/Qwen-Image" \
|
|||||||
--quantization-config '{"method": "fp8"}'
|
--quantization-config '{"method": "fp8"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
Other vLLM-Omni quantization methods, such as `gguf`, `modelopt`, `mxfp4`,
|
Other vLLM-Omni quantization methods, such as `modelopt`, `mxfp4`, `mxfp8`,
|
||||||
`mxfp8`, serialized checkpoints, static FP8 configs, and layer-name skip lists
|
serialized checkpoints, static FP8 configs, and layer-name skip lists
|
||||||
such as `ignored_layers`, are intentionally not translated by this compatibility
|
such as `ignored_layers`, are intentionally not translated by this compatibility
|
||||||
shortcut. Use Diffusers-native configuration through `diffusers_load_kwargs` or
|
shortcut. Use Diffusers-native configuration through `diffusers_load_kwargs` or
|
||||||
a native vLLM-Omni pipeline for those cases.
|
a native vLLM-Omni pipeline for those cases.
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ checkpoint's `config.json` and auto-detects
|
|||||||
AutoRound is static quantization: no `--quantization` flag is needed at
|
AutoRound is static quantization: no `--quantization` flag is needed at
|
||||||
inference time when the checkpoint already contains the quantization config.
|
inference time when the checkpoint already contains the quantization config.
|
||||||
|
|
||||||
|
The validation terms on this page follow the
|
||||||
|
[support levels](overview.md#support-levels) defined in the overview. A listed
|
||||||
|
checkpoint is not enough by itself to establish end-to-end model support.
|
||||||
|
|
||||||
## Hardware Support
|
## Hardware Support
|
||||||
|
|
||||||
| Device | Support |
|
| Device | Support |
|
||||||
@@ -21,46 +25,57 @@ inference time when the checkpoint already contains the quantization config.
|
|||||||
| Intel XPU | ✅ |
|
| Intel XPU | ✅ |
|
||||||
| Ascend NPU | ❌ |
|
| Ascend NPU | ❌ |
|
||||||
|
|
||||||
Legend: `✅` supported, `❌` unsupported, `⭕` not verified in this
|
Legend: `✅` backend available, `❌` unsupported, `⭕` not verified in this
|
||||||
guide. AutoRound is Intel-supported.
|
guide. This table is method-level backend availability. AutoRound is an Intel
|
||||||
|
project; vLLM selects a compatible CUDA or XPU compute backend at load time.
|
||||||
|
|
||||||
## Model Type Support
|
## Model Type Support
|
||||||
|
|
||||||
### Diffusion Model (Qwen-Image, Wan2.2)
|
### Diffusion Model (Qwen-Image, Wan2.2)
|
||||||
|
|
||||||
| Model | Checkpoint | Scope | Scheme | Backend |
|
| Model | Checkpoint | Scope | Scheme | Validation |
|
||||||
|-------|------------|-------|--------|---------|
|
|-------|------------|-------|--------|------------|
|
||||||
| FLUX.1-dev | `vllm-project-org/FLUX.1-dev-AutoRound-w4a16` | Diffusion transformer | W4A16 | GPTQ-Marlin or Intel-supported AutoRound backend |
|
| FLUX.1-dev | `vllm-project-org/FLUX.1-dev-AutoRound-w4a16` | Diffusion transformer | W4A16 | Validated |
|
||||||
| Qwen-Image | Not listed | Diffusion transformer | W4A16 | Not validated |
|
| Qwen-Image | `INC4AI/Qwen-Image-AutoRound-W4A16` | Diffusion transformer | W4A16 | Validated with end-to-end generation and quality comparison |
|
||||||
| Wan2.2-I2V | `Intel/Wan2.2-I2V-A14B-Diffusers-int4-AutoRound` | Diffusion transformer | W4A16 | GPTQ-Marlin or Intel-supported AutoRound backend |
|
| Wan2.2-I2V | `Intel/Wan2.2-I2V-A14B-Diffusers-int4-AutoRound` | Both diffusion transformers | W4A16 | CI-backed |
|
||||||
| Wan2.2-T2V | `Intel/Wan2.2-T2V-A14B-Diffusers-int4-AutoRound` | Diffusion transformer | W4A16 | GPTQ-Marlin or Intel-supported AutoRound backend |
|
| Wan2.2-T2V | `Intel/Wan2.2-T2V-A14B-Diffusers-int4-AutoRound` | Both diffusion transformers | W4A16 | CI-backed |
|
||||||
| Wan2.2-TI2V | `Intel/Wan2.2-TI2V-5B-Diffusers-int4-AutoRound` | Diffusion transformer | W4A16 | GPTQ-Marlin or Intel-supported AutoRound backend |
|
| Wan2.2-TI2V | `Intel/Wan2.2-TI2V-5B-Diffusers-int4-AutoRound` | Diffusion transformer | W4A16 | Validated; not in the scheduled Wan AutoRound job |
|
||||||
|
|
||||||
|
CUDA execution uses GPTQ-Marlin or another compatible vLLM AutoRound backend;
|
||||||
|
Intel XPU uses the Intel-supported backend. The text encoder and VAE stay in
|
||||||
|
BF16 for all entries above.
|
||||||
|
|
||||||
### World Model (Cosmos3)
|
### World Model (Cosmos3)
|
||||||
|
|
||||||
| Model | Checkpoint | Scope | Scheme | Backend |
|
| Model | Checkpoint | Scope | Scheme | Validation |
|
||||||
|-------|------------|-------|--------|---------|
|
|-------|------------|-------|--------|------------|
|
||||||
| Cosmos3-Nano | `Intel/Cosmos3-Nano-int4-AutoRound` | World-model transformer | W4A16 | GPTQ-Marlin or Intel-supported AutoRound backend |
|
| Cosmos3-Nano | `Intel/Cosmos3-Nano-int4-AutoRound` | World-model transformer | W4A16 | Integrated; no model-specific end-to-end test is maintained in-tree |
|
||||||
| Cosmos3-Super | `Intel/Cosmos3-Super-int4-AutoRound` | World-model transformer | W4A16 | GPTQ-Marlin or Intel-supported AutoRound backend |
|
| Cosmos3-Super | `Intel/Cosmos3-Super-int4-AutoRound` | World-model transformer | W4A16 | Validated with a manual serving run |
|
||||||
|
|
||||||
|
The Cosmos VAE and guardrail components stay in BF16.
|
||||||
|
|
||||||
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
||||||
|
|
||||||
| Model | Checkpoint | Scope | Scheme | Backend |
|
| Model | Checkpoint | Scope | Scheme | Validation |
|
||||||
|-------|------------|-------|--------|---------|
|
|-------|------------|-------|--------|------------|
|
||||||
| Qwen2.5-Omni-7B | `Intel/Qwen2.5-Omni-7B-int4-AutoRound` | Language-model stage | W4A16 | AutoRound |
|
| Qwen2.5-Omni-7B | `Intel/Qwen2.5-Omni-7B-int4-AutoRound` | Thinker language-model stage | W4A16 | Validated outside scheduled CI |
|
||||||
| Qwen3-Omni-30B-A3B-Instruct | `Intel/Qwen3-Omni-30B-A3B-Instruct-int4-AutoRound` | Thinker language-model stage | W4A16 | AutoRound |
|
| Qwen3-Omni-30B-A3B-Instruct | `Intel/Qwen3-Omni-30B-A3B-Instruct-int4-AutoRound` | Thinker language-model stage | W4A16 | Validated outside scheduled CI |
|
||||||
| Qwen3-TTS | Not listed | TTS language-model stage | W4A16 | Not validated |
|
| Qwen3-TTS | Not listed | TTS language-model stage | W4A16 | Not validated |
|
||||||
|
|
||||||
AutoRound support is checkpoint-driven. A model is supported when its
|
AutoRound support is checkpoint-driven. A model is supported when its
|
||||||
checkpoint uses a compatible INC/AutoRound config and the target stage maps to
|
checkpoint uses a compatible INC/AutoRound config and the target stage maps to
|
||||||
vLLM-Omni's runtime module names.
|
vLLM-Omni's runtime module names.
|
||||||
|
|
||||||
|
For Qwen-Omni, only the thinker language model uses the AutoRound checkpoint.
|
||||||
|
Audio and vision encoders, the talker, and waveform-decoder stages stay in
|
||||||
|
BF16.
|
||||||
|
|
||||||
### Multi-Stage Diffusion Model (BAGEL, GLM-Image)
|
### Multi-Stage Diffusion Model (BAGEL, GLM-Image)
|
||||||
|
|
||||||
| Model | Scope | Status | Notes |
|
| Model | Checkpoint | Scope | Validation |
|
||||||
|-------|-------|--------|-------|
|
|-------|------------|-------|------------|
|
||||||
| GLM-Image | Diffusion transformer | ✅ | `Intel/GLM-Image-int4-AutoRound` |
|
| GLM-Image | `Intel/GLM-Image-int4-AutoRound` | Diffusion transformer | CI-backed |
|
||||||
| BAGEL | Checkpoint-defined diffusion or transformer stage | Not validated | Requires a compatible AutoRound checkpoint |
|
| BAGEL | Not listed | Checkpoint-defined diffusion or transformer stage | Not validated |
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@@ -119,6 +134,14 @@ The checkpoint should contain a config like:
|
|||||||
At load time, vLLM-Omni builds an `OmniINCConfig`, remaps checkpoint block names
|
At load time, vLLM-Omni builds an `OmniINCConfig`, remaps checkpoint block names
|
||||||
to runtime module names, and selects the matching vLLM compute backend.
|
to runtime module names, and selects the matching vLLM compute backend.
|
||||||
|
|
||||||
|
Checkpoint auto-detection and successful layer construction establish the
|
||||||
|
**Integrated** level. Promote a model to **Validated** only after the named
|
||||||
|
checkpoint completes an end-to-end generation or multimodal request and its
|
||||||
|
output passes a model-appropriate sanity or quality check. Compare against the
|
||||||
|
BF16 baseline for quality-sensitive image and video paths. Promote it to
|
||||||
|
**CI-backed** only when that named checkpoint is selected by scheduled hardware
|
||||||
|
CI.
|
||||||
|
|
||||||
Example checkpoint creation:
|
Example checkpoint creation:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ stay in BF16/FP16. No pre-quantized checkpoint is required.
|
|||||||
This is an online (dynamic) path: load the normal HuggingFace checkpoint and
|
This is an online (dynamic) path: load the normal HuggingFace checkpoint and
|
||||||
quantize during model loading via the `bitsandbytes` CUDA kernels.
|
quantize during model loading via the `bitsandbytes` CUDA kernels.
|
||||||
|
|
||||||
|
The validation terms on this page follow the
|
||||||
|
[support levels](overview.md#support-levels). BitsAndBytes kernel support does
|
||||||
|
not imply that every diffusion transformer has been quality-validated.
|
||||||
|
|
||||||
## Hardware Support
|
## Hardware Support
|
||||||
|
|
||||||
| Device | Support |
|
| Device | Support |
|
||||||
@@ -21,7 +25,7 @@ quantize during model loading via the `bitsandbytes` CUDA kernels.
|
|||||||
| Intel XPU | ❌ |
|
| Intel XPU | ❌ |
|
||||||
| Ascend NPU | ❌ |
|
| Ascend NPU | ❌ |
|
||||||
|
|
||||||
Legend: `✅` supported, `❌` unsupported. Non-CUDA platforms raise in
|
Legend: `✅` backend available, `❌` unsupported. Non-CUDA platforms raise in
|
||||||
`get_quant_method()`; CUDA requires compute capability 7.5+ (`SM 75+`).
|
`get_quant_method()`; CUDA requires compute capability 7.5+ (`SM 75+`).
|
||||||
|
|
||||||
Requires the optional `bitsandbytes` package (`pip install bitsandbytes`).
|
Requires the optional `bitsandbytes` package (`pip install bitsandbytes`).
|
||||||
@@ -30,11 +34,11 @@ Requires the optional `bitsandbytes` package (`pip install bitsandbytes`).
|
|||||||
|
|
||||||
### Diffusion Model (Qwen-Image, Wan2.2)
|
### Diffusion Model (Qwen-Image, Wan2.2)
|
||||||
|
|
||||||
| Model | HF models | CUDA | Mode | Recommendation |
|
| Model | HF models | Scope | Level | Recommendation |
|
||||||
|-------|-----------|:----:|------|----------------|
|
|-------|-----------|-------|-------|----------------|
|
||||||
| Z-Image | `Tongyi-MAI/Z-Image-Turbo` | Yes | Online W4 weight-only | All heavy linear layers; sensitive embedders stay BF16 |
|
| Z-Image | `Tongyi-MAI/Z-Image-Turbo` | Diffusion transformer | Validated | All heavy linear layers; sensitive embedders stay BF16 |
|
||||||
| Qwen-Image | `Qwen/Qwen-Image`, `Qwen/Qwen-Image-2512` | Not validated | Online W4 weight-only | Compare vs BF16 before enabling |
|
| Qwen-Image | `Qwen/Qwen-Image`, `Qwen/Qwen-Image-2512` | Diffusion transformer | Not validated | Compare with BF16 before enabling |
|
||||||
| Wan2.2 | Wan2.2 diffusion pipelines | Not validated | Online W4 weight-only | Validate before enabling in docs |
|
| Wan2.2 | Wan2.2 diffusion pipelines | One or both diffusion transformers | Not validated | Validate quality and cascade behavior before enabling |
|
||||||
|
|
||||||
Other diffusion models may work if their transformer uses supported linear
|
Other diffusion models may work if their transformer uses supported linear
|
||||||
layers, but they are not validated in this guide.
|
layers, but they are not validated in this guide.
|
||||||
@@ -104,6 +108,10 @@ Multi-GPU tensor parallelism (`tensor_parallel_size` > 1) is not validated for
|
|||||||
BitsAndBytes in diffusion models; each rank quantizes its own weight shard
|
BitsAndBytes in diffusion models; each rank quantizes its own weight shard
|
||||||
independently.
|
independently.
|
||||||
|
|
||||||
|
Z-Image-Turbo is validated outside scheduled full-model BitsAndBytes CI. The
|
||||||
|
real CUDA kernel smoke tests cover quantization and matrix multiplication, but
|
||||||
|
do not establish model-level quality for unlisted architectures.
|
||||||
|
|
||||||
If quality regresses, use `ignored_layers` to keep sensitive projections in BF16
|
If quality regresses, use `ignored_layers` to keep sensitive projections in BF16
|
||||||
(for example `to_out` or `w2`).
|
(for example `to_out` or `w2`).
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ components, such as the tokenizer, scheduler, text encoder, vision/audio
|
|||||||
encoder, and VAE, on the base checkpoint unless a model-specific guide says
|
encoder, and VAE, on the base checkpoint unless a model-specific guide says
|
||||||
otherwise.
|
otherwise.
|
||||||
|
|
||||||
|
The validation terms on this page follow the
|
||||||
|
[support levels](overview.md#support-levels) defined in the overview. ModelOpt
|
||||||
|
format detection and checkpoint adaptation are shared runtime integrations;
|
||||||
|
each model and stage still requires its own end-to-end validation.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
ModelOpt checkpoints are pre-quantized checkpoints. Do not pass
|
ModelOpt checkpoints are pre-quantized checkpoints. Do not pass
|
||||||
`--quantization fp8` for these checkpoints. The checkpoint
|
`--quantization fp8` for these checkpoints. The checkpoint
|
||||||
@@ -58,11 +63,11 @@ stay unquantized unless the model-specific recipe validates otherwise.
|
|||||||
| Intel XPU | ⭕ |
|
| Intel XPU | ⭕ |
|
||||||
| Ascend NPU | ❌ |
|
| Ascend NPU | ❌ |
|
||||||
|
|
||||||
Legend: `✅` supported, `❌` unsupported, `⭕` not verified in this guide.
|
Legend: `✅` backend available, `❌` unsupported, `⭕` not verified in this
|
||||||
The optional CUTLASS FP8 runtime override requires CUDA SM89+. ModelOpt NVFP4
|
guide. The optional CUTLASS FP8 runtime override requires CUDA SM89+. ModelOpt
|
||||||
and mixed FP8/NVFP4 diffusion checkpoints are currently validated on Blackwell
|
NVFP4 and mixed FP8/NVFP4 diffusion checkpoints are currently validated on
|
||||||
CUDA systems in the recipes below; other CUDA generations require separate
|
Blackwell CUDA systems in the recipes below; other CUDA generations require
|
||||||
backend and quality validation.
|
separate backend and quality validation.
|
||||||
|
|
||||||
## Model Type Support
|
## Model Type Support
|
||||||
|
|
||||||
@@ -79,6 +84,9 @@ backend and quality validation.
|
|||||||
| HunyuanImage-3.0 | `feizhai123/hunyuan-image3-modelopt-mixed-experts-nvfp4-dense-fp8` | MoE diffusion transformer | Validated for ModelOpt mixed FP8/NVFP4 checkpoints |
|
| HunyuanImage-3.0 | `feizhai123/hunyuan-image3-modelopt-mixed-experts-nvfp4-dense-fp8` | MoE diffusion transformer | Validated for ModelOpt mixed FP8/NVFP4 checkpoints |
|
||||||
| Wan2.2 | Not available | Diffusion transformer | Not validated |
|
| Wan2.2 | Not available | Diffusion transformer | Not validated |
|
||||||
|
|
||||||
|
The validated diffusion entries above have named checkpoint and recipe
|
||||||
|
evidence. They are not all exercised by scheduled full-model CI.
|
||||||
|
|
||||||
For full serving commands and benchmark context, see
|
For full serving commands and benchmark context, see
|
||||||
[`recipes/Qwen/Qwen-Image.md`](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Qwen/Qwen-Image.md)
|
[`recipes/Qwen/Qwen-Image.md`](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Qwen/Qwen-Image.md)
|
||||||
and
|
and
|
||||||
@@ -88,8 +96,8 @@ and
|
|||||||
|
|
||||||
| Model | Scope | Status |
|
| Model | Scope | Status |
|
||||||
|-------|-------|--------|
|
|-------|-------|--------|
|
||||||
| Qwen3-Omni | Thinker language-model stage | ModelOpt FP8 checkpoint path |
|
| Qwen3-Omni | Thinker language-model stage (FP8) | Validated outside scheduled CI |
|
||||||
| Qwen3-Omni | Thinker language-model stage (W4A4 NVFP4) | Validated; see [Qwen3-Omni NVFP4 W4A4](#qwen3-omni-nvfp4-w4a4-thinker) below |
|
| Qwen3-Omni | Thinker language-model stage (W4A4 NVFP4) | CI-backed for the H100 Marlin fallback; native Blackwell path is recipe-validated |
|
||||||
| Qwen3-TTS | TTS language-model stage | Not validated |
|
| Qwen3-TTS | TTS language-model stage | Not validated |
|
||||||
|
|
||||||
Audio encoder, vision encoder, talker, and code2wav stages stay in BF16 unless
|
Audio encoder, vision encoder, talker, and code2wav stages stay in BF16 unless
|
||||||
@@ -102,9 +110,9 @@ Qwen3-Omni-30B-A3B-Instruct thinker language model. The thinker text body
|
|||||||
(attention + MoE experts) is quantized to NVFP4 with FP8 per-tensor input
|
(attention + MoE experts) is quantized to NVFP4 with FP8 per-tensor input
|
||||||
scales; the audio encoder, vision encoder, talker, and code2wav stay in BF16.
|
scales; the audio encoder, vision encoder, talker, and code2wav stay in BF16.
|
||||||
|
|
||||||
| Variant | HF checkpoint | Hardware |
|
| Variant | HF checkpoint | Hardware and validation |
|
||||||
|---------|---------------|----------|
|
|---------|---------------|-------------------------|
|
||||||
| W4A4 NVFP4 (full thinker) | `YihongJin/Qwen3-Omni-30B-A3B-Instruct-NVFP4-W4A4-full-thinker-awqclip` | sm_100+ (Blackwell, FlashInfer FP4 GEMM) |
|
| W4A4 NVFP4 (full thinker) | `YihongJin/Qwen3-Omni-30B-A3B-Instruct-NVFP4-W4A4-full-thinker-awqclip` | sm_100+ native FlashInfer FP4 GEMM; H100 Marlin fallback is CI-backed |
|
||||||
|
|
||||||
Calibration uses ModelOpt `mtq.NVFP4_DEFAULT_CFG` with the `awq_clip` algorithm
|
Calibration uses ModelOpt `mtq.NVFP4_DEFAULT_CFG` with the `awq_clip` algorithm
|
||||||
on 1024 ultrachat samples chat-templated through the Qwen3-Omni tokenizer.
|
on 1024 ultrachat samples chat-templated through the Qwen3-Omni tokenizer.
|
||||||
@@ -232,3 +240,6 @@ omni = Omni(
|
|||||||
5. Keep `--quantization fp8` for online FP8 from BF16 checkpoints; use this
|
5. Keep `--quantization fp8` for online FP8 from BF16 checkpoints; use this
|
||||||
ModelOpt path only when the checkpoint already contains ModelOpt quantized
|
ModelOpt path only when the checkpoint already contains ModelOpt quantized
|
||||||
weights and scales.
|
weights and scales.
|
||||||
|
6. The Qwen3-Omni full-model CI smoke exercises the H100 Marlin fallback. A
|
||||||
|
scheduled Blackwell test is still required before treating the native
|
||||||
|
FlashInfer FP4 kernel path as CI-backed.
|
||||||
@@ -3,12 +3,17 @@
|
|||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
[msModelSlim](https://github.com/Ascend/msmodelslim) is an Ascend compression
|
[msModelSlim](https://github.com/Ascend/msmodelslim) is an Ascend compression
|
||||||
toolkit for producing pre-quantized model checkpoints. In vLLM-Omni, these
|
toolkit for producing pre-quantized model checkpoints. vLLM-Omni consumes its
|
||||||
checkpoints run through the Ascend/NPU path with `--quantization ascend`.
|
output either through a compatible generic Ascend checkpoint path or through
|
||||||
|
the native MXFP8 and MXFP4 merge workflows documented in this guide.
|
||||||
|
|
||||||
msModelSlim is static quantization: quantized weights are generated offline
|
msModelSlim is static quantization: quantized weights are generated offline
|
||||||
before vLLM-Omni inference starts.
|
before vLLM-Omni inference starts.
|
||||||
|
|
||||||
|
The validation terms on this page follow the
|
||||||
|
[support levels](overview.md#support-levels). A producer recipe or hardware
|
||||||
|
backend alone is an integration point, not model-level validation.
|
||||||
|
|
||||||
## Hardware Support
|
## Hardware Support
|
||||||
|
|
||||||
| Device | Support |
|
| Device | Support |
|
||||||
@@ -20,23 +25,28 @@ before vLLM-Omni inference starts.
|
|||||||
| Intel XPU | ❌ |
|
| Intel XPU | ❌ |
|
||||||
| Ascend NPU | ✅ |
|
| Ascend NPU | ✅ |
|
||||||
|
|
||||||
Legend: `✅` supported, `❌` unsupported, `⭕` not verified in this
|
Legend: `✅` backend available, `❌` unsupported, `⭕` not verified in this
|
||||||
guide.
|
guide.
|
||||||
|
|
||||||
## Model Type Support
|
## Model Type Support
|
||||||
|
|
||||||
### Diffusion Model (Qwen-Image, Wan2.2)
|
### Diffusion Model (Qwen-Image, Wan2.2)
|
||||||
|
|
||||||
| Model | Base model | Scope | Hardware | Notes |
|
| Model | Base model | Scope | Level | Notes |
|
||||||
|-------|------------|-------|----------|-------|
|
|-------|------------|-------|-------|-------|
|
||||||
| Wan2.2 | Wan2.2 diffusion weights | DiT or diffusion stage | Ascend NPU | Upstream msModelSlim provides a Wan2.2 quantization recipe; vLLM-Omni inference validation is not listed |
|
| Wan2.2 | Wan2.2 diffusion weights | DiT or diffusion stage | Integrated | Upstream msModelSlim provides a producer recipe; use the [MXFP8](mxfp8.md) or [MXFP4](mxfp4.md) workflow for vLLM-Omni's native merged formats |
|
||||||
| Qwen-Image | `Qwen/Qwen-Image`, `Qwen/Qwen-Image-2512` | DiT or diffusion stage | Ascend NPU | Not validated in this guide |
|
| Qwen-Image | `Qwen/Qwen-Image`, `Qwen/Qwen-Image-2512` | DiT or diffusion stage | Not validated | No supported checkpoint workflow is documented |
|
||||||
| HunyuanImage-3.0 | `tencent/HunyuanImage-3.0`, `tencent/HunyuanImage-3.0-Instruct` | DiT or diffusion stage | Ascend A2/A3 NPU | Generate quantized weights with the HunyuanImage-3.0 msModelSlim adaptation |
|
| HunyuanImage-3.0 | `tencent/HunyuanImage-3.0`, `tencent/HunyuanImage-3.0-Instruct` | DiT or diffusion stage | Integrated | Generate quantized weights with the HunyuanImage-3.0 msModelSlim adaptation and validate against BF16 |
|
||||||
|
|
||||||
Public Hugging Face quantized weights are not available yet. Use the
|
Public Hugging Face quantized weights are not available yet. Use the
|
||||||
[HunyuanImage-3.0 msModelSlim adaptation](https://gitcode.com/betta18/msmodelslim/tree/hyimage3_mxfp8)
|
[HunyuanImage-3.0 msModelSlim adaptation](https://gitcode.com/betta18/msmodelslim/tree/hyimage3_mxfp8)
|
||||||
to generate the checkpoint manually.
|
to generate the checkpoint manually.
|
||||||
|
|
||||||
|
msModelSlim is also the producer used by the native offline MXFP8 and MXFP4
|
||||||
|
DualScale workflows. Those workflows preprocess and annotate each transformer
|
||||||
|
checkpoint and are auto-detected at inference; follow their method pages rather
|
||||||
|
than passing `--quantization ascend`.
|
||||||
|
|
||||||
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
||||||
|
|
||||||
| Model | Scope | Status | Notes |
|
| Model | Scope | Status | Notes |
|
||||||
@@ -53,25 +63,32 @@ to generate the checkpoint manually.
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
For a compatible generic Ascend checkpoint, select the Ascend quantization
|
||||||
|
method explicitly:
|
||||||
|
|
||||||
Offline inference:
|
Offline inference:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python text_to_image.py --model <quantized-model-path> --quantization ascend
|
python text_to_image.py --model <compatible-ascend-checkpoint> --quantization ascend
|
||||||
```
|
```
|
||||||
|
|
||||||
Online serving:
|
Online serving:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vllm serve <quantized-model-path> --omni --quantization ascend
|
vllm serve <compatible-ascend-checkpoint> --omni --quantization ascend
|
||||||
```
|
```
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
| Parameter | Type | Default | Description |
|
| Parameter | Type | Default | Description |
|
||||||
|-----------|------|---------|-------------|
|
|-----------|------|---------|-------------|
|
||||||
| `--quantization` | str | - | Use `ascend` for msModelSlim-produced checkpoints |
|
| `--quantization` | str | - | Use `ascend` only for a compatible generic Ascend-format checkpoint; omit it for native merged MXFP8/MXFP4 checkpoints |
|
||||||
| `model` | str | - | Path to the quantized checkpoint generated by Ascend tooling |
|
| `model` | str | - | Path to the quantized checkpoint generated by Ascend tooling |
|
||||||
|
|
||||||
|
Do not pass `--quantization ascend` for a Wan checkpoint processed by the native
|
||||||
|
MXFP8 or MXFP4 merge tools. Their transformer metadata selects `mxfp8` or
|
||||||
|
`mxfp4_dualscale` automatically.
|
||||||
|
|
||||||
Example msModelSlim command for a Wan2.2 W8A8 checkpoint:
|
Example msModelSlim command for a Wan2.2 W8A8 checkpoint:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -93,3 +110,6 @@ For HunyuanImage-3.0, use the Hunyuan-specific adaptation linked above.
|
|||||||
tooling; it is not a load-time CUDA quantizer.
|
tooling; it is not a load-time CUDA quantizer.
|
||||||
3. Keep the quantized checkpoint aligned with the same model architecture and
|
3. Keep the quantized checkpoint aligned with the same model architecture and
|
||||||
stage config used for BF16 inference.
|
stage config used for BF16 inference.
|
||||||
|
4. No named full-model msModelSlim checkpoint is currently scheduled in NPU
|
||||||
|
CI. Treat new producer outputs as **Integrated** until an end-to-end BF16
|
||||||
|
comparison establishes **Validated** support.
|
||||||
@@ -13,6 +13,10 @@ vLLM-Omni provides two quantization methods with different scale structures:
|
|||||||
| `mxfp4` | Single-scale (per-32 fine only) | Online only | Quick accuracy baseline; no checkpoint prep needed |
|
| `mxfp4` | Single-scale (per-32 fine only) | Online only | Quick accuracy baseline; no checkpoint prep needed |
|
||||||
| `mxfp4_dualscale` | Dual-scale (fine per-32 + coarse per-512 + per-channel `mul_scale`) | Online + Offline | Production; better accuracy; offline recommended |
|
| `mxfp4_dualscale` | Dual-scale (fine per-32 + coarse per-512 + per-channel `mul_scale`) | Online + Offline | Production; better accuracy; offline recommended |
|
||||||
|
|
||||||
|
The validation terms on this page follow the
|
||||||
|
[support levels](overview.md#support-levels). Current model guidance covers
|
||||||
|
Wan diffusion transformers only; the text encoder and VAE stay in BF16.
|
||||||
|
|
||||||
!!! tip "Recommended: `mxfp4_dualscale` offline"
|
!!! tip "Recommended: `mxfp4_dualscale` offline"
|
||||||
For production deployments, use the `mxfp4_dualscale` offline mode with a
|
For production deployments, use the `mxfp4_dualscale` offline mode with a
|
||||||
pre-quantized checkpoint produced by msModelSlim. Offline checkpoints load
|
pre-quantized checkpoint produced by msModelSlim. Offline checkpoints load
|
||||||
@@ -34,26 +38,45 @@ vLLM-Omni provides two quantization methods with different scale structures:
|
|||||||
|
|
||||||
## Hardware Support
|
## Hardware Support
|
||||||
|
|
||||||
| Device | Support |
|
| Device | Online `mxfp4` | Online `mxfp4_dualscale` | Offline `mxfp4_dualscale` |
|
||||||
|--------|---------|
|
|--------|-----------------|---------------------------|----------------------------|
|
||||||
| 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 (gfx950 / MI355X) | ✅ |
|
| AMD ROCm (gfx950 / MI355X) | ✅ | ❌ | ❌ |
|
||||||
| Intel XPU | ⭕ |
|
| Intel XPU | ❌ | ❌ | ❌ |
|
||||||
| Ascend NPU (Atlas 950 A5) | ✅ |
|
| Ascend NPU (Atlas 950 A5) | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
Legend: `✅` supported, `❌` unsupported, `⭕` not verified in this guide.
|
Legend: `✅` backend available, `❌` unsupported, `⭕` not verified in this
|
||||||
|
guide. Backend availability does not imply full-model checkpoint validation.
|
||||||
|
|
||||||
|
!!! note "Why NVIDIA is marked unsupported"
|
||||||
|
This table describes the methods currently wired into vLLM-Omni, not every
|
||||||
|
low-precision format NVIDIA hardware could execute. `DiffusionMXFP4Config`
|
||||||
|
has runtime implementations only for Ascend and ROCm gfx950; on CUDA it
|
||||||
|
raises `NotImplementedError` because no CUDA MXFP4 linear method or
|
||||||
|
checkpoint loader is registered.
|
||||||
|
|
||||||
|
On NVIDIA, use [ModelOpt](modelopt.md) NVFP4 for the documented 4-bit
|
||||||
|
checkpoint path. NVFP4 and OCP MXFP4 have different checkpoint metadata,
|
||||||
|
scale layouts, and kernel contracts, so they are not interchangeable. A
|
||||||
|
native CUDA MXFP4 path would require a CUDA linear implementation, loader
|
||||||
|
contract, and model-level output validation.
|
||||||
|
|
||||||
|
The ROCm path dispatches online single-scale `mxfp4` to AITER on gfx950. It has
|
||||||
|
kernel and dispatch coverage, but this guide does not yet claim end-to-end
|
||||||
|
quality validation for a named Wan checkpoint on ROCm. DualScale and
|
||||||
|
pre-quantized MXFP4 checkpoints are not supported on ROCm.
|
||||||
|
|
||||||
## Model Type Support
|
## Model Type Support
|
||||||
|
|
||||||
### Diffusion Model (Wan2.2)
|
### Diffusion Model (Wan2.2)
|
||||||
|
|
||||||
| Model | Online | Offline | Notes |
|
| Model | Online (Ascend) | Offline (Ascend) | Validation and notes |
|
||||||
|-------|--------|---------|-------|
|
|-------|-----------------|------------------|----------------------|
|
||||||
| Wan2.2-T2V-A14B | `mxfp4` / `mxfp4_dualscale` | `mxfp4_dualscale` | MoE cascade (`transformer` + `transformer_2`); both transformers quantized with the same config |
|
| Wan2.2-T2V-A14B | `mxfp4` / `mxfp4_dualscale` | `mxfp4_dualscale` | Validated outside scheduled full-model offline NPU CI; both cascade transformers are quantized |
|
||||||
| Wan2.2-I2V-A14B | `mxfp4` / `mxfp4_dualscale` | `mxfp4_dualscale` | MoE cascade; same scheme as T2V-A14B |
|
| Wan2.2-I2V-A14B | `mxfp4` / `mxfp4_dualscale` | `mxfp4_dualscale` | Validated outside scheduled full-model offline NPU CI; same cascade scheme as T2V-A14B |
|
||||||
| Wan2.2-TI2V-5B | ❌ | ❌ | Parameter count too small; W4A4 causes unacceptable accuracy loss |
|
| Wan2.2-TI2V-5B | ❌ | ❌ | Unsupported because W4A4 causes unacceptable accuracy loss |
|
||||||
|
|
||||||
The choice between `mxfp4` and `mxfp4_dualscale` in **online mode** is about
|
The choice between `mxfp4` and `mxfp4_dualscale` in **online mode** is about
|
||||||
quantization quality, not model compatibility — both work on cascade (A14B) and
|
quantization quality, not model compatibility — both work on cascade (A14B) and
|
||||||
@@ -83,12 +106,13 @@ by the merge script); there is no offline `mxfp4` single-scale format.
|
|||||||
|
|
||||||
### `mxfp4` — Single-Scale Online Mode
|
### `mxfp4` — Single-Scale Online Mode
|
||||||
|
|
||||||
Online mode requires no pre-processing. vLLM-Omni quantizes BF16 weights to
|
Online mode requires no pre-processing. On Ascend, vLLM-Omni quantizes BF16
|
||||||
MXFP4 at load time using `npu_dynamic_mx_quant`. A single block scale
|
weights at load time using `npu_dynamic_mx_quant`. On ROCm gfx950, the same
|
||||||
|
`mxfp4` config dispatches to the AITER online kernel. A single block scale
|
||||||
(`float8_e8m0fnu`, one per 32 K elements) is computed on the fly; no
|
(`float8_e8m0fnu`, one per 32 K elements) is computed on the fly; no
|
||||||
calibration `mul_scale` is available. Applies equally to single-transformer
|
calibration `mul_scale` is available. The config applies equally to
|
||||||
and cascade (A14B) models — both transformers in a cascade receive the same
|
single-transformer and cascade (A14B) models, but named Wan model validation is
|
||||||
quantization config automatically.
|
currently limited to the documented Ascend path.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from vllm_omni import Omni
|
from vllm_omni import Omni
|
||||||
@@ -111,10 +135,10 @@ vllm serve <your-model> --omni --quantization mxfp4
|
|||||||
|
|
||||||
### `mxfp4_dualscale` — DualScale Online Mode
|
### `mxfp4_dualscale` — DualScale Online Mode
|
||||||
|
|
||||||
Online DualScale mode computes both fine and coarse scales on the fly from BF16
|
Ascend-only online DualScale mode computes both fine and coarse scales on the
|
||||||
weights using `npu_dynamic_dual_level_mx_quant`. Applies equally to
|
fly from BF16 weights using `npu_dynamic_dual_level_mx_quant`. It applies
|
||||||
single-transformer and cascade (A14B) models. Compared to `mxfp4` online,
|
equally to single-transformer and cascade (A14B) models. Compared to `mxfp4`
|
||||||
DualScale provides better quantization accuracy at higher compute cost.
|
online, DualScale provides better quantization accuracy at higher compute cost.
|
||||||
|
|
||||||
The default configuration keeps the leading 5 transformer blocks in BF16
|
The default configuration keeps the leading 5 transformer blocks in BF16
|
||||||
(`num_bf16_fallback_layers=5`). Accuracy evaluation on Wan2.2-A14B shows this
|
(`num_bf16_fallback_layers=5`). Accuracy evaluation on Wan2.2-A14B shows this
|
||||||
@@ -341,6 +365,11 @@ unless they appear in `ignored_layers`.
|
|||||||
quantization. Use `ignored_layers` and `num_bf16_fallback_layers` to trade
|
quantization. Use `ignored_layers` and `num_bf16_fallback_layers` to trade
|
||||||
off compression vs. accuracy for precision-sensitive layers.
|
off compression vs. accuracy for precision-sensitive layers.
|
||||||
|
|
||||||
|
8. The native methods, merge mappings, and per-transformer cascade metadata
|
||||||
|
have unit coverage. The current Buildkite configuration does not schedule a
|
||||||
|
named full-model offline MXFP4 checkpoint on NPU, so this path is
|
||||||
|
**Validated**, not **CI-backed**.
|
||||||
|
|
||||||
## Adapting MXFP4 for a New Model
|
## Adapting MXFP4 for a New Model
|
||||||
|
|
||||||
This section is aimed at developers who want to add MXFP4 support to a model
|
This section is aimed at developers who want to add MXFP4 support to a model
|
||||||
|
|||||||
@@ -15,30 +15,45 @@ This method supports three modes:
|
|||||||
| **Offline (Native)** | msModelSlim-exported MXFP8 weights converted to diffusers format via `merge_mxfp8_checkpoint.py` — weights and scales are loaded directly from the preprocessed checkpoint |
|
| **Offline (Native)** | msModelSlim-exported MXFP8 weights converted to diffusers format via `merge_mxfp8_checkpoint.py` — weights and scales are loaded directly from the preprocessed checkpoint |
|
||||||
| **Offline (AutoRound)** | AutoRound MXFP8 checkpoints with `data_type="mx_fp"` — auto-detected from `config.json` |
|
| **Offline (AutoRound)** | AutoRound MXFP8 checkpoints with `data_type="mx_fp"` — auto-detected from `config.json` |
|
||||||
|
|
||||||
|
The validation terms on this page follow the
|
||||||
|
[support levels](overview.md#support-levels). Current model guidance covers
|
||||||
|
Wan diffusion transformers only; the text encoder and VAE stay in BF16.
|
||||||
|
|
||||||
## Hardware Support
|
## Hardware Support
|
||||||
|
|
||||||
| Device | Online | Offline (Native) | Offline (AutoRound) |
|
| Device | Online | Offline (Native) | Offline (AutoRound) |
|
||||||
|--------|--------|------------------|---------------------|
|
|--------|--------|------------------|---------------------|
|
||||||
| 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 (Atlas 950 A5) | ✅ | ✅ | ⭕ |
|
| Ascend NPU (Atlas 950 A5) | ✅ | ✅ | ⭕ |
|
||||||
|
|
||||||
Legend: `✅` supported, `❌` unsupported, `⭕` not verified in this guide.
|
Legend: `✅` backend available, `❌` unsupported, `⭕` not verified in this
|
||||||
|
guide. Backend availability does not imply full-model checkpoint validation.
|
||||||
|
|
||||||
**Note**: Intel XPU only supports AutoRound MXFP8 for offline mode. Use AutoRound quantized checkpoints or online mode for XPU.
|
The native `DiffusionMXFP8Config` currently dispatches only to Intel XPU and
|
||||||
|
Ascend NPU implementations. CUDA reaches `NotImplementedError`, so the NVIDIA
|
||||||
|
rows are marked unsupported even though this is an implementation gap rather
|
||||||
|
than a general claim about NVIDIA hardware capabilities. On NVIDIA, use
|
||||||
|
[FP8](fp8.md) for load-time quantization or [ModelOpt](modelopt.md) FP8 for
|
||||||
|
pre-quantized checkpoints. Adding native CUDA MXFP8 requires a registered CUDA
|
||||||
|
linear method, a compatible checkpoint contract, and model-level validation.
|
||||||
|
|
||||||
|
Native `mxfp8` is available online on Intel XPU and online or offline on
|
||||||
|
Ascend. Intel XPU only supports AutoRound MXFP8 for offline mode. The AutoRound
|
||||||
|
column is a separate checkpoint format and compute path.
|
||||||
|
|
||||||
## Model Type Support
|
## Model Type Support
|
||||||
|
|
||||||
### Diffusion Model (Wan2.2)
|
### Diffusion Model (Wan2.2)
|
||||||
|
|
||||||
| Model | Mode | Notes |
|
| Model | Mode | Scope | Validation |
|
||||||
|-------|------|-------|
|
|-------|------|-------|------------|
|
||||||
| Wan2.2-T2V-A14B | Online + Offline | MoE cascade; quantizes two transformers (`transformer` + `transformer_2`) |
|
| Wan2.2-T2V-A14B | Online + Offline | MoE cascade: `transformer` + `transformer_2` | Validated outside scheduled full-model offline NPU CI |
|
||||||
| Wan2.2-I2V-A14B | Online + Offline | MoE cascade; quantizes two transformers (`transformer` + `transformer_2`) |
|
| Wan2.2-I2V-A14B | Online + Offline | MoE cascade: `transformer` + `transformer_2` | Validated outside scheduled full-model offline NPU CI |
|
||||||
| Wan2.2-TI2V-5B | Online + Offline | Single transformer |
|
| Wan2.2-TI2V-5B | Online + Offline | Single diffusion transformer | Validated outside scheduled full-model offline NPU CI |
|
||||||
|
|
||||||
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
||||||
|
|
||||||
@@ -218,3 +233,7 @@ omni = Omni(model="<autoround-mxfp8-model>")
|
|||||||
`--output-dir`), regenerate it with the current script. The old script
|
`--output-dir`), regenerate it with the current script. The old script
|
||||||
wrote a separate `quantization_config.json` that is not read by vLLM-Omni;
|
wrote a separate `quantization_config.json` that is not read by vLLM-Omni;
|
||||||
the current script injects the config directly into `transformer/config.json`.
|
the current script injects the config directly into `transformer/config.json`.
|
||||||
|
4. The native loader, merge mappings, and Wan cascade config propagation have
|
||||||
|
unit coverage. The current Buildkite configuration does not schedule a
|
||||||
|
named full-model offline MXFP8 checkpoint on NPU, so this path is
|
||||||
|
**Validated**, not **CI-backed**.
|
||||||
@@ -6,7 +6,7 @@ Online quantization means vLLM-Omni computes quantized weights and scales while
|
|||||||
loading the model. Use it when you want memory savings without preparing a
|
loading the model. Use it when you want memory savings without preparing a
|
||||||
separate quantized checkpoint.
|
separate quantized checkpoint.
|
||||||
|
|
||||||
This mode is different from pre-quantized checkpoint formats such as GGUF,
|
This mode is different from pre-quantized checkpoint formats such as ModelOpt,
|
||||||
AutoRound, msModelSlim, or serialized Int8 checkpoints. Those formats are
|
AutoRound, msModelSlim, or serialized Int8 checkpoints. Those formats are
|
||||||
prepared before serving and are documented in their method-specific guides.
|
prepared before serving and are documented in their method-specific guides.
|
||||||
For MXFP8 and MXFP4, use this page for load-time quantization from BF16
|
For MXFP8 and MXFP4, use this page for load-time quantization from BF16
|
||||||
@@ -15,18 +15,22 @@ by msModelSlim and the merge tools.
|
|||||||
|
|
||||||
## Hardware Support
|
## Hardware Support
|
||||||
|
|
||||||
| Device | FP8 W8A8 | Int8 W8A8 | MXFP8 W8A8 | MXFP4 W4A4 |
|
| Device | FP8 W8A8 | Int8 W8A8 | BitsAndBytes W4 | MXFP8 W8A8 | MXFP4 W4A4 |
|
||||||
|--------|----------|-----------|------------|------------|
|
|--------|----------|-----------|------------------|------------|------------|
|
||||||
| 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 (gfx950) | ⭕ | ⭕ | ❌ | ❌ | ✅ |
|
||||||
| Intel XPU | ⭕ | ⭕ | ✅ | ⭕ |
|
| Intel XPU | ⭕ | ⭕ | ❌ | ✅ | ❌ |
|
||||||
| Ascend NPU | ❌ | ✅ | ✅ | ✅ |
|
| Ascend NPU | ❌ | ✅ | ❌ | ✅ | ✅ |
|
||||||
|
|
||||||
Legend: `✅` supported, `❌` unsupported, `⭕` not verified in this
|
Legend: `✅` backend available, `❌` unsupported, `⭕` not verified in this
|
||||||
guide. FP8 on Ampere may use a weight-only path where available. MXFP8 and
|
guide. These entries describe method-level backend availability, not validation
|
||||||
MXFP4 are documented for the Ascend NPU path.
|
for every model. FP8 on Ampere may use a weight-only path where available.
|
||||||
|
On ROCm gfx950, MXFP4 means online single-scale `mxfp4`; online and offline
|
||||||
|
`mxfp4_dualscale` are Ascend-only. The native MXFP8/MXFP4 configs do not
|
||||||
|
currently register CUDA implementations; use FP8 or ModelOpt FP8/NVFP4 on
|
||||||
|
NVIDIA instead.
|
||||||
|
|
||||||
## Model Type Support
|
## Model Type Support
|
||||||
|
|
||||||
@@ -36,8 +40,9 @@ MXFP4 are documented for the Ascend NPU path.
|
|||||||
|--------|-------|----------------|--------|
|
|--------|-------|----------------|--------|
|
||||||
| FP8 W8A8 | [FP8](fp8.md) | Qwen-Image; Wan2.2 is not validated | Validated for Qwen-Image family and other DiT models |
|
| FP8 W8A8 | [FP8](fp8.md) | Qwen-Image; Wan2.2 is not validated | Validated for Qwen-Image family and other DiT models |
|
||||||
| Int8 W8A8 | [Int8](int8.md) | Qwen-Image; Wan2.2 is not validated | Validated for Qwen-Image and Z-Image |
|
| Int8 W8A8 | [Int8](int8.md) | Qwen-Image; Wan2.2 is not validated | Validated for Qwen-Image and Z-Image |
|
||||||
|
| BitsAndBytes W4 | [BitsAndBytes](bitsandbytes.md) | Z-Image-Turbo; Qwen-Image and Wan2.2 are not validated | Validated for Z-Image-Turbo |
|
||||||
| MXFP8 W8A8 | [MXFP8](mxfp8.md) | Wan2.2-T2V-A14B, Wan2.2-I2V-A14B, Wan2.2-TI2V-5B | Validated on Ascend NPU and Intel XPU |
|
| MXFP8 W8A8 | [MXFP8](mxfp8.md) | Wan2.2-T2V-A14B, Wan2.2-I2V-A14B, Wan2.2-TI2V-5B | Validated on Ascend NPU and Intel XPU |
|
||||||
| MXFP4 W4A4 | [MXFP4](mxfp4.md) | Wan2.2-T2V-A14B, Wan2.2-I2V-A14B | Ascend NPU only; TI2V-5B is not supported |
|
| MXFP4 W4A4 | [MXFP4](mxfp4.md) | Wan2.2-T2V-A14B, Wan2.2-I2V-A14B | Validated on Ascend NPU; the ROCm gfx950 backend is integrated but not model-validated; TI2V-5B is unsupported |
|
||||||
|
|
||||||
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
||||||
|
|
||||||
@@ -59,6 +64,7 @@ from vllm_omni import Omni
|
|||||||
|
|
||||||
omni_fp8 = Omni(model="<your-model>", quantization="fp8")
|
omni_fp8 = Omni(model="<your-model>", quantization="fp8")
|
||||||
omni_int8 = Omni(model="<your-model>", quantization="int8")
|
omni_int8 = Omni(model="<your-model>", quantization="int8")
|
||||||
|
omni_bnb = Omni(model="Tongyi-MAI/Z-Image-Turbo", quantization="bitsandbytes")
|
||||||
omni_mxfp8 = Omni(model="<your-model>", quantization="mxfp8")
|
omni_mxfp8 = Omni(model="<your-model>", quantization="mxfp8")
|
||||||
omni_mxfp4 = Omni(model="<your-model>", quantization="mxfp4")
|
omni_mxfp4 = Omni(model="<your-model>", quantization="mxfp4")
|
||||||
omni_mxfp4_dualscale = Omni(model="<your-model>", quantization="mxfp4_dualscale")
|
omni_mxfp4_dualscale = Omni(model="<your-model>", quantization="mxfp4_dualscale")
|
||||||
@@ -69,6 +75,7 @@ CLI:
|
|||||||
```bash
|
```bash
|
||||||
vllm serve <your-model> --omni --quantization fp8
|
vllm serve <your-model> --omni --quantization fp8
|
||||||
vllm serve <your-model> --omni --quantization int8
|
vllm serve <your-model> --omni --quantization int8
|
||||||
|
vllm serve Tongyi-MAI/Z-Image-Turbo --omni --quantization bitsandbytes
|
||||||
vllm serve <your-model> --omni --quantization mxfp8
|
vllm serve <your-model> --omni --quantization mxfp8
|
||||||
vllm serve <your-model> --omni --quantization mxfp4
|
vllm serve <your-model> --omni --quantization mxfp4
|
||||||
vllm serve <your-model> --omni --quantization mxfp4_dualscale
|
vllm serve <your-model> --omni --quantization mxfp4_dualscale
|
||||||
@@ -85,14 +92,19 @@ config = build_quant_config({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Component routing is an extension mechanism, not automatic support for every
|
||||||
|
pipeline component. See [Quantization Scope](overview.md#quantization-scope)
|
||||||
|
before applying it to an encoder, VAE, or decoder.
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
| Parameter | Methods | Description |
|
| Parameter | Methods | Description |
|
||||||
|-----------|---------|-------------|
|
|-----------|---------|-------------|
|
||||||
| `method` | FP8, Int8, MXFP8, MXFP4 | Quantization method: `"fp8"`, `"int8"`, `"mxfp8"`, `"mxfp4"`, or `"mxfp4_dualscale"` |
|
| `method` | FP8, Int8, BitsAndBytes, MXFP8, MXFP4 | Quantization method: `"fp8"`, `"int8"`, `"bitsandbytes"`, `"mxfp8"`, `"mxfp4"`, or `"mxfp4_dualscale"` |
|
||||||
| `ignored_layers` | FP8, Int8, MXFP8, MXFP4 | Layer name patterns to keep in BF16/FP16 |
|
| `ignored_layers` | FP8, Int8, BitsAndBytes, MXFP8, MXFP4 | Layer name patterns to keep in BF16/FP16 |
|
||||||
| `activation_scheme` | FP8, Int8 | The runtime value `"dynamic"` selects online activation scaling |
|
| `activation_scheme` | FP8, Int8 | The runtime value `"dynamic"` selects online activation scaling |
|
||||||
| `weight_block_size` | FP8 | Optional block-wise FP8 weight quantization size |
|
| `weight_block_size` | FP8 | Optional block-wise FP8 weight quantization size |
|
||||||
|
| `quant_type` | BitsAndBytes | Weight format: `"nf4"` (default) or `"fp4"` |
|
||||||
| `num_bf16_fallback_layers` | MXFP4 DualScale | Leading transformer blocks to keep in BF16 for online `mxfp4_dualscale`; defaults to `5` |
|
| `num_bf16_fallback_layers` | MXFP4 DualScale | Leading transformer blocks to keep in BF16 for online `mxfp4_dualscale`; defaults to `5` |
|
||||||
|
|
||||||
## Validation and Notes
|
## Validation and Notes
|
||||||
|
|||||||
@@ -12,76 +12,127 @@ 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, 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, BitsAndBytes W4, 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, GGUF, 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, AutoRound, msModelSlim, serialized Int8, offline MXFP8, offline MXFP4 DualScale |
|
||||||
|
|
||||||
## Hardware Support
|
Online quantization starts from a normal BF16/FP16 checkpoint and repeats the
|
||||||
|
conversion on each fresh model load. It is the simplest path for experiments.
|
||||||
|
Pre-quantized checkpoints store the packed weights and scales produced by a
|
||||||
|
separate tool; they are easier to reuse across deployments and can carry
|
||||||
|
calibrated or mixed-precision policies that load-time conversion cannot
|
||||||
|
reproduce.
|
||||||
|
|
||||||
| Device | FP8 W8A8 | Int8 W8A8 | ModelOpt | MXFP8 W8A8 | MXFP4 W4A4 | GGUF | AutoRound | msModelSlim |
|
## Support Levels
|
||||||
|--------|----------|-----------|----------|------------|------------|------|-----------|-------------|
|
|
||||||
| NVIDIA Blackwell GPU (SM 100+) | ✅ | ✅ | ✅ | ⭕ | ⭕ | ✅ | ✅ | ❌ |
|
|
||||||
| NVIDIA Ada/Hopper GPU (SM 89+) | ✅ | ✅ | ✅ | ⭕ | ⭕ | ✅ | ✅ | ❌ |
|
|
||||||
| NVIDIA Ampere GPU (SM 80+) | ✅ | ✅ | ⭕ | ⭕ | ⭕ | ✅ | ✅ | ❌ |
|
|
||||||
| AMD ROCm | ⭕ | ⭕ | ⭕ | ⭕ | ✅ | ⭕ | ⭕ | ❌ |
|
|
||||||
| Intel XPU | ⭕ | ⭕ | ⭕ | ⭕ | ⭕ | ⭕ | ✅ | ❌ |
|
|
||||||
| Ascend NPU | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ |
|
|
||||||
|
|
||||||
Legend: `✅` supported, `❌` unsupported, `⭕` not verified in this
|
Quantization support is specific to a model, component, checkpoint format, and
|
||||||
guide. FP8 on Ampere may use a weight-only path where available.
|
hardware backend. The tables in this guide use the following levels:
|
||||||
|
|
||||||
## Model Type Support
|
| Level | Meaning |
|
||||||
|
|-------|---------|
|
||||||
|
| **CI-backed** | A named model and quantized checkpoint are exercised by scheduled full-model hardware CI. |
|
||||||
|
| **Validated** | A named model and checkpoint have end-to-end or quality-validation evidence, but the check is not necessarily scheduled in CI. |
|
||||||
|
| **Integrated** | The config, loader, quantized layers, or model wiring exists, but this guide has no completed end-to-end validation for the named model and checkpoint. |
|
||||||
|
| **Not validated** | There is no current support recommendation for this model/component combination. |
|
||||||
|
| **Unsupported** | The combination is intentionally rejected or known not to work. |
|
||||||
|
|
||||||
### Diffusion Model (Qwen-Image, Wan2.2)
|
A method being available on a device does not make every model on that device
|
||||||
|
supported. Likewise, successfully detecting a checkpoint's
|
||||||
|
`quantization_config` establishes loader compatibility, not output quality.
|
||||||
|
|
||||||
These models run a diffusion transformer as the primary inference module. The
|
## Hardware × Quantization Method
|
||||||
default quantization target is the transformer; tokenizer, scheduler, text
|
|
||||||
encoder, and VAE stay on the base checkpoint unless a method guide says
|
|
||||||
otherwise.
|
|
||||||
|
|
||||||
| Method | Guide | Mode | Example models | Status |
|
This matrix uses concrete inference paths as columns. Producer toolkits are not
|
||||||
|--------|-------|------|----------------|--------|
|
separate methods: NVIDIA ModelOpt produces the ModelOpt FP8/NVFP4 formats,
|
||||||
| 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 |
|
Intel AutoRound produces AutoRound checkpoints, and Ascend msModelSlim produces
|
||||||
| Int8 W8A8 | [Int8](int8.md) | Online or serialized W8A8 | Qwen-Image; Wan2.2 is not validated | Validated for Qwen-Image and Z-Image |
|
checkpoints consumed by the native MXFP or compatible Ascend paths.
|
||||||
| 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 |
|
|
||||||
| 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 |
|
|
||||||
| msModelSlim | [msModelSlim](msmodelslim.md) | Pre-quantized Ascend checkpoints | Wan2.2 recipe; HunyuanImage-3.0 inference target | Ascend/NPU path |
|
|
||||||
|
|
||||||
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
| Hardware | [Online FP8](fp8.md) | [Int8](int8.md) | [BitsAndBytes W4](bitsandbytes.md) | [ModelOpt FP8](modelopt.md) | [ModelOpt NVFP4](modelopt.md#supported-modelopt-checkpoint-formats) | [AutoRound W4A16](autoround.md) | [OCP MXFP8](mxfp8.md) | [OCP MXFP4](mxfp4.md) |
|
||||||
|
|----------|-----------------------|------------------|------------------------------------------|--------------------------------|-------------------------------------------------------------------------|--------------------------------------|--------------------------|--------------------------|
|
||||||
|
| NVIDIA Blackwell (SM 100+) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||||
|
| NVIDIA Hopper (SM 90) | ✅ | ✅ | ✅ | ✅ | ✅ (Marlin fallback) | ✅ | ❌ | ❌ |
|
||||||
|
| NVIDIA Ada (SM 89) | ✅ | ✅ | ✅ | ✅ | ⭕ | ✅ | ❌ | ❌ |
|
||||||
|
| NVIDIA Ampere (SM 80+) | ✅ | ✅ | ✅ | ⭕ | ⭕ | ✅ | ❌ | ❌ |
|
||||||
|
| AMD ROCm (gfx950 for MXFP4) | ⭕ | ⭕ | ❌ | ⭕ | ⭕ | ⭕ | ❌ | ✅ (online only) |
|
||||||
|
| Intel XPU | ⭕ | ⭕ | ❌ | ⭕ | ⭕ | ✅ | ✅ | ❌ |
|
||||||
|
| Ascend NPU | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
|
||||||
|
|
||||||
These models combine an AR language model with audio, vision, talker, or TTS
|
Legend: `✅` backend available, `❌` rejected or no implementation, `⭕` not
|
||||||
stages. Quantization is scoped to the AR language-model stage when the
|
verified in this guide. This matrix does not establish model-level support; use
|
||||||
checkpoint contains a supported `quantization_config`; the non-AR stages stay
|
the model-and-stage tables below for that.
|
||||||
in BF16 unless the model guide explicitly adds support.
|
|
||||||
|
|
||||||
| Method | Guide | Scope | Example models | Status |
|
The Int8 column covers online and serialized Int8 paths. OCP MXFP8 is online on
|
||||||
|--------|-------|-------|----------------|--------|
|
Intel XPU and online/offline on Ascend; Intel's separate AutoRound MXFP8
|
||||||
| ModelOpt | [ModelOpt](modelopt.md) | Thinker or language-model checkpoint config | Qwen3-Omni thinker | ModelOpt checkpoint path |
|
checkpoint path is described in the [MXFP8 guide](mxfp8.md). OCP MXFP4 on AMD
|
||||||
| Int8 | [Int8](int8.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
means online single-scale `mxfp4` on gfx950, while MXFP4 DualScale is
|
||||||
| MXFP8 | [MXFP8](mxfp8.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
Ascend-only. NVIDIA's NVFP4 and OCP MXFP4 have different checkpoint metadata,
|
||||||
| MXFP4 | [MXFP4](mxfp4.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
scale layouts, and kernel contracts. Mixed ModelOpt FP8/NVFP4 checkpoints also
|
||||||
| GGUF | [GGUF](gguf.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
require a model-specific mixed-precision policy and validation. FP8 on Ampere
|
||||||
| AutoRound | [AutoRound](autoround.md) | Thinker or language-model checkpoint config | Qwen2.5-Omni, Qwen3-Omni | Supported through AutoRound checkpoints |
|
may use a weight-only path where available.
|
||||||
| msModelSlim | [msModelSlim](msmodelslim.md) | Not currently validated for omni/TTS stages | Qwen3-Omni, Qwen3-TTS | Not validated |
|
|
||||||
|
|
||||||
### Multi-Stage Diffusion Model (BAGEL, GLM-Image)
|
## Current Model and Stage Support
|
||||||
|
|
||||||
These models split generation across multiple stages. Quantization must be
|
The current offline checkpoint guidance is transformer-first. A listed
|
||||||
attached to the intended stage rather than applied globally.
|
checkpoint quantizes the stage named in the **Scope** column; it does not imply
|
||||||
|
that the model's encoders, VAE, decoder, or other stages are also quantized.
|
||||||
|
|
||||||
| Method | Guide | Scope | Example models | Status |
|
### Vendor and Runtime Ecosystems
|
||||||
|--------|-------|-------|----------------|--------|
|
|
||||||
| FP8 | [FP8](fp8.md) | Stage-specific DiT or transformer module | BAGEL, GLM-Image | Requires model-specific validation |
|
The producer and runtime backend are related, but they are not the same support
|
||||||
| Int8 | [Int8](int8.md) | Stage-specific DiT or transformer module | BAGEL, GLM-Image | Requires model-specific validation |
|
claim. A producer can emit several checkpoint formats, and each format still
|
||||||
| ModelOpt | [ModelOpt](modelopt.md) | Checkpoint-defined diffusion stage | BAGEL, GLM-Image | Requires model-specific validation |
|
needs a compatible vLLM-Omni loader, model-stage wiring, and validation. A
|
||||||
| MXFP8 | [MXFP8](mxfp8.md) | Stage-specific DiT or transformer module | BAGEL, GLM-Image | Not validated |
|
runtime may also provide online quantization without an offline producer path.
|
||||||
| 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 |
|
| Ecosystem | Current paths | Current vLLM-Omni scope | Model evidence |
|
||||||
| 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 |
|
| NVIDIA | ModelOpt FP8, NVFP4, and mixed FP8/NVFP4 | Diffusion transformer or Qwen-Omni thinker | Qwen-Image and other named diffusion checkpoints are validated; Qwen3-Omni NVFP4 has H100 CI for the Marlin fallback |
|
||||||
|
| Intel | AutoRound W4A16 and AutoRound MXFP8 | Diffusion/world-model transformer or Qwen-Omni thinker | Wan A14B and GLM-Image W4A16 are CI-backed; Qwen-Image, Qwen-Omni, and Cosmos3-Super have validation outside scheduled CI |
|
||||||
|
| AMD ROCm | Online OCP MXFP4 through AITER on gfx950; no pre-quantized MXFP4 or AMD Quark workflow is integrated | Diffusion transformer linear layers | Integrated backend with dispatch and weight-allocation unit coverage; no named-model quality validation or scheduled gfx950 full-model CI |
|
||||||
|
| Ascend | msModelSlim output through native MXFP8/MXFP4 merge workflows or a compatible generic Ascend format | Wan diffusion transformers | Native Wan MXFP8/MXFP4 paths are validated outside scheduled full-model NPU CI; other model families remain integrated or unvalidated |
|
||||||
|
|
||||||
|
### Pre-Quantized Model Matrix
|
||||||
|
|
||||||
|
| Model family | Method | Scope | Level | Notes |
|
||||||
|
|--------------|--------|-------|-------|-------|
|
||||||
|
| Qwen-Image | [ModelOpt](modelopt.md) FP8 or mixed FP8/NVFP4 | Diffusion transformer | Validated | Named Qwen-Image 2512 checkpoints and recipes are available. Text encoder and VAE stay BF16. |
|
||||||
|
| Qwen-Image | [AutoRound](autoround.md) W4A16 | Diffusion transformer | Validated | Uses `INC4AI/Qwen-Image-AutoRound-W4A16`; text encoder and VAE stay BF16. |
|
||||||
|
| Wan2.2 I2V/T2V A14B | [AutoRound](autoround.md) W4A16 | Both diffusion transformers | CI-backed | The high-noise and low-noise DiTs are quantized; text encoder and VAE stay BF16. |
|
||||||
|
| Wan2.2 TI2V 5B | [AutoRound](autoround.md) W4A16 | Diffusion transformer | Validated | Named checkpoint available; not covered by the scheduled Wan AutoRound job. |
|
||||||
|
| Wan2.2 | [MXFP8](mxfp8.md) | One or both diffusion transformers | Validated | Native Ascend and AutoRound checkpoint formats are integrated; no scheduled full-model offline NPU job. |
|
||||||
|
| Wan2.2 I2V/T2V A14B | [MXFP4 DualScale](mxfp4.md) | Both diffusion transformers | Validated | Offline calibrated checkpoints are recommended; no scheduled full-model offline NPU job. |
|
||||||
|
| Qwen2.5-Omni | [AutoRound](autoround.md) W4A16 | Thinker language model | Validated | Encoders and output/audio stages stay BF16. |
|
||||||
|
| Qwen3-Omni | [ModelOpt](modelopt.md) FP8 | Thinker language model | Validated | Tested outside scheduled CI. |
|
||||||
|
| Qwen3-Omni | [ModelOpt](modelopt.md) NVFP4 | Thinker language model | CI-backed | H100 CI exercises the Marlin fallback; the native Blackwell FP4 path has separate recipe validation. |
|
||||||
|
| Qwen3-Omni | [AutoRound](autoround.md) W4A16 | Thinker language model | Validated | Audio encoder, vision encoder, talker, and Code2Wav stay BF16. |
|
||||||
|
| GLM-Image | [AutoRound](autoround.md) W4A16 | Diffusion transformer stage | CI-backed | Text-to-image and image-to-image full-model smoke coverage. |
|
||||||
|
| Cosmos3-Super | [AutoRound](autoround.md) W4A16 | World-model transformer | Validated | Manually validated serving path; VAE and other components stay BF16. |
|
||||||
|
| Cosmos3-Nano | [AutoRound](autoround.md) W4A16 | World-model transformer | Integrated | Named checkpoint is listed, but no model-specific end-to-end test is maintained in-tree. |
|
||||||
|
| MiniMax H3 | None | None | Not validated | No offline vendor checkpoint path is documented; online FP8 targets the DiT only. |
|
||||||
|
|
||||||
|
The method pages contain the complete checkpoint lists, including FLUX,
|
||||||
|
Z-Image, and HunyuanImage-3.0 variants.
|
||||||
|
|
||||||
|
### Online Quantization
|
||||||
|
|
||||||
|
| Model family | Methods | Scope | Level |
|
||||||
|
|--------------|---------|-------|-------|
|
||||||
|
| Qwen-Image | FP8, Int8 | Diffusion transformer | Validated |
|
||||||
|
| Z-Image | Int8, BitsAndBytes W4 | Diffusion transformer | Validated |
|
||||||
|
| Wan2.2 | MXFP8 | One or both diffusion transformers | Validated on the documented XPU/NPU paths |
|
||||||
|
| Wan2.2 A14B | MXFP4 | Both diffusion transformers | Validated on Ascend; the ROCm gfx950 online backend is integrated but not model-validated |
|
||||||
|
| MiniMax H3 | FP8 | DiT and optional reference DiT | Validated |
|
||||||
|
| Qwen3-Omni | Generic online methods, such as FP8 | Thinker language model only | Integrated; not model-validated as an online recommendation |
|
||||||
|
|
||||||
|
See [Online Quantization](online.md) for configuration and hardware details.
|
||||||
|
|
||||||
|
### Unlisted and Long-Tail Models
|
||||||
|
|
||||||
|
For models not listed above, generic quantized linear layers or compatible
|
||||||
|
checkpoint metadata are only an integration starting point. The default support
|
||||||
|
target is the model's primary transformer, DiT, or AR language-model stage.
|
||||||
|
Text/audio/vision encoders, VAEs, vocoders, decoders, and output stages remain
|
||||||
|
BF16 until a model-specific integration supplies quant-aware layers, checkpoint
|
||||||
|
mapping, end-to-end output validation, and backend coverage.
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
"Online quantization" means vLLM-Omni computes the quantization data while
|
"Online quantization" means vLLM-Omni computes the quantization data while
|
||||||
@@ -92,8 +143,8 @@ attached to the intended stage rather than applied globally.
|
|||||||
|
|
||||||
### Diffusion Model (Qwen-Image, Wan2.2)
|
### Diffusion Model (Qwen-Image, Wan2.2)
|
||||||
|
|
||||||
The default target is the diffusion transformer. Component routing is available
|
The default target is the diffusion transformer. `build_quant_config()` can
|
||||||
through `build_quant_config()`:
|
construct a component router:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from vllm_omni.quantization import build_quant_config
|
from vllm_omni.quantization import build_quant_config
|
||||||
@@ -104,11 +155,19 @@ config = build_quant_config({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! warning "Routing syntax is not a support claim"
|
||||||
|
A component dictionary only selects a quantization config by module prefix.
|
||||||
|
It does not make an encoder or VAE quantizable. The model must construct
|
||||||
|
that component from quantization-aware layers, and an offline checkpoint
|
||||||
|
must contain matching packed weights and scales. Current user guidance
|
||||||
|
keeps diffusion text encoders and VAEs in BF16.
|
||||||
|
|
||||||
| Component | Default quantized? | Notes |
|
| Component | Default quantized? | Notes |
|
||||||
|-----------|--------------------|-------|
|
|-----------|--------------------|-------|
|
||||||
| Diffusion transformer | Yes | Primary target for FP8, Int8, ModelOpt, MXFP8, MXFP4, GGUF, AutoRound, and msModelSlim |
|
| Diffusion transformer | Yes, for listed model/method pairs | Primary target for FP8, Int8, BitsAndBytes, ModelOpt, MXFP8, MXFP4, AutoRound, and msModelSlim |
|
||||||
|
| Second diffusion transformer | Yes, for listed Wan2.2 A14B methods | `transformer` and `transformer_2` read their own offline checkpoint metadata; arbitrary mixed methods are not currently validated |
|
||||||
| 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 |
|
||||||
| Scheduler/tokenizer | No | Loaded from the base model repository |
|
| Scheduler/tokenizer | No | Loaded from the base model repository |
|
||||||
|
|
||||||
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
### Multi-Stage Omni/TTS Model (Qwen3-Omni, Qwen3-TTS)
|
||||||
@@ -121,6 +180,18 @@ config = build_quant_config({
|
|||||||
| Talker or TTS stage | No | BF16 unless model-specific support is documented |
|
| Talker or TTS stage | No | BF16 unless model-specific support is documented |
|
||||||
| Code2Wav | No | BF16 |
|
| Code2Wav | No | BF16 |
|
||||||
|
|
||||||
|
An explicit `ComponentQuantizationConfig` can be used by model developers to
|
||||||
|
route different configs to `audio_tower`, `visual`, and `language_model`, but
|
||||||
|
the supported Qwen-Omni checkpoints currently quantize only the thinker
|
||||||
|
language model.
|
||||||
|
|
||||||
|
### World Model (Cosmos3)
|
||||||
|
|
||||||
|
| Component | Default quantized? | Notes |
|
||||||
|
|-----------|--------------------|-------|
|
||||||
|
| World-model transformer | AutoRound checkpoints only | Cosmos3-Super is validated; Cosmos3-Nano is integrated but not end-to-end tested in-tree |
|
||||||
|
| VAE and guardrail components | No | BF16 |
|
||||||
|
|
||||||
### Multi-Stage Diffusion Model (BAGEL, GLM-Image)
|
### Multi-Stage Diffusion Model (BAGEL, GLM-Image)
|
||||||
|
|
||||||
| Component | Default quantized? | Notes |
|
| Component | Default quantized? | Notes |
|
||||||
@@ -139,8 +210,9 @@ 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"})
|
# Component routing syntax; model-specific support is still required.
|
||||||
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.request_output
|
output = stage_outputs
|
||||||
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
|
||||||
|
|||||||
@@ -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.request_output
|
output = stage_outputs
|
||||||
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
|
||||||
|
|||||||
@@ -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].request_output
|
first_out = outputs[0]
|
||||||
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 = getattr(omni_out, "request_output", None)
|
req_out = omni_out
|
||||||
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:
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ def main():
|
|||||||
)
|
)
|
||||||
img_idx = 0
|
img_idx = 0
|
||||||
for req_output in omni_outputs:
|
for req_output in omni_outputs:
|
||||||
ro = getattr(req_output, "request_output", None)
|
ro = req_output
|
||||||
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 = getattr(output, "request_output", None)
|
req_out = output
|
||||||
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,8 +60,12 @@ 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,
|
||||||
)
|
)
|
||||||
@@ -83,6 +87,22 @@ 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=(
|
||||||
@@ -294,6 +314,30 @@ 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"),
|
||||||
@@ -487,6 +531,12 @@ 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:
|
||||||
@@ -517,19 +567,39 @@ 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,
|
||||||
@@ -540,7 +610,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={"sample_solver": args.sample_solver},
|
extra_args=sampling_extra_args,
|
||||||
)
|
)
|
||||||
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
|
||||||
@@ -576,8 +646,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.request_output is not None:
|
if frames.is_pipeline_output and frames is not None:
|
||||||
inner_output = frames.request_output
|
inner_output = frames
|
||||||
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"]
|
||||||
|
|||||||
@@ -0,0 +1,319 @@
|
|||||||
|
# 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()
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
"""
|
||||||
|
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.request_output
|
output = stage_outputs
|
||||||
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.request_output
|
output = stage_outputs
|
||||||
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
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
# 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.request_output
|
output = stage_outputs
|
||||||
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.request_output
|
output = stage_outputs
|
||||||
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
|
||||||
|
|||||||
@@ -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.request_output
|
output = omni_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,7 +26,6 @@ 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
|
||||||
|
|
||||||
|
|
||||||
@@ -352,8 +351,8 @@ def main():
|
|||||||
else:
|
else:
|
||||||
print("[Profiler] No valid profiling data returned.")
|
print("[Profiler] No valid profiling data returned.")
|
||||||
|
|
||||||
# Extract output from result
|
# omni.generate() returns a list for sync calls; unwrap single-result list.
|
||||||
output = OmniRequestOutput.unwrap_result(result)
|
output = result[0] if isinstance(result, list) else 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.")
|
||||||
|
|||||||
@@ -345,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.request_output
|
raw_output = stage_outputs
|
||||||
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":
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ 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")
|
||||||
@@ -388,9 +389,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 hasattr(output, "request_output") or not output.request_output:
|
if not isinstance(output, OmniRequestOutput) or not output:
|
||||||
raise ValueError("No request_output found in OmniRequestOutput")
|
raise ValueError("No request_output found in OmniRequestOutput")
|
||||||
request_output = output.request_output
|
request_output = 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,8 +57,9 @@ python examples/offline_inference/text_to_image/text_to_image.py \
|
|||||||
--output lingbot_t2i.png
|
--output lingbot_t2i.png
|
||||||
```
|
```
|
||||||
|
|
||||||
The LingBot prompt builder selects the `image` output modality and requests one
|
The shared T2I entry point selects the `image` output modality. The LingBot
|
||||||
frame. Passing an input image is not supported in LingBot T2I mode.
|
pipeline validates that T2I requests produce one frame and do not include an
|
||||||
|
input image.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@@ -109,8 +110,9 @@ 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 |
|
| `--lora-path` | str | — | Path to PEFT LoRA adapter folder or checkpoint file |
|
||||||
| `--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.request_output
|
req_out = 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,7 +8,9 @@ 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
|
||||||
@@ -19,7 +21,9 @@ 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,
|
build_text_to_image_prompt as build_model_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,
|
||||||
@@ -54,6 +58,28 @@ 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(
|
||||||
@@ -247,6 +273,7 @@ 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.",
|
||||||
)
|
)
|
||||||
@@ -256,6 +283,15 @@ 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,
|
||||||
@@ -391,8 +427,12 @@ 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_args["lora_path"] = args.lora_path
|
lora_path = args.lora_path
|
||||||
print(f"Using LoRA from: {args.lora_path}")
|
if len(lora_path) == 1:
|
||||||
|
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
|
||||||
@@ -481,20 +521,27 @@ def main():
|
|||||||
|
|
||||||
# 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:
|
if args.lora_path and args.lora_backend == "peft":
|
||||||
lora_request_id = stable_lora_int_id(args.lora_path)
|
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_id = stable_lora_int_id(lora_path)
|
||||||
lora_request = LoRARequest(
|
lora_request = LoRARequest(
|
||||||
lora_name=Path(args.lora_path).stem,
|
lora_name=Path(lora_path).stem,
|
||||||
lora_int_id=lora_request_id,
|
lora_int_id=lora_request_id,
|
||||||
lora_path=args.lora_path,
|
lora_path=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,
|
||||||
)
|
)
|
||||||
@@ -609,7 +656,7 @@ def main():
|
|||||||
images = getattr(output, "images", None)
|
images = getattr(output, "images", None)
|
||||||
if images:
|
if images:
|
||||||
break
|
break
|
||||||
req_out = getattr(output, "request_output", None)
|
req_out = output
|
||||||
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
|
||||||
@@ -619,6 +666,7 @@ 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,6 +24,7 @@ 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
|
||||||
@@ -371,18 +372,10 @@ 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
|
||||||
Generate a WAV offline and a JSON sidecar with word-level timestamps from
|
Word-level timestamps are currently a serving-path feature: launch
|
||||||
`Qwen/Qwen3-ForcedAligner-0.6B`:
|
`vllm-omni serve` with `--forced-aligner` and request `word_timestamps`
|
||||||
```bash
|
(see `examples/online_serving/text_to_speech/README.md`). An offline
|
||||||
python examples/offline_inference/text_to_speech/qwen3_tts/word_timestamps.py \
|
example is not provided in this release.
|
||||||
--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):
|
||||||
@@ -437,11 +430,14 @@ Streaming is exposed through the online OpenAI Speech API (`stream=true`). See [
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## IndexTTS-2
|
## IndexTTS-2 and IndexTTS-2.5
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
### 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 \
|
||||||
@@ -449,6 +445,24 @@ 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
|
||||||
@@ -474,10 +488,13 @@ python examples/offline_inference/text_to_speech/indextts2/end2end.py \
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Notes
|
### Notes
|
||||||
- `--ref-audio` is **required** — IndexTTS-2 does not support text-only synthesis.
|
- `--ref-audio` is **required** — neither version provides text-only synthesis.
|
||||||
- Stage 0 (AR Talker): GPT-2 generates mel codes from text + reference audio.
|
- 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 1 (S2Mel + BigVGAN): CFM DiT converts mel codes to waveform at 22.05 kHz.
|
- 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.
|
||||||
- Deploy config: `vllm_omni/deploy/indextts2.yaml`. Stage 1 runs with `enforce_eager: true` (DiT has dynamic shapes).
|
- A seed controls Stage 0 AR sampling and per-request CFM noise. Different concurrent batch compositions do not guarantee a bit-identical waveform.
|
||||||
|
- 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.request_output
|
ro = output
|
||||||
if ro is None:
|
if ro is None:
|
||||||
print("No request_output found.")
|
print("No request_output found.")
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -179,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
|
request_output = stage_outputs
|
||||||
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(
|
||||||
@@ -215,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.request_output.outputs[0].multimodal_output
|
mm = stage_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)
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
# 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 IndexTTS2 via vLLM-Omni.
|
"""Offline inference example for IndexTTS 2.0 and 2.5 via vLLM-Omni.
|
||||||
|
|
||||||
Two-stage pipeline: GPT AR (Stage 0) → S2Mel + BigVGAN (Stage 1).
|
Two-stage pipeline: GPT AR (Stage 0) → semantic codec + S2Mel + BigVGAN
|
||||||
|
(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/IndexTeam/IndexTTS-2 \
|
--model /path/to/indextts-2.5 \
|
||||||
|
--model-version 2.5 \
|
||||||
|
--lang zh \
|
||||||
--text "你好,这是一个语音合成测试。" \
|
--text "你好,这是一个语音合成测试。" \
|
||||||
--ref-audio /path/to/ref.wav
|
--ref-audio /path/to/ref.wav
|
||||||
|
|
||||||
@@ -47,6 +50,10 @@ 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:
|
||||||
@@ -63,12 +70,44 @@ 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(model, text),
|
"prompt_token_ids": build_indextts2_prefill_prompt_ids(
|
||||||
|
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)
|
||||||
@@ -95,9 +134,14 @@ 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=args.deploy_config,
|
deploy_config=deploy_config,
|
||||||
stage_init_timeout=args.stage_init_timeout,
|
stage_init_timeout=args.stage_init_timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -126,7 +170,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}")
|
||||||
inputs = build_request(
|
request_kwargs = dict(
|
||||||
model=args.model,
|
model=args.model,
|
||||||
text=args.text,
|
text=args.text,
|
||||||
ref_audio_path=args.ref_audio,
|
ref_audio_path=args.ref_audio,
|
||||||
@@ -136,7 +180,17 @@ 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
|
||||||
@@ -154,8 +208,37 @@ def main(args) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
parser = FlexibleArgumentParser(description="IndexTTS2 offline inference")
|
parser = FlexibleArgumentParser(description="IndexTTS 2.0/2.5 offline inference")
|
||||||
parser.add_argument("--model", required=True, help="HF model path for IndexTTS2.")
|
parser.add_argument(
|
||||||
|
"--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.")
|
||||||
@@ -170,7 +253,15 @@ 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("--seed", type=int, default=None)
|
parser.add_argument(
|
||||||
|
"--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(
|
||||||
@@ -180,7 +271,10 @@ 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)
|
||||||
return parser.parse_args()
|
args = 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.request_output):
|
for i, req_output in enumerate(stage_outputs):
|
||||||
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.request_output
|
ro = 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.request_output
|
output = stage_outputs
|
||||||
_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.request_output.outputs[0].multimodal_output
|
mm = stage_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")
|
||||||
|
|||||||
@@ -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.request_output
|
final_output = stage_outputs
|
||||||
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 = getattr(omni_out, "request_output", None)
|
ro = omni_out
|
||||||
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 = getattr(out, "request_output", None)
|
ro = out
|
||||||
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,6 +8,7 @@ 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 |
|
||||||
@@ -57,18 +58,31 @@ python text_to_video.py \
|
|||||||
|
|
||||||
### LingBot-Video
|
### LingBot-Video
|
||||||
|
|
||||||
The dedicated LingBot example explicitly selects the video output modality:
|
The shared runner recognizes the official dense and MoE checkpoint IDs, selects
|
||||||
|
`LingBotVideoPipeline`, and builds the canonical video request envelope:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python examples/offline_inference/text_to_video/text_to_video_lingbot.py \
|
python text_to_video.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.
|
VAE's `4n+1` grid. For a local checkpoint path whose name does not contain
|
||||||
|
`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
|
||||||
|
|
||||||
@@ -218,6 +232,9 @@ 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,7 +14,9 @@ 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.model_extras import get_extra_body_params, get_model_class_name
|
from vllm_omni.lora.request import LoRARequest
|
||||||
|
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
|
||||||
|
|
||||||
@@ -78,6 +80,17 @@ _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,
|
||||||
@@ -108,27 +121,51 @@ _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 "distilled" in class_lower or "distilled" in model_lower:
|
if "lingbot" in model_lower or "lingbotvideo" in class_lower:
|
||||||
return _MODEL_PRESETS["ltx2_distilled"]
|
return _MODEL_PRESETS["lingbot"]
|
||||||
if "ltx23" in class_lower or "ltx-2.3" in model_lower or "ltx_2.3" in model_lower:
|
if "ltx" in class_lower or "ltx" in model_lower:
|
||||||
return _MODEL_PRESETS["ltx23"]
|
if "distilled" in class_lower or "distilled" in model_lower:
|
||||||
if "ltx2" in class_lower or "ltx-2" in model_lower or "ltx_2" in model_lower:
|
return _MODEL_PRESETS["ltx2_distilled"]
|
||||||
|
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:
|
if "vace" in model_lower or "vace" in class_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:
|
if "cosmos" in model_lower or "cosmos" in class_lower:
|
||||||
return _MODEL_PRESETS["cosmos"]
|
return _MODEL_PRESETS["cosmos"]
|
||||||
if "hunyuan" in model_lower:
|
if "hunyuan" in model_lower or "hunyuan" in class_lower:
|
||||||
return _MODEL_PRESETS["hunyuan"]
|
return _MODEL_PRESETS["hunyuan"]
|
||||||
if "helios" in model_lower:
|
if "helios" in model_lower or "helios" in class_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)
|
||||||
@@ -158,7 +195,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, and other text-to-video models."
|
"Supports Wan2.2, HunyuanVideo-1.5, Helios, LingBot-Video, and other text-to-video models."
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--model",
|
"--model",
|
||||||
@@ -334,6 +371,29 @@ 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",
|
||||||
@@ -365,7 +425,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 = getattr(result, "request_output", None)
|
inner = result
|
||||||
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)
|
||||||
@@ -383,6 +443,7 @@ 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)
|
||||||
@@ -440,6 +501,12 @@ 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).
|
||||||
@@ -449,6 +516,7 @@ 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...")
|
||||||
@@ -469,12 +537,29 @@ 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")
|
||||||
|
|
||||||
prompt_dict = {"prompt": args.prompt}
|
lora_request = None
|
||||||
if args.negative_prompt is not None:
|
if args.lora_path and args.lora_backend == "peft":
|
||||||
prompt_dict["negative_prompt"] = args.negative_prompt
|
if len(args.lora_path) != 1:
|
||||||
elif preset not in (_MODEL_PRESETS["ltx2"], _MODEL_PRESETS["ltx23"]):
|
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_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.
|
||||||
prompt_dict["negative_prompt"] = ""
|
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,
|
||||||
@@ -483,6 +568,7 @@ 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
|
||||||
@@ -525,8 +611,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.request_output is not None:
|
if frames.is_pipeline_output and frames is not None:
|
||||||
inner_output = frames.request_output
|
inner_output = frames
|
||||||
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"]
|
||||||
@@ -580,8 +666,7 @@ 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)
|
||||||
if frame_tensor.is_floating_point():
|
frame_tensor = _normalize_float_tensor(frame_tensor, output_tensor_range)
|
||||||
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
|
||||||
@@ -633,8 +718,7 @@ 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)
|
||||||
if video_tensor.is_floating_point():
|
video_tensor = _normalize_float_tensor(video_tensor, output_tensor_range)
|
||||||
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
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
# 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 = getattr(result, "request_output", None)
|
inner = result
|
||||||
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,6 +1,9 @@
|
|||||||
# Image-To-Video
|
# Image-To-Video
|
||||||
|
|
||||||
This example demonstrates how to deploy the Wan2.2 image-to-video model for online video generation using vLLM-Omni.
|
This example demonstrates online image-to-video generation with 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
|
||||||
|
|
||||||
@@ -20,9 +23,7 @@ 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: `8091`)
|
- `PORT` (default: `8099`)
|
||||||
- `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
|
||||||
# Wan2.2 image-to-video server start script
|
# Shared 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 Wan2.2 I2V server..."
|
echo "Starting image-to-video 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 15%, leaving headroom
|
logical device 0 with memory budgets of 55%, 15%, and 18%, 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 startup video
|
profile admits at most four sequences per stage and bounds Talker context
|
||||||
profiling to 32 frames per video. For throughput,
|
to 4096 tokens. 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,6 +196,7 @@ 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)
|
||||||
@@ -383,6 +384,12 @@ 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,6 +1,9 @@
|
|||||||
# Text-To-Image
|
# Text-To-Image
|
||||||
|
|
||||||
This example demonstrates how to deploy Qwen-Image model for online image generation service using vLLM-Omni.
|
This example demonstrates online text-to-image generation with vLLM-Omni. The
|
||||||
|
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
|
||||||
|
|
||||||
@@ -115,7 +118,23 @@ python gradio_demo.py
|
|||||||
|
|
||||||
## LoRA
|
## LoRA
|
||||||
|
|
||||||
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).
|
This example supports both startup-time distilled LoRAs and request-time
|
||||||
|
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
|
||||||
|
|
||||||
@@ -226,8 +245,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 | 50 | Number of denoising steps |
|
| `num_inference_steps` | int | model | Number of denoising steps |
|
||||||
| `true_cfg_scale` | float | 4.0 | Qwen-Image CFG scale |
|
| `true_cfg_scale` | float | model | True CFG scale when supported |
|
||||||
| `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
|
||||||
"""
|
"""
|
||||||
Qwen-Image Gradio Demo for online serving.
|
Shared text-to-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="Qwen-Image Demo") as demo:
|
with gr.Blocks(title="Text-to-Image Demo") as demo:
|
||||||
gr.Markdown("# Qwen-Image Online Generation")
|
gr.Markdown("# Text-to-Image Online Generation")
|
||||||
gr.Markdown("Generate images using Qwen-Image model")
|
gr.Markdown("Generate images using the model configured on the server")
|
||||||
|
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
with gr.Column(scale=1):
|
with gr.Column(scale=1):
|
||||||
@@ -115,7 +115,6 @@ 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,
|
||||||
)
|
)
|
||||||
@@ -173,7 +172,7 @@ def create_demo(server_url: str):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Qwen-Image Gradio Demo")
|
parser = argparse.ArgumentParser(description="Shared text-to-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
|
||||||
"""
|
"""
|
||||||
Qwen-Image OpenAI-compatible image generation client.
|
Shared OpenAI-compatible text-to-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: Qwen-Image CFG scale
|
true_cfg_scale: True CFG scale for models that support it
|
||||||
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:
|
if negative_prompt is not None:
|
||||||
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="Qwen-Image chat client")
|
parser = argparse.ArgumentParser(description="Shared text-to-image 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="qwen_image_output.png", help="Output file")
|
parser.add_argument("--output", "-o", default="text_to_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, default=1024, help="Image height")
|
parser.add_argument("--height", type=int, help="Image height; defaults to the model setting")
|
||||||
parser.add_argument("--width", type=int, default=1024, help="Image width")
|
parser.add_argument("--width", type=int, help="Image width; defaults to the model setting")
|
||||||
parser.add_argument("--steps", type=int, default=50, help="Inference steps")
|
parser.add_argument("--steps", type=int, help="Inference steps; defaults to the model setting")
|
||||||
parser.add_argument("--cfg-scale", type=float, default=4.0, help="True CFG scale")
|
parser.add_argument("--cfg-scale", type=float, help="True CFG scale when supported")
|
||||||
parser.add_argument("--seed", type=int, default=0, help="Random seed")
|
parser.add_argument("--seed", type=int, 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
|
||||||
# Qwen-Image online serving startup script
|
# Shared text-to-image online serving startup script
|
||||||
|
|
||||||
MODEL="${MODEL:-Qwen/Qwen-Image}"
|
MODEL="${MODEL:-Qwen/Qwen-Image}"
|
||||||
PORT="${PORT:-8091}"
|
PORT="${PORT:-8091}"
|
||||||
|
|
||||||
echo "Starting Qwen-Image server..."
|
echo "Starting text-to-image server..."
|
||||||
echo "Model: $MODEL"
|
echo "Model: $MODEL"
|
||||||
echo "Port: $PORT"
|
echo "Port: $PORT"
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ 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`) | compat only, non-chunk | uploaded audio voice only; no presets | — |
|
| IndexTTS-2 | `IndexTeam/IndexTTS-2` | ✓ (`ref_audio` or uploaded `voice`) | `stream=true` response, 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) | — | ✓ |
|
||||||
@@ -138,15 +139,29 @@ bash examples/online_serving/text_to_speech/glm_tts/run_gradio_demo.sh
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## IndexTTS-2
|
## IndexTTS-2 and IndexTTS-2.5
|
||||||
|
|
||||||
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`.
|
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`.
|
||||||
|
|
||||||
|
### 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
|
||||||
@@ -161,14 +176,35 @@ 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; IndexTTS-2 does not provide 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; neither version provides 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.
|
||||||
- `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.
|
- IndexTTS-2.5 uses `vllm_omni/deploy/indextts2_5.yaml` with the official code-only Stage 0 to Stage 1 contract.
|
||||||
- Deploy config: `vllm_omni/deploy/indextts2.yaml` (auto-loaded).
|
- For IndexTTS-2.5, set `MODEL` to the local native bundle; asset discovery accepts its nested `checkpoints/` layout.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -569,6 +605,13 @@ 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,20 +1,37 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Launch vLLM-Omni server for IndexTTS2
|
# Launch vLLM-Omni server for IndexTTS 2.0 or 2.5.
|
||||||
#
|
#
|
||||||
# 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
|
||||||
# CUDA_VISIBLE_DEVICES=0 PORT=8092 MODEL=/path/to/IndexTeam/IndexTTS-2 examples/online_serving/text_to_speech/indextts2/run_server.sh
|
# MODEL_VERSION=2.5 MODEL=/path/to/native/bundle \
|
||||||
|
# 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="${MODEL:-IndexTeam/IndexTTS-2}"
|
MODEL_VERSION="${MODEL_VERSION:-2.0}"
|
||||||
PORT="${PORT:-8092}"
|
PORT="${PORT:-8092}"
|
||||||
DEPLOY_CONFIG="${DEPLOY_CONFIG:-$ROOT_DIR/vllm_omni/deploy/indextts2.yaml}"
|
|
||||||
|
|
||||||
echo "Starting IndexTTS2 server with model: $MODEL"
|
if [[ "$MODEL_VERSION" == "2.5" ]]; then
|
||||||
|
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" \
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 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
|
||||||
"""OpenAI-compatible client for IndexTTS2 TTS via /v1/audio/speech endpoint.
|
"""OpenAI-compatible client for IndexTTS 2.0/2.5 via /v1/audio/speech.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
# With reference audio for voice cloning
|
# With reference audio for voice cloning
|
||||||
@@ -54,13 +54,44 @@ def main() -> None:
|
|||||||
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("--model", type=str, default="IndexTeam/IndexTTS-2")
|
parser.add_argument(
|
||||||
|
"--model-version",
|
||||||
|
choices=("2.0", "2.5"),
|
||||||
|
default="2.0",
|
||||||
|
)
|
||||||
|
parser.add_argument("--model", type=str, default=None)
|
||||||
|
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("--voice", type=str, default=None, help="Uploaded voice name to use instead of --ref-audio")
|
parser.add_argument("--voice", type=str, default=None, help="Uploaded voice name to use instead of --ref-audio")
|
||||||
parser.add_argument("--output", type=str, default="output.wav")
|
parser.add_argument("--output", type=str, default="output.wav")
|
||||||
parser.add_argument("--api-base", type=str, default=DEFAULT_API_BASE)
|
parser.add_argument("--api-base", type=str, default=DEFAULT_API_BASE)
|
||||||
parser.add_argument("--api-key", type=str, default=DEFAULT_API_KEY)
|
parser.add_argument("--api-key", type=str, default=DEFAULT_API_KEY)
|
||||||
parser.add_argument("--response-format", type=str, default="wav")
|
parser.add_argument("--response-format", type=str, default="wav")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
if args.model is None:
|
||||||
|
if args.model_version == "2.5":
|
||||||
|
parser.error("--model is required for IndexTTS 2.5")
|
||||||
|
args.model = "IndexTeam/IndexTTS-2"
|
||||||
|
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")
|
||||||
|
|
||||||
if not args.ref_audio and not args.voice:
|
if not args.ref_audio and not args.voice:
|
||||||
parser.error("IndexTTS2 requires --ref-audio or --voice for voice cloning")
|
parser.error("IndexTTS2 requires --ref-audio or --voice for voice cloning")
|
||||||
@@ -97,6 +128,10 @@ def main() -> None:
|
|||||||
extra_params["use_emo_text"] = True
|
extra_params["use_emo_text"] = True
|
||||||
if args.use_random:
|
if args.use_random:
|
||||||
extra_params["use_random"] = True
|
extra_params["use_random"] = True
|
||||||
|
if args.model_version == "2.5":
|
||||||
|
payload["speed"] = args.speed
|
||||||
|
extra_params["lang"] = args.lang
|
||||||
|
extra_params["text_normalization"] = args.text_normalization
|
||||||
if extra_params:
|
if extra_params:
|
||||||
payload["extra_params"] = extra_params
|
payload["extra_params"] = extra_params
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ plugins:
|
|||||||
- "vllm_omni.entrypoints.openai" # avoid importing vllm in mkdocs building
|
- "vllm_omni.entrypoints.openai" # avoid importing vllm in mkdocs building
|
||||||
- "vllm_omni.entrypoints.openai.protocol" # avoid importing vllm in mkdocs building
|
- "vllm_omni.entrypoints.openai.protocol" # avoid importing vllm in mkdocs building
|
||||||
- "vllm_omni.experimental" # avoid importing optional serving deps (openai, fastapi, uvicorn, httpx) in mkdocs building
|
- "vllm_omni.experimental" # avoid importing optional serving deps (openai, fastapi, uvicorn, httpx) in mkdocs building
|
||||||
|
- "vllm_omni.model_executor.models.nemotron_voicechat.nemo_vendored" # vendored NeMo code; upstream docstrings trip griffe strict warnings
|
||||||
- mkdocstrings:
|
- mkdocstrings:
|
||||||
handlers:
|
handlers:
|
||||||
python:
|
python:
|
||||||
|
|||||||
@@ -84,11 +84,26 @@ forced-aligner = [
|
|||||||
"qwen-asr>=0.0.6", # faithful Japanese/Korean word segmentation for the TTS forced aligner
|
"qwen-asr>=0.0.6", # faithful Japanese/Korean word segmentation for the TTS forced aligner
|
||||||
]
|
]
|
||||||
|
|
||||||
|
indextts2 = [
|
||||||
|
"fugashi>=1.2.0",
|
||||||
|
"unidic-lite>=1.0.0",
|
||||||
|
"wetext>=0.0.9; sys_platform != 'linux'",
|
||||||
|
"WeTextProcessing; sys_platform == 'linux'",
|
||||||
|
]
|
||||||
|
|
||||||
# Local-test-only extras
|
# Local-test-only extras
|
||||||
local = [
|
local = [
|
||||||
"FlagEmbedding",
|
"FlagEmbedding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# LongCat-Video-Avatar A2V/AI2V runtime dependencies for vocal
|
||||||
|
# separation and loudness normalization.
|
||||||
|
longcat-video-avatar = [
|
||||||
|
"audio-separator==0.44.2",
|
||||||
|
"pyloudnorm==0.1.1",
|
||||||
|
"soundfile>=0.13.1",
|
||||||
|
]
|
||||||
|
|
||||||
# Optional Blackwell-only fused-bias FP8 GEMM; package is `quack-kernels` (imported
|
# Optional Blackwell-only fused-bias FP8 GEMM; package is `quack-kernels` (imported
|
||||||
# as `quack`), not the unrelated `quack`. See docs/user_guide/quantization/fp8.md.
|
# as `quack`), not the unrelated `quack`. See docs/user_guide/quantization/fp8.md.
|
||||||
quack = [
|
quack = [
|
||||||
@@ -191,6 +206,12 @@ ignore = [
|
|||||||
# These files keep upstream conventions (star imports, long lines, math-symbol
|
# These files keep upstream conventions (star imports, long lines, math-symbol
|
||||||
# argument names) so they stay easy to diff against upstream releases.
|
# argument names) so they stay easy to diff against upstream releases.
|
||||||
"vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni_llm.py" = ["E402", "E501", "F403", "F405", "N803"]
|
"vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_omni_llm.py" = ["E402", "E501", "F403", "F405", "N803"]
|
||||||
|
# NemotronVoiceChat inference modules vendored from NVIDIA-NeMo/Speech (branch
|
||||||
|
# nemotron-labs-voicechat). Numerics-bearing code is kept byte-close to
|
||||||
|
# upstream to stay diffable and parity-safe, so upstream style (long lines,
|
||||||
|
# legacy typing, whitespace, unused-import shims) is exempted. TID251 and the
|
||||||
|
# F-correctness rules still apply.
|
||||||
|
"vllm_omni/model_executor/models/nemotron_voicechat/nemo_vendored/**" = ["E501", "E721", "E741", "F401", "F841", "I001", "N803", "N814", "UP006", "UP007", "UP008", "UP011", "UP032", "UP035", "W291", "W293"]
|
||||||
"vllm_omni/experimental/fullduplex/joyvl/decision/prompts.py" = ["E501"] # System/summarizer prompts are long prose lines
|
"vllm_omni/experimental/fullduplex/joyvl/decision/prompts.py" = ["E501"] # System/summarizer prompts are long prose lines
|
||||||
|
|
||||||
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
[tool.ruff.lint.flake8-tidy-imports.banned-api]
|
||||||
@@ -299,6 +320,10 @@ extend-ignore-identifiers-re = [
|
|||||||
".*[Nn][Dd]3.*",
|
".*[Nn][Dd]3.*",
|
||||||
".*updator.*",
|
".*updator.*",
|
||||||
"byt5.*",
|
"byt5.*",
|
||||||
|
# NemotronVoiceChat vendored triton kernel: dout/DOUT abbreviate d_out
|
||||||
|
# (output feature dim), not "doubt".
|
||||||
|
".*dout.*",
|
||||||
|
".*DOUT.*",
|
||||||
]
|
]
|
||||||
extend-ignore-re = [
|
extend-ignore-re = [
|
||||||
"\\.\\.\\.n,d->\\.\\.\\.nd",
|
"\\.\\.\\.n,d->\\.\\.\\.nd",
|
||||||
@@ -319,3 +344,10 @@ byt5 = "byt5"
|
|||||||
# "seeked" is the standard HTML media element DOM event name (fires when a
|
# "seeked" is the standard HTML media element DOM event name (fires when a
|
||||||
# seek completes), used by the word-timestamps Gradio demo. Not a typo.
|
# seek completes), used by the word-timestamps Gradio demo. Not a typo.
|
||||||
seeked = "seeked"
|
seeked = "seeked"
|
||||||
|
# Ryan Leary: author credited in the NemotronVoiceChat vendored NeMo
|
||||||
|
# features.py copyright header. A surname, not a typo of "leery".
|
||||||
|
Leary = "Leary"
|
||||||
|
# IndexTTS 2.5 language codes and speech-emotion-recognition event token.
|
||||||
|
fo = "fo"
|
||||||
|
ba = "ba"
|
||||||
|
SER = "SER"
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
# IndexTTS-2.5 for multilingual voice-cloned TTS on 1x GPU
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- Vendor: IndexTeam
|
||||||
|
- Model: `IndexTeam/IndexTTS-2.5`
|
||||||
|
- Task: Multilingual text-to-speech with voice cloning, emotion control, and
|
||||||
|
native speed control
|
||||||
|
- Mode: Online serving with the OpenAI-compatible `/v1/audio/speech` API, or
|
||||||
|
offline inference through `Omni`
|
||||||
|
- Maintainer: Community
|
||||||
|
|
||||||
|
## When to use this recipe
|
||||||
|
|
||||||
|
Use this recipe to serve IndexTTS-2.5 as a two-stage TTS system. Stage 0 is an
|
||||||
|
autoregressive talker; Stage 1 uses EnhancedCodec, S2Mel CFM/DiT, and BigVGAN
|
||||||
|
to produce 22.05 kHz mono speech. Each request supplies synthesis text and
|
||||||
|
reference audio, or an uploaded audio voice, for zero-shot voice cloning.
|
||||||
|
|
||||||
|
IndexTTS-2.5 adds multilingual text processing and model-native speed control
|
||||||
|
to the IndexTTS-2 serving contract.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- Upstream model:
|
||||||
|
[IndexTeam/IndexTTS-2.5 on Hugging Face](https://huggingface.co/IndexTeam/IndexTTS-2.5)
|
||||||
|
- Online serving example:
|
||||||
|
[`examples/online_serving/text_to_speech/README.md#indextts-2-and-indextts-25`](../../examples/online_serving/text_to_speech/README.md#indextts-2-and-indextts-25)
|
||||||
|
- Offline inference example:
|
||||||
|
[`examples/offline_inference/text_to_speech/README.md#indextts-2-and-indextts-25`](../../examples/offline_inference/text_to_speech/README.md#indextts-2-and-indextts-25)
|
||||||
|
- OpenAI-compatible client:
|
||||||
|
[`examples/online_serving/text_to_speech/indextts2/speech_client.py`](../../examples/online_serving/text_to_speech/indextts2/speech_client.py)
|
||||||
|
- Standard deploy config:
|
||||||
|
[`vllm_omni/deploy/indextts2_5.yaml`](../../vllm_omni/deploy/indextts2_5.yaml)
|
||||||
|
|
||||||
|
## Hardware Support
|
||||||
|
|
||||||
|
### GPU
|
||||||
|
|
||||||
|
### 1x NVIDIA H20 96GB
|
||||||
|
|
||||||
|
#### Environment
|
||||||
|
|
||||||
|
- OS: Linux
|
||||||
|
- Python: 3.10+
|
||||||
|
- Driver / runtime: NVIDIA CUDA environment
|
||||||
|
- vLLM version: Match the repository requirements for your checkout
|
||||||
|
- vLLM-Omni version or commit: Use the commit you are deploying from
|
||||||
|
|
||||||
|
Install vLLM-Omni with the IndexTTS text-processing dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install 'vllm-omni[indextts2]'
|
||||||
|
```
|
||||||
|
|
||||||
|
Obtain the native IndexTTS-2.5 bundle and point `MODEL` at its root. The model
|
||||||
|
loader accepts the upstream nested `checkpoints/` layout.
|
||||||
|
|
||||||
|
#### Command
|
||||||
|
|
||||||
|
Start the standard code-only server from the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MODEL_VERSION=2.5 \
|
||||||
|
MODEL=/path/to/indextts-2.5 \
|
||||||
|
bash examples/online_serving/text_to_speech/indextts2/run_server.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This selects `vllm_omni/deploy/indextts2_5.yaml`, which uses
|
||||||
|
`use_gpt_latent=false`. To launch directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vllm serve /path/to/indextts-2.5 \
|
||||||
|
--omni \
|
||||||
|
--trust-remote-code \
|
||||||
|
--port 8092 \
|
||||||
|
--deploy-config vllm_omni/deploy/indextts2_5.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Optional: NVIDIA MPS for higher single-GPU throughput
|
||||||
|
|
||||||
|
Stage 0 and Stage 1 run in separate processes on the same GPU. Enabling
|
||||||
|
NVIDIA Multi-Process Service (MPS) can improve steady-state throughput by
|
||||||
|
allowing work from both stages to overlap more effectively. MPS is an opt-in
|
||||||
|
deployment optimization: this recipe does not start it automatically, and the
|
||||||
|
gain depends on the request mix and concurrency, so benchmark it with the
|
||||||
|
intended workload before enabling it in production.
|
||||||
|
|
||||||
|
Pay attention to CUDA device renumbering. For example, if the MPS control
|
||||||
|
daemon is started with physical `CUDA_VISIBLE_DEVICES=1`, that GPU is exposed
|
||||||
|
to MPS clients as logical device `0`; launch the server with
|
||||||
|
`CUDA_VISIBLE_DEVICES=0`, not `1`. Use a dedicated MPS pipe/log directory when
|
||||||
|
the host is shared with other services, and stop only the MPS daemon owned by
|
||||||
|
this deployment.
|
||||||
|
|
||||||
|
#### Verification
|
||||||
|
|
||||||
|
Send a multilingual voice-cloning request with the bundled client. Local audio
|
||||||
|
paths are converted to base64 data URLs before transmission:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python examples/online_serving/text_to_speech/indextts2/speech_client.py \
|
||||||
|
--api-base http://localhost:8092 \
|
||||||
|
--model-version 2.5 \
|
||||||
|
--model /path/to/indextts-2.5 \
|
||||||
|
--lang zh \
|
||||||
|
--text "你好,这是 IndexTTS-2.5 语音合成测试。" \
|
||||||
|
--ref-audio /path/to/reference.wav \
|
||||||
|
--output indextts2_5.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Native speed control
|
||||||
|
|
||||||
|
The public `speed` field is handled natively. Its accepted range is
|
||||||
|
`[0.5, 2.0]`; values above `1.0` produce shorter, faster speech. The serving
|
||||||
|
adapter converts the API convention to the model-native control with
|
||||||
|
`duration_factor = 1.0 / speed`, so `speed=1.25` uses
|
||||||
|
`duration_factor=0.8`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python examples/online_serving/text_to_speech/indextts2/speech_client.py \
|
||||||
|
--api-base http://localhost:8092 \
|
||||||
|
--model-version 2.5 \
|
||||||
|
--model /path/to/indextts-2.5 \
|
||||||
|
--speed 1.25 \
|
||||||
|
--text "这是使用模型原生语速控制的测试。" \
|
||||||
|
--ref-audio /path/to/reference.wav \
|
||||||
|
--output indextts2_5_speed.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Upload and reuse a named voice
|
||||||
|
|
||||||
|
Upload a reference recording once when callers should reuse the same voice
|
||||||
|
without sending `ref_audio` on every request. `consent` is required by the
|
||||||
|
voice-storage API:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8092/v1/audio/voices \
|
||||||
|
-F "audio_sample=@/path/to/reference.wav" \
|
||||||
|
-F "consent=user-consent-id" \
|
||||||
|
-F "name=indextts_demo_voice" \
|
||||||
|
-F "speaker_description=IndexTTS-2.5 demonstration voice"
|
||||||
|
```
|
||||||
|
|
||||||
|
Confirm that the voice was stored, then synthesize with its name instead of a
|
||||||
|
reference-audio payload:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8092/v1/audio/voices
|
||||||
|
|
||||||
|
python examples/online_serving/text_to_speech/indextts2/speech_client.py \
|
||||||
|
--api-base http://localhost:8092 \
|
||||||
|
--model-version 2.5 \
|
||||||
|
--model /path/to/indextts-2.5 \
|
||||||
|
--voice indextts_demo_voice \
|
||||||
|
--lang zh \
|
||||||
|
--text "你好,这是复用已上传音色的语音合成测试。" \
|
||||||
|
--output indextts2_5_named_voice.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
There are no built-in text-only preset voices. Each named IndexTTS voice must
|
||||||
|
first be created from an uploaded reference recording.
|
||||||
|
|
||||||
|
##### Multilingual synthesis and text normalization
|
||||||
|
|
||||||
|
The client accepts `zh` (Mandarin), `en` (English), `zhen` (mixed Chinese and
|
||||||
|
English), `ja` (Japanese), and `yue` (Cantonese). The following commands use
|
||||||
|
the same reference voice across all supported language modes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CLIENT=examples/online_serving/text_to_speech/indextts2/speech_client.py
|
||||||
|
MODEL=/path/to/indextts-2.5
|
||||||
|
REF_AUDIO=/path/to/reference.wav
|
||||||
|
|
||||||
|
python "$CLIENT" --model-version 2.5 --model "$MODEL" \
|
||||||
|
--ref-audio "$REF_AUDIO" --lang zh \
|
||||||
|
--text "你好,这是中文语音合成测试。" --output indextts2_5_zh.wav
|
||||||
|
|
||||||
|
python "$CLIENT" --model-version 2.5 --model "$MODEL" \
|
||||||
|
--ref-audio "$REF_AUDIO" --lang en \
|
||||||
|
--text "Hello, this is an English speech synthesis test." \
|
||||||
|
--output indextts2_5_en.wav
|
||||||
|
|
||||||
|
python "$CLIENT" --model-version 2.5 --model "$MODEL" \
|
||||||
|
--ref-audio "$REF_AUDIO" --lang zhen \
|
||||||
|
--text "Hello,欢迎使用 IndexTTS 二点五。" --output indextts2_5_zhen.wav
|
||||||
|
|
||||||
|
python "$CLIENT" --model-version 2.5 --model "$MODEL" \
|
||||||
|
--ref-audio "$REF_AUDIO" --lang ja \
|
||||||
|
--text "こんにちは、これは日本語の音声合成テストです。" \
|
||||||
|
--output indextts2_5_ja.wav
|
||||||
|
|
||||||
|
python "$CLIENT" --model-version 2.5 --model "$MODEL" \
|
||||||
|
--ref-audio "$REF_AUDIO" --lang yue \
|
||||||
|
--text "你好,呢段係粵語語音合成測試。" --output indextts2_5_yue.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
Text normalization is enabled by default. Disable it only when the input is
|
||||||
|
already written exactly as it should be spoken:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python examples/online_serving/text_to_speech/indextts2/speech_client.py \
|
||||||
|
--model-version 2.5 --model /path/to/indextts-2.5 \
|
||||||
|
--ref-audio /path/to/reference.wav --lang en --no-text-normalization \
|
||||||
|
--text "The temperature is twenty five degrees." \
|
||||||
|
--output indextts2_5_pre_normalized.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
For raw HTTP requests, put the same language and text-normalization controls
|
||||||
|
under `extra_params`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8092/v1/audio/speech \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"model": "/path/to/indextts-2.5",
|
||||||
|
"input": "This is an IndexTTS-2.5 test.",
|
||||||
|
"response_format": "wav",
|
||||||
|
"speed": 1.0,
|
||||||
|
"ref_audio": "data:audio/wav;base64,<BASE64_ENCODED_AUDIO>",
|
||||||
|
"extra_params": {
|
||||||
|
"lang": "en",
|
||||||
|
"text_normalization": true
|
||||||
|
}
|
||||||
|
}' \
|
||||||
|
--output indextts2_5_en.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Emotion control
|
||||||
|
|
||||||
|
IndexTTS-2.5 supports an explicit eight-value emotion vector, an emotion text
|
||||||
|
description, or a separate emotion-reference recording. Use one source at a
|
||||||
|
time. Vector values are ordered as `happy`, `angry`, `sad`, `afraid`,
|
||||||
|
`disgusted`, `melancholic`, `surprised`, and `calm`; each value must be in
|
||||||
|
`[0, 1.2]`. `emo_alpha` controls the strength in `[0, 1]`.
|
||||||
|
|
||||||
|
Use an explicit emotion vector for deterministic control:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python examples/online_serving/text_to_speech/indextts2/speech_client.py \
|
||||||
|
--model-version 2.5 --model /path/to/indextts-2.5 \
|
||||||
|
--ref-audio /path/to/reference.wav --lang zh \
|
||||||
|
--text "今天真是令人开心的一天!" \
|
||||||
|
--emo-vector 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 \
|
||||||
|
--emo-alpha 0.8 --output indextts2_5_happy_vector.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
Infer the emotion from a natural-language description with `use_emo_text`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python examples/online_serving/text_to_speech/indextts2/speech_client.py \
|
||||||
|
--model-version 2.5 --model /path/to/indextts-2.5 \
|
||||||
|
--ref-audio /path/to/reference.wav --lang zh \
|
||||||
|
--text "我们终于完成了这项工作!" \
|
||||||
|
--use-emo-text --emo-text "开心、兴奋而且充满活力" \
|
||||||
|
--emo-alpha 0.8 --output indextts2_5_happy_text.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
Transfer emotion from a recording while retaining the speaker identity from
|
||||||
|
`ref_audio`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python examples/online_serving/text_to_speech/indextts2/speech_client.py \
|
||||||
|
--model-version 2.5 --model /path/to/indextts-2.5 \
|
||||||
|
--ref-audio /path/to/reference.wav --lang zh \
|
||||||
|
--text "请用情绪参考音频中的表达方式朗读这句话。" \
|
||||||
|
--emo-audio /path/to/emotion_reference.wav \
|
||||||
|
--emo-alpha 0.8 --output indextts2_5_emotion_audio.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
If multiple emotion sources are supplied, the official precedence is
|
||||||
|
`use_emo_text` > `emo_vector` > `emo_audio` > the emotion in the speaker
|
||||||
|
reference. `--use-random` is also available for exploratory random emotion
|
||||||
|
prototypes, but should not be used when reproducible conditioning is required.
|
||||||
|
|
||||||
|
For offline inference:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python examples/offline_inference/text_to_speech/indextts2/end2end.py \
|
||||||
|
--model /path/to/indextts-2.5 \
|
||||||
|
--model-version 2.5 \
|
||||||
|
--lang zh \
|
||||||
|
--speed 1.0 \
|
||||||
|
--text "你好,这是离线语音合成测试。" \
|
||||||
|
--ref-audio /path/to/reference.wav
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Notes
|
||||||
|
|
||||||
|
- Hardware scope: the standard recipe has been exercised on one NVIDIA H200.
|
||||||
|
It does not claim an unverified throughput or quality result.
|
||||||
|
- Audio output: 22.05 kHz mono WAV.
|
||||||
|
- Voice cloning: reference audio is required on the documented raw request
|
||||||
|
path. Alternatively, `voice` may name an uploaded audio voice; there is no
|
||||||
|
built-in text-only preset voice.
|
||||||
|
- Native speed: serving maps `speed` to the model's duration factor and skips
|
||||||
|
generic waveform speed adjustment, so speed is applied exactly once.
|
||||||
|
- Languages: common codes include `zh`, `en`, `zhen` (mixed Chinese/English),
|
||||||
|
`ja`, and `yue`; `Mandarin` is accepted as an alias for `zh`.
|
||||||
|
- Japanese normalization: `ja` tokenization does not automatically expand
|
||||||
|
numbers, dates, or percentages. Write these inputs as readable Japanese text
|
||||||
|
before inference.
|
||||||
|
- Emotion controls: `use_emo_text`, `emo_vector`, and `emo_audio` are
|
||||||
|
alternative conditioning modes. Their precedence is `use_emo_text` >
|
||||||
|
`emo_vector` > `emo_audio` > the speaker-reference emotion.
|
||||||
|
- Sampling difference: Stage 0 uses plain vLLM sampling, not the upstream
|
||||||
|
default `num_beams=3` beam search. Use upstream `num_beams=1` for parity
|
||||||
|
comparisons; output may differ from the official beam-search result.
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
# MiniMax-H3 on RTX 4090
|
||||||
|
|
||||||
|
This recipe uses BF16 weights, tiled VAE decode, tensor parallelism, optional
|
||||||
|
Ulysses sequence parallelism, and distributed layerwise offload (DLO). It is the
|
||||||
|
24 GiB sibling of [MiniMax-H3-5090.md](MiniMax-H3-5090.md): the two-GPU topology
|
||||||
|
matches that recipe, but the resident DiT-block count is lower so the per-rank
|
||||||
|
peak fits in 24 GiB. Lower resident counts reduce HBM use and increase
|
||||||
|
CPU-to-GPU transfer time.
|
||||||
|
|
||||||
|
## Capacity requirements
|
||||||
|
|
||||||
|
| Resource | Two RTX 4090s | Four RTX 4090s |
|
||||||
|
| --- | ---: | ---: |
|
||||||
|
| GPU HBM | 24 GiB per GPU | 24 GiB per GPU |
|
||||||
|
| Checkpoint storage | 135 GiB per partition | 135 GiB per partition |
|
||||||
|
| Available system RAM | 200 GiB minimum | 200 GiB minimum |
|
||||||
|
| Recommended system RAM | 384 GiB | 384 GiB |
|
||||||
|
|
||||||
|
`FL2VA` and `Ref2VA` are separate 135 GiB checkpoint partitions. Start one
|
||||||
|
server at a time. DLO keeps rank-local weights in pinned host memory; increasing
|
||||||
|
`--dlo-resident-layers` improves latency but does **not** reduce host RAM in the
|
||||||
|
current implementation because resident layers retain pinned CPU master copies.
|
||||||
|
|
||||||
|
A single RTX 4090 is not covered here. The one-GPU DLO profile in the RTX 5090
|
||||||
|
recipe peaked at 26.50 GiB with 12 resident layers, which exceeds 24 GiB. On one
|
||||||
|
4090, use the model-level CPU offload command in
|
||||||
|
[MiniMax-H3.md](MiniMax-H3.md#single-gpu-accuracy-and-memory-first) instead, or
|
||||||
|
lower the resident count and re-measure before trusting it.
|
||||||
|
|
||||||
|
> **Modular H3:** after #5720 lands, preserve this recipe's one-partition
|
||||||
|
> behavior with `--task-type fl2va` or `--task-type ref2va`.
|
||||||
|
|
||||||
|
## Two RTX 4090s: 1024x576, 5 seconds
|
||||||
|
|
||||||
|
Use TP2 and 12 resident DiT layers. This is the `rtx4090` profile from the main
|
||||||
|
recipe. On two RTX 4090s it peaked at 15,620 MiB per rank for T2VA and
|
||||||
|
14,918 MiB per rank for Ref2VA, leaving roughly 9 GiB of headroom per card. See
|
||||||
|
[Target-hardware validation](#target-hardware-validation) for the full numbers.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export VLLM_WORKER_MULTIPROC_METHOD=spawn
|
||||||
|
export VLLM_OMNI_VIDEO_SYNC_TIMEOUT=14400
|
||||||
|
|
||||||
|
CUDA_VISIBLE_DEVICES=0,1 vllm serve /path/to/MiniMax-H3/FL2VA \
|
||||||
|
--omni --trust-remote-code --host 0.0.0.0 --port 8000 \
|
||||||
|
--num-gpus 2 --tensor-parallel-size 2 --text-encoder-tp-size 2 \
|
||||||
|
--usp 1 --ring 1 --vae-patch-parallel-size 2 \
|
||||||
|
--vae-parallel-mode tile --vae-use-tiling \
|
||||||
|
--enable-distributed-layerwise-offload --dlo-no-use-allgather \
|
||||||
|
--dlo-resident-layers 12 --enforce-eager \
|
||||||
|
--diffusion-attention-backend CUDNN_ATTN
|
||||||
|
```
|
||||||
|
|
||||||
|
For Ref2VA, stop the FL2VA server and restart the same command with
|
||||||
|
`/path/to/MiniMax-H3/Ref2VA`. Ref2VA reference image count and prompt length can
|
||||||
|
increase activation memory; begin with one request at a time.
|
||||||
|
|
||||||
|
`VLLM_OMNI_VIDEO_SYNC_TIMEOUT` matters more here than on larger cards. DLO
|
||||||
|
streams non-resident DiT blocks over PCIe on every denoising step, so a 60-step
|
||||||
|
request takes several minutes and would otherwise hit the 600-second default.
|
||||||
|
|
||||||
|
## Four RTX 4090s: 1024x576, 5 seconds
|
||||||
|
|
||||||
|
Keep TP2 and raise Ulysses sequence parallel to 2 so the world size is
|
||||||
|
`TP × USP = 4`. Text-encoder TP and VAE patch parallel follow the GPU count.
|
||||||
|
Resident layers stay at 12: adding USP does not shard DiT weights further, so
|
||||||
|
per-GPU peak HBM stays close to the two-GPU profile while wall-clock improves.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export VLLM_WORKER_MULTIPROC_METHOD=spawn
|
||||||
|
export VLLM_OMNI_VIDEO_SYNC_TIMEOUT=14400
|
||||||
|
|
||||||
|
CUDA_VISIBLE_DEVICES=0,1,2,3 vllm serve /path/to/MiniMax-H3/FL2VA \
|
||||||
|
--omni --trust-remote-code --host 0.0.0.0 --port 8000 \
|
||||||
|
--num-gpus 4 --tensor-parallel-size 2 --text-encoder-tp-size 4 \
|
||||||
|
--usp 2 --ring 1 --vae-patch-parallel-size 4 \
|
||||||
|
--vae-parallel-mode tile --vae-use-tiling \
|
||||||
|
--enable-distributed-layerwise-offload --dlo-no-use-allgather \
|
||||||
|
--dlo-resident-layers 12 --enforce-eager \
|
||||||
|
--diffusion-attention-backend CUDNN_ATTN
|
||||||
|
```
|
||||||
|
|
||||||
|
For Ref2VA, stop the FL2VA server and restart the same command with
|
||||||
|
`/path/to/MiniMax-H3/Ref2VA`. Pin four dedicated GPUs; if the launcher exposes
|
||||||
|
fewer than four devices, startup fails with
|
||||||
|
`Stage 0 requires 4 device(s) based on parallel_config`.
|
||||||
|
|
||||||
|
## Target-hardware validation
|
||||||
|
|
||||||
|
Measured on RTX 4090 (24,564 MiB each, driver 580.126.09) with the serve
|
||||||
|
commands above, at vLLM-Omni `0.26.1.dev55+g81b48e83e`, vLLM `0.26.0`, and
|
||||||
|
PyTorch `2.11.0+cu130`. GPUs used by each server were dedicated. `Client E2E`
|
||||||
|
and `Peak per GPU` come from the `/v1/videos/sync` response headers
|
||||||
|
`x-inference-time-s` and `x-peak-memory-mb`; the latter is the rank-0 CUDA
|
||||||
|
reserved high-water mark, so it is a per-GPU figure.
|
||||||
|
|
||||||
|
| GPUs | Topology | Task | Shape | Frames | Steps | Client E2E | Peak per GPU | Output validation |
|
||||||
|
| ---: | --- | --- | ---: | ---: | ---: | ---: | ---: | --- |
|
||||||
|
| 2 | TP2 × USP1 | T2VA (`FL2VA`) | 1024x576 | 124 at 24 FPS | 60 | 7 min 9 s | 15.3 GiB | H.264 video + 32 kHz stereo AAC; full `ffmpeg` decode passed |
|
||||||
|
| 2 | TP2 × USP1 | Ref2VA | 1024x576 | 124 at 24 FPS | 60 | 14 min 52 s | 14.6 GiB | H.264 video + 32 kHz stereo AAC; full `ffmpeg` decode passed |
|
||||||
|
| 4 | TP2 × USP2 | T2VA (`FL2VA`) | 1024x576 | 124 at 24 FPS | 60 | 4 min 29 s | 15.2 GiB | H.264 video + 32 kHz stereo AAC; full `ffmpeg` decode passed |
|
||||||
|
| 4 | TP2 × USP2 | Ref2VA | 1024x576 | 124 at 24 FPS | 60 | 9 min 5 s | 16.1 GiB | H.264 video + 32 kHz stereo AAC; successful `/v1/videos/sync` 200 |
|
||||||
|
|
||||||
|
All rows use `seed=1101`, `flow_shift=12`, and `audio_flow_shift=3.0`.
|
||||||
|
|
||||||
|
Two-GPU repeats: T2VA measured 434.9 s and 429.3 s; Ref2VA measured 891.9 s and
|
||||||
|
892.1 s. Peak memory was identical across repeats for both tasks, and the two
|
||||||
|
outputs of each pair agreed to within 115 bytes.
|
||||||
|
|
||||||
|
Four-GPU T2VA repeats: 273.8 s and 269.5 s, peaking at 15,560 MiB and
|
||||||
|
15,612 MiB. Four-GPU Ref2VA is a single successful end-to-end run (545.2 s,
|
||||||
|
16,448 MiB). Follow-up Ref2VA requests on this build completed diffusion but
|
||||||
|
then hit the engine's hardcoded 30 s async output wait
|
||||||
|
(`_ASYNC_OUTPUT_TIMEOUT` in `diffusion_engine.py`) during post-compute D2H,
|
||||||
|
so they are not reported as repeats.
|
||||||
|
|
||||||
|
Relative to the two-GPU baseline on the same shape, four-GPU TP2×USP2 was about
|
||||||
|
1.6× faster for both tasks, while per-GPU peak HBM stayed in the mid-15 GiB
|
||||||
|
range because USP does not further shard resident DiT weights. These are
|
||||||
|
single-request validation runs, not concurrent throughput benchmarks.
|
||||||
|
|
||||||
|
## Request examples
|
||||||
|
|
||||||
|
`t2va` requires an explicit `aspect_ratio`. Without it the request fails with
|
||||||
|
`t2va requires an explicit aspect_ratio`, even when `width` and `height` are set.
|
||||||
|
|
||||||
|
Text to video and audio, against the `FL2VA` server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -D headers_t2va.txt -o out_t2va.mp4 --max-time 14400 \
|
||||||
|
-X POST "http://127.0.0.1:8000/v1/videos/sync" \
|
||||||
|
-F 'prompt=傍晚的小厨房,夕阳余晖从窗边洒进来,旧木桌上放着洗到一半的马克杯和起雾的玻璃瓶,悬挂的抹布轻轻晃动。画面带有手持拍摄的轻微晃动和逆光曝光波动,生活感十足。环境音是安静的厨房底噪。' \
|
||||||
|
-F 'aspect_ratio=16:9' \
|
||||||
|
-F 'width=1024' -F 'height=576' -F 'fps=24' \
|
||||||
|
-F 'num_inference_steps=60' -F 'flow_shift=12' -F 'seed=1101' \
|
||||||
|
-F 'extra_params={"task":"t2va","duration":5,"audio_flow_shift":3.0}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Reference to video and audio, against the `Ref2VA` server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -D headers_ref2va.txt -o out_ref2va.mp4 --max-time 14400 \
|
||||||
|
-X POST "http://127.0.0.1:8000/v1/videos/sync" \
|
||||||
|
-F 'prompt=傍晚的小厨房,夕阳余晖从窗边洒进来,旧木桌上放着洗到一半的马克杯和起雾的玻璃瓶,悬挂的抹布轻轻晃动。画面带有手持拍摄的轻微晃动和逆光曝光波动,生活感十足。环境音是安静的厨房底噪。' \
|
||||||
|
-F 'input_reference=@/path/to/reference.jpg;type=image/jpeg' \
|
||||||
|
-F 'audio_reference=</path/to/audio_reference.json' \
|
||||||
|
-F 'width=1024' -F 'height=576' -F 'fps=24' \
|
||||||
|
-F 'num_inference_steps=60' -F 'flow_shift=12' -F 'seed=1101' \
|
||||||
|
-F 'extra_params={"task":"ref2va","duration":5,"audio_flow_shift":3.0}'
|
||||||
|
```
|
||||||
|
|
||||||
|
`audio_reference` is a JSON object, which is why the example loads it from a file
|
||||||
|
with curl's `<` syntax. Its `audio_url` must be an `http(s)` URL or a data URL;
|
||||||
|
a bare filesystem path is rejected with `Invalid audio_reference.audio_url`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"audio_url": "https://example.com/reference.wav"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Reference images are validated before inference: the short edge must be at least
|
||||||
|
256 pixels, the long edge at most 5760, and the aspect ratio must fall between
|
||||||
|
0.4 and 2.5.
|
||||||
|
|
||||||
|
The response headers carry the measurements used in the table above:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat headers_t2va.txt
|
||||||
|
ffmpeg -v error -i out_t2va.mp4 -f null - && echo DECODE_OK
|
||||||
|
```
|
||||||
Loaded 100 of 447 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user