Compare commits

...
Author SHA1 Message Date
Gao Han 0ae9bdd8c2 Merge branch 'main' into dev/vllm-align 2026-09-10 11:23:08 +08:00
tzhouam 80a0d8d20a ci: use vLLM 0.29 release image and align installation docs
Drop the old-base wheel reinstall and dependency repair block. Update source installation and configuration references to vLLM 0.29 while retaining explicitly matched 0.28 prebuilt Omni examples until its next release.

Validation: official image and CUDA/ROCm release artifacts checked; 6 documentation tests passed; diff whitespace checks passed. Markdown lint reports existing snippet/anchor issues. Container build not run: no builder available.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 11:11:42 +08:00
Mu GuanLin 4b8dcac681 [Kernel] Enable LTX DiffVAE fusions on SM100 and SM103 (#7350)
Signed-off-by: mglyn <1203789601@qq.com>
2026-09-10 03:07:35 +00:00
Weiming Liaoandwangyu 35a83a0885 [NPU][CI] Add A5 and 310P CI support (#6875)
Signed-off-by: Weiming Liao <liaowm5@gmail.com>
Co-authored-by: wangyu <53896905+yenuo26@users.noreply.github.com>
2026-09-10 02:16:14 +00:00
andyluo7 3d71a2b3ad [CI/Build] Stabilize LTX2 vocoder autocast test on ROCm (#7336)
Signed-off-by: andyluo7 <andy.luo@amd.com>
2026-09-10 10:09:39 +08:00
Gao Hanandamy-why-3459 6e63ea875e [Bugfix] Allow LLM replicas on different GPUs to initialize concurrently (#7292)
Signed-off-by: Gao Han <hgaoaf@connect.ust.hk>
Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
2026-09-10 09:54:20 +08:00
Hongsheng Liuandhsliu_ustc 0637454301 [Skills] Add perf-evidence isolation, stage-attribution, and realtime-contract requirements (#6820)
Signed-off-by: hsliu_ustc <hsliu_ustc@noreply.gitcode.com>
Co-authored-by: hsliu_ustc <hsliu_ustc@noreply.gitcode.com>
2026-09-10 08:05:47 +08:00
Guangjian Dong faad918588 [Doc] Document num_outputs_per_prompt for /v1/videos (#7341)
Signed-off-by: Guangjian <hiro20833@gmail.com>
2026-09-10 08:00:13 +08:00
tzhouamandClaude Fable 5.1 121d0b9aee fix(glm_image): build the target-shape scaffold without HF's private formatter
Nightly 3034 (Simple · Model Executor Test) failed all six
test_generation_prompt_ids_preserve_hf_target_scaffold cases with

    AttributeError: type object 'GlmImageProcessor' has no attribute '_build_prompt_with_target_shape'

The 0.29 rewrite of GlmImageMultiModalProcessor.apply reached for
GlmImageProcessor._build_prompt_with_target_shape, a private transformers
method. The test suite stubs transformers.models.glm_image.processing_glm_image
with a bare class to import glm_image_ar without the real package, so in the
full CPU run the private attribute is not there; the same dependency would
break at runtime on any transformers build that renames or removes it.

Format the scaffold in Omni instead: _build_target_shape_scaffold mirrors the
HF formula (identical in transformers 5.13 and 5.14: target grid, plus the
preview grid for text-to-image, then the image bos token) using only the
processor's public grid_bos_token / grid_eos_token / bos_token attributes.
The test keeps its hard-coded expected suffix, which is what HF produces for
512x768, and no longer imports the HF class.

Validation: tests/model_executor/models/glm_image passes (25 tests) both alone
and inside the full `tests/model_executor -m "core_model and cpu"` run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpXe2LeRrUQGjZfKFaocMZ
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 07:59:25 +08:00
tzhouamandClaude Fable 5.1 c4b37471c4 test(qwen3_tts): accept the mapper argument in the code2wav weight-loader double
Nightly 3034 (Simple · Model Executor Test) failed 11 cases in
tests/model_executor/models/qwen3_tts/test_qwen3_tts_code2wav.py with

    TypeError: _FakeAutoWeightsLoader.load_weights() got an unexpected keyword argument 'mapper'

vLLM 0.29 removed skip_prefixes from AutoWeightsLoader, so on this branch
Qwen3TTSCode2Wav.load_weights passes the "encoder." exclusion as a
WeightsMapper via load_weights(weights, mapper=...). The test double still
modelled the old one-argument signature. Let it accept the keyword.
Branch-only rebase fallout; no production code changed.

Validation: tests/model_executor/models/qwen3_tts passes (232 tests).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpXe2LeRrUQGjZfKFaocMZ
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 07:58:27 +08:00
Joshna-Medisetty 51cf142bc1 [Bugfix][XPU] Restore N-D output shape for W8A16 FP8 linear (#7301)
Signed-off-by: Joshna Medisetty <joshna.medisetty@intel.com>
Signed-off-by: Joshna-Medisetty <joshna.medisetty@intel.com>
2026-09-09 17:11:31 -05:00
Sy03 b02862beee [Model] Optimize MOSS-TTS Local batched execution and streaming codec (#7202)
Signed-off-by: Sy03 <1370724210@qq.com>
2026-09-09 13:02:12 -07:00
bojiang-li 504e5a6a6e [Doc] Add dual DGX Spark MiniMax-H3 results (#7343)
Signed-off-by: bojiang-li <327132355+bojiang-li@users.noreply.github.com>
2026-09-09 18:48:41 +00:00
tzhouamandClaude Fable 5.1 f270ff7c3d test(minicpmo): teach the audio_embeds tokenizer stub the 0.29 token-id contract
main's #5730 added tests/model_executor/models/minicpmo_4_5/test_audio_embeds_input.py
with an identity tokenizer double that only implements encode/decode. On this
branch MiniCPMO45OmniLLMMultiModalProcessor._get_prompt_updates follows the
vLLM 0.29 prompt-update contract (PromptUpdateDetails.select_text is gone),
so it resolves the <unk> placeholder id once via
tokenizer.convert_tokens_to_ids("<unk>"), which the double lacks:

    AttributeError: '_IdentityTokenizer' object has no attribute 'convert_tokens_to_ids'

Give the double that method. The tests still read only the placeholder text
from PromptUpdateDetails.full, so the returned id is irrelevant to them.
Branch-only rebase fallout; no production code changed.

Validation: the four failing cases in test_audio_embeds_input.py pass (5 passed);
tests/model_executor/models/minicpmo_4_5 otherwise unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpXe2LeRrUQGjZfKFaocMZ
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 01:06:25 +08:00
tzhouamandClaude Fable 5.1 fb94b87d4b refactor(inputs): build the Omni renderer as a subclass instead of a proxy
OmniRenderer wrapped the upstream renderer, forwarded unknown attributes via
__getattr__, and re-bound eight BaseRenderer methods (render_cmpl, render_chat,
process_for_engine, _process_enc_dec and their async twins) onto the wrapper
so that they would reach Omni's _process_singleton. That allow-list hid any
concrete-renderer override of those methods and would silently skip the
Omni extras for any future BaseRenderer entry point not on the list.

Make OmniRenderer a mixin and create the renderer as a real subclass of the
upstream class instead:

- omni_renderer_cls(base_cls) builds (and caches) Omni<Base> = (OmniRenderer,
  base_cls); only _process_singleton / _process_singleton_async are extended,
  everything else is the upstream renderer unchanged.
- build_omni_renderer(vllm_config) resolves the class the way upstream
  renderer_from_config does (cached_tokenizer_from_config,
  tokenizer_args_from_config, RENDERER_REGISTRY.load_renderer_cls) and
  instantiates the Omni subclass; renderer_cls/tokenizer can be passed to
  skip the resolution.
- build_stage0_input_processor passes the renderer to InputProcessor up
  front instead of replacing it afterwards; the skip-tokenizer branch keeps
  _build_token_only_renderer and never resolves a tokenizer.

Rebinding an existing instance's __class__ was rejected: CPython refuses it
for HfRenderer ("object layout differs"), so the subclass is constructed.

Tests: tests/inputs/test_preprocess.py builds the renderer with
omni_renderer_cls and adds checks that the subclass is created once, that a
concrete renderer's render_cmpl override is no longer suppressed, and that
build_omni_renderer resolves like upstream or honours an explicit class;
tests/engine/test_async_omni_engine_stage_init.py asserts the stage-0
processor receives the built subclass instance and that the skip-tokenizer
branch never calls build_omni_renderer. Two pre-existing mypy complaints in
test_preprocess.py are fixed with annotations.

Validation: ruff, pre-commit (incl. mypy) on the four files; the modules
import and omni_renderer_cls(HfRenderer) builds; tests/inputs (32),
tests/engine/test_async_omni_engine_stage_init.py (55) and tests/entrypoints
(548) pass on CPU.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpXe2LeRrUQGjZfKFaocMZ
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouamandClaude Fable 5.1 a58825ed6c perf(omnivoice): tokenize the prompt once per request
apply() already tokenized the prompt to build the engine prompt ids, but the
shared OmniMultiModalProcessor._apply_hf_processor_main then called
_call_hf_processor again, which rebuilt the control-token prompt and ran the
text tokenizer a second time before encoding the reference audio. Upstream's
_cached_apply_hf_processor invokes _apply_hf_processor_main for every
request, so this also happened for text-only requests and for requests whose
reference audio was already cached.

Split the old _call_hf_processor into _encode_prompt_text (used once, in
apply) and _encode_reference_audio, and override _apply_hf_processor_main to
encode the reference audio only, returning just the passthrough data when
there is no audio. _get_mm_fields_config registers only ref_audio_tokens and
ref_audio_len, so dropping input_ids from the media result changes no
consumer. The now-unreferenced _call_hf_processor is deleted; the prompt-text
kwarg transport is left as is.

Validation: ruff and pre-commit on both files; the module imports cleanly;
new tests/model_executor/models/omnivoice/test_processor_prompt_tokenization.py
covers text-only apply (one tokenizer call), the audio path (audio encoded,
no re-tokenization) and the empty-items path (no tokenizer, no encoder);
tests/model_executor/models/omnivoice (106), tests/inputs/test_mm_processor.py
and tests/entrypoints/openai_api/test_tts_{adapter,detection}.py (162) pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpXe2LeRrUQGjZfKFaocMZ
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouamandClaude Fable 5.1 2f454823a6 refactor(qwen3_omni): inherit _maybe_apply_prompt_updates from the Qwen2.5-Omni processor
The vLLM 0.29 rewrite left Qwen3OmniMoeThinkerMultiModalProcessor with a
_maybe_apply_prompt_updates that is identical to the one on its first base,
Omni's Qwen2_5OmniThinkerMultiModalProcessor (same body modulo docstring).
The MRO is Omni Qwen3 -> Omni Qwen2.5 -> upstream Qwen3 -> upstream Qwen2.5
-> BaseMultiModalProcessor, so deleting the override resolves to the same
code; asserted with
Qwen3OmniMoeThinkerMultiModalProcessor._maybe_apply_prompt_updates is
Qwen2_5OmniThinkerMultiModalProcessor._maybe_apply_prompt_updates.

Validation: ruff and pre-commit on the file; the MRO assertion above;
tests/model_executor/models/{qwen3_omni,qwen2_5_omni} pass (20 + 81 tests).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpXe2LeRrUQGjZfKFaocMZ
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouamandClaude Fable 5.1 ec3880df34 refactor(voxtral_tts): drop pass-through _cached_apply_hf_processor
After the vLLM 0.29 adaptation the override only returned
super()._cached_apply_hf_processor(inputs, timing_ctx). Delete it together
with the imports it alone used (MultiModalProcessingInfo, TimingContext).
The find-only _maybe_apply_prompt_updates override is unchanged.

Validation: ruff and pre-commit on the file; the module imports cleanly;
tests/model_executor/models/voxtral_tts passes (62 tests).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpXe2LeRrUQGjZfKFaocMZ
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouamandClaude Fable 5.1 1439847f33 refactor(processors): drop _call_hf_processor overrides upstream no longer calls
vLLM 0.29 removed BaseMultiModalProcessor._call_hf_processor; the pipeline
now reaches the HF processor through _apply_hf_processor_main and the
_preprocess_hf_mm_data / _postprocess_hf_mm_data hooks. These three
overrides still called super()._call_hf_processor, which would raise
AttributeError if anything reached them:

- bagel: _apply_hf_processor_main already performs the image / img2img
  split via self.info.ctx.call_hf_processor, so the override was unreachable.
- mimo_audio_llm: the audios -> audio rename and the sampling_rate kwarg
  already live in _preprocess_hf_mm_data.
- step_audio2: the audios -> audio normalisation and the empty
  audio_mels / audio_lens defaults already live in _preprocess_hf_mm_data
  and _postprocess_hf_mm_data.

Caller audit: the only remaining _call_hf_processor call sites are the live
ones in vllm_omni/inputs/mm_processor.py, omnivoice.py and the GLM-TTS test.

Validation: ruff and pre-commit on the three files; the three modules
import cleanly; CPU tests in tests/inputs, tests/model_executor/models/
{mimo_audio,step_audio2} and test_upstream_prompt_update_api.py pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpXe2LeRrUQGjZfKFaocMZ
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouamandClaude Fable 5.1 188d021023 refactor(processors): drop _hf_processor_applies_updates overrides removed in vLLM 0.29
vLLM 0.29 deleted BaseMultiModalProcessor._hf_processor_applies_updates; no
reference to the hook remains anywhere in vllm/, so these six overrides were
never called. Delete them. No behaviour change.

Validation: pre-commit on the six files; CPU tests in tests/inputs and
tests/model_executor/models/{cosyvoice3,mimo_audio,step_audio2,omnivoice},
test_glm_tts_prompt_len.py and test_upstream_prompt_update_api.py pass;
the six processor modules import cleanly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpXe2LeRrUQGjZfKFaocMZ
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouam efda9369a8 refactor: reduce copied vLLM 0.29 compatibility code
Reuse upstream weight mapping and multimodal processing, consolidate Omni processor bridges, and trim unused Hunyuan internals. Route no-media kwargs through the active renderer and preserve GLM generation prompt formatting.

Validation: 453 CPU tests passed, 7 skipped; processor imports, Ruff, and diff checks passed. GPU and end-to-end CI not rerun.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouam eef8e2bb94 ci: advance vLLM 0.29 release pin to 98dff2a81d74
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouam 26a37abfaf test: align SANA SP CPU fixtures with vLLM 0.29 GEMM dispatch
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouam b751b6e62c fix: preserve Qwen Image rotary frequency precision on CUDA
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouam b79911841f fix: keep frontend log-stats flag out of stage ownership validation
The API server consumes disable_log_stats and passes log_stats to AsyncOmni. Treat it as a frontend field during structured config resolution so existing TTS launch commands still start after rebasing onto the main config refactor. Retain validation for genuinely unowned stage options.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouam 4653b49a0c test: measure dots TTS PCM quality at its native 48 kHz rate
The response preserves the native sample rate when none is requested. The HNR helper assumed 24 kHz, halving the perceived pitch of dots.tts-soar output and using the wrong autocorrelation window. Let PCM tests declare the native rate, honor requested resampling rates first, and retain the quality thresholds. Cover the failure with a 120 Hz synthetic voice and rate-selection tests.

Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouam 51c5a284a6 rebase: remove speech import superseded by main adapter migration
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
Zhou Taichang 90b7b6c5c9 [CI] Preserve secret-backed HF token in scheduled GPU jobs (#7118)
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
Signed-off-by: Zhou Taichang <tzhouam@connect.ust.hk>
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouam 9af0331b21 rebase: align vllm-omni with vLLM d2906cc19586
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouam e4c47a2569 rebase: align vllm-omni with vLLM d2906cc19586
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouamandClaude Opus 5 94914a1270 rebase: keep the omni scheduler on the v1 model runner
Every omni worker already forces the v1 model runner because the v2 runner
carries no omni hooks (four worker classes do this). The scheduler was left
reading the config value, and vLLM 0.29 changed that default: 0.28 resolved
this config to v1, 0.29 ends its new fallback chain with v2.

The two then disagreed. The scheduler took its v2 fast path, which
deliberately omits resumed-request token ids ("No resumed reqs in fast
path"), while the worker-side v1 runner still read them, so resuming a
request raised KeyError: <req_id> in _update_states and killed the engine
core. That is the Multi-Replica Startup Test failure: it passes on main
6/6 and on the branch before the rebase (3005), and fails on every build
since.

Forcing it once in OmniSchedulerMixin covers both OmniARScheduler and
OmniGenerationScheduler, mirroring the wording the workers already use.

Adds tests/core/sched/test_omni_scheduler_forces_v1_model_runner.py, since a
default-sensitive regression like this would otherwise return silently: it
pins Omni's side of the contract rather than the upstream default, and was
negative-controlled by removing the fix (2 of 3 cases fail without it).

The 4x H100 multi-replica path itself is not reproducible here, so that part
is verified only by proxy: 165 sched tests, plus a 599-test
core/diffusion/ar_diffusion sweep, and the change is inert whenever the
config already resolves to v1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146Xu5Sgj4vXxLxvLjV5YPj
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouamandClaude Opus 5 b5146fa981 rebase: rename Qwen3-Omni audio attention projection to qkv_proj
vLLM 0.29 renamed the audio attention projection from qkv to qkv_proj, and
its stacked mapping now rewrites checkpoint q_proj/k_proj/v_proj onto that
name. Omni subclasses the upstream audio attention and mirrors its __init__
to add quant_config, so it kept building self.qkv and weight loading died
with "There is no module or parameter named layers.0.self_attn.qkv_proj in
Qwen3OmniMoeAudioEncoder ... available: self_attn.qkv.weight". The inherited
forward calls self.qkv_proj too, so the old name broke both paths.

This is a second, independent cause behind the Omni Accuracy Test failure:
the shutdown-timeout signature also appeared in that job, which is why the
earlier pass attributed it solely to the shutdown fix. Both builds 3009 and
3010 carry this weight-loading error, so it predates that pass.

Not reproducible locally: it needs the 30B checkpoint. The rename mirrors
upstream Qwen3OmniMoeAudioAttention exactly, and the 20 Qwen3-Omni unit tests
still pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146Xu5Sgj4vXxLxvLjV5YPj
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouamandClaude Opus 5 85d1f92ee5 test: guard prompt-update helpers against upstream removal
The MiniCPM-o select_text breakage cost eight CI jobs and surfaced only as
"Engine core initialization failed", because the replacement closures that
call these helpers are not driven by any unit test. A static check is what
actually catches the next removal.

Resolves each PromptUpdateDetails attribute referenced under vllm_omni/
against the installed class, so it tracks upstream instead of a hard-coded
list, and names the offending file in the failure. Verified by reintroducing
select_text: the guard fails with that file named, and passes once reverted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146Xu5Sgj4vXxLxvLjV5YPj
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouamandClaude Opus 5 c1e32fc01d rebase: close the remaining vLLM 0.29 drift found by CI 3009
Five further removals/renames, each traced from a failing 3009 job:

- StageEngineCoreProcManager mirrors the parent __init__ rather than calling
  super(), and 0.29 added _request_shutdown_timeout there. The inherited
  shutdown() read it, raised AttributeError, and left the engine core
  subprocesses running, so the test process hung at interpreter exit until
  the 15 minute job timeout. That is the whole of the Engine&Model Executor
  exit-124: its pytest reported 567 passed, then sat idle for 663s. The
  failure surfaced only as a StagePool warning, never as a test failure.

- PromptUpdateDetails.select_text is gone; prompt updates are token oriented
  now. MiniCPM-o 4.5 kept three text-based image/video/audio replacements,
  which crashed stage-0 startup and account for the whole MiniCPM-o cluster.
  They encode the text and select the unk placeholder by token id instead.

- Qwen3-Omni: 0.29 marks audio_feature_lengths keep_on_cpu, while the audio
  tower derives device placement from feature_lens, so the mask comparison
  mixed cuda and cpu tensors. Moved explicitly, matching upstream
  Qwen3OmniMoeThinker._process_audio_input.

- AttentionBackend.get_kv_cache_shape is gone; the per-layer shape comes from
  the spec via compute_layer_kv_cache_shape_bytes, whose content dim is in
  bytes.

- WeightsMapper.get_unstacked_mapper is renamed to get_rename_mapper.

tests/engine/test_async_omni_engine_abort.py::test_abort fails here both with
and without these changes, so it is environmental rather than a regression.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146Xu5Sgj4vXxLxvLjV5YPj
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouamandClaude Opus 5 9a399adba8 chore: satisfy changed-file policy
Scoped pre-commit over the 27 files this rebase touches: ruff format
reflow, the vLLM-Omni SPDX copyright line on the new modules, and wording
the typos hook rejected.

The Python 3.10 mypy hook still reports 24 errors across five of these
files. All 24 sit on pre-existing lines; none fall on a line this rebase
changed, which the repository documents as the CI-skipped condition.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146Xu5Sgj4vXxLxvLjV5YPj
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouamandClaude Opus 5 08884c4cfb rebase: adapt to vLLM 0.29 callback, embedding and video decoder changes
Four unrelated 0.29 surface changes, each reproduced locally against the
pinned f5c3cc24 build:

- PromptUpdateDetails.is_embed now takes only the full token list. The
  Bagel img2img replacement still declared (tok, seq), which crashed
  stage-0 engine core startup and produced the Distributed and Qwen3-Omni
  "no live replica" failures rather than anything KV related.

- dispatch_unquantized_gemm gained a linear_backend argument and is called
  with it. The CPU-forcing test stubs took no arguments; they now accept
  and ignore whatever upstream passes so a further signature change does
  not break them again.

- VocabParallelEmbedding gained a fused kernel that engages whenever
  tp_size > 1 on CUDA, and it has no CPU implementation. The encoder TP
  fixtures drive CPU tensors under a faked TP group, so they pin the
  portable masked path.

- The pyav video decoder backend was removed; valid options are now
  opencv, torchcodec, pynvvideocodec and deepstream. The OpenAI video
  endpoint hardcoded pyav, which turned every video request into a 400.
  It moves to opencv, the upstream default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146Xu5Sgj4vXxLxvLjV5YPj
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouamandClaude Opus 5 c306445ff2 rebase: migrate diffusion KV cache to the vLLM 0.29 layout model
vLLM 0.29 removed AttentionSpec.indexes_kv_by_block_stride and reshaped
KVCacheTensor from (size, shared_by) to an explicit layout: layers in L
order plus layer_stride and block_stride. The block-stride decision now
lives on one physical CacheConfig.kv_cache_layout that the engine core
resolves once, and layout.is_block_outermost is the direct successor of
the old per-spec flag.

Diffusion backends subclass the Omni AttentionBackend ABC, so upstream
get_supported_kv_cache_layouts never sees them and the engine core never
resolves a layout for them. Add diffusion_kv/layout.py to pin it before
get_kv_cache_configs runs, defaulting to LBNHC, which reproduces the
indexes_kv_by_block_stride=False that every dense diffusion backend
declared. The backend hook is kept as an enforced contract rather than
deleted: a backend needing block-stride indexing selects a block-outermost
layout, and a contradiction raises instead of mis-addressing K/V.

Workers receive only a KVCacheConfig, so the resolved name travels on it
(KVCacheConfig.kv_cache_layout) and is adopted into each worker CacheConfig
before allocation. Without that, init_kv_cache raises in the worker because
get_resolved_kv_cache_layout has nothing to return.

init_kv_cache also lost attn_groups and cache_dtype in 0.29; the production
caller in model_runner_backend and the test stubs move to the six-argument
form.

Tensor construction goes through one derivation built on the upstream
compute_layout_strides helper, shared by production and tests, so a wrong
stride cannot be masked by a parallel test-only copy.

Also updates the sizing expectation: 0.29 subtracts the null block that
BlockPool permanently holds back before estimating, so sixteen physical
pages now yield fifteen usable ones.

One review point is addressed only in part, deliberately.
build_native_kv_cache_configs now takes indexes_kv_by_block_stride and
propagates a block-outermost layout end to end when it is set, with a test
covering control plane to worker adoption. Automatic discovery of that
requirement from the workers is not added: 0.29 removed the field that used
to carry it, every diffusion backend in tree declares False, and inventing
an executor RPC for a capability nothing requests would ship untestable
plumbing. Until a backend needs it, the seam is explicit and the mismatch
raises at worker init rather than corrupting addressing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0146Xu5Sgj4vXxLxvLjV5YPj
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:53 +08:00
tzhouam f9879239ba chore: satisfy changed-file policy
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:52 +08:00
tzhouam 87a1fd8da2 rebase: adapt to vLLM v0.29 APIs
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:52 +08:00
tzhouam 6a63f5cb99 rebase: initialize vLLM v0.29 alignment
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
2026-09-10 00:41:52 +08:00
hyw 6fb7b0a05e [Model] Skip unused frame packing in Wan2.2 S2V (#7155)
Signed-off-by: hyw <yuweih205@gmail.com>
2026-09-09 14:34:46 +00:00
andyluo7 b9932aa17b [CI/Build][ROCm] Normalize SenseNova paged-decode hardware markers (#6935)
Signed-off-by: andyluo7 <andy.luo@amd.com>
2026-09-09 07:32:38 -07:00
shiyichuanandmershi 06f67695f1 [CI/Build] Avoid duplicate stage CLI deploy config (#7007)
Signed-off-by: mershi <mershi@tencent.com>
Co-authored-by: mershi <mershi@tencent.com>
2026-09-09 22:05:44 +08:00
andyluo7andHongsheng Liu c94cf4ddde [CI][ROCm] Match CUDA/NPU L2/L3 label routing (#6966)
Signed-off-by: andyluo7 <andy.luo@amd.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
2026-09-09 13:47:15 +00:00
0e602509aa [BugFix] Add field validators for /v1/audio/generate request (#4741)
Signed-off-by: Shaun Walsh <shaunwalsh24@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Nick Cao <ncao@redhat.com>
2026-09-09 13:38:42 +00:00
kunkun 2f1845fa4e fix: reserve Qwen3-Omni NVFP4 backend fix (#7200)
Signed-off-by: kunkunblueberry <1833921874@qq.com>
2026-09-09 20:35:16 +08:00
Zheng Wengangandamy-why-3459 7c1b1e4189 [Bugfix][Engine] Group overlapping device stages into one sequential init component (#7328)
Signed-off-by: ZhengWG <zwg0606@gmail.com>
Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
2026-09-09 20:25:41 +08:00
zyz111222 6e79d582e3 [Bugfix][Ascend] fix npu 310/a5 bugs (#6685)
Signed-off-by: zouyizhou <zouyizhou@huawei.com>
2026-09-09 19:03:09 +08:00
NumberWan 038c9b948f [Bugfix] Resolve #6931 hub FA3 on torch 2.13 via kernels 0.16.1 (#7185)
Signed-off-by: NumberWan <wantszkin2003@gmail.com>
2026-09-09 10:20:34 +00:00
Bo Li cdd4638b17 [Feature][Diffusion] Expose detailed pipeline timings (#6822)
Signed-off-by: Bo Li <22713281+bobboli@users.noreply.github.com>
2026-09-09 09:58:42 +00:00
Anjie HouandClaude Sonnet 5 ecdd762ff5 [2/N] Add a minimal temporal chunk callback for MiniMax-H3 (#7017)
Signed-off-by: specture724 <specture724@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 16:40:32 +08:00
Mu GuanLin ffa991b24f [Perf][Model] Add optimized LTX-2.5 DiffVAE operators (#7308)
Signed-off-by: mglyn <1203789601@qq.com>
2026-09-09 08:31:16 +00:00
chi030303 c66df06c0f [Doc] Qwen3-TTS: add 0.6B on 1x A100 40GB (#7289)
Signed-off-by: chi030303 <106855944+chi030303@users.noreply.github.com>
2026-09-09 07:56:14 +00:00
Sparks 7712701578 [Doc] [Recipe] Add FLUX.1-schnell recipe for RTX 5090 32GB (#7299)
Signed-off-by: Sparks-M <41097544+Sparks-M@users.noreply.github.com>
2026-09-09 07:54:46 +00:00
Mu GuanLin ab56148505 [Bugfix] Make LTX vocoder decoding deterministic (#7231)
Signed-off-by: mglyn <1203789601@qq.com>
2026-09-09 06:37:01 +00:00
NATURE 5b002727b0 [Core] Split Omni connector model runner mixin (#6903)
Signed-off-by: natureofnature <wzliu@connect.hku.hk>
2026-09-09 13:49:43 +08:00
andyluo7 e0d0879794 [Bugfix][Magi2] Allow import without an active Triton driver (#7239)
Signed-off-by: andyluo7 <andy.luo@amd.com>
2026-09-08 22:48:08 -07:00
wangyu 09b720b0d2 [CI] Skip Qwen3-Omni Server VAD multi-turn realtime test (#7279) (#7314)
Signed-off-by: wangyu <410167048@qq.com>
2026-09-09 12:23:29 +08:00
xutianle 968f3e379c [Bugfix][CosyVoice3] Resolve hash snapshot pipeline (#6896)
Signed-off-by: xutianle <xutianle@fudan.edu.cn>
2026-09-08 20:33:38 -07:00
Allen WuandTRAE CLI f2e30f46b6 [Bugfix][TTS] Isolate shared Higgs v3 reference encode from request cancellation (#7076)
Signed-off-by: Allen Wu <allenwu2795@gmail.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
2026-09-08 20:32:05 -07:00
Jim Ban 358b7d79c1 [Bugfix][Frontend] Enforce image pixel limits for video input references (#6963)
Signed-off-by: BANANASJIM <bananasjim1@gmail.com>
2026-09-08 20:31:54 -07:00
Ziming Wang 43b697541a [Bugfix][Frontend] Clear the P0 multimodal cache through the renderer (#7003)
Signed-off-by: ZenAlexa <zimingwang945@gmail.com>
2026-09-08 20:31:42 -07:00
2bae550c04 [Frontend] Opt-in WebSocket TTS split_granularity and session seed (#7046)
Signed-off-by: rk9595 <rakesh.kariya@somaiya.edu>
Signed-off-by: Rakesh Kariya <rakesh.kariya@somaiya.edu>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 19:15:59 -07:00
zhengjiaandCursor 81c1c460b3 [Bugfix][Frontend] Accept the msgpack-numpy package's numpy markers on the OpenPI endpoint (#6051)
Signed-off-by: zjli2013 <leezhengjiang@126.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-08 19:15:48 -07:00
NancyFyongandNancyFyong e74fe3c6e8 [Model] Add Audio8 TTS Preview 0.6B (DualAR, 44.1 kHz codec) (#6157)
Signed-off-by: NancyFyong <NancyFyong@users.noreply.github.com>
Co-authored-by: NancyFyong <NancyFyong@users.noreply.github.com>
2026-09-08 19:00:25 -07:00
boatman cdf027874c [Feat][OmniVoice]Support Varlen Attn, Request-Batch and Step-Execution (#6408)
Signed-off-by: boatman <109857087+sphinxkkkbc@users.noreply.github.com>
2026-09-08 18:58:38 -07:00
eval d7678e2bb3 [Bugfix][MiniCPM-o] Fix the audio_embeds input path (#5730)
Signed-off-by: eval-dev <0xe5bca0@gmail.com>
Signed-off-by: eval <74645252+eval-dev@users.noreply.github.com>
2026-09-08 18:41:42 -07:00
Zhichao Zhang 5078c7ad66 [Bugfix][MiMo-Audio] Align code2wav decode with tokenizer device (#6539)
Signed-off-by: chaosansui <zzc15560846421@163.com>
Signed-off-by: Zhichao Zhang <60429419+smartDream-chao@users.noreply.github.com>
2026-09-08 18:38:11 -07:00
Hongsheng Liu f67cc52f74 [Doc] Add AI usage policy for contributions (#7305)
Signed-off-by: hsliuustc0106 <liuhongsheng4@huawei.com>
2026-09-09 09:17:24 +08:00
Anjie HouandClaude Opus 5 b6f4df320d [Refactor][Diffusion] Resolve offload topology through one plan resolver (#7209)
Signed-off-by: specture724 <specture724@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 08:17:38 +08:00
Tianyao Wu b3dd45874a [Bugfix] Build the forced-aligner prompt without a chat template (word timestamps one bin late) (#7240)
Signed-off-by: Tianyao Wu <rayroy31@gmail.com>
2026-09-08 19:27:38 +00:00
Lei KeandKrystalRay 5e4baea64d [Bugfix][NPU] Limit MiniMax H3 modulation grid size (#6794)
Signed-off-by: KrystalRay <keeleiray@gmail.com>
Co-authored-by: KrystalRay <keeleiray@gmail.com>
2026-09-08 09:08:46 -07:00
fdaf4e31bd Add vLLM-Omni library info to Hugging Face Hub requests (#5381)
Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 10:08:38 -06:00
liangmenghuangandClaude Fable 5.1 7a80b79698 [Bugfix][Model] GR00T-N1.7: honor the per-request seed for flow-matching noise (#7253)
Signed-off-by: liangmengh <liangmengh@nvidia.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 15:53:20 +00:00
andyluo7 de1b0708bd [CI][ROCm] Route LTX2 Ulysses parity to two-GPU lane (#7234)
Signed-off-by: andyluo7 <andy.luo@amd.com>
2026-09-08 22:59:37 +08:00
Hongsheng Liu 7422a85cc7 [Bugfix] Skip HWR store-size scans when no limit is configured (#7131)
Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com>
2026-09-08 22:54:49 +08:00
YancyandAsthenia a31690041d [Bugfix][Examples] Use --profiler-config flag in offline TTS examples (#6763)
Signed-off-by: Asthenia <asthenia0412@gmail.com>
Co-authored-by: Asthenia <asthenia0412@gmail.com>
2026-09-08 13:56:27 +00:00
Honghan Zhuandamy-why-3459 d59d166485 [Bugfix] Preserve additional_config for LLM stages (#7272)
Signed-off-by: zhuhh97 <zhuhonghan@huawei.com>
Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
2026-09-08 20:44:49 +08:00
Shuolei Wang 5378b77be4 [Model][JoyAI-VL-Interaction] Add native multi-stage pipeline (#5352)
Signed-off-by: Shuolei Wang <shuoleiwang123@gmail.com>
2026-09-08 05:07:08 -07:00
liangmenghuangandClaude Fable 5.1 d7740336ff [Bugfix][Model] GR00T-N1.7: honor processor_config image pipeline (use_albumentations / crop_fraction / letter_box_transform) to match Isaac-GR00T (#7083)
Signed-off-by: liangmengh <liangmengh@nvidia.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:59:01 +08:00
LHXuuu c9c25b33c3 [Frontend] Add server-side VAD for turn-based omni models (#6618)
Signed-off-by: LHXuuu <xulianhao.xlh@antgroup.com>
2026-09-08 03:17:00 -07:00
Alicia 031612f250 [Bugfix] Fix video benchmark timeout and failure accounting (#7259)
Signed-off-by: Alicia <115451386+congw729@users.noreply.github.com>
2026-09-08 09:40:08 +00:00
andyluo7andwangyu b58ff5cb8b [CI/Build] Wait for duplex reaper recovery in retry test (#7225)
Signed-off-by: andyluo7 <andy.luo@amd.com>
Co-authored-by: wangyu <53896905+yenuo26@users.noreply.github.com>
2026-09-08 17:10:51 +08:00
tlysanhuo 7c2e30afd3 [Bugfix] Bound omni benchmark per-request timeout to 15 min by default (#7130)
Signed-off-by: tly <2200895168@qq.com>
2026-09-08 02:00:25 -07:00
Chenguang Zheng f3fabe4145 [Bugfix] Keep --disable-log-stats out of stage config resolution (#7237)
Signed-off-by: Chenguang ZHENG <645327136@qq.com>
2026-09-08 16:19:07 +08:00
Honghan Zhu c086554d1f [Perf]Boogu-Image Skip dense attention masks (#6871)
Signed-off-by: zhuhh97 <zhuhonghan@huawei.com>
2026-09-08 00:53:20 -07:00
wuli666 c704aeccae [Bugfix] Fix Step-Audio2 L4 CI KV cache allocation (#6839)
Signed-off-by: wuli666 <421774554@qq.com>
2026-09-08 14:17:33 +08:00
Chenguang Zheng 087ac46318 [Refactor 1/N] Centralize Omni config resolution (#5140)
Signed-off-by: Chenguang ZHENG <645327136@qq.com>
2026-09-08 10:30:26 +08:00
Deep Shahandamy-why-3459 1413eaab7c [Bugfix][Qwen3-Omni] Read code-predictor RoPE theta from rope_parameters (#7228)
Signed-off-by: Deep Shah <deep@socratic.co>
Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
2026-09-08 10:25:27 +08:00
ZacheryAU b1ec136bd7 [Frontend] Add omni benchmark support for image and video endpoints (#4728) 2026-09-08 09:28:21 +08:00
Yueqian Lin e1c59edec6 [Refactor] fish_speech: vendor DAC codec modules, drop external fish-speech dependency (#4871)
Signed-off-by: Yueqian Lin <linyueqian@outlook.com>
2026-09-08 09:22:36 +08:00
boatman 043f03d576 [Migrate]Move model-specific helpers out of serving_speech (#6697)
Signed-off-by: boatman <109857087+sphinxkkkbc@users.noreply.github.com>
2026-09-08 08:59:45 +08:00
Jierui (Jerry) Xu c3498fb869 [Model] Add Boogu-Image Turbo pipeline resolution (#6961)
Signed-off-by: Jerry Xu <xjr2423@gmail.com>
2026-09-08 08:59:23 +08:00
6942a5b600 [1/N] Stream Wan VAE chunks to the media consumer (#7016)
Signed-off-by: specture724 <specture724@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
2026-09-08 07:41:13 +08:00
Rahul SteigerandNils Hoffmann 3b335814cc [Bugfix][Cosmos3] Fix distributed Transfer output envelope (#7205)
Signed-off-by: Rahul Steiger <rsteiger@nvidia.com>
Co-authored-by: Nils Hoffmann <nhoffmann@nvidia.com>
2026-09-08 07:34:08 +08:00
cf36f5e474 [Bugfix][MiniCPM-o] Allow ragged audio_feature_lens across a batch (#7071)
Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Co-authored-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
2026-09-07 23:51:13 +08:00
Lei KeandKrystalRay 7be014bce6 [Bugfix][NPU] Fix MiniMax-H3 INT8 quantization dispatch (#6876)
Signed-off-by: KrystalRay <keeleiray@gmail.com>
Co-authored-by: KrystalRay <keeleiray@gmail.com>
2026-09-07 14:50:44 +00:00
汪志鹏 9b6a308914 [Perf][Diffusion] Keep LoRA weights resident across activation cycles (#7195)
Signed-off-by: princepride <wangzhipeng628@gmail.com>
2026-09-07 14:27:24 +00:00
WeiQing Chen 30d6a0b4e6 [Bugfix][MiniMax-H3] Stabilize keyframe VAE encoding (#7191)
Signed-off-by: david6666666 <530634352@qq.com>
Signed-off-by: David Chen <530634352@qq.com>
2026-09-07 21:52:54 +08:00
Zeyu Huang | 黃澤宇andAlicia 4b96c57730 [skip ci][Doc] Module design doc for diffusion runtime (#6440)
Signed-off-by: Huang, Zeyu <11222265+fhfuih@users.noreply.github.com>
Co-authored-by: Alicia <115451386+congw729@users.noreply.github.com>
2026-09-07 21:52:24 +08:00
Mu GuanLin 3204a0b2ad [Model][Perf] Optimize LTX Ulysses sequence parallelism (#7079)
Signed-off-by: mglyn <1203789601@qq.com>
2026-09-07 10:48:26 +00:00
Rahul SteigerandRahul Steiger f080f41e03 [Bugfix][Cosmos3] Auto-pad non-divisible Ulysses GEN sequences (#6918)
Signed-off-by: Rahul Steiger <rsteiger@nvl72d188-T17.cm.cluster>
Co-authored-by: Rahul Steiger <rsteiger@nvl72d188-T17.cm.cluster>
2026-09-07 08:58:28 +00:00
FredHuangNV 85b0bad2d3 [Frontend][Model] Add Cosmos3 control uploads to video API (#7027)
Signed-off-by: Fred Huang <frhuang@nvidia.com>
2026-09-07 01:38:56 -07:00
677265b64b [Bugfix][Diffusion] Make Cosmos3 transfer image arrays writable (#6915)
Signed-off-by: Rahul Steiger <rsteiger@nvl72d179-T17.cm.cluster>
Co-authored-by: Rahul Steiger <rsteiger@nvl72d179-T17.cm.cluster>
Co-authored-by: Rahul Steiger <rsteiger@aws-cmh-slurm-1-vscode-04.cm.cluster>
2026-09-07 01:06:16 -07:00
Shenglei FuandClaude 91dfafcaab [Bugfix][Diffusion] Include guidance_scale_2_provided in the request-batch key (#7078)
Signed-off-by: Shenglei Fu <sfu@confluent.io>
Co-authored-by: Claude <noreply@anthropic.com>
2026-09-07 15:58:19 +08:00
summerandAlicia 4b81f692d1 [Bugfix][Diffusion] Canonicalize cache_backend=None to "none" (#7041)
Signed-off-by: summer <128961079+zhang-keliang@users.noreply.github.com>
Co-authored-by: Alicia <115451386+congw729@users.noreply.github.com>
2026-09-07 15:56:51 +08:00
WeiQing Chen b78c31eda6 [Doc] Update vLLM-Omni WeChat QR code (#7178)
Signed-off-by: David Chen <530634352@qq.com>
2026-09-07 06:28:30 +00:00
Lucas Ruan 34aa1d2afa [Doc] Add A100 recipe for Wan2.2-TI2V-5B (#7189)
Signed-off-by: lucasruan1618 <huy40025071@gmail.com>
2026-09-07 14:09:32 +08:00
BruceLoveDecimal 5cd925631c [Feature][LingBot World]stepwise execution (#6844)
Signed-off-by: BruceLoveDecimal <liuqihao970610@gmail.com>
Signed-off-by: liuqihao <liuqihao970610@gmail.com>
2026-09-07 13:38:23 +08:00
NumberWanandAlicia 7f32c326c6 [BugFix][Qwen-Image-Edit] restore txt_seq_lens RoPE width after #3588 regression (#5586)
Signed-off-by: NumberWan <wantszkin2003@gmail.com>
Co-authored-by: Alicia <115451386+congw729@users.noreply.github.com>
2026-09-07 12:10:47 +08:00
linzhenpl07andClaude Sonnet 5 38abc0ba28 [AR-Diffusion] Allocate K and V separately so the compiled path stops cloning the pool (#6463)
Signed-off-by: linzhenpl07 <linzhenpl07@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 11:40:20 +08:00
Zeyu Huang | 黃澤宇 2df95de421 [Core][Diffusion] Refactor prompt updates into a modality-ready interaction framework (#6294)
Signed-off-by: Huang, Zeyu <11222265+fhfuih@users.noreply.github.com>
2026-09-07 11:40:04 +08:00
汪志鹏 eeee257913 [Bugfix][Diffusion] Restore the TRTLLM attention default for the MiniMax-H3 modular alias (#7162)
Signed-off-by: princepride <wangzhipeng628@gmail.com>
2026-09-07 07:21:21 +08:00
Xiaobaixia 615f97b9c7 [Feature] Define the runner preprocess phase contract and regression coverage (#7136)
Signed-off-by: xiaobaixia222 <289982752@qq.com>
2026-09-06 17:20:04 +00:00
NATUREandamy-why-3459 ff6e906a25 [Model] Preserve MiniCPM-o Talker full attention until capacity (#6799)
Signed-off-by: natureofnature <wzliu@connect.hku.hk>
Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
2026-09-06 22:25:35 +08:00
Hongsheng Liu 715b8b8746 [Model] MiniMax-H3: validate versioned text-conditioning handoff (#6720)
Signed-off-by: hsliuustc0106 <liuhongsheng4@huawei.com>
2026-09-06 21:45:10 +08:00
SYLARandhsliuustc0106 14d2d00536 [Bugfix] Fix Hunyuan Image-3 expert mapping contract (#7021)
Signed-off-by: hsliuustc0106 <liuhongsheng4@huawei.com>
Co-authored-by: hsliuustc0106 <liuhongsheng4@huawei.com>
2026-09-06 21:43:37 +08:00
汪志鹏andClaude Opus 5 af74a5a155 [Feature][MiniMax-H3] Support the full LightX2V Turbo LoRA matrix (#7062)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 20:23:36 +08:00
XenorynandClaude 039808e0d9 [Perf][Diffusion] Skip the Ulysses length all-gather when shards are padded equal (#5717)
Signed-off-by: Xenoryn <timberh2000@outlook.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-09-06 16:23:30 +08:00
Mengjie ZhaoandCursor e8526cc552 [CI/Build] Stabilize Qwen3-TTS speaker-embedding tests against miss-EOS flake (#6924)
Signed-off-by: Mengjie Zhao <zmj0129@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-06 07:44:23 +00:00
DMAN 06541d1f52 [Model] FP8 Support For Boogu-Image (#6925)
Signed-off-by: Dmaner <2663515256@qq.com>
2026-09-06 04:48:38 +00:00
Weiming LiaoandGao Han 1cd92553cf [CI][Bugfix] Use the matching checkpoint for Qwen3-TTS VoiceDesign tests (#7054)
Signed-off-by: Weiming Liao <liaowm5@gmail.com>
Signed-off-by: Gao Han <hgaoaf@connect.ust.hk>
Co-authored-by: Gao Han <hgaoaf@connect.ust.hk>
2026-09-06 04:27:21 +00:00
prettygirlisnotme 32370968d0 [Model] Add Boogu-Image Turbo native DMD path (#6699)
Signed-off-by: Dragonborn <1078757180@qq.com>
2026-09-06 08:44:15 +08:00
Max Freedom Pollard 946b843e61 [Doc] Fix markdownlint findings in the serving API reference (#7111)
Signed-off-by: Max Freedom Pollard <272618364+MaxFreedomPollard@users.noreply.github.com>
2026-09-06 08:40:10 +08:00
f26f9c3a7f [Diffusion] Implement paged AR↔DiT KV connector ("v1", reusing vllm's native mooncake) (#6310)
Signed-off-by: asukaqaq-s <1311722138@qq.com>
Signed-off-by: Huang, Zeyu <11222265+fhfuih@users.noreply.github.com>
Co-authored-by: asukaqaq-s <1311722138@qq.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
2026-09-06 05:41:54 +08:00
SYLAR ea3cf99894 [diffusion][feature] Add component-selective offload policies (#5929)
Signed-off-by: lishunyang12 <lishunyang12@163.com>
2026-09-06 05:40:09 +08:00
Samit 44d3ae100a [Bugfix][Diffusion] Fuse distilled LoRA weights before HSDP sharding (#6948)
Signed-off-by: samithuang <285365963@qq.com>
Signed-off-by: Samit <285365963@qq.com>
2026-09-06 01:29:59 +08:00
汪志鹏andClaude Opus 5 ae70479619 [Bugfix] Drop the removed diffusion_batch_size kwarg from the stage-init test (#7101)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 17:05:33 +00:00
Zhou TaichangandClaude Fable 5 142151fef5 Parallel stage initialization (admission + SH/EX device locks) (Worker refactor 1/N) (#5224)
Signed-off-by: Taichang Zhou <tzhouam@connect.ust.hk>
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-05 22:22:41 +08:00
5d7fdf9350 [Core] Graduate MiniCPM-o 4.5 and PersonaPlex full-duplex serving out of experimental (#6196)
Signed-off-by: chickeyton <ngton2014@gmail.com>
Signed-off-by: Gao Han <hgaoaf@connect.ust.hk>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Gao Han <hgaoaf@connect.ust.hk>
2026-09-05 08:20:21 +00:00
Hongsheng Liu d4096e01fe [Model] Add native MAGI-2 Preview diffusion support (#5918)
Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com>
Signed-off-by: hsliuustc0106 <liuhongsheng4@huawei.com>
2026-09-05 15:32:30 +08:00
vOv 13c617a140 [Model] Add sequence parallelism to SANA-Video 2B (#5940)
Signed-off-by: cr-gao <gaochenrui@sjtu.edu.cn>
2026-09-05 15:22:53 +08:00
boatman 66bbebd9d3 [Bugfix] Fix fallback loading for IndexTTS external models (#6137)
Signed-off-by: boatman <109857087+sphinxkkkbc@users.noreply.github.com>
2026-09-04 19:58:33 -07:00
BruceLoveDecimalandClaude Opus 5 1e74807c35 [Feature][Moss-TTS] Reference Audio Preprocessing (#4982)
Signed-off-by: BruceLoveDecimal <39156883+BruceLoveDecimal@users.noreply.github.com>
Signed-off-by: liuqihao <liuqihao970610@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:36:26 -07:00
akshatvishuandandyluo7 ae57e406fd [Perf][CI] Use Whisper on GPU when memory permits (#5675)
Signed-off-by: akshatvishu <akshatnayak197@gmail.com>
Signed-off-by: andyluo7 <andy.luo@amd.com>
Co-authored-by: andyluo7 <andy.luo@amd.com>
2026-09-04 16:35:48 -07:00
LOGO127 b6b992ac7b [Bugfix] Fix Higgs Audio v3 voice-clone token validation (#7065)
Signed-off-by: luozijian <luozijian0924@gmail.com>
2026-09-04 16:06:47 -07:00
Inesh Reddy ChappidiandGao Han 66d938100b [Bugfix] Give exact-shape codec models an unpadded input_ids view (#6775)
Signed-off-by: Inesh Reddy <ineshreddy249@gmail.com>
Signed-off-by: IneshReddy249 <ineshreddy249@gmail.com>
Co-authored-by: Gao Han <hgaoaf@connect.ust.hk>
2026-09-04 15:28:49 -07:00
boatman 348fd0fc07 [Feat][dots.tts] Add Online Serving Support (#6235)
Signed-off-by: boatman <109857087+sphinxkkkbc@users.noreply.github.com>
2026-09-04 14:44:10 -07:00
TriggerandGao Han ea14b04e9f [Bugfix][BAGEL] Restore original denoise step count (#7049)
Signed-off-by: Trigger <Meng.Bo.Wang@outlook.com>
Signed-off-by: Gao Han <hgaoaf@connect.ust.hk>
Co-authored-by: Gao Han <hgaoaf@connect.ust.hk>
2026-09-04 17:05:05 +00:00
Weiming Liao 0f9ee6afd8 [CI][Bugfix] Normalize spoken units in text comparison (#6947)
Signed-off-by: Weiming Liao <liaowm5@gmail.com>
2026-09-04 15:18:01 +00:00
SYLARandhsliuustc0106 22cefe04b4 [Bugfix] Update Hub kernel dependency floor (#7020)
Signed-off-by: hsliuustc0106 <liuhongsheng4@huawei.com>
Co-authored-by: hsliuustc0106 <liuhongsheng4@huawei.com>
2026-09-04 22:15:55 +08:00
SYLARandhsliuustc0106 7493cbd3c2 [Bugfix] Fix Qwen3-Omni MoE backend selection (#7019)
Signed-off-by: hsliuustc0106 <liuhongsheng4@huawei.com>
Co-authored-by: hsliuustc0106 <liuhongsheng4@huawei.com>
2026-09-04 21:16:58 +08:00
MikeyDong1 41f4f9d8c9 [Perf][DreamZero] Remove redundant CUDA compile guard (#6983)
Signed-off-by: MikeyDong1 <xdong-1219@outlook.com>
2026-09-04 09:01:05 -04:00
heyuanliu-intel 10701e1838 [Bugfix] Release diffusion worker RPC results on ranks that do not reply (#6989)
Signed-off-by: heyuanliu-intel <heyuan.liu@intel.com>
2026-09-04 16:45:17 +08:00
Shenglei FuandClaude Opus 4.8 0e903e9695 [Diffusion] Enable request-level batching for Boogu-Image (T2I) (#6968)
Signed-off-by: Shenglei Fu <117230642+ShengleiFu@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-04 16:27:51 +08:00
Mu GuanLin 175dec11be [Perf][Model] Optimize LTX video output transport (#7000)
Signed-off-by: mglyn <1203789601@qq.com>
2026-09-04 06:47:28 +00:00
8d98cc0cb7 [Benchmark] Add local OmniInteract performance cases (#6817)
Signed-off-by: natureofnature <wzliu@connect.hku.hk>
Co-authored-by: Ruirui Yang | Rein <73573651+R2-Y@users.noreply.github.com>
Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
2026-09-04 10:36:02 +08:00
Yuekai ZhangandClaude Fable 5 4179b0cae1 [Perf][NemotronVoiceChat] Fast defaults for all three deploy profiles: 2.8x offline, realtime full-duplex audio (#6354)
Signed-off-by: Yuekai Zhang <zhangyuekai@foxmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-03 18:49:19 -07:00
Trigger e43c787544 [Model] Add BAGEL step execution and continuous batching (#6359) 2026-09-04 01:36:11 +00:00
Bo Li fda3af8bbc [Bugfix] Preserve BF16 residual semantics in fused MiniMax-H3 modulation (#6878)
Signed-off-by: Bo Li <22713281+bobboli@users.noreply.github.com>
2026-09-04 02:01:34 +08:00
948 changed files with 77593 additions and 18718 deletions

No files matched your search

+78 -88
View File
@@ -14,6 +14,8 @@ set -euo pipefail
# shellcheck disable=SC1091
source .buildkite/common/scripts/resolve_skip_ci.sh
AMD_SUITE_SELECTOR=".buildkite/amd/scripts/select_test_suites.py"
if [[ -z "${RUN_ALL:-}" ]]; then
RUN_ALL=0
fi
@@ -30,31 +32,20 @@ if [[ -z "${AMD_MIRROR_HW:-}" ]]; then
AMD_MIRROR_HW="amdproduction"
fi
fail_fast() {
DISABLE_LABEL="ci-no-fail-fast"
# If BUILDKITE_PULL_REQUEST != "false", then we check the PR labels using curl and jq
if [ "$BUILDKITE_PULL_REQUEST" != "false" ]; then
PR_LABELS=$(curl -s "https://api.github.com/repos/vllm-project/vllm-omni/pulls/$BUILDKITE_PULL_REQUEST" | jq -r '.labels[].name')
if [[ $PR_LABELS == *"$DISABLE_LABEL"* ]]; then
echo false
else
echo true
fi
else
echo false # not a PR or BUILDKITE_PULL_REQUEST not set
PR_LABELS=""
# Explicit debug suites do not depend on PR labels.
if [[ "${BUILDKITE_PULL_REQUEST:-false}" != "false" && -z "${DEBUG_TEST_YAML:-}" ]]; then
if ! PR_LABELS=$(curl -fsSL \
"https://api.github.com/repos/vllm-project/vllm-omni/pulls/$BUILDKITE_PULL_REQUEST" \
| jq -r '.labels[].name'); then
echo "ERROR: Could not read PR labels; refusing to select an incorrect AMD test tier." >&2
exit 1
fi
}
fi
check_run_all_label() {
RUN_ALL_LABEL="ready-run-all-tests"
# If BUILDKITE_PULL_REQUEST != "false", then we check the PR labels using curl and jq
if [ "$BUILDKITE_PULL_REQUEST" != "false" ]; then
PR_LABELS=$(curl -s "https://api.github.com/repos/vllm-project/vllm-omni/pulls/$BUILDKITE_PULL_REQUEST" | jq -r '.labels[].name')
if [[ $PR_LABELS == *"$RUN_ALL_LABEL"* ]]; then
echo true
else
echo false
fi
fail_fast() {
if [[ "${BUILDKITE_PULL_REQUEST:-false}" != "false" ]]; then
echo true
else
echo false # not a PR or BUILDKITE_PULL_REQUEST not set
fi
@@ -64,6 +55,57 @@ if [[ -z "${COV_ENABLED:-}" ]]; then
COV_ENABLED=0
fi
resolve_test_specs() {
local selected_specs
if ! selected_specs=$(python3 "$AMD_SUITE_SELECTOR" \
--branch "$BUILDKITE_BRANCH" \
--labels "$PR_LABELS" \
--debug-test-yaml "${DEBUG_TEST_YAML:-}"); then
exit 1
fi
TEST_SPECS=()
while IFS= read -r suite_spec; do
[[ -z "$suite_spec" ]] && continue
TEST_SPECS+=("$suite_spec")
done <<< "$selected_specs"
}
filter_test_specs_by_skip_ci() {
local suite_spec level decision
local skip_all=0
local -a runnable_specs=()
for suite_spec in "${TEST_SPECS[@]}"; do
case "$suite_spec" in
READY_TESTS:*) level="l2" ;;
MERGE_TESTS:*) level="l3" ;;
*)
echo "ERROR: unknown AMD test suite spec '$suite_spec'" >&2
exit 1
;;
esac
if decision=$(python3 "$SKIP_CI_PY" gate amd "$level"); then
echo "Skipping AMD $level suite due to $decision decision."
[[ "$decision" == "skip-all" ]] && skip_all=1
else
runnable_specs+=("$suite_spec")
fi
done
TEST_SPECS=("${runnable_specs[@]}")
if [[ ${#TEST_SPECS[@]} -eq 0 ]]; then
if [[ $skip_all -eq 1 ]]; then
buildkite-agent annotate \
":memo: CI skipped — docs or pytest skip-mark changes only" \
--style "info" 2>/dev/null || true
fi
echo "No AMD L2/L3 suites remain after skip-ci filtering."
exit 0
fi
}
upload_pipeline() {
echo "Uploading pipeline..."
# Install minijinja
@@ -84,7 +126,7 @@ upload_pipeline() {
# (WIP) Use pipeline generator instead of jinja template
if [ -e ".buildkite/amd/pipeline_generator/pipeline_generator.py" ]; then
python -m pip install click pydantic
python .buildkite/amd/pipeline_generator/pipeline_generator.py --run_all=$RUN_ALL --list_file_diff="$LIST_FILE_DIFF" --nightly="$NIGHTLY" --mirror_hw="$AMD_MIRROR_HW"
python .buildkite/amd/pipeline_generator/pipeline_generator.py --run_all="$RUN_ALL" --list_file_diff="$LIST_FILE_DIFF" --nightly="$NIGHTLY" --mirror_hw="$AMD_MIRROR_HW"
buildkite-agent pipeline upload .buildkite/amd/pipeline.yaml
exit 0
fi
@@ -97,49 +139,12 @@ upload_pipeline() {
cd .buildkite/amd
# Select test definition file: merge suite for main, ready suite for PRs.
# For debugging, DEBUG_TEST_YAML accepts a comma-separated list containing
# "merge" and/or "ready" (case-insensitive). Multiple suites are combined
# before rendering so they share one amd-build step.
if [[ -n "${DEBUG_TEST_YAML:-}" ]]; then
declare -a DEBUG_TEST_SPECS=()
declare -A SEEN_DEBUG_TESTS=()
IFS=',' read -ra REQUESTED_DEBUG_TESTS <<< "${DEBUG_TEST_YAML,,}"
for requested_test in "${REQUESTED_DEBUG_TESTS[@]}"; do
# Permit readable values such as "ready, merge," and ignore the
# empty item produced by a trailing comma.
requested_test="${requested_test//[[:space:]]/}"
[[ -z "$requested_test" ]] && continue
if [[ -n "${SEEN_DEBUG_TESTS[$requested_test]:-}" ]]; then
echo "ERROR: duplicate DEBUG_TEST_YAML suite '$requested_test'" >&2
exit 1
fi
SEEN_DEBUG_TESTS[$requested_test]=1
case "$requested_test" in
ready)
DEBUG_TEST_SPECS+=("READY_TESTS:test-amd-ready.yml")
;;
merge)
DEBUG_TEST_SPECS+=("MERGE_TESTS:test-amd-merge.yml")
;;
*)
echo "ERROR: DEBUG_TEST_YAML entries must be 'merge' or 'ready', got '$requested_test'" >&2
exit 1
;;
esac
done
if [[ ${#DEBUG_TEST_SPECS[@]} -eq 0 ]]; then
echo "ERROR: DEBUG_TEST_YAML did not contain a test suite" >&2
exit 1
elif [[ ${#DEBUG_TEST_SPECS[@]} -eq 1 ]]; then
TEST_YAML="${DEBUG_TEST_SPECS[0]#*:}"
else
TEST_YAML=$(mktemp "${TMPDIR:-/tmp}/amd-debug-tests.XXXXXX.yml")
python - "$TEST_YAML" "${DEBUG_TEST_SPECS[@]}" <<'PY'
# Multiple label-selected or debug-selected suites share one image build.
if [[ ${#TEST_SPECS[@]} -eq 1 ]]; then
TEST_YAML="${TEST_SPECS[0]#*:}"
else
TEST_YAML=$(mktemp "${TMPDIR:-/tmp}/amd-selected-tests.XXXXXX.yml")
python - "$TEST_YAML" "${TEST_SPECS[@]}" <<'PY'
import sys
import yaml
@@ -172,13 +177,8 @@ for suite_spec in suite_specs:
with open(output_path, "w", encoding="utf-8") as output_file:
yaml.safe_dump(combined, output_file, sort_keys=False)
PY
fi
echo "DEBUG_TEST_YAML override: using ${DEBUG_TEST_SPECS[*]}"
elif [[ $BUILDKITE_BRANCH == "main" ]]; then
TEST_YAML="test-amd-merge.yml"
else
TEST_YAML="test-amd-ready.yml"
fi
echo "AMD test suites: ${TEST_SPECS[*]}"
(
set -x
@@ -198,7 +198,7 @@ PY
> pipeline.yaml
)
cat pipeline.yaml
if [[ "$TEST_YAML" == "${TMPDIR:-/tmp}/amd-debug-tests."*.yml ]]; then
if [[ "$TEST_YAML" == "${TMPDIR:-/tmp}/amd-selected-tests."*.yml ]]; then
rm -f -- "$TEST_YAML"
fi
buildkite-agent artifact upload pipeline.yaml
@@ -219,12 +219,10 @@ if [[ $BUILDKITE_BRANCH == "main" ]]; then
file_diff=$(get_diff_main)
fi
# Early exit: unified skip-ci (docs / skip marks) and CI-yaml-only level targeting.
if [[ $BUILDKITE_BRANCH == "main" ]]; then
gate_bootstrap_ci amd l3
else
gate_bootstrap_ci amd l2
fi
# Resolve PR tier labels before skip-ci so ready and merge suites can be
# filtered independently for CI-YAML-only changes.
resolve_test_specs
filter_test_specs_by_skip_ci
patterns=(
"docker/Dockerfile"
@@ -274,14 +272,6 @@ for file in $file_diff; do
fi
done
# Check for ready-run-all-tests label
LABEL_RUN_ALL=$(check_run_all_label)
if [[ $LABEL_RUN_ALL == true ]]; then
RUN_ALL=1
NIGHTLY=1
echo "Found 'ready-run-all-tests' label. Running all tests including optional tests."
fi
# Decide whether to use precompiled wheels
# Relies on existing patterns array as a basis.
if [[ -n "${VLLM_USE_PRECOMPILED:-}" ]]; then
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Select AMD L2/L3 suites after Buildkite has created an AMD build.
This repository-side selector does not decide which GitHub label events start
the external ``vllm-omni-amd-ci`` pipeline. Its Buildkite PR build condition
must admit the relevant label event before this code can inspect the labels.
"""
from __future__ import annotations
import argparse
import sys
from collections.abc import Iterable
SUITE_SPECS = {
"ready": "READY_TESTS:test-amd-ready.yml",
"merge": "MERGE_TESTS:test-amd-merge.yml",
}
def _parse_debug_suites(value: str) -> tuple[str, ...]:
suites = tuple(item.strip().lower() for item in value.split(",") if item.strip())
if not suites:
raise ValueError("DEBUG_TEST_YAML did not contain a test suite")
seen: set[str] = set()
for suite in suites:
if suite not in SUITE_SPECS:
raise ValueError(f"DEBUG_TEST_YAML entries must be 'merge' or 'ready', got {suite!r}")
if suite in seen:
raise ValueError(f"duplicate DEBUG_TEST_YAML suite {suite!r}")
seen.add(suite)
return suites
def select_amd_test_suites(
*,
branch: str,
labels: Iterable[str],
debug_test_yaml: str = "",
) -> tuple[str, ...]:
"""Return ordered suite names while preserving legacy unlabeled PRs."""
if debug_test_yaml:
return _parse_debug_suites(debug_test_yaml)
if branch == "main":
return ("merge",)
label_set = {label.strip() for label in labels if label.strip()}
selected = tuple(suite for label, suite in (("ready", "ready"), ("merge-test", "merge")) if label in label_set)
# AMD historically uploaded L2 for every PR build. Keep that behavior for
# triggers such as amd-test while adding tier selection for ready/merge-test.
return selected or ("ready",)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--branch", required=True)
parser.add_argument("--labels", default="")
parser.add_argument("--debug-test-yaml", default="")
args = parser.parse_args()
labels = tuple(args.labels.splitlines())
try:
suites = select_amd_test_suites(
branch=args.branch,
labels=labels,
debug_test_yaml=args.debug_test_yaml,
)
except ValueError as exc:
parser.error(str(exc))
label_set = {label.strip() for label in labels if label.strip()}
if not args.debug_test_yaml and args.branch != "main" and not ({"ready", "merge-test"} & label_set):
print(
"No AMD L2/L3 tier label found; preserving the legacy ready-suite fallback.",
file=sys.stderr,
)
if "nightly-test" in label_set:
print(
"AMD nightly-test has no suite yet and does not affect L2/L3 selection.",
file=sys.stderr,
)
for suite in suites:
print(SUITE_SPECS[suite])
return 0
if __name__ == "__main__":
raise SystemExit(main())
+12 -1
View File
@@ -107,6 +107,11 @@ steps:
- export VLLM_LOGGING_LEVEL=DEBUG
- export VLLM_WORKER_MULTIPROC_METHOD=spawn
- timeout 20m pytest -s -v tests/diffusion/distributed/test_sequence_parallel.py -m core_model
- >-
timeout 20m pytest -s -v
tests/diffusion/models/ltx2/test_ltx2_transformer_ulysses.py
-m 'core_model and rocm and cards_2'
--run-level "core_model"
- label: "Diffusion GPU Worker Test"
agent_pool: mi300_2
@@ -122,7 +127,13 @@ steps:
mirror_hardwares: [amdproduction]
grade: Blocking
commands:
- timeout 15m pytest -sv tests/diffusion/models/ -m 'core_model and cuda' --run-level "core_model"
# Preserve legacy one-GPU tests without cards_1 while excluding every
# registered multi-GPU marker from this one-GPU job.
- >-
timeout 15m pytest -sv tests/diffusion/models/
-m 'core_model and cuda and not
(cards_2 or cards_3 or cards_4 or cards_5 or cards_6 or cards_7 or cards_8)'
--run-level "core_model"
- label: "Diffusion · Wan22 Test"
agent_pool: mi300_1
+66 -12
View File
@@ -17,6 +17,8 @@
# a2b3_npu_1 — ascend-a2b3, resource_class npu-1 (A2/B3 CI image)
# 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)
# a5_npu_2 — ascend-a5, resource_class npu-2
# 310p_npu_1 — ascend-310p, resource_class npu-1
#
# B200 (b200-k8s): matches vLLM device ``b200-k8s`` from ci-infra
# (AgentQueue.B200_K8S + b200_plugin_template). Queue routes the K8s pool;
@@ -26,6 +28,9 @@
# packed onto g6.12xlarge L4 nodes (``vllm.ci/gpu-pool=l4x4``). GPU / CPU /
# memory / shm requests scale with card count (1 GPU = 10 CPU / 40Gi / 24Gi shm).
# Retry covers lost agents / killed pods, not pytest or OOM (exit 1 / 137).
# The Kubernetes HF secret deliberately uses VLLM_CI_HF_TOKEN. Scheduled
# build-level HF_TOKEN values can override pod env entries with the same name;
# upload_pipeline.py restores the secret-backed value inside the job shell.
x-npu-k8s-plugins: &npu_k8s_plugins
- kubernetes:
@@ -84,7 +89,7 @@ mirror_hardwares:
env:
- name: HF_HOME
value: /fsx/hf_cache
- name: HF_TOKEN
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
@@ -135,7 +140,7 @@ mirror_hardwares:
env:
- name: HF_HOME
value: /fsx/hf_cache
- name: HF_TOKEN
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
@@ -186,7 +191,7 @@ mirror_hardwares:
env:
- name: HF_HOME
value: /fsx/hf_cache
- name: HF_TOKEN
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
@@ -237,7 +242,7 @@ mirror_hardwares:
env:
- name: HF_HOME
value: /fsx/hf_cache
- name: HF_TOKEN
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
@@ -271,7 +276,7 @@ mirror_hardwares:
env:
- name: HF_HOME
value: /root/.cache/huggingface
- name: HF_TOKEN
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
@@ -306,7 +311,7 @@ mirror_hardwares:
env:
- name: HF_HOME
value: /root/.cache/huggingface
- name: HF_TOKEN
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
@@ -341,7 +346,7 @@ mirror_hardwares:
env:
- name: HF_HOME
value: /root/.cache/huggingface
- name: HF_TOKEN
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
@@ -376,7 +381,7 @@ mirror_hardwares:
env:
- name: HF_HOME
value: /root/.cache/huggingface
- name: HF_TOKEN
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
@@ -426,7 +431,7 @@ mirror_hardwares:
value: "0"
- name: HF_HOME
value: /raid/hf_cache
- name: HF_TOKEN
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
@@ -477,7 +482,7 @@ mirror_hardwares:
value: "0"
- name: HF_HOME
value: /raid/hf_cache
- name: HF_TOKEN
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
@@ -528,7 +533,7 @@ mirror_hardwares:
value: "0"
- name: HF_HOME
value: /raid/hf_cache
- name: HF_TOKEN
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
@@ -579,7 +584,7 @@ mirror_hardwares:
value: "0"
- name: HF_HOME
value: /raid/hf_cache
- name: HF_TOKEN
- name: VLLM_CI_HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
@@ -646,3 +651,52 @@ mirror_hardwares:
resource_class: "npu-16"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-a3"
plugins: *npu_k8s_plugins
a5_npu_2:
agents:
queue: "ascend-a5"
resource_class: "npu-2"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-a5"
plugins: *npu_k8s_plugins
a5_npu_4:
agents:
queue: "ascend-a5"
resource_class: "npu-4"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-a5"
plugins: *npu_k8s_plugins
a5_npu_8:
agents:
queue: "ascend-a5"
resource_class: "npu-8"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-a5"
plugins: *npu_k8s_plugins
a5_npu_16:
agents:
queue: "ascend-a5"
resource_class: "npu-16"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-a5"
plugins: *npu_k8s_plugins
310p_npu_1:
agents:
queue: "ascend-310p"
resource_class: "npu-1"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-310p"
plugins: *npu_k8s_plugins
310p_npu_2:
agents:
queue: "ascend-310p"
resource_class: "npu-2"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-310p"
plugins: *npu_k8s_plugins
310p_npu_4:
agents:
queue: "ascend-310p"
resource_class: "npu-4"
image: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/vllm-omni-ci-npu:${BUILDKITE_COMMIT}-310p"
plugins: *npu_k8s_plugins
+22 -1
View File
@@ -70,7 +70,15 @@ from tests.helpers.mark import ( # noqa: E402
LOG = "upload_pipeline"
BOOTSTRAP_STEPS_FILENAME = "bootstrap-upload-steps.yml"
BOOTSTRAP_IMAGE_BUILD_KEYS = frozenset({"image-build", "image-build-a2", "image-build-a3"})
BOOTSTRAP_IMAGE_BUILD_KEYS = frozenset(
{
"image-build",
"image-build-a2",
"image-build-a3",
"image-build-a5",
"image-build-310p",
}
)
BOOTSTRAP_UPLOAD_IF_KEYS = {
"upload-ready-pipeline": "ready",
"upload-merge-pipeline": "merge",
@@ -79,6 +87,8 @@ BOOTSTRAP_UPLOAD_IF_KEYS = {
}
E2E_GROUP_MARKER = "E2E Test"
CI_MIRROR_HARDWARES_PATH = ROOT / ".buildkite/common/ci_mirror_hardwares.yml"
CUDA_HF_TOKEN_ENV = "VLLM_CI_HF_TOKEN"
CUDA_HF_TOKEN_EXPORT = f'if [ -n "$${{{CUDA_HF_TOKEN_ENV}:-}}" ]; then export HF_TOKEN="$${{{CUDA_HF_TOKEN_ENV}}}"; fi'
# Bootstrap Buildkite ``if`` expressions.
# ``*_MAIN_IF``: main + env schedule. ``*_LABEL_IF``: PR label (and/or composed with MAIN).
@@ -501,6 +511,17 @@ def _expand_mirror_hardwares(step: dict[str, Any]) -> dict[str, Any] | None:
expanded = copy.deepcopy(preset)
merged = {key: value for key, value in step.items() if key != "mirror_hardwares"} | expanded
# A scheduled build's HF_TOKEN may override a pod env entry with the same
# name. The preset injects the Kubernetes secret under a collision-proof
# alias; restore the conventional name after Buildkite has merged env.
if any(preset_name == chip or preset_name.startswith(f"{chip}_") for chip in _cuda_mirror_chips()):
commands = merged.get("commands")
if isinstance(commands, list):
merged["commands"] = [CUDA_HF_TOKEN_EXPORT, *commands]
elif commands is None:
merged["commands"] = [CUDA_HF_TOKEN_EXPORT]
else:
merged["commands"] = [CUDA_HF_TOKEN_EXPORT, commands]
# Preset retry (K8S_RETRY on l4_*) must not clobber a step that opted out.
if "retry" in step:
merged["retry"] = step["retry"]
+25 -6
View File
@@ -3,7 +3,6 @@
env:
VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_USE_FLASHINFER_MOE_FP16: 0
HF_HUB_DOWNLOAD_TIMEOUT: 300
HF_HUB_ETAG_TIMEOUT: 60
@@ -80,6 +79,9 @@ steps:
steps:
- label: "TTS · Qwen3-TTS CustomVoice Test"
source_file_dependencies:
- tests/e2e/features/prefill_phase/
- vllm_omni/worker/gpu_model_runner.py
- tests/helpers/media.py
- tests/e2e/online_serving/test_qwen3_tts_customvoice.py
- tests/e2e/offline_inference/test_qwen3_tts_customvoice.py
- vllm_omni/model_executor/models/qwen3_tts/
@@ -96,12 +98,13 @@ steps:
timeout 40m bash -c "
export VLLM_LOGGING_LEVEL=DEBUG
export VLLM_ALLOW_LONG_MAX_MODEL_LEN=1
pytest -s -v tests/e2e/online_serving/test_qwen3_tts_customvoice.py tests/e2e/offline_inference/test_qwen3_tts_customvoice.py -m 'advanced_model and cuda' --run-level 'advanced_model'
pytest -s -v tests/e2e/online_serving/test_qwen3_tts_customvoice.py tests/e2e/offline_inference/test_qwen3_tts_customvoice.py tests/e2e/features/prefill_phase/test_chunked_prefill_tail.py -m 'advanced_model and cuda' --run-level 'advanced_model'
"
mirror_hardwares: l4_1
- label: "TTS · Qwen3-TTS Base Test"
source_file_dependencies:
- tests/helpers/media.py
- tests/e2e/online_serving/test_qwen3_tts_base.py
- tests/e2e/offline_inference/test_qwen3_tts_base.py
- vllm_omni/model_executor/models/qwen3_tts/
@@ -162,10 +165,11 @@ steps:
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'
# 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).
# response with Whisper, and tests/helpers/media.py requires at least
# 16 GiB free VRAM on a device to run Whisper on GPU. When the model
# server leaves insufficient memory, Whisper falls back to CPU:
# ~8-10 min per clip vs ~30 s, which overruns the 50-minute job timeout
# (build 2956). h100_2 guarantees a second GPU with ample free VRAM.
mirror_hardwares: h100_2
- label: "Omni · MiniCPM-o 4.5 Duplex Test"
@@ -181,6 +185,21 @@ steps:
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_soft_interrupt.py
- vllm_omni/deploy/minicpmo_4_5.yaml
- tests/e2e/online_serving/helpers/minicpmo_4_5_duplex.py
- vllm_omni/engine/duplex/
- vllm_omni/entrypoints/duplex/
# Public duplex client library + its unit suites: client-side
# changes must re-run the duplex e2e group. The live client probe
# (test_duplex_client_live.py, single GPU, core tier) runs in the
# ready pipeline's duplex group.
- vllm_omni/clients/
- tests/clients/
- vllm_omni/entrypoints/duplex_request_client.py
- vllm_omni/outputs/duplex.py
- vllm_omni/model_executor/duplex_sampling.py
- tests/engine/duplex/
- tests/entrypoints/duplex/
- tests/model_executor/models/minicpmo_4_5/duplex/
# Legacy Realtime probe client + video stacking used by the e2e drivers.
- vllm_omni/experimental/fullduplex/
- vllm_omni/model_executor/models/minicpmo_4_5/
- vllm_omni/model_executor/models/step_audio2/
+6 -1
View File
@@ -4,7 +4,6 @@
env:
VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_USE_FLASHINFER_MOE_FP16: 0
HF_HUB_DOWNLOAD_TIMEOUT: 300
HF_HUB_ETAG_TIMEOUT: 60
@@ -344,6 +343,12 @@ steps:
- pytest -s -v tests/e2e/online_serving/test_hunyuan_video_15_expansion.py -m "full_model and cuda" --run-level "full_model"
mirror_hardwares: h100_2
- label: ":full_moon: Diffusion X2V · MAGI-2 Preview Serving Smoke"
timeout_in_minutes: 180
commands:
- pytest -s -v tests/e2e/online_serving/test_magi2.py -m "advanced_model and diffusion and cuda and H100 and cards_4" --run-level "advanced_model"
mirror_hardwares: h100_4
- label: ":full_moon: Diffusion X2V · Doc Test"
timeout_in_minutes: 60
commands:
+20 -5
View File
@@ -3,7 +3,6 @@
env:
VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_USE_FLASHINFER_MOE_FP16: 0
HF_HUB_DOWNLOAD_TIMEOUT: 300
HF_HUB_ETAG_TIMEOUT: 60
@@ -77,7 +76,7 @@ steps:
- label: "Diffusion · Other Test"
commands:
- timeout 15m pytest -sv tests/diffusion/*.py -m 'core_model and cuda' --run-level "core_model"
- timeout 15m pytest -sv tests/diffusion/*.py tests/diffusion/ar_diffusion -m 'core_model and cuda' --run-level "core_model"
mirror_hardwares: l4_1
- group: ":card_index_dividers: Tiny Model Tests [Single GPU]"
@@ -94,7 +93,7 @@ steps:
commands:
- |
timeout 15m bash -c "
pytest -sv tests/engine/ tests/model_executor/ -m 'core_model and cuda' --run-level 'core_model'
pytest -sv tests/engine/ tests/model_executor/ tests/worker/test_batched_omni_output.py -m 'core_model and cuda' --run-level 'core_model'
"
mirror_hardwares: l4_1
@@ -215,6 +214,21 @@ steps:
- tests/e2e/online_serving/run_minicpmo_realtime_duplex_soft_interrupt.py
- vllm_omni/deploy/minicpmo_4_5.yaml
- tests/e2e/online_serving/helpers/minicpmo_4_5_duplex.py
- vllm_omni/engine/duplex/
- vllm_omni/entrypoints/duplex/
# Public duplex client library + its unit suites: client-side
# changes must re-run the duplex e2e group, which includes the
# live client probe (test_duplex_client_live.py, single GPU).
- vllm_omni/clients/
- tests/clients/
- tests/e2e/online_serving/test_duplex_client_live.py
- vllm_omni/entrypoints/duplex_request_client.py
- vllm_omni/outputs/duplex.py
- vllm_omni/model_executor/duplex_sampling.py
- tests/engine/duplex/
- tests/entrypoints/duplex/
- tests/model_executor/models/minicpmo_4_5/duplex/
# Legacy Realtime probe client + video stacking used by the e2e drivers.
- vllm_omni/experimental/fullduplex/
- vllm_omni/model_executor/models/minicpmo_4_5/
- vllm_omni/model_executor/models/step_audio2/
@@ -232,8 +246,8 @@ steps:
- vllm_omni/config/stage_config.py
commands:
- |
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'
timeout 35m bash -c "
pytest -s -v tests/e2e/online_serving/test_minicpmo_4_5_duplex.py tests/e2e/online_serving/test_duplex_client_live.py -m 'core_model and cuda' --run-level 'core_model'
"
mirror_hardwares: h100_1
@@ -251,6 +265,7 @@ steps:
- label: "TTS · Qwen3-TTS CustomVoice Test"
source_file_dependencies:
- tests/helpers/media.py
- tests/e2e/online_serving/test_qwen3_tts_customvoice.py
- vllm_omni/model_executor/models/qwen3_tts/
- vllm_omni/model_executor/models/common/qwen3_code_predictor.py
-1
View File
@@ -3,7 +3,6 @@
env:
VLLM_WORKER_MULTIPROC_METHOD: spawn
VLLM_USE_FLASHINFER_MOE_FP16: 0
HF_HUB_DOWNLOAD_TIMEOUT: 300
HF_HUB_ETAG_TIMEOUT: 60
+30 -104
View File
@@ -3,10 +3,10 @@
steps:
- label: ":buildkit: Build and Push NPU Test Image - A2"
key: image-build-a2
agents:
agents: &npu-image-build-agents
queue: "ascend-a2b3"
resource_class: "npu-1"
plugins:
plugins: &npu-image-build-plugins
- kubernetes:
metadata:
annotations:
@@ -46,123 +46,47 @@ steps:
IMAGE_NAME: "vllm-omni-ci-npu"
IMAGE_REGISTRY: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry"
BUILDKITD_ADDR: "tcp://buildkitd-service.buildkitd:1234"
command: |
set -ex
echo "--- Building and pushing NPU Test Image"
echo "Image: $${IMAGE_REGISTRY}/$${IMAGE_NAME}:$${VLLM_IMAGE_TAG}"
echo "buildkitd address: $${BUILDKITD_ADDR}"
if ! command -v buildctl &> /dev/null; then
echo "Installing buildctl..."
mkdir -p /tmp/buildkit
BUILDKIT_VERSION="v0.29.0"
wget -q "https://github.com/moby/buildkit/releases/download/$${BUILDKIT_VERSION}/buildkit-$${BUILDKIT_VERSION}.linux-arm64.tar.gz" -O /tmp/buildkit.tar.gz
tar -xzf /tmp/buildkit.tar.gz -C /tmp/buildkit
cp /tmp/buildkit/bin/buildctl /usr/local/bin/
fi
export DOCKER_CONFIG=/home/user/.docker
buildctl \
--addr="$${BUILDKITD_ADDR}" \
--tlscacert=/home/user/.docker/ca.pem \
--tlscert=/home/user/.docker/cert.pem \
--tlskey=/home/user/.docker/key.pem \
build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=./docker \
--opt filename=Dockerfile.npu.ci \
--secret id=dockerconfig,src=/home/user/.docker/config.json \
--output type=image,name=$${IMAGE_REGISTRY}/$${IMAGE_NAME}:$${VLLM_IMAGE_TAG},push=true \
--progress=plain
echo "--- Image pushed successfully"
echo "$${IMAGE_REGISTRY}/$${IMAGE_NAME}:$${VLLM_IMAGE_TAG}"
command: bash .buildkite/npu/scripts/build-npu-image.sh Dockerfile.npu.ci "$${VLLM_IMAGE_TAG}"
- label: ":buildkit: Build and Push NPU Test Image - A3"
key: image-build-a3
agents:
queue: "ascend-a2b3"
resource_class: "npu-1"
plugins:
- kubernetes:
metadata:
annotations:
vault.hashicorp.com/agent-init-first: "true"
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-perms-ca.pem: "0400"
vault.hashicorp.com/agent-inject-perms-cert.pem: "0400"
vault.hashicorp.com/agent-inject-perms-config.json: "0400"
vault.hashicorp.com/agent-inject-perms-key.pem: "0400"
vault.hashicorp.com/agent-inject-secret-ca.pem: internal/data/ascend/buildkitd
vault.hashicorp.com/agent-inject-secret-cert.pem: internal/data/ascend/buildkitd
vault.hashicorp.com/agent-inject-secret-config.json: internal/data/ascend/buildkitd
vault.hashicorp.com/agent-inject-secret-key.pem: internal/data/ascend/buildkitd
vault.hashicorp.com/agent-inject-template-ca.pem: "{{- with secret \"internal/data/ascend/buildkitd\" -}}\n{{ .Data.data.RootCA }}\n{{- end }}"
vault.hashicorp.com/agent-inject-template-cert.pem: "{{- with secret \"internal/data/ascend/buildkitd\" -}}\n{{ .Data.data.ClientCaCert }}\n{{- end }}"
vault.hashicorp.com/agent-inject-template-config.json: "{{- with secret \"internal/data/ascend/buildkitd\" -}}\n{{ .Data.data.dockerConfig }}\n{{- end }}"
vault.hashicorp.com/agent-inject-template-key.pem: "{{- with secret \"internal/data/ascend/buildkitd\" -}}\n{{ .Data.data.ClientCaKey }}\n{{- end }}"
vault.hashicorp.com/agent-pre-populate-only: "true"
vault.hashicorp.com/agent-run-as-group: "1000"
vault.hashicorp.com/agent-run-as-user: "1000"
vault.hashicorp.com/agent-service-account-token-volume-name: token-vol
vault.hashicorp.com/role: ascend-gha-runners
vault.hashicorp.com/secret-volume-path: /home/user/.docker/
vault.hashicorp.com/tls-skip-verify: "true"
podSpecPatch:
volumes:
- name: token-vol
projected:
defaultMode: 420
sources:
- serviceAccountToken:
audience: api
expirationSeconds: 600
path: token
agents: *npu-image-build-agents
plugins: *npu-image-build-plugins
env:
VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}-a3"
IMAGE_NAME: "vllm-omni-ci-npu"
IMAGE_REGISTRY: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry"
BUILDKITD_ADDR: "tcp://buildkitd-service.buildkitd:1234"
command: |
set -ex
command: bash .buildkite/npu/scripts/build-npu-image.sh Dockerfile.npu.ci "$${VLLM_IMAGE_TAG}" -a3
echo "--- Building and pushing NPU Test Image"
echo "Image: $${IMAGE_REGISTRY}/$${IMAGE_NAME}:$${VLLM_IMAGE_TAG}"
echo "buildkitd address: $${BUILDKITD_ADDR}"
- label: ":buildkit: Build and Push NPU Test Image - A5"
key: image-build-a5
agents: *npu-image-build-agents
plugins: *npu-image-build-plugins
env:
VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}-a5"
IMAGE_NAME: "vllm-omni-ci-npu"
IMAGE_REGISTRY: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry"
BUILDKITD_ADDR: "tcp://buildkitd-service.buildkitd:1234"
command: bash .buildkite/npu/scripts/build-npu-image.sh Dockerfile.npu.ci "$${VLLM_IMAGE_TAG}" -a5
if ! command -v buildctl &> /dev/null; then
echo "Installing buildctl..."
mkdir -p /tmp/buildkit
BUILDKIT_VERSION="v0.29.0"
wget -q "https://github.com/moby/buildkit/releases/download/$${BUILDKIT_VERSION}/buildkit-$${BUILDKIT_VERSION}.linux-arm64.tar.gz" -O /tmp/buildkit.tar.gz
tar -xzf /tmp/buildkit.tar.gz -C /tmp/buildkit
cp /tmp/buildkit/bin/buildctl /usr/local/bin/
fi
export DOCKER_CONFIG=/home/user/.docker
buildctl \
--addr="$${BUILDKITD_ADDR}" \
--tlscacert=/home/user/.docker/ca.pem \
--tlscert=/home/user/.docker/cert.pem \
--tlskey=/home/user/.docker/key.pem \
build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=./docker \
--opt filename=Dockerfile.npu.ci.a3 \
--secret id=dockerconfig,src=/home/user/.docker/config.json \
--output type=image,name=$${IMAGE_REGISTRY}/$${IMAGE_NAME}:$${VLLM_IMAGE_TAG},push=true \
--progress=plain
echo "--- Image pushed successfully"
echo "$${IMAGE_REGISTRY}/$${IMAGE_NAME}:$${VLLM_IMAGE_TAG}"
- label: ":buildkit: Build and Push NPU Test Image - 310P"
key: image-build-310p
agents: *npu-image-build-agents
plugins: *npu-image-build-plugins
env:
VLLM_IMAGE_TAG: "${BUILDKITE_COMMIT}-310p"
IMAGE_NAME: "vllm-omni-ci-npu"
IMAGE_REGISTRY: "swr.cn-southwest-2.myhuaweicloud.com/modelfoundry"
BUILDKITD_ADDR: "tcp://buildkitd-service.buildkitd:1234"
command: bash .buildkite/npu/scripts/build-npu-image.sh Dockerfile.npu.ci "$${VLLM_IMAGE_TAG}" -310p
- label: "Upload Ready Pipeline"
depends_on:
- image-build-a2
- image-build-a3
- image-build-a5
- image-build-310p
key: upload-ready-pipeline
agents:
queue: "cpu_queue_premerge"
@@ -174,6 +98,8 @@ steps:
depends_on:
- image-build-a2
- image-build-a3
- image-build-a5
- image-build-310p
key: upload-nightly-pipeline
commands:
- python3 .buildkite/common/scripts/upload_pipeline.py --upload .buildkite/npu/test-npu-nightly.yml
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
set -ex
dockerfile=${1:?Dockerfile is required}
image_tag=${2:?Image tag is required}
device_suffix=${3:-}
image_name=${IMAGE_NAME:-vllm-omni-ci-npu}
image_registry=${IMAGE_REGISTRY:-swr.cn-southwest-2.myhuaweicloud.com/modelfoundry}
buildkitd_addr=${BUILDKITD_ADDR:-tcp://buildkitd-service.buildkitd:1234}
echo "--- Building and pushing NPU Test Image"
echo "Image: ${image_registry}/${image_name}:${image_tag}"
echo "Dockerfile: ${dockerfile}"
if ! command -v buildctl &> /dev/null; then
mkdir -p /tmp/buildkit
buildkit_version=v0.29.0
wget -q \
"https://github.com/moby/buildkit/releases/download/${buildkit_version}/buildkit-${buildkit_version}.linux-arm64.tar.gz" \
-O /tmp/buildkit.tar.gz
tar -xzf /tmp/buildkit.tar.gz -C /tmp/buildkit
cp /tmp/buildkit/bin/buildctl /usr/local/bin/
fi
build_args=()
if [[ -n "${device_suffix}" ]]; then
build_args+=(--opt "build-arg:VLLM_ASCEND_DEVICE_SUFFIX=${device_suffix}")
fi
export DOCKER_CONFIG=/home/user/.docker
buildctl \
--addr="${buildkitd_addr}" \
--tlscacert=/home/user/.docker/ca.pem \
--tlscert=/home/user/.docker/cert.pem \
--tlskey=/home/user/.docker/key.pem \
build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=./docker \
--opt "filename=${dockerfile}" \
"${build_args[@]}" \
--secret id=dockerconfig,src=/home/user/.docker/config.json \
--output "type=image,name=${image_registry}/${image_name}:${image_tag},push=true" \
--progress=plain
echo "--- Image pushed successfully"
echo "${image_registry}/${image_name}:${image_tag}"
+53 -20
View File
@@ -1,26 +1,59 @@
steps:
- label: "Platforms Test"
key: ready-platforms-test
- group: ":card_index_dividers: Simple Test"
depends_on: upload-ready-pipeline
mirror_hardwares: a3_npu_2
commands:
- >-
pytest -s -v
tests/platforms/npu/test_minicpmo_code2wav_npugraph.py
-m 'core_model and npu and A3 and omni'
--run-level 'core_model'
steps:
- label: "Platforms Test"
key: ready-platforms-test
mirror_hardwares: a3_npu_2
commands:
- >-
pytest -s -v
tests/platforms/npu/test_minicpmo_code2wav_npugraph.py
-m 'core_model and npu and A3 and omni'
--run-level 'core_model'
- label: "Diffusion NPU layer tests"
key: ready-diffusion-npu-layer-tests
depends_on: upload-ready-pipeline
mirror_hardwares: a3_npu_2
commands:
- >-
pytest -s -v
tests/diffusion/layers/test_activation.py
tests/diffusion/layers/test_fused_qk_norm_rope_npu.py
-m 'core_model and npu and A3'
--run-level 'core_model'
- label: "Diffusion NPU layer tests - A2"
key: ready-diffusion-npu-layer-tests-a2
mirror_hardwares: a2b3_npu_1
commands:
- >-
pytest -s -v
tests/diffusion/layers/test_activation.py
-m 'core_model and npu and A2'
--run-level 'core_model'
- label: "Diffusion NPU layer tests - A3"
key: ready-diffusion-npu-layer-tests
mirror_hardwares: a3_npu_2
commands:
- >-
pytest -s -v
tests/diffusion/layers/test_activation.py
tests/diffusion/layers/test_fused_qk_norm_rope_npu.py
tests/diffusion/models/minimax_h3/test_minimax_h3_qwen3vl_swiglu.py
tests/diffusion/models/minimax_h3/test_minimax_h3_modulation.py
-m 'core_model and npu and A3'
--run-level 'core_model'
- label: "Diffusion NPU layer tests - A5"
key: ready-diffusion-npu-layer-tests-a5
mirror_hardwares: a5_npu_2
commands:
- >-
pytest -s -v
tests/diffusion/layers/test_activation.py
-m 'core_model and npu and A5'
--run-level 'core_model'
- label: "Diffusion NPU layer tests - 310P"
key: ready-diffusion-npu-layer-tests-310p
mirror_hardwares: 310p_npu_1
commands:
- >-
pytest -s -v
tests/diffusion/layers/test_activation.py
-m 'core_model and npu and 310P'
--run-level 'core_model'
- group: ":card_index_dividers: Omni Model Test"
key: ready-omni-group
+90 -207
View File
@@ -1,6 +1,6 @@
---
name: add-diffusion-model
description: Add a new diffusion model (text-to-image, text-to-video, image-to-video, text-to-audio, image editing) to vLLM-Omni, including Cache-DiT acceleration and parallelism support (TP, SP/USP, CFG-Parallel, HSDP). Use when integrating a new diffusion model, porting a diffusers pipeline or a custom model repo to vllm-omni, creating a new DiT transformer adapter, adding diffusion model support, or enabling multi-GPU parallelism and cache acceleration for an existing model.
description: Add a new diffusion model (text-to-image, text-to-video, image-to-video, text-to-audio, image editing) to vLLM-Omni, including native non-Diffusers ports, reference-parity validation, Cache-DiT, offload, and parallelism support (TP, SP/USP, CFG-Parallel, HSDP). Use when integrating or reviewing a new diffusion model, porting a Diffusers pipeline or custom model repository, creating a DiT adapter, reusing shared examples, or qualifying multi-GPU and memory optimizations.
---
# Adding a Diffusion Model to vLLM-Omni
@@ -26,9 +26,16 @@ Check the model's HF repo for `model_index.json`. This determines your path:
|----------|----------------|----------------|
| **Already supported** | `_class_name` in `model_index.json` matches a key in `_DIFFUSION_MODELS` in `registry.py` | Skip implementation, then validate model-specific examples, tests, and docs as needed |
| **Diffusers-based** | Has standard `model_index.json` with `_diffusers_version`, subfolders for `transformer/`, `vae/`, etc. | Follow **Path A** below |
| **Custom/private repo** | No diffusers `model_index.json`, weights in non-standard format, custom model code in a separate git repo | Follow **Path B** below |
| **Native non-Diffusers model** | No Diffusers index, non-standard checkpoint hierarchy, or custom architecture in a separate repo | Follow **Path B** below; port the runtime natively unless an external adapter was explicitly requested |
| **Hybrid** | Has some diffusers components (VAE) but custom transformer/fusion | Mix of Path A and Path B |
Before coding, write a short integration contract covering runtime ownership,
checkpoint discovery, reference revision, I/O geometry, attention semantics,
CFG behavior, auxiliary components, target hardware, and the default deployment.
For Path B, hybrid models, or any optimization work, read
[references/native-model-integration-checklist.md](references/native-model-integration-checklist.md)
and use its phase gates.
## Path A: Diffusers-Based Model
For models with a standard diffusers layout. See [references/transformer-adaptation.md](references/transformer-adaptation.md) for detailed code patterns.
@@ -103,9 +110,15 @@ For custom loop structures, follow `vllm_omni/diffusion/models/progress_bar.py`
---
## Path B: Custom/Private Repo Model
## Path B: Native / Non-Diffusers Model
For models without a diffusers pipeline — weights in custom format, model code in a private repo. Real examples: BAGEL, HunyuanImage3. See [references/custom-model-patterns.md](references/custom-model-patterns.md) for concrete custom-model integration patterns.
For models without a Diffusers pipeline—weights in custom formats and model
code in another public or private repository. Treat that repository as a pinned
correctness oracle. A request for native support means the vLLM-Omni runtime
must not import the reference implementation; an external adapter is appropriate
only when the requested scope explicitly permits that dependency. See
[references/custom-model-patterns.md](references/custom-model-patterns.md) for
concrete integration patterns.
### B1. Understand the reference repo
@@ -126,16 +139,25 @@ This is the key design decision for custom models. Follow these placement rules:
| **Pipeline orchestration** (init, forward, denoise loop) | `vllm_omni/diffusion/models/<name>/pipeline_<name>.py` | Always required |
| **Custom transformer/backbone** (ported and adapted to vllm-omni) | `vllm_omni/diffusion/models/<name>/<name>_transformer.py` or similar | `wan2_2.py`, `fusion.py`, `bagel_transformer.py` |
| **Custom sub-models** (VAE, fusion, autoencoder) | `vllm_omni/diffusion/models/<name>/` as separate files | `autoencoder.py`, `fusion.py` |
| **External dependency code** (original repo utilities) | **External repo**, installed via download script or pip | original-repo package via git clone |
| **Reference-only code** | Keep outside the runtime; use a pinned revision for golden outputs and architecture analysis | Reference inference script |
| **Explicit external adapter dependency** | External package, only when the requested scope and maintainer direction allow it | Compatibility adapter, not native support |
| **Hardcoded model configs** | Module-level dicts in pipeline file | `VIDEO_CONFIG`, `AUDIO_CONFIG` dicts |
| **Download/setup script** | `examples/offline_inference/<name>/download_<name>.py` | `download_<name>.py` |
| **Custom `model_index.json`** | Generated by download script, placed at model root | Minimal: `{"_class_name": "YourPipeline", ...}` |
### B3. Handle external dependencies
If the model's code lives in a separate git repo:
If the model's code lives in a separate git repo, first decide whether the
requested deliverable is native support or an external adapter. Do not silently
choose the adapter path.
**Option 1: Import with graceful fallback** (recommended for models with external utils)
**Option 1: Port the code directly** (default for native support)
Copy the essential model files into `vllm_omni/diffusion/models/<name>/` and
adapt them to shared vLLM-Omni contracts. Keep checkpoint loading strict and
use the pinned reference only to generate parity evidence.
**Option 2: Import with graceful fallback** (adapter scope only)
```python
try:
@@ -147,11 +169,9 @@ except ImportError:
)
```
**Option 2: Port the code directly** (preferred when feasible)
Copy the essential model files into `vllm_omni/diffusion/models/<name>/` and adapt them. This avoids external dependencies. BAGEL does this — `autoencoder.py` and `bagel_transformer.py` are ported directly.
**Decision criteria**: Port if the code is self-contained and won't diverge. Use external deps if the model repo is actively maintained and the code is complex.
Use an external runtime dependency only when the user explicitly requested an
adapter or a maintainer approved the exception. Document the dependency,
pinned revision, installation path, and unsupported native features.
### B4. Handle custom weight loading
@@ -221,7 +241,8 @@ class CustomPipeline(nn.Module):
### B5. Create the `model_index.json`
Custom models need a `model_index.json` at the model root for vllm-omni to discover them. For custom models, this is minimal:
Prefer a `model_index.json` at the model root when vLLM-Omni owns an assembled
checkpoint directory. For custom models, this is minimal:
```json
{
@@ -230,7 +251,14 @@ Custom models need a `model_index.json` at the model root for vllm-omni to disco
}
```
The `_class_name` must match a key in `_DIFFUSION_MODELS` in `registry.py`. Additional keys are model-specific (accessed via `od_config.model_config`).
The `_class_name` must match a key in `_DIFFUSION_MODELS` in `registry.py`.
Additional keys are model-specific (accessed via `od_config.model_config`).
If the released repository is immutable and has neither a root `config.json`
nor a Diffusers index, add it to a generic native-checkpoint signature resolver:
match the exact Hub ID or a distinctive set of local files and return the
pipeline class. Do not use model-name substrings or add parallel one-off
predicates in CLI, config, and serving consumers.
If the model's weights come from multiple HF repos, write a **download script** that:
1. Downloads from each repo
@@ -292,7 +320,9 @@ Use the appropriate existing example script:
| Image-to-Image | `examples/offline_inference/image_to_image/image_edit.py` |
| Text-to-Audio | `examples/offline_inference/text_to_audio/text_to_audio.py` |
For custom/Omni models that don't fit these categories, create a dedicated example script.
Reuse these shared scripts even for custom models when their request and output
contracts fit. Create a dedicated model script only when the shared category
cannot represent the protocol, and document that gap in the PR.
**Validation**: No errors, output is meaningful, quality matches reference implementation.
@@ -300,13 +330,17 @@ See [references/troubleshooting.md](references/troubleshooting.md) for common er
### Step 6: Add Example Scripts
For Omni or custom models, create:
Only when the shared category scripts cannot represent the model, create:
- `examples/offline_inference/your_model_name/` — offline script + README
- `examples/online_serving/your_model_name/` — server script + client
- Download script if weights require assembly from multiple sources
### Step 7: Update Documentation
Follow the [`add-recipe` skill](../add-recipe/SKILL.md) to add or update the
model-family recipe and its `recipes/README.md` row with verified specifications,
hardware, commands, feature links, and qualification evidence.
Required updates:
1. `docs/user_guide/diffusion/parallelism/overview.md` — parallelism support overview/table
2. `docs/user_guide/diffusion/cpu_offload.md` — if CPU offload supported (add to supported models table)
@@ -346,107 +380,20 @@ Classify the model's **CI priority** first:
**Keep model-specific code inside test modules — not `tests/helpers/{slug}.py`:** deploy constants, prompts, sampling dicts, and inline `request_config` / `form_data` belong in each `test_{slug}.py` and `test_{slug}_expansion.py`. Do not add per-model files under `tests/helpers/`; reuse only repo-wide harness (`mark`, `media`, `runtime`, `stage_config`, `assertions`). **L2+ online/offline e2e:** reuse or add `send_*_request` in `tests/helpers/runtime.py` — tests call the handler, not raw `omni.generate` / HTTP. See [vllm-omni-test skill](../vllm-omni-test/SKILL.md) § **Runtime send helpers**.
Keep the model suite proportional using the six distinct failure owners in the
native integration checklist. Combine supported features into a few
parametrized E2E rows instead of creating one test per optimization.
### Step 9: Add Cache-DiT Acceleration
Cache-DiT accelerates inference by caching intermediate computation results across denoising steps. After your model is working correctly on a single GPU, add cache-dit support.
Add caching only after uncached single-device correctness. Read
[references/cache-dit-patterns.md](references/cache-dit-patterns.md), use the
automatic single-block-list path when possible, and add a registered
`BlockAdapter` only for genuinely custom block topology.
See [references/cache-dit-patterns.md](references/cache-dit-patterns.md) for detailed code patterns.
#### 9a. Determine your model type
| Model Type | Description | Action |
|------------|-------------|--------|
| **Standard single-transformer** | One transformer with one `ModuleList` of blocks | No code needed — `CacheDiTBackend` auto-detects via `enable_cache_for_dit()` |
| **Multi-block-list** | One transformer with multiple block lists (e.g., `transformer_blocks` + `single_transformer_blocks`) | Write custom enabler with `BlockAdapter` |
| **Dual-transformer** | Two transformers (e.g., high-noise + low-noise) | Write custom enabler with `BlockAdapter` wrapping both |
#### 9b. Standard models — verify automatic support
For standard single-transformer models, test directly:
```python
omni = Omni(
model="your-model-name",
cache_backend="cache_dit",
cache_config={
"Fn_compute_blocks": 1,
"Bn_compute_blocks": 0,
"max_warmup_steps": 4,
}
)
```
Check logs for "Cache-dit enabled successfully on xxx". If it works, skip to Step 9e.
#### 9c. Custom architectures — write a custom enabler
For multi-block-list or dual-transformer models, write a custom enabler function:
```python
from cache_dit import BlockAdapter, ForwardPattern, ParamsModifier, DBCacheConfig
def enable_cache_for_your_model(pipeline, cache_config):
db_cache_config = DBCacheConfig(
num_inference_steps=None,
Fn_compute_blocks=cache_config.Fn_compute_blocks,
Bn_compute_blocks=cache_config.Bn_compute_blocks,
max_warmup_steps=cache_config.max_warmup_steps,
max_cached_steps=cache_config.max_cached_steps,
max_continuous_cached_steps=cache_config.max_continuous_cached_steps,
residual_diff_threshold=cache_config.residual_diff_threshold,
)
cache_dit.enable_cache(
BlockAdapter(
transformer=pipeline.transformer,
blocks=[
pipeline.transformer.transformer_blocks,
pipeline.transformer.single_transformer_blocks,
],
forward_pattern=[ForwardPattern.Pattern_1, ForwardPattern.Pattern_1],
params_modifiers=[ParamsModifier(...)],
),
cache_config=db_cache_config,
)
def refresh_cache_context(pipeline, num_inference_steps, verbose=True):
cache_dit.refresh_context(
pipeline.transformer, num_inference_steps=num_inference_steps, verbose=verbose
)
return refresh_cache_context
```
#### 9d. Register the custom enabler
Add your enabler to `CUSTOM_DIT_ENABLERS` in
`vllm_omni/diffusion/cache/cachedit/model_specific.py`:
```python
CUSTOM_DIT_ENABLERS = {
"Wan22Pipeline": enable_cache_for_wan22,
"LongCatImagePipeline": enable_cache_for_longcat_image,
"YourModelPipeline": enable_cache_for_your_model, # Add here
}
```
#### 9e. Test Cache-DiT
```python
omni = Omni(
model="your-model-name",
cache_backend="cache_dit",
cache_config={
"Fn_compute_blocks": 1, "Bn_compute_blocks": 0,
"max_warmup_steps": 4, "residual_diff_threshold": 0.24,
}
)
images = omni.generate("a beautiful landscape",
OmniDiffusionSamplingParams(num_inference_steps=50))
```
**Verify**: 1) logs show cache enabled, 2) 1.5-2x speedup, 3) output quality acceptable vs baseline.
If quality degrades, lower `residual_diff_threshold` (try 0.12-0.18) or increase `max_warmup_steps` (try 6-8).
Verify a real cache hit and compare quality with the uncached baseline. Make a
speed claim only at a realistic step count where warmup permits hits; an
all-warmup smoke proves integration, not acceleration.
---
@@ -460,107 +407,30 @@ See [references/parallelism-patterns.md](references/parallelism-patterns.md) for
#### 10a. Tensor Parallelism (TP)
Shards DiT linear layers across GPUs. Requires code changes in the transformer.
**What to change in the transformer**:
1. Replace `nn.Linear` with `ColumnParallelLinear` / `RowParallelLinear` / `QKVParallelLinear`
2. Update `load_weights()` to handle QKV fusion with `stacked_params_mapping`
3. Use `self.to_qkv.num_heads` (local heads) instead of total heads for split sizes
```python
from vllm.model_executor.layers.linear import (
QKVParallelLinear, RowParallelLinear, ColumnParallelLinear,
)
# Attention: QKV → RowParallel output
self.to_qkv = QKVParallelLinear(dim, head_dim, num_heads, num_kv_heads)
self.to_out = RowParallelLinear(dim, dim, input_is_parallel=True)
# FFN: ColumnParallel → RowParallel
self.w1 = ColumnParallelLinear(dim, ffn_dim)
self.w2 = RowParallelLinear(ffn_dim, dim, input_is_parallel=True)
```
**Constraints**: `num_heads % tp_size == 0` and `num_kv_heads % tp_size == 0`.
**Test**: `--tensor-parallel-size 2`
Replace compatible projections with vLLM parallel linears, preserve checkpoint
fusion/loading, and use local head counts. Require query/KV head divisibility
and compare a multi-rank forward with the one-rank oracle.
#### 10b. Sequence Parallelism (SP / USP)
Splits sequence tokens across GPUs. Non-intrusive via `_sp_plan` on the transformer class — no changes to `forward()`.
**What to change in the transformer**:
Add `_sp_plan` class attribute:
```python
from vllm_omni.diffusion.distributed.sp_plan import (
SequenceParallelInput, SequenceParallelOutput,
)
class YourTransformer(nn.Module):
_sp_plan = {
"blocks.0": {
"hidden_states": SequenceParallelInput(split_dim=1, expected_dims=3),
},
"proj_out": SequenceParallelOutput(gather_dim=1, expected_dims=3),
}
```
If inline tensor ops (e.g., `torch.cat`) exist between shard/gather points, extract them into `nn.Module` submodules so hooks can intercept them.
For RoPE that needs splitting, add an entry for the RoPE module with `split_output=True`.
**Test**: `--ulysses-degree 2` (offline) or `--usp 2` (online serving)
Prefer the declarative `_sp_plan`. For packed variable-length attention or
learned-sink LSE correction, keep model math explicit and reuse shared exchange
utilities. Validate uneven sequence splits, RoPE coordinates, and outputs
against the one-rank oracle.
#### 10c. CFG Parallel
Distributes positive/negative CFG branches across 2 GPUs. Requires the pipeline to inherit `CFGParallelMixin`.
**What to change in the pipeline**:
```python
from vllm_omni.diffusion.distributed.cfg_parallel import CFGParallelMixin
class YourPipeline(nn.Module, CFGParallelMixin):
def diffuse(self, ...) -> torch.Tensor:
for i, t in enumerate(timesteps):
positive_kwargs = {...}
negative_kwargs = {...} if do_true_cfg else None
noise_pred = self.predict_noise_maybe_with_cfg(
do_true_cfg=do_true_cfg, true_cfg_scale=cfg_scale,
positive_kwargs=positive_kwargs, negative_kwargs=negative_kwargs,
)
latents = self.scheduler_step_maybe_with_cfg(
noise_pred, t, latents, do_true_cfg
)
return latents
```
Override `predict_noise()` if your transformer call is non-standard. Override `combine_cfg_noise()` for multi-output models (e.g., video + audio).
**Constraint**: Exactly 2 GPUs. Only for models using classifier-free guidance.
**Test**: `--cfg-parallel-size 2`
Confirm the model uses CFG. Then reuse `CFGParallelMixin`, overriding prediction
or recombination only for non-standard or multi-output pipelines. Distinguish a
packed positive/negative implementation from two independent branches and
validate the two-rank result against the packed one-rank oracle.
#### 10d. HSDP (Hybrid Sharded Data Parallel)
Shards transformer weights via PyTorch FSDP2 to reduce per-GPU VRAM. No code changes to the forward pass — just add a class attribute.
**What to change in the transformer**:
```python
class YourTransformer(nn.Module):
@staticmethod
def _is_transformer_block(name: str, module) -> bool:
return "blocks" in name and name.split(".")[-1].isdigit()
_hsdp_shard_conditions = [_is_transformer_block]
```
**Constraint**: Cannot combine with TP. For standalone HSDP, set `hsdp_shard_size` explicitly.
**Test**: `--use-hsdp` or `DiffusionParallelConfig(use_hsdp=True)`
Declare layer shard conditions and ignored rank-local modules; preserve mixed
checkpoint dtypes. HSDP cannot combine with TP. Measure parameter loading,
FSDP materialization, warm HBM, and host PSS rather than assuming sharding saves
peak memory. Keep the resident layout as default if HSDP is worse.
#### 10e. Update parallelism documentation
@@ -599,6 +469,12 @@ Pipelines without `SupportsComponentDiscovery` fall back to scanning
well-known attribute names (`transformer`, `text_encoder`, `vae`,
etc.), which fails for non-standard names.
Keep model-specific checkpoint paths, nested block aliases, layout transforms,
and component lifecycles in the model package. Change a shared offloader only
for a general contract, demonstrate another consumer or a framework-level bug,
and add one focused shared regression. Avoid `if ModelName` branches in shared
backends.
### Step 12: Performance Profiling
After verifying correctness and implementing parallelism/caching, profile the model's performance to identify bottlenecks and ensure optimal execution.
@@ -608,6 +484,12 @@ See the [Profiling Single-Stage Diffusion](../../../docs/contributing/profiling.
2. Using Nsight Systems (`nsys`) with `profiler: "cuda"` for low-overhead CUDA traces.
3. Controlling profiling via `omni.start_profile()` and `omni.stop_profile()`.
Report cold E2E, warm user latency, steady-state wave time, peak allocated and
reserved HBM, host PSS for the full process tree, and stage boundaries. State
whether prompt encoding and VAE/audio decoding are included. For multi-device
layouts, plot user latency against throughput per device and keep different
denoising-step counts on separate Pareto frontiers.
---
## Pre-commit conventions
@@ -635,7 +517,7 @@ shellcheck). In particular:
3. **Use small models**: Test with smaller variants first
4. **Check tensor shapes**: Most errors are reshape mismatches in attention
5. **Add features incrementally**: Single GPU → TP → SP → CFG → HSDP → Cache-DiT
6. **For custom models**: Get the model running with the original code first, then progressively replace components with vllm-omni equivalents
6. **For custom models**: Run the pinned reference separately, then port the runtime natively; do not ship temporary imports from the reference implementation
7. **Cache-DiT before parallelism tuning**: Cache-DiT is lossy — verify quality at baseline before combining with parallelism
8. **Combine lossless + lossy**: e.g., TP + SP + Cache-DiT for maximum throughput
@@ -644,6 +526,7 @@ shellcheck). In particular:
- [vllm-omni-test skill](../vllm-omni-test/SKILL.md) — L1–L4 markers, naming, Buildkite wiring, run commands
- [Transformer Adaptation](references/transformer-adaptation.md) — porting transformers from diffusers
- [Custom Model Patterns](references/custom-model-patterns.md) — patterns for non-diffusers models
- [Native Model Integration Checklist](references/native-model-integration-checklist.md) — ownership boundaries, phase gates, qualification matrix, and review evidence
- [Parallelism Patterns](references/parallelism-patterns.md) — TP, SP/USP, CFG parallel, HSDP implementation details
- [Cache-DiT Patterns](references/cache-dit-patterns.md) — cache-dit acceleration for standard and custom architectures
- [Troubleshooting](references/troubleshooting.md) — common errors and fixes
@@ -9,7 +9,9 @@ Three caching strategies:
- **TaylorSeer**: Calibration-based prediction using Taylor expansion to estimate block outputs
- **SCM** (Step Computation Masking): Dynamic step skipping based on configurable policies
**Typical speedup**: 1.5-2.5x depending on model and configuration.
Speedup is model-, step-, and policy-dependent. Treat a cache-enabled run as
integration evidence until logs or a focused test show real hits and repeated
timings show an improvement over the uncached baseline.
**Official docs**: https://docs.vllm.ai/projects/vllm-omni/en/latest/design/feature/cache_dit
@@ -243,8 +245,10 @@ vllm serve your-model --omni --port 8098 \
**Verification checklist**:
1. Logs show "Cache-dit enabled successfully on xxx"
2. Performance: 1.5-2x speedup vs no cache
3. Quality: compare output with `cache_backend=None`
2. A focused test or summary confirms at least one real cache hit
3. Quality is compared with `cache_backend=None`
4. Performance uses a realistic step count, repeated timings, and a policy
whose warmup does not consume the whole run
## Excluded Models
@@ -16,7 +16,7 @@ vllm_omni/diffusion/models/wan2_2/
The transformer is loaded separately via `weights_sources` + `load_weights()`. Non-transformer components (VAE, text encoder) are loaded in `__init__` via `from_pretrained()`.
### Custom model with external deps
### External adapter (not native support)
```
vllm_omni/diffusion/models/<name>/
@@ -28,7 +28,10 @@ examples/offline_inference/<task>/
└── download_<name>.py # Downloads weights and/or clones code repo
```
All weights loaded eagerly in `__init__`. `load_weights()` is a no-op. External dependency imported with try/except.
All weights are loaded eagerly in `__init__`. `load_weights()` is a no-op. The
external dependency is imported with try/except. Use this pattern only when the
requested scope explicitly allows an adapter; it does not satisfy a request for
a native vLLM-Omni implementation.
### Custom model with ported code (e.g., BAGEL)
@@ -86,7 +89,7 @@ init → load ALL weights eagerly via custom helpers → load_weights() = no-op
- `load_weights()` is `pass`
- Weights may come from multiple HF repos in different formats (`.pth`, `.safetensors`)
Use this when:
Use this only for an explicitly approved external adapter when:
- The original model has complex, well-tested loading code you don't want to rewrite
- Weights span multiple HF repos
- Weight format is non-standard (e.g., a single `.pth` file, not sharded safetensors)
@@ -112,7 +115,14 @@ Custom model `model_index.json` (minimal):
}
```
The only **required** field is `_class_name` — it must match a key in `_DIFFUSION_MODELS` in `registry.py`. Other fields are model-specific and accessible via `od_config.model_config` dict.
The only **required** field is `_class_name` — it must match a key in
`_DIFFUSION_MODELS` in `registry.py`. Other fields are model-specific and
accessible via `od_config.model_config`.
When the released repository cannot be modified and contains neither this file
nor a root `config.json`, use the generic native-checkpoint signature resolver.
Match the exact Hub ID or a distinctive local file set; never infer support from
a model-name substring.
## External Dependency Management
@@ -141,6 +151,10 @@ def download_dependency():
Copy essential files from the original repo into `vllm_omni/diffusion/models/<name>/`. Adapt imports to use vllm-omni utilities. Benefits: no external dependency, no git clone step. Drawback: must maintain the ported code.
This is the default for native model support. Keep the reference repository at
a pinned revision for architecture analysis and golden outputs, but do not
import it from production code.
## Multi-Modal Input/Output Protocols
Custom models that handle images, audio, or video I/O should implement protocol classes:
@@ -0,0 +1,131 @@
# Native Diffusion Model Integration Checklist
Use this checklist for native non-Diffusers models, hybrid pipelines, and
model-specific optimization work.
## 1. Freeze the contract before coding
Record:
- Reference repository and immutable revision.
- Official checkpoint ID, local layout, license, and required auxiliary files.
- Pipeline stages: prompt/image encoding, denoising, video/image/audio decoding,
and muxing.
- Token packing, RoPE coordinates, attention sinks/LSE behavior, MoE layout,
scheduler equations, CFG formulation, dtypes, and checkpoint transforms.
- Output geometry, frame count/FPS, audio rate/channels, supported tasks, and
default generation steps.
- Available local hardware versus the official qualification hardware.
- Intended default deployment and optional optimization paths.
State whether the deliverable is a native implementation or an external
adapter. For native support, use the reference as an oracle only.
## 2. Use phase gates
| Phase | Required exit evidence |
|---|---|
| Architecture | Component/weight inventory; unsupported reference dependencies identified; ownership decisions documented |
| Native correctness | Strict checkpoint load; tiny forward or one-step golden vs pinned reference; output contract validated |
| Framework integration | Registry/discovery, shared request types, existing category example, serving route, docs, and recipe work without model-name branching in shared scripts |
| Lossless distribution | One-rank oracle vs each supported TP/SP/CFG/HSDP/VAE layout; released-checkpoint smoke for the recommended layout |
| Memory optimization | Ordinary/layerwise/DLO behavior, peak HBM, full-tree host PSS, and stage timeline; shared backend unchanged unless a general contract is proven |
| Lossy optimization | Cache hit demonstrated; quality delta measured; speed claim made only when warmup permits hits |
| Full qualification | Default-resolution E2E at official step count for every supported task; media metadata and hashes recorded |
| Review readiness | Focused tests, Ruff/format, strict docs, DCO, current-main rebase, issue/RFC for large changes, concise PR evidence and limitations |
Do not start performance tuning before native one-step parity. Do not claim a
feature from topology validation alone when released weights can exercise it.
## 3. Keep ownership narrow
Default placement:
| Concern | Owner |
|---|---|
| Architecture, checkpoint remap, packed attention math, nested block alias, auxiliary lifecycle | Model package |
| Kernel/version dispatch already useful to multiple models | Shared attention utility |
| Request-dependent example defaults | Typed model-extra entry consumed through a registry |
| Exact-ID/local-file detection for repositories without standard metadata | Generic native-checkpoint signature table |
| General offload/cache/HSDP bug | Shared layer plus one focused shared regression |
Before changing shared code, answer:
1. Is this a framework contract or only this checkpoint's layout?
2. Can the model expose an existing declarative hook instead?
3. Is there a second consumer or a framework-level failing test?
4. Can the shared change avoid importing or naming the model?
If the answers do not justify a shared change, keep it model-local.
## 4. Reuse examples without leaking model logic
- Reuse the existing `x_to_y.py` category script.
- Resolve the pipeline class through standard metadata or native signature
discovery, not path substrings.
- Put dynamic model defaults and validation in `model_extras`; let shared
examples consume only the generic typed API.
- Do not create image/speech/default abstractions without a real consumer.
- Add a dedicated example only when the shared request/output protocol cannot
express the model.
## 5. Build a bounded evidence matrix
Start with the recommended resident layout. Add only supported, useful points:
- Single request: TP, SP, or hybrid TP/SP.
- Throughput: DLO/DP layouts when concurrent requests are valid.
- CFG: compare CFG-parallel output with the packed-CFG oracle only if the model
actually uses CFG.
- HSDP: record loading and materialization peaks; it can use more HBM than a
resident SP layout.
- VAE patch parallelism: compare decoded tensors or media exactly where the
algorithm should be lossless.
- Cache: separate cache integration from acceleration evidence.
With four devices, qualify four-device layouts such as DP4, DP2SP2, SP4, or
CFG2xSP2. Do not present CFG2xSP4 or an official eight-device runtime as locally
validated.
For each measured layout record:
- Cold E2E and warm user latency.
- Steady-state wave time and throughput per device.
- Prompt, denoise, video decode, audio decode, and mux times.
- Peak allocated/reserved HBM and full-process-tree host PSS.
- Step count, seed, output geometry, frame/FPS/audio metadata, and parity metric.
Plot latency versus throughput/device. Keep one-step, four-step, and full-step
results separate because they are not quality-equivalent.
## 6. Keep tests proportional
Prefer a small set with distinct failure ownership:
1. Strict weight-key/dtype/load contract.
2. Tiny native golden or one-step reference parity.
3. One parametrized distributed-parity test covering supported layouts.
4. Pipeline/request/output geometry and serving smoke.
5. Shared-example/default test when the category script changes.
6. One shared regression only for a genuine shared-layer fix.
Avoid duplicating the same shape or configuration assertion across
model-specific files. Keep expensive released-checkpoint evidence in the PR or
recipe unless the repository's CI tier explicitly requires it.
## 7. Write evidence, not marketing
In the PR description include:
- A compact architecture/data-flow explanation.
- Default deployment and why it is recommended.
- Code-change counts by component.
- Accuracy and distributed-parity evidence tied to commands/scripts.
- Separate Pareto figures for different step counts.
- Supported versus validated versus planned optimizations.
- Hardware gaps and untested official-runtime claims.
Call a result a bounded local qualification unless it includes repeated timing
and variance. State whether latency includes text/image encoding and all
decoders. Preserve the reference revision and exact commands needed to
reproduce the evidence.
+126
View File
@@ -0,0 +1,126 @@
---
name: add-recipe
description: Add or update an in-repository vLLM-Omni model recipe with verified task, input, output, hardware, command, feature, and validation contracts. Use when creating files under recipes/, restructuring a recipe after review, documenting a newly supported model, or synchronizing recipe claims with support tables and shared feature guides.
---
# Add a vLLM-Omni Recipe
## Source the contract
Read [`recipes/TEMPLATE.md`](../../../recipes/TEMPLATE.md) and
[`recipes/README.md`](../../../recipes/README.md) before editing. Inspect the
canonical model card/repository, implementation, shared examples, tests, and
available local qualification evidence.
Do not infer support from a model name, sibling recipe, registry entry, or
topology validator. Mark unexecuted configurations as configuration-only.
## Route by model family
Read only the contributor and user documentation relevant to the model:
| Family | Contributor guide | User-facing documentation to synchronize |
|---|---|---|
| Diffusion | [`adding a diffusion model`](../add-diffusion-model/SKILL.md) | `docs/user_guide/diffusion_features.md`, applicable diffusion feature guides, and shared image/video/audio examples |
| TTS | [`adding a TTS model`](../add-tts-model/SKILL.md) | `examples/offline_inference/text_to_speech/README.md`, `examples/online_serving/text_to_speech/README.md`, and `docs/serving/speech_api.md` |
| Omni | [`adding an omni model`](../../../docs/contributing/model/adding_omni_model.md) | model-family offline/online example docs, `docs/serving/chat_completions_api.md`, and `docs/user_guide/feature_compatibility.md` |
All families must update `docs/models/supported_models.md` and the matching row
in `recipes/README.md`. Do not add a modality-specific support document when
the repository has no such table; update the closest shared user contract.
## Structure the recipe
Keep the recipe task-oriented and use one hardware-specific file named
`recipes/<vendor>/<model>-<hardware>.md`. A recipe may contain multiple
deployment topologies on that hardware, but each additional accelerator model
or platform requires its own suffixed file.
1. **Summary:** vendor, exact model ID, runtime, modes, named hardware,
recommended deployment, and maintainer.
2. **Supported model contract:** put task, input, output, and provided-profile
tables before setup or commands.
3. **References:** link the canonical model source, shared examples, supported
model table, and feature matrix.
4. **Checkpoint/setup:** pin revisions when possible and state required assets.
5. **Hardware:** document the single accelerator named by the file suffix,
including every validated device-count/topology profile on it.
6. **Commands:** reuse shared `examples/` entrypoints unless the model contract
truly requires a dedicated script. Include offline and online paths that
were validated.
7. **Supported features:** use a compact model-specific topology/status table
with links to shared feature guides.
8. **Verification:** provide a quick command and exact expected output
contract.
9. **Qualification evidence:** report correctness, memory, and timing evidence
with its measurement scope and caveats.
## Separate hardware from software
For every locally validated hardware profile, record:
- accelerator vendor/model and per-device memory;
- number of devices;
- interconnect (`NVLink`, `PCIe`, or the platform equivalent);
- host memory when CPU staging/offload is material;
- whether the profile is runtime-qualified or configuration-only.
Record OS, Python, driver/runtime, framework versions, and the vLLM-Omni
revision in a separate software-environment table. Keep precision, worker
count, and DP/TP/SP/PP sizes with the deployment profile or exact command.
Never generalize a result from one accelerator family to another or combine
NVIDIA, AMD, NPU, or distinct accelerator models in one recipe. Create a
separate hardware-suffixed recipe and `recipes/README.md` row. Distinguish
upstream requirements from hardware exercised by the PR.
## Keep shared documentation concise
Link the applicable diffusion, TTS, omni, serving, or design guide for generic
feature semantics and launch instructions. The recipe should contain only the
model-specific status, valid topology, required flag difference, and evidence
boundary.
Put unsupported combinations in the same feature table. Avoid repeating the
same model-specific prose below the global feature matrix; the recipe is the
detailed source of truth.
## Report evidence precisely
For every performance or memory value, state:
- checkpoint/revision and workload;
- accelerator and device count;
- precision and topology;
- cold/warm scope, step count, and concurrency;
- peak HBM and host-memory metric when measured;
- output/quality guard and sample count;
- monitoring overhead or other known variance.
Call a single bounded run qualification evidence, not a benchmark. Do not add
a benchmark script unless the contribution explicitly requires one.
## Synchronize repository documentation
Update all applicable locations:
- the row in `recipes/README.md`;
- `docs/models/supported_models.md`;
- the family-specific documentation selected in **Route by model family**;
- shared example documentation when commands or flags change.
Keep global support tables compact. Link to the recipe instead of duplicating
its deployment explanation.
## Validate
Run, at minimum:
```bash
pre-commit run --files <changed recipe/docs/skill files>
mkdocs build --strict
git diff --check
```
Remove generated documentation artifacts after validation. Confirm every
recipe link resolves and every claimed profile has matching code or evidence.
+8
View File
@@ -499,6 +499,13 @@ Common pre-commit failures, recovery commands for missing sign-off, and the
full `pre-commit run` invocation for a TTS model:
[references/precommit-dco.md](references/precommit-dco.md).
## Documentation and recipe
Follow the [`add-recipe` skill](../add-recipe/SKILL.md) to add or update the
in-repository model-family recipe, the `recipes/README.md` index, the supported
models table, TTS example documentation, and the speech API contract using
validated evidence.
## Integration Checklist
Use this checklist when integrating a new TTS model:
@@ -543,6 +550,7 @@ Use this checklist when integrating a new TTS model:
- [ ] Buildkite entries match level: `test-ready.yml` / `test-merge.yml` or nightly TTS job / `test-nightly.yml`
- [ ] Gradio demo working
- [ ] Documentation added (offline + online docs, nav, supported models)
- [ ] In-repository recipe and `recipes/README.md` row added via the `add-recipe` skill
### Phase 4: Async Chunk
- [ ] Pipeline declares the async handoff processor
+3
View File
@@ -26,6 +26,9 @@ self-check.
- [`add-diffusion-model`](add-diffusion-model/SKILL.md): guides integration of
a new diffusion model into `vllm-omni`
- [`add-recipe`](add-recipe/SKILL.md): creates or updates in-repository model
recipes with verified task/input/output contracts, concrete hardware,
shared feature-guide links, exact commands, and qualification evidence
- [`add-tts-model`](add-tts-model/SKILL.md): covers integration of new TTS
models and related serving workflows
- [`diffusion-perf-opt`](diffusion-perf-opt/SKILL.md): guides diffusion model
+6 -5
View File
@@ -123,10 +123,11 @@ it changes again, report the churn and wait for a stable target.
For a trusted PR head, materialize the pinned head in an isolated detached
worktree. A worktree freezes identity but is not a security sandbox. Treat fork
heads as untrusted unless the user and environment policy explicitly establish
otherwise: execute them only in a disposable, secret-free sandbox with restricted
filesystem, network, and resources; without one, use static SHA-addressed reads
and CI evidence only. For a local review, freeze the committed, index, worktree,
heads as untrusted until the user and environment policy explicitly establish
trust: execute their code only after that trust is recorded for this host,
with the trusted SHA noted in the review state and secrets kept out of the
execution scope; without recorded trust, use static SHA-addressed reads and CI
evidence only. For a local review, freeze the committed, index, worktree,
and NUL-safe in-scope untracked contents. Follow
[review-execution.md](references/process/review-execution.md) for trust gates,
state fingerprints, and byte-for-byte staleness checks.
@@ -196,7 +197,7 @@ style preferences as simplification findings.
Before each validation group, verify the frozen SHA plus the tracked, index,
untracked, and ignored-file fingerprint, or recreate a pristine snapshot. On a
trusted head or inside the required sandbox, run an import/version preflight,
head the user explicitly trusted for this host, run an import/version preflight,
then the narrowest relevant tests and low-cost static checks. Bind every result
to the head SHA, snapshot fingerprint, and environment fingerprint. Never run
imports, tests, builds, hooks, or repo-configurable tooling from an untrusted
@@ -22,6 +22,19 @@ Keep exact commands and report variability rather than a cherry-picked run.
Use the repository's benchmark when it covers the claim; otherwise use the
smallest reproducible workload that reaches the changed production path.
## Isolation per claim
Isolate one claim per comparison. When a PR stacks several changes (a fused or
restructured load path, a distilled step schedule, a new cache), request one
column per claim at equal steps/workload instead of one blended base-vs-head
delta, and name the unchanged stage or metric that pins equivalence.
When the changed path is runtime-switchable (feature flag or environment
variable), run every column on the same head and toggle the switch rather than
diffing against a base commit that cannot separate the stacked changes:
base, head with the switch off, head with the switch on, then head with the
switch on plus each further claim.
## Evidence table
| Dimension | Typical evidence |
@@ -34,6 +47,100 @@ smallest reproducible workload that reaches the changed production path.
Every optimization needs correctness or quality evidence. A faster result on a
different workload, precision, seed, or topology is not a valid comparison.
## E2e stage-attribution table
For an end-to-end latency claim, request the stage-attribution table the
repository's perf PRs use:
- Rows carry the benchmark's own field names: total e2e, per-stage execution,
postprocess, response encoding, and a request-to-output roll-up. Add a stage
handoff/idle row when the pipeline spans stages — connector transfer and
scheduling gaps are invisible in per-stage latency deltas.
- Sanity rows pin comparability: inference steps, output frames/pixels/samples,
and generated tokens held identical across columns.
- Report P50 and P100 from the stated warmup and measured counts. One
population per number: never mix cold-start and warm samples into one value.
- Name the baseline and candidate commit SHAs, and close with an isolation
statement naming the metric that stayed unchanged ("X remained effectively
unchanged, isolating the gain to Y").
When the serving path caches, captures, or regenerates at runtime (CUDA-graph
bucket growth, cache rebuilds, recompilation), medians hide the resulting tail.
Also request the event count and the maximum observed stall.
Component-level tables pair with, not replace, the e2e table: per-op reference
vs candidate timings (with the numerical-equality result per op) plus one
complete-pipeline check under the production shapes.
## Proportionality
When a PR adds a model and explicitly labels its numbers as smoke observations
rather than claims, do not demand the attribution machinery. Ask only for
evidence protecting the model's stated operating contract — for a
realtime/duplex model that is sustained cadence over a realistic session
(enough ticks to leave warmup), with any mid-session capture/regeneration
event and its stall reported, since a cadence that collapses mid-session is a
functional failure, not a perf claim.
## Realtime/duplex world models
Request the operating-contract evidence as serving metrics, not claims:
- Sustained per-tick latency over one realistic session: median, max, and the
argmax tick.
- Stall events with their causes.
- Peak memory at first versus last tick — session-owned state must not grow
per tick.
- Control latency from a submitted action to the tick that applies it.
- Realtime-versus-offline parity under the same controls, using the
repository's video-similarity helpers. Parity plotted over tick index is the
drift-over-horizon curve and the honest measure of session lifetime;
distribution-level suites (FVD, VBench) belong to model evaluation, not the
serving integration.
Watch the contract boundaries the model documents: if a control input is
tick-API-only, the parity pair must use a horizon both paths can run.
## Speech-generation (TTS) models
The streaming contract outranks the average — a synthesis that pauses
mid-stream is broken for realtime use regardless of its averages. Request:
- The benchmark's own speech metrics over one long utterance (enough audio to
leave warmup, tens of seconds): `audio_ttfp` (time to first packet),
`audio_rtf` (real-time factor; above 1.0 cannot keep up), `e2el`, and
`ttft`/`tpot` where tokens precede audio.
- Mid-utterance stalls as the benchmark's continuity fields, not prose:
`audio_underrun` percentiles and the `audio_continuity_ok` rate
(`vllm_omni/benchmarks/metrics/metrics.py`), computed by
`vllm_omni/benchmarks/audio_continuity.py` — the companion to `audio_ttfp`
and `audio_rtf`. A synthesis that bursts chunks fast enough on average but
underruns mid-stream fails the streaming contract even when the averages
pass.
- Quality as a paired comparison against the reference implementation under
the same text, speaker prompt, and seed; report sample rate and frame
count, and assert output completeness — truncated or dropped trailing audio
is a known failure class, not a quality nuance.
- For realtime or duplex TTS: interruption (barge-in) latency and the
per-session memory trend across turns.
## Omni models
Apply the e2e stage-attribution table directly to the voice-to-voice path of
omni (speech-to-speech and multimodal AR) models:
- TTFT for text output, `audio_ttfp` for audio output, and per-stage
ITL/TPOT cadence across the perception-understanding-generation chain.
- Cross-modal parity — the omni analogue of realtime-versus-offline parity:
the same semantic request through text and audio inputs, compared on output
content or timing.
- For duplex omni sessions: turn-taking latency, interruption handling, the
concurrent session count actually validated, and a per-turn memory trend —
session-owned KV state must not grow per turn. When either turn produces
audio, continuity is judged with the same `audio_underrun` and
`audio_continuity_ok` fields as TTS, since turn-taking latency alone does
not expose mid-utterance underruns.
## Reviewer verification ladder
1. Run base/head A/B on suitable hardware when affordable.
@@ -121,6 +121,19 @@ Search bounded callers and sibling implementations before reporting dead code,
duplication, or a missed consumer. Keep new abstractions only when they have a
distinct live caller, invariant, or compatibility need.
Verify absence and equality claims with whole-file evidence. A line window, a
grep for one symbol, or a commit message proves presence, never absence: to
assert that a file matches another version or lacks a change, diff or hash the
full files (for example `git diff <sha1>:<path> <sha2>:<path>` or the contents
API's size and SHA fields) before anchoring the finding. A verification
shortcut that would itself be wrong to run is not evidence.
For a value written or finalized in more than one place, establish which site
executes first on each entry path — and whether the earlier site makes the
later ones unreachable — before naming where a fix belongs. Enumerating all
call sites correctly still misses the bug when a path the enumeration ranked
last actually finalizes first and clears the state the proposed fix checks.
## Finding bar
Anchor each finding to a changed `path:line`; name the trigger or call path,
@@ -39,13 +39,13 @@ isolated detached worktree. Run every source read, `rg` search, import, and test
from that snapshot, not the caller's checkout. A detached worktree provides
snapshot isolation, not a security boundary.
Treat a fork head as untrusted unless the user and environment policy explicitly
establish trust. Never run its imports, tests, builds, hooks, package setup, or
repo-configurable linters/plugins on the reviewer host. Execute them only in a
disposable sandbox or VM with no credentials or inherited secrets, no host agent
or service sockets, minimal read-only host mounts, disabled or explicitly
allowlisted network, resource/time limits, and destruction after the run. If
that boundary is unavailable, limit the review to the remote diff, SHA-addressed
Treat a fork head as untrusted until the user and environment policy
explicitly establish trust. Without recorded trust, never run its imports,
tests, builds, hooks, package setup, or repo-configurable linters/plugins on
the reviewer host. Once the user explicitly trusts this pinned commit for
this host, record the trusted SHA with the review state and execute with
credentials, agent sockets, and other secrets out of scope. Without recorded
trust, limit the review to the remote diff, SHA-addressed
reads such as `git show <head_sha>:<path>`, and existing CI evidence; report all
executable validation as a gap.
+2
View File
@@ -2,6 +2,8 @@
You may find information about contributing to vLLM-Omni on [Contributing](https://vllm-omni.readthedocs.io/en/latest/contributing/).
AI-assisted contributions must follow the [AI Usage Policy](docs/contributing/README.md#ai-usage-policy), including disclosure, human review, and validation requirements.
Local `pre-commit` gates (SPDX, forbidden imports, `torch.cuda`, shellcheck,
mypy, test marks, TTS adapter ratchet, markdownlint, Buildkite schema, and
how to extend allowlists) are documented in the
+1 -1
View File
@@ -53,7 +53,7 @@ vLLM-Omni is flexible and easy to use with:
- Tensor, pipeline, data and expert parallelism support for distributed inference
- Streaming outputs
- OpenAI-compatible API server
- Full-duplex realtime serving with streaming audio input and output (experimental)
- [Full-duplex realtime serving](docs/serving/realtime_duplex_api.md) with streaming audio input and output
vLLM-Omni seamlessly supports most popular open-source models on HuggingFace, including:
+30 -15
View File
@@ -17,18 +17,18 @@ The main entrypoint is:
vllm serve Qwen/Qwen-Image --omni --port 8099
```
2. Run a minimal benchmark:
1. Run a minimal benchmark:
```bash
python3 benchmarks/diffusion/diffusion_benchmark_serving.py \
--base-url http://localhost:8099 \
--model Qwen/Qwen-Image \
--task t2i \
--dataset vbench \
--num-prompts 5
--base-url http://localhost:8099 \
--model Qwen/Qwen-Image \
--task t2i \
--dataset vbench \
--num-prompts 5
```
**Notes**
### Notes
- By default, image tasks talk to `http://<host>:<port>/v1/chat/completions`; video tasks talk to `/v1/videos`.
- If you run the server on another host or port, pass `--base-url` accordingly.
@@ -52,14 +52,14 @@ Example (`t2v`):
```bash
python3 benchmarks/diffusion/diffusion_benchmark_serving.py \
--base-url http://localhost:8099 \
--model Wan-AI/Wan2.2-T2V-A14B-Diffusers \
--task t2v \
--dataset vbench \
--num-prompts 50 \
--width 640 --height 480 \
--num-frames 81 --fps 16 \
--num-inference-steps 40
--base-url http://localhost:8099 \
--model Wan-AI/Wan2.2-T2V-A14B-Diffusers \
--task t2v \
--dataset vbench \
--num-prompts 50 \
--width 640 --height 480 \
--num-frames 81 --fps 16 \
--num-inference-steps 40
```
Note: `vbench` can also be used for other tasks such as `t2i` / `i2v` (and `i2i`). For `t2i`, the loader reuses VBench t2v text prompts; for `i2v` / `i2i`, it loads the VBench i2v dataset (with image paths).
@@ -140,6 +140,21 @@ Traffic / concurrency flags:
- `--request-rate`: Target request rate (requests/second). If set to `inf`, the script sends all requests immediately.
- `--max-concurrency`: Max number of in-flight requests (default: `1`). This can hard-cap the achieved QPS: if it is too small, requests will queue behind the semaphore, and both achieved throughput and observed SLO attainment can be skewed.
### Video job timeout and failures
`--video-job-timeout` controls how long each `/v1/videos` job may be polled,
including time queued on the server (default: `900` seconds). For long video
jobs at high concurrency, use a larger budget, for example
`--video-job-timeout 1800`. This also applies to warmup jobs. The polling budget
starts after job creation; it excludes time waiting for the client concurrency
semaphore. Individual HTTP calls still use the aiohttp session timeout.
An expired job is counted as failed and deleted, which cancels queued or running
server generation. The benchmark continues processing the remaining requests.
The progress bar counts both successful and failed requests. The final report
shows the failure count and the first 10 errors; `--output-file` saves all errors
with their request IDs in `request_errors`.
### Batched warmup note
For batched serving runs, warm the same in-flight shape you plan to measure.
+64 -57
View File
@@ -1,3 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import asyncio
import base64
import json
@@ -43,6 +46,7 @@ class RequestFuncInput:
video_paths: list[str] | None = None
request_id: str = field(default_factory=lambda: str(uuid.uuid4()))
default_bot_task: str | None = DEFAULT_EDITS_BOT_TASK
video_job_timeout: float = 900.0
@dataclass
@@ -331,64 +335,64 @@ async def async_request_v1_videos(
output = RequestFuncOutput()
output.start_time = time.perf_counter()
files = dict(input.extra_body)
if input.prompt:
files.setdefault("prompt", input.prompt)
if input.width and input.height:
files.setdefault("height", input.height)
files.setdefault("width", input.width)
if input.num_frames:
files.setdefault("num_frames", input.num_frames)
if input.num_inference_steps:
files.setdefault("num_inference_steps", input.num_inference_steps)
if input.seed is not None:
files.setdefault("seed", input.seed)
if input.fps:
files.setdefault("fps", input.fps)
form = aiohttp.FormData()
for k, v in files.items():
form.add_field(k, str(v))
image_file = None
if input.image_paths and input.video_paths:
output.error = "Only one of image_paths or video_paths can be provided"
output.success = False
return output
job_id = None
try:
files = dict(input.extra_body)
if input.prompt:
files.setdefault("prompt", input.prompt)
if input.width and input.height:
files.setdefault("height", input.height)
files.setdefault("width", input.width)
if input.num_frames:
files.setdefault("num_frames", input.num_frames)
if input.num_inference_steps:
files.setdefault("num_inference_steps", input.num_inference_steps)
if input.seed is not None:
files.setdefault("seed", input.seed)
if input.fps:
files.setdefault("fps", input.fps)
if input.image_paths and len(input.image_paths) > 0:
image_path = input.image_paths[0]
image_file = open(image_path, "rb")
form.add_field(
"input_reference",
image_file,
filename=os.path.basename(image_path),
content_type=_guess_mime_type(image_path),
)
elif input.video_paths and len(input.video_paths) > 0:
video_path = input.video_paths[0]
if _is_minimax_h3_mixed_reference(input.model):
with open(video_path, "rb") as reference_file:
video_b64 = base64.b64encode(reference_file.read()).decode("ascii")
form.add_field(
"video_reference",
json.dumps([{"video_url": f"data:video/mp4;base64,{video_b64}"}]),
)
else:
image_file = open(video_path, "rb")
form = aiohttp.FormData()
for k, v in files.items():
form.add_field(k, str(v))
if input.image_paths and input.video_paths:
output.error = "Only one of image_paths or video_paths can be provided"
output.success = False
return output
if input.image_paths and len(input.image_paths) > 0:
image_path = input.image_paths[0]
image_file = open(image_path, "rb")
form.add_field(
"input_reference",
image_file,
filename=os.path.basename(video_path),
content_type=_guess_mime_type(video_path),
filename=os.path.basename(image_path),
content_type=_guess_mime_type(image_path),
)
elif input.video_paths and len(input.video_paths) > 0:
video_path = input.video_paths[0]
if _is_minimax_h3_mixed_reference(input.model):
with open(video_path, "rb") as reference_file:
video_b64 = base64.b64encode(reference_file.read()).decode("ascii")
form.add_field(
"video_reference",
json.dumps([{"video_url": f"data:video/mp4;base64,{video_b64}"}]),
)
else:
image_file = open(video_path, "rb")
form.add_field(
"input_reference",
image_file,
filename=os.path.basename(video_path),
content_type=_guess_mime_type(video_path),
)
job_id = None
job_status = None
poll_json = {}
resp_json = {}
job_status = None
poll_json = {}
resp_json = {}
try:
# invoke a post request (POST /v1/videos)
async with session.post(input.api_url, data=form) as response:
if response.status == 200:
@@ -406,7 +410,7 @@ async def async_request_v1_videos(
# invoke a poll request (GET /v1/videos/{video_id})
poll_interval = 2.0 # Unit(s)
timeout_seconds = 600.0
timeout_seconds = input.video_job_timeout
deadline = time.perf_counter() + timeout_seconds
job_url = f"{input.api_url}/{job_id}"
@@ -422,8 +426,11 @@ async def async_request_v1_videos(
poll_json = await poll_response.json()
job_status = poll_json.get("status")
if time.perf_counter() >= deadline:
output.error = f"Timed out waiting for video job {job_id} to complete."
if job_status not in {"completed", "failed"} and time.perf_counter() >= deadline:
output.error = (
f"Timed out after {timeout_seconds:g}s waiting for video job {job_id} to complete. "
"Increase --video-job-timeout for long-running or queued video jobs."
)
output.success = False
return output
@@ -452,7 +459,7 @@ async def async_request_v1_videos(
elif "peak_memory_mb" in resp_json:
output.peak_memory_mb = resp_json["peak_memory_mb"]
except Exception as e:
output.error = str(e)
output.error = f"{type(e).__name__}: {e}"
output.success = False
finally:
if image_file is not None:
@@ -465,13 +472,13 @@ async def async_request_v1_videos(
except Exception as e:
print(f"Failed to clean up video job {job_id}: {e}")
output.latency = time.perf_counter() - output.start_time
output.latency = time.perf_counter() - output.start_time
if pbar is not None:
pbar.update(1)
if output.success and input.slo_ms is not None:
output.slo_achieved = (output.latency * 1000.0) <= float(input.slo_ms)
if pbar:
pbar.update(1)
return output
@@ -1,6 +1,6 @@
# adapted from fastvideo
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""
Benchmark online serving for diffusion models (Image/Video Generation).
@@ -87,6 +87,7 @@ import base64
import glob
import json
import logging
import math
import os
import random
import tempfile
@@ -1116,6 +1117,11 @@ def calculate_metrics(
"duration": total_duration,
"completed_requests": num_success,
"failed_requests": len(error_outputs),
"request_errors": [
{"request_id": req.request_id, "error": out.error}
for req, out in zip(requests_list, outputs)
if not out.success
],
"throughput_qps": num_success / total_duration if total_duration > 0 else 0,
"latency_mean": np.mean(latencies) if latencies else 0,
"latency_median": np.median(latencies) if latencies else 0,
@@ -1261,6 +1267,9 @@ def _default_endpoint_for_task(task: str) -> str:
async def benchmark(args):
if not math.isfinite(args.video_job_timeout) or args.video_job_timeout <= 0:
raise ValueError("--video-job-timeout must be a positive finite number of seconds.")
# Construct base_url if not provided
if args.base_url is None:
args.base_url = f"http://{args.host}:{args.port}"
@@ -1312,6 +1321,8 @@ async def benchmark(args):
print("Loading requests...")
requests_list = dataset.get_requests()
print(f"Prepared {len(requests_list)} requests from {args.dataset} dataset.")
for req in requests_list:
req.video_job_timeout = args.video_job_timeout
if args.return_stage_metrics and args.endpoint in _STAGE_METRICS_ENDPOINTS:
for req in requests_list:
@@ -1376,6 +1387,8 @@ async def benchmark(args):
metrics["model"] = args.model
metrics["dataset"] = args.dataset
metrics["task"] = args.task
if args.endpoint == "/v1/videos":
metrics["video_job_timeout"] = args.video_job_timeout
if args.endpoint == "/v1/images/edits":
metrics["bot_task"] = args.bot_task
@@ -1398,6 +1411,11 @@ async def benchmark(args):
)
)
print("{:<40} {}/{:<15}".format("Successful requests:", metrics["completed_requests"], len(requests_list)))
print("{:<40} {:<15}".format("Failed requests:", metrics["failed_requests"]))
for failure in metrics["request_errors"][:10]:
print(f"Request {failure['request_id']} failed: {failure['error']}")
if metrics["failed_requests"] > 10:
print("Showing first 10 failures; use --output-file to save all request errors.")
# Section 3: Performance Metrics
print(f"{'-' * 50}")
@@ -1481,6 +1499,13 @@ if __name__ == "__main__":
help="Path to local dataset file (optional).",
)
parser.add_argument("--num-prompts", type=int, default=10, help="Number of prompts to benchmark.")
parser.add_argument(
"--video-job-timeout",
type=float,
default=900.0,
help="Maximum polling time in seconds per video job, including server queue time (default: 900). "
"Increase this for long-running video generation or high concurrency.",
)
parser.add_argument(
"--max-concurrency",
type=int,
+53 -24
View File
@@ -2,7 +2,7 @@
A model-agnostic serving benchmark for TTS models in vllm-omni. One CLI
(`bench_tts.py`) + one YAML registry (`model_configs.yaml`) drive perf and
quality runs for every registered checkpoint: **Qwen3-TTS** (Base / CustomVoice)
quality runs for every registered checkpoint: **Qwen3-TTS** (Base / CustomVoice / VoiceDesign)
and **VoxCPM2** today, more to come.
The same three task types — `voice_clone`, `default_voice`, `voice_design` —
@@ -21,6 +21,11 @@ The server auto-loads its Deploy YAML from `vllm_omni/deploy/qwen3_tts.yaml`
(Pipeline + Deploy schema introduced in #2383). No explicit `--deploy-config`
flag is needed for a registered model.
Start the checkpoint that matches the benchmark task below: `-Base` for
`voice_clone`, `-CustomVoice` for `default_voice`, or `-VoiceDesign` for
`voice_design`. Restart the server with the matching checkpoint when switching
tasks; changing only the benchmark's `--model` does not reload the server.
### 2. Run the benchmark (`vllm bench serve --omni`)
The primary, directly-controllable path. Copy-paste one of these and tweak
@@ -62,12 +67,12 @@ vllm bench serve --omni \
--save-result --result-dir ./results
```
#### voice_design (Qwen3-TTS-CustomVoice, bundled seed_tts_design)
#### voice_design (Qwen3-TTS-VoiceDesign, bundled seed_tts_design)
```bash
vllm bench serve --omni \
--host 127.0.0.1 --port 8000 \
--model Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice \
--model Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign \
--backend openai-audio-speech \
--endpoint /v1/audio/speech \
--dataset-name seed-tts-design \
@@ -80,6 +85,25 @@ vllm bench serve --omni \
--save-result --result-dir ./results
```
#### Set the sample rate for models that are not 24 kHz
Streamed PCM carries no header, so the harness derives `audio_duration` from the
byte count and a **default 24000 Hz** assumption
(`vllm_omni/metrics/definitions.py:DEFAULT_AUDIO_SAMPLE_RATE`). For a model that
emits another rate, export the real one or every duration-derived metric is
wrong by `real_sr / 24000`:
```bash
# Audio8 TTS, Fish Speech S2 Pro, Ming-omni-tts, ... all decode at 44.1 kHz
export VLLM_OMNI_BENCH_AUDIO_SAMPLE_RATE=44100
```
Without it, a 44.1 kHz model reports `audio_duration` 1.84x too long, so
`audio_rtf` looks 1.84x *better* than reality and `audio_underrun` /
`Streaming continuity OK rate` are measured against inflated chunk budgets.
A quick sanity check: compare `Mean AUDIO_DURATION (s)` against the actual
length of a saved WAV for the same prompt.
#### Streaming continuity (`audio_underrun`)
`audio_underrun` (seconds) is the per-request worst-case buffer deficit
@@ -180,11 +204,11 @@ Outputs TTFP / RTF / throughput curves (and a markdown table) for every
## Task types
| Task | Dataset | Request body | Checkpoints that support it |
|-----------------|-------------------|-----------------------------------------------------|------------------------------------------|
| `voice_clone` | `seed-tts` | `ref_audio` + `ref_text` + `task_type=Base` | `Qwen3-TTS-*-Base`, `VoxCPM2` |
| `default_voice` | `seed-tts-text` | `voice=Vivian` + `task_type=CustomVoice` | `Qwen3-TTS-*-CustomVoice` |
| `voice_design` | `seed-tts-design` | `instructions=<natural-language description>` + `task_type=VoiceDesign` | `Qwen3-TTS-*-CustomVoice` |
| Task | Dataset | Request body | Checkpoints that support it |
| --------------- | ----------------- | ----------------------------------------------------------------------- | ----------------------------- |
| `voice_clone` | `seed-tts` | `ref_audio` + `ref_text` + `task_type=Base` | `Qwen3-TTS-*-Base`, `VoxCPM2` |
| `default_voice` | `seed-tts-text` | `voice=Vivian` + `task_type=CustomVoice` | `Qwen3-TTS-*-CustomVoice` |
| `voice_design` | `seed-tts-design` | `instructions=<natural-language description>` + `task_type=VoiceDesign` | `Qwen3-TTS-*-VoiceDesign` |
**`-CustomVoice` checkpoints do NOT ship `speaker_encoder` weights**, so
voice_clone requests raise `ValueError` at model runtime. Use `-Base` for
@@ -210,12 +234,12 @@ Then add the model's Deploy YAML under `vllm_omni/deploy/<model>.yaml`
## Datasets
| Dataset | Bundled? | Format | Source |
|--------------------|----------|-------------------|----------------------------------------------------------------|
| `seed-tts-design` | ✅ | 5-field meta.lst | `benchmarks/build_dataset/seed_tts_design/en/meta.lst` (20 prompts) |
| `seed_tts_smoke` | ✅ | 4-field meta.lst | `benchmarks/build_dataset/seed_tts_smoke/en/meta.lst` (20 text-only) |
| `seed-tts` | ❌ | 4-field meta.lst + WAVs | Google-Drive: [BytedanceSpeech/seed-tts-eval][seedtts] (~1.2 GB) |
| `seed-tts-text` | ❌ | 4-field meta.lst | Same archive as `seed-tts` (wav column unused) |
| Dataset | Bundled? | Format | Source |
| ----------------- | -------- | ----------------------- | -------------------------------------------------------------------- |
| `seed-tts-design` | ✅ | 5-field meta.lst | `benchmarks/build_dataset/seed_tts_design/en/meta.lst` (20 prompts) |
| `seed_tts_smoke` | ✅ | 4-field meta.lst | `benchmarks/build_dataset/seed_tts_smoke/en/meta.lst` (20 text-only) |
| `seed-tts` | ❌ | 4-field meta.lst + WAVs | Google-Drive: [BytedanceSpeech/seed-tts-eval][seedtts] (~1.2 GB) |
| `seed-tts-text` | ❌ | 4-field meta.lst | Same archive as `seed-tts` (wav column unused) |
[seedtts]: https://github.com/BytedanceSpeech/seed-tts-eval
@@ -227,11 +251,11 @@ For manual voice_clone / default_voice runs against the full corpus, follow
`tests/dfx/perf/tests/test_tts.json` wires three perf regimes plus quality:
| eval_phase | concurrency | purpose | Baseline metrics |
|---------------|-------------|---------------------------------------------------------|-----------------------------------------|
| `latency` | 1 | Single-request TTFP / RTF SLO | `median_audio_ttfp_ms`, `median_audio_rtf` |
| `throughput` | 8 | Codec-batching cliff sentinel (PDF #272 concurrency≥8) | `median_audio_ttfp_ms`, `median_audio_rtf` |
| `quality` | 4 | WER / SIM / UTMOS regression (disabled in CI by default)| `mean_audio_rtf` |
| eval_phase | concurrency | purpose | Baseline metrics |
| ------------ | ----------- | -------------------------------------------------------- | ------------------------------------------ |
| `latency` | 1 | Single-request TTFP / RTF SLO | `median_audio_ttfp_ms`, `median_audio_rtf` |
| `throughput` | 8 | Codec-batching cliff sentinel (PDF #272 concurrency≥8) | `median_audio_ttfp_ms`, `median_audio_rtf` |
| `quality` | 4 | WER / SIM / UTMOS regression (disabled in CI by default) | `mean_audio_rtf` |
Why `median_*` for latency/throughput and `mean_*` for quality: latency
distributions have cold-start tails that drag the mean; quality aggregates
@@ -245,10 +269,15 @@ PR #2558 — quality runs are manual / release-validation, not nightly).
Observed on H20-3e, Qwen3-TTS-1.7B (measured pre-merge on this branch):
| Task | Model | c=1 | c=4 | **c=8** | c=16 | c=32 |
|---------------|---------------|--------|--------|------------|--------|--------|
| voice_clone | 1.7B-Base | RTF 0.15 / TTFP 165ms | 0.28 / 412ms | **0.49 / 1701ms** | 0.72 / 3355ms | 0.77 / 3772ms |
| voice_design | 1.7B-CustomVoice | RTF 0.08 / TTFP 53ms | 0.11 / 154ms | **0.21 / 872ms** | 0.33 / 1801ms | 0.38 / 1989ms |
| Task | Model | c=1 | c=4 | **c=8** | c=16 | c=32 |
| ------------ | ---------------- | --------------------- | ------------ | ----------------- | ------------- | ------------- |
| voice_clone | 1.7B-Base | RTF 0.15 / TTFP 165ms | 0.28 / 412ms | **0.49 / 1701ms** | 0.72 / 3355ms | 0.77 / 3772ms |
| voice_design | 1.7B-CustomVoice | RTF 0.08 / TTFP 53ms | 0.11 / 154ms | **0.21 / 872ms** | 0.33 / 1801ms | 0.38 / 1989ms |
The historical `voice_design` row above lists a CustomVoice checkpoint, which
current task validation rejects for VoiceDesign requests. It does not establish
a VoiceDesign checkpoint baseline; rerun with `Qwen3-TTS-12Hz-1.7B-VoiceDesign`
before comparing performance.
Both models show a **4–6× TTFP jump from c=4 to c=8** while audio throughput
saturates around c=4–8 — the codec-bs=1 bottleneck documented in
@@ -257,7 +286,7 @@ sentinel for regressions in this area.
## File layout
```
```text
benchmarks/tts/
├── README.md (this file)
├── bench_tts.py CLI — serve-mode benchmark driver
+19 -1
View File
@@ -17,12 +17,23 @@ models:
extra_params:
lang: en
text_normalization: true
# Audio8 TTS Preview: DualAR, 44.1 kHz. trust_remote_code stays false.
# Bench at 44.1 kHz: export VLLM_OMNI_BENCH_AUDIO_SAMPLE_RATE=44100, otherwise
# the streamed-PCM duration defaults to 24 kHz and RTF looks 1.84x too good.
Audio8/Audio8-TTS-Preview-0.6b:
supported_tasks: [default_voice, voice_clone]
backend: openai-audio-speech
endpoint: /v1/audio/speech
trust_remote_code: false
task_extra_body:
default_voice: {}
voice_clone: {}
# -CustomVoice checkpoints lack speaker_encoder weights, so voice_clone is
# NOT supported (an attempt raises ValueError from _extract_speaker_embedding
# at model runtime). Use -Base for voice_clone.
Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice:
supported_tasks: [default_voice, voice_design]
supported_tasks: [default_voice]
backend: openai-audio-speech
endpoint: /v1/audio/speech
trust_remote_code: true
@@ -31,6 +42,13 @@ models:
voice: Vivian
language: English
task_type: CustomVoice
Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign:
supported_tasks: [voice_design]
backend: openai-audio-speech
endpoint: /v1/audio/speech
trust_remote_code: true
task_extra_body:
voice_design:
task_type: VoiceDesign
language: English
+6 -23
View File
@@ -1,27 +1,10 @@
# Target vllm version: v0.28.0 (tag v0.28.0 @ 2cf0a6915ce5).
# 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. The 0.28 rebase repeated the cycle: the v0.28.0rc / final-tag wheels
# rode on the v0.27.0 base image with the full reinstall block until the
# official v0.28.0 image shipped, at which point this file dropped back to the
# form you are reading. 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.
# Target vLLM version: v0.29.0 (tag @ 98dff2a81d74).
# Use the matching release image and its coherent CUDA/PyTorch/FlashInfer
# runtime. Do not reinstall a commit wheel or repair dependencies on top of
# an older base image at a release boundary. For a future unreleased target,
# restore an explicit wheel pin and validate its complete runtime stack.
ARG VLLM_BASE_IMAGE=vllm/vllm-openai
ARG VLLM_BASE_TAG=v0.28.0
ARG VLLM_BASE_TAG=v0.29.0
# We use registry-based layer caching for the dependencies,
# where the tag is determined based on the hash of the contents
+4 -16
View File
@@ -1,20 +1,7 @@
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
ARG VLLM_ASCEND_TAG=v0.28.0
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
# WORKDIR /vllm-workspace/vllm
# RUN git fetch origin --tags && git checkout v0.18.0
# WORKDIR /vllm-workspace/vllm-ascend
# RUN git fetch origin releases/v0.18.0 && git checkout d781902ce9dbda8ab1e11bb0f2f0c1bc508fee7a
# # Install vllm-ascend
# # Append `libascend_hal.so` path (devlib) to LD_LIBRARY_PATH
# RUN export PIP_EXTRA_INDEX_URL=https://mirrors.huaweicloud.com/ascend/repos/pypi && \
# source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
# source /usr/local/Ascend/nnal/atb/set_env.sh && \
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
# python3 -m pip install -v -e /vllm-workspace/vllm-ascend/ --extra-index https://download.pytorch.org/whl/cpu/ && \
# python3 -m pip cache purge
ARG VLLM_ASCEND_VERSION=v0.28.0
ARG VLLM_ASCEND_DEVICE_SUFFIX=
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_VERSION}${VLLM_ASCEND_DEVICE_SUFFIX}
ARG APP_DIR=/vllm-workspace/vllm-omni
WORKDIR ${APP_DIR}
@@ -24,6 +11,7 @@ RUN export PIP_EXTRA_INDEX_URL=https://mirrors.huaweicloud.com/ascend/repos/pypi
source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
source /usr/local/Ascend/nnal/atb/set_env.sh && \
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
python3 -m pip install -v "mindiesd==3.1.0" && \
python3 -m pip install -v -e /vllm-workspace/vllm-omni/ --no-build-isolation
RUN python3 -m pip install -v --no-deps \
-36
View File
@@ -1,36 +0,0 @@
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
ARG VLLM_ASCEND_TAG=v0.28.0-a3
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
# WORKDIR /vllm-workspace/vllm
# RUN git fetch origin --tags && git checkout v0.18.0
# WORKDIR /vllm-workspace/vllm-ascend
# RUN git fetch origin releases/v0.18.0 && git checkout d781902ce9dbda8ab1e11bb0f2f0c1bc508fee7a
# # Install vllm-ascend
# # Append `libascend_hal.so` path (devlib) to LD_LIBRARY_PATH
# RUN export PIP_EXTRA_INDEX_URL=https://mirrors.huaweicloud.com/ascend/repos/pypi && \
# source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
# source /usr/local/Ascend/nnal/atb/set_env.sh && \
# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
# python3 -m pip install -v -e /vllm-workspace/vllm-ascend/ --extra-index https://download.pytorch.org/whl/cpu/ && \
# python3 -m pip cache purge
ARG APP_DIR=/vllm-workspace/vllm-omni
WORKDIR ${APP_DIR}
COPY . .
RUN export PIP_EXTRA_INDEX_URL=https://mirrors.huaweicloud.com/ascend/repos/pypi && \
source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
source /usr/local/Ascend/nnal/atb/set_env.sh && \
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
python3 -m pip install -v -e /vllm-workspace/vllm-omni/ --no-build-isolation
RUN python3 -m pip install -v --no-deps \
"stepaudio2-minicpmo>=0.1.1" \
"step-audio2>=1.0.0"
ENV VLLM_TARGET_DEVICE=npu
ENV VLLM_WORKER_MULTIPROC_METHOD=spawn
ENTRYPOINT []
+12 -8
View File
@@ -1,10 +1,10 @@
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
ARG VLLM_ASCEND_TAG=v0.28.0
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
ARG VLLM_ASCEND_VERSION=v0.28.0
ARG VLLM_ASCEND_DEVICE_SUFFIX=
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_VERSION}${VLLM_ASCEND_DEVICE_SUFFIX}
ARG APP_DIR=/vllm-workspace/vllm-omni
WORKDIR ${APP_DIR}
COPY . .
RUN sed -i "s@^\(deb.*\)https\?://[a-z0-9.-]*\.ubuntu\.com@\1http://cache-service.nginx-pypi-cache.svc.cluster.local:8081@g" /etc/apt/sources.list
@@ -22,11 +22,9 @@ RUN apt-get update && \
rm -rf /var/cache/apt/* && \
rm -rf /var/lib/apt/lists/*
RUN source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
source /usr/local/Ascend/nnal/atb/set_env.sh && \
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
python3 -m pip install -U pip && \
RUN python3 -m pip install -U pip && \
python3 -m pip install -v \
"mindiesd==3.1.0" \
"pytest>=7.0.0" \
"pytest-asyncio>=0.21.0" \
"pytest-cov>=4.0.0" \
@@ -48,7 +46,13 @@ RUN source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
"datasets>=3.0.0" \
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
--default-timeout=60 && \
--default-timeout=60
COPY . .
RUN source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
source /usr/local/Ascend/nnal/atb/set_env.sh && \
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
python3 -m pip install -v -e /vllm-workspace/vllm-omni/ --no-build-isolation \
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
-72
View File
@@ -1,72 +0,0 @@
ARG VLLM_ASCEND_IMAGE=quay.io/atlas-ci/vllm-ascend
ARG VLLM_ASCEND_TAG=v0.28.0-a3
FROM ${VLLM_ASCEND_IMAGE}:${VLLM_ASCEND_TAG}
ARG APP_DIR=/vllm-workspace/vllm-omni
WORKDIR ${APP_DIR}
COPY . .
RUN sed -i "s@^\(deb.*\)https\?://[a-z0-9.-]*\.ubuntu\.com@\1http://cache-service.nginx-pypi-cache.svc.cluster.local:8081@g" /etc/apt/sources.list
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ffmpeg \
libgl1 \
libglib2.0-0 \
libgomp1 \
libsm6 \
libxext6 \
libxrender1 \
libxcb1 \
espeak-ng && \
rm -rf /var/cache/apt/* && \
rm -rf /var/lib/apt/lists/*
RUN source /usr/local/Ascend/ascend-toolkit/set_env.sh && \
source /usr/local/Ascend/nnal/atb/set_env.sh && \
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \
python3 -m pip install -U pip && \
python3 -m pip install -v \
"pytest>=7.0.0" \
"pytest-asyncio>=0.21.0" \
"pytest-cov>=4.0.0" \
"pytest-mock>=3.10.0" \
"openpyxl>=3.0.0" \
"psutil>=7.2.0" \
"soundfile>=0.13.1" \
"pyttsx3>=2.99" \
"opencc==1.4.1" \
"opencv-python>=4.12.0.88" \
"imageio[ffmpeg]>=0.6.0" \
"jiwer>=3.0.0" \
"zhon>=2.0.0" \
"zhconv>=1.4.2" \
"funasr>=1.0.0" \
"hyperpyyaml>=1.2.0" \
"onnx>=1.16.0" \
"librosa>=0.10.0" \
"torchmetrics>=1.9.0" \
"datasets>=3.0.0" \
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
--default-timeout=60 && \
python3 -m pip install -v -e /vllm-workspace/vllm-omni/ --no-build-isolation \
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
--default-timeout=60
# MiniCPM-o 4.5 Stage-2 vocoder assets: s3tokenizer plus flashcosyvoice, which
# is shipped by step-audio2. --no-deps keeps pip from replacing the Ascend torch
# build and from shadowing onnxruntime-cann with the vanilla onnxruntime wheel;
# the deps these two actually need are installed above.
RUN python3 -m pip install -v --no-deps \
"s3tokenizer>=0.3.0" \
"step-audio2>=1.0.0" \
--index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple \
--trusted-host cache-service.nginx-pypi-cache.svc.cluster.local \
--default-timeout=60
ENV VLLM_TARGET_DEVICE=npu
ENV VLLM_WORKER_MULTIPROC_METHOD=spawn
ENTRYPOINT []
+5 -2
View File
@@ -17,6 +17,7 @@ nav:
- Streaming and Realtime:
- Realtime Audio: serving/realtime_api.md
- Full Duplex: serving/full_duplex_api.md
- Realtime Duplex Reference: serving/realtime_duplex_api.md
- Streaming Video Input: serving/video_stream_api.md
- Streaming Video Output: serving/streaming_video_output_api.md
- OpenPI Robot Policy: serving/openpi_api.md
@@ -65,13 +66,14 @@ nav:
- ModelOpt: user_guide/quantization/modelopt.md
- AutoRound: user_guide/quantization/autoround.md
- msModelSlim: user_guide/quantization/msmodelslim.md
- TorchAO: user_guide/quantization/torchao.md
- Diffusion Acceleration:
- Overview and Compatibility:
- Overview: user_guide/diffusion_features.md
- Feature Compatibility: user_guide/feature_compatibility.md
- CPU Offloading:
- Overview: user_guide/diffusion/cpu_offload.md
- Model-Level: user_guide/diffusion/offloader/cpu_offload.md
- Model-Level: user_guide/diffusion/offloader/module_offload.md
- Layerwise: user_guide/diffusion/offloader/layerwise_offload.md
- Distributed Layerwise: user_guide/diffusion/offloader/distributed_layerwise_offload.md
- Cache Acceleration:
@@ -125,6 +127,7 @@ nav:
- Async Chunk: design/feature/async_chunk.md
- Async Diffusion Output: design/feature/async_diffusion_output.md
- Async Omni Output Materialization: design/feature/omni_async_output_materialization.md
- Prefill/Decode Phase Contract: design/feature/preprocess_phase_contract.md
- Automatic Prefix Caching: design/feature/prefix_caching.md
- Realtime AR-Diffusion: design/feature/realtime_ar_diffusion.md
- Communication:
@@ -150,7 +153,7 @@ nav:
- Skip-Softmax: design/feature/skip_softmax.md
- CPU Offloading:
- Overview: design/feature/offloader/README.md
- Model-Level: design/feature/offloader/cpu_offload.md
- Model-Level: design/feature/offloader/module_offload.md
- Layerwise: design/feature/offloader/layerwise_offload.md
- Distributed Layerwise: design/feature/offloader/distributed_layerwise_offload.md
- Cache-DiT: design/feature/cache_dit.md
+1 -2
View File
@@ -23,7 +23,6 @@ Easy, fast, and cheap omni-modality model serving for everyone
<a class="github-button" href="https://github.com/vllm-project/vllm-omni/fork" data-show-count="true" data-icon="octicon-repo-forked" data-size="large" aria-label="Fork">Fork</a>
</p>
## About
[vLLM](https://github.com/vllm-project/vllm) was originally designed to support large language models for text-based autoregressive generation tasks. vLLM-Omni is a framework that extends its support for omni-modality model inference and serving:
@@ -52,7 +51,7 @@ vLLM-Omni is flexible and easy to use with:
- Tensor, pipeline, data and expert parallelism support for distributed inference
- Streaming outputs
- OpenAI-compatible API server
- Full-duplex realtime serving with streaming audio input and output (experimental)
- [Full-duplex realtime serving](serving/realtime_duplex_api.md) with streaming audio input and output
vLLM-Omni seamlessly supports most popular open-source models on HuggingFace, including:
+30
View File
@@ -15,6 +15,36 @@ Main entry points for vLLM-Omni inference and serving.
- [vllm_omni.entrypoints.omni_base.OmniBase][]
- [vllm_omni.entrypoints.pd_utils.PDDisaggregationMixin][]
## Clients
Client libraries that talk to a running vLLM-Omni server (the full-duplex Realtime client and its per-model session presets).
- [vllm_omni.clients.duplex.AudioDelta][]
- [vllm_omni.clients.duplex.AudioFormat][]
- [vllm_omni.clients.duplex.ConnectionResumed][]
- [vllm_omni.clients.duplex.DuplexClient][]
- [vllm_omni.clients.duplex.DuplexClientError][]
- [vllm_omni.clients.duplex.DuplexConnectionError][]
- [vllm_omni.clients.duplex.DuplexEvent][]
- [vllm_omni.clients.duplex.DuplexProtocolError][]
- [vllm_omni.clients.duplex.DuplexSessionClosedError][]
- [vllm_omni.clients.duplex.ErrorEvent][]
- [vllm_omni.clients.duplex.EventCollector][]
- [vllm_omni.clients.duplex.ListenDecision][]
- [vllm_omni.clients.duplex.ReconnectPolicy][]
- [vllm_omni.clients.duplex.ResponseCreated][]
- [vllm_omni.clients.duplex.ResponseDone][]
- [vllm_omni.clients.duplex.ResponseHandle][]
- [vllm_omni.clients.duplex.SessionClosed][]
- [vllm_omni.clients.duplex.SessionConfig][]
- [vllm_omni.clients.duplex.SessionCreated][]
- [vllm_omni.clients.duplex.SessionExpired][]
- [vllm_omni.clients.duplex.SessionResumed][]
- [vllm_omni.clients.duplex.SpeakDecision][]
- [vllm_omni.clients.duplex.TextDelta][]
- [vllm_omni.clients.duplex.TranscriptDelta][]
- [vllm_omni.clients.duplex.WebSocketTransport][]
## Inputs
Input data structures for multi-modal inputs.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 272 KiB

+115 -7
View File
@@ -1,11 +1,15 @@
# vLLM-Omni Benchmark CLI Guide
The vllm bench command launches the vLLM-Omni benchmark to evaluate the performance of multimodal models.
## Notes
vLLM-Omni registers the `openai-chat-omni`, `openai-audio-speech`, `openai-image-edits-omni`, `daily-omni`, and `openai-realtime-duplex` serving benchmark backends. It also adds the `omniinteract` dataset.
## Basic Parameter Description
You can use `vllm bench serve --omni --help=all` to get descriptions of all parameters. The commonly used parameters are described below:
- `--omni`
Enable Omni (multimodal) mode, supporting multimodal inputs and outputs such as images, videos, and audio.
@@ -77,8 +81,8 @@ Specify to save benchmark results to a json file
- `--random-prefix-len`
Number of fixed prefix tokens before the random context in a request.
The total input length is the sum of random-prefix-len and a random
context length sampled from [input_len * (1 - range_ratio),
input_len * (1 + range_ratio)].Only the random and random-mm modes
context length sampled from [input_len *(1 - range_ratio),
input_len* (1 + range_ratio)].Only the random and random-mm modes
support this parameter.
- `--random-input-len`
@@ -91,7 +95,7 @@ Specify to save benchmark results to a json file
Range ratio for sampling input/output length,
used only for random sampling. Must be in the range [0, 1) to define
a symmetric sampling range
[length * (1 - range_ratio), length * (1 + range_ratio)].
[length *(1 - range_ratio), length* (1 + range_ratio)].
Only the random and random-mm modes support this parameter.
- `--random-mm-base-items-per-request`
@@ -137,6 +141,7 @@ Specify to save benchmark results to a json file
## Usage Examples
### Online Benchmark
<details class="admonition abstract" markdown="1">
<summary>Show more</summary>
@@ -162,7 +167,9 @@ vllm bench serve \
--dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \
--percentile-metrics ttft,tpot,itl,e2el
```
If successful, you will see the following output:
```text
============ Serving Benchmark Result ============
Successful requests: 2
@@ -264,6 +271,7 @@ Median AUDIO_RTF: 0.47
P99 AUDIO_RTF: 0.48
==================================================
```
Notes:
We use audio generation time / audio duration to calculate RTF.
@@ -301,12 +309,36 @@ options apply. Use `--omniinteract-require-response` only for functional E2E cas
Each completed case writes `output.wav`, `wav_transcript.json`, `events.json`, `result.json`, and a final `.done` marker under
`--omniinteract-output-dir`. The root also contains `batch_summary.json` and `official_eval_manifest.jsonl`; failed cases write
`.failed.json`. Runs sharing one output root are serialized. Completion validates transport, response lifecycle, and artifacts,
not answer accuracy. Transcript timestamps are serialized playback-queue times. Clipped or cancelled outputs are ineligible and
omitted from the official manifest; `audio_clipped_bytes` records output beyond the rounded video horizon.
not answer accuracy. Transcript timestamps are serialized playback-queue times. Playback ACKs report cumulative progress
incrementally along that serialized clock, like a live listener; the first ack for a response goes out as soon as its audio
arrives, checkpointing the response's history position so a later committed user input updates it in place. A residual
`playback_ack_too_late` rejection is recorded as an artifact warning rather than failing the case. Clipped or cancelled
outputs are ineligible and omitted from the official manifest; `audio_clipped_bytes` records output beyond the rounded video
horizon.
TTFT, TTFP, and RTF start at client receipt of `response.created`. TPOT/ITL use engine stage-0 timing; ITL is emitted only when
every token interval is present.
The checked-in local performance configuration measures four deterministic cases from each OmniInteract subset (12 videos
total), with no benchmark warmups and a maximum concurrency of two. Each subset also sends one readiness request before its
measured cases. Run it from the repository root with:
```bash
export HF_HOME=/path/to/persistent/huggingface-cache
export BENCHMARK_DIR=tests/dfx/perf/results
bash tools/nightly/run_nightly_jobs.sh \
--test-type local \
--model-type omni \
--label-substr minicpmo_4_5_omniinteract
```
The first run downloads the pinned OmniInteract archive into `HF_HOME`; later runs reuse that cache.
It requires every case to commit its input, complete any emitted response lifecycles, and publish the expected WAV,
transcript, event, and result artifacts without errors. A valid LISTEN-only case may have no response audio or transcript
chunks. Official-manifest eligibility is reported separately because clipped or cancelled output is a benchmark-quality
signal, not a transport failure. This local performance test does not score answer accuracy.
### Multi-Modal Benchmark
<details class="admonition abstract" markdown="1">
@@ -319,8 +351,8 @@ Generate synthetic image、video、audio inputs alongside random text prompts to
Notes:
- Works only with online benchmark via:
- the OpenAI chat backend (`--backend openai-chat-omni`) and endpoint `/v1/chat/completions`.
- the OpenAI edit image backend (`--backend openai-image-edits-omni`) and endpoint `/v1/images/edits`.
- the OpenAI chat backend (`--backend openai-chat-omni`) and endpoint `/v1/chat/completions`.
- the OpenAI edit image backend (`--backend openai-image-edits-omni`) and endpoint `/v1/images/edits`.
Start the server (example):
@@ -331,6 +363,7 @@ vllm serve Qwen/Qwen2.5-Omni-7B --omni
It is recommended to use the flag `--ignore-eos` to simulate real responses. You can set the size of the output via the arg `random-output-len`.
Then run the benchmarking script:
```bash
vllm bench serve \
--omni \
@@ -400,8 +433,79 @@ How sampling works:
- If a modality (e.g., image) reaches its limit from `--random-mm-limit-mm-per-prompt`, all buckets of that modality are excluded and the remaining bucket probabilities are renormalized before continuing.
This should be seen as an edge case, and if this behavior can be avoided by setting `--random-mm-limit-mm-per-prompt` to a large number. Note that this might result in errors due to engine config `--limit-mm-per-prompt`.
- The resulting request contains synthetic image data in `multi_modal_data` (OpenAI Chat format). When `random-mm` is used with the OpenAI Chat backend, prompts remain text and MM content is attached via `multi_modal_data`.
</details>
### Image / video generation
For `/v1/images/generations`, `/v1/images/edits`, and `/v1/videos`, set `--endpoint` to that path and omit `--backend`. The endpoint path is registered as the bench request adapter, so a separate named backend is not required.
Example (`/v1/images/generations`):
```bash
vllm bench serve --omni \
--endpoint /v1/images/generations \
--dataset-name random \
--model ~/models/Qwen/Qwen-Image \
--tokenizer ~/models/Qwen/Qwen-Image/tokenizer \
--max-concurrency 1 \
--num-warmups 2 \
--num-prompts 10 \
--random-input-len 64 \
--random-output-len 1 \
--ignore-eos \
--percentile-metrics e2el \
--extra-body '{
"num_inference_steps": 20,
"seed": 42,
"true_cfg_scale": 4.0
}'
```
If successful, following output is like:
```text
============ Serving Benchmark Result ============
Successful requests: 3
Failed requests: 0
Maximum request concurrency: 1
Benchmark duration (s): 8.42
Request throughput (req/s): 0.36
Peak concurrent requests: 2.00
-------------------Peak Memory--------------------
Mean PEAK_MEMORY_MB (MB): 58832.00
Median PEAK_MEMORY_MB (MB): 58832.00
P99 PEAK_MEMORY_MB (MB): 58832.00
----------------End-to-end Latency----------------
Mean E2EL (ms): 2798.16
Median E2EL (ms): 2799.83
P99 E2EL (ms): 2800.03
================== Image Result ==================
Total images generated: 3
Image throughput (img/s): 0.36
Average pixels per image: 1048576.00
Mean denoise step latency (ms): 136.59
---------------- Image Generation ----------------
Mean IMAGE_GENERATION (ms): 2731.84
Median IMAGE_GENERATION (ms): 2732.02
P99 IMAGE_GENERATION (ms): 2734.65
==================================================
```
Use the same pattern with `--endpoint /v1/images/edits` or `--endpoint /v1/videos` (and model-specific `--extra-body` as needed). Pure image/video runs omit the Text Result section when there is no generated text.
`/v1/images/edits` defaults to **non-streaming JSON** (`stream=false`) so single-stage edit models are not rejected by the server. For multi-stage pipelines that need SSE (AR TTFT / image chunks), pass `"stream": true` in `--extra-body`.
`/v1/videos` is an async job API: the client creates a job, then polls until `completed`/`failed`. Measured **e2el** therefore includes client poll sleep and any overshoot after the job actually finishes. Tune polling via `--extra-body`:
- `poll_interval_s` (default `2.0`): sleep between status polls
- `poll_timeout_s` (default `21600`, i.e. 6 hours): give up waiting for the job
**VIDEO_RTF** prefers server-reported generation time when available:
- `video_rtf = video_generation_time_ms / 1000 / video_duration`, where `video_generation_time_ms` comes from response `stage_durations` (or `inference_time_s` as fallback)
- If generation time is missing, falls back to `e2el / video_duration` (this fallback *does* include poll overhead)
### Multi-Stage Benchmark
<details class="admonition abstract" markdown="1">
@@ -414,6 +518,7 @@ vllm serve --omni --port 29999 --model ~/Qwen3-Omni-30B-A3B-Instruct
```
Then run the benchmarking script (remember to add the flag `--print-stage`):
```bash
vllm bench serve --omni \
--dataset-name random \
@@ -433,6 +538,7 @@ vllm bench serve --omni \
```
Besides the "Serving Benchmark Result", there will be "Stage Benchmark Result" below:
```text
============= Stage Benchmark Result =============
=============== Stage 0 (thinker) ================
@@ -489,7 +595,9 @@ Mean AUDIO_RTF: 0.24
Median AUDIO_RTF: 0.26
P99 AUDIO_RTF: 0.27
```
Explanation:
- stage_gen_time: Time from submitting a request to a specific stage to that stage finishing generation.
- Serving TTFC (Time to First Chunk): Time from the HTTP request being accepted by the serving frontend to
+1 -1
View File
@@ -2,7 +2,7 @@
This section lists the most common options for running vLLM-Omni.
For options within a vLLM Engine, please refer to the [vLLM 0.28 configuration guide](https://docs.vllm.ai/en/v0.28.0/configuration/index.html).
For options within a vLLM Engine, please refer to the [vLLM 0.29 configuration guide](https://docs.vllm.ai/en/v0.29.0/configuration/index.html).
Each model defines fixed topology in a registered `PipelineConfig` and runtime
overrides in a deploy YAML.
+1 -1
View File
@@ -151,7 +151,7 @@ their keys only.
## Inherited vLLM variables
vLLM-Omni also reads variables through its aligned vLLM dependency. Refer to
the [vLLM 0.28 environment-variable reference](https://docs.vllm.ai/en/v0.28.0/configuration/env_vars.html)
the [vLLM 0.29 environment-variable reference](https://docs.vllm.ai/en/v0.29.0/configuration/env_vars.html)
for their definitions. This includes vLLM launch, cache, logging, plugin, ROCm,
XPU, ModelScope, and FlashInfer workspace settings.
+22
View File
@@ -262,6 +262,28 @@ 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.
## AI Usage Policy
AI tools are welcome in vLLM-Omni contributions. Contributors, including maintainers, remain responsible for everything they submit. These requirements apply to pull requests, issues, discussions, and review comments:
- **Disclose AI assistance.** Name the tools used and describe their role in the submission, including assistance with code, tests, documentation, or writing.
- **Understand your changes.** Review every generated change and be able to explain its behavior, design choices, and interaction with vLLM-Omni without relying on AI to answer for you.
- **Validate before submitting.** Run relevant checks and report actual results. For model, kernel, or performance changes, include the hardware, model, configuration, and commands needed to reproduce accuracy or benchmark evidence. Clearly identify checks you could not run; generated claims are not test results.
- **Keep communication useful.** Verify technical claims, search for existing reports, and edit AI-assisted text for accuracy and brevity. Submit only content you have personally reviewed, and take responsibility for responding to review feedback.
- **Label generated media.** Model-generated images, video, and audio are valid examples or test artifacts for this multimodal project. Identify them as generated output and provide the model, inputs, and reproduction settings. Do not present them as ground-truth references or independent evidence of correctness.
For example, a PR description could include:
```text
AI assistance: Used Codex to draft the regression test and PR description.
I reviewed the changes and verified the test reproduces the reported failure.
Validation: <commands, results, hardware, and any checks not run>.
```
Maintainer review time is limited. Contributions that are unverified, misleading, or beyond the author's understanding may be closed. Repeated violations may lead to contribution restrictions. New contributors are welcome to ask for help and submit small, well-understood changes.
This policy is adapted from [Ghostty's AI Usage Policy](https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md) for vLLM-Omni's contribution and multimodal validation workflows. The [coding-agent workflow](#using-repository-skills-with-coding-agents) provides practical guidance for applying it.
## 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.
+10 -3
View File
@@ -129,7 +129,7 @@ Canonical layout (prefer these paths for new changes):
=== "NPU"
**Bootstrap:** [`npu/pipeline-npu.yml`](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/npu/pipeline-npu.yml) + [`npu/bootstrap-upload-steps.yml`](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/npu/bootstrap-upload-steps.yml)—same split as CUDA; builds A2/B3 and A3 CI images, then uploads child test pipelines.
**Bootstrap:** [`npu/pipeline-npu.yml`](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/npu/pipeline-npu.yml) + [`npu/bootstrap-upload-steps.yml`](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/npu/bootstrap-upload-steps.yml)—same split as CUDA; builds A2/B3, A3, A5, and 310P CI images, then uploads child test pipelines.
**Test YAML:** `npu/test-npu-ready.yml` (L2), `test-npu-nightly.yml` (L4).
@@ -137,7 +137,12 @@ Canonical layout (prefer these paths for new changes):
**Upload:** same as CUDA—`upload_pipeline.py --upload`.
**Hardware in YAML:** `mirror_hardwares` preset (string), expanded to `agents`, top-level `image`, and `plugins`. Presets: `a2b3_npu_1`, `a2b3_npu_4`, `a3_npu_2` in `common/ci_mirror_hardwares.yml`.
**Hardware in YAML:** `mirror_hardwares` preset (string), expanded to `agents`, top-level `image`, and `plugins`. NPU presets in `common/ci_mirror_hardwares.yml` are:
- A2: `a2b3_npu_1`, `a2b3_npu_4`, `a2b3_npu_8`
- A3: `a3_npu_2`, `a3_npu_4`, `a3_npu_8`, `a3_npu_16`
- A5: `a5_npu_2`, `a5_npu_4`, `a5_npu_8`, `a5_npu_16`
- 310P: `310p_npu_1`, `310p_npu_2`, `310p_npu_4`
**Conventions**
@@ -161,7 +166,9 @@ Canonical layout (prefer these paths for new changes):
**Bootstrap:** [`amd/scripts/bootstrap-amd-omni.sh`](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/amd/scripts/bootstrap-amd-omni.sh)—skip-ci, diff filtering, Jinja render, then `buildkite-agent pipeline upload`.
**Test YAML (data):** `amd/test-amd-ready.yml` (L2 / PR), `test-amd-merge.yml` (L3 / main).
**Test YAML (data):** `amd/test-amd-ready.yml` (L2 / `ready`), `test-amd-merge.yml` (L3 / `merge-test` and `main`). On a PR carrying both labels, AMD combines both suites behind one image build. PR builds without either tier label retain the legacy L2 fallback for compatibility with the `amd-test` trigger. `DEBUG_TEST_YAML` remains an explicit override. AMD has no L4 file yet, so `nightly-test` does not select an additional suite.
**Trigger boundary:** AMD label handling has two layers. First, the external `vllm-omni-amd-ci` Buildkite pipeline condition decides whether a GitHub label event creates a build. Only after that build starts does this repository's bootstrap inspect all current PR labels and select L2, L3, or both. Repository-side selection therefore cannot make a `merge-test` event start AMD CI by itself. The external condition must admit both `ready` and `merge-test`, while preserving the legacy `amd-test` trigger and non-PR `main` / scheduled builds. For PR builds without a `DEBUG_TEST_YAML` override, an unreadable label set fails the bootstrap instead of silently selecting the wrong tier. Debug overrides skip label lookup and are validated directly; invalid values, including whitespace-only values, fail the bootstrap. Keep `nightly-test` out of the AMD condition until an AMD L4 suite exists; otherwise it would start a build without providing nightly coverage.
**Rendering:** [`test-template-amd-omni.j2`](https://github.com/vllm-project/vllm-omni/blob/main/.buildkite/amd/test-template-amd-omni.j2) wraps data steps with `amd-build` image build and `amd_<agent_pool>` queues. Do **not** hand-edit generated `pipeline.yaml`.
+3 -1
View File
@@ -48,6 +48,8 @@ Defined in `pyproject.toml`:
| `MI325` | Tests that require MI325 GPU (AMD/ROCm) * |
| `A2` | Tests that require A2 NPU * |
| `A3` | Tests that require A3 NPU * |
| `A5` | Tests that require A5 NPU * |
| `310P` | Tests that require 310P NPU * |
| `cards_{n}` | Tests that require *n* accelerator cards (`cards_1` … `cards_8`); auto-added from `num_cards` * |
| `slow` | Slow tests (may skip in quick CI) |
| `benchmark` | Benchmark tests (decorator on runner test functions; perf JSON uses `full_model` + type marker instead) |
@@ -76,7 +78,7 @@ def test_video_to_audio()
This decorator is intended to make hardware-aware, cross-platform test authoring easier and more robust for CI/CD environments. The `hardware_test` decorator in `vllm-omni/tests/helpers/mark.py` performs the following actions:
1. **Applies platform and resource markers**
Adds the appropriate pytest markers for each specified hardware platform (e.g., `cuda`, `rocm`, `xpu`, `npu`) and resource type (e.g., `L4`, `H100`, `H200`, `B200`, `MI325`, `B60`, `A2`, `A3`).
Adds the appropriate pytest markers for each specified hardware platform (e.g., `cuda`, `rocm`, `xpu`, `npu`) and resource type (e.g., `L4`, `H100`, `H200`, `B200`, `MI325`, `B60`, `A2`, `A3`, `A5`, `310P`).
```python
@pytest.mark.cuda
@pytest.mark.L4
@@ -779,6 +779,31 @@ turning them on. For Qwen-Image-style serving examples, document
`--step-execution` as the feature gate and `--max-num-seqs N` as the
companion batching knob.
### Streaming Output and Mid-Stream Interactions
To add streaming generation with optional mid-stream prompt updates to a new diffusion pipeline (often a video generation pipeline):
0. **Model should internally support** chunked video emission and bounded VAE decode of a chunk.
1. **Implement stepwise execution** so interaction RPCs can be consumed at chunk
boundaries (see [Step Execution](#step-execution) above).
2. **Mix in** ``InteractionMixin`` on the pipeline class.
3. **Implement** ``peek_chunk_media(StepRequestState) -> ChunkMediaSpec`` to offer information
about the next chunk to generate.
This is called whenever one supported interaction modality needs frame-level calculation,
e.g., per-frame camera trajectory. If only chunk-level calculation is involved, peeking is skipped.
4. **Optionally implement** ``prepare_next_chunk(StepRequestState)`` if the
pipeline must rebuild something (e.g., per-chunk state) after an interaction is applied.
This function is called at chunk boundary.
5. **Register** supported modalities and handlers in ``vllm_omni/diffusion/interaction/registry.py``
(keyed by ``od_config.model_class_name`` → modality key as in ``req["multi_modal_data"]``).
Serve with ``--diffusion-streaming-output`` (auto-enables step execution). See
[Diffusion Execution Modes](../../user_guide/diffusion/execution_modes.md#streaming-output)
and the example script at `examples/online_serving/streaming_video_generation/`.
**Interaction payload contract:** Although ``OmniInteractionPrompt`` type annocation requires ``event_id``,
the WebSocket API payload may omit ``event_id`` and let the API layer assigns one.
### Cache Acceleration
#### TeaCache
+36 -17
View File
@@ -29,7 +29,7 @@ Each stage is implemented as a separate model class that can be configured indep
When adding a new model, you'll need to create the following structure:
```
```text
vllm_omni/model_executor/models/
└── your_model_name/ # Model directory (e.g., qwen3_omni)
├── __init__.py # Exports main model class
@@ -222,6 +222,16 @@ __all__ = ["Qwen3OmniMoeForConditionalGeneration"]
## Key Components
### Preprocess phase metadata
Models with `has_preprocess=True`, including out-of-tree plugins, can rely on
the runner's stable `_omni_is_prefill`, `_omni_prompt_len`, and
`_omni_num_computed_tokens` fields. Use request progress to distinguish prompt
prefill from decode: a one-token span can still be a prefill tail. See the
[runner-to-model phase contract](../../design/feature/preprocess_phase_contract.md)
for types, batched-hook semantics, MTP routing guarantees, and the correctness
workaround for vLLM-Omni 0.20.x and earlier.
### 1. Model Interfaces
Your model should implement the appropriate interfaces:
@@ -422,22 +432,25 @@ The stage transition process follows these steps:
Understanding the data structures is crucial for implementing stage transitions:
**Input to your function:**
- `stage_list[source_stage_id].engine_outputs`: List of `EngineCoreOutput` objects
- - Each contains `outputs`: List of `RequestOutput` objects
- Each `RequestOutput` has:
- - - `token_ids`: Generated token IDs
- `multimodal_output`: Dict with keys like `"code_predictor_codes"`, etc.These are the hidden states or intermediate outputs from the model's forward pass
- `prompt_token_ids`: Original prompt token IDs
- Each contains `outputs`: List of `RequestOutput` objects
- Each `RequestOutput` has:
- `token_ids`: Generated token IDs
- `multimodal_output`: Dict with keys like `"code_predictor_codes"`, etc. These are the hidden states or intermediate outputs from the model's forward pass
- `prompt_token_ids`: Original prompt token IDs
**Output from your function:**
- Must return `list[OmniTokensPrompt]` where each `OmniTokensPrompt` contains:
- - `prompt_token_ids`: List[int] - Token IDs for the next stage
- `prompt_token_ids`: List[int] - Token IDs for the next stage
- `additional_information`: Dict[str, Any] - Optional metadata (e.g., embeddings, hidden states)
- `multi_modal_data`: Optional multimodal data if needed
### How Model Outputs Are Stored
The model's `forward()` method returns an `OmniOutput` object that contains:
- `text_hidden_states`: Final hidden states for text generation
- `multimodal_outputs`: Dict containing intermediate outputs
@@ -464,6 +477,7 @@ def forward(self, ...):
```
These keys are then accessible in your stage transition function:
```python
# In stage_input_processors/qwen3_omni.py
thinker_prefill_embeddings = output.multimodal_output["0"] # Access by key
@@ -493,7 +507,7 @@ thinker_hidden_states = output.multimodal_output["24"]
Create stage transition processors in `vllm_omni/model_executor/stage_input_processors/your_model_name.py`. Each inter-stage edge should provide a **coherent processor set** rather than a single monolithic function:
| Suffix | Role | Runs when |
|--------|------|-----------|
| -------- | ------ | ----------- |
| `*_full_payload` | Worker-side payload producer | `async_chunk=false`; accumulates tensors and ships via connector |
| `*_async_chunk` | Scheduler-side streaming producer | `async_chunk=true`; emits per-chunk payloads |
| `*_token_only` | Orchestrator placeholder builder | `async_chunk=false`; allocates downstream prompt slots only |
@@ -554,7 +568,16 @@ For comprehensive testing guidelines, please refer to the [Test Writing Guide](.
## Adding a Model Recipe
After implementing and testing your model, please add a model recipe to the [vllm-project/recipes](https://github.com/vllm-project/recipes) repository. This helps other users understand how to use your model with vLLM-Omni.
After implementing and testing your model, add a recipe under this repository's
`recipes/` directory. Follow the
[`add-recipe` skill](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/add-recipe/SKILL.md)
and [`recipes/TEMPLATE.md`](https://github.com/vllm-project/vllm-omni/blob/main/recipes/TEMPLATE.md),
then update the model row in
[`recipes/README.md`](https://github.com/vllm-project/vllm-omni/blob/main/recipes/README.md).
The external
[vllm-project/recipes](https://github.com/vllm-project/recipes) repository can
be used as a structural reference, but it is not the destination for a
vLLM-Omni model recipe.
### What to Include
@@ -568,15 +591,10 @@ Your recipe should include:
3. **Usage Examples**: Command-line examples demonstrating how to run the model
4. **Configuration Details**: Important configuration parameters and their meanings
### Example
For reference, see the [LongCat recipe example](https://github.com/vllm-project/recipes/pull/179) which demonstrates the expected format and structure.
### Recipe Location
Create your recipe file in the appropriate directory structure:
- For organization-specific models: `OrganizationName/ModelName.md`
- For general models: `ModelName.md`
Create `recipes/<vendor>/<model-family>.md` and keep commands, feature status,
hardware evidence, and limitations specific to the model family.
The recipe should be a Markdown file that provides clear, reproducible instructions for users to get started with your model.
@@ -590,7 +608,7 @@ Adding a new model to vLLM-Omni involves:
4. **Define pipeline topology and deploy defaults** in `pipeline.py` and `vllm_omni/deploy/`
5. **Implement stage input processors** for stage transitions
6. **Write tests** to verify functionality
7. **Add model recipe** to the [vllm-project/recipes](https://github.com/vllm-project/recipes) repository (see [Adding a Model Recipe](#adding-a-model-recipe) section)
7. **Add an in-repository model recipe** under `recipes/` and update its index (see [Adding a Model Recipe](#adding-a-model-recipe))
### Qwen3-Omni Reference Files
@@ -607,6 +625,7 @@ For a complete reference implementation, see:
- **Testing**: `vllm_omni/tests/e2e/offline_inference/test_qwen3_omni.py`
For more information, see:
- [Architecture Overview](../../design/architecture_overview.md)
- [Supported Models](../../models/supported_models.md)
- [Pipeline and Deploy Configuration Guide](../../configuration/stage_configs.md)
+8 -5
View File
@@ -991,10 +991,13 @@ git push origin your-branch --force-with-lease
## Adding a Model Recipe
After implementing and testing your model, add a model recipe to the
[vllm-project/recipes](https://github.com/vllm-project/recipes) repository so users can
get started quickly. See [Adding an Omni-Modality Model](./adding_omni_model.md#adding-a-model-recipe)
for the expected format.
After implementing and testing your model, add a recipe under this repository's
`recipes/` directory. Follow the
[`add-recipe` skill](https://github.com/vllm-project/vllm-omni/blob/main/.claude/skills/add-recipe/SKILL.md)
and [`recipes/TEMPLATE.md`](https://github.com/vllm-project/vllm-omni/blob/main/recipes/TEMPLATE.md),
update the model row in
[`recipes/README.md`](https://github.com/vllm-project/vllm-omni/blob/main/recipes/README.md),
and synchronize the relevant TTS example and speech API documentation.
## Summary
@@ -1011,7 +1014,7 @@ Adding a TTS model to vLLM-Omni involves:
8. **Online serving** - add all 5 integration points to `serving_speech.py` in one commit
9. **Tests** - cover single request, batching, and streaming
10. **Pre-commit + DCO** - run `pre-commit` before pushing; sign every commit with `git commit -s`
11. **Model recipe** - add to [vllm-project/recipes](https://github.com/vllm-project/recipes)
11. **Model recipe** - add under the in-repository `recipes/` directory and update its index
12. **Invariants** - re-check I1–I5 (streaming contract, consumer hygiene, hot-loop discipline, validation pyramid, per-request state) at the end of every phase
### Qwen3-TTS Reference Files
+15 -10
View File
@@ -22,11 +22,15 @@ A pipeline opts in by implementing `SupportsARDiffusionPipeline`. Its immutable
- named fixed-length cross-attention KV, model-specific scratch geometry, and
the pipeline's requested session-capacity upper bound.
The runner binds an `ARDiffusionKVState` only around one `forward()` call. The
pipeline may use it during that context but must not retain it. The session
survives subsequent requests with the same `session_id`. Request reset, explicit
close, LRU eviction, and forward exceptions all release runner-owned KV and call
the pipeline's reset/close lifecycle hook. Cross-attention KV is allocated lazily
The runner binds an `ARDiffusionKVState` only around one runner invocation
(`execute_model` or `execute_stepwise`). The pipeline may use it during that
context but must not retain it. Scratch and committed pages stay on the
runner-owned session object, so unbinding does not drop an in-flight stepwise
chunk. Tick-mode sessions survive subsequent requests with the same
`session_id`. On the stepwise path the session id is the request id and is
released when that request finishes or fails. Request reset, explicit close,
LRU eviction, and forward exceptions all release runner-owned KV and call the
pipeline's reset/close lifecycle hook. Cross-attention KV is allocated lazily
per session and logical KV branch, and is released on the same path. A pipeline
publishes one named cache transactionally by yielding exactly one `(k, v)` pair
per layer:
@@ -48,11 +52,12 @@ successful forward. The runner also exposes `reset_session()` and
## Current execution limits
The experimental runner supports request-mode execution with
`max_num_seqs=1`. It rejects step execution and request-batch execution at load
time, and its inherited batch/step entry points also fail explicitly. Supporting
those paths requires a batch-aware state-binding contract rather than silently
bypassing `bind_ar_diffusion_state()`.
The experimental runner supports `max_num_seqs=1`. Request-batch execution is
still rejected. Step execution is allowed only when the loaded pipeline
implements `SupportsStepExecution`; other AR-Diffusion pipelines still fail at
load if `step_execution=True`. Capable pipelines bind KV around each
`execute_stepwise` invocation instead of inheriting the generic runner entry
point unwrapped.
The implementation may retain multiple sessions per model instance. It selects
the largest feasible capacity up to the pipeline's `session_capacity`, based on
+17 -8
View File
@@ -323,18 +323,24 @@ inputs, fields, and ownership rules are described below.
```mermaid
flowchart TB
layer1["Layer 1 · Authoring inputs<br/>PipelineConfig + DeployConfig"]
layer2["Layer 2 · Resolve once<br/>StageConfigFactory.create_from_model()<br/>VllmOmniConfig.from_pipeline_config()"]
layer3["Layer 3 · Transport-safe control plane<br/>VllmOmniConfig"]
layer2["Layer 2 · Production resolution boundary<br/>resolve_omni_config()<br/>(StageConfigFactory + VllmOmniConfig internally)"]
layer3["Layer 3 · Startup hand-off<br/>OmniConfigResolution<br/>PipelineConfig + temporary OmegaConf stage bridge"]
layer4["Layer 4 · Runtime launch planning<br/>StageRuntime"]
layer5["Layer 5 · Engine materialization<br/>VllmConfig / OmniDiffusionConfig"]
layer1 --> layer2 --> layer3 --> layer4 --> layer5
```
The single resolution boundary is implemented by
`StageConfigFactory.create_from_model()` and
`VllmOmniConfig.from_pipeline_config()` in
The production resolution boundary is `resolve_omni_config()` in
[`vllm_omni/config`](https://github.com/vllm-project/vllm-omni/tree/main/vllm_omni/config).
It delegates typed construction to `StageConfigFactory.create_from_model()` and
`VllmOmniConfig.from_pipeline_config()`, then returns an
`OmniConfigResolution` consumed by both `AsyncOmniEngine` and headless startup.
Until `StageRuntime` consumes typed stage configs directly, this envelope carries
the effective `PipelineConfig` alongside OmegaConf-compatible `stage_configs` as
a temporary runtime bridge; both views describe the same resolved topology,
including injected stages.
The legacy `stage_args` YAML path has been removed. Model topology now resolves
through `PipelineConfig`, with runtime overrides supplied by `DeployConfig`.
@@ -354,9 +360,12 @@ The important ownership rules are:
deploy-time defaults; it does not redefine the model graph.
3. CLI and Python overrides are applied at the resolution boundary, with
per-stage overrides taking precedence over global values where supported.
4. `StageRuntime` owns launch planning and replica lifecycle. `ReplicaInitPlan`
4. `OmniConfigResolution` is the sole startup hand-off. Its `pipeline_config`
and temporary `stage_configs` compatibility view must describe the same
topology.
5. `StageRuntime` owns launch planning and replica lifecycle. `ReplicaInitPlan`
is runtime-private state, not a user configuration object.
5. `VllmConfig` and the enriched `OmniDiffusionConfig` are materialized in the
6. `VllmConfig` and the enriched `OmniDiffusionConfig` are materialized in the
process that owns the corresponding engine.
## Main features
@@ -418,7 +427,7 @@ The public interfaces map onto the same engine and stage boundaries:
flowchart LR
offline["Offline Python<br/>Omni.generate()"] --> engine["AsyncOmniEngine"]
online["OpenAI-compatible serving<br/>vllm serve ... --omni"] --> engine
duplex["Experimental duplex WebSocket<br/>/v1/duplex or realtime duplex"] --> engine
duplex["Duplex WebSocket<br/>/v1/realtime?duplex=1 or /v1/duplex"] --> engine
engine --> stages["Configured AR and diffusion stages"]
stages --> result["Streaming or final multimodal output"]
```
@@ -150,8 +150,17 @@ that do not belong in the shared contract should be stored in `state.extra`.
Queueing and lifecycle metadata remain in the scheduler's request state.
Current native pipelines that explicitly enable step execution include
Qwen-Image, HunyuanImage3, and Helios. Step execution alone does not imply
continuous-batching support: Qwen-Image accepts batched step states.
Qwen-Image, BAGEL, HunyuanImage3, and Helios. Step execution alone does not
imply continuous-batching support: Qwen-Image accepts batched step states.
BAGEL accepts batched step states for image generation with its default,
think-mode, and single-stage deploy configs. Only the diffusion stage uses the
step protocol in two-stage deployments; explicit single-stage text output
retains complete-request execution. BAGEL requires at least two inference steps
because it schedules `num_inference_steps - 1` denoising updates, and its step
path does not currently support sequence parallelism or a diffusion cache
backend. When a finished stepwise request is followed by an explicit text
fallback wave, the runner retires its step state and paged diffusion-KV rows
before the full forward dispatch.
HunyuanImage3 accepts batched step states only when its resolved self-attention
backend is `TORCH_SDPA`; otherwise it rejects groups larger than one request.
Configure `DIFFUSION_ATTENTION_BACKEND=TORCH_SDPA` or
+42 -18
View File
@@ -5,23 +5,27 @@ The three strategies have separate user and design pages:
| Strategy | User guide | Design contract |
| --- | --- | --- |
| Model-level | [Guide](../../../user_guide/diffusion/offloader/cpu_offload.md) | [Design](cpu_offload.md) |
| Model-level | [Guide](../../../user_guide/diffusion/offloader/module_offload.md) | [Design](module_offload.md) |
| Layerwise | [Guide](../../../user_guide/diffusion/offloader/layerwise_offload.md) | [Design](layerwise_offload.md) |
| Distributed layerwise | [Guide](../../../user_guide/diffusion/offloader/distributed_layerwise_offload.md) | [Design](distributed_layerwise_offload.md) |
## Strategy selection
`OffloadConfig.from_od_config()` converts diffusion configuration into one
`OffloadStrategy`. If callers set multiple flags, selection is deterministic:
`OffloadStrategy`. The public config separates component selection from
policy:
1. `DISTRIBUTED_LAYERWISE`;
2. `LAYERWISE`;
3. `MODEL_LEVEL`; and
4. `NONE`.
- `mode="module"` selects model-level offload;
- `mode="layer"` with rank-local transfers selects ordinary layerwise
offload; and
- AllGather transfer or resident layers selects the distributed layerwise
backend that implements those capabilities.
The factory logs when a higher-priority strategy disables another. It derives
parallel and HSDP state from `DiffusionParallelConfig`; callers do not provide
a separate offload group size.
The compatibility boolean flags retain their historical priority (distributed
layerwise, layerwise, then model-level). A compact config rejects a conflicting
legacy strategy or non-default legacy DLO tuning. The factory derives parallel
and HSDP state from `DiffusionParallelConfig`; callers do not provide a
separate offload group size.
`get_offload_backend()` then validates platform offload support, resolves the
device, and creates exactly one backend. Returning `None` means offloading is
@@ -39,11 +43,21 @@ Every backend implements `OffloadBackend`:
`disable()` does not promise to restore the pipeline's original device
placement. The caller owns any subsequent rematerialization.
An enable failure must remove every partially installed hook. Rank-local
backends restore ordinary tensors so the pipeline can be retried. A failed
multi-rank AllGather startup must not enter unmatched recovery collectives;
that worker is safely discarded after local resource cleanup.
Hooks are registered through `HookRegistry` and `ModelHook`; offload backends
must use distinct hook names and remove only hooks they own.
## Discovery and topology
`resolve_offload_plan(pipeline, config)` is the single topology entry point.
Every backend calls it once when `enable()` starts and then consumes the
resolved components, block stacks, resident heads, and staged-residency flags
it returns. Backends do not re-read model declarations.
Pipeline component discovery prefers `SupportsComponentDiscovery` declarations:
- `_dit_modules`;
@@ -52,16 +66,26 @@ Pipeline component discovery prefers `SupportsComponentDiscovery` declarations:
- `_resident_modules`.
Dotted paths are supported. Legacy pipelines may use the fallback scan of
well-known attribute names, but new integrations should declare components
explicitly.
well-known attribute names; it now warns once per pipeline class, and new
integrations must declare components explicitly.
Ordinary layerwise offload obtains topology from declared
`_layerwise_offload_blocks_attrs` (with a deprecated singular-name
compatibility path). Distributed layerwise offload may instead use a pipeline
`OffloadPlan`, then falls back to declared block attributes and its documented
compatibility discovery. Discovery metadata describes structure only; the
backend remains responsible for transfer, synchronization, and storage
ownership.
Block topology comes from the pipeline `OffloadPlan` first: `block_attrs` maps
each DiT path to its ordered block containers, `encoder_block_attrs` declares
streamable encoder stacks, `on_demand_component_paths` marks pipeline-managed
residency, `resident_dit_paths` marks the DiTs that may hold resident layers,
and `encoder_dlo_weight_replication` marks the encoders whose loader-produced
weights are safe for AllGather. DiTs absent from the plan fall back to
`_layerwise_offload_blocks_attrs` (including the deprecated singular-name
compatibility path).
The resolver is pure: it moves no tensor, installs no hook, writes no module
attribute, and reads no process group — multi-rank facts come from
`OffloadConfig`. It owns topology validation, so a configuration the model
cannot serve fails before the first component is placed. Explicit component
selection turns every declaration problem into an error, while the
compatibility topology (no component selector) keeps the historical
warn-and-skip behavior. Metadata still describes structure only; the backend
remains responsible for transfer, synchronization, and storage ownership.
## Cross-strategy invariants
@@ -1,10 +1,11 @@
# Distributed Layerwise Offload
This document describes distributed layerwise offload (DLO) for diffusion
models. DLO keeps only a small number of DiT blocks on the accelerator and
streams the remaining blocks from host memory. The distributed backend can
either shard those host-side weights across an existing parallel group or keep
complete rank-local block sources and avoid an additional collective.
models. DLO keeps only a small number of selected DiT or text-encoder blocks
on the accelerator and streams the remaining blocks from host memory. The
distributed backend can either shard those host-side weights across an
existing parallel group or keep complete rank-local block sources and avoid
an additional collective.
For user-facing commands, see the
[distributed layerwise offloading guide](../../../user_guide/diffusion/offloader/distributed_layerwise_offload.md)
@@ -38,10 +39,11 @@ detailed contracts and validation boundaries.
## Status
DLO is implemented for multi-device diffusion execution. The default
AllGather path is the primary path for DP and SP deployments. The
`--dlo-no-use-allgather` path streams complete blocks independently and adds no
DLO weight collective.
DLO is implemented for multi-device diffusion execution. The default public
transfer is `rank-local`; setting a selected component's
`layer_options.NAME.weight_transfer` to `allgather` enables weight sharding across a
compatible DP or SP group. Transfers are configured independently for `dit`
and `text_encoder`.
Host storage is selected separately from the transfer protocol. The loader can
produce a direct-checkpoint mmap plan for a proven-compatible runtime layout;
@@ -109,7 +111,7 @@ checkpoint adapter beside the ordinary loader.
### AllGather path
With the default `dlo_use_allgather=True`, each rank stores approximately
For a component configured with `weight_transfer="allgather"`, each rank stores approximately
`1 / group_size` of each streamable block in pinned host memory. The next
block's shard is copied to a device buffer and reconstructed with
`all_gather_into_tensor` on a communication stream while the current block is
@@ -132,9 +134,8 @@ source used to prepare each rank's persistent shard. They can be closed after
shard preparation. Across the AllGather group, those private shards total
approximately one runtime model copy.
An effective DLO group size of one performs no collective, even when
`dlo_use_allgather=True`; it follows the rank-local transfer path described
below.
An effective DLO group size of one performs no collective; it follows the
rank-local transfer path described below.
When DP is greater than one, the engine can process one request per DP rank in
the same denoising wave. Because AllGather is a collective, all participating
@@ -142,9 +143,10 @@ requests must take the same execution path at every denoising step.
### Rank-local path without DLO AllGather
With `--dlo-no-use-allgather`, DLO forces its internal offload shard size to
one and streams complete blocks using H2D copies only. The host backing may be
either a loader-approved checkpoint mapping or ordinary runtime tensors.
With `weight_transfer="rank-local"`, DLO forces the affected component's internal
offload shard size to one and streams complete blocks using H2D copies only.
The host backing may be either a loader-approved checkpoint mapping or
ordinary runtime tensors.
For direct mmap, each process retains immutable safetensors views and uses two
bounded pinned host staging slots. Processes on the same node that map the same
@@ -204,7 +206,7 @@ adopt it.
> optimizations without changing AllGather behavior.
Final-layout Host Weight Runtime (HWR) backing is opt-in and currently applies
only to no-AllGather DLO. Enable it with
only to a selected rank-local DiT. Enable it with
`--host-weight-runtime-mode preferred` (or `required`) and
`--host-weight-runtime-root <node-local-root>`.
@@ -422,8 +424,8 @@ failure.
collective. When the model declares the final-layout contract, HWR keys the
reusable artifact by TP rank/size and SP layout.
- **HSDP + SP:** the general parallel configuration permits HSDP over SP, but
DLO must use `--dlo-no-use-allgather`. HSDP remains responsible for weight
materialization and synchronization.
each selected component must use `weight_transfer="rank-local"`. HSDP remains
responsible for weight materialization and synchronization.
- **HSDP + DP or TP:** rejected independently by the diffusion parallel
configuration.
@@ -447,8 +449,9 @@ recorded layouts (they all keep plain 1-byte dtypes over ordinary strided
views: online INT8 has an int8 weight plus fp32 scale, online MXFP8 has an
fp8 weight plus e8m0 block scale, each with a contiguous or transposed-view
finalized tensor, all covered by the same physical-order packing as online
FP8). Other online methods must use `--dlo-no-use-allgather` or disable
online quantization until their runtime layouts are validated.
FP8). Other online methods must set
`layer_options.dit.weight_transfer="rank-local"` or disable online quantization
until their runtime layouts are validated.
The Host Weight Runtime representation, publication, and no-AllGather consumer
contracts are merged; see
@@ -36,12 +36,14 @@ dependencies, not incidental global synchronization, define correctness.
## Topology contract
A DiT declares ordered block-container attributes using
`_layerwise_offload_blocks_attrs` or an `OffloadPlan`. Each resolved item must
be an executable `nn.Module`; ordering must match forward execution. Multiple
containers are concatenated in execution order.
`_layerwise_offload_blocks_attrs` or an `OffloadPlan`. A text encoder declares
its block-list paths through `OffloadPlan.encoder_block_attrs`. Each resolved
item must be an executable `nn.Module`; ordering must match forward execution.
Multiple containers are streamed as separate hook rings.
Non-block DiT modules, encoders, VAEs, and declared resident modules are not
streamed by this backend.
Non-block state of a selected DiT or text encoder remains device resident.
Unselected components, VAEs, and declared resident modules are not streamed by
this backend.
## Invariants and limitations
@@ -50,7 +52,8 @@ streamed by this backend.
- Blocks with heterogeneous shapes and dtypes use their recorded metadata;
implementations must not assume identical blocks.
- The ring is initialized before the first forward so block zero is available.
- This backend is single-device; distributed weight reconstruction belongs to
- This backend uses rank-local host weights. Multi-device compute may remain
active, but cross-rank weight reconstruction belongs to
[Distributed Layerwise Offload](distributed_layerwise_offload.md).
Shared selection and lifecycle behavior is defined in the
@@ -2,7 +2,7 @@
Model-level offload enforces mutual exclusion between pipeline component
groups. For commands and model support, see the
[model-level user guide](../../../user_guide/diffusion/offloader/cpu_offload.md).
[model-level user guide](../../../user_guide/diffusion/offloader/module_offload.md).
## Generic hook path
@@ -10,10 +10,15 @@ groups. For commands and model support, see the
makes encoders and VAEs device resident for initialization, then installs
`SequentialOffloadHook` instances:
- each DiT hook offloads all encoders and other DiTs before loading itself;
- each encoder hook offloads every DiT before loading itself; and
- each DiT hook offloads selected encoders and other selected DiTs before
loading itself;
- each encoder hook offloads selected DiTs before loading itself; and
- VAEs are not part of the mutual-exclusion hook set.
All execution-stage modules still receive a hook so an unselected resident
stage can evict a selected component before it runs. Only components listed in
`diffusion_offload_config.components` are eligible to move to CPU.
The hook moves parameters and buffers without recursively calling
`module.to()`. This avoids recursion through modules that retain references to
other transformer blocks. Host tensors are pinned when configured; DTensors
@@ -36,7 +41,9 @@ contract:
- only the executing component is device resident;
- enable and disable are idempotent;
- transfers use the supplied device, pinning, and HSDP settings; and
- transfers use the supplied device, pinning, and HSDP settings;
- explicit component selection is either honored or rejected before partial
setup; and
- disabling removes all model-local offload hooks or contexts.
The delegation is currently duck typed. If another independent implementation
@@ -0,0 +1,172 @@
# Runner-to-model prefill/decode phase contract
## Overview
Autoregressive models with a custom `preprocess` hook need to distinguish
prompt processing from decoding. A one-token span can be either a complete
one-token prompt, the last chunk of a longer prompt, or a decode step. Using
span length as the phase signal can replace a prompt embedding with a generated
audio embedding and advance teacher-forced text too early without an exception.
[Issue #4382](https://github.com/vllm-project/vllm-omni/issues/4382) requests a
stable contract for the metadata and routing guards introduced by
[PR #3662](https://github.com/vllm-project/vllm-omni/pull/3662).
The shared runner already implements those guards. This design documents the
existing interface and adds runner-level regression coverage rather than
introducing a second phase detector or changing scheduling policy.
## Scope and objectives
- Make the three existing phase keys safe to depend on in out-of-tree models.
- Preserve prompt embeddings and model state on a one-token prefill tail.
- Keep normal and batched decode routing consistent in mixed batches.
- Explain the correctness workaround required on older releases.
This contract does not enable chunked prefill for models whose other state
handling does not support it. It does not change the scheduler, introduce new
kwargs, or standardize other `_omni_*` fields. The implementation adds
documentation and regression coverage; no hot-path allocation is added.
## Design
### Ownership and data flow
```text
Scheduler / current input batch
prompt_token_ids + num_computed_tokens_cpu (before this step)
|
OmniGPUModelRunner._preprocess
|
per-request phase metadata
/ \
prefill / other spans single-token decode
preprocess preprocess or preprocess_decode_batch
| |
prompt embedding runner-managed talker_mtp
\ /
model forward
```
The producer is `OmniGPUModelRunner._preprocess` in
`vllm_omni/worker/gpu_model_runner.py`. `GPUARModelRunner` and
`GPUGenerationModelRunner` inherit it. The current `OmniNPUModelRunner` also
inherits this method; device-specific execution still needs its own hardware
validation. A plugin runner that overrides preprocessing is responsible for
preserving this interface.
### Stable model-facing fields
The following names, types, and meanings are a supported interface for in-tree
and out-of-tree model hooks despite the leading underscore. Models should treat
them as read-only runner-owned values. Changes to their meanings or removal
require an explicit compatibility/migration plan.
| Key | Python type | Meaning |
| --- | --- | --- |
| `_omni_prompt_len` | `int` | Length of the current request state's `prompt_token_ids`. This is the stage's prompt length, not the current chunk size or raw text length. |
| `_omni_num_computed_tokens` | `int` | The row's `input_batch.num_computed_tokens_cpu` value **before** processing the scheduled span. It includes already-computed/cached progress, not just tokens computed in the current call. |
| `_omni_is_prefill` | `bool` | Exactly `_omni_num_computed_tokens < _omni_prompt_len`. Equality and greater progress mean decode. |
The runner refreshes the fields after current request state has been gathered,
immediately before selecting the per-row hook. Existing values in intermediate
model state cannot override the current phase. Row indices follow the current
input batch (including any reordering), not request arrival order. A resumed
or streaming request is classified from its **current** prompt/progress values;
the phase is not latched for the lifetime of the request.
The normal hook receives these fields as keyword arguments:
```python
def preprocess(self, input_ids, input_embeds, **info):
if info["_omni_is_prefill"]:
offset = info["_omni_num_computed_tokens"]
# Select the scheduled slice of the model's prompt embeddings.
...
else:
# Advance decode-only text/audio state.
...
```
For `preprocess_decode_batch(input_ids=..., req_infos=...)`, each `req_infos`
entry contains the same fields, aligned with its corresponding input row.
Every such row has `_omni_is_prefill == False`. The optional earlier
`preprocess_batch` hook is a separate whole-batch preparation hook; these freshly
computed per-row fields are not promised at that earlier boundary.
### Routing guarantee
With `has_preprocess=True`, the batched decode hook is eligible only when it is
callable, the runner has `talker_mtp`, the scheduled span is one token, and
`_omni_is_prefill` is false. Otherwise the normal `preprocess` hook runs.
Runner-managed MTP is likewise restricted to single-token decode rows. A prefill
row, including a one-token tail, is never added to that MTP batch: the runner
does not consume its `mtp_inputs`, generate codes for it, or overwrite its
preprocessed embedding with an MTP embedding. Decode rows in the same step
continue to receive their MTP outputs at their own token offsets. Models may
still implement their own model-specific work inside `preprocess`.
| Prompt length | Computed before step | Scheduled span | Phase | Runner MTP eligible |
| --- | --- | --- | --- | --- |
| 5 | 0 | 4 | Prefill | No |
| 5 | 4 | 1 | Prefill tail | No |
| 5 | 5 | 1 | Decode | Yes |
| 5 | 6 | 1 | Decode | Yes |
| 1 | 0 | 1 | Prefill | No |
| 5 | 5 | 2 | Decode | No (not a single-token span) |
### Compatibility
!!! warning "vLLM-Omni 0.20.x and earlier"
AR talker models with custom preprocessing that infer phase from span length
must use `enable_chunked_prefill: false` for correctness on these releases.
They lack the phase metadata and the runner's prefill-tail MTP protection.
A model-only heuristic cannot prevent the older runner from overwriting a
one-token prompt embedding. Disabling chunking avoids split tails; it does
not make span length a reliable phase detector for every possible prompt.
The mechanism landed in #3662 (v0.21.0rc2 / v0.22.0 and later); this document
formalizes that existing behavior. Prefer upgrading the matching vLLM and
vLLM-Omni release pair and consuming the explicit phase keys. Do not silently
fall back to `input_ids.numel() > 1` when the model requires chunking correctness.
For a custom or backported runtime, verify both metadata and MTP guards.
## Correctness and testing plan
Runner tests call the production `_preprocess` and `_talker_mtp_forward` methods
with CPU buffers and a deterministic MTP model. They inspect kwargs at the model
boundary, resulting embeddings, and per-request generated codes. Coverage must
include a multi-step prompt split with a one-token tail followed by decode,
mixed prefill/decode batches in different orders, normal/batched decode hooks,
adjacent decode requests in the same hook call with exact per-request codes,
single-token prompts, cached progress, and decode spans longer than one token.
No test should pass merely because MTP was disabled for every row.
Real-weight validation uses Qwen3-TTS CustomVoice through the full talker and
code2wav pipeline. Record the actual scheduled prompt progress, assert that at
least one one-token prefill tail was observed, check its hook metadata and MTP
exclusion, and require finite non-empty output audio. Compare to an unchunked
run with the same real input, greedy sampling, and `VLLM_BATCH_INVARIANT=1`.
Greedy sampling alone does not guarantee identical codes across different
prefill shapes: floating-point reduction differences can change an argmax and
diverge in subsequent autoregressive steps. Invariant kernels keep this exact
comparison focused on the phase contract. The two engines must be initialized
separately because their chunking settings and token budgets differ. GPU tests belong under
`tests/e2e/features/`; existing model tests remain useful adjacent regression
coverage. Functional audio checks do not establish throughput or all-model
semantic parity; this change makes no performance claim.
## Open questions and discussions
There are no new field names or execution modes to choose. Future runner
implementations, including Model Runner V2, should preserve these semantics or
provide an explicit migration path. Hardware validation beyond the tested
platform remains separate from the shared Python routing tests.
## References
- [Issue #4382](https://github.com/vllm-project/vllm-omni/issues/4382)
- [Original metadata and guards, #3662](https://github.com/vllm-project/vllm-omni/pull/3662)
- [Qwen2.5-Omni consumer fix, #5019](https://github.com/vllm-project/vllm-omni/pull/5019)
- [Adding an Omni model](../../contributing/model/adding_omni_model.md)
- [Test writing guide](../../contributing/ci/test_writing_guide.md)
+2 -2
View File
@@ -84,8 +84,8 @@ dictionary narrows this global scope through runtime layer-prefix routing.
The factory uses Omni-specific overrides for methods whose diffusion behavior
or platform dispatch is not provided by the vLLM registry. The current override
set includes INT8, BitsAndBytes, MXFP8, MXFP4, MXFP4 dual-scale, and INC/
AutoRound. Other methods, such as FP8, GGUF, and ModelOpt, are resolved through
set includes INT8, BitsAndBytes, MXFP8, MXFP4, MXFP4 dual-scale, INC/AutoRound,
and TorchAO. Other methods, such as FP8, GGUF, and ModelOpt, are resolved through
vLLM's registry when their configuration is compatible.
## Per-component routing
@@ -0,0 +1,59 @@
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="460" viewBox="0 0 760 460" role="img" aria-labelledby="title desc">
<title id="title">MAGI-2 DLO one-step Pareto frontier</title>
<desc id="desc">Warm user latency in seconds versus throughput per device. DP1SP4 is 5.735 seconds and 0.0436 outputs per second per device, DP2SP2 is 6.785 and 0.0737, and DP4 is 9.997 and 0.1000.</desc>
<rect width="760" height="460" fill="#ffffff"/>
<style>
text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
.title { font-size: 20px; font-weight: 600; }
.axis-label { font-size: 14px; font-weight: 500; }
.tick { font-size: 12px; fill: #4b5563; }
.point-label { font-size: 13px; font-weight: 600; }
.value-label { font-size: 11px; fill: #4b5563; }
.grid { stroke: #e5e7eb; stroke-width: 1; }
.axis { stroke: #6b7280; stroke-width: 1.5; }
.frontier { fill: none; stroke: #2563eb; stroke-width: 3; }
.point { fill: #2563eb; stroke: #ffffff; stroke-width: 2; }
</style>
<text x="380" y="28" text-anchor="middle" class="title">One diffusion step</text>
<g aria-hidden="true">
<line x1="85" y1="309" x2="700" y2="309" class="grid"/>
<line x1="85" y1="226" x2="700" y2="226" class="grid"/>
<line x1="85" y1="143" x2="700" y2="143" class="grid"/>
<line x1="85" y1="61" x2="700" y2="61" class="grid"/>
<line x1="85" y1="40" x2="85" y2="350" class="axis"/>
<line x1="85" y1="350" x2="700" y2="350" class="axis"/>
<text x="73" y="313" text-anchor="end" class="tick">0.04</text>
<text x="73" y="230" text-anchor="end" class="tick">0.06</text>
<text x="73" y="147" text-anchor="end" class="tick">0.08</text>
<text x="73" y="65" text-anchor="end" class="tick">0.10</text>
<text x="85" y="370" text-anchor="middle" class="tick">5</text>
<text x="197" y="370" text-anchor="middle" class="tick">6</text>
<text x="309" y="370" text-anchor="middle" class="tick">7</text>
<text x="421" y="370" text-anchor="middle" class="tick">8</text>
<text x="532" y="370" text-anchor="middle" class="tick">9</text>
<text x="644" y="370" text-anchor="middle" class="tick">10</text>
</g>
<text x="392" y="414" text-anchor="middle" class="axis-label">Warm user latency (seconds) — lower is better</text>
<text x="20" y="195" transform="rotate(-90 20 195)" text-anchor="middle" class="axis-label">Throughput/device (outputs/s) — higher is better</text>
<polyline points="167,294 285,169 644,61" class="frontier"/>
<circle cx="167" cy="294" r="7" class="point"/>
<text x="179" y="288" class="point-label">DP1SP4</text>
<text x="179" y="305" class="value-label">5.735 s, 0.0436</text>
<circle cx="285" cy="169" r="7" class="point"/>
<text x="297" y="163" class="point-label">DP2SP2</text>
<text x="297" y="180" class="value-label">6.785 s, 0.0737</text>
<circle cx="644" cy="61" r="7" class="point"/>
<text x="632" y="84" text-anchor="end" class="point-label">DP4</text>
<text x="632" y="101" text-anchor="end" class="value-label">9.997 s, 0.1000</text>
<text x="700" y="444" text-anchor="end" class="tick">4x NVIDIA L20X · warm qualification run</text>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -0,0 +1,65 @@
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="460" viewBox="0 0 760 460" role="img" aria-labelledby="title desc">
<title id="title">MAGI-2 DLO four-step Pareto frontier</title>
<desc id="desc">Warm user latency in seconds versus throughput per device. DP1SP4 is 8.647 seconds and 0.0289 outputs per second per device, DP2SP2 is 11.682 and 0.0428, and DP4 is 20.957 and 0.0477.</desc>
<rect width="760" height="460" fill="#ffffff"/>
<style>
text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
.title { font-size: 20px; font-weight: 600; }
.axis-label { font-size: 14px; font-weight: 500; }
.tick { font-size: 12px; fill: #4b5563; }
.point-label { font-size: 13px; font-weight: 600; }
.value-label { font-size: 11px; fill: #4b5563; }
.grid { stroke: #e5e7eb; stroke-width: 1; }
.axis { stroke: #6b7280; stroke-width: 1.5; }
.frontier { fill: none; stroke: #2563eb; stroke-width: 3; }
.point { fill: #2563eb; stroke: #ffffff; stroke-width: 2; }
</style>
<text x="380" y="28" text-anchor="middle" class="title">Four diffusion steps</text>
<g aria-hidden="true">
<line x1="85" y1="350" x2="700" y2="350" class="grid"/>
<line x1="85" y1="288" x2="700" y2="288" class="grid"/>
<line x1="85" y1="226" x2="700" y2="226" class="grid"/>
<line x1="85" y1="164" x2="700" y2="164" class="grid"/>
<line x1="85" y1="102" x2="700" y2="102" class="grid"/>
<line x1="85" y1="40" x2="700" y2="40" class="grid"/>
<line x1="85" y1="40" x2="85" y2="350" class="axis"/>
<line x1="85" y1="350" x2="700" y2="350" class="axis"/>
<text x="73" y="354" text-anchor="end" class="tick">0.025</text>
<text x="73" y="292" text-anchor="end" class="tick">0.030</text>
<text x="73" y="230" text-anchor="end" class="tick">0.035</text>
<text x="73" y="168" text-anchor="end" class="tick">0.040</text>
<text x="73" y="106" text-anchor="end" class="tick">0.045</text>
<text x="73" y="44" text-anchor="end" class="tick">0.050</text>
<text x="85" y="370" text-anchor="middle" class="tick">8</text>
<text x="173" y="370" text-anchor="middle" class="tick">10</text>
<text x="261" y="370" text-anchor="middle" class="tick">12</text>
<text x="349" y="370" text-anchor="middle" class="tick">14</text>
<text x="436" y="370" text-anchor="middle" class="tick">16</text>
<text x="524" y="370" text-anchor="middle" class="tick">18</text>
<text x="612" y="370" text-anchor="middle" class="tick">20</text>
<text x="700" y="370" text-anchor="middle" class="tick">22</text>
</g>
<text x="392" y="414" text-anchor="middle" class="axis-label">Warm user latency (seconds) — lower is better</text>
<text x="20" y="195" transform="rotate(-90 20 195)" text-anchor="middle" class="axis-label">Throughput/device (outputs/s) — higher is better</text>
<polyline points="113,302 247,129 654,69" class="frontier"/>
<circle cx="113" cy="302" r="7" class="point"/>
<text x="125" y="296" class="point-label">DP1SP4</text>
<text x="125" y="313" class="value-label">8.647 s, 0.0289</text>
<circle cx="247" cy="129" r="7" class="point"/>
<text x="259" y="123" class="point-label">DP2SP2</text>
<text x="259" y="140" class="value-label">11.682 s, 0.0428</text>
<circle cx="654" cy="69" r="7" class="point"/>
<text x="642" y="92" text-anchor="end" class="point-label">DP4</text>
<text x="642" y="109" text-anchor="end" class="value-label">20.957 s, 0.0477</text>
<text x="700" y="444" text-anchor="end" class="tick">4x NVIDIA L20X · warm qualification run</text>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -23,16 +23,18 @@ The target is the engine-native path:
-> response.audio.delta + response.audio_transcript.delta
```
The standalone `/api/chat` and `/v1/audio/duplex` server remains a compatibility
surface. It is not evidence that the unified engine path works.
The standalone `/api/chat` and `/v1/audio/duplex` server that accompanied the
original PR was demo-only and has been removed from the tree; the unified
engine path above is the only serving surface. It was never evidence that the
unified engine path works.
## Why configuration-only enablement is invalid
## Why configuration-only enablement was invalid
The current staged pipeline is explicitly turn based. Its Talker reads
`pplex_user_codes`, `pplex_prefill_text`, and `pplex_silence_codes`, but no
production staged input path writes those fields. The voice prompt, persona
prefill, and streaming Mimi state live only in the standalone
`PersonaPlexEngine`.
The staged pipeline as shipped by the original PR was explicitly turn based.
Its Talker read `pplex_user_codes`, `pplex_prefill_text`, and
`pplex_silence_codes`, but no production staged input path wrote those fields.
The voice prompt, persona prefill, and streaming Mimi state lived only in the
standalone `PersonaPlexEngine` (demo-only, since removed).
Setting only the following fields would therefore advertise an endpoint whose
model never receives the live microphone stream:
@@ -56,7 +58,12 @@ The unified implementation supports:
- continuous user input while assistant audio is generated or played;
- bundled `.pt` voice prompts and a session persona;
- greedy text and depformer sampling, matching the current PersonaPlex port;
- `/v1/realtime?duplex=1`;
- `/v1/realtime?duplex=1` (the wire vocabulary is catalogued in the
[Realtime Duplex API](../serving/realtime_duplex_api.md) serving guide);
- the public client preset
`vllm_omni.clients.personaplex.create_duplex_session_config()` (24 kHz
`pcm_f32le` input format, voice prompt, persona) as the canonical
session-config source for `DuplexClient` consumers;
- engine lease close, disconnect cleanup, reconnect after cleanup, and explicit
response cancellation without cross-session state reuse.
@@ -189,10 +196,17 @@ model-neutral native results:
```
It owns per-request audio and text cursors so a cumulative output cannot replay
old audio. PersonaPlex keeps one visible response open while continuous output
arrives. Session close or cancellation terminates that response through the
generic Realtime lifecycle; a codec segment finishing is not a conversational
turn boundary.
old audio. The generic projector turns each native result into the Realtime
`response.audio.delta` / `response.audio_transcript.delta` pair (and
`response.output_text.delta` for text) under one `response_id`; the full
mapping is the name map in the
[Realtime Duplex API](../serving/realtime_duplex_api.md) serving guide.
PersonaPlex keeps one visible response open while continuous output arrives.
Session close or cancellation terminates that response through the generic
Realtime lifecycle; a codec segment finishing is not a conversational turn
boundary. PersonaPlex advertises `supports_barge_in=false` and
`supports_session_resume=false`, so `barge_in`, `turn_detection.server_vad`,
and `session.resume` are rejected on this model.
## Error and lifecycle contracts
@@ -210,8 +224,8 @@ turn boundary.
- New sessions cannot observe the previous voice, persona, PCM tail, Mimi
convolution state, or Talker delayed code frame.
The legacy `PersonaPlexDuplexRuntime.run()` and standalone server drain path
must follow the same exception-safe rule while they remain in the tree.
(The legacy `PersonaPlexDuplexRuntime.run()` and standalone server drain path,
which followed the same exception-safe rule, have been removed from the tree.)
## Testing and acceptance
@@ -1,5 +1,13 @@
# MiniCPM-o 4.5 Full-Duplex Runtime Architecture
> Relocation note: after the H20 validation recorded below, the full-duplex
> implementation moved out of `vllm_omni/experimental/fullduplex/` into the
> stable tree (`vllm_omni/engine/duplex/`, `vllm_omni/entrypoints/duplex/`,
> `vllm_omni/model_executor/models/minicpmo_4_5/duplex/`, and companion
> modules). Module paths in this document reflect the new layout; the runtime
> evidence was produced on the pre-move tree, which is file-identical apart
> from module paths.
## Purpose
This document records the runtime architecture implemented by PR #3907, the
@@ -31,8 +39,8 @@ session was rejected and capacity returned after one session closed. The two
accepted sessions independently completed their audio, transcript, response,
playback, and close lifecycles. This is isolation and admission evidence for the
tested two-session deployment, not a production fairness, arbitrary-capacity,
or failure-recovery claim. The validated paths include the experimental runtime
extension, typed resumable policy, extracted control plane and clients, request
or failure-recovery claim. The validated paths include the MiniCPM duplex
runtime extension, typed resumable policy, extracted control plane and clients, request
preregistration, typed direct-output decision, engine-managed two-session
capacity in the checked-in deploy profile, separate public/runtime configuration
channels, private Session ledgers, ordered `session.update`, the MiniCPM runner
@@ -58,7 +66,8 @@ The checkpoint keeps these verified contracts:
- scheduler data-plane append over a resumable request;
- stale epoch/turn/response fencing;
- `/v1/duplex` and OpenAI Realtime projection;
- optional OpenAI `server_vad` turn detection backed by per-session Silero VAD;
- optional OpenAI `server_vad` turn detection backed by a process-shared Silero
model and per-session VAD state;
- existing JoyVL behavior.
The checkpoint does not claim:
@@ -67,7 +76,9 @@ The checkpoint does not claim:
- an acoustic-onset-to-cancel latency target across arbitrary client chunk sizes;
- production multi-session admission, fairness, capacity, or failure recovery;
- bounded long-session KV;
- video input or audio/video synchronization.
- audio/video synchronization beyond one camera frame per model unit (camera
frames ride the audio appends as `video_frames`; the wire contract is in the
[Realtime Duplex API](../serving/realtime_duplex_api.md) guide).
## Active Runtime Path
@@ -81,7 +92,7 @@ WebSocket
-> AsyncOmni thin open/append/signal/close proxies
-> DuplexControlClient over the engine-owned correlated RPC transport
-> DuplexControlPlane + DuplexSessionRuntimeManager
-> experimental DuplexRuntimeExtension + engine session/stage bindings
-> MiniCPM DuplexRuntimeExtension + engine session/stage bindings
-> StagePool
-> resumable scheduler request
-> MiniCPM Stage0
@@ -177,21 +188,21 @@ ordering guarantee.
### Extracted control ownership
The generic engine mechanism and the experimental duplex plugin are split by
The generic engine mechanism and the duplex plugin are split by
responsibility:
| Module | Responsibility |
| --- | --- |
| `engine.messages` | ordinary engine queue messages and collective RPC envelopes |
| `engine.rpc_result_router` | generic correlation routing through each result message's `rpc_correlation_key` |
| `experimental.fullduplex.engine.contracts` | immutable duplex DTOs, extension/stage protocols, typed decisions, request-ID codec |
| `experimental.fullduplex.engine.lease` | duplex lease activity, TTL/grace configuration, and expiry records |
| `experimental.fullduplex.engine.messages` | identity fence and duplex control message/result envelopes |
| `experimental.fullduplex.engine.duplex_runtime` | extension loading, validation, and compatibility exports |
| `experimental.fullduplex.engine.duplex_session` | session resources, fences, append reservations, idempotency cache, binding cleanup |
| `experimental.fullduplex.engine.duplex_control_plane` | control algorithms and the narrow `DuplexStagePort` used to submit and clean up stage requests |
| `experimental.fullduplex.engine.duplex_control_client` | typed control-message construction and calls through the engine-owned correlated RPC transport |
| `experimental.fullduplex.request_client` | deterministic request identity, client-state preregistration, acknowledgement validation, data-plane collection, rollback |
| `engine.duplex.contracts` | immutable duplex DTOs, extension/stage protocols, typed decisions, request-ID codec |
| `engine.duplex.lease` | duplex lease activity, TTL/grace configuration, and expiry records |
| `engine.duplex.messages` | identity fence and duplex control message/result envelopes |
| `engine.duplex.runtime` | extension loading, validation, and compatibility exports |
| `engine.duplex.session` | session resources, fences, append reservations, idempotency cache, binding cleanup |
| `engine.duplex.control_plane` | control algorithms and the narrow `DuplexStagePort` used to submit and clean up stage requests |
| `engine.duplex.control_client` | typed control-message construction and calls through the engine-owned correlated RPC transport |
| `entrypoints.duplex_request_client` | deterministic request identity, client-state preregistration, acknowledgement validation, data-plane collection, rollback |
`Orchestrator`, `AsyncOmniEngine`, and `AsyncOmni` retain explicit wiring and
compatibility proxies. They no longer own the corresponding duplex algorithms.
@@ -362,33 +373,34 @@ vllm_omni.engine.rpc_result_router # generic correlation routing
vllm_omni.engine.async_engine_utils # ordinary engine lifecycle helpers
```
The full-duplex implementation remains experimental:
The full-duplex implementation lives in dedicated duplex modules:
```text
vllm_omni.experimental.fullduplex.engine.duplex_runtime
vllm_omni.engine.duplex.runtime
# extension loading and validation
vllm_omni.experimental.fullduplex.engine.contracts
vllm_omni.engine.duplex.contracts
# immutable DTOs and narrow protocols
vllm_omni.experimental.fullduplex.engine.lease
vllm_omni.engine.duplex.lease
# duplex lease primitives
vllm_omni.experimental.fullduplex.engine.messages
vllm_omni.engine.duplex.messages
# duplex identity and queue envelopes
vllm_omni.experimental.fullduplex.engine.duplex_session
vllm_omni.engine.duplex.session
# session transaction implementation
vllm_omni.experimental.fullduplex.engine.duplex_control_plane
vllm_omni.engine.duplex.control_plane
# engine-side control algorithms
vllm_omni.experimental.fullduplex.engine.duplex_control_client
vllm_omni.engine.duplex.control_client
# correlated control client
vllm_omni.experimental.fullduplex.request_client
vllm_omni.entrypoints.duplex_request_client
# entrypoint request/output lifecycle
```
Stable engine and entrypoint modules import only the kernel at module load.
They load the experimental ControlPlane, ControlClient, request client, and
runtime extension inside duplex-only construction or call paths. Importing
`Orchestrator`, `AsyncOmniEngine`, or `AsyncOmni` for an ordinary deployment
therefore does not import `vllm_omni.experimental.fullduplex`. Compatibility
exports remain lazy and are not part of ordinary startup.
Stable engine and entrypoint modules import the duplex kernel eagerly at
module load (top-of-file imports). Duplex behavior remains opt-in: the
ControlPlane, ControlClient, and request client are *constructed* only on
duplex-enabled deployments, and the model runtime extension still loads only
through the dotted-string plugin path. The client-side package
(`vllm_omni/clients/`, home of the public `DuplexClient`) is never imported
by runtime code.
`PipelineConfig.duplex_control_enabled` enables the generic mechanism, while
`PipelineConfig.duplex_runtime_extension` separately selects the model adapter.
@@ -452,7 +464,7 @@ sampling generation unchanged.
`extra_body` or session payloads:
| Field | Default | Owner | Lifecycle or resource effect |
|---|---:|---|---|
| --- | ---: | --- | --- |
| `idle_ttl_s` | `300.0` | Engine lease manager | Expires an attached or detached session after inactivity; `null` disables only idle expiry. |
| `disconnect_grace_s` | `30.0` | Engine lease manager and API attachment registry | Bounds how long a detached session may retain engine resources before cleanup. |
| `reaper_interval_s` | `5.0` | Orchestrator | Sets the cadence for lease expiry and pending cleanup retries. |
@@ -467,6 +479,30 @@ Deploy profiles may lower or raise these values to match scheduler capacity,
but clients cannot override them. The engine remains authoritative for
admission and cleanup ownership even when the API performs an early check.
### Client-side companion package
The public client surface ships under `vllm_omni/clients/` and stays on the
consumer side of the boundary above: `vllm_omni.clients.duplex` provides the
model-agnostic `DuplexClient` (typed events, per-response demux via
`ResponseHandle`, `session.resume` with acknowledgement-based replay dedup)
and the `EventCollector` helper used for timing and request-metric summaries.
Model-specific session defaults live in thin per-model preset modules —
`vllm_omni.clients.minicpmo_4_5.create_duplex_session_config()` and
`vllm_omni.clients.personaplex.create_duplex_session_config()` — so the core
client never imports model packages and the model duplex plugin packages
never import the client (both directions are enforced by tests:
`tests/engine/test_duplex_import_boundary.py`,
`tests/entrypoints/openai_api/test_duplex_capability.py`, and the subprocess
purity guards in `tests/clients/test_model_session_configs.py`).
`examples/online_serving/barge_in_client.py` (with its flow companion
`barge_in_client_flow.md`) is the runnable reference for the client library
against a live duplex deployment. The wire vocabulary the client speaks —
every `/v1/realtime?duplex=1` event with its OpenAI-compatibility tier and a
JSON example, plus the native-to-Realtime name map — is catalogued in the
[Realtime Duplex API](../serving/realtime_duplex_api.md) serving guide; that
page derives from the normative contract table above and must be updated
with it.
## Generic-Path Cleanup
Model-specific `MINICPMO45_PROFILE_LOGS` probes were removed from:
@@ -508,14 +544,16 @@ The MiniCPM Stage1 input processor consumes that capability for token-faithful
delta transcripts. The generic orchestrator no longer installs or names a
MiniCPM-specific bridge-state key.
The stable API server inspects the deployment `session_mode` before importing or
constructing the experimental duplex handler. Ordinary deployments do not load
the full-duplex serving package or create its registries and background state.
The stable API server imports the duplex serving package eagerly and inspects
the deployment `session_mode` before constructing the duplex handler. Ordinary
deployments do not create its registries and background state.
For an enabled deployment, the client still selects the Realtime duplex route
with `?duplex=1` (or an equivalent explicit true value). Model-name matching is
not used for routing or native-runtime activation. MiniCPM clients explicitly
set `extra_body.minicpmo45_native_duplex=true`; repository demos do so in their
session payloads.
set `extra_body.native_duplex=true`; the canonical way to do so is
the `vllm_omni.clients.minicpmo_4_5.create_duplex_session_config()` preset,
which the repository's barge-in example
(`examples/online_serving/barge_in_client.py`) uses in its session payload.
`DuplexRequestClient` derives the data-plane request identity from the accepted
fence and preregisters a resumable `ClientRequestState` before the append crosses
@@ -615,7 +653,7 @@ EOS decisions advance the model conversation.
Explicit Realtime clients may still use `input_audio_buffer.commit` to create a
conversation item. The translator validates that wire input before producing a
commit carrying `realtime_item_id`. Native auto-response may already have
commit carrying `item_id`. Native auto-response may already have
streamed those PCM samples into the runtime, so the runner accepts a validated
commit even when no runtime-side chunk remains. A truly empty explicit buffer
continues to return `input_audio_buffer_empty`.
@@ -623,7 +661,7 @@ continues to return `input_audio_buffer_empty`.
The commit-to-response contract is:
| Session state at `input_audio_buffer.commit` | Runtime action | Required event outcome |
|---|---|---|
| --- | --- | --- |
| Auto-response disabled, valid idle input | Commit and retain the input item. | Emit `input_audio_buffer.committed`; emit no response until an explicit `response.create`. |
| Auto-response enabled, valid idle speech | Submit the final native append for the current model turn. | Emit `input_audio_buffer.committed` before model output. Create a response only on the first visible text/audio output; a model listen decision may end with `response.listen` and no response. |
| Auto-response enabled, short overlap during an active response | Discard the short overlap as an acknowledgement. | Emit the committed/no-response acknowledgement; do not create or defer a response. |
@@ -640,8 +678,7 @@ multi-turn runtime evidence.
During auto-response overlap, `preserve_realtime_input` distinguishes "do not
append this silent chunk to the native buffer" from "clear the open Realtime
item". Silent overlap no longer discards earlier user PCM. A separate explicit
`server_vad` policy is documented in the Realtime contract above.
item". Silent overlap no longer discards earlier user PCM.
The first chunk of one overlapping input item also reserves its target model
turn. A later Realtime commit uses that reserved identity even if response EOS
@@ -770,9 +807,12 @@ Graph capture. This is a model execution constraint, not an E2E-only override.
### Resource capabilities
The checked-in MiniCPM duplex deploy profile sets Stage0 and Stage1
`max_num_seqs` to two and configures the engine runtime manager with
`max_sessions=2`. The Realtime capability response therefore advertises
At the time of this snapshot the checked-in MiniCPM duplex deploy profile set
Stage0 and Stage1 `max_num_seqs` to two and configured the engine runtime
manager with `max_sessions=2`; the duplex settings have since been folded into
`vllm_omni/deploy/minicpmo_4_5.yaml`, which admits four sessions
(`duplex_session.max_sessions: 4`, matching its stage `max_num_seqs`). The
Realtime capability response advertises
multi-session and same-replica multi-session support with
`session_admission_mode="engine_managed"`. Client session fields cannot raise
this server-owned limit. Fresh E2E evidence covers two concurrent sessions in
@@ -1025,4 +1065,6 @@ Passing this checkpoint supports the statement:
It does not support claims for an acoustic-onset barge-in latency target across
arbitrary client chunk sizes, multi-session production concurrency, bounded
long-session KV, scheduler-native append, or video input.
long-session KV, or scheduler-native append. Camera-frame input
(`video_frames`) landed after this snapshot and is covered by its own E2E
test rather than by the evidence above.
+4 -1
View File
@@ -17,11 +17,14 @@ implementation contract; it is not, by itself, a general support claim.
### Runtime and stage execution
- [Full-Duplex Runtime (MiniCPM-o 4.5)](fullduplex.md)
- [Full-Duplex Runtime (PersonaPlex)](fullduplex-personaplex.md)
- [Disaggregated Inference](feature/disaggregated_inference.md)
- [Host Weight Runtime](feature/host_weight_runtime.md)
- [Async Chunk](feature/async_chunk.md)
- [Async Diffusion Output](feature/async_diffusion_output.md)
- [Async Omni Output Materialization](feature/omni_async_output_materialization.md)
- [Runner-to-model Prefill/Decode Phase Contract](feature/preprocess_phase_contract.md)
- [Automatic Prefix Caching in Omni Models](feature/prefix_caching.md)
- [Realtime AR-Diffusion Sessions](feature/realtime_ar_diffusion.md)
@@ -64,7 +67,7 @@ The design contracts separate selection mechanics from backend algorithms:
#### CPU offloading
- [Overview and Shared Contracts](feature/offloader/README.md)
- [Model-Level Offload](feature/offloader/cpu_offload.md)
- [Model-Level Offload](feature/offloader/module_offload.md)
- [Layerwise Offload](feature/offloader/layerwise_offload.md)
- [Distributed Layerwise Offload](feature/offloader/distributed_layerwise_offload.md)
@@ -8,22 +8,44 @@ owners:
- "@SamitHuang"
- "@fhfuih"
primary_code_paths:
- vllm_omni/diffusion/diffusion_engine.py
- vllm_omni/diffusion/request.py
- vllm_omni/diffusion/data.py
- vllm_omni/diffusion/sched/**
- vllm_omni/diffusion/executor/**
- vllm_omni/diffusion/worker/**
related_code_paths:
- vllm_omni/diffusion/stage_diffusion_client.py
- vllm_omni/diffusion/stage_diffusion_proc.py
- vllm_omni/diffusion/inline_stage_diffusion_client.py
- vllm_omni/diffusion/ipc.py
- vllm_omni/diffusion/hooks/**
- vllm_omni/diffusion/postprocess/**
depends_on:
- ../engine_orchestration.md
- ../stage_runtime.md
- ../input_output_modality_contracts.md
- diffusion_model_integration.md
validation_paths:
- tests/diffusion/diffusion_backend/**
- tests/diffusion/hooks/**
- tests/diffusion/test_diffusion_engine.py
- tests/diffusion/test_diffusion_engine_cleanup.py
- tests/diffusion/test_diffusion_engine_dummy_run.py
- tests/diffusion/test_diffusion_engine_rpc_routing.py
- tests/diffusion/test_diffusion_scheduler.py
- tests/diffusion/test_diffusion_model_runner.py
- tests/diffusion/test_diffusion_worker.py
- tests/diffusion/test_multiproc_engine_concurrency.py
- tests/diffusion/test_uniproc_executor.py
- tests/diffusion/test_result_pump.py
- tests/diffusion/test_async_output_worker.py
- tests/diffusion/test_diffusion_ipc.py
- tests/diffusion/test_diffusion_streaming_output.py
- tests/diffusion/test_stage_diffusion_proc.py
- tests/diffusion/test_inline_stage_diffusion_client.py
upstream_refs:
- diffusers.DiffusionPipeline
last_reviewed: 2026-07-16
last_reviewed: 2026-09-02
last_verified_commit: e51fe6ec1b9a9a0e14bb1fdb296d61b6593b93c6
---
# Diffusion runtime
@@ -31,6 +53,380 @@ last_reviewed: 2026-07-16
The diffusion runtime owns request admission, scheduling, execution, progress,
output, cancellation, and cleanup inside a diffusion stage.
It is easiest to think of the runtime as a small control loop:
1. the **scheduler** chooses ready requests;
2. the **executor** sends that choice to the worker layer;
3. each **worker** manages its device and delegates model work to a **runner**;
4. the runner calls the model pipeline and returns per-request results; and
5. the engine feeds those results back to the scheduler and output streams.
!!! note
This page describes the runtime plumbing. Model loading and pipeline
contracts belong to [Diffusion model integration](diffusion_model_integration.md).
Batch compatibility and step-state details belong to
[Diffusion continuous batching](continuous_batching.md).
## Goals and non-goals
The runtime aims to keep request policy separate from device execution. It
also keeps request identity stable from admission through the final output,
including cancellation and failures.
This module does **not** choose the next Omni stage, place stage replicas, or
define a model's denoising algorithm:
- cross-stage routing belongs to [Engine orchestration](../engine_orchestration.md);
- process placement and replica lifecycle belong to
[Stage runtime](../stage_runtime.md); and
- model-specific work belongs to
[Diffusion model integration](diffusion_model_integration.md).
## Runtime at a glance
The stage owns `DiffusionEngine` and a `DiffusionExecutor` backend. The inline
stage client keeps that stack in the caller process; a process-backed stage
runs it inside `StageDiffusionProc`.
The executor then chooses how the worker runs:
- **`uni`** (default when `num_gpus == 1`): `UniProcDiffusionExecutor` builds
one in-process worker. There is no worker subprocess and no shared-memory
RPC.
- **`mp`** (default when `num_gpus > 1`, or when set explicitly):
`MultiprocDiffusionExecutor` starts one `WorkerProc` per device.
Either way, each worker owns its device, distributed state,
`DiffusionWorker`, `DiffusionModelRunner`, and pipeline instance.
```mermaid
flowchart TB
client["Stage client"]
subgraph stage["Diffusion stage process"]
stageProc["StageDiffusionProc<br/>stage transport and lifecycle"]
engine["DiffusionEngine<br/>admission and control loop"]
scheduler["RequestScheduler or StepScheduler<br/>request state and policy"]
executor["DiffusionExecutor<br/>uni or mp backend"]
streams["Per-request output streams"]
stageProc --> engine
engine --> scheduler
scheduler -->|"DiffusionSchedulerOutput"| engine
engine --> executor
engine --> streams
end
subgraph workers["Worker layer"]
workerProc["WorkerProc<br/>mp IPC loop only"]
worker["DiffusionWorker<br/>device and distributed setup"]
runner["DiffusionModelRunner<br/>model state and execution"]
pipeline["Diffusion pipeline<br/>model-specific computation"]
workerProc --> worker
worker --> runner --> pipeline
end
client -->|"process-backed: ZMQ"| stageProc
client -.->|"inline: direct call"| engine
executor -->|"mp: broadcast / result queues"| workerProc
executor -.->|"uni: in-process call"| worker
streams -->|"process-backed"| stageProc
stageProc -->|"ZMQ results"| client
streams -.->|"inline"| client
```
!!! info "Stage process vs worker process"
A deployed stage may already run in a `StageDiffusionProc` child process.
That is separate from the optional `mp` worker processes. With `uni`, the
worker lives in the same process as the engine.
## Component responsibilities
| Component | Owns | Does not own |
| --- | --- | --- |
| `DiffusionEngine` | Admission, the busy loop, scheduler/executor coordination, RPC ordering, output streams, warmup, abort routing | Batch policy details, device setup, model computation |
| `BaseScheduler` | Request states, waiting/running sets, capacity, compatibility, terminal transitions | IPC, worker calls, output formatting |
| `RequestScheduler` | Complete-request waves and optional admission delay for request-level batching | Denoising-step progress |
| `StepScheduler` | Per-request denoising progress and step-wise completion | Pipeline tensors and model state |
| `DiffusionExecutor` | Execution backend contract, worker RPC, health, shutdown | Admission and request-state transitions |
| `UniProcDiffusionExecutor` | Single in-process worker for `num_gpus == 1`; no IPC or async output pump | Multi-GPU execution |
| `MultiprocDiffusionExecutor` | Worker processes, shared-memory message queues, result dispatch, worker monitoring | In-process single-GPU path |
| `WorkerProc` | One `mp` worker process's IPC loop and reply rules | Scheduling policy; unused by `uni` |
| `DiffusionWorker` | Device/distributed setup, LoRA activation, profiling, sleep/wake, runner delegation | Request admission |
| `DiffusionModelRunner` | Pipeline loading, request-local model state, cache/compile setup, request or step execution | Queueing and cross-stage routing |
The boundary between policy and execution is
`DiffusionSchedulerOutput`. It contains newly admitted request payloads,
IDs for requests whose runner state is already cached, finished IDs,
optional KV-prefetch work, and per-request `diffusion_kv_metadata` on
`NewRequestData` envelopes when paged Diffusion KV is enabled. Executors and
workers consume this output; they do not decide what should run next.
`BaseScheduler.initialize()` sets `max_num_running_reqs` from
`od_config.max_num_seqs` (default 1). The engine may override that capacity
for distributed layerwise offload with AllGather DP concurrency.
## One scheduling tick
Async requests share one engine busy loop. Worker calls and control RPCs pass
through that loop, so they cannot race on the executor transport.
```mermaid
sequenceDiagram
participant Caller
participant Engine as DiffusionEngine
participant Scheduler
participant Executor
participant Worker
participant Runner
Caller->>Engine: add request
Engine->>Scheduler: add_request(request)
Engine->>Scheduler: schedule()
Scheduler-->>Engine: DiffusionSchedulerOutput
Engine->>Executor: execute_batch() or execute_step()
Executor->>Worker: run worker method
Worker->>Runner: execute model or one step
Runner-->>Worker: RunnerOutput(s)
Worker-->>Executor: result
Executor-->>Engine: BaseRunnerOutput
Engine->>Scheduler: update_from_output(...)
Scheduler-->>Engine: finished request IDs
Engine-->>Caller: chunk or final output
```
The engine catches request execution errors and turns them into per-request
error outputs. A dead worker group is different: the executor marks itself
failed, health checks raise `EngineDeadError`, and the owning stage client
handles the stage-level failure.
## Execution modes
The engine resolves one mode at startup and binds the matching scheduler and
executor call.
| Mode | Scheduler | Executor call | Runner path |
| --- | --- | --- | --- |
| Request batch | `RequestScheduler` | `execute_batch()` | `execute_model()` for one request, or `execute_model_batch()` for a fused batch |
| Step batch | `StepScheduler` | `execute_step()` | `execute_stepwise()` |
**Request mode** runs a complete pipeline forward for each scheduled wave. A
single-request wave is the conservative path. A multi-request wave usually
uses fused `execute_model_batch()` and requires the pipeline to declare
request-batch support.
**Distributed layerwise offload with AllGather (DLO DP concurrency)** is a
separate multiproc dispatch path. It activates when
`data_parallel_size > 1`, `enable_distributed_layerwise_offload` is set, and
`dlo_use_allgather` is true. The engine then sets `dp_concurrent = True` and
raises `scheduler.max_num_running_reqs` to `dp_size`, overriding the
`max_num_seqs` value from `initialize()`. The scheduler still emits a
multi-request `DiffusionSchedulerOutput`; the multiproc executor routes that
wave through `execute_request()` instead of fused `execute_model_batch()`.
Optional admission coalescing can fill a `dp_size` wave: when
`request_batch_max_wait_ms > 0`, `RequestScheduler` may wait briefly before
the first schedule of a wave (under `dp_concurrent`, the stable window is
`min(0.3s, wait/2)`). With the default `request_batch_max_wait_ms == 0`,
there is no wait.
Before dispatch, `MultiprocDiffusionExecutor.execute_request()` rejects waves
whose requests differ in sampling-parameter compatibility or `extra_args`
(AllGather requires every DP rank to follow the same forward schedule). Each
worker rank then picks one envelope from the wave:
`req[dp_rank % len(req)]`, so every rank enters the same collective while
computing different requests.
**Step mode** keeps `StepRequestState` in the runner. New requests run
`prepare_encode()` once; every tick runs `denoise_step()` and
`step_scheduler()`; completed requests run `post_decode()`. The scheduler keeps
only lifecycle and progress metadata—it does not hold model tensors.
**Step mode is also the path for streaming diffusion output**. Enabling
`streaming_output` turns on step execution if needed. Chunk-capable pipelines
can emit intermediate outputs through the same request stream; final-only step
pipelines still emit only the final result. If the pipeline does not implement
step execution, initialization fails.
!!! tip
For user-facing flags and examples, see
[Diffusion execution modes](../../../user_guide/diffusion/execution_modes.md).
That guide explains how to select a mode; this page explains what happens
after the choice is made.
## Request state and identity
The scheduler is the source of truth for an admitted request. Its normal state
flow is:
```mermaid
stateDiagram-v2
[*] --> WAITING: add_request
WAITING --> RUNNING: schedule
WAITING --> FINISHED_ABORTED: abort
RUNNING --> FINISHED_ABORTED: abort
RUNNING --> FINISHED_COMPLETED: successful output
RUNNING --> FINISHED_ERROR: failed or missing output
FINISHED_COMPLETED --> [*]: deliver and remove state
FINISHED_ABORTED --> [*]: deliver and remove state
FINISHED_ERROR --> [*]: deliver and remove state
```
`PREEMPTED` and `BaseScheduler.preempt_request()` exist on the scheduler API,
but the engine loop does not call them today—only scheduler unit tests do.
`request_id` ties together scheduler state, runner state, executor results, and
the output queue. Batch positions are temporary and must never replace request
identity when results are mapped back.
Only compatible requests enter the same running wave. The scheduler compares a
mode-specific sampling-parameter key and stops at the first incompatible
waiting request. This is intentionally conservative and can cause
head-of-line blocking.
## Executor and IPC
`DiffusionExecutor` is the backend interface. Built-in backends are selected by
`distributed_executor_backend`:
| Backend | When chosen | Worker layout |
| --- | --- | --- |
| `uni` | Default for `num_gpus == 1`, or set explicitly | One in-process worker |
| `mp` | Default for `num_gpus > 1`, or set explicitly | One worker process per device |
Ray and external-launcher diffusion backends are not implemented. A custom
`DiffusionExecutor` subclass or import path is also accepted.
### Uniproc backend
`UniProcDiffusionExecutor` constructs `WorkerWrapperBase` in the engine process
and calls worker methods directly. It avoids a second model load, MessageQueue
rings, ZMQ IPC sockets, and `/dev/shm` tensor packing. RPC timeouts are
accepted for interface parity but are not enforced: a hung worker blocks the
calling thread. Sticky accelerator faults mark the executor dead through
failure callbacks; ordinary request errors stay per-request.
### Multiproc backend
`MultiprocDiffusionExecutor`:
1. creates a broadcast queue shared by all workers;
2. spawns one worker process per configured device;
3. waits until every worker reports ready;
4. sends generation and control calls as RPC messages;
5. applies rank-aware reply rules so only expected ranks respond; and
6. monitors worker process sentinels and fails the executor if a worker dies.
In request mode on this backend, each async output produces two correlated
messages keyed by `async_output_id`:
- `COMPUTE_DONE` — the forward finished and the device can start the next
request.
- `OUTPUT_READY` — background D2H/SHM packing finished and the final output
is ready.
The worker queues background packing work before it enqueues `COMPUTE_DONE`,
but either message may reach the executor first. The result pump and
`execute_batch()` must handle both orderings. Step mode keeps the synchronous
result path and does not start those pumps.
See [Async diffusion output](../../feature/async_diffusion_output.md) for the
multiproc request-mode timeline.
## Worker and runner boundary
On the `mp` path, `WorkerProc` receives messages and calls methods through
`WorkerWrapperBase`. On the `uni` path, the executor owns that wrapper
directly. In both cases, `DiffusionWorker` handles the parts tied to a device:
distributed initialization, model-runner construction, LoRA activation,
profiling, and memory sleep/wake. It delegates actual model work to
`DiffusionModelRunner`.
The runner owns long-lived model-side state:
- the loaded pipeline and compilation setup;
- cache and offload integration;
- random-generator setup;
- request batches for complete forwards; and
- `StepRequestState` plus `InputBatch` for step execution.
This split keeps infrastructure out of model pipelines. A pipeline receives a
request batch or step state and performs model work; it should not inspect
engine queues or change scheduler state.
## Lifecycle and cleanup
### Startup
`DiffusionEngine.make_engine()` resolves the engine class, constructs the
executor and scheduler, then normally runs a small dummy request to warm up the
model.
Warmup is skipped only when distributed layerwise offload with AllGather
is enabled (via `enable_distributed_layerwise_offload` and `dlo_use_allgather`)
and `max(data_parallel_size, sequence_parallel_size) > 1`, because
the dummy run sends one request while AllGather needs every shard rank to
enter the same collective. If initialization or warmup fails, the runtime
closes scheduler state and worker resources before returning the error.
### Cancellation
`abort()` places request IDs on an abort queue. The busy loop marks those
requests `FINISHED_ABORTED`; finalization removes scheduler state and emits an
aborted output when a consumer still exists. The runner removes cached
step-mode state when it receives a scheduler output that reports the finished
request ID.
On the multiproc request-mode async path, abort or consumer drop while output
is still materializing should release the associated async-output bookkeeping
so late results are not retained after the request ends
*(pending #6253/#6439/#6580)*. Today, an unconsumed `OUTPUT_READY` is cached
by design until `wait_output_ready()` or executor teardown. Dropping an
output-stream consumer removes that consumer's queue. It does not take
ownership of scheduler cleanup.
### Shutdown and worker failure
`DiffusionEngine.close()` is idempotent. It stops the busy loop, wakes pending
streams with an error, closes scheduler state, and shuts down the executor.
- **`mp`**: the executor asks workers to stop, waits for them, then terminates
workers that miss the grace period. Shutdown marks unfinished RPC and
async-output futures as failed, then clears them. Completed async outputs
cached for later `wait_output_ready()` calls are dropped with the executor
object.
- **`uni`**: the executor shuts down the in-process worker, drops the final
model reference, and empties the accelerator cache so a later engine can reuse
the device.
!!! warning
Do not continue using a worker group after a fatal collective timeout or
unexpected worker exit. Distributed state may be incomplete. The
multiprocess executor deliberately fails closed so the stage can restart.
The uniproc executor fails closed when the accelerator context itself is
poisoned.
## Extension boundaries
- A custom engine may set `default_diffusion_model_runner_cls`; an explicit
`diffusion_model_runner_cls` configuration still wins.
- Tests and custom engine integrations may inject a `BaseScheduler` subclass.
`SchedulerInterface` remains only as a deprecated compatibility name.
- `distributed_executor_backend` may be `"uni"`, `"mp"`, a custom `DiffusionExecutor`
subclass, or an import path. A backend must preserve scheduler output,
request identity, health, and cleanup contracts. Use `"mp"` when you need
process isolation or multi-GPU; `"uni"` is the single-GPU default.
- Worker extensions go through `WorkerWrapperBase`. They add worker methods;
they do not become a second scheduler or request lifecycle.
These are advanced Python integration points, not stable end-user CLI
customization surfaces.
## Candidate invariants
### DIFF-RUNTIME-INV-001: One lifecycle owner
@@ -41,19 +437,50 @@ until completion, cancellation, or failure.
### DIFF-RUNTIME-INV-002: Execution follows scheduler output
**Rule:** Executors and workers MUST execute scheduler decisions without
admitting, reordering, or forwarding requests independently.
admitting, reordering, or forwarding requests independently. Distributed
layerwise offload with AllGather is an exception at dispatch time: the
scheduler may schedule a multi-request wave, but each DP rank executes one
envelope from that wave so every rank enters the same collective schedule.
### DIFF-RUNTIME-INV-003: Terminal cleanup is complete
**Rule:** Every terminal path MUST release request state, temporary tensors,
hooks, and runtime-owned resources.
hooks, and runtime-owned resources. Async-output bookkeeping on abort or
consumer drop before consumption is also required *(pending #6253/#6439/#6580)*.
### DIFF-RUNTIME-INV-004: Optional features use runtime hooks
**Rule:** Cache, profiling, offload, and parallel features SHOULD integrate at
defined hooks instead of creating another request lifecycle.
### DIFF-RUNTIME-INV-005: Results keep request identity
**Rule:** Batched and asynchronous results MUST be mapped by stable request ID,
not by assumed completion order.
## Safe-change guide
Test success, cancellation, failure, shutdown, multiple requests, and resource
cleanup.
Test the smallest affected slice, then cover its neighboring boundary:
| Change area | Minimum evidence |
| --- | --- |
| Admission or state transitions | `test_diffusion_scheduler.py`, including duplicate IDs, compatibility, abort, and missing output |
| Engine loop or output delivery | `test_diffusion_engine.py`, `test_diffusion_engine_cleanup.py`, `test_diffusion_engine_rpc_routing.py` |
| Executor or IPC | `test_multiproc_engine_concurrency.py`, `test_uniproc_executor.py`, `test_result_pump.py`, `test_diffusion_ipc.py`, `test_async_output_worker.py` |
| Worker or runner | `test_diffusion_worker.py`, `test_diffusion_model_runner.py` |
| Stage boundary | `test_stage_diffusion_proc.py`, `test_inline_stage_diffusion_client.py` |
| Warmup or streaming | `test_diffusion_engine_dummy_run.py`, `test_diffusion_streaming_output.py` |
Always exercise success, cancellation, per-request failure, fatal worker
failure, repeated shutdown, one request, and multiple compatible requests.
For step-mode changes, also test partial progress and cleanup of runner state.
## Related documents
- [Diffusion module overview](index.md)
- [Diffusion model integration](diffusion_model_integration.md)
- [Diffusion continuous batching](continuous_batching.md)
- [Diffusion execution modes](../../../user_guide/diffusion/execution_modes.md)
- [Async diffusion output](../../feature/async_diffusion_output.md)
- [Engine orchestration](../engine_orchestration.md)
- [Stage runtime](../stage_runtime.md)
+5 -1
View File
@@ -28,6 +28,9 @@ related_code_paths:
- vllm_omni/engine/stage_pool.py
- vllm_omni/outputs/output_processor.py
- vllm_omni/distributed/omni_coordinator/**
# Duplex kernel design is owned by ../fullduplex.md; listed here because the
# orchestrator hosts the duplex control plane.
- vllm_omni/engine/duplex/**
depends_on:
- input_output_modality_contracts.md
- error_contracts.md
@@ -44,7 +47,8 @@ validation_paths:
- tests/engine/test_orchestrator_stage_input_bridge.py
- tests/engine/test_cfg_companion_lifecycle.py
- tests/engine/test_rpc_result_router.py
- tests/e2e/features/fullduplex/engine/**
- tests/engine/duplex/**
- tests/engine/test_duplex_import_boundary.py
upstream_refs:
- vllm.v1.engine.EngineCoreRequest
- vllm.v1.engine.EngineCoreOutputs
+9
View File
@@ -26,9 +26,18 @@ primary_code_paths:
- vllm_omni/entrypoints/client_request_state.py
- vllm_omni/entrypoints/stage_utils.py
- vllm_omni/entrypoints/utils.py
- vllm_omni/entrypoints/duplex/**
- vllm_omni/entrypoints/duplex_request_client.py
- vllm_omni/clients/**
primary_path_exceptions:
- path: vllm_omni/entrypoints/openai/errors.py
owner: error_contracts.md
- path: vllm_omni/entrypoints/duplex/**
owner: ../fullduplex.md
- path: vllm_omni/entrypoints/duplex_request_client.py
owner: ../fullduplex.md
- path: vllm_omni/clients/**
owner: ../fullduplex.md
related_code_paths:
- vllm_omni/errors.py
- vllm_omni/inputs/**
@@ -410,8 +410,8 @@ Notes:
- `stages[].max_num_seqs` controls stage-level batching.
- Thinker/Talker commonly use `enforce_eager: false` for CUDA Graph paths.
- Code2Wav often remains eager (`enforce_eager: true`) depending on runtime behavior.
- Qwen3-Omni defaults `VLLM_USE_FLASHINFER_MOE_FP16=0`. The Triton has been more stable & faster
than the FlashInfer CUTLASS unquantized MoE backend on recent vLLM rebases.
- The Qwen3-Omni deploy config pins `moe_backend: triton` for the Thinker and Talker stages. Triton has been more
stable and faster than the FlashInfer CUTLASS unquantized MoE backend on recent vLLM rebases.
#### 2) Enable async chunk
+1 -1
View File
@@ -1,7 +1,7 @@
# Installation
!!! important
vLLM-Omni is released against the matching upstream vLLM major/minor version. vLLM-Omni 0.28.x requires vLLM 0.28.x; the stable v0.28.0 instructions pin vLLM 0.28.0.
vLLM-Omni is released against the matching upstream vLLM major/minor version. The 0.29 development line requires vLLM 0.29.x; the source-install instructions pin vLLM 0.29.0. Published vLLM-Omni 0.28.0 wheels and images still require vLLM 0.28.x.
vLLM-Omni supports the following hardware platforms:
+2
View File
@@ -34,6 +34,8 @@ vLLM-Omni is a Python library that supports the following GPU variants. The libr
### Pre-built wheels
The source-install instructions target vLLM 0.29.0. Until vLLM-Omni 0.29.0 artifacts are published, the CUDA/ROCm pre-built examples below remain on the matching 0.28.0 release pair.
Note: Pre-built wheels are currently available for vLLM-Omni 0.11.0rc1, 0.12.0rc1, 0.14.0rc1, 0.14.0, 0.16.0, 0.18.0, 0.20.0, 0.21.0, 0.22.0, 0.23.0, 0.24.0, 0.25.0, 0.26.0, and 0.28.0. If you need a newer unreleased revision, please [build from source](https://docs.vllm.ai/projects/vllm-omni/en/latest/getting_started/installation/gpu/#build-wheel-from-source).
=== "NVIDIA CUDA"
@@ -5,7 +5,7 @@
# --8<-- [end:requirements]
# --8<-- [start:set-up-using-python]
vLLM-Omni depends on the matching major/minor release of vLLM. The vLLM-Omni 0.28.x release line uses vLLM 0.28.x.
vLLM-Omni depends on the matching major/minor release of vLLM. The 0.29 development line uses vLLM 0.29.x. Published 0.28.0 wheels and images use vLLM 0.28.x.
!!! note
PyTorch installed via `conda` will statically link `NCCL` library, which can cause issues when vLLM tries to use `NCCL`. See <gh-issue:8420> for more details.
@@ -18,6 +18,8 @@ Therefore, it is recommended to install vLLM and vLLM-Omni with a **fresh new**
#### Installation of vLLM
These pre-built wheel instructions install the published vLLM-Omni 0.28.0 release. For the 0.29 development line, use the source-install instructions below.
vLLM-Omni is built based on vLLM. Please install it with command below.
```bash
uv pip install vllm==0.28.0 --torch-backend=auto
@@ -26,12 +28,12 @@ uv pip install vllm==0.28.0 --torch-backend=auto
#### Installation of vLLM-Omni
```bash
uv pip install vllm-omni
uv pip install vllm-omni==0.28.0
```
To run Gradio demos, also install the optional extras:
```bash
uv pip install 'vllm-omni[demo]'
uv pip install 'vllm-omni[demo]==0.28.0'
```
# --8<-- [end:pre-built-wheels]
@@ -39,13 +41,13 @@ uv pip install 'vllm-omni[demo]'
# --8<-- [start:build-wheel-from-source]
#### Installation of vLLM
If you do not need to modify source code of vLLM, you can directly install the stable 0.28.0 release version of the library
If you do not need to modify source code of vLLM, you can directly install the stable 0.29.0 release version of the library
```bash
uv pip install vllm==0.28.0 --torch-backend=auto
uv pip install vllm==0.29.0 --torch-backend=auto
```
The 0.28.0 release of vLLM ships CUDA 13.0-compatible binaries by default. If you need a different CUDA variant or want to reuse an existing PyTorch installation, build vLLM from source instead.
The 0.29.0 release of vLLM ships CUDA 13.0-compatible binaries by default. If you need a different CUDA variant or want to reuse an existing PyTorch installation, build vLLM from source instead.
#### Installation of vLLM-Omni
Since vllm-omni is rapidly evolving, it's recommended to install it from source
@@ -66,12 +68,12 @@ If you want to check, modify or debug with source code of vLLM, install the libr
```bash
git clone https://github.com/vllm-project/vllm.git
cd vllm
git checkout v0.28.0
git checkout v0.29.0
```
Set up environment variables to get pre-built wheels. If there are internet problems, just download the whl file manually. And set `VLLM_PRECOMPILED_WHEEL_LOCATION` as your local absolute path of whl file.
```bash
#For CUDA 13.0 (the default for v0.28.0; the wheel filename has no `+cu130` suffix)
export VLLM_PRECOMPILED_WHEEL_LOCATION=https://github.com/vllm-project/vllm/releases/download/v0.28.0/vllm-0.28.0-cp38-abi3-manylinux_2_28_x86_64.whl
#For CUDA 13.0 (the default for v0.29.0; the wheel filename has no `+cu130` suffix)
export VLLM_PRECOMPILED_WHEEL_LOCATION=https://github.com/vllm-project/vllm/releases/download/v0.29.0/vllm-0.29.0-cp38-abi3-manylinux_2_28_x86_64.whl
```
Install vllm with command below (If you have no existing PyTorch).
```bash
@@ -131,7 +133,7 @@ If you want to specify the base vLLM version:
```bash
DOCKER_BUILDKIT=1 docker build \
-f docker/Dockerfile.cuda \
--build-arg BASE_IMAGE=vllm/vllm-openai:v0.28.0 \
--build-arg BASE_IMAGE=vllm/vllm-openai:v0.29.0 \
-t vllm-omni-cuda .
```
@@ -11,6 +11,8 @@ vLLM-Omni current recommends the steps in under setup through Docker Images.
#### Installation of vLLM
These pre-built wheel instructions install the published vLLM-Omni 0.28.0 release. For the 0.29 development line, use the source-install instructions below.
vLLM-Omni is built based on vLLM. Please install it with command below.
```bash
uv pip install vllm==0.28.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.28.0/rocm723
@@ -22,7 +24,7 @@ uv pip install vllm==0.28.0+rocm723 --extra-index-url https://wheels.vllm.ai/roc
# we need to add --no-build-isolation as the torch
# is not obtained from pypi, we have to install using the
# torch installed in our environment
uv pip install vllm-omni
uv pip install vllm-omni==0.28.0
# Optional if want to run Qwen3 TTS
uv pip uninstall onnxruntime # should be removed before we can install onnxruntime-rocm
@@ -34,13 +36,13 @@ uv pip install onnxruntime-rocm
# --8<-- [start:build-wheel-from-source]
#### Installation of vLLM
If you do not need to modify source code of vLLM, you can directly install the stable 0.28.0 release version of the library
If you do not need to modify source code of vLLM, you can directly install the stable 0.29.0 release version of the library
```bash
uv pip install vllm==0.28.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.28.0/rocm723
uv pip install vllm==0.29.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.29.0/rocm723
```
The pre-built 0.28.0 vLLM wheel targets ROCm 7.2.3. If you need a different ROCm stack or want to reuse an existing PyTorch installation, build vLLM from source instead.
The pre-built 0.29.0 vLLM wheel targets ROCm 7.2.3. If you need a different ROCm stack or want to reuse an existing PyTorch installation, build vLLM from source instead.
#### Installation of vLLM-Omni
Since vllm-omni is rapidly evolving, it's recommended to install it from source
@@ -58,7 +60,7 @@ If you want to check, modify or debug with source code of vLLM, install the libr
```bash
git clone https://github.com/vllm-project/vllm.git
cd vllm
git checkout v0.28.0
git checkout v0.29.0
python3 -m pip install -r requirements/rocm.txt
python3 setup.py develop
```
+156 -42
View File
@@ -7,13 +7,34 @@ For detailed hardware and software requirements, please refer to the [vLLM-Ascen
The recommended way to use vLLM-Omni on NPU is through the vLLM-Ascend pre-built Docker images:
=== "A2"
```bash
export IMAGE=quay.io/atlas-ci/vllm-ascend:v0.28.0
```
=== "A3"
```bash
export IMAGE=quay.io/atlas-ci/vllm-ascend:v0.28.0-a3
```
=== "A5"
```bash
export IMAGE=quay.io/atlas-ci/vllm-ascend:v0.28.0-a5
```
=== "310P (Experimental)"
```bash
export IMAGE=quay.io/atlas-ci/vllm-ascend:v0.28.0-310p
```
```bash
# vLLM-Ascend image
# Atlas A2: quay.io/atlas-ci/vllm-ascend:v0.28.0
# Atlas A3: quay.io/atlas-ci/vllm-ascend:v0.28.0-a3
docker run --rm \
--name vllm-omni-npu \
--shm-size=1g \
--shm-size=64g \
--device /dev/davinci0 \
--device /dev/davinci1 \
--device /dev/davinci2 \
@@ -27,8 +48,8 @@ docker run --rm \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v /root/.cache:/root/.cache \
-p 8000:8000 \
-it quay.io/atlas-ci/vllm-ascend:v0.28.0-a3 bash
-p 8091:8091 \
-it "$IMAGE" bash
# Inside the container, install vLLM-Omni from source
cd /vllm-workspace
@@ -72,53 +93,146 @@ Supported images as following.
| image name | Hardware | OS |
|-|-|-|
| image-tag | Atlas A2 | Ubuntu |
| image-tag-a3 | Atlas A3 | Ubuntu |
| image-tag | A2 | Ubuntu |
| image-tag-a3 | A3 | Ubuntu |
| image-tag-a5 | A5 | Ubuntu |
| image-tag-310p | 310P (experimental) | Ubuntu |
Here's an example deployment command that has been verified on 4 x NPUs:
```bash
# Atlas A2: quay.io/ascend/vllm-omni:v0.28.0
# Atlas A3: quay.io/ascend/vllm-omni:v0.28.0-a3
docker run --rm \
--name vllm-omni-a3 \
--shm-size=4g \
--device /dev/davinci0 \
--device /dev/davinci1 \
--device /dev/davinci2 \
--device /dev/davinci3 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v ~/.cache:/root/.cache \
-p 8091:8091 \
-it quay.io/ascend/vllm-omni:v0.28.0-a3 bash
```
=== "A2"
```bash
export IMAGE=quay.io/ascend/vllm-omni:v0.28.0
docker run --rm \
--name vllm-omni-a2 \
--shm-size=64g \
--device /dev/davinci0 \
--device /dev/davinci1 \
--device /dev/davinci2 \
--device /dev/davinci3 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v ~/.cache:/root/.cache \
-p 8091:8091 \
-it "$IMAGE" bash
```
=== "A3"
```bash
export IMAGE=quay.io/ascend/vllm-omni:v0.28.0-a3
docker run --rm \
--name vllm-omni-a3 \
--shm-size=64g \
--device /dev/davinci0 \
--device /dev/davinci1 \
--device /dev/davinci2 \
--device /dev/davinci3 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v ~/.cache:/root/.cache \
-p 8091:8091 \
-it "$IMAGE" bash
```
=== "A5"
```bash
export IMAGE=quay.io/ascend/vllm-omni:v0.28.0-a5
docker run --rm \
--name vllm-omni-a5 \
--shm-size=64g \
--device /dev/davinci0 \
--device /dev/davinci1 \
--device /dev/davinci2 \
--device /dev/davinci3 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v ~/.cache:/root/.cache \
-p 8091:8091 \
-it "$IMAGE" bash
```
=== "310P (Experimental)"
```bash
export IMAGE=quay.io/ascend/vllm-omni:v0.28.0-310p
docker run --rm \
--name vllm-omni-310p \
--shm-size=64g \
--device /dev/davinci0 \
--device /dev/davinci1 \
--device /dev/davinci2 \
--device /dev/davinci3 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v ~/.cache:/root/.cache \
-p 8091:8091 \
-it "$IMAGE" bash
```
!!! tip
You can use this docker image to serve models the same way you would with in vLLM! To do so, make sure you overwrite the default entrypoint (`vllm serve --omni`) which works only for models supported in the vLLM-Omni project.
Or build IMAGE from **source code**:
Or build the image from **source code**:
```bash
git clone https://github.com/vllm-project/vllm-omni.git
cd vllm-omni
# A2
# docker build -t vllm-omni-dev-image:latest -f ./docker/Dockerfile.npu .
# A3
docker build -t vllm-omni-dev-image:latest -f ./docker/Dockerfile.npu.a3 .
```
All hardware variants use `docker/Dockerfile.npu`. Choose the device suffix for your hardware:
=== "A2"
```bash
docker build -t vllm-omni-dev-image:latest -f docker/Dockerfile.npu .
```
=== "A3"
```bash
docker build -t vllm-omni-dev-image:latest -f docker/Dockerfile.npu \
--build-arg VLLM_ASCEND_DEVICE_SUFFIX=-a3 .
```
=== "A5"
```bash
docker build -t vllm-omni-dev-image:latest -f docker/Dockerfile.npu \
--build-arg VLLM_ASCEND_DEVICE_SUFFIX=-a5 .
```
=== "310P (Experimental)"
```bash
docker build -t vllm-omni-dev-image:latest -f docker/Dockerfile.npu \
--build-arg VLLM_ASCEND_DEVICE_SUFFIX=-310p .
```
# --8<-- [end:pre-built-images]
+3 -3
View File
@@ -19,10 +19,10 @@ uv venv --python 3.12 --seed
source .venv/bin/activate
# On CUDA
uv pip install vllm==0.28.0 --torch-backend=auto
uv pip install vllm==0.29.0 --torch-backend=auto
# On ROCm
uv pip install vllm==0.28.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.28.0/rocm723
uv pip install vllm==0.29.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.29.0/rocm723
git clone https://github.com/vllm-project/vllm-omni.git
cd vllm-omni
@@ -34,7 +34,7 @@ For additional installation methods — please see the [installation guide](inst
!!! note
It is important to install the same major & minor version of vLLM and vLLM Omni, otherwise things may not work as expected. If the versions are misaligned, you will see a warning when you import vLLM Omni.
If you are seeing strange behavior with the `vllm` command not handling the `--omni` flag correctly, you most likely have a version mismatch with vLLM < `0.28.0` and vLLM Omni `0.28.0`, as vLLM Omni no longer hijacks the vLLM entrypoint. Updating vLLM should resolve this issue.
If you are seeing strange behavior with the `vllm` command not handling the `--omni` flag correctly, you most likely have a version mismatch with vLLM < `0.29.0` and vLLM Omni `0.29.0`, as vLLM Omni no longer hijacks the vLLM entrypoint. Updating vLLM should resolve this issue.
## Offline Inference
+7
View File
@@ -22,6 +22,13 @@ CATEGORIES = {
"name": "Entry Points",
"description": "Main entry points for vLLM-Omni inference and serving.",
},
"clients": {
"name": "Clients",
"description": (
"Client libraries that talk to a running vLLM-Omni server "
"(the full-duplex Realtime client and its per-model session presets)."
),
},
"inputs": {
"name": "Inputs",
"description": "Input data structures for multi-modal inputs.",
+1
View File
@@ -124,6 +124,7 @@ def extract_omni_serve_subparser_init():
"make_arg_parser": lambda parser: parser, # no-op for doc
"_ensure_vllm_platform": lambda: None, # no-op for doc
"_nonneg_finite_float": float,
"_parse_stage_overrides": __import__("json").loads,
"VLLM_SUBCMD_PARSER_EPILOG": "",
"logger": logger,
"DummySubparsers": DummySubparsers,
+8 -5
View File
@@ -18,7 +18,7 @@ th {
</style>
| Architecture | Models | Example HF Models | NVIDIA GPU | AMD GPU | Ascend NPU | Intel GPU | Recipe |
|--------------|--------|-------------------|------------|---------|-----|-----------|--------|
| -------------- | -------- | ------------------- | ------------ | --------- | ----- | ----------- | -------- |
| `Qwen3OmniMoeForConditionalGeneration` | Qwen3-Omni | `Qwen/Qwen3-Omni-30B-A3B-Instruct` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Qwen/Qwen3-Omni.md) |
| `Qwen2_5OmniForConditionalGeneration` | Qwen2.5-Omni | `Qwen/Qwen2.5-Omni-7B`, `Qwen/Qwen2.5-Omni-3B` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
| `MingFlashOmniForConditionalGeneration` + `MingImagePipeline` | Ming-flash-omni-2.0 (omni-speech + imagegen<sup>1</sup>) | `Jonathan1909/Ming-flash-omni-2.0` | ✅︎ | | | | — |
@@ -42,7 +42,7 @@ th {
| `SanaWmPipeline` | SANA-WM | `BBBBruce/SANA-WM_bidirectional-stage1-diffusers` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/NVIDIA/SANA-WM.md) |
| `Wan22S2VPipeline` | Wan2.2-S2V | `Wan-AI/Wan2.2-S2V-14B` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
| `Wan22VACEPipeline` | Wan2.1-VACE | `Wan-AI/Wan2.1-VACE-1.3B-diffusers`, `Wan-AI/Wan2.1-VACE-14B-diffusers` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
| `Wan22VACEPipeline` | Wan2.2-VACE | `Pyros13/Wan2.2-VACE-Fun-A14B-Diffusers` | ✅︎ | | | ✅︎ | — |
| `Wan22VACEPipeline` | Wan2.2-VACE | `Pyros13/Wan2.2-VACE-Fun-A14B-Diffusers` | ✅︎ | | | ✅︎ | — |
| `LTX2Pipeline` | LTX-2 / LTX-2.3 one-stage T2V and I2V | `Lightricks/LTX-2`, `diffusers/LTX-2.3-Diffusers` | ✅︎ | ✅︎ | | | — |
| `LTX2TwoStagePipeline` | LTX-2 / LTX-2.3 ordinary two-stage T2V and I2V | `Lightricks/LTX-2`, `diffusers/LTX-2.3-Diffusers` + matching Lightricks LoRA and upsampler | ✅︎ | ✅︎ | | | — |
| `LTX2DistilledOneStagePipeline` | LTX-2 / LTX-2.3 merged-distilled one-stage T2V and I2V | `rootonchair/LTX-2-19b-distilled`, `diffusers/LTX-2.3-Distilled-Diffusers` | ✅︎ | ✅︎ | | | — |
@@ -55,6 +55,7 @@ th {
| `SanaVideoPipeline` | SANA-Video-2B T2V (native 480p/720p; Diffusers adapter validated at both resolutions) | `Efficient-Large-Model/SANA-Video_2B_480p_diffusers`, `Efficient-Large-Model/SANA-Video_2B_720p_diffusers` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/NVIDIA/SANA-Video-2B.md) |
| `SanaImageToVideoPipeline` | SANA-Video-2B I2V (native and Diffusers adapter validated at 480p/720p) | `Efficient-Large-Model/SANA-Video_2B_480p_diffusers`, `Efficient-Large-Model/SANA-Video_2B_720p_diffusers` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/NVIDIA/SANA-Video-2B.md) |
| `MiniMaxH3Pipeline` | MiniMax H3 T2VA, first/last-keyframe FL2VA, and mixed-reference Ref2VA | `MiniMaxAI/MiniMax-H3` | ✅︎ | ✅︎ | | | [Published](https://recipes.vllm.ai/MiniMaxAI/MiniMax-H3) |
| `Magi2Pipeline` | MAGI-2 native Preview-stage T2VA and I2VA (272p/540p) | `sand-ai/MAGI-2-preview` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/SandAI/MAGI-2-preview-L20X.md) |
| `DreamZeroPipeline` | DreamZero-DROID | `GEAR-Dreams/DreamZero-DROID` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
| `Pi0Pipeline` | π0 (Pi-Zero) VLA | `lerobot/pi0_base` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/lerobot/Pi0.md) |
| `HeliosPipeline`, `HeliosPyramidPipeline` | Helios | `BestWishYsh/Helios-Base`, `BestWishYsh/Helios-Mid`, `BestWishYsh/Helios-Distilled` | ✅︎ | ✅︎ | ✅︎ | | — |
@@ -63,12 +64,13 @@ th {
| `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`, `Boogu/Boogu-Image-0.1-Edit-Turbo` | ✅︎ | | | ✅︎ | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Boogu/Boogu-Image.md) |
| `BooguImageTurboPipeline` | Boogu-Image Turbo distilled T2I | `Boogu/Boogu-Image-0.1-Turbo` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/Boogu/Boogu-Image.md) |
| `StableDiffusionXLPipeline` | Stable-Diffusion-XL | `stabilityai/stable-diffusion-xl-base-1.0` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
| `StableDiffusion3Pipeline` | Stable-Diffusion-3 | `stabilityai/stable-diffusion-3.5-medium` | ✅︎ | ✅︎ | | ✅︎ | — |
| `CosyVoice3Model` | CosyVoice3 | `FunAudioLLM/Fun-CosyVoice3-0.5B-2512` | ✅︎ | ✅︎ | | ✅︎ | — |
| `OmniVoiceModel` | OmniVoice | `k2-fsa/OmniVoice` | ✅︎ | | | ✅︎ | — |
| `VoxCPM2TalkerForConditionalGeneration` | VoxCPM2 | `openbmb/VoxCPM2` | ✅︎ | | | ✅︎ | — |
| `DotsTTSForConditionalGeneration` | dots.tts | `rednote-hilab/dots.tts-soar` | ✅︎ | | | | — |
| `DotsTTSForConditionalGeneration` | dots.tts | `dots-studio/dots.tts-soar` | ✅︎ | | | | — |
| `MammothModa2ForConditionalGeneration` | MammothModa2-Preview | `bytedance-research/MammothModa2-Preview` | ✅︎ | ✅︎ | | | — |
| `MammothModa2ForConditionalGeneration` | MammothModa2-Dev (AR-only image understanding) | `bytedance-research/MammothModa2-Dev` | ✅︎ | | | | — |
| `Flux2KleinPipeline` | FLUX.2-klein | `black-forest-labs/FLUX.2-klein-4B`, `black-forest-labs/FLUX.2-klein-9B` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
@@ -95,17 +97,18 @@ th {
| `MiMoV2ASRForCausalLM` | MiMo-V2.5-ASR | `XiaomiMiMo/MiMo-V2.5-ASR` | ✅︎ | ✅︎ | | | — |
| `Flux2Pipeline` | FLUX.2-dev | `black-forest-labs/FLUX.2-dev` | ✅︎ | ✅︎ | | | — |
| `FishSpeechSlowARForConditionalGeneration` | Fish Speech S2 Pro | `fishaudio/s2-pro` | ✅︎ | ✅︎ | | ✅︎ | — |
| `Audio8TTSSlowARForConditionalGeneration` | Audio8 TTS Preview | `Audio8/Audio8-TTS-Preview-0.6b` | ✅︎ | | | | — |
| `SenseNovaU1Pipeline` | SenseNova-U1 (DiT-only), SenseNova-U1.5 | `SenseNova/SenseNova-U1-8B-MoT`, `sensenova/SenseNova-U1.5-8B-MoT` | ✅︎ | | | | [Repository](https://github.com/vllm-project/vllm-omni/blob/main/recipes/SenseNova/SenseNova-U1.5.md) |
| `LancePipeline` | Lance | `bytedance-research/Lance` | ✅︎ | | | | — |
| `HunyuanVideo15Pipeline` | HunyuanVideo-1.5-T2V | `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v`, `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-720p_t2v` | ✅︎ | ✅︎ | | | — |
| `HunyuanVideo15ImageToVideoPipeline` | HunyuanVideo-1.5-I2V | `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_i2v`, `hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-720p_i2v` | ✅︎ | ✅︎ | | | — |
| `VoxtralTTSForConditionalGeneration` | Voxtral TTS | `mistralai/Voxtral-4B-TTS-2603` | ✅︎ | ✅︎ | | | — |
| `CovoAudioForConditionalGeneration` | Covo-Audio-Chat | `tencent/Covo-Audio-Chat` | ✅︎ | | | | — |
|`DyninOmniForConditionalGeneration` | Dynin-Omni | `snu-aidas/Dynin-Omni` | ✅︎ | | | ✅︎ | — |
| `DyninOmniForConditionalGeneration` | Dynin-Omni | `snu-aidas/Dynin-Omni` | ✅︎ | | | ✅︎ | — |
| `MiniCPMO45OmniForConditionalGeneration` | MiniCPM-o 4.5 | `openbmb/MiniCPM-o-4_5` | ✅︎ | | ✅︎ | | — |
| `ErnieImagePipeline` | ERNIE-Image | `baidu/ERNIE-Image`, `baidu/ERNIE-Image-Turbo` | ✅︎ | ✅︎ | ✅︎ | ✅︎ | — |
| `GepardTalkerForConditionalGeneration` | Gepard-1.0 | `nineninesix/gepard-1.0` | ✅︎ | | | ✅︎ | — |
|`HiDreamImagePipeline` | HiDream-I1-Full | `HiDream-ai/HiDream-I1-Full` | ✅︎ | ✅︎ | | | — |
| `HiDreamImagePipeline` | HiDream-I1-Full | `HiDream-ai/HiDream-I1-Full` | ✅︎ | ✅︎ | | | — |
| `HiDreamO1ImagePipeline` | HiDream-O1-Image | `HiDream-ai/HiDream-O1-Image` | ✅︎ | | | | — |
The `Recipe` column links the corresponding deployment recipe. `Published` is
+6 -7
View File
@@ -43,7 +43,7 @@ Completions for conversational or heterogeneous omni pipelines, rather than as
the default wrapper for every generation task.
| Task | Endpoint | Request | Response | Details |
|------|----------|---------|----------|---------|
| ------ | ---------- | --------- | ---------- | --------- |
| Conversation or multimodal understanding/generation | `POST /v1/chat/completions` | JSON | OpenAI-style JSON or SSE | [Chat Completions](chat_completions_api.md) |
| Text-to-speech | `POST /v1/audio/speech` | JSON | Audio bytes or SSE | [Speech](speech_api.md) |
| Sound, music, or ambient audio generation | `POST /v1/audio/generate` | JSON | Audio bytes | [Audio Generation](audio_generate_api.md) |
@@ -141,7 +141,7 @@ These WebSocket APIs have different event schemas and cannot be used
interchangeably.
| Workload | Endpoint | Interaction model | Details |
|----------|----------|-------------------|---------|
| ---------- | ---------- | ------------------- | --------- |
| Incremental text input for speech synthesis | `WS /v1/audio/speech/stream` | Send text events and receive audio | [Streaming Text to Speech](speech_api.md#streaming-text-input-websocket) |
| Live video understanding | `WS /v1/video/chat/stream` | Send video frames and receive text/audio | [Streaming Video Input](video_stream_api.md) |
| Turn-based realtime audio | `WS /v1/realtime` | Stream one audio input and receive transcript/audio events | [Realtime Audio](realtime_api.md) |
@@ -158,7 +158,7 @@ ordinary realtime handler when duplex is unavailable.
## Related Endpoints
| Purpose | Endpoints | Reference |
|---------|-----------|-----------|
| --------- | ----------- | ----------- |
| Discovery and readiness | `GET /health`, `GET /v1/models` | This page |
| Batched conversations | `POST /v1/chat/completions/batch` | [Batch requests](chat_completions_api.md#batch-requests) |
| Batched speech | `POST /v1/audio/speech/batch` | [Batch speech generation](speech_api.md#batch-speech-generation) |
@@ -168,11 +168,10 @@ ordinary realtime handler when duplex is unavailable.
## Standalone Experimental Servers
PersonaPlex also provides a Moshi-compatible WebSocket server, while JoyVL
provides a stateful interaction orchestrator in front of another model server.
These are separate processes with their own routes; they are not additional
JoyVL provides a stateful interaction orchestrator in front of another model
server. It is a separate process with its own routes; they are not additional
paths on every `vllm serve --omni` instance. See [Standalone Experimental
Servers](standalone_servers.md) before deploying either one.
Servers](standalone_servers.md) before deploying it.
## Compatibility Notes
+7 -7
View File
@@ -55,7 +55,7 @@ with open("cat.wav", "wb") as f:
### Endpoint
```
```text
POST /v1/audio/generate
Content-Type: application/json
```
@@ -63,7 +63,7 @@ Content-Type: application/json
### Request Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| ----------- | ------ | --------- | ------------- |
| `input` | string | **required** | Text prompt describing the audio to generate |
| `model` | string | server's model | Model to use (optional, should match server if specified) |
| `response_format` | string | "wav" | Audio format: wav, mp3, flac, pcm, opus |
@@ -72,7 +72,7 @@ Content-Type: application/json
#### Diffusion Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| ----------- | ------ | --------- | ------------- |
| `audio_length` | float | null | Audio duration in seconds (default value is the max ~47s for `stable-audio-open-1.0`) |
| `audio_start` | float | 0.0 | Audio start time in seconds |
| `negative_prompt` | string | null | Text describing what to avoid in generation |
@@ -85,7 +85,7 @@ Content-Type: application/json
Returns binary audio data with the appropriate `Content-Type` header:
| `response_format` | Content-Type |
|--------------------|--------------|
| -------------------- | -------------- |
| `wav` | `audio/wav` |
| `mp3` | `audio/mpeg` |
| `flac` | `audio/flac` |
@@ -210,7 +210,7 @@ with open("thunder.wav", "wb") as f:
Controls how closely the generated audio follows the text prompt.
| Range | Behaviour |
|-------|-----------|
| ------- | ----------- |
| 3 - 5 | More creative / varied output |
| 7 (default) | Balanced adherence |
| 10+ | Strict adherence to the prompt |
@@ -220,7 +220,7 @@ Controls how closely the generated audio follows the text prompt.
Controls the number of denoising steps in the diffusion process.
| Steps | Quality | Speed | Use Case |
|-------|---------|-------|----------|
| ------- | --------- | ------- | ---------- |
| 50 | Good | Fast | Quick previews |
| 100 | Very Good | Medium | General purpose |
| 150+ | Excellent | Slow | Final / critical audio |
@@ -240,7 +240,7 @@ Describes characteristics to avoid. Common negative prompts include:
## Supported Models
| Model | Description |
|-------|-------------|
| ----- | ----------- |
| `stabilityai/stable-audio-open-1.0` | Open-source audio generation model, up to ~47 seconds, 44.1 kHz stereo |
## Error Responses
+12 -6
View File
@@ -1,16 +1,20 @@
# Full-Duplex WebSocket API
vLLM-Omni provides an experimental full-duplex runtime for models that can
vLLM-Omni provides a full-duplex runtime for models that can
continue receiving speech while producing speech. It adds persistent session
state, model-specific turn policy, overlap handling, playback acknowledgement,
and optional session resume.
Full duplex is distinct from the turn-based [Realtime Audio API](realtime_api.md).
This page is the endpoint overview. For the complete wire contract, the
`vllm_omni.clients.duplex.DuplexClient` Python library, and the per-model
capability gates, see the [Realtime Duplex API guide](realtime_duplex_api.md).
## Choose an Endpoint
| Endpoint | Protocol | Recommended use |
|----------|----------|-----------------|
| --- | --- | --- |
| `WS /v1/realtime?duplex=1` | OpenAI Realtime-style event projection | Applications and browser clients |
| `WS /v1/duplex` | Native vLLM-Omni duplex events | Runtime integration and low-level testing |
@@ -41,8 +45,10 @@ and control plane as part of their registered pipeline.
The current unified-runtime integrations are:
- MiniCPM-o 4.5, using `vllm_omni/deploy/minicpmo_4_5_duplex.yaml`;
- PersonaPlex, whose default `vllm_omni/deploy/personaplex.yaml` enables duplex.
- MiniCPM-o 4.5, using `vllm_omni/deploy/minicpmo_4_5.yaml`
(`session_mode: duplex`);
- PersonaPlex, whose default `vllm_omni/deploy/personaplex.yaml` enables duplex;
- Nemotron VoiceChat, via its registered duplex plugin package.
JoyVL is a separate HTTP interaction orchestrator and does not use these
WebSocket endpoints. See [Standalone Experimental Servers](standalone_servers.md).
@@ -53,7 +59,7 @@ Start the duplex deployment:
```bash
vllm serve openbmb/MiniCPM-o-4_5 --omni \
--deploy-config vllm_omni/deploy/minicpmo_4_5_duplex.yaml \
--deploy-config vllm_omni/deploy/minicpmo_4_5.yaml \
--trust-remote-code \
--port 8091
```
@@ -112,5 +118,5 @@ applications should use the provided Realtime client where possible.
See the [MiniCPM-o example](https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/minicpmo),
[PersonaPlex example](https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/personaplex),
and [full-duplex runtime design](https://github.com/vllm-project/vllm-omni/blob/main/vllm_omni/experimental/fullduplex/DESIGN.md)
and the [full-duplex runtime design](../design/fullduplex.md)
for model-specific validation and architecture details.
+4 -5
View File
@@ -30,7 +30,6 @@ curl -s -D >(grep -i x-request-id >&2) \
-F "output_format=png"
```
**Using OpenAI SDK:**
```python
@@ -72,7 +71,7 @@ with open("edit_out_http.jpeg", "wb") as f:
### Endpoint
```
```text
POST /v1/images/edits
Content-Type: multipart/form-data
```
@@ -82,7 +81,7 @@ Content-Type: multipart/form-data
#### OpenAI Standard Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| ----------- | ------ | --------- | ------------- |
| `prompt` | string | **required** | A text description of the desired image |
| `model` | string | server's model | Model to use (optional, should match server if specified) |
| `image` | string or array | **required** | The image(s) to edit. |
@@ -98,7 +97,7 @@ Content-Type: multipart/form-data
#### vllm-omni Extension Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| ----------- | ------ | --------- | ------------- |
| `url` | string or array | None | The image(s) to edit. |
| `negative_prompt` | string | null | Text describing what to avoid in the image |
| `num_inference_steps` | integer | model defaults | Number of diffusion steps |
@@ -212,7 +211,6 @@ Use the `ar_delta` events for progressive display of the AR-generated
recaption. Decode the `b64_json` field from the final `image` event to get the
edited image.
## Parameter Handling
The API passes parameters directly to the diffusion pipeline without model-specific transformation:
@@ -266,6 +264,7 @@ curl http://localhost:8000/v1/images/edits \
### Out of Memory
If you encounter OOM errors:
1. Reduce image size: `"size": "512x512"`
2. Reduce inference steps: `"num_inference_steps": 25`
+4 -4
View File
@@ -45,7 +45,6 @@ curl -o dragon.png -X POST http://localhost:8000/v1/images/generations \
}'
```
**Using Python:**
```python
@@ -123,7 +122,7 @@ response = client.images.generate(
### Endpoint
```
```text
POST /v1/images/generations
Content-Type: application/json
```
@@ -133,7 +132,7 @@ Content-Type: application/json
#### OpenAI Standard Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| ----------- | ------ | --------- | ------------- |
| `prompt` | string | **required** | Text description of the desired image |
| `model` | string | server's model | Model to use (optional, should match server if specified) |
| `n` | integer | 1 | Number of images to generate (1-10) |
@@ -144,7 +143,7 @@ Content-Type: application/json
#### vllm-omni Extension Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| ----------- | ------ | --------- | ------------- |
| `negative_prompt` | string | null | Text describing what to avoid in the image |
| `num_inference_steps` | integer | model defaults | Number of diffusion steps |
| `guidance_scale` | float | model defaults | Classifier-free guidance scale (typically 0.0-20.0) |
@@ -267,6 +266,7 @@ curl http://localhost:8000/v1/images/generations \
### Out of Memory
If you encounter OOM errors:
1. Reduce image size: `"size": "512x512"`
2. Reduce inference steps: `"num_inference_steps": 25`
3. Generate fewer images: `"n": 1`
+7 -1
View File
@@ -64,7 +64,7 @@ connect
<- msgpack(policy_server_config)
infer
-> msgpack({"endpoint": "infer", "session_id": "...", ...observation})
-> msgpack({"endpoint": "infer", "session_id": "...", "seed": <optional int>, ...observation})
<- msgpack(ndarray | dict[str, ndarray])
reset
@@ -92,6 +92,12 @@ frames are not observation messages.
- The policy pipeline owns observation transforms and persistent model state,
normally keyed by `session_id`; the API layer forwards the raw observation
dictionary.
- An optional integer `seed` in an inference message becomes the engine
request's `sampling_params.seed`. A policy whose sampling consumes the
request generator (GR00T-N1.7) returns the same action chunk for the same
observation and seed. If omitted, the engine assigns a random per-request
seed, so unseeded inferences are independent of each other. The API layer
consumes `seed`; it is not forwarded inside the observation.
## Limits and Errors
+2 -2
View File
@@ -5,7 +5,7 @@ receive incremental transcription and synthesized response audio. This is a
turn-based streaming API: it starts one generation over an audio stream and
finishes after the client closes that input stream.
For a model that listens while it is already speaking, use the experimental
For a model that listens while it is already speaking, use the
[Full-Duplex API](full_duplex_api.md) instead.
## Quick Start
@@ -35,7 +35,7 @@ Messages in both directions are JSON text frames. Audio carried inside an
event is base64-encoded raw PCM16.
| Direction | Event | Purpose |
|-----------|-------|---------|
| ----------- | ------- | --------- |
| Server to client | `session.created` | Confirms that the WebSocket connection is ready |
| Client to server | `session.update` | Selects and validates the served model |
| Client to server | `input_audio_buffer.commit` with `final: false` | Starts generation over the incoming stream |
File diff suppressed because it is too large. Load diff
+31 -12
View File
@@ -279,10 +279,15 @@ curl -X POST http://localhost:8091/v1/audio/voices \
## Streaming Text Input (WebSocket)
The `/v1/audio/speech/stream` WebSocket endpoint accepts text incrementally and generates audio per sentence as boundaries are detected.
The `/v1/audio/speech/stream` WebSocket endpoint accepts text incrementally.
By default (`split_granularity=none`) it buffers until `input.done` and
synthesizes that flush as **one** TTS request, which keeps long-form timbre
stable. Set `split_granularity` to `sentence` or `clause` to emit a request
at each detected boundary (lower time-to-first-audio for STT/LLM pipelines).
> Note: text input is always streamed incrementally. Audio output remains sentence-scoped:
> use `stream_audio=false` for one binary frame per sentence, or `stream_audio=true` for one or more PCM chunks per sentence.
> Note: `stream_audio` only changes how **audio bytes** are framed (one WAV/PCM
> payload vs chunked PCM). Text segmentation is controlled separately by
> `split_granularity`.
### WebSocket Protocol
@@ -314,14 +319,16 @@ upstream LLM) pays the WebSocket handshake once instead of once per utterance.
- The session config is sticky. Send `input.text` again straight after
`session.done` to reuse it, or send another `session.config` first to change
voice, format, or reference audio. A `session.config` sent while text is
still buffered is rejected so no pending input is silently dropped.
- An utterance is the flush unit, not a linguistic one: it is whatever text was
buffered when `input.done` arrived, of any length, synthesized as one request.
`utterance_index` counts those flushes across the connection, so it tells you
which `input.done` a frame belongs to. `sentence_index` counts within one
flush and so pairs with `total_sentences`, which means every utterance reports
`sentence_index: 0` of `total_sentences: 1` (or `0` for an empty buffer).
voice, format, or reference audio. A `session.config` sent in the middle of
an utterance is rejected so no pending input is silently dropped and so a
split utterance cannot end up half in one voice and half in another.
- An utterance is the flush unit, not a linguistic one: it is one `input.done`
cycle. `utterance_index` counts those flushes across the connection, so it
tells you which `input.done` a frame belongs to. `sentence_index` counts the
TTS requests inside one flush and so pairs with `total_sentences`: with the
default `split_granularity=none` that is always `sentence_index: 0` of
`total_sentences: 1` (or `0` for an empty buffer), while `sentence` or
`clause` counts the linguistic units actually synthesized.
- End the connection with `session.close`, or by closing the socket. An idle
connection is still closed after the server's idle timeout, which now also
applies to the gap between utterances.
@@ -332,7 +339,15 @@ All REST API parameters are supported, plus:
| Parameter | Type | Default | Description |
| ----------- | ------ | --------- | ------------- |
| `stream_audio` | bool | false | Stream one or more PCM chunks for the buffered input over WebSocket |
| `stream_audio` | bool | false | Stream one or more PCM chunks for each TTS request over WebSocket |
| `split_granularity` | string | `"none"` | `"none"`: one request per `input.done`. `"sentence"`: split on `.!?` plus CJK `。!?…`, Indic danda `।॥`, and Arabic `؟`. `"clause"`: also split on `,;,;،؛`. |
| `seed` | integer | null | Forwarded to the speech engine for this session |
ASCII punctuation only ends a unit when whitespace or `input.done` follows it,
and decimals (`3.14`), thousands separators (`1,000`), abbreviations (`Dr.`,
`e.g.`) and initials (`J. R.`) are not treated as boundaries. A punctuation run
and any closing quote or bracket stay with the unit they close, so `Wait...`
and `He said "Hello."` are one request each.
```bash
DELETE /v1/audio/voices/{name}
@@ -752,6 +767,10 @@ Fish Speech uses `ref_audio` and `ref_text` for voice cloning (no `task_type` ne
| ------- | ------------- |
| `k2-fsa/OmniVoice` | Pure-diffusion TTS. Supports voice cloning via `ref_audio` (with optional `ref_text`); no built-in voice presets. |
OmniVoice uses packed variable-length attention for batched generator execution. The attention operator accepts FP16 and BF16 inputs, so its query,
key, and value tensors are evaluated in BF16 even when the stage is configured with `dtype: float32`; the attention output is converted back to the model's
hidden-state dtype before the output projection. Consequently, a float32 stage configuration does not imply FP32 attention arithmetic.
### VoxCPM2
| Model | Description |
+10 -52
View File
@@ -1,61 +1,19 @@
# Standalone Experimental Servers
Most vLLM-Omni endpoints run on the unified server started with
`vllm serve <model> --omni`. The experimental full-duplex package also contains
two separate user-facing server processes. Their routes are not added to the
unified API server.
`vllm serve <model> --omni`. The experimental package also contains one
separate user-facing server process; its routes are not added to the unified
API server.
| Server | Transport | Role |
|--------|-----------|------|
| PersonaPlex compatibility server | WebSocket | Hosts the official PersonaPlex browser protocol and a raw-PCM alternative |
| --- | --- | --- |
| JoyVL interaction server | HTTP | Adds state, memory, proactive decisions, and delegation in front of a separate OpenAI-compatible model backend |
## PersonaPlex Compatibility Server
Use this server when a client requires the official Moshi/PersonaPlex wire
protocol or the bundled browser UI. For the engine-owned unified duplex path,
use [`WS /v1/realtime?duplex=1`](full_duplex_api.md) instead.
Start it with:
```bash
HF_TOKEN=... CUDA_VISIBLE_DEVICES=0 python -m \
vllm_omni.experimental.fullduplex.personaplex.serving.server \
--port 8091
```
| Route | Purpose |
|-------|---------|
| `GET /` | Official PersonaPlex browser client, when its assets are available |
| `GET /health` | Readiness check |
| `WS /api/chat` | Moshi-compatible tagged binary protocol with Opus audio |
| `WS /v1/audio/duplex` | Simpler float32 raw-PCM protocol |
### `/api/chat` Protocol
The first byte of every binary frame is a message tag:
| Direction | Tag | Payload |
|-----------|-----|---------|
| Server to client | `0x00` | Ready handshake |
| Client to server | `0x01` | Opus microphone audio at 24 kHz |
| Server to client | `0x01` | Opus response audio |
| Server to client | `0x02` | UTF-8 inner-monologue text |
The optional `text_prompt` and `voice_prompt` query parameters select a persona
and voice. The default server admits one conversation; use `--batch-size N` to
configure multiple lockstep slots. Connections beyond capacity close with
WebSocket code 1013.
### `/v1/audio/duplex` Protocol
Send `{"type":"open","persona":"...","voice":"..."}` as a JSON text frame,
then exchange binary float32 PCM frames. The server returns generated float32
audio as binary frames and text as `{"type":"text","text":"..."}`. Send
`{"type":"close"}` and wait for `{"type":"done"}` to flush and finish.
See the [PersonaPlex example](https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/personaplex)
for the browser and headless clients.
PersonaPlex no longer ships a standalone compatibility server: it is served
through the unified duplex path (`vllm_omni/deploy/personaplex.yaml`) over
[`WS /v1/realtime?duplex=1`](full_duplex_api.md); see the
[PersonaPlex example](https://github.com/vllm-project/vllm-omni/tree/main/examples/online_serving/personaplex)
for its clients.
## JoyVL Interaction Server
@@ -80,7 +38,7 @@ python -m vllm_omni.experimental.fullduplex.joyvl.serving.server \
```
| Method and route | Purpose |
|------------------|---------|
| ------------------ | --------- |
| `GET /health` | Readiness check |
| `GET /v1/models` | Reports the configured interaction model |
| `POST /v1/chat/completions` | Processes one frame or interaction tick |
+1 -1
View File
@@ -36,7 +36,7 @@ immediately followed by one binary WebSocket frame containing fragmented MP4
bytes.
| Direction | Event or frame | Purpose |
|-----------|----------------|---------|
| ----------- | ---------------- | --------- |
| Client to server | `session.start` | Starts generation with a prompt and video parameters |
| Server to client | `video.start` | Confirms the request ID, format, and accepted configuration |
| Server to client | `video.chunk_metadata` | Describes the next binary media frame |
+4 -4
View File
@@ -36,7 +36,7 @@ WebSocket /v1/video/chat/stream
### Protocol
| Direction | Type | Required fields | Description |
|-----------|------|-----------------|-------------|
| ----------- | ------ | ----------------- | ------------- |
| Client -> Server | `session.config` | none | First message. Configures output modalities, frame sampling, EVS, and prompts. |
| Client -> Server | `video.frame` | `data` | Base64 JPEG/PNG frame. |
| Client -> Server | `audio.chunk` | `data` | Base64 PCM16 16 kHz mono audio bytes. |
@@ -53,7 +53,7 @@ WebSocket /v1/video/chat/stream
### `session.config` Fields
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| ------- | ------ | --------- | ------------- |
| `model` | string or null | null | Optional model name. Usually omitted because the server hosts one model. |
| `modalities` | list[string] | `["text", "audio"]` | Output modalities. Use `["text"]`, `["audio"]`, or both. |
| `num_frames` | integer, 1-128 | `4` | Number of buffered frames sampled for each query. |
@@ -69,7 +69,7 @@ WebSocket /v1/video/chat/stream
The server accepts these legacy field names and rewrites them before validation. New clients should send the canonical names above.
| Legacy field | Canonical field |
|--------------|-----------------|
| -------------- | ----------------- |
| `num_sample_frames` | `num_frames` |
| `evs_enabled` | `enable_frame_filter` |
| `evs_threshold` | `frame_filter_threshold` |
@@ -77,7 +77,7 @@ The server accepts these legacy field names and rewrites them before validation.
### Environment Variables
| Variable | Values | Default | Description |
|----------|--------|---------|-------------|
| -------- | ------ | ------- | ----------- |
| `VLLM_VIDEO_ASYNC_CHUNK` | `on`, `off` | `on` | Wire-level streaming switch. `off` buffers server-side deltas and emits coalesced outputs at the end of a query. |
| `VLLM_VIDEO_AUDIO_DELTA_MODE` | `fast`, `slow` | `fast` | Audio delta extraction strategy. `fast` emits only newly produced chunks; `slow` recomputes from accumulated audio and exists for A/B verification. |
+22 -4
View File
@@ -41,7 +41,7 @@ curl -L "http://localhost:8091/v1/videos/${video_id}/content" -o output.mp4
### Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| ---------- | -------- | ------------- |
| `/v1/videos` | `POST` | Create an asynchronous video generation job |
| `/v1/videos/sync` | `POST` | Generate a video synchronously and return raw video bytes |
| `/v1/videos/{video_id}` | `GET` | Retrieve job status and metadata |
@@ -56,7 +56,7 @@ curl -L "http://localhost:8091/v1/videos/${video_id}/content" -o output.mp4
#### OpenAI-style fields
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| ----------- | ------ | --------- | ------------- |
| `prompt` | string | **required** | Text prompt for video generation |
| `model` | string | server's model | Optional model name |
| `seconds` | string | null | Requested clip duration in seconds |
@@ -66,8 +66,10 @@ curl -L "http://localhost:8091/v1/videos/${video_id}/content" -o output.mp4
#### vLLM-Omni extension fields
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| ----------- | ------ | --------- | ------------- |
| `input_reference` | file | null | Uploaded reference image or video for image-to-video/video-to-video requests |
| `control_reference` | file | null | Optional uploaded image/video control, up to 512 MiB, for models that declare control-upload support |
| `control_type` | string | null | Model control name associated with `control_reference`; currently Cosmos3 supports `edge`, `blur`, `depth`, `seg`, and `wsm` |
| `image_reference` | string | null | JSON-encoded reference image payload; do not combine with `input_reference` or `video_reference` |
| `video_reference` | string | null | JSON-encoded reference video payload; do not combine with `input_reference` or `image_reference` |
| `audio_reference` | string | null | JSON-encoded audio reference for speech-to-video: `{"audio_url": "..."}` — supports HTTP(s) URLs or base64 data URLs |
@@ -177,6 +179,23 @@ corresponding addition. `negative_metadata_mode` accepts `same`, `inverse`, or
`none` and defaults to `same` for transfer. See the Cosmos3 recipe for complete
examples.
A client that cannot place the control on the server filesystem can upload one
control with `control_reference` and identify it with `control_type`. The API
streams the upload to request-scoped storage, supplies its path to the model,
and removes it after synchronous or asynchronous generation completes. Other
options for the selected control can remain in `extra_params`; do not also set
`control` or `control_path` there. Uploads larger than 512 MiB are rejected.
```bash
curl -s http://localhost:8091/v1/videos/sync \
-F "prompt=Preserve the scene while following the world-state control" \
-F "input_reference=@input.mp4;type=video/mp4" \
-F "control_reference=@wsm.mp4;type=video/mp4" \
-F "control_type=wsm" \
-F 'extra_params={"wsm":{"control_weight":1.0}}' \
-o output.mp4
```
HTTP redirects for `image_reference.image_url` follow vLLM's
`VLLM_MEDIA_URL_ALLOW_REDIRECTS` setting. Before starting the server, set it to
`1` (the default) to allow redirects or `0` to reject them. A redirect target
@@ -203,7 +222,6 @@ curl -s http://localhost:8091/v1/videos \
-F "fps=16"
```
### Synchronous Generation
```bash
+1 -1
View File
@@ -18,4 +18,4 @@ A: AWQ is available through the inherited vLLM quantization registry, and vLLM-O
> Q: Does vLLM-Omni support multimodal streaming input and output?
A: Yes, for specific models and serving paths. Current capabilities include [streaming video input](../serving/video_stream_api.md), [streaming diffusion output](../user_guide/diffusion/execution_modes.md#streaming-output), and experimental full-duplex real-time audio described in the [project overview](../README.md). Check the model and API documentation because input and output support is model-specific.
A: Yes, for specific models and serving paths. Current capabilities include [streaming video input](../serving/video_stream_api.md), [streaming diffusion output](../user_guide/diffusion/execution_modes.md#streaming-output), and [full-duplex realtime audio](../serving/realtime_duplex_api.md). Check the model and API documentation because input and output support is model-specific.
@@ -12,7 +12,7 @@ implementations to reduce numerical drift and sampling divergence.
## Installation
```bash
pip install kernels==0.14.1
pip install kernels==0.16.1
```
If `kernels` is unavailable, an explicit `FLASH_ATTN_HUB` or
+3 -3
View File
@@ -11,7 +11,7 @@ For the shared factory, discovery, and lifecycle contract, see the
| Strategy | Device residency | Parallel scope | Guide |
| --- | --- | --- | --- |
| Model-level (sequential) | One pipeline component group at a time | Single device | [Model-Level Offloading](offloader/cpu_offload.md) |
| Model-level (sequential) | One pipeline component group at a time | Single device | [Model-Level Offloading](offloader/module_offload.md) |
| Layerwise (blockwise) | One transformer block, with next-block prefetch | Single device | [Layerwise Offloading](offloader/layerwise_offload.md) |
| Distributed layerwise | Fixed two-block device buffer; optional host-weight sharding and AllGather | Multiple GPU/NPU ranks | [Distributed Layerwise Offloading](offloader/distributed_layerwise_offload.md) |
@@ -27,7 +27,7 @@ higher-priority strategy is selected.
## Quick selection
- Use [model-level offloading](offloader/cpu_offload.md) when swapping whole
- Use [model-level offloading](offloader/module_offload.md) when swapping whole
encoders and DiTs is enough to fit and phase-boundary transfers are
acceptable.
- Use [layerwise offloading](offloader/layerwise_offload.md) for compute-heavy video
@@ -63,7 +63,7 @@ These headings preserve links to sections that moved into dedicated guides.
## Model-level (Sequential) Offloading
See [Model-Level Offloading](offloader/cpu_offload.md).
See [Model-Level Offloading](offloader/module_offload.md).
## Layerwise (Blockwise) Offloading
+16 -6
View File
@@ -8,7 +8,7 @@ are exposed.
## Choose a Mode
| Goal | CLI configuration |
|---|---|
| --- | --- |
| Serial request execution | `--max-num-seqs 1` |
| Fused request-level batching | `--max-num-seqs N` |
| Single-request step execution | `--step-execution --max-num-seqs 1` |
@@ -94,8 +94,16 @@ validating a model or debugging correctness, then increase it for
multi-request throughput.
Step execution is capability-based, not a generic switch for every diffusion
model. Qwen-Image supports step-wise continuous batching. HunyuanImage3 also
supports it, but only when its resolved self-attention backend is `TORCH_SDPA`;
model. Qwen-Image supports step-wise continuous batching. BAGEL supports
step-wise continuous batching for image generation with `bagel.yaml`,
`bagel_think.yaml`, and `bagel_single_stage.yaml`. In the two-stage topologies,
only the diffusion stage uses step execution; the autoregressive Thinker is
unchanged. Explicit text-output requests in the single-stage topology retain
the complete-request path. Use at least two inference steps for BAGEL image
requests because BAGEL schedules `num_inference_steps - 1` denoising updates.
BAGEL step execution does not currently support sequence parallelism or a
diffusion cache backend. HunyuanImage3 also supports step execution, but only
when its resolved self-attention backend is `TORCH_SDPA`;
set `DIFFUSION_ATTENTION_BACKEND=TORCH_SDPA` or configure
`diffusion_attention_config.default.backend=TORCH_SDPA` before using
`--max-num-seqs >1`. See the
@@ -169,8 +177,10 @@ wait
The scheduler may batch these requests when their sampling parameters are
compatible. See the
[Image Generation API](../../serving/image_generation_api.md) for response
formats and additional client examples. Streaming models can use
model-specific streaming endpoints documented by their serving guide.
formats and additional client examples.
Streaming models can use model-specific streaming endpoints documented at
[Streaming Video Output API](../../serving/streaming_video_output_api.md).
## Python API
@@ -233,7 +243,7 @@ For step execution, set `step_execution: true` and remove
## CLI Reference
| Flag | Default | Effect |
|---|---:|---|
| --- | ---: | --- |
| `--step-execution` | disabled | Select step-wise scheduling |
| `--max-num-seqs` | `1` for diffusion stages | Set request- or step-scheduler capacity |
| `--request-batch-max-wait-ms` | `0` | Wait for burst coalescing in request mode |
+8 -2
View File
@@ -249,8 +249,14 @@ MiniMax-H3 supports two few-step mechanisms that must not be conflated:
- **Checkpoint-pinned schedule**: a merged release writes `base_schedule` into
`model_index.json`. Requests must pass `num_inference_steps` as the interval
count (for example `4` for `[1.0, 0.7, 0.4, 0.15, 0.0]`).
- **Runtime Turbo LoRA**: LightX2V Turbo artifacts request five sigma points and
enforce `flow_shift=6`, `audio_flow_shift=3`.
- **Runtime Turbo LoRA**: each LightX2V Turbo artifact carries its own contract,
read from its filename. A four-step artifact requests five sigma points and an
eight-step one requests nine; the 768p retrains enforce `flow_shift=6` and the
544p artifacts `flow_shift=12`. `audio_flow_shift=3` across the family. A
request that does not match the loaded artifact is rejected by name. Alpha
comes from the artifact's metadata, or 8 when it declares none. `ref2v`
artifacts require `--task-type ref2va`; `fl2v` artifacts serve T2VA and FL2VA.
The ComfyUI exports use a fused-QKV layout and are not supported.
- **Runtime native LoRA**: FlashGen-style artifacts declare
`key_format=minimax-h3-native` and embed `base_schedule` in safetensors
metadata. When active, the adapter schedule overrides the base checkpoint and
@@ -1,11 +1,13 @@
# Distributed Layerwise Offloading
Distributed layerwise offloading (DLO) extends block streaming to multi-device
deployments. With AllGather enabled, each rank stores roughly `1 / dp_size` of
the host weights and reconstructs each layer at runtime. Without AllGather,
each rank streams a complete block independently. Compatible TP1 deployments
can share checkpoint-backed host pages among processes on the same node;
otherwise DLO streams the ordinary loader's rank-local tensors.
deployments. Each selected component chooses its transfer independently. With
`allgather`, each rank stores roughly one shard per member of the effective DLO
group (DP, or SP when DP is one) and reconstructs each layer at runtime. With
`rank-local`, each rank streams a complete loader-produced block independently.
Compatible TP1 DiT deployments can share checkpoint-backed host pages among
processes on the same node; otherwise DLO streams the ordinary loader's
rank-local tensors.
See the [DLO feature design](../../../design/feature/offloader/distributed_layerwise_offload.md)
for the implementation contract and compatibility matrix.
@@ -24,52 +26,120 @@ Slots: slot 0: Layer N slot 1: Layer N+1
AllGather communicates only request-independent weight shards, so data-
parallel ranks may process different requests concurrently.
The public API describes intent in one nested configuration:
| Level | Setting | Question answered |
| --- | --- | --- |
| Granularity | `mode` | move a complete component (`module`) or stream its blocks (`layer`) |
| Selection | `components` list | whether `dit` and/or `text_encoder` may move to CPU |
| Per-component layer options | `layer_options` | choose rank-local or AllGather weight transfer and optionally retain leading DiT blocks |
`weight_transfer` applies only to model weights. It does not change encoder
activations or the encoder's compute parallelism. `rank-local` copies each
rank's loader-produced block to its device. `allgather` stores one shard of
that block per rank, copies each shard to its device, and reconstructs the
complete block with a device-side AllGather before compute.
The historical `enable_cpu_offload`, `enable_layerwise_offload`, and
`enable_distributed_layerwise_offload` fields remain compatibility aliases
for existing callers and model-specific stage lifecycles. Existing
combinations retain their priority (`distributed layerwise` > `layerwise` >
`module`). A compact config rejects conflicting compatibility strategies and
non-default legacy DLO tuning.
`distributed-layerwise` remains an internal backend name; users select
`mode="layer"`, and the runtime chooses the capable backend.
## Usage
```bash
# Four ranks with sharded host weights and AllGather
vllm serve /path/to/model --omni \
--enable-distributed-layerwise-offload \
--data-parallel-size 4
# Four SP ranks; Wan declares replicated text-encoder weights, so both
# selected components can use AllGather safely.
vllm serve Wan-AI/Wan2.2-T2V-A14B-Diffusers --omni \
--diffusion-offload-config \
'{"mode":"layer","components":["dit","text_encoder"],"layer_options":{"dit":{"weight_transfer":"allgather"},"text_encoder":{"weight_transfer":"allgather"}}}' \
--usp 4
# Standard-loader rank-local weights, without DLO AllGather
# Mix transfers independently: sharded DiT, loader-produced encoder layout
vllm serve /path/to/model --omni \
--enable-distributed-layerwise-offload \
--data-parallel-size 4 \
--dlo-no-use-allgather
--diffusion-offload-config \
'{"mode":"layer","components":["dit","text_encoder"],"layer_options":{"dit":{"weight_transfer":"allgather"},"text_encoder":{"weight_transfer":"rank-local"}}}' \
--usp 4
# Sequence parallel deployment
# Full-topology rank-local DLO for an existing model-specific lifecycle
vllm serve /path/to/model --omni \
--enable-distributed-layerwise-offload \
--dlo-no-use-allgather \
--usp 4
```
A compact layer config whose selected components are all `rank-local` and
whose `resident_layers` is zero resolves to the ordinary layerwise backend. It
uses the ordinary loader and does not request DLO's direct-checkpoint mmap.
Keep the compatibility DLO flags for a model-specific full-topology rank-local
lifecycle; do not add resident layers solely to force backend selection.
```python
from vllm_omni import Omni
omni = Omni(
model="/path/to/model",
enable_distributed_layerwise_offload=True,
dlo_use_allgather=True,
diffusion_offload_config={
"mode": "layer",
"components": ["dit", "text_encoder"],
"layer_options": {
"dit": {
"weight_transfer": "rank-local",
"resident_layers": 20,
},
"text_encoder": {
"weight_transfer": "allgather",
},
},
"pin_memory": True,
},
)
```
## Flags
| Flag | Meaning | Default |
| Setting | Meaning | Default |
| --- | --- | --- |
| `--enable-distributed-layerwise-offload` | Enable DLO | `false` |
| `--data-parallel-size N` | DP ranks and AllGather weight-sharding group | `1` |
| `--dlo-use-allgather` | Shard host weights and reconstruct with AllGather | `true` |
| `--dlo-no-use-allgather` | Stream complete rank-local blocks without a DLO weight collective | `false` |
| `--dlo-resident-layers N` | Keep N leading main-DiT blocks on device; requires no-AllGather and model-declared resident paths | `0` |
| `diffusion_offload_config.mode` | `module` or `layer` granularity | required |
| `diffusion_offload_config.components` | Non-empty list containing `dit`, `text_encoder`, or both | required |
| `layer_options.NAME.weight_transfer` | `rank-local` or `allgather` | `rank-local` |
| `layer_options.dit.resident_layers` | Leading main-DiT blocks kept on device; requires `rank-local` and model-declared resident paths | `0` |
| `diffusion_offload_config.pin_memory` | Pin streamed host memory for faster H2D copies | `true` |
| `--data-parallel-size N` | DP ranks; DP is the DLO group when greater than one, otherwise SP is used | `1` |
| `--host-weight-runtime-mode {disabled,preferred,required}` | HWR policy: no interaction, populate on a miss, or require an exact hit | `disabled` |
| `--host-weight-runtime-root PATH` | Writable node-local HWR store shared by workers in one storage domain; required for `preferred` and `required` | unset |
| `--dlo-host-registration-limit-gib N` | Optional per-worker ceiling for registering an HWR mmap; zero adds no ceiling | `0` |
HWR and host-registration tuning remain on the compatibility DLO interface and
cannot be combined with `diffusion_offload_config` in this release.
## Component and weight-transfer matrix
| Component | `allgather` | `rank-local` |
| --- | --- | --- |
| DiT | Shard each loader-visible block across the DLO DP group, or the SP group when DP is one | Stream each rank's complete loader-produced block |
| Text encoder | Same shard + AllGather path when the model declares identical encoder weights across the DLO group | Stream each rank's complete encoder block, including encoder-TP shards |
The text encoder's compute group and weight-transfer group are separate
concepts. An encoder TP group owns different parameter shards, so it is not a
valid AllGather offload group. Models opt in through
`OffloadPlan.encoder_dlo_weight_replication`; otherwise multi-rank encoder
AllGather fails at startup with guidance to use `rank-local`. This makes SP
AllGather available to replicated encoders without silently corrupting an
encoder-TP layout.
`resident_layers` remains a DiT-only setting and currently requires the DiT
transfer to be `rank-local`.
## Host-weight loading
The diffusion loader chooses host storage before DLO is enabled. It first
When the distributed backend is selected by AllGather, resident DiT layers, or
the compatibility DLO flag, the diffusion loader chooses host storage before
DLO is enabled. It first
attempts to build a complete, validated direct-checkpoint mmap plan. If names,
coverage, shape, dtype, topology, or loader-callback compatibility cannot be
proven, it runs the ordinary model loader instead. DLO consumes that result and
@@ -97,20 +167,20 @@ With direct checkpoint mmap, the loader:
6. preserves `post_load_weights()` and `validate_loaded_weights()` lifecycle
hooks.
For AllGather with a group larger than one, each process copies only its
persistent shard and then releases the source mapping. For no-AllGather, each
process keeps the mapping open and packs complete blocks through two bounded
pinned staging slots. Processes mapping the same files on one node share the
immutable pages; no-AllGather still performs a complete-block H2D copy in each
process.
For a DiT using `allgather` with a group larger than one, each process copies
only its persistent shard and then releases the source mapping. For a DiT using
`rank-local`, each process keeps the mapping open and packs complete blocks
through two bounded pinned staging slots. Processes mapping the same files on
one node share the immutable pages; rank-local transfer still performs a
complete-block H2D copy in each process.
When the effective DLO group size is one, `dlo_use_allgather=True` does not
When the effective DLO group size is one, `weight_transfer="allgather"` does not
perform a collective and uses the same rank-local transfer behavior.
### Final-layout Host Weight Runtime
HWR is an opt-in startup optimization for models that declare the final-layout
BF16 restore contract. Use it only with no-AllGather DLO:
BF16 restore contract. Use it only when the selected DiT uses `rank-local`:
The validated BF16 model contracts currently cover MiniMax H3 and
`black-forest-labs/FLUX.2-klein-4B`. FLUX.2-klein-9B shares the same model
@@ -204,7 +274,7 @@ H2D behavior.
#### Registered direct H2D
Registration is attempted automatically only for an eligible warm HWR hit when
`pin_cpu_memory` is enabled. A successful path is:
the existing `pin_cpu_memory` policy is enabled. A successful path is:
```text
shared read-only HWR mmap -> existing rotating HBM block buffer -> GPU kernel
@@ -217,10 +287,10 @@ kernels access host memory directly.
CUDA requires read-only host-registration capability for the immutable HWR
mapping. Unsupported capability, a positive registration limit smaller than
the complete page-aligned mapping, or a safely rolled-back registration error
falls back to bounded staging. Programmatic configurations can set
`pin_cpu_memory=False` to disable registration explicitly. A successful
registration locks the complete mapped range in host memory for that worker's
lifetime, so use
falls back to bounded staging. Programmatic compatibility configurations can
set `pin_cpu_memory=False` to disable registration explicitly.
A successful registration locks the complete mapped range in host memory for
that worker's lifetime, so use
`--dlo-host-registration-limit-gib` when an operator-enforced ceiling is
required.
@@ -228,8 +298,8 @@ Shutdown drains H2D work, releases hook/source references, unregisters every
range, and only then closes the HWR lease. AllGather and direct checkpoint mmap
retain their existing transfer paths.
When HWR is disabled, DLO is disabled, or AllGather is enabled, the loader does
not resolve HWR sources or construct its store.
When HWR is disabled, DLO is disabled, or the DiT uses `allgather`, the loader
does not resolve HWR sources or construct its store.
## Declarative topology
@@ -241,38 +311,54 @@ from vllm_omni.diffusion.offloader import OffloadPlan
class MyPipeline(nn.Module):
_dit_modules = ["transformer"]
_encoder_modules = ["prompt_model"]
_offload_plan = OffloadPlan(
block_attrs={"transformer": ("blocks",)},
offload_submodules={"context_encoder": "layers"},
encoder_component_types={"prompt_model": "text_encoder"},
encoder_block_attrs={"prompt_model": ("encoder.layers",)},
encoder_dlo_weight_replication=frozenset({"prompt_model"}),
on_demand_component_paths=frozenset({"prompt_model"}),
)
```
When no plan exists, discovery falls back to
`_layerwise_offload_blocks_attrs` and then heuristic attribute lookup.
`encoder_component_types` maps arbitrary encoder paths to the public
`text_encoder` selector. `encoder_dlo_weight_replication` is the explicit
safety declaration for reusing the DiT DLO group. On-demand encoder non-block
state is loaded and offloaded by its pipeline phase.
When no plan exists, DiT discovery falls back to
`_layerwise_offload_blocks_attrs` and then heuristic attribute lookup;
undeclared auxiliary components remain resident.
## Data-parallel concurrency
With `data_parallel_size > 1` and AllGather enabled, the scheduler can process
up to `dp_size` requests per denoising step. Every concurrent request must set
the same explicit `num_inference_steps`; `None` is rejected because every rank
must enter each collective.
up to `dp_size` requests per denoising step. Concurrent requests must resolve
to the same denoise schedule, so every request must provide the same explicit
`num_inference_steps`. Pipeline defaults are rejected because different request
modes can resolve the same `None` value to different schedules.
## Limitations
- Direct checkpoint mmap currently requires TP1. TP greater than one is
outside the Phase A shared-mmap support scope and falls back before model
mutation to the ordinary TP-aware loader. DLO can stream that runtime layout,
and eligible no-AllGather configurations may use HWR final-layout artifacts,
and eligible DiT rank-local configurations may use HWR final-layout artifacts,
but direct checkpoint mmap provides no shared-mmap host-memory guarantee for
TP greater than one.
- HSDP plus AllGather is rejected to avoid double sharding. HSDP without
AllGather has limited end-to-end validation.
- HSDP with `allgather` on any selected component is rejected to avoid double
sharding. HSDP with rank-local transfers has limited end-to-end validation.
- With data parallelism, `allgather` cannot be combined with TeaCache or
Cache-DiT on any selected component. Prompt-embedding cache is also
incompatible when the text encoder uses `allgather`. Rank-local cache hits
could otherwise make ranks enter different weight collectives.
- Per-tensor online FP8 linears use the ordinary loader and can run with either
DLO transfer path. With AllGather, every rank temporarily materializes the
complete FP8 model in host memory before DLO retains only its shard. Other
online quantization methods require no-AllGather until their runtime layouts
are validated.
- Resident leading layers require `--dlo-no-use-allgather` and a model
DiT transfer path. With DiT `allgather`, every rank temporarily materializes
the complete FP8 model in host memory before DLO retains only its shard.
Other online quantization methods require rank-local transfer for the
affected component until their runtime layouts are validated.
- Resident leading layers require DiT `rank-local` transfer and a model
`OffloadPlan` that declares eligible `resident_dit_paths`.
- DP concurrency requires an explicit, identical inference-step count.
@@ -18,8 +18,9 @@ stream.
| ... | ... | ... | ... |
| last block | Prefetch block 0 | Compute last block | Free last block |
Encoders, VAE modules, and non-block DiT modules such as embeddings and norms
remain device resident.
Selected, plan-declared text-encoder blocks can use the same rank-local
streaming mechanism. Image encoders, VAEs, and non-block DiT modules remain
device resident.
## Usage
@@ -28,15 +29,44 @@ from vllm_omni import Omni
omni = Omni(
model="Wan-AI/Wan2.2-T2V-A14B-Diffusers",
enable_layerwise_offload=True,
diffusion_offload_config={
"mode": "layer",
"components": ["dit"],
},
)
```
```bash
vllm serve Wan-AI/Wan2.2-T2V-A14B-Diffusers \
--omni --enable-layerwise-offload
--omni \
--diffusion-offload-config '{"mode":"layer","components":["dit"]}'
```
## Component selection
Add `dit`, `text_encoder`, or both to the `components` list. Omitting
`layer_options` uses safe rank-local defaults:
- `["dit"]` streams only DiT blocks.
- `["text_encoder"]` streams only declared text-encoder blocks.
- Listing both streams both components.
```bash
# DiT-only layer offload
vllm serve /path/to/model --omni \
--diffusion-offload-config \
'{"mode":"layer","components":["dit"]}'
# Stream a model-declared text encoder while keeping the DiT resident
vllm serve /path/to/model --omni \
--diffusion-offload-config \
'{"mode":"layer","components":["text_encoder"]}'
```
Encoder categories are resolved from `OffloadPlan.encoder_component_types`
first. A name-based fallback is retained for pipelines that predate
`OffloadPlan`.
## Model integration
Transformer classes declare containers of executable blocks:
@@ -53,13 +83,29 @@ class Flux2Transformer2DModel(nn.Module):
]
```
See the [layerwise design](../../../design/feature/offloader/layerwise_offload.md) for
the discovery and hook invariants. `OffloadPlan` is a separate declarative
topology path for distributed layerwise offload.
Auxiliary components use declarative pipeline metadata:
```python
from vllm_omni.diffusion.offloader import OffloadPlan
class MyPipeline(nn.Module):
_encoder_modules = ["prompt_model"]
_offload_plan = OffloadPlan(
encoder_component_types={"prompt_model": "text_encoder"},
encoder_block_attrs={"prompt_model": ("encoder.layers",)},
)
```
See the [layerwise design](../../../design/feature/offloader/layerwise_offload.md)
for the discovery and hook invariants. Both ordinary and distributed layerwise
offload consume the same `OffloadPlan` metadata.
## Limitations
- Single device only.
- The default weight transfer is `rank-local`. Set a selected component's
`weight_transfer` to `allgather` to shard its host weights across a
compatible multi-device group; backend selection is automatic.
- Setup consolidates and pins block parameters, increasing cold-start time.
- Performance depends on block compute time and host-to-device bandwidth;
lightweight blocks may not hide transfers.
@@ -2,14 +2,14 @@
Model-level, or sequential, offloading keeps only the pipeline component group
currently executing on the accelerator. It is the simplest offload strategy
and is selected with `--enable-cpu-offload`.
and is selected with `diffusion_offload_config.mode="module"`.
## How it works
Pre-forward hooks enforce mutual exclusion between DiT and encoder modules:
- before an encoder runs, the DiT moves to CPU;
- before a DiT runs, encoders and other DiTs move to CPU; and
- before an encoder runs, selected DiTs move to CPU;
- before a DiT runs, selected encoders and other selected DiTs move to CPU; and
- VAE modules remain on the accelerator.
Pinned host memory reduces transfer overhead. Transfers occur at phase
@@ -22,15 +22,28 @@ from vllm_omni import Omni
omni = Omni(
model="Wan-AI/Wan2.2-T2V-A14B-Diffusers",
enable_cpu_offload=True,
diffusion_offload_config={
"mode": "module",
"components": ["dit", "text_encoder"],
},
)
```
```bash
vllm serve Wan-AI/Wan2.2-T2V-A14B-Diffusers \
--omni --enable-cpu-offload
--omni \
--diffusion-offload-config \
'{"mode":"module","components":["dit","text_encoder"]}'
```
List a component to select it for offload. Module mode rejects `layer_options`
such as `weight_transfer` and `resident_layers`. The
`enable_cpu_offload=True` compatibility entry point remains supported. New
integrations should prefer the explicit config; existing model-specific stage
lifecycles do not need to migrate until equivalent component coverage exists.
For example, MiniMax-H3's compatibility lifecycle also stages its VAEs, while
the compact selector intentionally covers only `dit` and `text_encoder`.
## Model integration
Pipelines should implement `SupportsComponentDiscovery`:
@@ -73,9 +86,10 @@ while reusing sequential `.to()` movers.
## Limitations
- Single device only.
- Transfers are rank-local; module mode does not shard host payloads or add a
weight AllGather across ranks.
- Higher cold-start latency.
- Transfers between encoder and denoising phases add latency.
See the [model-level design](../../../design/feature/offloader/cpu_offload.md)
See the [model-level design](../../../design/feature/offloader/module_offload.md)
for lifecycle and extension invariants.
+14 -14
View File
@@ -1,5 +1,7 @@
# Diffusion Advanced Features
<!-- markdownlint-disable MD028 MD060 -->
## Table of Contents
- [Overview](#overview)
@@ -30,7 +32,6 @@ Cache methods trade minimal quality for significant speedup. Quality loss is typ
| **[TeaCache](diffusion/cache_acceleration/teacache.md)** | Adaptive caching using modulated inputs | Quick setup, balanced quality/speed on single GPU |
| **[Cache-DiT](diffusion/cache_acceleration/cache_dit.md)** | Multiple caching techniques: DBCache, TaylorSeer, SCM | Fine-grained control, tunable quality-speed tradeoff |
#### Lossless Acceleration
Parallelism methods distribute computation across GPUs without quality loss (mathematically equivalent to single-GPU).
@@ -72,7 +73,6 @@ Extension methods add specialized capabilities to diffusion models beyond standa
| **[LoRA Inference](diffusion/lora.md)** | Enables inference with Low-Rank Adaptation (LoRA) adapters weights | Reinforcement learning extensions |
| **[Frame Interpolation](diffusion/frame_interpolation.md)** | Inserts intermediate video frames after generation for smoother motion | Video generation pipelines that need higher temporal smoothness |
### Execution Modes
Execution modes control how the diffusion pipeline processes requests and
@@ -116,7 +116,7 @@ The following tables show which models support each feature:
| Model | ⚡TeaCache | ⚡Cache-DiT | 🔀SP (Ulysses & Ring) | 🔀CFG-Parallel | 🔀Tensor-Parallel | 🔀Pipeline-Parallel | 🔀HSDP | 💾CPU Offload (Layerwise) | 💾VAE-Patch-Parallel | 💾Quantization | 🔄Step Execution |
|--------------------------|:---------:|:----------:|:---------------------:|:--------------:|:-----------------:|:-------------------:|:------:|:-------------------------:|:--------------------:|:--------------:|:----------------:|
| **Bagel** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| **Bagel** | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ |
| **Boogu-Image** | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| **FLUX.1-dev** | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ |
| **FLUX.1-schnell** | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ✅ | ❌ |
@@ -147,11 +147,13 @@ The following tables show which models support each feature:
| **Cosmos3** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ✅ | ❌ |
> Notes:
>
> 1. Nextstep_1(T2I) does not support cache acceleration methods such as TeaCache or Cache-DiT.
> 2. `Tongyi-MAI/Z-Image-Turbo` is a distilled model 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.
> 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).
> 6. BAGEL step execution supports image generation with `bagel.yaml`, `bagel_think.yaml`, and `bagel_single_stage.yaml`; two-stage Thinker execution and explicit single-stage text output remain on their existing complete-request paths. Image requests require `num_inference_steps >= 2`. BAGEL step execution cannot currently be combined with sequence parallelism or a diffusion cache backend; see [Diffusion Execution Modes](diffusion/execution_modes.md#step-execution).
### VideoGen
@@ -160,27 +162,27 @@ The following tables show which models support each feature:
| **Wan2.2** | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ (encode/decode) | ❌ | ❌ |
| **Wan2.2-S2V** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (encode/decode) | ❌ | ❌ |
| **Wan2.1-VACE** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
| **LTX-2** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
| **LTX-2.3** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
| **LTX-2.5** | ❌ | ❓ (one-stage) | ❓ (Ulysses only) | ✅ (Full only) | ❓ | ❌ | ✅ | ✅ | ✅ (decode) | ❓ (FP8) | ❌ |
| **SANA-Video-2B T2V I2V** | ❌ | ❌ | ❌ | ✅ | ✅ (TP=2 only) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| **LTX-2** | ❌ | ✅ | ✅ (Ulysses only) | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
| **LTX-2.3** | ❌ | ✅ | ✅ (Ulysses only) | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (decode) | ❌ | ❌ |
| **LTX-2.5** | ❌ | ❓ (one-stage) | ✅* (Ulysses only) | ✅ (Full only) | ❓ | ❌ | ✅ | ✅ | ✅ (decode) | ❓ (FP8) | ❌ |
| **SANA-Video-2B T2V I2V** | ❌ | ❌ | ✅ (`--usp`, frame-sharded) | ✅ | ✅ (TP=2 only) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| **Helios** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅* |
| **HunyuanVideo-1.5 T2V I2V** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (encode/decode) | ✅ | ❌ |
| **Cosmos3** | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ (encode/decode) | ✅ | ❌ |
| **LongCat-Video-Avatar-1.5** | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| **MiniMax-H3** | ✅ (FL2VA) | ✅ | ✅ | ❌ | ✅ (DiT/TE) | ❌ | ✅ | ✅ | ✅ (tile) | ✅ (DiT) | ❌ |
| **MAGI-2 Preview** | ❌ | ✅ | ✅ (Ulysses) | ✅ (2-way) | ✅ | ❌ | ✅ | ✅ (1-GPU/LW; DLO DP-AG/SP no-AG) | ✅ (tile) | ❌ | ❌ |
| **SANA-WM** | ❌ | ❌ | ❌<sup>5</sup> | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
> Notes:
> 5. SANA-WM cannot support sequence parallelism: its bidirectional gated delta
> recurrence carries state across frames, so a rank cannot denoise a slice of
> the token sequence in isolation. Doing so would need a distributed scan or
> an all-gather before every GDN block. The remaining ❌ columns are simply
> unvalidated on this model, not known-broken.
> recurrence carries state across frames, so a rank cannot denoise a slice of
> the token sequence in isolation. Doing so would need a distributed scan or
> an all-gather before every GDN block. The remaining ❌ columns are simply
> unvalidated on this model, not known-broken.
> **Step execution note:** Helios supports single-request step execution only;
> use `max_num_seqs=1`.
**Frame Interpolation Support**
- **Supported**: Wan2.2 text-to-video, image-to-video, and TI2V pipelines; SANA-Video-2B native text-to-video and image-to-video pipelines
@@ -192,7 +194,6 @@ The following tables show which models support each feature:
|-----------------------|:---------:|:----------:|:---------------------:|:--------------:|:-----------------:|:-------------------:|:------:|:-------------------------:|:--------------------:|:--------------:|:----------------:|
| **Stable-Audio-Open** | ✅ | ❌ | ❓ | ❓ | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ |
## Feature Compatibility
**Legend:**
@@ -231,7 +232,6 @@ The following tables show which models support each feature:
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).
## Multi-Thread Weight Loading
The loading guide now lives at [Diffusion Startup and
@@ -126,11 +126,7 @@ python examples/offline_inference/text_to_speech/cosyvoice3/end2end.py \
## Fish Speech S2 Pro
4B dual-AR text-to-speech model from FishAudio with the DAC codec at 44.1 kHz.
### Prerequisites
```bash
pip install fish-speech
```
No extra packages are required; the DAC codec is vendored in vLLM-Omni.
### Quick start
```bash
@@ -60,6 +60,50 @@ vllm serve ByteDance-Seed/BAGEL-7B-MoT --omni --port 8091 \
See [`bagel_single_stage.yaml`](https://github.com/vllm-project/vllm-omni/tree/main/vllm_omni/deploy/bagel_single_stage.yaml) for configuration. The `pipeline: bagel_single_stage` field selects the single-stage topology from the pipeline registry.
### Step Execution and Continuous Batching
Image generation supports step execution with all three BAGEL deploy configs.
The global flag is applied only to diffusion stages:
- With `bagel.yaml` or `bagel_think.yaml`, Stage 1 uses step execution and the
Stage 0 Thinker remains autoregressive.
- With `bagel_single_stage.yaml`, image requests use step execution in Stage 0.
Explicit `text2text` and `img2text` requests use the same complete-request
text path as they do without `--step-execution`.
Start with the deploy file's default diffusion capacity of one:
```bash
vllm serve ByteDance-Seed/BAGEL-7B-MoT --omni --port 8091 \
--deploy-config vllm_omni/deploy/bagel.yaml \
--step-execution
```
To enable step-wise continuous batching, override `max_num_seqs` on the
diffusion stage. Use Stage 1 for either two-stage config:
```bash
vllm serve ByteDance-Seed/BAGEL-7B-MoT --omni --port 8091 \
--deploy-config vllm_omni/deploy/bagel.yaml \
--step-execution \
--stage-overrides '{"1":{"max_num_seqs":4}}'
```
Use Stage 0 for the single-stage config:
```bash
vllm serve ByteDance-Seed/BAGEL-7B-MoT --omni --port 8091 \
--deploy-config vllm_omni/deploy/bagel_single_stage.yaml \
--step-execution \
--stage-overrides '{"0":{"max_num_seqs":4}}'
```
BAGEL image requests require `num_inference_steps >= 2`: the original BAGEL
schedule uses `num_inference_steps - 1` denoising updates. The scheduler batches
only requests with compatible effective image sizes and BAGEL CFG/renormalization
settings. Sequence parallelism and diffusion cache backends such as TeaCache or
Cache-DiT are not currently supported together with BAGEL step execution.
### Tensor Parallelism (TP)
For larger models or multi-GPU environments, enable TP via CLI:
@@ -203,6 +247,13 @@ curl http://localhost:8091/v1/chat/completions \
-d @payload.json
```
BAGEL img2img derives its generated image size from the input image. It
preserves the input aspect ratio, aligns dimensions to the latent stride, and
applies the checkpoint size limit; explicit `height` and `width` values do not
override this model-specific resize policy. In step mode, the effective size is
resolved before scheduler admission so requests with different resulting
shapes are not combined.
### Image to Text (img2text)
**Python client:**
@@ -267,8 +318,8 @@ curl http://localhost:8091/v1/chat/completions \
| `--server` / `-s` | `http://localhost:8091` | Server URL |
| `--image-url` / `-i` | `None` | Input image URL or local path (img2img/img2text) |
| `--modality` / `-m` | `text2img` | `text2img`, `img2img`, `img2text`, `text2text` |
| `--height` | `512` | Image height in pixels |
| `--width` | `512` | Image width in pixels |
| `--height` | `512` | Text-to-image output height; BAGEL img2img derives its size from the input image |
| `--width` | `512` | Text-to-image output width; BAGEL img2img derives its size from the input image |
| `--steps` | `25` | Number of inference steps |
| `--seed` | `42` | Random seed |
| `--negative` | `None` | Negative prompt for CFG |
@@ -293,6 +344,7 @@ python openai_chat_client.py \
| File | Description |
| :--- | :---------- |
| [`bagel.yaml`](https://github.com/vllm-project/vllm-omni/tree/main/vllm_omni/deploy/bagel.yaml) | Two-stage default (Thinker + DiT on GPU 0) |
| [`bagel_think.yaml`](https://github.com/vllm-project/vllm-omni/tree/main/vllm_omni/deploy/bagel_think.yaml) | Two-stage think mode (Thinker + DiT) |
| [`bagel_single_stage.yaml`](https://github.com/vllm-project/vllm-omni/tree/main/vllm_omni/deploy/bagel_single_stage.yaml) | Single-stage (DiT only) |
### Key Deploy YAML Fields
@@ -16,6 +16,7 @@ implemented with clear Diffusers or vLLM-Omni behavior.
- Sequence parallel execution
- TeaCache / Cache-DiT acceleration
- Step-wise execution (continuous batching)
- Component-selective `diffusion_offload_config` (the legacy whole-pipeline CPU-offload options remain supported)
For these features, it is recommended to use natively supported pipelines instead.
Loaded 100 of 948 files, more files were not shown because too many files have changed in this diff. Show more