From dd0c893e79c9f5c6dbb23141d0665bd1e3c1d457 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 01:46:53 +0000 Subject: [PATCH 01/47] Align dev/vllm-align with upstream vLLM changes - Update Dockerfile.ci to install vLLM from specific commit wheel, add flashinfer/cublas/numpy dependencies - Fix worker_type leak in omni_stage by using pop instead of get Made-with: Cursor Signed-off-by: tzhouam --- docker/Dockerfile.ci | 18 +++++++++++++++--- vllm_omni/entrypoints/omni_stage.py | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index cb80828eb95..01cc002bac0 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -11,10 +11,22 @@ RUN apt-get update && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* -# Install vllm-omni into the same uv-managed Python environment used by the base image. -# Use bash -c so that $(python3 -c ...) is expanded inside the container. -RUN uv pip install --system --no-cache-dir ".[dev]" +# Install vLLM wheel for the specific commit +# URL format: https://wheels.vllm.ai// +# Current vLLM wheel commit: 6521ccf2860eb72cf23a9fa9044bf9721fd9a7c6 +# Note: --prerelease=allow is required because nightly/commit wheels use +# pre-release version numbers (e.g. 2.10.0.dev...) which uv ignores by default, +# causing it to fall back to the older stable release from PyPI. +RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/6521ccf2860eb72cf23a9fa9044bf9721fd9a7c6 +RUN uv pip install --system --no-cache-dir ".[dev]" +RUN uv pip install --system --upgrade \ + "flashinfer-cubin==0.6.4" \ + "nvidia-cublas-cu12==12.9.1.4" \ + "numpy==2.2.6" +RUN uv pip install --system --upgrade \ + "flashinfer-jit-cache==0.6.4" \ + --index-url https://flashinfer.ai/whl/cu129 RUN ln -sf /usr/bin/python3 /usr/bin/python ENTRYPOINT [] diff --git a/vllm_omni/entrypoints/omni_stage.py b/vllm_omni/entrypoints/omni_stage.py index 3fa722b032a..b0f94e426f7 100644 --- a/vllm_omni/entrypoints/omni_stage.py +++ b/vllm_omni/entrypoints/omni_stage.py @@ -210,7 +210,7 @@ def _sequential_init_lock(engine_args: dict[str, Any], stage_init_timeout: int = def _resolve_worker_cls(engine_args: dict[str, Any]) -> None: - worker_type = engine_args.get("worker_type", None) + worker_type = engine_args.pop("worker_type", None) if not worker_type: return worker_cls = engine_args.get("worker_cls") From 3630bdfaa7db1de4d1ff574b1ecf996000f4ebc1 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 01:55:12 +0000 Subject: [PATCH 02/47] Update Dockerfile.ci to install vLLM from a new commit and simplify worker_type retrieval in omni_stage.py. Removed unnecessary dependency installations for flashinfer and numpy. Signed-off-by: tzhouam --- docker/Dockerfile.ci | 12 ++---------- vllm_omni/entrypoints/omni_stage.py | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index 01cc002bac0..51c03cb7eda 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -13,20 +13,12 @@ RUN apt-get update && \ # Install vLLM wheel for the specific commit # URL format: https://wheels.vllm.ai// -# Current vLLM wheel commit: 6521ccf2860eb72cf23a9fa9044bf9721fd9a7c6 +# Current vLLM wheel commit: 097eb544e9a22810c9b7a59e586b61627b308362 # Note: --prerelease=allow is required because nightly/commit wheels use # pre-release version numbers (e.g. 2.10.0.dev...) which uv ignores by default, # causing it to fall back to the older stable release from PyPI. -RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/6521ccf2860eb72cf23a9fa9044bf9721fd9a7c6 +RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/097eb544e9a22810c9b7a59e586b61627b308362 RUN uv pip install --system --no-cache-dir ".[dev]" -RUN uv pip install --system --upgrade \ - "flashinfer-cubin==0.6.4" \ - "nvidia-cublas-cu12==12.9.1.4" \ - "numpy==2.2.6" -RUN uv pip install --system --upgrade \ - "flashinfer-jit-cache==0.6.4" \ - --index-url https://flashinfer.ai/whl/cu129 -RUN ln -sf /usr/bin/python3 /usr/bin/python ENTRYPOINT [] diff --git a/vllm_omni/entrypoints/omni_stage.py b/vllm_omni/entrypoints/omni_stage.py index b0f94e426f7..3fa722b032a 100644 --- a/vllm_omni/entrypoints/omni_stage.py +++ b/vllm_omni/entrypoints/omni_stage.py @@ -210,7 +210,7 @@ def _sequential_init_lock(engine_args: dict[str, Any], stage_init_timeout: int = def _resolve_worker_cls(engine_args: dict[str, Any]) -> None: - worker_type = engine_args.pop("worker_type", None) + worker_type = engine_args.get("worker_type", None) if not worker_type: return worker_cls = engine_args.get("worker_cls") From e150a1b28be3f0e9cf4325820effe41e4377700c Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 06:55:27 +0000 Subject: [PATCH 03/47] debug: empty commit to test CI pipeline (vLLM 156e33553ccd) Signed-off-by: tzhouam From 7706132d8aa2e467f48e874324ec834be539cadb Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 07:08:42 +0000 Subject: [PATCH 04/47] debug: empty commit to test CI pipeline (vLLM 156e33553ccd) Signed-off-by: tzhouam From cfbdb57ec606d889cfd179793971a9c62b32e334 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 07:11:31 +0000 Subject: [PATCH 05/47] debug: empty commit to test CI pipeline (vLLM 156e33553ccd) Signed-off-by: tzhouam From 8ac369c84ac050a7d109aae25bbe61759998a78e Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 07:13:09 +0000 Subject: [PATCH 06/47] debug: empty commit to test CI pipeline (vLLM 156e33553ccd) Signed-off-by: tzhouam From cf0a7a5475dad1e559fa0392a1b5cdcd0312af77 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 08:05:28 +0000 Subject: [PATCH 07/47] Update Dockerfile.ci to install additional dependencies for flashinfer and numpy, and create a symlink for python3. This enhances the CI environment setup. Signed-off-by: tzhouam --- docker/Dockerfile.ci | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index 51c03cb7eda..3b90ac261e2 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -21,4 +21,13 @@ RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm RUN uv pip install --system --no-cache-dir ".[dev]" +RUN uv pip install --system --upgrade \ + "flashinfer-cubin==0.6.4" \ + "nvidia-cublas-cu12==12.9.1.4" \ + "numpy==2.2.6" +RUN uv pip install --system --upgrade \ + "flashinfer-jit-cache==0.6.4" \ + --index-url https://flashinfer.ai/whl/cu129 +RUN ln -sf /usr/bin/python3 /usr/bin/python + ENTRYPOINT [] From 9dc5d15f1d239e53cf4ad5fb8914485dd94ad2aa Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 08:09:53 +0000 Subject: [PATCH 08/47] debug: empty commit to test CI pipeline (vLLM ddbb0d230a35) Signed-off-by: tzhouam From 5c845ce50bf5c09c60c5f6ff55b6df2e01ee6a16 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 08:54:06 +0000 Subject: [PATCH 09/47] debug: empty commit to test CI pipeline (vLLM ddbb0d230a35) Signed-off-by: tzhouam From 4014e7679588602ff6d03701e6a02841ecb17316 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 08:54:54 +0000 Subject: [PATCH 10/47] debug: empty commit to test CI pipeline (vLLM ddbb0d230a35) Signed-off-by: tzhouam From dd0ec557b1f5bfc2a2cdb12dffbd801aa0120b4a Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 08:55:56 +0000 Subject: [PATCH 11/47] debug: empty commit to test CI pipeline (vLLM ddbb0d230a35) Signed-off-by: tzhouam From b0cf788d8fcd62cb48148803a9000662977ed5b2 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 08:56:23 +0000 Subject: [PATCH 12/47] debug: empty commit to test CI pipeline (vLLM ddbb0d230a35) Signed-off-by: tzhouam From cee2b4bd117b6521e8f25997b34e4eb834568b29 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 08:58:06 +0000 Subject: [PATCH 13/47] debug: empty commit to test CI pipeline (vLLM ddbb0d230a35) Signed-off-by: tzhouam From 1c0a71e30d3c7ae556f75ac6121a1a98214d2e9f Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 09:13:24 +0000 Subject: [PATCH 14/47] Refactor Dockerfile.ci to consolidate package installation commands into a single RUN statement, improving readability and efficiency of the CI environment setup. Signed-off-by: tzhouam --- docker/Dockerfile.ci | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index 3b90ac261e2..e39a9bab47f 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -17,17 +17,15 @@ RUN apt-get update && \ # Note: --prerelease=allow is required because nightly/commit wheels use # pre-release version numbers (e.g. 2.10.0.dev...) which uv ignores by default, # causing it to fall back to the older stable release from PyPI. -RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/097eb544e9a22810c9b7a59e586b61627b308362 - -RUN uv pip install --system --no-cache-dir ".[dev]" - -RUN uv pip install --system --upgrade \ - "flashinfer-cubin==0.6.4" \ - "nvidia-cublas-cu12==12.9.1.4" \ - "numpy==2.2.6" -RUN uv pip install --system --upgrade \ - "flashinfer-jit-cache==0.6.4" \ - --index-url https://flashinfer.ai/whl/cu129 -RUN ln -sf /usr/bin/python3 /usr/bin/python +RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/097eb544e9a22810c9b7a59e586b61627b308362 && \ + uv pip install --system --no-cache-dir ".[dev]" && \ + uv pip install --system --upgrade \ + "flashinfer-cubin==0.6.4" \ + "nvidia-cublas-cu12==12.9.1.4" \ + "numpy==2.2.6" && \ + uv pip install --system --upgrade \ + "flashinfer-jit-cache==0.6.4" \ + --index-url https://flashinfer.ai/whl/cu129 && \ + ln -sf /usr/bin/python3 /usr/bin/python ENTRYPOINT [] From a9862f17f3433450894152cb7bee7f7fe1807bc6 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 09:41:45 +0000 Subject: [PATCH 15/47] Revert "Refactor Dockerfile.ci to consolidate package installation commands into a single RUN statement, improving readability and efficiency of the CI environment setup." This reverts commit 1c0a71e30d3c7ae556f75ac6121a1a98214d2e9f. Signed-off-by: tzhouam --- docker/Dockerfile.ci | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index e39a9bab47f..3b90ac261e2 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -17,15 +17,17 @@ RUN apt-get update && \ # Note: --prerelease=allow is required because nightly/commit wheels use # pre-release version numbers (e.g. 2.10.0.dev...) which uv ignores by default, # causing it to fall back to the older stable release from PyPI. -RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/097eb544e9a22810c9b7a59e586b61627b308362 && \ - uv pip install --system --no-cache-dir ".[dev]" && \ - uv pip install --system --upgrade \ - "flashinfer-cubin==0.6.4" \ - "nvidia-cublas-cu12==12.9.1.4" \ - "numpy==2.2.6" && \ - uv pip install --system --upgrade \ - "flashinfer-jit-cache==0.6.4" \ - --index-url https://flashinfer.ai/whl/cu129 && \ - ln -sf /usr/bin/python3 /usr/bin/python +RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/097eb544e9a22810c9b7a59e586b61627b308362 + +RUN uv pip install --system --no-cache-dir ".[dev]" + +RUN uv pip install --system --upgrade \ + "flashinfer-cubin==0.6.4" \ + "nvidia-cublas-cu12==12.9.1.4" \ + "numpy==2.2.6" +RUN uv pip install --system --upgrade \ + "flashinfer-jit-cache==0.6.4" \ + --index-url https://flashinfer.ai/whl/cu129 +RUN ln -sf /usr/bin/python3 /usr/bin/python ENTRYPOINT [] From 20c102d3cfea41927ec80f3e75175b6c5587f91c Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 10 Mar 2026 10:04:29 +0000 Subject: [PATCH 16/47] debug: empty commit to test CI pipeline (vLLM ddbb0d230a35) Signed-off-by: tzhouam From 08a2673c6650a8e573da020938c93555b418c31f Mon Sep 17 00:00:00 2001 From: tzhouam Date: Wed, 11 Mar 2026 07:29:52 +0000 Subject: [PATCH 17/47] rebase: align vllm-omni with vLLM 84e436ed1c94 Signed-off-by: tzhouam --- docker/Dockerfile.ci | 2 +- .../qwen2_5_omni/test_qwen2_5_omni_embed.py | 4 +-- .../core/sched/omni_generation_scheduler.py | 14 +++++++--- vllm_omni/engine/input_processor.py | 3 ++- vllm_omni/entrypoints/openai/api_server.py | 18 +++---------- .../models/hunyuan_image3/hunyuan_image3.py | 1 - .../models/mimo_audio/mimo_audio_llm.py | 2 -- .../qwen2_5_omni/qwen2_5_omni_talker.py | 2 -- .../qwen2_5_omni/qwen2_5_omni_thinker.py | 12 ++++++--- .../qwen3_omni/qwen3_omni_moe_thinker.py | 3 --- vllm_omni/worker/gpu_ar_model_runner.py | 19 +++++++------- vllm_omni/worker/gpu_ar_worker.py | 2 +- .../worker/gpu_generation_model_runner.py | 26 ++++++++++++------- vllm_omni/worker/gpu_generation_worker.py | 2 +- vllm_omni/worker/gpu_model_runner.py | 21 ++++++++++++--- 15 files changed, 74 insertions(+), 57 deletions(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index 3b90ac261e2..117aba15319 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -17,7 +17,7 @@ RUN apt-get update && \ # Note: --prerelease=allow is required because nightly/commit wheels use # pre-release version numbers (e.g. 2.10.0.dev...) which uv ignores by default, # causing it to fall back to the older stable release from PyPI. -RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/097eb544e9a22810c9b7a59e586b61627b308362 +RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/84e436ed1c94b1b94f809927b5d6bff45f7af919 RUN uv pip install --system --no-cache-dir ".[dev]" diff --git a/tests/model_executor/models/qwen2_5_omni/test_qwen2_5_omni_embed.py b/tests/model_executor/models/qwen2_5_omni/test_qwen2_5_omni_embed.py index 84112336e4b..8e04b04966b 100644 --- a/tests/model_executor/models/qwen2_5_omni/test_qwen2_5_omni_embed.py +++ b/tests/model_executor/models/qwen2_5_omni/test_qwen2_5_omni_embed.py @@ -123,6 +123,7 @@ def make_mock_model(hidden: int = 8): cfg.video_token_index = VIDEO_TOKEN_ID cfg.audio_token_index = AUDIO_TOKEN_ID model.config = cfg + model._has_oov_mm_tokens = False def fake_lm_embed(ids: torch.Tensor) -> torch.Tensor: # Use .clone() so the tensor is contiguous (expand() creates a strided @@ -137,13 +138,12 @@ def fake_lm_embed(ids: torch.Tensor) -> torch.Tensor: model._embed_text_input_ids = lambda *a, **kw: SupportsMultiModal._embed_text_input_ids(model, *a, **kw) - def fake_super_embed(ids, mm_embs=None, *, is_multimodal=None, handle_oov_mm_token=False): + def fake_super_embed(ids, mm_embs=None, *, is_multimodal=None): return SupportsMultiModal.embed_input_ids( model, ids, mm_embs, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) model.embed_input_ids = lambda *a, **kw: Qwen2_5OmniThinkerForConditionalGeneration.embed_input_ids(model, *a, **kw) diff --git a/vllm_omni/core/sched/omni_generation_scheduler.py b/vllm_omni/core/sched/omni_generation_scheduler.py index a397e608518..b2e64224b10 100644 --- a/vllm_omni/core/sched/omni_generation_scheduler.py +++ b/vllm_omni/core/sched/omni_generation_scheduler.py @@ -247,6 +247,15 @@ def schedule(self) -> SchedulerOutput: ) total_num_scheduled_tokens = sum(num_scheduled_tokens.values()) + + # Record the request ids scheduled in this step (v0.14.0 behavior). + self.prev_step_scheduled_req_ids.clear() + self.prev_step_scheduled_req_ids.update(num_scheduled_tokens.keys()) + + new_block_ids_to_zero = ( + (self.kv_cache_manager.take_new_block_ids() or None) if self.needs_kv_cache_zeroing else None + ) + scheduler_output = SchedulerOutput( scheduled_new_reqs=new_reqs_data, scheduled_cached_reqs=cached_reqs_data, @@ -258,12 +267,9 @@ def schedule(self) -> SchedulerOutput: finished_req_ids=self.finished_req_ids, free_encoder_mm_hashes=self.encoder_cache_manager.get_freed_mm_hashes(), preempted_req_ids=set(), + new_block_ids_to_zero=new_block_ids_to_zero, ) - # Record the request ids scheduled in this step (v0.14.0 behavior). - self.prev_step_scheduled_req_ids.clear() - self.prev_step_scheduled_req_ids.update(num_scheduled_tokens.keys()) - # KVTransfer: package metadata if self.connector is not None: meta = self.connector.build_connector_meta(scheduler_output) diff --git a/vllm_omni/engine/input_processor.py b/vllm_omni/engine/input_processor.py index 5bbd16b38d3..d785db2fd8a 100644 --- a/vllm_omni/engine/input_processor.py +++ b/vllm_omni/engine/input_processor.py @@ -11,6 +11,7 @@ from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalRegistry from vllm.multimodal.inputs import MultiModalFeatureSpec from vllm.multimodal.utils import argsort_mm_positions +from vllm.platforms import current_platform from vllm.pooling_params import PoolingParams from vllm.renderers import BaseRenderer from vllm.sampling_params import SamplingParams @@ -184,7 +185,7 @@ def process_inputs( tokenization_kwargs=tokenization_kwargs, ) - self._platform_validate_request(processed_inputs, params) + current_platform.validate_request(processed_inputs, params) encoder_inputs, decoder_inputs = split_enc_dec_inputs(processed_inputs) self._validate_model_inputs(encoder_inputs, decoder_inputs) diff --git a/vllm_omni/entrypoints/openai/api_server.py b/vllm_omni/entrypoints/openai/api_server.py index 6db97dd2ddb..b6298c03730 100644 --- a/vllm_omni/entrypoints/openai/api_server.py +++ b/vllm_omni/entrypoints/openai/api_server.py @@ -67,7 +67,7 @@ ) from vllm.entrypoints.openai.utils import validate_json_request from vllm.entrypoints.pooling.classify.serving import ServingClassification -from vllm.entrypoints.pooling.embed.serving import OpenAIServingEmbedding +from vllm.entrypoints.pooling.embed.serving import ServingEmbedding from vllm.entrypoints.pooling.pooling.serving import OpenAIServingPooling from vllm.entrypoints.pooling.score.serving import ServingScores from vllm.entrypoints.serve.disagg.serving import ServingTokens @@ -570,7 +570,6 @@ async def omni_init_app_state( enable_prompt_tokens_details=args.enable_prompt_tokens_details, enable_force_include_usage=args.enable_force_include_usage, enable_log_outputs=args.enable_log_outputs, - log_error_stack=args.log_error_stack, ) if "generate" in supported_tasks else None @@ -594,14 +593,13 @@ async def omni_init_app_state( enable_force_include_usage=args.enable_force_include_usage, enable_log_outputs=args.enable_log_outputs, enable_log_deltas=args.enable_log_deltas, - log_error_stack=args.log_error_stack, ) if "generate" in supported_tasks else None ) # Warm up chat template processing to avoid first-request latency if state.openai_serving_chat is not None: - await state.openai_serving_chat.warmup() + state.openai_serving_chat.warmup() state.openai_serving_completion = ( OpenAIServingCompletion( @@ -611,7 +609,6 @@ async def omni_init_app_state( return_tokens_as_token_ids=args.return_tokens_as_token_ids, enable_prompt_tokens_details=args.enable_prompt_tokens_details, enable_force_include_usage=args.enable_force_include_usage, - log_error_stack=args.log_error_stack, ) if "generate" in supported_tasks else None @@ -625,20 +622,18 @@ async def omni_init_app_state( chat_template=resolved_chat_template, chat_template_content_format=args.chat_template_content_format, trust_request_chat_template=args.trust_request_chat_template, - log_error_stack=args.log_error_stack, ) if any(task in POOLING_TASKS for task in supported_tasks) else None ) state.openai_serving_embedding = ( - OpenAIServingEmbedding( + ServingEmbedding( engine_client, state.openai_serving_models, request_logger=request_logger, chat_template=resolved_chat_template, chat_template_content_format=args.chat_template_content_format, trust_request_chat_template=args.trust_request_chat_template, - log_error_stack=args.log_error_stack, ) if "embed" in supported_tasks else None @@ -651,7 +646,6 @@ async def omni_init_app_state( chat_template=resolved_chat_template, chat_template_content_format=args.chat_template_content_format, trust_request_chat_template=args.trust_request_chat_template, - log_error_stack=args.log_error_stack, ) if "classify" in supported_tasks else None @@ -664,7 +658,7 @@ async def omni_init_app_state( score_template=resolved_chat_template, log_error_stack=args.log_error_stack, ) - if ("embed" in supported_tasks or "score" in supported_tasks) + if any(t in supported_tasks for t in ("embed", "score", "token_embed")) else None ) state.openai_serving_tokenization = OpenAIServingTokenization( @@ -674,14 +668,12 @@ async def omni_init_app_state( chat_template=resolved_chat_template, chat_template_content_format=args.chat_template_content_format, trust_request_chat_template=args.trust_request_chat_template, - log_error_stack=args.log_error_stack, ) state.openai_serving_transcription = ( OpenAIServingTranscription( engine_client, state.openai_serving_models, request_logger=request_logger, - log_error_stack=args.log_error_stack, enable_force_include_usage=args.enable_force_include_usage, ) if "transcription" in supported_tasks @@ -692,7 +684,6 @@ async def omni_init_app_state( engine_client, state.openai_serving_models, request_logger=request_logger, - log_error_stack=args.log_error_stack, enable_force_include_usage=args.enable_force_include_usage, ) if "transcription" in supported_tasks @@ -722,7 +713,6 @@ async def omni_init_app_state( state.openai_serving_models, request_logger=request_logger, return_tokens_as_token_ids=args.return_tokens_as_token_ids, - log_error_stack=args.log_error_stack, enable_prompt_tokens_details=args.enable_prompt_tokens_details, enable_log_outputs=args.enable_log_outputs, force_no_detokenize=args.tokens_only, diff --git a/vllm_omni/model_executor/models/hunyuan_image3/hunyuan_image3.py b/vllm_omni/model_executor/models/hunyuan_image3/hunyuan_image3.py index b015679c2e6..b886aec696e 100644 --- a/vllm_omni/model_executor/models/hunyuan_image3/hunyuan_image3.py +++ b/vllm_omni/model_executor/models/hunyuan_image3/hunyuan_image3.py @@ -1328,7 +1328,6 @@ def embed_input_ids( multimodal_embeddings: MultiModalEmbeddings | None = None, *, is_multimodal: torch.Tensor | None = None, - handle_oov_mm_token: bool = False, ) -> torch.Tensor: """Embed input IDs with optional multimodal embeddings.""" # Get text embeddings diff --git a/vllm_omni/model_executor/models/mimo_audio/mimo_audio_llm.py b/vllm_omni/model_executor/models/mimo_audio/mimo_audio_llm.py index c1b71e076e4..1424ca7756b 100644 --- a/vllm_omni/model_executor/models/mimo_audio/mimo_audio_llm.py +++ b/vllm_omni/model_executor/models/mimo_audio/mimo_audio_llm.py @@ -773,7 +773,6 @@ def embed_input_ids( multimodal_embeddings: MultiModalEmbeddings | None = None, *, is_multimodal: torch.Tensor | None = None, - handle_oov_mm_token: bool = False, ) -> torch.Tensor: # This is to satisfy the type checker for each overload if multimodal_embeddings is None or is_multimodal is None: @@ -783,7 +782,6 @@ def embed_input_ids( input_ids, multimodal_embeddings=multimodal_embeddings, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) def base_local_forward( diff --git a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_talker.py b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_talker.py index 988d8c23673..8c348a9c663 100644 --- a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_talker.py +++ b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_talker.py @@ -109,7 +109,6 @@ def embed_input_ids( multimodal_embeddings: MultiModalEmbeddings | None = None, *, is_multimodal: torch.Tensor | None = None, - handle_oov_mm_token: bool = False, ) -> torch.Tensor: # This is to satisfy the type checker for each overload if multimodal_embeddings is None or is_multimodal is None: @@ -119,7 +118,6 @@ def embed_input_ids( input_ids, multimodal_embeddings=multimodal_embeddings, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) def forward( diff --git a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py index 64eaefaa28a..c32b121c290 100644 --- a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py +++ b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py @@ -586,11 +586,19 @@ def embed_input_ids( multimodal_embeddings: MultiModalEmbeddings | None = None, *, is_multimodal: torch.Tensor | None = None, - handle_oov_mm_token: bool = False, ) -> torch.Tensor: if multimodal_embeddings is None or is_multimodal is None: return super().embed_input_ids(input_ids) + inputs_embeds = self._embed_text_input_ids( + input_ids, + self.get_language_model().embed_input_ids, + is_multimodal=is_multimodal, + ) + + if len(multimodal_embeddings) == 0: + return inputs_embeds + # Check for audio-in-video: interleaved video and audio tokens # in the multimodal region. Only use the interleaved path when # needed; otherwise fall back to the default parent implementation. @@ -608,7 +616,6 @@ def embed_input_ids( input_ids, self.get_language_model().embed_input_ids, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) return merge_interleaved_embeddings( inputs_embeds, @@ -625,7 +632,6 @@ def embed_input_ids( input_ids, multimodal_embeddings=multimodal_embeddings, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) def forward( diff --git a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py index 7dfb988eb88..852d5572b1c 100644 --- a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py +++ b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py @@ -966,13 +966,11 @@ def embed_input_ids( multimodal_embeddings: MultiModalEmbeddings | None = None, *, is_multimodal: torch.Tensor | None = None, - handle_oov_mm_token: bool = False, ) -> torch.Tensor: inputs_embeds = self._embed_text_input_ids( input_ids, self.language_model.embed_input_ids, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) if multimodal_embeddings is None or len(multimodal_embeddings) == 0: @@ -1064,7 +1062,6 @@ def embed_input_ids( input_ids, multimodal_embeddings=multimodal_embeddings, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) def forward( diff --git a/vllm_omni/worker/gpu_ar_model_runner.py b/vllm_omni/worker/gpu_ar_model_runner.py index d7d45031af4..9dce9778dfc 100644 --- a/vllm_omni/worker/gpu_ar_model_runner.py +++ b/vllm_omni/worker/gpu_ar_model_runner.py @@ -141,7 +141,7 @@ def execute_model( # Update persistent batch states. self._update_states(scheduler_output) - if has_ec_transfer() and get_ec_transfer().is_producer: + if has_ec_transfer() and not get_ec_transfer().is_consumer: with self.maybe_get_ec_connector_output( scheduler_output, encoder_cache=self.encoder_cache, @@ -276,9 +276,9 @@ def execute_model( # Run the model. # Use persistent buffers for CUDA graphs. - # When spec decode is enabled, delay clearing connector metadata - # until after draft model runs in sample_tokens. - clear_kv_metadata = self.speculative_config is None + # When spec decode is enabled, defer connector finalization + # (wait_for_save + clear metadata) until after draft model runs. + defer_kv_connector_finalize = self.speculative_config is not None with ( set_forward_context( attn_metadata, @@ -292,7 +292,8 @@ def execute_model( ), record_function_or_nullcontext("gpu_model_runner: forward"), self.maybe_get_kv_connector_output( - scheduler_output, clear_metadata=clear_kv_metadata + scheduler_output, + defer_finalize=defer_kv_connector_finalize, ) as kv_connector_output, ): model_output = self._model_forward( @@ -533,11 +534,11 @@ def propose_draft_token_ids(sampled_token_ids): # tokens on the CPU, so they are run after bookkeeping. propose_draft_token_ids(valid_sampled_token_ids) - # Clear KV connector metadata after draft model runs (if spec decode). - # This was deferred from target model forward to allow draft model - # to also save its KV cache. + # Finalize KV connector (wait_for_save + clear metadata) after + # draft model runs. Deferred from target model forward to allow + # draft model to also save its KV cache. if self.speculative_config is not None: - self.clear_kv_connector_metadata() + self.finalize_kv_connector() with record_function_or_nullcontext("gpu_model_runner: eplb"): self.eplb_step() diff --git a/vllm_omni/worker/gpu_ar_worker.py b/vllm_omni/worker/gpu_ar_worker.py index 198d42c8a31..8734524a221 100644 --- a/vllm_omni/worker/gpu_ar_worker.py +++ b/vllm_omni/worker/gpu_ar_worker.py @@ -79,7 +79,7 @@ def init_device(self): # Now take memory snapshot after NCCL is initialized gc.collect() - torch.cuda.empty_cache() + torch.accelerator.empty_cache() # take current memory snapshot self.init_snapshot = init_snapshot = MemorySnapshot(device=self.device) diff --git a/vllm_omni/worker/gpu_generation_model_runner.py b/vllm_omni/worker/gpu_generation_model_runner.py index 05785d7aa6c..0dd304ed23f 100644 --- a/vllm_omni/worker/gpu_generation_model_runner.py +++ b/vllm_omni/worker/gpu_generation_model_runner.py @@ -108,7 +108,7 @@ def execute_model( if not scheduler_output.total_num_scheduled_tokens: return EMPTY_MODEL_RUNNER_OUTPUT - if has_ec_transfer() and get_ec_transfer().is_producer: + if has_ec_transfer() and not get_ec_transfer().is_consumer: with self.maybe_get_ec_connector_output( scheduler_output, encoder_cache=self.encoder_cache, @@ -265,9 +265,9 @@ def execute_model( # Run the model. # Use persistent buffers for CUDA graphs. - # When spec decode is enabled, delay clearing connector metadata - # until after draft model runs in sample_tokens. - clear_kv_metadata = self.speculative_config is None + # When spec decode is enabled, defer connector finalization + # (wait_for_save + clear metadata) until after draft model runs. + defer_kv_connector_finalize = self.speculative_config is not None with ( set_forward_context( attn_metadata, @@ -281,7 +281,8 @@ def execute_model( ), record_function_or_nullcontext("Forward"), self.maybe_get_kv_connector_output( - scheduler_output, clear_metadata=clear_kv_metadata + scheduler_output, + defer_finalize=defer_kv_connector_finalize, ) as kv_connector_output, ): outputs = self._run_generation_model( @@ -351,9 +352,10 @@ def sample_tokens( ) = self.execute_model_state self.execute_model_state = None - # Clear KV connector metadata after draft model runs (if spec decode). + # Finalize KV connector (wait_for_save + clear metadata) after + # draft model runs. Deferred from target model forward. if self.speculative_config is not None: - self.clear_kv_connector_metadata() + self.finalize_kv_connector() pooler_output: list[object] = [] if isinstance(multimodal_outputs, torch.Tensor): @@ -476,6 +478,7 @@ def _dummy_run( remove_lora: bool = True, is_graph_capturing: bool = False, num_active_loras: int = 0, + profile_seq_lens: int | None = None, ) -> tuple[torch.Tensor, torch.Tensor]: """ Run a dummy forward pass to warm up/profile run or capture the @@ -500,6 +503,9 @@ def _dummy_run( remove_lora: If False, dummy LoRAs are not destroyed after the run num_active_loras: Number of distinct active LoRAs to capture for. LoRA is activated when num_active_loras > 0. + profile_seq_lens: If provided, use this value for seq_lens instead + of max_query_len. Used to profile attention workspace that + scales with context length. """ mm_config = self.vllm_config.model_config.multimodal_config if mm_config and mm_config.mm_encoder_only: @@ -621,11 +627,13 @@ def _dummy_run( # If force_attention is True, we always capture attention. # Otherwise, it only happens for cudagraph_runtime_mode=FULL. if force_attention or cudagraph_runtime_mode == CUDAGraphMode.FULL: - if create_mixed_batch: + if profile_seq_lens is not None: + seq_lens = profile_seq_lens # type: ignore[assignment] + elif create_mixed_batch: # In the mixed batch mode (used for FI warmup), we use # shorter sequence lengths to run faster. # TODO(luka) better system for describing dummy batches - seq_lens = [1] * num_decode_tokens + [num_prefill_tokens + 1] + seq_lens = [1] * num_decode_tokens + [num_prefill_tokens + 1] # type: ignore[assignment] else: seq_lens = max_query_len # type: ignore[assignment] self.seq_lens.np[:num_reqs] = seq_lens diff --git a/vllm_omni/worker/gpu_generation_worker.py b/vllm_omni/worker/gpu_generation_worker.py index 7de9a79227b..341797ebb67 100644 --- a/vllm_omni/worker/gpu_generation_worker.py +++ b/vllm_omni/worker/gpu_generation_worker.py @@ -79,7 +79,7 @@ def init_device(self): # Now take memory snapshot after NCCL is initialized gc.collect() - torch.cuda.empty_cache() + torch.accelerator.empty_cache() # take current memory snapshot self.init_snapshot = init_snapshot = MemorySnapshot(device=self.device) diff --git a/vllm_omni/worker/gpu_model_runner.py b/vllm_omni/worker/gpu_model_runner.py index e17d52bdd53..5a3d2f9ca35 100644 --- a/vllm_omni/worker/gpu_model_runner.py +++ b/vllm_omni/worker/gpu_model_runner.py @@ -238,6 +238,7 @@ def _update_states(self, scheduler_output: "SchedulerOutput") -> None: self.requests.pop(req_id, None) self.model_intermediate_buffer.pop(req_id, None) self.num_prompt_logprobs.pop(req_id, None) + self.late_interaction_runner.on_requests_finished(scheduler_output.finished_req_ids) # Remove the finished requests from the persistent batch. # NOTE(woosuk): There could be an edge case where finished_req_ids and # scheduled_req_ids overlap. This happens when a request is aborted and @@ -247,6 +248,11 @@ def _update_states(self, scheduler_output: "SchedulerOutput") -> None: for req_id in scheduler_output.finished_req_ids: self.input_batch.remove_request(req_id) + # Zero GPU memory for freshly allocated cache blocks to prevent + # stale NaN/data from corrupting attention or SSM computation. + if scheduler_output.new_block_ids_to_zero: + self._zero_block_ids(scheduler_output.new_block_ids_to_zero) + # Free the cached encoder outputs. for mm_hash in scheduler_output.free_encoder_mm_hashes: self.encoder_cache.pop(mm_hash, None) @@ -314,6 +320,7 @@ def _update_states(self, scheduler_output: "SchedulerOutput") -> None: lora_request=new_req_data.lora_request, ) self.requests[req_id] = req_state + self.late_interaction_runner.register_request(req_id, pooling_params) # If prompt embeddings are provided, decode and attach to inter_data try: @@ -399,13 +406,13 @@ def _update_states(self, scheduler_output: "SchedulerOutput") -> None: # prev_num_draft_len is used in async scheduling mode with # spec decode. it indicates if need to update num_computed_tokens # of the request. for example: - # fist step: num_computed_tokens = 0, spec_tokens = [], + # first step: num_computed_tokens = 0, spec_tokens = [], # prev_num_draft_len = 0. # second step: num_computed_tokens = 100(prompt length), # spec_tokens = [a,b], prev_num_draft_len = 0. # third step: num_computed_tokens = 100 + 2, spec_tokens = [c,d], # prev_num_draft_len = 2. - # num_computed_tokens in first step and second step does't contain + # num_computed_tokens in first step and second step doesn't contain # the spec tokens length, but in third step it contains the # spec tokens length. we only need to update num_computed_tokens # when prev_num_draft_len > 0. @@ -532,6 +539,7 @@ def _dummy_run( remove_lora: bool = True, is_graph_capturing: bool = False, num_active_loras: int = 0, + profile_seq_lens: int | None = None, ) -> tuple[torch.Tensor, torch.Tensor]: """ Run a dummy forward pass to warm up/profile run or capture the @@ -556,6 +564,9 @@ def _dummy_run( remove_lora: If False, dummy LoRAs are not destroyed after the run num_active_loras: Number of distinct active LoRAs to capture for. LoRA is activated when num_active_loras > 0. + profile_seq_lens: If provided, use this value for seq_lens instead + of max_query_len. Used to profile attention workspace that + scales with context length. """ mm_config = self.vllm_config.model_config.multimodal_config if mm_config and mm_config.mm_encoder_only: @@ -676,11 +687,13 @@ def _dummy_run( # If force_attention is True, we always capture attention. # Otherwise, it only happens for cudagraph_runtime_mode=FULL. if force_attention or cudagraph_runtime_mode == CUDAGraphMode.FULL: - if create_mixed_batch: + if profile_seq_lens is not None: + seq_lens = profile_seq_lens # type: ignore[assignment] + elif create_mixed_batch: # In the mixed batch mode (used for FI warmup), we use # shorter sequence lengths to run faster. # TODO(luka) better system for describing dummy batches - seq_lens = [1] * num_decode_tokens + [num_prefill_tokens + 1] + seq_lens = [1] * num_decode_tokens + [num_prefill_tokens + 1] # type: ignore[assignment] else: seq_lens = max_query_len # type: ignore[assignment] self.seq_lens.np[:num_reqs] = seq_lens From 11b6c160078bca5a4d38e39d3383ce173b547d63 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Wed, 11 Mar 2026 19:24:44 +0000 Subject: [PATCH 18/47] rebase: align vllm-omni with vLLM 84e436ed1c94 Signed-off-by: tzhouam --- .../model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py index 852d5572b1c..a0baefca769 100644 --- a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py +++ b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py @@ -942,7 +942,7 @@ def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings | None: return [] # The result multimodal_embeddings is tuple of tensors, with each - # tensor correspoending to a multimodal data item (image or video). + # tensor corresponding to a multimodal data item (image or video). multimodal_embeddings: tuple[torch.Tensor, ...] = () # NOTE: It is important to iterate over the keys in this dictionary From 72cb4f661b6ba138c584856202cea6ef170ff02c Mon Sep 17 00:00:00 2001 From: tzhouam Date: Thu, 12 Mar 2026 13:57:52 +0000 Subject: [PATCH 19/47] Adjust GPU memory utilization and device settings in qwen2_5_omni_ci.yaml for improved performance Signed-off-by: tzhouam --- tests/e2e/stage_configs/qwen2_5_omni_ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/stage_configs/qwen2_5_omni_ci.yaml b/tests/e2e/stage_configs/qwen2_5_omni_ci.yaml index c11de3e5d66..188f219ec59 100644 --- a/tests/e2e/stage_configs/qwen2_5_omni_ci.yaml +++ b/tests/e2e/stage_configs/qwen2_5_omni_ci.yaml @@ -47,7 +47,7 @@ stage_args: max_model_len: 16384 max_num_batched_tokens: 16384 max_num_seqs: 1 - gpu_memory_utilization: 0.9 + gpu_memory_utilization: 0.4 skip_mm_profiling: true enforce_eager: true trust_remote_code: true @@ -67,14 +67,14 @@ stage_args: - stage_id: 2 runtime: process: true - devices: "2" # Example: use a different GPU than the previous stage; use "0" if single GPU + devices: "1" # Example: use a different GPU than the previous stage; use "0" if single GPU max_batch_size: 1 engine_args: model_stage: code2wav model_arch: Qwen2_5OmniForConditionalGeneration worker_type: generation scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler - gpu_memory_utilization: 0.9 #increase the gpu memory utilization to enable the test on H800 + gpu_memory_utilization: 0.5 #increase the gpu memory utilization to enable the test on H800 enforce_eager: true trust_remote_code: true enable_prefix_caching: false From e17fb0e97e6924948af5722349e74773b51c2c35 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Fri, 13 Mar 2026 01:55:17 +0000 Subject: [PATCH 20/47] Refactor OpenAI serving components and enhance multimodal handling - Updated import for OpenAIServingEmbedding in api_server.py. - Enhanced omni_init_app_state to initialize renderer for engine_client. - Added handle_oov_mm_token parameter to multiple model classes for better multimodal token handling. - Improved comments for clarity in various model files. - Adjusted GPUARModelRunner to ensure proper handling of late interaction runner attributes. Signed-off-by: [Your Name] <[Your Email]> Signed-off-by: tzhouam --- vllm_omni/entrypoints/openai/api_server.py | 12 +++++++++--- .../models/mimo_audio/mimo_audio_llm.py | 2 ++ .../qwen2_5_omni/qwen2_5_omni_talker.py | 4 +++- .../qwen2_5_omni/qwen2_5_omni_thinker.py | 18 ++++++++++++++++++ .../qwen3_omni/qwen3_omni_moe_talker.py | 2 +- .../qwen3_omni/qwen3_omni_moe_thinker.py | 3 +++ vllm_omni/worker/gpu_ar_model_runner.py | 19 +++++++++---------- vllm_omni/worker/gpu_model_runner.py | 8 +++++--- 8 files changed, 50 insertions(+), 18 deletions(-) diff --git a/vllm_omni/entrypoints/openai/api_server.py b/vllm_omni/entrypoints/openai/api_server.py index b6298c03730..6404e56d9aa 100644 --- a/vllm_omni/entrypoints/openai/api_server.py +++ b/vllm_omni/entrypoints/openai/api_server.py @@ -67,7 +67,7 @@ ) from vllm.entrypoints.openai.utils import validate_json_request from vllm.entrypoints.pooling.classify.serving import ServingClassification -from vllm.entrypoints.pooling.embed.serving import ServingEmbedding +from vllm.entrypoints.pooling.embed.serving import OpenAIServingEmbedding from vllm.entrypoints.pooling.pooling.serving import OpenAIServingPooling from vllm.entrypoints.pooling.score.serving import ServingScores from vllm.entrypoints.serve.disagg.serving import ServingTokens @@ -536,7 +536,13 @@ async def omni_init_app_state( else vllm_config.model_config ) io_processor_plugin = model_config.io_processor_plugin - engine_client.io_processor = get_io_processor(vllm_config, io_processor_plugin) + renderer = getattr(engine_client, "renderer", None) + if renderer is None: + from vllm.renderers import renderer_from_config + + renderer = renderer_from_config(vllm_config) + engine_client.renderer = renderer + engine_client.io_processor = get_io_processor(vllm_config, renderer, io_processor_plugin) logger.info("Initialized io_processor for AsyncOmni") else: logger.warning("Cannot initialize processors: tokenizer is None. OpenAIServingModels may fail.") @@ -627,7 +633,7 @@ async def omni_init_app_state( else None ) state.openai_serving_embedding = ( - ServingEmbedding( + OpenAIServingEmbedding( engine_client, state.openai_serving_models, request_logger=request_logger, diff --git a/vllm_omni/model_executor/models/mimo_audio/mimo_audio_llm.py b/vllm_omni/model_executor/models/mimo_audio/mimo_audio_llm.py index 1424ca7756b..c1b71e076e4 100644 --- a/vllm_omni/model_executor/models/mimo_audio/mimo_audio_llm.py +++ b/vllm_omni/model_executor/models/mimo_audio/mimo_audio_llm.py @@ -773,6 +773,7 @@ def embed_input_ids( multimodal_embeddings: MultiModalEmbeddings | None = None, *, is_multimodal: torch.Tensor | None = None, + handle_oov_mm_token: bool = False, ) -> torch.Tensor: # This is to satisfy the type checker for each overload if multimodal_embeddings is None or is_multimodal is None: @@ -782,6 +783,7 @@ def embed_input_ids( input_ids, multimodal_embeddings=multimodal_embeddings, is_multimodal=is_multimodal, + handle_oov_mm_token=handle_oov_mm_token, ) def base_local_forward( diff --git a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_talker.py b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_talker.py index 8c348a9c663..210d262f187 100644 --- a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_talker.py +++ b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_talker.py @@ -109,6 +109,7 @@ def embed_input_ids( multimodal_embeddings: MultiModalEmbeddings | None = None, *, is_multimodal: torch.Tensor | None = None, + handle_oov_mm_token: bool = False, ) -> torch.Tensor: # This is to satisfy the type checker for each overload if multimodal_embeddings is None or is_multimodal is None: @@ -118,6 +119,7 @@ def embed_input_ids( input_ids, multimodal_embeddings=multimodal_embeddings, is_multimodal=is_multimodal, + handle_oov_mm_token=handle_oov_mm_token, ) def forward( @@ -230,7 +232,7 @@ def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings: return [] # The result multimodal_embeddings is tuple of tensors, with each - # tensor correspoending to a multimodal data item (image or video). + # tensor corresponding to a multimodal data item (image or video). multimodal_embeddings: tuple[torch.Tensor, ...] = () # NOTE: It is important to iterate over the keys in this dictionary diff --git a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py index c32b121c290..4560ede3999 100644 --- a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py +++ b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py @@ -58,6 +58,10 @@ MultiModalKwargsItems, ) from vllm.multimodal.parse import MultiModalDataItems +from vllm.multimodal.processing import ( + ProcessorInputs, + TimingContext, +) from vllm.multimodal.processing.processor import ( MultiModalPromptUpdates, PlaceholderFeaturesInfo, @@ -76,6 +80,16 @@ class Qwen2_5OmniThinkerMultiModalProcessor( ): """Override to fix use_audio_in_video detection when mm cache returns None.""" + def _cached_apply_hf_processor( + self, + inputs: ProcessorInputs, + timing_ctx: TimingContext, + ): + mm_processor_kwargs = inputs.hf_processor_mm_kwargs + if mm_processor_kwargs.get("use_audio_in_video", False): + return self._apply_hf_processor(inputs, timing_ctx) + return super()._cached_apply_hf_processor(inputs, timing_ctx) + def _maybe_apply_prompt_updates( self, mm_items: MultiModalDataItems, @@ -586,6 +600,7 @@ def embed_input_ids( multimodal_embeddings: MultiModalEmbeddings | None = None, *, is_multimodal: torch.Tensor | None = None, + handle_oov_mm_token: bool = False, ) -> torch.Tensor: if multimodal_embeddings is None or is_multimodal is None: return super().embed_input_ids(input_ids) @@ -594,6 +609,7 @@ def embed_input_ids( input_ids, self.get_language_model().embed_input_ids, is_multimodal=is_multimodal, + handle_oov_mm_token=handle_oov_mm_token, ) if len(multimodal_embeddings) == 0: @@ -616,6 +632,7 @@ def embed_input_ids( input_ids, self.get_language_model().embed_input_ids, is_multimodal=is_multimodal, + handle_oov_mm_token=handle_oov_mm_token, ) return merge_interleaved_embeddings( inputs_embeds, @@ -632,6 +649,7 @@ def embed_input_ids( input_ids, multimodal_embeddings=multimodal_embeddings, is_multimodal=is_multimodal, + handle_oov_mm_token=handle_oov_mm_token, ) def forward( diff --git a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py index 9f5c20cf7e4..7b3d0b017c0 100644 --- a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py +++ b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py @@ -341,7 +341,7 @@ def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings | None: ) # The result multimodal_embeddings is tuple of tensors, with each - # tensor correspoending to a multimodal data item (image or video). + # tensor corresponding to a multimodal data item (image or video). dummy_multimodal_embeddings: tuple[torch.Tensor, ...] = () # NOTE: It is important to iterate over the keys in this dictionary diff --git a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py index a0baefca769..ca7bb58c7e0 100644 --- a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py +++ b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py @@ -966,11 +966,13 @@ def embed_input_ids( multimodal_embeddings: MultiModalEmbeddings | None = None, *, is_multimodal: torch.Tensor | None = None, + handle_oov_mm_token: bool = False, ) -> torch.Tensor: inputs_embeds = self._embed_text_input_ids( input_ids, self.language_model.embed_input_ids, is_multimodal=is_multimodal, + handle_oov_mm_token=handle_oov_mm_token, ) if multimodal_embeddings is None or len(multimodal_embeddings) == 0: @@ -1062,6 +1064,7 @@ def embed_input_ids( input_ids, multimodal_embeddings=multimodal_embeddings, is_multimodal=is_multimodal, + handle_oov_mm_token=handle_oov_mm_token, ) def forward( diff --git a/vllm_omni/worker/gpu_ar_model_runner.py b/vllm_omni/worker/gpu_ar_model_runner.py index 9dce9778dfc..d7d45031af4 100644 --- a/vllm_omni/worker/gpu_ar_model_runner.py +++ b/vllm_omni/worker/gpu_ar_model_runner.py @@ -141,7 +141,7 @@ def execute_model( # Update persistent batch states. self._update_states(scheduler_output) - if has_ec_transfer() and not get_ec_transfer().is_consumer: + if has_ec_transfer() and get_ec_transfer().is_producer: with self.maybe_get_ec_connector_output( scheduler_output, encoder_cache=self.encoder_cache, @@ -276,9 +276,9 @@ def execute_model( # Run the model. # Use persistent buffers for CUDA graphs. - # When spec decode is enabled, defer connector finalization - # (wait_for_save + clear metadata) until after draft model runs. - defer_kv_connector_finalize = self.speculative_config is not None + # When spec decode is enabled, delay clearing connector metadata + # until after draft model runs in sample_tokens. + clear_kv_metadata = self.speculative_config is None with ( set_forward_context( attn_metadata, @@ -292,8 +292,7 @@ def execute_model( ), record_function_or_nullcontext("gpu_model_runner: forward"), self.maybe_get_kv_connector_output( - scheduler_output, - defer_finalize=defer_kv_connector_finalize, + scheduler_output, clear_metadata=clear_kv_metadata ) as kv_connector_output, ): model_output = self._model_forward( @@ -534,11 +533,11 @@ def propose_draft_token_ids(sampled_token_ids): # tokens on the CPU, so they are run after bookkeeping. propose_draft_token_ids(valid_sampled_token_ids) - # Finalize KV connector (wait_for_save + clear metadata) after - # draft model runs. Deferred from target model forward to allow - # draft model to also save its KV cache. + # Clear KV connector metadata after draft model runs (if spec decode). + # This was deferred from target model forward to allow draft model + # to also save its KV cache. if self.speculative_config is not None: - self.finalize_kv_connector() + self.clear_kv_connector_metadata() with record_function_or_nullcontext("gpu_model_runner: eplb"): self.eplb_step() diff --git a/vllm_omni/worker/gpu_model_runner.py b/vllm_omni/worker/gpu_model_runner.py index 5a3d2f9ca35..1b1d3d9cf47 100644 --- a/vllm_omni/worker/gpu_model_runner.py +++ b/vllm_omni/worker/gpu_model_runner.py @@ -238,7 +238,8 @@ def _update_states(self, scheduler_output: "SchedulerOutput") -> None: self.requests.pop(req_id, None) self.model_intermediate_buffer.pop(req_id, None) self.num_prompt_logprobs.pop(req_id, None) - self.late_interaction_runner.on_requests_finished(scheduler_output.finished_req_ids) + if hasattr(self, "late_interaction_runner"): + self.late_interaction_runner.on_requests_finished(scheduler_output.finished_req_ids) # Remove the finished requests from the persistent batch. # NOTE(woosuk): There could be an edge case where finished_req_ids and # scheduled_req_ids overlap. This happens when a request is aborted and @@ -250,7 +251,7 @@ def _update_states(self, scheduler_output: "SchedulerOutput") -> None: # Zero GPU memory for freshly allocated cache blocks to prevent # stale NaN/data from corrupting attention or SSM computation. - if scheduler_output.new_block_ids_to_zero: + if hasattr(scheduler_output, "new_block_ids_to_zero") and scheduler_output.new_block_ids_to_zero: self._zero_block_ids(scheduler_output.new_block_ids_to_zero) # Free the cached encoder outputs. @@ -320,7 +321,8 @@ def _update_states(self, scheduler_output: "SchedulerOutput") -> None: lora_request=new_req_data.lora_request, ) self.requests[req_id] = req_state - self.late_interaction_runner.register_request(req_id, pooling_params) + if hasattr(self, "late_interaction_runner"): + self.late_interaction_runner.register_request(req_id, pooling_params) # If prompt embeddings are provided, decode and attach to inter_data try: From d911475b22a7076169220e377be2fd542848a454 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Fri, 13 Mar 2026 01:59:04 +0000 Subject: [PATCH 21/47] Fix output queue handling in OmniStage to return None if queue is closed during shutdown Signed-off-by: tzhouam --- vllm_omni/entrypoints/omni_stage.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vllm_omni/entrypoints/omni_stage.py b/vllm_omni/entrypoints/omni_stage.py index 3fa722b032a..899d8ddff70 100644 --- a/vllm_omni/entrypoints/omni_stage.py +++ b/vllm_omni/entrypoints/omni_stage.py @@ -689,8 +689,10 @@ def try_collect(self) -> dict[str, Any] | None: Returns: Result dictionary if available, None otherwise. Result contains request_id, engine_outputs (or engine_outputs_shm), and metrics. + Returns None if output queue is already closed (e.g. during shutdown). """ - assert self._out_q is not None + if self._out_q is None: + return None try: return self._out_q.get_nowait() except Exception: From 62c8695dfd831f129b2e3021ae6d5c1673c51cce Mon Sep 17 00:00:00 2001 From: tzhouam Date: Fri, 13 Mar 2026 03:16:33 +0000 Subject: [PATCH 22/47] Update GPUGenerationModelRunner to clarify KV connector metadata handling during draft model runs - Changed variable name to better reflect its purpose in managing KV connector metadata. - Updated comments for improved clarity regarding the handling of speculative configurations. Signed-off-by: tzhouam --- vllm_omni/worker/gpu_generation_model_runner.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vllm_omni/worker/gpu_generation_model_runner.py b/vllm_omni/worker/gpu_generation_model_runner.py index 0dd304ed23f..58e4dfd721e 100644 --- a/vllm_omni/worker/gpu_generation_model_runner.py +++ b/vllm_omni/worker/gpu_generation_model_runner.py @@ -265,9 +265,9 @@ def execute_model( # Run the model. # Use persistent buffers for CUDA graphs. - # When spec decode is enabled, defer connector finalization - # (wait_for_save + clear metadata) until after draft model runs. - defer_kv_connector_finalize = self.speculative_config is not None + # When spec decode is enabled, defer clearing KV connector metadata + # until after draft model runs in sample_tokens. + clear_kv_metadata = self.speculative_config is None with ( set_forward_context( attn_metadata, @@ -282,7 +282,7 @@ def execute_model( record_function_or_nullcontext("Forward"), self.maybe_get_kv_connector_output( scheduler_output, - defer_finalize=defer_kv_connector_finalize, + clear_metadata=clear_kv_metadata, ) as kv_connector_output, ): outputs = self._run_generation_model( From 3a508d7b7a250c7c6a05b232bb4c5a7f71114c2c Mon Sep 17 00:00:00 2001 From: tzhouam Date: Mon, 16 Mar 2026 02:32:39 +0000 Subject: [PATCH 23/47] rebase: align vllm-omni with vLLM e9163b536e72 Signed-off-by: tzhouam --- docker/Dockerfile.ci | 2 +- tests/entrypoints/test_omni_diffusion.py | 20 +++++++++++ vllm_omni/core/sched/omni_ar_scheduler.py | 1 + .../core/sched/omni_generation_scheduler.py | 1 + vllm_omni/entrypoints/cli/benchmark/main.py | 2 +- vllm_omni/entrypoints/openai/api_server.py | 35 ++++++++++++++----- .../models/mimo_audio/mimo_audio_llm.py | 2 -- .../qwen2_5_omni/qwen2_5_omni_talker.py | 2 -- .../qwen2_5_omni/qwen2_5_omni_thinker.py | 13 ++++--- .../qwen3_omni/qwen3_omni_moe_thinker.py | 20 +++++++---- vllm_omni/worker/gpu_ar_model_runner.py | 23 ++++++------ vllm_omni/worker/gpu_ar_worker.py | 6 ++-- .../worker/gpu_generation_model_runner.py | 10 +++--- vllm_omni/worker/gpu_generation_worker.py | 6 ++-- 14 files changed, 97 insertions(+), 46 deletions(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index 117aba15319..6e7b64edd8b 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -17,7 +17,7 @@ RUN apt-get update && \ # Note: --prerelease=allow is required because nightly/commit wheels use # pre-release version numbers (e.g. 2.10.0.dev...) which uv ignores by default, # causing it to fall back to the older stable release from PyPI. -RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/84e436ed1c94b1b94f809927b5d6bff45f7af919 +RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/e9163b536e721c431500f6f43ace22fcb3532e7e RUN uv pip install --system --no-cache-dir ".[dev]" diff --git a/tests/entrypoints/test_omni_diffusion.py b/tests/entrypoints/test_omni_diffusion.py index 9e555fa85c8..ea4402ebdf3 100644 --- a/tests/entrypoints/test_omni_diffusion.py +++ b/tests/entrypoints/test_omni_diffusion.py @@ -1371,9 +1371,19 @@ def _fake_loader(model: str, base_engine_args=None): _setup_multiprocessing_mocks(monkeypatch, mocker) _setup_ipc_mocks(monkeypatch) _setup_log_mocks(monkeypatch) + _setup_connector_mocks(monkeypatch, mocker) + monkeypatch.setattr( + "vllm_omni.entrypoints.omni.omni_snapshot_download", + lambda model_id: model_id, + raising=False, + ) + + import vllm_omni.entrypoints.omni as omni_module from vllm_omni.entrypoints.omni import Omni + monkeypatch.setattr(omni_module, "omni_snapshot_download", lambda model_id: model_id) + monkeypatch.setattr( "vllm_omni.entrypoints.utils.load_stage_configs_from_model", _fake_loader, @@ -1417,9 +1427,19 @@ class NotATorchDtype: _setup_multiprocessing_mocks(monkeypatch, mocker) _setup_ipc_mocks(monkeypatch) _setup_log_mocks(monkeypatch) + _setup_connector_mocks(monkeypatch, mocker) + monkeypatch.setattr( + "vllm_omni.entrypoints.omni.omni_snapshot_download", + lambda model_id: model_id, + raising=False, + ) + + import vllm_omni.entrypoints.omni as omni_module from vllm_omni.entrypoints.omni import Omni + monkeypatch.setattr(omni_module, "omni_snapshot_download", lambda model_id: model_id) + monkeypatch.setattr( "vllm_omni.entrypoints.utils.load_stage_configs_from_model", _fake_loader, diff --git a/vllm_omni/core/sched/omni_ar_scheduler.py b/vllm_omni/core/sched/omni_ar_scheduler.py index 540fc391c51..3629ade63f5 100644 --- a/vllm_omni/core/sched/omni_ar_scheduler.py +++ b/vllm_omni/core/sched/omni_ar_scheduler.py @@ -364,6 +364,7 @@ def update_from_output( if stopped_preempted_reqs: # This is a rare case and unlikely to impact performance. self.waiting.remove_requests(stopped_preempted_reqs) + self.skipped_waiting.remove_requests(stopped_preempted_reqs) # [Main] Handle failed KV load requests if failed_kv_load_req_ids and not self.recompute_kv_load_failures: diff --git a/vllm_omni/core/sched/omni_generation_scheduler.py b/vllm_omni/core/sched/omni_generation_scheduler.py index b2e64224b10..64522b909d3 100644 --- a/vllm_omni/core/sched/omni_generation_scheduler.py +++ b/vllm_omni/core/sched/omni_generation_scheduler.py @@ -502,6 +502,7 @@ def update_from_output( if stopped_preempted_reqs: # This is a rare case and unlikely to impact performance. self.waiting.remove_requests(stopped_preempted_reqs) + self.skipped_waiting.remove_requests(stopped_preempted_reqs) # Handle failed KV load requests if failed_kv_load_req_ids and not self.recompute_kv_load_failures: diff --git a/vllm_omni/entrypoints/cli/benchmark/main.py b/vllm_omni/entrypoints/cli/benchmark/main.py index 8880e35c7cf..865064d1e9e 100644 --- a/vllm_omni/entrypoints/cli/benchmark/main.py +++ b/vllm_omni/entrypoints/cli/benchmark/main.py @@ -9,7 +9,7 @@ from vllm_omni.entrypoints.cli.benchmark.base import OmniBenchmarkSubcommandBase if typing.TYPE_CHECKING: - from vllm.utils import FlexibleArgumentParser + from vllm.utils.argparse_utils import FlexibleArgumentParser class OmniBenchmarkSubcommand(CLISubcommand): diff --git a/vllm_omni/entrypoints/openai/api_server.py b/vllm_omni/entrypoints/openai/api_server.py index 4929f951c37..c83cb7b7b70 100644 --- a/vllm_omni/entrypoints/openai/api_server.py +++ b/vllm_omni/entrypoints/openai/api_server.py @@ -35,13 +35,6 @@ from vllm.entrypoints.mcp.tool_server import DemoToolServer, MCPToolServer, ToolServer from vllm.entrypoints.openai.api_server import build_app as build_openai_app from vllm.entrypoints.openai.api_server import setup_server as setup_openai_server - -# vLLM moved `base` from openai.basic.api_router to serve.instrumentator.basic. -# Keep a fallback for older/newer upstream layouts during rebase windows. -try: - from vllm.entrypoints.serve.instrumentator.basic import base -except ModuleNotFoundError: - from vllm.entrypoints.openai.basic.api_router import base from vllm.entrypoints.openai.chat_completion.protocol import ( ChatCompletionRequest, ChatCompletionResponse, @@ -68,10 +61,15 @@ ) from vllm.entrypoints.openai.utils import validate_json_request from vllm.entrypoints.pooling.classify.serving import ServingClassification -from vllm.entrypoints.pooling.embed.serving import OpenAIServingEmbedding +from vllm.entrypoints.pooling.embed.serving import ServingEmbedding as OpenAIServingEmbedding from vllm.entrypoints.pooling.pooling.serving import OpenAIServingPooling from vllm.entrypoints.pooling.score.serving import ServingScores from vllm.entrypoints.serve.disagg.serving import ServingTokens + +# vLLM moved `base` from openai.basic.api_router to serve.instrumentator.basic. +# Keep a fallback for older/newer upstream layouts during rebase windows. +from vllm.entrypoints.serve.instrumentator.basic import base +from vllm.entrypoints.serve.render.serving import OpenAIServingRender from vllm.entrypoints.serve.tokenize.serving import OpenAIServingTokenization from vllm.entrypoints.utils import ( load_aware_call, @@ -306,6 +304,7 @@ async def omni_run_server_worker(listen_address, sock, args, client_config=None, ssl_certfile=args.ssl_certfile, ssl_ca_certs=args.ssl_ca_certs, ssl_cert_reqs=args.ssl_cert_reqs, + ssl_ciphers=args.ssl_ciphers, h11_max_incomplete_event_size=args.h11_max_incomplete_event_size, h11_max_header_count=args.h11_max_header_count, **uvicorn_kwargs, @@ -579,6 +578,22 @@ async def omni_init_app_state( ) await state.openai_serving_models.init_static_loras() + state.openai_serving_render = OpenAIServingRender( + model_config=engine_client.model_config, + renderer=engine_client.renderer, + io_processor=engine_client.io_processor, + model_registry=state.openai_serving_models.registry, + request_logger=request_logger, + chat_template=resolved_chat_template, + chat_template_content_format=args.chat_template_content_format, + trust_request_chat_template=args.trust_request_chat_template, + enable_auto_tools=args.enable_auto_tool_choice, + exclude_tools_when_tool_choice_none=args.exclude_tools_when_tool_choice_none, + tool_parser=args.tool_call_parser, + default_chat_template_kwargs=args.default_chat_template_kwargs, + log_error_stack=args.log_error_stack, + ) + state.openai_serving_responses = ( OpenAIServingResponses( engine_client, @@ -603,6 +618,7 @@ async def omni_init_app_state( engine_client, state.openai_serving_models, args.response_role, + openai_serving_render=state.openai_serving_render, request_logger=request_logger, chat_template=resolved_chat_template, chat_template_content_format=args.chat_template_content_format, @@ -629,6 +645,7 @@ async def omni_init_app_state( OpenAIServingCompletion( engine_client, state.openai_serving_models, + openai_serving_render=state.openai_serving_render, request_logger=request_logger, return_tokens_as_token_ids=args.return_tokens_as_token_ids, enable_prompt_tokens_details=args.enable_prompt_tokens_details, @@ -691,6 +708,7 @@ async def omni_init_app_state( request_logger=request_logger, chat_template=resolved_chat_template, chat_template_content_format=args.chat_template_content_format, + default_chat_template_kwargs=args.default_chat_template_kwargs, trust_request_chat_template=args.trust_request_chat_template, ) state.openai_serving_transcription = ( @@ -718,6 +736,7 @@ async def omni_init_app_state( engine_client, state.openai_serving_models, args.response_role, + openai_serving_render=state.openai_serving_render, request_logger=request_logger, chat_template=resolved_chat_template, chat_template_content_format=args.chat_template_content_format, diff --git a/vllm_omni/model_executor/models/mimo_audio/mimo_audio_llm.py b/vllm_omni/model_executor/models/mimo_audio/mimo_audio_llm.py index c1b71e076e4..1424ca7756b 100644 --- a/vllm_omni/model_executor/models/mimo_audio/mimo_audio_llm.py +++ b/vllm_omni/model_executor/models/mimo_audio/mimo_audio_llm.py @@ -773,7 +773,6 @@ def embed_input_ids( multimodal_embeddings: MultiModalEmbeddings | None = None, *, is_multimodal: torch.Tensor | None = None, - handle_oov_mm_token: bool = False, ) -> torch.Tensor: # This is to satisfy the type checker for each overload if multimodal_embeddings is None or is_multimodal is None: @@ -783,7 +782,6 @@ def embed_input_ids( input_ids, multimodal_embeddings=multimodal_embeddings, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) def base_local_forward( diff --git a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_talker.py b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_talker.py index 210d262f187..dbb6c49efae 100644 --- a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_talker.py +++ b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_talker.py @@ -109,7 +109,6 @@ def embed_input_ids( multimodal_embeddings: MultiModalEmbeddings | None = None, *, is_multimodal: torch.Tensor | None = None, - handle_oov_mm_token: bool = False, ) -> torch.Tensor: # This is to satisfy the type checker for each overload if multimodal_embeddings is None or is_multimodal is None: @@ -119,7 +118,6 @@ def embed_input_ids( input_ids, multimodal_embeddings=multimodal_embeddings, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) def forward( diff --git a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py index 4560ede3999..5c69fca66c4 100644 --- a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py +++ b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py @@ -116,6 +116,15 @@ def _maybe_apply_prompt_updates( tokenizer = self.info.get_tokenizer() audio_pad_id = tokenizer.convert_tokens_to_ids("<|audio_pad|>") use_audio_in_video = audio_pad_id not in prompt_ids + # for mutilmodality cache + if any(item is None for item in mm_kwargs["video"]): + video_token_id = self.info.get_hf_config().video_token_id + audio_token_id = self.info.get_hf_config().audio_token_id + video_audio_item_num = sum(id in (video_token_id, audio_token_id) for id in prompt_ids) + audio_updates_num = len(mm_prompt_updates.get("audio", [])) + video_updates_num = len(mm_prompt_updates.get("video", [])) + if video_audio_item_num != video_updates_num + audio_updates_num: + use_audio_in_video = True if is_update_applied: mm_placeholders = self._find_mm_placeholders( @@ -600,7 +609,6 @@ def embed_input_ids( multimodal_embeddings: MultiModalEmbeddings | None = None, *, is_multimodal: torch.Tensor | None = None, - handle_oov_mm_token: bool = False, ) -> torch.Tensor: if multimodal_embeddings is None or is_multimodal is None: return super().embed_input_ids(input_ids) @@ -609,7 +617,6 @@ def embed_input_ids( input_ids, self.get_language_model().embed_input_ids, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) if len(multimodal_embeddings) == 0: @@ -632,7 +639,6 @@ def embed_input_ids( input_ids, self.get_language_model().embed_input_ids, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) return merge_interleaved_embeddings( inputs_embeds, @@ -649,7 +655,6 @@ def embed_input_ids( input_ids, multimodal_embeddings=multimodal_embeddings, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) def forward( diff --git a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py index ca7bb58c7e0..5111fe1d492 100644 --- a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py +++ b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py @@ -146,9 +146,11 @@ def forward( cu_seqlens = np.repeat(grid_thw_np[:, 1] * grid_thw_np[:, 2], grid_thw_np[:, 0]).cumsum(axis=0, dtype=np.int32) cu_seqlens = np.concatenate([np.zeros(1, dtype=np.int32), cu_seqlens]) - sequence_lengths = MMEncoderAttention.maybe_compute_sequence_lengths(self.attn_backend, cu_seqlens) - if sequence_lengths is not None: - sequence_lengths = torch.from_numpy(sequence_lengths).to(self.device, non_blocking=True) + sequence_lengths = MMEncoderAttention.maybe_compute_seq_lens( + self.attn_backend, + cu_seqlens, + self.device, + ) max_seqlen = torch.tensor( MMEncoderAttention.compute_max_seqlen(self.attn_backend, cu_seqlens), dtype=torch.int32, @@ -475,6 +477,15 @@ def _maybe_apply_prompt_updates( tokenizer = self.info.get_tokenizer() audio_pad_id = tokenizer.convert_tokens_to_ids("<|audio_pad|>") use_audio_in_video = audio_pad_id not in prompt_ids + # for mutilmodality cache + if any(item is None for item in mm_kwargs["video"]): + video_token_id = self.info.get_hf_config().video_token_id + audio_token_id = self.info.get_hf_config().audio_token_id + video_audio_item_num = sum(id in (video_token_id, audio_token_id) for id in prompt_ids) + audio_updates_num = len(mm_prompt_updates.get("audio", [])) + video_updates_num = len(mm_prompt_updates.get("video", [])) + if video_audio_item_num != video_updates_num + audio_updates_num: + use_audio_in_video = True # normal case with `use_audio_in_video=False` if is_update_applied: @@ -966,13 +977,11 @@ def embed_input_ids( multimodal_embeddings: MultiModalEmbeddings | None = None, *, is_multimodal: torch.Tensor | None = None, - handle_oov_mm_token: bool = False, ) -> torch.Tensor: inputs_embeds = self._embed_text_input_ids( input_ids, self.language_model.embed_input_ids, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) if multimodal_embeddings is None or len(multimodal_embeddings) == 0: @@ -1064,7 +1073,6 @@ def embed_input_ids( input_ids, multimodal_embeddings=multimodal_embeddings, is_multimodal=is_multimodal, - handle_oov_mm_token=handle_oov_mm_token, ) def forward( diff --git a/vllm_omni/worker/gpu_ar_model_runner.py b/vllm_omni/worker/gpu_ar_model_runner.py index d7d45031af4..166cc6231ae 100644 --- a/vllm_omni/worker/gpu_ar_model_runner.py +++ b/vllm_omni/worker/gpu_ar_model_runner.py @@ -123,7 +123,7 @@ def execute_model( request_id_resolver=self._resolve_global_request_id, ) - if self.vllm_config.model_config.enable_return_routed_experts: + if self.routed_experts_initialized: capturer = RoutedExpertsCapturer.get_instance() if capturer is not None: capturer.clear_buffer() # noqa @@ -141,7 +141,7 @@ def execute_model( # Update persistent batch states. self._update_states(scheduler_output) - if has_ec_transfer() and get_ec_transfer().is_producer: + if has_ec_transfer() and not get_ec_transfer().is_consumer: with self.maybe_get_ec_connector_output( scheduler_output, encoder_cache=self.encoder_cache, @@ -276,9 +276,9 @@ def execute_model( # Run the model. # Use persistent buffers for CUDA graphs. - # When spec decode is enabled, delay clearing connector metadata - # until after draft model runs in sample_tokens. - clear_kv_metadata = self.speculative_config is None + # When spec decode is enabled, defer connector finalization + # (wait_for_save + clear metadata) until after draft model runs. + defer_kv_connector_finalize = self.speculative_config is not None with ( set_forward_context( attn_metadata, @@ -292,7 +292,8 @@ def execute_model( ), record_function_or_nullcontext("gpu_model_runner: forward"), self.maybe_get_kv_connector_output( - scheduler_output, clear_metadata=clear_kv_metadata + scheduler_output, + defer_finalize=defer_kv_connector_finalize, ) as kv_connector_output, ): model_output = self._model_forward( @@ -533,11 +534,11 @@ def propose_draft_token_ids(sampled_token_ids): # tokens on the CPU, so they are run after bookkeeping. propose_draft_token_ids(valid_sampled_token_ids) - # Clear KV connector metadata after draft model runs (if spec decode). - # This was deferred from target model forward to allow draft model - # to also save its KV cache. + # Finalize KV connector (wait_for_save + clear metadata) after + # draft model runs. Deferred from target model forward to allow + # draft model to also save its KV cache. if self.speculative_config is not None: - self.clear_kv_connector_metadata() + self.finalize_kv_connector() with record_function_or_nullcontext("gpu_model_runner: eplb"): self.eplb_step() @@ -591,7 +592,7 @@ def propose_draft_token_ids(sampled_token_ids): payload.update(mm_payload) pooler_output.append(payload) with record_function_or_nullcontext("gpu_model_runner: ModelRunnerOutput"): - if self.model_config.enable_return_routed_experts: + if self.routed_experts_initialized: capturer = RoutedExpertsCapturer.get_instance() if capturer is not None: capturer.save_captured_experts(indices=self.slot_mapping) # noqa diff --git a/vllm_omni/worker/gpu_ar_worker.py b/vllm_omni/worker/gpu_ar_worker.py index 8734524a221..4abe21964b3 100644 --- a/vllm_omni/worker/gpu_ar_worker.py +++ b/vllm_omni/worker/gpu_ar_worker.py @@ -48,17 +48,17 @@ def init_device(self): # DP_LOCAL_RANK * TP_PP_WORLD_SIZE + TP_LOCAL_RANK self.local_rank += dp_local_rank * tp_pp_world_size - assert self.local_rank < torch.cuda.device_count(), ( + assert self.local_rank < torch.accelerator.device_count(), ( f"DP adjusted local rank {self.local_rank} is out of bounds. " ) - visible_device_count = torch.cuda.device_count() if torch.cuda.is_available() else 0 + visible_device_count = torch.accelerator.device_count() if torch.cuda.is_available() else 0 assert self.parallel_config.local_world_size <= visible_device_count, ( f"local_world_size ({self.parallel_config.local_world_size}) must " f"be less than or equal to the number of visible devices " f"({visible_device_count})." ) self.device = torch.device(f"cuda:{self.local_rank}") - current_platform.set_device(self.device) + torch.accelerator.set_device_index(self.device) current_platform.check_if_supports_dtype(self.model_config.dtype) diff --git a/vllm_omni/worker/gpu_generation_model_runner.py b/vllm_omni/worker/gpu_generation_model_runner.py index 58e4dfd721e..4db683a8b4a 100644 --- a/vllm_omni/worker/gpu_generation_model_runner.py +++ b/vllm_omni/worker/gpu_generation_model_runner.py @@ -87,7 +87,7 @@ def execute_model( if self.execute_model_state is not None: raise RuntimeError("State error: sample_tokens() must be called after execute_model() returns None.") - if self.vllm_config.model_config.enable_return_routed_experts: + if self.routed_experts_initialized: capturer = RoutedExpertsCapturer.get_instance() if capturer is not None: capturer.clear_buffer() # noqa @@ -265,9 +265,9 @@ def execute_model( # Run the model. # Use persistent buffers for CUDA graphs. - # When spec decode is enabled, defer clearing KV connector metadata - # until after draft model runs in sample_tokens. - clear_kv_metadata = self.speculative_config is None + # When spec decode is enabled, defer connector finalization + # (wait_for_save + clear metadata) until after draft model runs. + defer_kv_connector_finalize = self.speculative_config is not None with ( set_forward_context( attn_metadata, @@ -282,7 +282,7 @@ def execute_model( record_function_or_nullcontext("Forward"), self.maybe_get_kv_connector_output( scheduler_output, - clear_metadata=clear_kv_metadata, + defer_finalize=defer_kv_connector_finalize, ) as kv_connector_output, ): outputs = self._run_generation_model( diff --git a/vllm_omni/worker/gpu_generation_worker.py b/vllm_omni/worker/gpu_generation_worker.py index 341797ebb67..267ed61c0a4 100644 --- a/vllm_omni/worker/gpu_generation_worker.py +++ b/vllm_omni/worker/gpu_generation_worker.py @@ -48,17 +48,17 @@ def init_device(self): # DP_LOCAL_RANK * TP_PP_WORLD_SIZE + TP_LOCAL_RANK self.local_rank += dp_local_rank * tp_pp_world_size - assert self.local_rank < torch.cuda.device_count(), ( + assert self.local_rank < torch.accelerator.device_count(), ( f"DP adjusted local rank {self.local_rank} is out of bounds. " ) - visible_device_count = torch.cuda.device_count() if torch.cuda.is_available() else 0 + visible_device_count = torch.accelerator.device_count() if torch.cuda.is_available() else 0 assert self.parallel_config.local_world_size <= visible_device_count, ( f"local_world_size ({self.parallel_config.local_world_size}) must " f"be less than or equal to the number of visible devices " f"({visible_device_count})." ) self.device = torch.device(f"cuda:{self.local_rank}") - current_platform.set_device(self.device) + torch.accelerator.set_device_index(self.device) current_platform.check_if_supports_dtype(self.model_config.dtype) From aae101f49556fad1060db38a752516a7f86f3eb6 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Mon, 16 Mar 2026 03:39:51 +0000 Subject: [PATCH 24/47] Refactor import statement for OpenAIServingEmbedding in api_server.py Signed-off-by: tzhouam --- vllm_omni/entrypoints/openai/api_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vllm_omni/entrypoints/openai/api_server.py b/vllm_omni/entrypoints/openai/api_server.py index c83cb7b7b70..2ef99cc8edf 100644 --- a/vllm_omni/entrypoints/openai/api_server.py +++ b/vllm_omni/entrypoints/openai/api_server.py @@ -61,7 +61,7 @@ ) from vllm.entrypoints.openai.utils import validate_json_request from vllm.entrypoints.pooling.classify.serving import ServingClassification -from vllm.entrypoints.pooling.embed.serving import ServingEmbedding as OpenAIServingEmbedding +from vllm.entrypoints.pooling.embed.serving import OpenAIServingEmbedding from vllm.entrypoints.pooling.pooling.serving import OpenAIServingPooling from vllm.entrypoints.pooling.score.serving import ServingScores from vllm.entrypoints.serve.disagg.serving import ServingTokens From 9c22e3afb0ea71cb1d09e7c991f40669de5a5b53 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Mon, 16 Mar 2026 06:13:07 +0000 Subject: [PATCH 25/47] Refactor import statement for OpenAIServingEmbedding in api_server.py to use aliasing for clarity Signed-off-by: tzhouam --- vllm_omni/entrypoints/openai/api_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vllm_omni/entrypoints/openai/api_server.py b/vllm_omni/entrypoints/openai/api_server.py index 2ef99cc8edf..c83cb7b7b70 100644 --- a/vllm_omni/entrypoints/openai/api_server.py +++ b/vllm_omni/entrypoints/openai/api_server.py @@ -61,7 +61,7 @@ ) from vllm.entrypoints.openai.utils import validate_json_request from vllm.entrypoints.pooling.classify.serving import ServingClassification -from vllm.entrypoints.pooling.embed.serving import OpenAIServingEmbedding +from vllm.entrypoints.pooling.embed.serving import ServingEmbedding as OpenAIServingEmbedding from vllm.entrypoints.pooling.pooling.serving import OpenAIServingPooling from vllm.entrypoints.pooling.score.serving import ServingScores from vllm.entrypoints.serve.disagg.serving import ServingTokens From de6bcc734cd3b7c77f2f88c37aa5bd7c5178d858 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Mon, 16 Mar 2026 08:33:28 +0000 Subject: [PATCH 26/47] Update Dockerfile.ci to use a new vllm wheel URL and refactor MooncakeConnector import logic in monkey_patch.py for improved compatibility. Adjust OpenAIServingChat to call the harmony request method from the correct context. Signed-off-by: tzhouam --- docker/Dockerfile.ci | 2 +- .../distributed/kv_transfer/monkey_patch.py | 34 ++++++++----------- vllm_omni/entrypoints/openai/serving_chat.py | 4 ++- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index 6e7b64edd8b..5a3358343a2 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -17,7 +17,7 @@ RUN apt-get update && \ # Note: --prerelease=allow is required because nightly/commit wheels use # pre-release version numbers (e.g. 2.10.0.dev...) which uv ignores by default, # causing it to fall back to the older stable release from PyPI. -RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/e9163b536e721c431500f6f43ace22fcb3532e7e +RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/2754231ba3a72f41e62922d1552c33e8f3f6a9d1 RUN uv pip install --system --no-cache-dir ".[dev]" diff --git a/vllm_omni/distributed/kv_transfer/monkey_patch.py b/vllm_omni/distributed/kv_transfer/monkey_patch.py index 455ad80652c..20ad53258ad 100644 --- a/vllm_omni/distributed/kv_transfer/monkey_patch.py +++ b/vllm_omni/distributed/kv_transfer/monkey_patch.py @@ -8,6 +8,7 @@ from __future__ import annotations +import importlib import logging import sys from dataclasses import dataclass @@ -30,30 +31,23 @@ class PatchedRecvReqMeta: def _import_mooncake_module(): """Import MooncakeConnector module, supporting both vLLM >=0.16 and older.""" - try: - from vllm.distributed.kv_transfer.kv_connector.v1.mooncake import mooncake_connector - - return mooncake_connector - except ImportError: - pass - try: - from vllm.distributed.kv_transfer.kv_connector.v1 import mooncake_connector - - return mooncake_connector - except ImportError: - return None + for mod_path in ( + "vllm.distributed.kv_transfer.kv_connector.v1.mooncake.mooncake_connector", + "vllm.distributed.kv_transfer.kv_connector.v1.mooncake_connector", + ): + try: + return importlib.import_module(mod_path) + except (ImportError, ModuleNotFoundError): + continue + return None def _create_patched_mooncake_connector(): """Return a subclass of MooncakeConnector with remote_request_id support.""" - try: - from vllm.distributed.kv_transfer.kv_connector.v1.mooncake.mooncake_connector import ( - MooncakeConnector as _OriginalMooncakeConnector, - ) - except (ImportError, AttributeError): - from vllm.distributed.kv_transfer.kv_connector.v1.mooncake_connector import ( - MooncakeConnector as _OriginalMooncakeConnector, - ) + _mc_mod = _import_mooncake_module() + if _mc_mod is None: + raise ImportError("Cannot import MooncakeConnector from upstream vLLM") + _OriginalMooncakeConnector = _mc_mod.MooncakeConnector class PatchedMooncakeConnector(_OriginalMooncakeConnector): """Fixes request-ID mismatch in PD disaggregation by injecting diff --git a/vllm_omni/entrypoints/openai/serving_chat.py b/vllm_omni/entrypoints/openai/serving_chat.py index ada4a698b48..933eabb3bcc 100644 --- a/vllm_omni/entrypoints/openai/serving_chat.py +++ b/vllm_omni/entrypoints/openai/serving_chat.py @@ -256,7 +256,9 @@ async def create_chat_completion( ) else: should_include_tools = tool_dicts is not None - conversation, engine_prompts = self._make_request_with_harmony(request, should_include_tools) + conversation, engine_prompts = self.openai_serving_render._make_request_with_harmony( + request, should_include_tools + ) except (ValueError, TypeError, RuntimeError, jinja2.TemplateError) as e: logger.exception("Error in preprocessing prompt inputs") From 57ac7eb155f854d4f7ae6adb7e5788e05e55ceb9 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Mon, 16 Mar 2026 09:28:56 +0000 Subject: [PATCH 27/47] rebase: align vllm-omni with vLLM 2754231ba3a7 Signed-off-by: tzhouam --- image_output.png | Bin 0 -> 113037 bytes .../qwen2_5_omni_ci_1773652593.yaml | 48 ++++++++++++++++++ .../qwen2_5_omni_ci_1773653262.yaml | 48 ++++++++++++++++++ .../qwen3_omni_ci_1773652593.yaml | 45 ++++++++++++++++ .../qwen3_omni_ci_1773653262.yaml | 45 ++++++++++++++++ 5 files changed, 186 insertions(+) create mode 100644 image_output.png create mode 100644 tests/e2e/stage_configs/qwen2_5_omni_ci_1773652593.yaml create mode 100644 tests/e2e/stage_configs/qwen2_5_omni_ci_1773653262.yaml create mode 100644 tests/e2e/stage_configs/qwen3_omni_ci_1773652593.yaml create mode 100644 tests/e2e/stage_configs/qwen3_omni_ci_1773653262.yaml diff --git a/image_output.png b/image_output.png new file mode 100644 index 0000000000000000000000000000000000000000..c216f4dff1b532caa7a067dad7e1474999572713 GIT binary patch literal 113037 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�=Agao8vtHQyy&#N3Y;Cr=QM>`OpsCINV&`{p#yq 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 FV1l4b{R992 literal 0 HcmV?d00001 diff --git a/tests/e2e/stage_configs/qwen2_5_omni_ci_1773652593.yaml b/tests/e2e/stage_configs/qwen2_5_omni_ci_1773652593.yaml new file mode 100644 index 00000000000..b68bdb71a0b --- /dev/null +++ b/tests/e2e/stage_configs/qwen2_5_omni_ci_1773652593.yaml @@ -0,0 +1,48 @@ +stage_args: +- stage_id: 0 + runtime: {process: true, devices: '0', max_batch_size: 1} + engine_args: {model_stage: thinker, model_arch: Qwen2_5OmniForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.9, + skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, engine_output_type: latent, + enable_prefix_caching: false, mm_processor_cache_gb: 0} + is_comprehension: true + final_output: true + final_output_type: text + default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 128, + seed: 42, detokenize: true, repetition_penalty: 1.1} +- stage_id: 1 + runtime: {process: true, devices: '1', max_batch_size: 1} + engine_args: {model_stage: talker, model_arch: Qwen2_5OmniForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.4, + skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, enable_prefix_caching: false, + engine_output_type: latent} + engine_input_source: [0] + custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen2_5_omni.thinker2talker + default_sampling_params: + temperature: 0.9 + top_p: 0.8 + top_k: 40 + max_tokens: 4096 + seed: 42 + detokenize: true + repetition_penalty: 1.05 + stop_token_ids: [8294] +- stage_id: 2 + runtime: {process: true, devices: '1', max_batch_size: 1} + engine_args: {model_stage: code2wav, model_arch: Qwen2_5OmniForConditionalGeneration, + worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, + gpu_memory_utilization: 0.5, enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, + engine_output_type: audio, max_num_batched_tokens: 4096, max_model_len: 4096} + engine_input_source: [1] + final_output: true + final_output_type: audio + default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 4096, + seed: 42, detokenize: true, repetition_penalty: 1.1} +runtime: + enabled: true + defaults: {window_size: -1, max_inflight: 1} + edges: + - {from: 0, to: 1, window_size: -1} + - {from: 1, to: 2, window_size: -1} diff --git a/tests/e2e/stage_configs/qwen2_5_omni_ci_1773653262.yaml b/tests/e2e/stage_configs/qwen2_5_omni_ci_1773653262.yaml new file mode 100644 index 00000000000..b68bdb71a0b --- /dev/null +++ b/tests/e2e/stage_configs/qwen2_5_omni_ci_1773653262.yaml @@ -0,0 +1,48 @@ +stage_args: +- stage_id: 0 + runtime: {process: true, devices: '0', max_batch_size: 1} + engine_args: {model_stage: thinker, model_arch: Qwen2_5OmniForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.9, + skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, engine_output_type: latent, + enable_prefix_caching: false, mm_processor_cache_gb: 0} + is_comprehension: true + final_output: true + final_output_type: text + default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 128, + seed: 42, detokenize: true, repetition_penalty: 1.1} +- stage_id: 1 + runtime: {process: true, devices: '1', max_batch_size: 1} + engine_args: {model_stage: talker, model_arch: Qwen2_5OmniForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.4, + skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, enable_prefix_caching: false, + engine_output_type: latent} + engine_input_source: [0] + custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen2_5_omni.thinker2talker + default_sampling_params: + temperature: 0.9 + top_p: 0.8 + top_k: 40 + max_tokens: 4096 + seed: 42 + detokenize: true + repetition_penalty: 1.05 + stop_token_ids: [8294] +- stage_id: 2 + runtime: {process: true, devices: '1', max_batch_size: 1} + engine_args: {model_stage: code2wav, model_arch: Qwen2_5OmniForConditionalGeneration, + worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, + gpu_memory_utilization: 0.5, enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, + engine_output_type: audio, max_num_batched_tokens: 4096, max_model_len: 4096} + engine_input_source: [1] + final_output: true + final_output_type: audio + default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 4096, + seed: 42, detokenize: true, repetition_penalty: 1.1} +runtime: + enabled: true + defaults: {window_size: -1, max_inflight: 1} + edges: + - {from: 0, to: 1, window_size: -1} + - {from: 1, to: 2, window_size: -1} diff --git a/tests/e2e/stage_configs/qwen3_omni_ci_1773652593.yaml b/tests/e2e/stage_configs/qwen3_omni_ci_1773652593.yaml new file mode 100644 index 00000000000..c763bb2c72f --- /dev/null +++ b/tests/e2e/stage_configs/qwen3_omni_ci_1773652593.yaml @@ -0,0 +1,45 @@ +stage_args: +- stage_id: 0 + runtime: {devices: '0', max_batch_size: 5} + engine_args: {model_stage: thinker, model_arch: Qwen3OmniMoeForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + gpu_memory_utilization: 0.9, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, + distributed_executor_backend: mp, max_num_batched_tokens: 32768, max_model_len: 32768, + enable_prefix_caching: false, mm_processor_cache_gb: 0, hf_config_name: thinker_config, + tensor_parallel_size: 1, load_format: dummy, custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_async_chunk} + final_output: true + final_output_type: text + is_comprehension: true + default_sampling_params: {temperature: 0.4, top_p: 0.9, top_k: 1, max_tokens: 100, + seed: 42, ignore_eos: false, detokenize: true, repetition_penalty: 1.05} +- stage_id: 1 + runtime: {devices: '1', max_batch_size: 5} + engine_args: {model_stage: talker, model_arch: Qwen3OmniMoeForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + gpu_memory_utilization: 0.5, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, + enable_prefix_caching: false, max_num_batched_tokens: 32768, max_model_len: 32768, + distributed_executor_backend: mp, hf_config_name: talker_config, load_format: dummy, + custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_async_chunk} + engine_input_source: [0] + custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker + default_sampling_params: + temperature: 0.9 + top_k: 50 + max_tokens: 1000 + seed: 42 + detokenize: false + repetition_penalty: 1.05 + stop_token_ids: [2150] +- stage_id: 2 + runtime: {devices: '1', max_batch_size: 1} + engine_args: {model_stage: code2wav, model_arch: Qwen3OmniMoeForConditionalGeneration, + worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, + enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, engine_output_type: audio, + gpu_memory_utilization: 0.1, distributed_executor_backend: mp, max_num_batched_tokens: 100000, + hf_config_name: thinker_config, async_scheduling: false, load_format: dummy} + engine_input_source: [1] + final_output: true + final_output_type: audio + default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 2000, + seed: 42, detokenize: true, repetition_penalty: 1.1} +async_chunk: true diff --git a/tests/e2e/stage_configs/qwen3_omni_ci_1773653262.yaml b/tests/e2e/stage_configs/qwen3_omni_ci_1773653262.yaml new file mode 100644 index 00000000000..c763bb2c72f --- /dev/null +++ b/tests/e2e/stage_configs/qwen3_omni_ci_1773653262.yaml @@ -0,0 +1,45 @@ +stage_args: +- stage_id: 0 + runtime: {devices: '0', max_batch_size: 5} + engine_args: {model_stage: thinker, model_arch: Qwen3OmniMoeForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + gpu_memory_utilization: 0.9, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, + distributed_executor_backend: mp, max_num_batched_tokens: 32768, max_model_len: 32768, + enable_prefix_caching: false, mm_processor_cache_gb: 0, hf_config_name: thinker_config, + tensor_parallel_size: 1, load_format: dummy, custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_async_chunk} + final_output: true + final_output_type: text + is_comprehension: true + default_sampling_params: {temperature: 0.4, top_p: 0.9, top_k: 1, max_tokens: 100, + seed: 42, ignore_eos: false, detokenize: true, repetition_penalty: 1.05} +- stage_id: 1 + runtime: {devices: '1', max_batch_size: 5} + engine_args: {model_stage: talker, model_arch: Qwen3OmniMoeForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + gpu_memory_utilization: 0.5, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, + enable_prefix_caching: false, max_num_batched_tokens: 32768, max_model_len: 32768, + distributed_executor_backend: mp, hf_config_name: talker_config, load_format: dummy, + custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_async_chunk} + engine_input_source: [0] + custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker + default_sampling_params: + temperature: 0.9 + top_k: 50 + max_tokens: 1000 + seed: 42 + detokenize: false + repetition_penalty: 1.05 + stop_token_ids: [2150] +- stage_id: 2 + runtime: {devices: '1', max_batch_size: 1} + engine_args: {model_stage: code2wav, model_arch: Qwen3OmniMoeForConditionalGeneration, + worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, + enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, engine_output_type: audio, + gpu_memory_utilization: 0.1, distributed_executor_backend: mp, max_num_batched_tokens: 100000, + hf_config_name: thinker_config, async_scheduling: false, load_format: dummy} + engine_input_source: [1] + final_output: true + final_output_type: audio + default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 2000, + seed: 42, detokenize: true, repetition_penalty: 1.1} +async_chunk: true From 8e81e60d325a68184211cbdc74b2e4f6a14ecbe5 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Mon, 16 Mar 2026 10:01:18 +0000 Subject: [PATCH 28/47] Refactor Dockerfile.ci to install vLLM from source using precompiled artifacts at a specified commit. This change replaces the previous wheel installation method and ensures the correct versioning of dependencies. Signed-off-by: tzhouam Signed-off-by: tzhouam --- docker/Dockerfile.ci | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index 5a3358343a2..cb301f898fa 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -11,20 +11,20 @@ RUN apt-get update && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* -# Install vLLM wheel for the specific commit -# URL format: https://wheels.vllm.ai// -# Current vLLM wheel commit: 097eb544e9a22810c9b7a59e586b61627b308362 -# Note: --prerelease=allow is required because nightly/commit wheels use -# pre-release version numbers (e.g. 2.10.0.dev...) which uv ignores by default, -# causing it to fall back to the older stable release from PyPI. -RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/2754231ba3a72f41e62922d1552c33e8f3f6a9d1 +# Install vLLM from source at the selected commit using precompiled artifacts +ARG VLLM_COMMIT=2754231ba3a72f41e62922d1552c33e8f3f6a9d1 +RUN git clone https://github.com/vllm-project/vllm.git /workspace/vllm && \ + cd /workspace/vllm && \ + git checkout ${VLLM_COMMIT} && \ + (uv pip uninstall --system -y vllm || true) && \ + VLLM_USE_PRECOMPILED=1 uv pip install --system -e . && \ + cd ${APP_DIR} && \ + uv pip install --system ".[dev]" && \ + uv pip install --system --upgrade \ + "flashinfer-cubin==0.6.4" \ + "nvidia-cublas-cu12==12.9.1.4" \ + "numpy==2.2.6" -RUN uv pip install --system --no-cache-dir ".[dev]" - -RUN uv pip install --system --upgrade \ - "flashinfer-cubin==0.6.4" \ - "nvidia-cublas-cu12==12.9.1.4" \ - "numpy==2.2.6" RUN uv pip install --system --upgrade \ "flashinfer-jit-cache==0.6.4" \ --index-url https://flashinfer.ai/whl/cu129 From 22a2bd3406f8004820b2b9c8849db699c565cc12 Mon Sep 17 00:00:00 2001 From: Zhou Taichang Date: Mon, 16 Mar 2026 19:31:03 +0800 Subject: [PATCH 29/47] Upgrade flashinfer-cubin and flashinfer-jit-cache versions Signed-off-by: Zhou Taichang --- docker/Dockerfile.ci | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index cb301f898fa..1397c8f90c2 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -21,12 +21,12 @@ RUN git clone https://github.com/vllm-project/vllm.git /workspace/vllm && \ cd ${APP_DIR} && \ uv pip install --system ".[dev]" && \ uv pip install --system --upgrade \ - "flashinfer-cubin==0.6.4" \ + "flashinfer-cubin==0.6.6" \ "nvidia-cublas-cu12==12.9.1.4" \ "numpy==2.2.6" RUN uv pip install --system --upgrade \ - "flashinfer-jit-cache==0.6.4" \ + "flashinfer-jit-cache==0.6.6" \ --index-url https://flashinfer.ai/whl/cu129 RUN ln -sf /usr/bin/python3 /usr/bin/python From 83b05ce3e450c07b5bf5daaf17c653ff766c3654 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 17 Mar 2026 01:35:12 +0000 Subject: [PATCH 30/47] Refactor Dockerfile.ci to install vLLM using a precompiled wheel from a specified commit, replacing the previous source installation method. This update simplifies the installation process and ensures correct dependency management. Signed-off-by: Zhou Taichang Signed-off-by: tzhouam --- docker/Dockerfile.ci | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index 1397c8f90c2..584034531c8 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -11,16 +11,15 @@ RUN apt-get update && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* +RUN uv pip uninstall --system -y vllm || true + # Install vLLM from source at the selected commit using precompiled artifacts -ARG VLLM_COMMIT=2754231ba3a72f41e62922d1552c33e8f3f6a9d1 -RUN git clone https://github.com/vllm-project/vllm.git /workspace/vllm && \ - cd /workspace/vllm && \ - git checkout ${VLLM_COMMIT} && \ - (uv pip uninstall --system -y vllm || true) && \ - VLLM_USE_PRECOMPILED=1 uv pip install --system -e . && \ - cd ${APP_DIR} && \ - uv pip install --system ".[dev]" && \ - uv pip install --system --upgrade \ +ENV VLLM_PRECOMPILED_WHEEL_COMMIT=2754231ba3a72f41e62922d1552c33e8f3f6a9d1 +RUN uv pip install --system --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/${VLLM_PRECOMPILED_WHEEL_COMMIT} + +RUN uv pip install --system ".[dev]" + +RUN uv pip install --system --upgrade \ "flashinfer-cubin==0.6.6" \ "nvidia-cublas-cu12==12.9.1.4" \ "numpy==2.2.6" From b0e36f9f5d825d22ae78ddb99d9c938a67e43061 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 17 Mar 2026 02:04:51 +0000 Subject: [PATCH 31/47] Update Dockerfile.ci to force reinstall vLLM from a precompiled wheel, ensuring the latest version is used. This change enhances the installation process by guaranteeing that any existing installations are replaced. Signed-off-by: Zhou Taichang Signed-off-by: tzhouam --- docker/Dockerfile.ci | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index 584034531c8..2306dc48c1c 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -15,7 +15,7 @@ RUN uv pip uninstall --system -y vllm || true # Install vLLM from source at the selected commit using precompiled artifacts ENV VLLM_PRECOMPILED_WHEEL_COMMIT=2754231ba3a72f41e62922d1552c33e8f3f6a9d1 -RUN uv pip install --system --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/${VLLM_PRECOMPILED_WHEEL_COMMIT} +RUN uv pip install --system --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/${VLLM_PRECOMPILED_WHEEL_COMMIT} RUN uv pip install --system ".[dev]" From ac937b82e62ec55eab44bdf32714dc1b40b47efe Mon Sep 17 00:00:00 2001 From: Zhou Taichang Date: Tue, 17 Mar 2026 11:08:45 +0800 Subject: [PATCH 32/47] Refactor Dockerfile to install vLLM from source Signed-off-by: Zhou Taichang --- docker/Dockerfile.ci | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index 2306dc48c1c..9ba24f2a503 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -15,11 +15,14 @@ RUN uv pip uninstall --system -y vllm || true # Install vLLM from source at the selected commit using precompiled artifacts ENV VLLM_PRECOMPILED_WHEEL_COMMIT=2754231ba3a72f41e62922d1552c33e8f3f6a9d1 -RUN uv pip install --system --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/${VLLM_PRECOMPILED_WHEEL_COMMIT} - -RUN uv pip install --system ".[dev]" - -RUN uv pip install --system --upgrade \ +RUN git clone https://github.com/vllm-project/vllm.git /workspace/vllm && \ + cd /workspace/vllm && \ + git checkout ${VLLM_PRECOMPILED_WHEEL_COMMIT} && \ + (uv pip uninstall --system -y vllm || true) && \ + VLLM_USE_PRECOMPILED=1 uv pip install --system -e . && \ + cd ${APP_DIR} && \ + uv pip install --system ".[dev]" && \ + uv pip install --system --upgrade \ "flashinfer-cubin==0.6.6" \ "nvidia-cublas-cu12==12.9.1.4" \ "numpy==2.2.6" From 722ad6bbaea83ace6478c7c68800033fd76c9c8f Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 17 Mar 2026 03:26:42 +0000 Subject: [PATCH 33/47] rebase: align vllm-omni with vLLM 0a0a1a198be8 Signed-off-by: tzhouam --- docker/Dockerfile.ci | 15 +++--- .../qwen2_5_omni_ci_1773717276.yaml | 48 +++++++++++++++++++ .../qwen2_5_omni_ci_1773717938.yaml | 48 +++++++++++++++++++ .../qwen3_omni_ci_1773717276.yaml | 45 +++++++++++++++++ .../qwen3_omni_ci_1773717938.yaml | 45 +++++++++++++++++ .../qwen2_5_omni/qwen2_5_omni_thinker.py | 14 ------ .../qwen3_omni/qwen3_omni_moe_thinker.py | 26 ++-------- 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 tests/e2e/stage_configs/qwen2_5_omni_ci_1773717276.yaml create mode 100644 tests/e2e/stage_configs/qwen2_5_omni_ci_1773717938.yaml create mode 100644 tests/e2e/stage_configs/qwen3_omni_ci_1773717276.yaml create mode 100644 tests/e2e/stage_configs/qwen3_omni_ci_1773717938.yaml diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index 2306dc48c1c..15999c8fb44 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -14,12 +14,15 @@ RUN apt-get update && \ RUN uv pip uninstall --system -y vllm || true # Install vLLM from source at the selected commit using precompiled artifacts -ENV VLLM_PRECOMPILED_WHEEL_COMMIT=2754231ba3a72f41e62922d1552c33e8f3f6a9d1 -RUN uv pip install --system --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/${VLLM_PRECOMPILED_WHEEL_COMMIT} - -RUN uv pip install --system ".[dev]" - -RUN uv pip install --system --upgrade \ +ENV VLLM_PRECOMPILED_WHEEL_COMMIT=0a0a1a198be88e1782b52fa31738896468200a76 +RUN git clone https://github.com/vllm-project/vllm.git /workspace/vllm && \ + cd /workspace/vllm && \ + git checkout ${VLLM_PRECOMPILED_WHEEL_COMMIT} && \ + (uv pip uninstall --system -y vllm || true) && \ + VLLM_USE_PRECOMPILED=1 uv pip install --system -e . && \ + cd ${APP_DIR} && \ + uv pip install --system ".[dev]" && \ + uv pip install --system --upgrade \ "flashinfer-cubin==0.6.6" \ "nvidia-cublas-cu12==12.9.1.4" \ "numpy==2.2.6" diff --git a/tests/e2e/stage_configs/qwen2_5_omni_ci_1773717276.yaml b/tests/e2e/stage_configs/qwen2_5_omni_ci_1773717276.yaml new file mode 100644 index 00000000000..b68bdb71a0b --- /dev/null +++ b/tests/e2e/stage_configs/qwen2_5_omni_ci_1773717276.yaml @@ -0,0 +1,48 @@ +stage_args: +- stage_id: 0 + runtime: {process: true, devices: '0', max_batch_size: 1} + engine_args: {model_stage: thinker, model_arch: Qwen2_5OmniForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.9, + skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, engine_output_type: latent, + enable_prefix_caching: false, mm_processor_cache_gb: 0} + is_comprehension: true + final_output: true + final_output_type: text + default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 128, + seed: 42, detokenize: true, repetition_penalty: 1.1} +- stage_id: 1 + runtime: {process: true, devices: '1', max_batch_size: 1} + engine_args: {model_stage: talker, model_arch: Qwen2_5OmniForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.4, + skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, enable_prefix_caching: false, + engine_output_type: latent} + engine_input_source: [0] + custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen2_5_omni.thinker2talker + default_sampling_params: + temperature: 0.9 + top_p: 0.8 + top_k: 40 + max_tokens: 4096 + seed: 42 + detokenize: true + repetition_penalty: 1.05 + stop_token_ids: [8294] +- stage_id: 2 + runtime: {process: true, devices: '1', max_batch_size: 1} + engine_args: {model_stage: code2wav, model_arch: Qwen2_5OmniForConditionalGeneration, + worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, + gpu_memory_utilization: 0.5, enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, + engine_output_type: audio, max_num_batched_tokens: 4096, max_model_len: 4096} + engine_input_source: [1] + final_output: true + final_output_type: audio + default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 4096, + seed: 42, detokenize: true, repetition_penalty: 1.1} +runtime: + enabled: true + defaults: {window_size: -1, max_inflight: 1} + edges: + - {from: 0, to: 1, window_size: -1} + - {from: 1, to: 2, window_size: -1} diff --git a/tests/e2e/stage_configs/qwen2_5_omni_ci_1773717938.yaml b/tests/e2e/stage_configs/qwen2_5_omni_ci_1773717938.yaml new file mode 100644 index 00000000000..b68bdb71a0b --- /dev/null +++ b/tests/e2e/stage_configs/qwen2_5_omni_ci_1773717938.yaml @@ -0,0 +1,48 @@ +stage_args: +- stage_id: 0 + runtime: {process: true, devices: '0', max_batch_size: 1} + engine_args: {model_stage: thinker, model_arch: Qwen2_5OmniForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.9, + skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, engine_output_type: latent, + enable_prefix_caching: false, mm_processor_cache_gb: 0} + is_comprehension: true + final_output: true + final_output_type: text + default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 128, + seed: 42, detokenize: true, repetition_penalty: 1.1} +- stage_id: 1 + runtime: {process: true, devices: '1', max_batch_size: 1} + engine_args: {model_stage: talker, model_arch: Qwen2_5OmniForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.4, + skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, enable_prefix_caching: false, + engine_output_type: latent} + engine_input_source: [0] + custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen2_5_omni.thinker2talker + default_sampling_params: + temperature: 0.9 + top_p: 0.8 + top_k: 40 + max_tokens: 4096 + seed: 42 + detokenize: true + repetition_penalty: 1.05 + stop_token_ids: [8294] +- stage_id: 2 + runtime: {process: true, devices: '1', max_batch_size: 1} + engine_args: {model_stage: code2wav, model_arch: Qwen2_5OmniForConditionalGeneration, + worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, + gpu_memory_utilization: 0.5, enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, + engine_output_type: audio, max_num_batched_tokens: 4096, max_model_len: 4096} + engine_input_source: [1] + final_output: true + final_output_type: audio + default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 4096, + seed: 42, detokenize: true, repetition_penalty: 1.1} +runtime: + enabled: true + defaults: {window_size: -1, max_inflight: 1} + edges: + - {from: 0, to: 1, window_size: -1} + - {from: 1, to: 2, window_size: -1} diff --git a/tests/e2e/stage_configs/qwen3_omni_ci_1773717276.yaml b/tests/e2e/stage_configs/qwen3_omni_ci_1773717276.yaml new file mode 100644 index 00000000000..c763bb2c72f --- /dev/null +++ b/tests/e2e/stage_configs/qwen3_omni_ci_1773717276.yaml @@ -0,0 +1,45 @@ +stage_args: +- stage_id: 0 + runtime: {devices: '0', max_batch_size: 5} + engine_args: {model_stage: thinker, model_arch: Qwen3OmniMoeForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + gpu_memory_utilization: 0.9, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, + distributed_executor_backend: mp, max_num_batched_tokens: 32768, max_model_len: 32768, + enable_prefix_caching: false, mm_processor_cache_gb: 0, hf_config_name: thinker_config, + tensor_parallel_size: 1, load_format: dummy, custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_async_chunk} + final_output: true + final_output_type: text + is_comprehension: true + default_sampling_params: {temperature: 0.4, top_p: 0.9, top_k: 1, max_tokens: 100, + seed: 42, ignore_eos: false, detokenize: true, repetition_penalty: 1.05} +- stage_id: 1 + runtime: {devices: '1', max_batch_size: 5} + engine_args: {model_stage: talker, model_arch: Qwen3OmniMoeForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + gpu_memory_utilization: 0.5, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, + enable_prefix_caching: false, max_num_batched_tokens: 32768, max_model_len: 32768, + distributed_executor_backend: mp, hf_config_name: talker_config, load_format: dummy, + custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_async_chunk} + engine_input_source: [0] + custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker + default_sampling_params: + temperature: 0.9 + top_k: 50 + max_tokens: 1000 + seed: 42 + detokenize: false + repetition_penalty: 1.05 + stop_token_ids: [2150] +- stage_id: 2 + runtime: {devices: '1', max_batch_size: 1} + engine_args: {model_stage: code2wav, model_arch: Qwen3OmniMoeForConditionalGeneration, + worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, + enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, engine_output_type: audio, + gpu_memory_utilization: 0.1, distributed_executor_backend: mp, max_num_batched_tokens: 100000, + hf_config_name: thinker_config, async_scheduling: false, load_format: dummy} + engine_input_source: [1] + final_output: true + final_output_type: audio + default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 2000, + seed: 42, detokenize: true, repetition_penalty: 1.1} +async_chunk: true diff --git a/tests/e2e/stage_configs/qwen3_omni_ci_1773717938.yaml b/tests/e2e/stage_configs/qwen3_omni_ci_1773717938.yaml new file mode 100644 index 00000000000..c763bb2c72f --- /dev/null +++ b/tests/e2e/stage_configs/qwen3_omni_ci_1773717938.yaml @@ -0,0 +1,45 @@ +stage_args: +- stage_id: 0 + runtime: {devices: '0', max_batch_size: 5} + engine_args: {model_stage: thinker, model_arch: Qwen3OmniMoeForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + gpu_memory_utilization: 0.9, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, + distributed_executor_backend: mp, max_num_batched_tokens: 32768, max_model_len: 32768, + enable_prefix_caching: false, mm_processor_cache_gb: 0, hf_config_name: thinker_config, + tensor_parallel_size: 1, load_format: dummy, custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_async_chunk} + final_output: true + final_output_type: text + is_comprehension: true + default_sampling_params: {temperature: 0.4, top_p: 0.9, top_k: 1, max_tokens: 100, + seed: 42, ignore_eos: false, detokenize: true, repetition_penalty: 1.05} +- stage_id: 1 + runtime: {devices: '1', max_batch_size: 5} + engine_args: {model_stage: talker, model_arch: Qwen3OmniMoeForConditionalGeneration, + worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, + gpu_memory_utilization: 0.5, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, + enable_prefix_caching: false, max_num_batched_tokens: 32768, max_model_len: 32768, + distributed_executor_backend: mp, hf_config_name: talker_config, load_format: dummy, + custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_async_chunk} + engine_input_source: [0] + custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker + default_sampling_params: + temperature: 0.9 + top_k: 50 + max_tokens: 1000 + seed: 42 + detokenize: false + repetition_penalty: 1.05 + stop_token_ids: [2150] +- stage_id: 2 + runtime: {devices: '1', max_batch_size: 1} + engine_args: {model_stage: code2wav, model_arch: Qwen3OmniMoeForConditionalGeneration, + worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, + enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, engine_output_type: audio, + gpu_memory_utilization: 0.1, distributed_executor_backend: mp, max_num_batched_tokens: 100000, + hf_config_name: thinker_config, async_scheduling: false, load_format: dummy} + engine_input_source: [1] + final_output: true + final_output_type: audio + default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 2000, + seed: 42, detokenize: true, repetition_penalty: 1.1} +async_chunk: true diff --git a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py index 5c69fca66c4..0307034089c 100644 --- a/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py +++ b/vllm_omni/model_executor/models/qwen2_5_omni/qwen2_5_omni_thinker.py @@ -58,10 +58,6 @@ MultiModalKwargsItems, ) from vllm.multimodal.parse import MultiModalDataItems -from vllm.multimodal.processing import ( - ProcessorInputs, - TimingContext, -) from vllm.multimodal.processing.processor import ( MultiModalPromptUpdates, PlaceholderFeaturesInfo, @@ -80,16 +76,6 @@ class Qwen2_5OmniThinkerMultiModalProcessor( ): """Override to fix use_audio_in_video detection when mm cache returns None.""" - def _cached_apply_hf_processor( - self, - inputs: ProcessorInputs, - timing_ctx: TimingContext, - ): - mm_processor_kwargs = inputs.hf_processor_mm_kwargs - if mm_processor_kwargs.get("use_audio_in_video", False): - return self._apply_hf_processor(inputs, timing_ctx) - return super()._cached_apply_hf_processor(inputs, timing_ctx) - def _maybe_apply_prompt_updates( self, mm_items: MultiModalDataItems, diff --git a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py index 5111fe1d492..84d8cbaced3 100644 --- a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py +++ b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py @@ -498,19 +498,13 @@ def _maybe_apply_prompt_updates( mm_item_counts, ) else: - if use_audio_in_video: - # When use_audio_in_video=True, audio is extracted from video and embedded - # in the video placeholder tokens. We should: - # 1. Filter out audio from prompt updates (audio has no separate placeholder) - # 2. Apply remaining updates (video, image, etc.) - # 3. Derive audio placeholders from video placeholders + if use_audio_in_video and "audio" in mm_prompt_updates: filtered_updates = {k: v for k, v in mm_prompt_updates.items() if k != "audio"} prompt_ids, mm_placeholders = self._apply_prompt_updates( prompt_ids, filtered_updates, ) - # Derive audio placeholders from video placeholders - mm_placeholders = self._derive_audio_from_video_placeholders(mm_placeholders, mm_item_counts) + mm_placeholders = self._derive_audio_from_video_placeholders(mm_placeholders, mm_prompt_updates) else: prompt_ids, mm_placeholders = self._apply_prompt_updates( prompt_ids, @@ -629,7 +623,7 @@ def get_replacement_qwen2_vision(item_idx: int, modality: str): def get_replacement_qwen2_use_audio_in_video(item_idx: int): nonlocal audio_in_video_item_idx - audio_num_features = audio_output_lengths[audio_in_video_item_idx + item_idx] + audio_num_features = audio_output_lengths[audio_in_video_item_idx] video_grid_thw = out_mm_data["video_grid_thw"][item_idx] audio_in_video_item_idx += 1 @@ -675,27 +669,17 @@ def get_replacement_qwen2_use_audio_in_video(item_idx: int): def _derive_audio_from_video_placeholders( self, placeholders: Mapping[str, list[PlaceholderFeaturesInfo]], - mm_item_counts: Mapping[str, int], + mm_prompt_updates: MultiModalPromptUpdates, ) -> Mapping[str, list[PlaceholderFeaturesInfo]]: """ Helper to derive audio placeholders from video placeholders when use_audio_in_video=True. - - In use_audio_in_video mode, audio is extracted from video and embedded - within the video placeholder tokens. This function creates audio placeholder - info by extracting the audio token positions from video placeholders. - - Args: - placeholders: Current placeholders (should contain "video") - mm_item_counts: Counts of multimodal items from mm_items.get_all_counts() """ if "video" not in placeholders: return placeholders - # Validate audio and video counts match - # In use_audio_in_video mode, audio count comes from mm_items (extracted from video) num_videos = len(placeholders["video"]) - num_audios = mm_item_counts.get("audio", 0) + num_audios = len(mm_prompt_updates.get("audio", [])) if num_audios != num_videos: raise ValueError( f"use_audio_in_video requires equal number of audio and video items, got {num_audios=}, {num_videos=}" From 83effe39ac152904c1aeab1b882a154a6e2c4dc9 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 17 Mar 2026 04:07:50 +0000 Subject: [PATCH 34/47] Update Dockerfile.ci to include an additional extra index URL for pip installation, enhancing package resolution for vLLM. This change ensures compatibility with both precompiled wheels and packages from PyPI. Signed-off-by: tzhouam --- docker/Dockerfile.ci | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index 08ce54b566f..e5182e0763d 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -15,7 +15,9 @@ RUN uv pip uninstall --system -y vllm || true # Install vLLM from source at the selected commit using precompiled artifacts ENV VLLM_PRECOMPILED_WHEEL_COMMIT=0a0a1a198be88e1782b52fa31738896468200a76 -RUN uv pip install --system --force-reinstall --prerelease=allow vllm --extra-index-url https://wheels.vllm.ai/${VLLM_PRECOMPILED_WHEEL_COMMIT} +RUN uv pip install --system --force-reinstall --prerelease=allow vllm \ + --index-url https://wheels.vllm.ai/${VLLM_PRECOMPILED_WHEEL_COMMIT} \ + --extra-index-url https://pypi.org/simple/ RUN uv pip install --system ".[dev]" From 35b3dd638abe7d64fe1116e2c2ad293cc401a746 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 17 Mar 2026 06:08:59 +0000 Subject: [PATCH 35/47] Refactor Dockerfile.ci to install vLLM from a dynamically retrieved precompiled wheel URL, improving the installation process by ensuring compatibility with PEP 440 local version identifiers. This change enhances the reliability of vLLM installations. Signed-off-by: tzhouam Signed-off-by: tzhouam --- docker/Dockerfile.ci | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index e5182e0763d..c56e9e83fcf 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -13,11 +13,16 @@ RUN apt-get update && \ RUN uv pip uninstall --system -y vllm || true -# Install vLLM from source at the selected commit using precompiled artifacts +# Install vLLM from precompiled wheel at the selected commit. +# Must use direct URL because the wheel has a PEP 440 local version identifier +# (e.g. +g0a0a1a198) which pip/uv refuse to install from a PEP 503 package index. ENV VLLM_PRECOMPILED_WHEEL_COMMIT=0a0a1a198be88e1782b52fa31738896468200a76 -RUN uv pip install --system --force-reinstall --prerelease=allow vllm \ - --index-url https://wheels.vllm.ai/${VLLM_PRECOMPILED_WHEEL_COMMIT} \ - --extra-index-url https://pypi.org/simple/ +RUN VLLM_WHEEL_URL=$(python3 -c "import urllib.request,re; \ + html=urllib.request.urlopen('https://wheels.vllm.ai/${VLLM_PRECOMPILED_WHEEL_COMMIT}/vllm/').read().decode(); \ + m=re.search(r'>(\S+x86_64\.whl)<',html); \ + print('https://wheels.vllm.ai/${VLLM_PRECOMPILED_WHEEL_COMMIT}/'+m.group(1).replace('+','%2B'))") && \ + echo "Installing vLLM from: ${VLLM_WHEEL_URL}" && \ + uv pip install --system --force-reinstall "${VLLM_WHEEL_URL}" RUN uv pip install --system ".[dev]" From cc4ade4ba797b689721fbce539b30a24a895bc9b Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 17 Mar 2026 08:19:56 +0000 Subject: [PATCH 36/47] Refactor shutdown logic in OmniLLM and stage worker to ensure proper engine closure. The shutdown method now checks for the presence of engine_core before invoking shutdown, enhancing resource management. Additionally, the stage worker now explicitly calls stage_engine.close() upon receiving a shutdown signal. This improves the overall stability and reliability of the shutdown process. Signed-off-by: tzhouam --- vllm_omni/entrypoints/omni_llm.py | 6 +++--- vllm_omni/entrypoints/omni_stage.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/vllm_omni/entrypoints/omni_llm.py b/vllm_omni/entrypoints/omni_llm.py index e06e50c83ad..f7880eb8a52 100644 --- a/vllm_omni/entrypoints/omni_llm.py +++ b/vllm_omni/entrypoints/omni_llm.py @@ -217,10 +217,10 @@ def close(self) -> None: Note: Stage management is now handled by Omni class. This method closes the LLM engine but not stages. """ - # Close the LLM engine if it exists if hasattr(self, "llm_engine") and self.llm_engine is not None: - if hasattr(self.llm_engine, "shutdown"): - self.llm_engine.shutdown() + engine_core = getattr(self.llm_engine, "engine_core", None) + if engine_core is not None and hasattr(engine_core, "shutdown"): + engine_core.shutdown() def __del__(self) -> None: # best-effort try: diff --git a/vllm_omni/entrypoints/omni_stage.py b/vllm_omni/entrypoints/omni_stage.py index b40311d1918..1f14d0ab4c4 100644 --- a/vllm_omni/entrypoints/omni_stage.py +++ b/vllm_omni/entrypoints/omni_stage.py @@ -968,6 +968,7 @@ def handle_profiler_task_local(task_type: OmniStageTaskType) -> dict: task_type = task.get("type", OmniStageTaskType.GENERATE) if task_type == OmniStageTaskType.SHUTDOWN: logger.info("Received shutdown signal") + stage_engine.close() break # Handle profiler control commands From 004c60a0317a99e6439238fad956fa6dac25ff15 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Tue, 17 Mar 2026 09:33:28 +0000 Subject: [PATCH 37/47] Refactor shutdown and cleanup logic in OmniRunner and OmniStage to improve process termination and resource management. The changes ensure that stage workers are stopped before closing queues, preventing potential orphaned subprocesses. Additionally, enhanced error handling during process termination provides clearer logging for failed shutdown attempts. This update aims to increase the reliability and stability of the shutdown process across the application. Signed-off-by: tzhouam --- tests/conftest.py | 27 ++++++++++--- vllm_omni/entrypoints/async_omni.py | 16 ++++---- vllm_omni/entrypoints/omni.py | 16 ++++---- vllm_omni/entrypoints/omni_stage.py | 59 +++++++++++++++++++---------- 4 files changed, 75 insertions(+), 43 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0a3c350d75e..e65df3d49a6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1911,6 +1911,7 @@ def generate_multimodal( def _cleanup_process(self): try: keywords = ["enginecore"] + matched = [] for proc in psutil.process_iter(["pid", "name", "cmdline", "username"]): try: @@ -1923,16 +1924,32 @@ def _cleanup_process(self): if is_process: print(f"Found vllm process: PID={proc.pid}, cmd={cmdline[:100]}") + matched.append(proc) + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass - try: - proc.terminate() - time.sleep(2) - except Exception: - proc.kill() + for proc in matched: + try: + proc.terminate() + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + _, still_alive = psutil.wait_procs(matched, timeout=5) + for proc in still_alive: + try: + proc.kill() except (psutil.NoSuchProcess, psutil.AccessDenied): pass + if still_alive: + _, stubborn = psutil.wait_procs(still_alive, timeout=3) + if stubborn: + print(f"Warning: failed to kill residual vllm pids: {[p.pid for p in stubborn]}") + else: + print(f"Force-killed residual vllm pids: {[p.pid for p in still_alive]}") + elif matched: + print(f"Terminated vllm pids: {[p.pid for p in matched]}") + except Exception as e: print(f"Error in psutil vllm cleanup: {e}") diff --git a/vllm_omni/entrypoints/async_omni.py b/vllm_omni/entrypoints/async_omni.py index 474834d16a3..d2501f0a54e 100644 --- a/vllm_omni/entrypoints/async_omni.py +++ b/vllm_omni/entrypoints/async_omni.py @@ -24,7 +24,7 @@ from vllm_omni.entrypoints.client_request_state import ClientRequestState from vllm_omni.entrypoints.omni import OmniBase from vllm_omni.entrypoints.omni_stage import OmniStage -from vllm_omni.entrypoints.stage_utils import SHUTDOWN_TASK, OmniStageTaskType +from vllm_omni.entrypoints.stage_utils import OmniStageTaskType from vllm_omni.entrypoints.stage_utils import maybe_load_from_ipc as _load from vllm_omni.entrypoints.utils import ( get_final_stage_id_for_e2e, @@ -51,11 +51,14 @@ def _weak_close_cleanup_async( except Exception as e: logger.warning("Failed to close inline diffusion engine: %s", e) if stage_list: - for q in stage_in_queues: + # Stop workers first, then close queues. Closing queues early can + # prevent workers from receiving shutdown tasks. + for stage in stage_list: try: - q.put_nowait(SHUTDOWN_TASK) + stage.stop_stage_worker() except Exception as e: - logger.warning(f"Failed to send shutdown signal to stage input queue: {e}") + logger.warning(f"Failed to stop stage worker: {e}") + for q in stage_in_queues: close_fn = getattr(q, "close", None) if callable(close_fn): close_fn() @@ -63,11 +66,6 @@ def _weak_close_cleanup_async( close_fn = getattr(q, "close", None) if callable(close_fn): close_fn() - for stage in stage_list: - try: - stage.stop_stage_worker() - except Exception as e: - logger.warning(f"Failed to stop stage worker: {e}") try_close_ray(ray_pg) # Cancel output handler if output_handler is not None: diff --git a/vllm_omni/entrypoints/omni.py b/vllm_omni/entrypoints/omni.py index 488b986d8ee..c91f4a7e1b0 100644 --- a/vllm_omni/entrypoints/omni.py +++ b/vllm_omni/entrypoints/omni.py @@ -38,7 +38,7 @@ ) from vllm_omni.entrypoints.cfg_companion_tracker import CfgCompanionTracker from vllm_omni.entrypoints.omni_stage import OmniStage -from vllm_omni.entrypoints.stage_utils import SHUTDOWN_TASK, OmniStageTaskType +from vllm_omni.entrypoints.stage_utils import OmniStageTaskType from vllm_omni.entrypoints.stage_utils import maybe_load_from_ipc as _load from vllm_omni.entrypoints.utils import ( filter_dataclass_kwargs, @@ -72,11 +72,14 @@ def _weak_close_cleanup( ): """Weak reference cleanup function for OmniBase instances.""" if stage_list: - for q in stage_in_queues: + # Stop workers first, then close queues. Closing queues too early can + # drop shutdown signals and leave engine subprocesses orphaned. + for stage in stage_list: try: - q.put_nowait(SHUTDOWN_TASK) + stage.stop_stage_worker() except Exception as e: - logger.warning(f"Failed to send shutdown signal to stage input queue: {e}") + logger.warning(f"Failed to stop stage worker: {e}") + for q in stage_in_queues: close_fn = getattr(q, "close", None) if callable(close_fn): close_fn() @@ -84,11 +87,6 @@ def _weak_close_cleanup( close_fn = getattr(q, "close", None) if callable(close_fn): close_fn() - for stage in stage_list: - try: - stage.stop_stage_worker() - except Exception as e: - logger.warning(f"Failed to stop stage worker: {e}") try_close_ray(ray_pg) # Gracefully shutdown handshake server thread diff --git a/vllm_omni/entrypoints/omni_stage.py b/vllm_omni/entrypoints/omni_stage.py index 1f14d0ab4c4..e556782ca5b 100644 --- a/vllm_omni/entrypoints/omni_stage.py +++ b/vllm_omni/entrypoints/omni_stage.py @@ -616,28 +616,17 @@ def stop_stage_worker(self) -> None: If graceful shutdown fails, forcefully terminates the process. Handles both multiprocessing Process and Ray Actor. """ - if self._in_q is not None: + in_q = self._in_q + out_q = self._out_q + + if in_q is not None: try: - self._in_q.put_nowait(SHUTDOWN_TASK) + # Prefer a blocking put to reduce the chance that shutdown + # is dropped when queue/socket buffers are momentarily full. + in_q.put(SHUTDOWN_TASK) except Exception as e: - # Queue may already be closed by the weak-ref cleanup - # (e.g. ZmqQueue socket closed) — this is expected. + # Queue may already be closed by cleanup paths. logger.debug("Failed to send shutdown to in_q: %s", e) - close_fn = getattr(self._in_q, "close", None) - if callable(close_fn): - try: - close_fn() - except Exception: - pass - self._in_q = None - if self._out_q is not None: - close_fn = getattr(self._out_q, "close", None) - if callable(close_fn): - try: - close_fn() - except Exception: - pass - self._out_q = None if self._ray_actor is not None: kill_ray_actor(self._ray_actor) @@ -645,7 +634,7 @@ def stop_stage_worker(self) -> None: self._ray_task_ref = None elif self._proc is not None: try: - self._proc.join(timeout=5) + self._proc.join(timeout=10) except Exception as e: logger.debug("join() failed: %s", e) if self._proc.is_alive(): @@ -653,6 +642,36 @@ def stop_stage_worker(self) -> None: self._proc.terminate() except Exception as e: logger.warning("terminate() failed: %s", e) + try: + self._proc.join(timeout=5) + except Exception as e: + logger.debug("join() after terminate failed: %s", e) + if self._proc.is_alive(): + try: + self._proc.kill() + except Exception as e: + logger.warning("kill() failed: %s", e) + try: + self._proc.join(timeout=3) + except Exception as e: + logger.debug("join() after kill failed: %s", e) + + if in_q is not None: + close_fn = getattr(in_q, "close", None) + if callable(close_fn): + try: + close_fn() + except Exception: + pass + if out_q is not None: + close_fn = getattr(out_q, "close", None) + if callable(close_fn): + try: + close_fn() + except Exception: + pass + self._in_q = None + self._out_q = None def submit(self, payload: dict[str, Any]) -> None: """Submit a task to the stage worker. From 8a31e7865d6ef8fa2c6c1c1b8bef29a5c9046c35 Mon Sep 17 00:00:00 2001 From: Taichang Zhou Date: Tue, 17 Mar 2026 23:43:46 +0800 Subject: [PATCH 38/47] Increase timeout for Diffusion Model Test and Diffusion Images API LoRA E2E in Buildkite configurations to 30 minutes. Also, introduce a new timeout constant for diffusion initialization in relevant test files to enhance stability during testing. Update OmniServer initialization to utilize the new timeout settings. --- .buildkite/test-amd.yaml | 4 ++-- .buildkite/test-merge.yml | 4 ++-- .buildkite/test-ready.yml | 2 +- tests/e2e/offline_inference/test_diffusion_lora.py | 7 ++++++- .../online_serving/test_images_generations_lora.py | 13 ++++++++++++- .../models/qwen3_omni/qwen3_omni_moe_thinker.py | 1 + 6 files changed, 24 insertions(+), 7 deletions(-) diff --git a/.buildkite/test-amd.yaml b/.buildkite/test-amd.yaml index f6354758b40..9b84ffec456 100644 --- a/.buildkite/test-amd.yaml +++ b/.buildkite/test-amd.yaml @@ -1,7 +1,7 @@ steps: - label: "Diffusion Model Test" - timeout_in_minutes: 20 + timeout_in_minutes: 30 agent_pool: mi325_2 depends_on: amd-build mirror_hardwares: [amdproduction] @@ -11,7 +11,7 @@ steps: - pytest -s -v tests/e2e/offline_inference/test_t2i_model.py - label: "Diffusion Images API LoRA E2E" - timeout_in_minutes: 20 + timeout_in_minutes: 30 agent_pool: mi325_1 depends_on: amd-build mirror_hardwares: [amdproduction] diff --git a/.buildkite/test-merge.yml b/.buildkite/test-merge.yml index 5479f8ac1e8..600a5d4dbe9 100644 --- a/.buildkite/test-merge.yml +++ b/.buildkite/test-merge.yml @@ -16,7 +16,7 @@ steps: - "/fsx/hf_cache:/fsx/hf_cache" - label: "Diffusion Model Test" - timeout_in_minutes: 20 + timeout_in_minutes: 30 depends_on: upload-merge-pipeline commands: - pytest -s -v tests/e2e/offline_inference/test_t2i_model.py -m "advanced_model and diffusion" --run-level "advanced_model" @@ -33,7 +33,7 @@ steps: - "/fsx/hf_cache:/fsx/hf_cache" - label: "Diffusion Images API LoRA E2E" - timeout_in_minutes: 20 + timeout_in_minutes: 30 depends_on: upload-merge-pipeline commands: - pytest -s -v tests/e2e/online_serving/test_images_generations_lora.py diff --git a/.buildkite/test-ready.yml b/.buildkite/test-ready.yml index c8579979c84..854c2eeade0 100644 --- a/.buildkite/test-ready.yml +++ b/.buildkite/test-ready.yml @@ -18,7 +18,7 @@ steps: - label: "Diffusion Model Test" depends_on: upload-ready-pipeline commands: - - timeout 20m pytest -s -v tests/e2e/offline_inference/test_t2i_model.py -m "core_model and diffusion" --run-level "core_model" + - timeout 30m pytest -s -v tests/e2e/offline_inference/test_t2i_model.py -m "core_model and diffusion" --run-level "core_model" agents: queue: "gpu_1_queue" plugins: diff --git a/tests/e2e/offline_inference/test_diffusion_lora.py b/tests/e2e/offline_inference/test_diffusion_lora.py index a95ab43da5e..64852b12dcf 100644 --- a/tests/e2e/offline_inference/test_diffusion_lora.py +++ b/tests/e2e/offline_inference/test_diffusion_lora.py @@ -24,6 +24,7 @@ # This test is specific to Z-Image LoRA behavior. Keep it focused on a single # model to reduce runtime and avoid extra downloads. models = ["Tongyi-MAI/Z-Image-Turbo"] +DIFFUSION_INIT_TIMEOUT_S = 600 @pytest.mark.parametrize("model_name", models) @@ -76,7 +77,11 @@ def _write_zimage_lora(adapter_dir: Path) -> str: ) return str(adapter_dir) - m = Omni(model=model_name) + m = Omni( + model=model_name, + stage_init_timeout=DIFFUSION_INIT_TIMEOUT_S, + init_timeout=DIFFUSION_INIT_TIMEOUT_S, + ) try: # high resolution may cause OOM on L4 height = 256 diff --git a/tests/e2e/online_serving/test_images_generations_lora.py b/tests/e2e/online_serving/test_images_generations_lora.py index a1f1d145004..e2079c9096d 100644 --- a/tests/e2e/online_serving/test_images_generations_lora.py +++ b/tests/e2e/online_serving/test_images_generations_lora.py @@ -28,6 +28,7 @@ os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn" MODEL = "Tongyi-MAI/Z-Image-Turbo" +DIFFUSION_INIT_TIMEOUT_S = 600 PROMPT = "a photo of a cat sitting on a laptop keyboard" @@ -37,7 +38,17 @@ @pytest.fixture(scope="module") def omni_server(): - with OmniServer(MODEL, ["--num-gpus", "1"]) as server: + with OmniServer( + MODEL, + [ + "--num-gpus", + "1", + "--stage-init-timeout", + str(DIFFUSION_INIT_TIMEOUT_S), + "--init-timeout", + str(DIFFUSION_INIT_TIMEOUT_S), + ], + ) as server: yield server diff --git a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py index 84d8cbaced3..9c116b16255 100644 --- a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py +++ b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py @@ -161,6 +161,7 @@ def forward( cu_seqlens, self.hidden_size, self.tp_size, + self.device, ) cu_seqlens = torch.from_numpy(cu_seqlens).to(self.device, non_blocking=True) From 7a68bd37b7a364c40cc69d4478fa3d45ee309d99 Mon Sep 17 00:00:00 2001 From: Taichang Zhou Date: Wed, 18 Mar 2026 23:23:41 +0800 Subject: [PATCH 39/47] Refactor cu_seqlens handling in Qwen3Omni_VisionTransformer to align with upstream vLLM changes. The sequence length helper now retains cu_seqlens in numpy format and converts it for attention execution, improving compatibility and performance. --- .../models/qwen3_omni/qwen3_omni_moe_thinker.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py index 9c116b16255..6416f2e24b1 100644 --- a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py +++ b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py @@ -156,13 +156,8 @@ def forward( dtype=torch.int32, device=self.device, ) - cu_seqlens = MMEncoderAttention.maybe_recompute_cu_seqlens( - self.attn_backend, - cu_seqlens, - self.hidden_size, - self.tp_size, - self.device, - ) + # Align with upstream vLLM qwen3_omni path: keep cu_seqlens in numpy for + # sequence length helpers and convert once for attention execution. cu_seqlens = torch.from_numpy(cu_seqlens).to(self.device, non_blocking=True) hidden_states = hidden_states.unsqueeze(1) From dc6f2fb81f7e55a53727360bfbf1a3801bf6b5e3 Mon Sep 17 00:00:00 2001 From: Taichang Zhou Date: Wed, 18 Mar 2026 23:27:55 +0800 Subject: [PATCH 40/47] Enhance cu_seqlens computation in Qwen3Omni_VisionTransformer by switching to tensor operations for improved performance and compatibility. Added error handling to fallback on a vectorized implementation if necessary. This update aligns with upstream changes and optimizes the handling of sequence lengths during attention execution. --- .../qwen3_omni/qwen3_omni_moe_thinker.py | 64 +++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py index 6416f2e24b1..f6140f5949f 100644 --- a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py +++ b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py @@ -29,6 +29,7 @@ import numpy as np import torch import torch.nn as nn +import torch.nn.functional as F from packaging.version import Version from transformers import PretrainedConfig from transformers import __version__ as TRANSFORMERS_VERSION @@ -139,42 +140,67 @@ def forward( rotary_pos_emb_cos, rotary_pos_emb_sin = self.rot_pos_emb(grid_thw) if isinstance(grid_thw, torch.Tensor): - grid_thw_np = grid_thw.cpu().numpy().astype(np.int32) + grid_thw_tensor = grid_thw.to(self.device) else: - grid_thw_np = np.array(grid_thw, dtype=np.int32) - - cu_seqlens = np.repeat(grid_thw_np[:, 1] * grid_thw_np[:, 2], grid_thw_np[:, 0]).cumsum(axis=0, dtype=np.int32) - cu_seqlens = np.concatenate([np.zeros(1, dtype=np.int32), cu_seqlens]) + grid_thw_tensor = torch.as_tensor(grid_thw, dtype=torch.int32, device=self.device) + + try: + cu_seqlens = torch.repeat_interleave( + grid_thw_tensor[:, 1] * grid_thw_tensor[:, 2], + grid_thw_tensor[:, 0], + ).cumsum( + dim=0, + dtype=grid_thw_tensor.dtype if torch.jit.is_tracing() else torch.int32, + ) + cu_seqlens = F.pad(cu_seqlens, (1, 0), value=0) + except RuntimeError: + logger.warning( + "torch.repeat_interleave not executable, " + "switching to vectorized searchsorted implementation." + ) + repeat_counts = grid_thw_tensor[:, 0] + values = grid_thw_tensor[:, 1] * grid_thw_tensor[:, 2] + repeat_cumsum = repeat_counts.cumsum(0) + total_items = repeat_cumsum[-1].item() + indices = torch.searchsorted( + repeat_cumsum, + torch.arange(total_items, device=grid_thw_tensor.device), + right=True, + ) + cu_seqlens = values[indices].cumsum( + dim=0, + dtype=grid_thw_tensor.dtype if torch.jit.is_tracing() else torch.int32, + ) + cu_seqlens = F.pad(cu_seqlens, (1, 0), value=0) + hidden_states = hidden_states.unsqueeze(1) + rotary_pos_emb_cos = rotary_pos_emb_cos.to(hidden_states.device) + rotary_pos_emb_sin = rotary_pos_emb_sin.to(hidden_states.device) + max_seqlen = self.compute_attn_mask_seqlen(cu_seqlens) + + grid_thw_np = grid_thw_tensor.cpu().numpy().astype(np.int32) + cu_seqlens_np = np.repeat( + grid_thw_np[:, 1] * grid_thw_np[:, 2], grid_thw_np[:, 0] + ).cumsum(axis=0, dtype=np.int32) + cu_seqlens_np = np.concatenate([np.zeros(1, dtype=np.int32), cu_seqlens_np]) sequence_lengths = MMEncoderAttention.maybe_compute_seq_lens( self.attn_backend, - cu_seqlens, + cu_seqlens_np, self.device, ) - max_seqlen = torch.tensor( - MMEncoderAttention.compute_max_seqlen(self.attn_backend, cu_seqlens), - dtype=torch.int32, - device=self.device, - ) - # Align with upstream vLLM qwen3_omni path: keep cu_seqlens in numpy for - # sequence length helpers and convert once for attention execution. - cu_seqlens = torch.from_numpy(cu_seqlens).to(self.device, non_blocking=True) - - hidden_states = hidden_states.unsqueeze(1) hidden_states_list = [] deepstack_visual_indexes = self.deepstack_visual_indexes for layer_num, blk in enumerate(self.blocks): - hidden_states = hidden_states + blk.attn( - blk.norm1(hidden_states), + hidden_states = blk( + hidden_states, cu_seqlens=cu_seqlens, rotary_pos_emb_cos=rotary_pos_emb_cos, rotary_pos_emb_sin=rotary_pos_emb_sin, max_seqlen=max_seqlen, sequence_lengths=sequence_lengths, ) - hidden_states = hidden_states + blk.mlp(blk.norm2(hidden_states)) if deepstack_visual_indexes is not None and layer_num in deepstack_visual_indexes: hidden_states_list.append(hidden_states) From 034d579d770bb39fa2fb8d2bd1caca27f1affae3 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Thu, 19 Mar 2026 05:14:34 +0000 Subject: [PATCH 41/47] Update vLLM precompiled wheel version and refactor various components for improved functionality and error handling. Changes include modifying the `get_name` method in `DiffusionQuantizationConfig` to use class-level attributes, enhancing metadata handling in `StageEngineCoreClient`, and adjusting the `AsyncOmni` class constructor for better parameter management. Additionally, improved error handling in `omni_snapshot_download` and optimized initial chunk size computation in `talker2code2wav_async_chunk` for dynamic load adaptation. Signed-off-by: tzhouam --- docker/Dockerfile.ci | 2 +- vllm_omni/diffusion/quantization/base.py | 11 ++++--- vllm_omni/engine/stage_engine_core_client.py | 33 +++++++++++-------- vllm_omni/entrypoints/async_omni.py | 21 +++++++++--- vllm_omni/entrypoints/omni_base.py | 5 +++ .../qwen3_omni/qwen3_omni_moe_thinker.py | 9 +++-- .../stage_input_processors/qwen3_tts.py | 22 +++++-------- 7 files changed, 61 insertions(+), 42 deletions(-) diff --git a/docker/Dockerfile.ci b/docker/Dockerfile.ci index c56e9e83fcf..83463687385 100644 --- a/docker/Dockerfile.ci +++ b/docker/Dockerfile.ci @@ -16,7 +16,7 @@ RUN uv pip uninstall --system -y vllm || true # Install vLLM from precompiled wheel at the selected commit. # Must use direct URL because the wheel has a PEP 440 local version identifier # (e.g. +g0a0a1a198) which pip/uv refuse to install from a PEP 503 package index. -ENV VLLM_PRECOMPILED_WHEEL_COMMIT=0a0a1a198be88e1782b52fa31738896468200a76 +ENV VLLM_PRECOMPILED_WHEEL_COMMIT=89138b21cc246ae944c741d5c399c148e2b770ab RUN VLLM_WHEEL_URL=$(python3 -c "import urllib.request,re; \ html=urllib.request.urlopen('https://wheels.vllm.ai/${VLLM_PRECOMPILED_WHEEL_COMMIT}/vllm/').read().decode(); \ m=re.search(r'>(\S+x86_64\.whl)<',html); \ diff --git a/vllm_omni/diffusion/quantization/base.py b/vllm_omni/diffusion/quantization/base.py index 17e6d32ead2..8a35547f9eb 100644 --- a/vllm_omni/diffusion/quantization/base.py +++ b/vllm_omni/diffusion/quantization/base.py @@ -31,14 +31,15 @@ class DiffusionQuantizationConfig(ABC): # The underlying vLLM config instance _vllm_config: "QuantizationConfig | None" = None - def get_name(self) -> str: + @classmethod + def get_name(cls) -> str: """Return the quantization method name (e.g., 'fp8', 'int8'). - By default, delegates to the underlying vLLM config instance. + Delegates to the underlying vLLM config class's get_name(). """ - if self._vllm_config is not None: - return self._vllm_config.get_name() - raise NotImplementedError("Subclass must initialize _vllm_config or override get_name().") + if cls.quant_config_cls is not None: + return cls.quant_config_cls.get_name() + raise NotImplementedError("Subclass must set quant_config_cls or override get_name().") def get_vllm_quant_config(self) -> "QuantizationConfig | None": """Return the underlying vLLM QuantizationConfig for linear layers.""" diff --git a/vllm_omni/engine/stage_engine_core_client.py b/vllm_omni/engine/stage_engine_core_client.py index a4e8bc0e932..284cc2d31a2 100644 --- a/vllm_omni/engine/stage_engine_core_client.py +++ b/vllm_omni/engine/stage_engine_core_client.py @@ -38,8 +38,12 @@ def __init__( self, vllm_config: Any, executor_class: type, - metadata: StageMetadata, + log_stats: bool = False, client_addresses: dict[str, str] | None = None, + client_count: int = 1, + client_index: int = 0, + *, + metadata: StageMetadata | None = None, engine_manager: Any = None, coordinator: Any = None, ): @@ -51,17 +55,18 @@ def __init__( and calls super().__init__(). """ # -------- Stage metadata (public fields used at runtime) -------- - self.stage_id = metadata.stage_id - self.stage_type = metadata.stage_type - self.engine_output_type = metadata.engine_output_type - self.is_comprehension = metadata.is_comprehension - self.requires_multimodal_data = metadata.requires_multimodal_data - self.engine_input_source = metadata.engine_input_source - self.final_output = metadata.final_output - self.final_output_type = metadata.final_output_type - self.default_sampling_params = metadata.default_sampling_params - self.custom_process_input_func = metadata.custom_process_input_func - self.model_stage = metadata.model_stage + if metadata is not None: + self.stage_id = metadata.stage_id + self.stage_type = metadata.stage_type + self.engine_output_type = metadata.engine_output_type + self.is_comprehension = metadata.is_comprehension + self.requires_multimodal_data = metadata.requires_multimodal_data + self.engine_input_source = metadata.engine_input_source + self.final_output = metadata.final_output + self.final_output_type = metadata.final_output_type + self.default_sampling_params = metadata.default_sampling_params + self.custom_process_input_func = metadata.custom_process_input_func + self.model_stage = metadata.model_stage self.engine_outputs: Any = None @@ -73,8 +78,10 @@ def __init__( super().__init__( vllm_config, executor_class, - log_stats=False, + log_stats=log_stats, client_addresses=client_addresses, + client_count=client_count, + client_index=client_index, ) if engine_manager is not None: self.resources.engine_manager = engine_manager diff --git a/vllm_omni/entrypoints/async_omni.py b/vllm_omni/entrypoints/async_omni.py index 3ba2c4a7efd..57c432ee1c1 100644 --- a/vllm_omni/entrypoints/async_omni.py +++ b/vllm_omni/entrypoints/async_omni.py @@ -29,6 +29,7 @@ if TYPE_CHECKING: from vllm.inputs.preprocess import InputPreprocessor from vllm.tokenizers import TokenizerLike + from vllm.v1.engine import PauseMode from vllm_omni.inputs.data import OmniPromptType, OmniSamplingParams @@ -66,7 +67,7 @@ class AsyncOmni(EngineClient, OmniBase): ... print(output) """ - def __init__(self, model: str, **kwargs: Any) -> None: + def __init__(self, *args: Any, model: str = "", **kwargs: Any) -> None: OmniBase.__init__(self, model=model, **kwargs) self._pause_cond: asyncio.Condition = asyncio.Condition() self._paused: bool = False @@ -130,9 +131,17 @@ def model_config(self): async def generate( self, prompt: OmniPromptType, - request_id: str, - sampling_params_list: Sequence[OmniSamplingParams] | None = None, + sampling_params: Any = None, + request_id: str = "", *, + prompt_text: str | None = None, + lora_request: Any = None, + tokenization_kwargs: dict[str, Any] | None = None, + trace_headers: Any = None, + priority: int = 0, + data_parallel_rank: int | None = None, + reasoning_ended: bool | None = None, + sampling_params_list: Sequence[OmniSamplingParams] | None = None, output_modalities: list[str] | None = None, ) -> AsyncGenerator[OmniRequestOutput, None]: """Generate outputs for the given prompt asynchronously. @@ -226,6 +235,7 @@ async def encode( trace_headers: dict[str, str] | None = None, priority: int = 0, tokenization_kwargs: dict[str, Any] | None = None, + reasoning_ended: bool | None = None, ) -> AsyncGenerator[PoolingRequestOutput, None]: """EngineClient.encode() stub. @@ -396,6 +406,7 @@ async def abort(self, request_id: str | Iterable[str]) -> None: async def pause_generation( self, *, + mode: PauseMode = "abort", wait_for_inflight_requests: bool = False, clear_cache: bool = True, ) -> None: @@ -467,7 +478,7 @@ async def reset_prefix_cache( logger.warning("[AsyncOmni] reset_prefix_cache not yet supported with Orchestrator process") return True - async def sleep(self, level: int = 1) -> None: + async def sleep(self, level: int = 1, mode: PauseMode = "abort") -> None: """Sleep all stages. Best-effort: unsupported stages will emit a TODO result. @@ -584,7 +595,7 @@ async def check_health(self) -> None: # ==================== Shutdown ==================== - def shutdown(self) -> None: + def shutdown(self, timeout: float | None = None) -> None: """Shutdown the engine.""" if self.final_output_task is not None: self.final_output_task.cancel() diff --git a/vllm_omni/entrypoints/omni_base.py b/vllm_omni/entrypoints/omni_base.py index fb5f3788dbe..af34a42213b 100644 --- a/vllm_omni/entrypoints/omni_base.py +++ b/vllm_omni/entrypoints/omni_base.py @@ -53,6 +53,11 @@ def omni_snapshot_download(model_id: str) -> str: ) except huggingface_hub.errors.RepositoryNotFoundError: logger.warning("Repository not found for '%s'.", model_id) + except PermissionError: + logger.warning( + "Permission denied when downloading '%s'. Assuming the model is already cached locally.", + model_id, + ) return model_id diff --git a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py index f6140f5949f..4693a231452 100644 --- a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py +++ b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_thinker.py @@ -155,8 +155,7 @@ def forward( cu_seqlens = F.pad(cu_seqlens, (1, 0), value=0) except RuntimeError: logger.warning( - "torch.repeat_interleave not executable, " - "switching to vectorized searchsorted implementation." + "torch.repeat_interleave not executable, switching to vectorized searchsorted implementation." ) repeat_counts = grid_thw_tensor[:, 0] values = grid_thw_tensor[:, 1] * grid_thw_tensor[:, 2] @@ -179,9 +178,9 @@ def forward( max_seqlen = self.compute_attn_mask_seqlen(cu_seqlens) grid_thw_np = grid_thw_tensor.cpu().numpy().astype(np.int32) - cu_seqlens_np = np.repeat( - grid_thw_np[:, 1] * grid_thw_np[:, 2], grid_thw_np[:, 0] - ).cumsum(axis=0, dtype=np.int32) + cu_seqlens_np = np.repeat(grid_thw_np[:, 1] * grid_thw_np[:, 2], grid_thw_np[:, 0]).cumsum( + axis=0, dtype=np.int32 + ) cu_seqlens_np = np.concatenate([np.zeros(1, dtype=np.int32), cu_seqlens_np]) sequence_lengths = MMEncoderAttention.maybe_compute_seq_lens( self.attn_backend, diff --git a/vllm_omni/model_executor/stage_input_processors/qwen3_tts.py b/vllm_omni/model_executor/stage_input_processors/qwen3_tts.py index 69724dfc09c..4db14f57a56 100644 --- a/vllm_omni/model_executor/stage_input_processors/qwen3_tts.py +++ b/vllm_omni/model_executor/stage_input_processors/qwen3_tts.py @@ -116,18 +116,12 @@ def talker2code2wav_async_chunk( initial_chunk_size = int(entry.list_data[0]) per_request_override = True - # Dynamic IC: cache per request so boundaries stay stable for its lifetime. + # Dynamic IC: recalculate each call so IC adapts to current load. if not per_request_override: - _ic_cache = getattr(transfer_manager, "_cached_ic", None) - if _ic_cache is None: - _ic_cache = {} - transfer_manager._cached_ic = _ic_cache - if request_id not in _ic_cache: - max_ic = max_ic_for_chunk_size(chunk_size) - active = sum(1 for v in transfer_manager.code_prompt_token_ids.values() if len(v) > 0) - capacity = getattr(transfer_manager, "scheduler_max_num_seqs", 1) - _ic_cache[request_id] = compute_dynamic_initial_chunk_size(active, capacity, max_ic) - initial_chunk_size = _ic_cache[request_id] + max_ic = max_ic_for_chunk_size(chunk_size) + active = sum(1 for v in transfer_manager.code_prompt_token_ids.values() if len(v) > 0) + capacity = getattr(transfer_manager, "scheduler_max_num_seqs", 1) + initial_chunk_size = compute_dynamic_initial_chunk_size(active, capacity, max_ic) if chunk_size <= 0 or left_context_size_config < 0 or initial_chunk_size < 0: raise ValueError( @@ -145,11 +139,13 @@ def talker2code2wav_async_chunk( initial_chunk_size = chunk_size length = len(transfer_manager.code_prompt_token_ids[request_id]) + finished_tensor = torch.tensor(finished, dtype=torch.bool) + if length <= 0: if finished: return { "code_predictor_codes": [], - "finished": True, + "finished": finished_tensor, } return None @@ -197,5 +193,5 @@ def talker2code2wav_async_chunk( return { "code_predictor_codes": code_predictor_codes, "left_context_size": left_context_size, - "finished": finished, + "finished": finished_tensor, } From f83df010efa161724377f0b327ac26e25661fcb9 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Thu, 19 Mar 2026 08:35:56 +0000 Subject: [PATCH 42/47] Remove redundant assignment of rope_theta in Qwen3OmniMoeTalker initialization for cleaner configuration setup. Signed-off-by: tzhouam --- .../model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py index 7b3d0b017c0..4a30ac90e15 100644 --- a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py +++ b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py @@ -100,7 +100,6 @@ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""): super().__init__() talker_config: Qwen3OmniMoeTalkerConfig = vllm_config.model_config.hf_config talker_config.text_config.rope_parameters = talker_config.text_config.rope_scaling - talker_config.text_config.rope_parameters["rope_theta"] = talker_config.text_config.rope_theta self.quant_config = vllm_config.quant_config self.prefix = prefix self.vllm_config = vllm_config From 0d23f68997f67937beac2b1c5d0b6624fa5e43e3 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Fri, 20 Mar 2026 02:11:10 +0000 Subject: [PATCH 43/47] revert changes in qwen 3 tts Signed-off-by: tzhouam --- .../stage_input_processors/qwen3_tts.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/vllm_omni/model_executor/stage_input_processors/qwen3_tts.py b/vllm_omni/model_executor/stage_input_processors/qwen3_tts.py index 4db14f57a56..69724dfc09c 100644 --- a/vllm_omni/model_executor/stage_input_processors/qwen3_tts.py +++ b/vllm_omni/model_executor/stage_input_processors/qwen3_tts.py @@ -116,12 +116,18 @@ def talker2code2wav_async_chunk( initial_chunk_size = int(entry.list_data[0]) per_request_override = True - # Dynamic IC: recalculate each call so IC adapts to current load. + # Dynamic IC: cache per request so boundaries stay stable for its lifetime. if not per_request_override: - max_ic = max_ic_for_chunk_size(chunk_size) - active = sum(1 for v in transfer_manager.code_prompt_token_ids.values() if len(v) > 0) - capacity = getattr(transfer_manager, "scheduler_max_num_seqs", 1) - initial_chunk_size = compute_dynamic_initial_chunk_size(active, capacity, max_ic) + _ic_cache = getattr(transfer_manager, "_cached_ic", None) + if _ic_cache is None: + _ic_cache = {} + transfer_manager._cached_ic = _ic_cache + if request_id not in _ic_cache: + max_ic = max_ic_for_chunk_size(chunk_size) + active = sum(1 for v in transfer_manager.code_prompt_token_ids.values() if len(v) > 0) + capacity = getattr(transfer_manager, "scheduler_max_num_seqs", 1) + _ic_cache[request_id] = compute_dynamic_initial_chunk_size(active, capacity, max_ic) + initial_chunk_size = _ic_cache[request_id] if chunk_size <= 0 or left_context_size_config < 0 or initial_chunk_size < 0: raise ValueError( @@ -139,13 +145,11 @@ def talker2code2wav_async_chunk( initial_chunk_size = chunk_size length = len(transfer_manager.code_prompt_token_ids[request_id]) - finished_tensor = torch.tensor(finished, dtype=torch.bool) - if length <= 0: if finished: return { "code_predictor_codes": [], - "finished": finished_tensor, + "finished": True, } return None @@ -193,5 +197,5 @@ def talker2code2wav_async_chunk( return { "code_predictor_codes": code_predictor_codes, "left_context_size": left_context_size, - "finished": finished_tensor, + "finished": finished, } From b8f736c5a9b18eea9b1bfdb982397d8abb2363b3 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Fri, 20 Mar 2026 02:11:18 +0000 Subject: [PATCH 44/47] Remove obsolete image and YAML configuration files for Qwen2.5 and Qwen3 models from the e2e stage configurations. Signed-off-by: tzhouam --- image_output.png | Bin 113037 -> 0 bytes .../qwen2_5_omni_ci_1773652593.yaml | 48 ------------------ .../qwen2_5_omni_ci_1773653262.yaml | 48 ------------------ .../qwen2_5_omni_ci_1773717276.yaml | 48 ------------------ .../qwen2_5_omni_ci_1773717938.yaml | 48 ------------------ .../qwen3_omni_ci_1773652593.yaml | 45 ---------------- .../qwen3_omni_ci_1773653262.yaml | 45 ---------------- .../qwen3_omni_ci_1773717276.yaml | 45 ---------------- .../qwen3_omni_ci_1773717938.yaml | 45 ---------------- 9 files changed, 372 deletions(-) delete mode 100644 image_output.png delete mode 100644 tests/e2e/stage_configs/qwen2_5_omni_ci_1773652593.yaml delete mode 100644 tests/e2e/stage_configs/qwen2_5_omni_ci_1773653262.yaml delete mode 100644 tests/e2e/stage_configs/qwen2_5_omni_ci_1773717276.yaml delete mode 100644 tests/e2e/stage_configs/qwen2_5_omni_ci_1773717938.yaml delete mode 100644 tests/e2e/stage_configs/qwen3_omni_ci_1773652593.yaml delete mode 100644 tests/e2e/stage_configs/qwen3_omni_ci_1773653262.yaml delete mode 100644 tests/e2e/stage_configs/qwen3_omni_ci_1773717276.yaml delete mode 100644 tests/e2e/stage_configs/qwen3_omni_ci_1773717938.yaml diff --git a/image_output.png b/image_output.png deleted file mode 100644 index c216f4dff1b532caa7a067dad7e1474999572713..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 113037 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�=Agao8vtHQyy&#N3Y;Cr=QM>`OpsCINV&`{p#yq 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 FV1l4b{R992 diff --git a/tests/e2e/stage_configs/qwen2_5_omni_ci_1773652593.yaml b/tests/e2e/stage_configs/qwen2_5_omni_ci_1773652593.yaml deleted file mode 100644 index b68bdb71a0b..00000000000 --- a/tests/e2e/stage_configs/qwen2_5_omni_ci_1773652593.yaml +++ /dev/null @@ -1,48 +0,0 @@ -stage_args: -- stage_id: 0 - runtime: {process: true, devices: '0', max_batch_size: 1} - engine_args: {model_stage: thinker, model_arch: Qwen2_5OmniForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.9, - skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, engine_output_type: latent, - enable_prefix_caching: false, mm_processor_cache_gb: 0} - is_comprehension: true - final_output: true - final_output_type: text - default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 128, - seed: 42, detokenize: true, repetition_penalty: 1.1} -- stage_id: 1 - runtime: {process: true, devices: '1', max_batch_size: 1} - engine_args: {model_stage: talker, model_arch: Qwen2_5OmniForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.4, - skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, enable_prefix_caching: false, - engine_output_type: latent} - engine_input_source: [0] - custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen2_5_omni.thinker2talker - default_sampling_params: - temperature: 0.9 - top_p: 0.8 - top_k: 40 - max_tokens: 4096 - seed: 42 - detokenize: true - repetition_penalty: 1.05 - stop_token_ids: [8294] -- stage_id: 2 - runtime: {process: true, devices: '1', max_batch_size: 1} - engine_args: {model_stage: code2wav, model_arch: Qwen2_5OmniForConditionalGeneration, - worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, - gpu_memory_utilization: 0.5, enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, - engine_output_type: audio, max_num_batched_tokens: 4096, max_model_len: 4096} - engine_input_source: [1] - final_output: true - final_output_type: audio - default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 4096, - seed: 42, detokenize: true, repetition_penalty: 1.1} -runtime: - enabled: true - defaults: {window_size: -1, max_inflight: 1} - edges: - - {from: 0, to: 1, window_size: -1} - - {from: 1, to: 2, window_size: -1} diff --git a/tests/e2e/stage_configs/qwen2_5_omni_ci_1773653262.yaml b/tests/e2e/stage_configs/qwen2_5_omni_ci_1773653262.yaml deleted file mode 100644 index b68bdb71a0b..00000000000 --- a/tests/e2e/stage_configs/qwen2_5_omni_ci_1773653262.yaml +++ /dev/null @@ -1,48 +0,0 @@ -stage_args: -- stage_id: 0 - runtime: {process: true, devices: '0', max_batch_size: 1} - engine_args: {model_stage: thinker, model_arch: Qwen2_5OmniForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.9, - skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, engine_output_type: latent, - enable_prefix_caching: false, mm_processor_cache_gb: 0} - is_comprehension: true - final_output: true - final_output_type: text - default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 128, - seed: 42, detokenize: true, repetition_penalty: 1.1} -- stage_id: 1 - runtime: {process: true, devices: '1', max_batch_size: 1} - engine_args: {model_stage: talker, model_arch: Qwen2_5OmniForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.4, - skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, enable_prefix_caching: false, - engine_output_type: latent} - engine_input_source: [0] - custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen2_5_omni.thinker2talker - default_sampling_params: - temperature: 0.9 - top_p: 0.8 - top_k: 40 - max_tokens: 4096 - seed: 42 - detokenize: true - repetition_penalty: 1.05 - stop_token_ids: [8294] -- stage_id: 2 - runtime: {process: true, devices: '1', max_batch_size: 1} - engine_args: {model_stage: code2wav, model_arch: Qwen2_5OmniForConditionalGeneration, - worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, - gpu_memory_utilization: 0.5, enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, - engine_output_type: audio, max_num_batched_tokens: 4096, max_model_len: 4096} - engine_input_source: [1] - final_output: true - final_output_type: audio - default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 4096, - seed: 42, detokenize: true, repetition_penalty: 1.1} -runtime: - enabled: true - defaults: {window_size: -1, max_inflight: 1} - edges: - - {from: 0, to: 1, window_size: -1} - - {from: 1, to: 2, window_size: -1} diff --git a/tests/e2e/stage_configs/qwen2_5_omni_ci_1773717276.yaml b/tests/e2e/stage_configs/qwen2_5_omni_ci_1773717276.yaml deleted file mode 100644 index b68bdb71a0b..00000000000 --- a/tests/e2e/stage_configs/qwen2_5_omni_ci_1773717276.yaml +++ /dev/null @@ -1,48 +0,0 @@ -stage_args: -- stage_id: 0 - runtime: {process: true, devices: '0', max_batch_size: 1} - engine_args: {model_stage: thinker, model_arch: Qwen2_5OmniForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.9, - skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, engine_output_type: latent, - enable_prefix_caching: false, mm_processor_cache_gb: 0} - is_comprehension: true - final_output: true - final_output_type: text - default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 128, - seed: 42, detokenize: true, repetition_penalty: 1.1} -- stage_id: 1 - runtime: {process: true, devices: '1', max_batch_size: 1} - engine_args: {model_stage: talker, model_arch: Qwen2_5OmniForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.4, - skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, enable_prefix_caching: false, - engine_output_type: latent} - engine_input_source: [0] - custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen2_5_omni.thinker2talker - default_sampling_params: - temperature: 0.9 - top_p: 0.8 - top_k: 40 - max_tokens: 4096 - seed: 42 - detokenize: true - repetition_penalty: 1.05 - stop_token_ids: [8294] -- stage_id: 2 - runtime: {process: true, devices: '1', max_batch_size: 1} - engine_args: {model_stage: code2wav, model_arch: Qwen2_5OmniForConditionalGeneration, - worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, - gpu_memory_utilization: 0.5, enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, - engine_output_type: audio, max_num_batched_tokens: 4096, max_model_len: 4096} - engine_input_source: [1] - final_output: true - final_output_type: audio - default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 4096, - seed: 42, detokenize: true, repetition_penalty: 1.1} -runtime: - enabled: true - defaults: {window_size: -1, max_inflight: 1} - edges: - - {from: 0, to: 1, window_size: -1} - - {from: 1, to: 2, window_size: -1} diff --git a/tests/e2e/stage_configs/qwen2_5_omni_ci_1773717938.yaml b/tests/e2e/stage_configs/qwen2_5_omni_ci_1773717938.yaml deleted file mode 100644 index b68bdb71a0b..00000000000 --- a/tests/e2e/stage_configs/qwen2_5_omni_ci_1773717938.yaml +++ /dev/null @@ -1,48 +0,0 @@ -stage_args: -- stage_id: 0 - runtime: {process: true, devices: '0', max_batch_size: 1} - engine_args: {model_stage: thinker, model_arch: Qwen2_5OmniForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.9, - skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, engine_output_type: latent, - enable_prefix_caching: false, mm_processor_cache_gb: 0} - is_comprehension: true - final_output: true - final_output_type: text - default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 128, - seed: 42, detokenize: true, repetition_penalty: 1.1} -- stage_id: 1 - runtime: {process: true, devices: '1', max_batch_size: 1} - engine_args: {model_stage: talker, model_arch: Qwen2_5OmniForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - max_model_len: 16384, max_num_batched_tokens: 16384, max_num_seqs: 1, gpu_memory_utilization: 0.4, - skip_mm_profiling: true, enforce_eager: 'true', trust_remote_code: true, enable_prefix_caching: false, - engine_output_type: latent} - engine_input_source: [0] - custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen2_5_omni.thinker2talker - default_sampling_params: - temperature: 0.9 - top_p: 0.8 - top_k: 40 - max_tokens: 4096 - seed: 42 - detokenize: true - repetition_penalty: 1.05 - stop_token_ids: [8294] -- stage_id: 2 - runtime: {process: true, devices: '1', max_batch_size: 1} - engine_args: {model_stage: code2wav, model_arch: Qwen2_5OmniForConditionalGeneration, - worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, - gpu_memory_utilization: 0.5, enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, - engine_output_type: audio, max_num_batched_tokens: 4096, max_model_len: 4096} - engine_input_source: [1] - final_output: true - final_output_type: audio - default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 4096, - seed: 42, detokenize: true, repetition_penalty: 1.1} -runtime: - enabled: true - defaults: {window_size: -1, max_inflight: 1} - edges: - - {from: 0, to: 1, window_size: -1} - - {from: 1, to: 2, window_size: -1} diff --git a/tests/e2e/stage_configs/qwen3_omni_ci_1773652593.yaml b/tests/e2e/stage_configs/qwen3_omni_ci_1773652593.yaml deleted file mode 100644 index c763bb2c72f..00000000000 --- a/tests/e2e/stage_configs/qwen3_omni_ci_1773652593.yaml +++ /dev/null @@ -1,45 +0,0 @@ -stage_args: -- stage_id: 0 - runtime: {devices: '0', max_batch_size: 5} - engine_args: {model_stage: thinker, model_arch: Qwen3OmniMoeForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - gpu_memory_utilization: 0.9, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, - distributed_executor_backend: mp, max_num_batched_tokens: 32768, max_model_len: 32768, - enable_prefix_caching: false, mm_processor_cache_gb: 0, hf_config_name: thinker_config, - tensor_parallel_size: 1, load_format: dummy, custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_async_chunk} - final_output: true - final_output_type: text - is_comprehension: true - default_sampling_params: {temperature: 0.4, top_p: 0.9, top_k: 1, max_tokens: 100, - seed: 42, ignore_eos: false, detokenize: true, repetition_penalty: 1.05} -- stage_id: 1 - runtime: {devices: '1', max_batch_size: 5} - engine_args: {model_stage: talker, model_arch: Qwen3OmniMoeForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - gpu_memory_utilization: 0.5, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, - enable_prefix_caching: false, max_num_batched_tokens: 32768, max_model_len: 32768, - distributed_executor_backend: mp, hf_config_name: talker_config, load_format: dummy, - custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_async_chunk} - engine_input_source: [0] - custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker - default_sampling_params: - temperature: 0.9 - top_k: 50 - max_tokens: 1000 - seed: 42 - detokenize: false - repetition_penalty: 1.05 - stop_token_ids: [2150] -- stage_id: 2 - runtime: {devices: '1', max_batch_size: 1} - engine_args: {model_stage: code2wav, model_arch: Qwen3OmniMoeForConditionalGeneration, - worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, - enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, engine_output_type: audio, - gpu_memory_utilization: 0.1, distributed_executor_backend: mp, max_num_batched_tokens: 100000, - hf_config_name: thinker_config, async_scheduling: false, load_format: dummy} - engine_input_source: [1] - final_output: true - final_output_type: audio - default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 2000, - seed: 42, detokenize: true, repetition_penalty: 1.1} -async_chunk: true diff --git a/tests/e2e/stage_configs/qwen3_omni_ci_1773653262.yaml b/tests/e2e/stage_configs/qwen3_omni_ci_1773653262.yaml deleted file mode 100644 index c763bb2c72f..00000000000 --- a/tests/e2e/stage_configs/qwen3_omni_ci_1773653262.yaml +++ /dev/null @@ -1,45 +0,0 @@ -stage_args: -- stage_id: 0 - runtime: {devices: '0', max_batch_size: 5} - engine_args: {model_stage: thinker, model_arch: Qwen3OmniMoeForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - gpu_memory_utilization: 0.9, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, - distributed_executor_backend: mp, max_num_batched_tokens: 32768, max_model_len: 32768, - enable_prefix_caching: false, mm_processor_cache_gb: 0, hf_config_name: thinker_config, - tensor_parallel_size: 1, load_format: dummy, custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_async_chunk} - final_output: true - final_output_type: text - is_comprehension: true - default_sampling_params: {temperature: 0.4, top_p: 0.9, top_k: 1, max_tokens: 100, - seed: 42, ignore_eos: false, detokenize: true, repetition_penalty: 1.05} -- stage_id: 1 - runtime: {devices: '1', max_batch_size: 5} - engine_args: {model_stage: talker, model_arch: Qwen3OmniMoeForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - gpu_memory_utilization: 0.5, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, - enable_prefix_caching: false, max_num_batched_tokens: 32768, max_model_len: 32768, - distributed_executor_backend: mp, hf_config_name: talker_config, load_format: dummy, - custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_async_chunk} - engine_input_source: [0] - custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker - default_sampling_params: - temperature: 0.9 - top_k: 50 - max_tokens: 1000 - seed: 42 - detokenize: false - repetition_penalty: 1.05 - stop_token_ids: [2150] -- stage_id: 2 - runtime: {devices: '1', max_batch_size: 1} - engine_args: {model_stage: code2wav, model_arch: Qwen3OmniMoeForConditionalGeneration, - worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, - enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, engine_output_type: audio, - gpu_memory_utilization: 0.1, distributed_executor_backend: mp, max_num_batched_tokens: 100000, - hf_config_name: thinker_config, async_scheduling: false, load_format: dummy} - engine_input_source: [1] - final_output: true - final_output_type: audio - default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 2000, - seed: 42, detokenize: true, repetition_penalty: 1.1} -async_chunk: true diff --git a/tests/e2e/stage_configs/qwen3_omni_ci_1773717276.yaml b/tests/e2e/stage_configs/qwen3_omni_ci_1773717276.yaml deleted file mode 100644 index c763bb2c72f..00000000000 --- a/tests/e2e/stage_configs/qwen3_omni_ci_1773717276.yaml +++ /dev/null @@ -1,45 +0,0 @@ -stage_args: -- stage_id: 0 - runtime: {devices: '0', max_batch_size: 5} - engine_args: {model_stage: thinker, model_arch: Qwen3OmniMoeForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - gpu_memory_utilization: 0.9, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, - distributed_executor_backend: mp, max_num_batched_tokens: 32768, max_model_len: 32768, - enable_prefix_caching: false, mm_processor_cache_gb: 0, hf_config_name: thinker_config, - tensor_parallel_size: 1, load_format: dummy, custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_async_chunk} - final_output: true - final_output_type: text - is_comprehension: true - default_sampling_params: {temperature: 0.4, top_p: 0.9, top_k: 1, max_tokens: 100, - seed: 42, ignore_eos: false, detokenize: true, repetition_penalty: 1.05} -- stage_id: 1 - runtime: {devices: '1', max_batch_size: 5} - engine_args: {model_stage: talker, model_arch: Qwen3OmniMoeForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - gpu_memory_utilization: 0.5, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, - enable_prefix_caching: false, max_num_batched_tokens: 32768, max_model_len: 32768, - distributed_executor_backend: mp, hf_config_name: talker_config, load_format: dummy, - custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_async_chunk} - engine_input_source: [0] - custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker - default_sampling_params: - temperature: 0.9 - top_k: 50 - max_tokens: 1000 - seed: 42 - detokenize: false - repetition_penalty: 1.05 - stop_token_ids: [2150] -- stage_id: 2 - runtime: {devices: '1', max_batch_size: 1} - engine_args: {model_stage: code2wav, model_arch: Qwen3OmniMoeForConditionalGeneration, - worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, - enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, engine_output_type: audio, - gpu_memory_utilization: 0.1, distributed_executor_backend: mp, max_num_batched_tokens: 100000, - hf_config_name: thinker_config, async_scheduling: false, load_format: dummy} - engine_input_source: [1] - final_output: true - final_output_type: audio - default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 2000, - seed: 42, detokenize: true, repetition_penalty: 1.1} -async_chunk: true diff --git a/tests/e2e/stage_configs/qwen3_omni_ci_1773717938.yaml b/tests/e2e/stage_configs/qwen3_omni_ci_1773717938.yaml deleted file mode 100644 index c763bb2c72f..00000000000 --- a/tests/e2e/stage_configs/qwen3_omni_ci_1773717938.yaml +++ /dev/null @@ -1,45 +0,0 @@ -stage_args: -- stage_id: 0 - runtime: {devices: '0', max_batch_size: 5} - engine_args: {model_stage: thinker, model_arch: Qwen3OmniMoeForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - gpu_memory_utilization: 0.9, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, - distributed_executor_backend: mp, max_num_batched_tokens: 32768, max_model_len: 32768, - enable_prefix_caching: false, mm_processor_cache_gb: 0, hf_config_name: thinker_config, - tensor_parallel_size: 1, load_format: dummy, custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker_async_chunk} - final_output: true - final_output_type: text - is_comprehension: true - default_sampling_params: {temperature: 0.4, top_p: 0.9, top_k: 1, max_tokens: 100, - seed: 42, ignore_eos: false, detokenize: true, repetition_penalty: 1.05} -- stage_id: 1 - runtime: {devices: '1', max_batch_size: 5} - engine_args: {model_stage: talker, model_arch: Qwen3OmniMoeForConditionalGeneration, - worker_type: ar, scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler, - gpu_memory_utilization: 0.5, enforce_eager: false, trust_remote_code: true, engine_output_type: latent, - enable_prefix_caching: false, max_num_batched_tokens: 32768, max_model_len: 32768, - distributed_executor_backend: mp, hf_config_name: talker_config, load_format: dummy, - custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.talker2code2wav_async_chunk} - engine_input_source: [0] - custom_process_input_func: vllm_omni.model_executor.stage_input_processors.qwen3_omni.thinker2talker - default_sampling_params: - temperature: 0.9 - top_k: 50 - max_tokens: 1000 - seed: 42 - detokenize: false - repetition_penalty: 1.05 - stop_token_ids: [2150] -- stage_id: 2 - runtime: {devices: '1', max_batch_size: 1} - engine_args: {model_stage: code2wav, model_arch: Qwen3OmniMoeForConditionalGeneration, - worker_type: generation, scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler, - enforce_eager: true, trust_remote_code: true, enable_prefix_caching: false, engine_output_type: audio, - gpu_memory_utilization: 0.1, distributed_executor_backend: mp, max_num_batched_tokens: 100000, - hf_config_name: thinker_config, async_scheduling: false, load_format: dummy} - engine_input_source: [1] - final_output: true - final_output_type: audio - default_sampling_params: {temperature: 0.0, top_p: 1.0, top_k: -1, max_tokens: 2000, - seed: 42, detokenize: true, repetition_penalty: 1.1} -async_chunk: true From 6ff31eb6ecbab66a97f5bad35928ee4a2969c0f2 Mon Sep 17 00:00:00 2001 From: tzhouam Date: Fri, 20 Mar 2026 03:42:07 +0000 Subject: [PATCH 45/47] Update rope parameters in Qwen3OmniMoeTalker to include rope_theta for improved configuration handling. Signed-off-by: tzhouam --- .../model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py index 51cfa9791cb..0f332caab9f 100644 --- a/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py +++ b/vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_talker.py @@ -100,6 +100,7 @@ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""): super().__init__() talker_config: Qwen3OmniMoeTalkerConfig = vllm_config.model_config.hf_config talker_config.text_config.rope_parameters = talker_config.text_config.rope_scaling + talker_config.text_config.rope_parameters["rope_theta"] = talker_config.text_config.rope_theta self.quant_config = vllm_config.quant_config self.prefix = prefix self.vllm_config = vllm_config From 521f17861992060e6ca045e5800c948738cd476b Mon Sep 17 00:00:00 2001 From: tzhouam Date: Fri, 20 Mar 2026 09:04:35 +0000 Subject: [PATCH 46/47] [Refactor] Remove unused parameters from AsyncOmni class constructor in async_omni.py Signed-off-by: tzhouam --- vllm_omni/entrypoints/async_omni.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/vllm_omni/entrypoints/async_omni.py b/vllm_omni/entrypoints/async_omni.py index 57c432ee1c1..18b4f200365 100644 --- a/vllm_omni/entrypoints/async_omni.py +++ b/vllm_omni/entrypoints/async_omni.py @@ -137,10 +137,6 @@ async def generate( prompt_text: str | None = None, lora_request: Any = None, tokenization_kwargs: dict[str, Any] | None = None, - trace_headers: Any = None, - priority: int = 0, - data_parallel_rank: int | None = None, - reasoning_ended: bool | None = None, sampling_params_list: Sequence[OmniSamplingParams] | None = None, output_modalities: list[str] | None = None, ) -> AsyncGenerator[OmniRequestOutput, None]: From 9538e3f30bd88f9749cffebec43177341d922351 Mon Sep 17 00:00:00 2001 From: gcanlin Date: Fri, 20 Mar 2026 09:53:34 +0000 Subject: [PATCH 47/47] [NPU] Upgrade to v0.18.0 Signed-off-by: gcanlin --- .../installation/npu/npu.inc.md | 6 +- .../npu/worker/npu_ar_model_runner.py | 88 ++++++++++++++----- .../npu/worker/npu_generation_model_runner.py | 34 +++++-- .../platforms/npu/worker/npu_model_runner.py | 28 +++--- 4 files changed, 115 insertions(+), 41 deletions(-) diff --git a/docs/getting_started/installation/npu/npu.inc.md b/docs/getting_started/installation/npu/npu.inc.md index bc2d3b60cba..a763637035d 100644 --- a/docs/getting_started/installation/npu/npu.inc.md +++ b/docs/getting_started/installation/npu/npu.inc.md @@ -68,18 +68,18 @@ We are keeping [issue #886](https://github.com/vllm-project/vllm-omni/issues/886 You can also build vLLM-Omni from the latest main branch if you want to use the latest features or bug fixes. (But sometimes it will break for a while. You can check [issue #886](https://github.com/vllm-project/vllm-omni/issues/886) for the status of the latest commit of vLLM-Omni main branch on NPU.) ```bash -# Pin vLLM version to 0.17.0 +# Pin vLLM version to 0.18.0 cd /vllm-workspace/vllm git pull origin main git fetch origin --tags -git checkout v0.17.0 +git checkout v0.18.0 VLLM_TARGET_DEVICE=empty pip install -v -e . # Because vllm-ascend has not yet entered continuous development and has not been officially released, we need to pin it to a specific commit. Please note that this commit may change over time. cd /vllm-workspace/vllm-ascend git pull origin main git fetch origin --tags -git checkout v0.17.0 +git checkout 1e05c4908f31737bc4eef865a9f351d030a77c9d pip install -v -e . # Install vLLM-Omni from the latest main branch diff --git a/vllm_omni/platforms/npu/worker/npu_ar_model_runner.py b/vllm_omni/platforms/npu/worker/npu_ar_model_runner.py index 22badb49ef6..3d9cb86bacd 100644 --- a/vllm_omni/platforms/npu/worker/npu_ar_model_runner.py +++ b/vllm_omni/platforms/npu/worker/npu_ar_model_runner.py @@ -26,6 +26,7 @@ from vllm.v1.spec_decode.metadata import SpecDecodeMetadata from vllm.v1.structured_output.utils import apply_grammar_bitmask from vllm.v1.utils import record_function_or_nullcontext +from vllm.v1.worker import mamba_utils from vllm.v1.worker.gpu_model_runner import AsyncGPUModelRunnerOutput, PerLayerAttnMetadata from vllm.v1.worker.ubatch_utils import maybe_create_ubatch_slices from vllm_ascend.ascend_forward_context import set_ascend_forward_context @@ -237,6 +238,23 @@ def execute_model( pad_attn = cudagraph_mode == CUDAGraphMode.FULL + # NOTE(Angazenn): According to https://github.com/vllm-project/vllm/pull/30877, + # there should be a corresponding 'postprocess_mamba'. However, it is called inside + # '_update_states_after_model_execute', which is not overridden in vLLM-Ascend. + # We simply utilize the implementation in vLLM. + if self.cache_config.mamba_cache_mode == "align": + mamba_utils.preprocess_mamba( + scheduler_output, + self.kv_cache_config, + self.cache_config, + self.mamba_state_idx, + self.input_batch, + self.requests, + self.compilation_config.static_forward_context, + self.model.get_mamba_state_copy_func(), + self._get_mamba_copy_bufs(), + ) + use_spec_decode = len(scheduler_output.scheduled_spec_decode_tokens) > 0 ubatch_slices_attn = ubatch_slices_padded if pad_attn else ubatch_slices @@ -339,7 +357,7 @@ def execute_model( skip_compiled=has_encoder_input, ), self.maybe_get_kv_connector_output( - scheduler_output, clear_metadata=clear_kv_metadata + scheduler_output, defer_finalize=not clear_kv_metadata ) as kv_connector_output, ): hidden_states = self._model_forward( @@ -606,6 +624,34 @@ def propose_draft_token_ids(sampled_token_ids): hidden_states, multimodal_outputs, num_scheduled_tokens_np, scheduler_output ) + # Pre-copy multimodal tensors to CPU once (not per-request) to avoid + # redundant D2H transfers when gpu_resident_buffer_keys keeps them on GPU. + mm_cpu: dict[str, object] = {} + if isinstance(multimodal_outputs, dict) and multimodal_outputs: + for k, v in multimodal_outputs.items(): + try: + if isinstance(v, torch.Tensor) and v.shape[0] == hidden_states_cpu.shape[0]: + mm_cpu[k] = v.detach().to("cpu").contiguous() + elif isinstance(v, dict): + sub_dict: dict[str, torch.Tensor] = {} + for sk, sv in v.items(): + if isinstance(sv, torch.Tensor) and sv.shape[0] == hidden_states_cpu.shape[0]: + sub_dict[str(sk)] = sv.detach().to("cpu").contiguous() + if sub_dict: + mm_cpu[k] = sub_dict + elif isinstance(v, list): + if len(v) == 0: + continue + cpu_list = [] + for elem in v: + if isinstance(elem, torch.Tensor): + cpu_list.append(elem.detach().to("cpu").contiguous()) + else: + cpu_list.append(elem) + mm_cpu[k] = cpu_list + except Exception as e: + logger.error(f"Error in merge multimodal outputs: {e}") + pooler_output: list[dict[str, object]] = [] for rid in req_ids_output_copy: idx = req_id_to_index_output_copy[rid] @@ -614,28 +660,26 @@ def propose_draft_token_ids(sampled_token_ids): end = start + sched hidden_slice = hidden_states_cpu[start:end] payload: dict[str, object] = {"hidden": hidden_slice} - if isinstance(multimodal_outputs, dict) and multimodal_outputs: + if mm_cpu: mm_payload: dict[str, object] = {} - for k, v in multimodal_outputs.items(): - try: - if isinstance(v, torch.Tensor) and v.shape[0] == hidden_states_cpu.shape[0]: - mm_payload[k] = v.detach().to("cpu")[start:end].contiguous() - elif isinstance(v, dict): - sub_dict: dict[str, torch.Tensor] = {} - for sk, sv in v.items(): - if isinstance(sv, torch.Tensor) and sv.shape[0] == hidden_states_cpu.shape[0]: - sub_dict[str(sk)] = sv.detach().to("cpu")[start:end].contiguous() - if sub_dict: - mm_payload[k] = sub_dict - elif isinstance(v, list): - element = v[0] - if isinstance(element, torch.Tensor): - element = element.detach().to("cpu").contiguous() - mm_payload[k] = element - except Exception as e: - logger.error(f"Error in merge multimodal outputs: {e}") - if mm_payload: - payload.update(mm_payload) + for k, v in mm_cpu.items(): + if isinstance(v, torch.Tensor) and v.shape[0] == hidden_states_cpu.shape[0]: + mm_payload[k] = v[start:end].contiguous() + elif isinstance(v, dict): + mm_payload[k] = {sk: sv[start:end].contiguous() for sk, sv in v.items()} + elif isinstance(v, list): + element = v[idx] if idx < len(v) else v[0] + # Clone tensors to avoid cross-request aliasing + if isinstance(element, torch.Tensor): + element = element.clone() + mm_payload[k] = element + elif isinstance(v, torch.Tensor): + # List-derived tensor payloads are request-invariant; clone to + # avoid accidental cross-request aliasing on downstream mutation. + mm_payload[k] = v.clone() + else: + mm_payload[k] = v + payload.update(mm_payload) pooler_output.append(payload) model_runner_output = OmniModelRunnerOutput( diff --git a/vllm_omni/platforms/npu/worker/npu_generation_model_runner.py b/vllm_omni/platforms/npu/worker/npu_generation_model_runner.py index 0296c042d5b..48a9982801d 100644 --- a/vllm_omni/platforms/npu/worker/npu_generation_model_runner.py +++ b/vllm_omni/platforms/npu/worker/npu_generation_model_runner.py @@ -20,6 +20,7 @@ from vllm.v1.core.sched.output import GrammarOutput, SchedulerOutput from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT, AsyncModelRunnerOutput, make_empty_encoder_model_runner_output from vllm.v1.utils import record_function_or_nullcontext +from vllm.v1.worker import mamba_utils from vllm.v1.worker.gpu_model_runner import AsyncGPUModelRunnerOutput, PerLayerAttnMetadata from vllm.v1.worker.ubatch_utils import maybe_create_ubatch_slices from vllm.v1.worker.utils import sanity_check_mm_encoder_outputs @@ -195,6 +196,23 @@ def execute_model( pad_attn = cudagraph_mode == CUDAGraphMode.FULL + # NOTE(Angazenn): According to https://github.com/vllm-project/vllm/pull/30877, + # there should be a corresponding 'postprocess_mamba'. However, it is called inside + # '_update_states_after_model_execute', which is not overridden in vLLM-Ascend. + # We simply utilize the implementation in vLLM. + if self.cache_config.mamba_cache_mode == "align": + mamba_utils.preprocess_mamba( + scheduler_output, + self.kv_cache_config, + self.cache_config, + self.mamba_state_idx, + self.input_batch, + self.requests, + self.compilation_config.static_forward_context, + self.model.get_mamba_state_copy_func(), + self._get_mamba_copy_bufs(), + ) + use_spec_decode = len(scheduler_output.scheduled_spec_decode_tokens) > 0 ubatch_slices_attn = ubatch_slices_padded if pad_attn else ubatch_slices @@ -300,7 +318,7 @@ def execute_model( skip_compiled=has_encoder_input, ), self.maybe_get_kv_connector_output( - scheduler_output, clear_metadata=clear_kv_metadata + scheduler_output, defer_finalize=not clear_kv_metadata ) as kv_connector_output, ): # -------------------------------------- Omni-new ------------------------------------------------- @@ -503,6 +521,7 @@ def _dummy_run( remove_lora: bool = True, is_graph_capturing: bool = False, num_active_loras: int = 0, + profile_seq_lens: int | None = None, ) -> tuple[torch.Tensor, torch.Tensor]: # only support eager mode and piecewise graph now assert cudagraph_runtime_mode is None or cudagraph_runtime_mode.valid_runtime_modes() @@ -609,11 +628,14 @@ def _dummy_run( # seq_lens. We use this seq_len only when capturing graph, and still use max_query_len # in inference. This will be removed once npu_fused_infer_attention_score # outperforms _npu_paged_attention on all cases. - seq_lens = ( - SEQ_LEN_WITH_MAX_PA_WORKSPACE - if is_graph_capturing and using_paged_attention(num_tokens, self.vllm_config) - else max_query_len - ) # type: ignore[assignment] + if profile_seq_lens is not None: + seq_lens = profile_seq_lens + else: + seq_lens = ( + SEQ_LEN_WITH_MAX_PA_WORKSPACE + if is_graph_capturing and using_paged_attention(num_tokens, self.vllm_config) + else max_query_len + ) # type: ignore[assignment] self.seq_lens.np[:num_reqs_padded] = seq_lens self.seq_lens.np[num_reqs_padded:] = 0 self.seq_lens.copy_to_gpu() diff --git a/vllm_omni/platforms/npu/worker/npu_model_runner.py b/vllm_omni/platforms/npu/worker/npu_model_runner.py index 9ff5f720e96..8ef39adfa67 100644 --- a/vllm_omni/platforms/npu/worker/npu_model_runner.py +++ b/vllm_omni/platforms/npu/worker/npu_model_runner.py @@ -35,9 +35,11 @@ def load_model(self, *args, **kwargs) -> None: enable_sp(self.vllm_config) # TODO move this model specific logic to a separate class # TTS model IS the talker (no .talker sub-attr); use getattr to support both Omni and TTS. + self.has_talker_mtp = False talker_mtp = getattr(self.model, "talker_mtp", None) if talker_mtp is not None: self.talker_mtp = talker_mtp # type: ignore[assignment] + self.has_talker_mtp = True cudagraph_mode = self.compilation_config.cudagraph_mode assert cudagraph_mode is not None # Only wrap talker_mtp in CUDAGraphWrapper for Omni models that @@ -73,6 +75,7 @@ def _dummy_run( remove_lora: bool = True, is_graph_capturing: bool = False, num_active_loras: int = 0, + profile_seq_lens: int | None = None, ) -> tuple[torch.Tensor, torch.Tensor]: # only support eager mode and piecewise graph now assert cudagraph_runtime_mode is None or cudagraph_runtime_mode.valid_runtime_modes() @@ -179,11 +182,14 @@ def _dummy_run( # seq_lens. We use this seq_len only when capturing graph, and still use max_query_len # in inference. This will be removed once npu_fused_infer_attention_score # outperforms _npu_paged_attention on all cases. - seq_lens = ( - SEQ_LEN_WITH_MAX_PA_WORKSPACE - if is_graph_capturing and using_paged_attention(num_tokens, self.vllm_config) - else max_query_len - ) # type: ignore[assignment] + if profile_seq_lens is not None: + seq_lens = profile_seq_lens + else: + seq_lens = ( + SEQ_LEN_WITH_MAX_PA_WORKSPACE + if is_graph_capturing and using_paged_attention(num_tokens, self.vllm_config) + else max_query_len + ) # type: ignore[assignment] self.seq_lens.np[:num_reqs_padded] = seq_lens self.seq_lens.np[num_reqs_padded:] = 0 self.seq_lens.copy_to_gpu() @@ -283,7 +289,7 @@ def dummy_drafter_compute_logits(hidden_states): model_instance=self.model, ): # ---------------------------------------Omni-new---------------------------------------------- - if getattr(self.model, "talker", None) is not None and hasattr(self.model, "talker_mtp"): + if getattr(self.model, "talker", None) is not None and self.has_talker_mtp: num_tokens_padded_talker_mtp = num_tokens_padded if num_tokens_padded_talker_mtp == self.max_num_tokens: num_tokens_padded_talker_mtp = self.talker_mtp_input_ids.gpu.shape[0] @@ -362,7 +368,7 @@ def _model_forward( # Omni-specific: wrap output if needed if not isinstance(model_output, OmniOutput) and hasattr(self.model, "make_omni_output"): - model_output = self.model.make_omni_output(model_output, **model_kwargs_extra) + model_output = self.model.make_omni_output(model_output, **model_kwargs, **model_kwargs_extra) # Omni-specific: cache model output for later sample_tokens self._omni_last_model_output = model_output @@ -415,12 +421,14 @@ def _talker_mtp_forward(self, decode_req_ids: list[str], inputs_embeds: torch.Te None, self.vllm_config, aclgraph_runtime_mode=_cudagraph_mode, batch_descriptor=batch_desc ): req_embeds, code_predictor_codes = self.talker_mtp(req_input_ids, req_embeds, last_talker_hidden, text_step) - # update the inputs_embeds and code_predictor_codes - code_predictor_codes_cpu = code_predictor_codes.detach().to("cpu").contiguous() + # code_predictor_codes stays on GPU here; _update_intermediate_buffer + # keeps it device-resident when the key is in gpu_resident_buffer_keys. + # D2H is deferred to sample_tokens where hidden_states.to("cpu") already + # syncs the stream, avoiding a per-step cudaStreamSynchronize. out_key = getattr(self.model, "talker_mtp_output_key", "code_predictor_codes") for idx, req_id in enumerate(decode_req_ids): req_index = self.input_batch.req_ids.index(req_id) start_offset = int(self.query_start_loc.cpu[req_index]) inputs_embeds[start_offset : start_offset + 1] = req_embeds[idx : idx + 1] - update_dict = {out_key: code_predictor_codes_cpu[idx : idx + 1]} + update_dict = {out_key: code_predictor_codes[idx : idx + 1]} self._merge_additional_information_update(req_id, update_dict)