Compare commits

...
Author SHA1 Message Date
Joshna-Medisetty 807db6efd7 [Refactor] Migrate Voxtral TTS XPU stage config to --deploy-config (#5467)
Signed-off-by: Joshna Medisetty <joshna.medisetty@intel.com>
2026-07-28 07:35:35 +08:00
Yueqian LinandSy03 d4a5eaac90 [Bugfix][Realtime] Stop disconnected sessions from cycling through stages (#5388)
Signed-off-by: Yueqian Lin <70319226+linyueqian@users.noreply.github.com>
Co-authored-by: Sy03 <1370724210@qq.com>
2026-07-27 17:04:17 +00:00
Yueqian Lin 8f115164c2 [Bugfix][Connectors] Flush processor tail on terminal async-chunk sends (#5414)
Signed-off-by: Yueqian Lin <70319226+linyueqian@users.noreply.github.com>
2026-07-27 16:04:08 +00:00
cbab1d4d8f [Rebase] Rebase to vllm 0.26.0 (#5443)
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
Signed-off-by: tzhouam <tzhouam@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: tzhouam <tzhouam@gmail.com>
2026-07-27 23:35:49 +08:00
52 changed files with 586 additions and 316 deletions

No files matched your search

+6 -2
View File
@@ -49,13 +49,17 @@ steps:
depends_on: upload-merge-pipeline
steps:
- label: "Entrypoint Test with H100"
timeout_in_minutes: 60
# TEMPORARY 60 -> 90: engine startup/sleep-wake is ~13-15 min slower per
# run on the vLLM 0.26 image (model loading ~2x, init ~2x; see builds
# 2915 vs 2916), which pushed this suite past 60 min. Revert once the
# 0.26 startup regression is understood and fixed.
timeout_in_minutes: 90
commands:
- pytest -s -v tests/entrypoints/ -m "advanced_model and cuda and H100" --run-level "advanced_model"
mirror_hardwares: h100_2
- label: "Entrypoint Test with L4"
timeout_in_minutes: 30
timeout_in_minutes: 60
commands:
- pytest -s -v tests/entrypoints/ -m "advanced_model and cuda and L4" --run-level "advanced_model"
mirror_hardwares: l4_1
+2 -2
View File
@@ -1,7 +1,7 @@
# Target vllm version: v0.25.0 (releases/v0.25.0 branch).
# Target vllm version: v0.26.0 (releases/v0.26.0 branch).
# Keep VLLM_BASE_TAG aligned with the target vLLM release on every rebase.
ARG VLLM_BASE_IMAGE=vllm/vllm-openai
ARG VLLM_BASE_TAG=v0.25.0
ARG VLLM_BASE_TAG=v0.26.0
# We use registry-based layer caching for the dependencies,
# where the tag is determined based on the hash of the contents
+1 -1
View File
@@ -1,4 +1,4 @@
ARG BASE_IMAGE=vllm/vllm-openai:v0.25.0
ARG BASE_IMAGE=vllm/vllm-openai:v0.26.0
FROM ${BASE_IMAGE}
ARG COMMON_WORKDIR=/app
+2 -2
View File
@@ -1,4 +1,4 @@
ARG BASE_IMAGE=vllm/vllm-openai-rocm:v0.25.0
ARG BASE_IMAGE=vllm/vllm-openai-rocm:v0.26.0
FROM ${BASE_IMAGE} AS base
# Declare a variable to know if we want to use the nightly build or the stable build.
@@ -10,7 +10,7 @@ FROM ${BASE_IMAGE} AS base
# we should swap over to use stable release ASAP.
# We should avoid relying on custom commits.
ARG USE_NIGHTLY_BUILD=0
ARG VLLM_VERSION_OR_COMMIT_HASH=89138b21cc246ae944c741d5c399c148e2b770ab
ARG VLLM_VERSION_OR_COMMIT_HASH=v0.26.0
ARG ARG_PYTORCH_ROCM_ARCH
ENV PYTORCH_ROCM_ARCH=${ARG_PYTORCH_ROCM_ARCH:-${PYTORCH_ROCM_ARCH}}
+1 -1
View File
@@ -88,7 +88,7 @@ ENV UV_EXTRA_INDEX_URL=${PIP_EXTRA_INDEX_URL}
ENV UV_INDEX_STRATEGY="unsafe-best-match"
ENV UV_LINK_MODE="copy"
ARG VLLM_VERSION=v0.25.0
ARG VLLM_VERSION=v0.26.0
RUN git clone -b ${VLLM_VERSION} https://github.com/vllm-project/vllm
WORKDIR /workspace/vllm
+1 -1
View File
@@ -34,7 +34,7 @@ vLLM-Omni is a Python library that supports the following GPU variants. The libr
### Pre-built wheels
Note: Pre-built wheels are currently available for vLLM-Omni 0.11.0rc1, 0.12.0rc1, 0.14.0rc1, 0.14.0, 0.16.0, 0.18.0, 0.20.0, 0.21.0, 0.22.0, 0.23.0, 0.24.0, and 0.25.0. If you need a newer unreleased revision, please [build from source](https://docs.vllm.ai/projects/vllm-omni/en/latest/getting_started/installation/gpu/#build-wheel-from-source).
Note: Pre-built wheels are currently available for vLLM-Omni 0.11.0rc1, 0.12.0rc1, 0.14.0rc1, 0.14.0, 0.16.0, 0.18.0, 0.20.0, 0.21.0, 0.22.0, 0.23.0, 0.24.0, 0.25.0, and 0.26.0. If you need a newer unreleased revision, please [build from source](https://docs.vllm.ai/projects/vllm-omni/en/latest/getting_started/installation/gpu/#build-wheel-from-source).
=== "NVIDIA CUDA"
@@ -20,7 +20,7 @@ Therefore, it is recommended to install vLLM and vLLM-Omni with a **fresh new**
vLLM-Omni is built based on vLLM. Please install it with command below.
```bash
uv pip install vllm==0.25.0 --torch-backend=auto
uv pip install vllm==0.26.0 --torch-backend=auto
```
#### Installation of vLLM-Omni
@@ -39,13 +39,13 @@ uv pip install 'vllm-omni[demo]'
# --8<-- [start:build-wheel-from-source]
#### Installation of vLLM
If you do not need to modify source code of vLLM, you can directly install the stable 0.25.0 release version of the library
If you do not need to modify source code of vLLM, you can directly install the stable 0.26.0 release version of the library
```bash
uv pip install vllm==0.25.0 --torch-backend=auto
uv pip install vllm==0.26.0 --torch-backend=auto
```
The 0.25.0 release of vLLM ships CUDA 13.0-compatible binaries by default. If you need a different CUDA variant or want to reuse an existing PyTorch installation, build vLLM from source instead.
The 0.26.0 release of vLLM ships CUDA 13.0-compatible binaries by default. If you need a different CUDA variant or want to reuse an existing PyTorch installation, build vLLM from source instead.
#### Installation of vLLM-Omni
Since vllm-omni is rapidly evolving, it's recommended to install it from source
@@ -66,12 +66,12 @@ If you want to check, modify or debug with source code of vLLM, install the libr
```bash
git clone https://github.com/vllm-project/vllm.git
cd vllm
git checkout v0.25.0
git checkout v0.26.0
```
Set up environment variables to get pre-built wheels. If there are internet problems, just download the whl file manually. And set `VLLM_PRECOMPILED_WHEEL_LOCATION` as your local absolute path of whl file.
```bash
#For CUDA 13.0 (the default for v0.25.0; the wheel filename has no `+cu130` suffix)
export VLLM_PRECOMPILED_WHEEL_LOCATION=https://github.com/vllm-project/vllm/releases/download/v0.25.0/vllm-0.25.0-cp38-abi3-manylinux_2_28_x86_64.whl
#For CUDA 13.0 (the default for v0.26.0; the wheel filename has no `+cu130` suffix)
export VLLM_PRECOMPILED_WHEEL_LOCATION=https://github.com/vllm-project/vllm/releases/download/v0.26.0/vllm-0.26.0-cp38-abi3-manylinux_2_28_x86_64.whl
```
Install vllm with command below (If you have no existing PyTorch).
```bash
@@ -102,7 +102,7 @@ docker run --runtime nvidia --gpus 2 \
--env "HF_TOKEN=$HF_TOKEN" \
-p 8091:8091 \
--ipc=host \
vllm/vllm-omni:v0.25.0 \
vllm/vllm-omni:v0.26.0 \
vllm serve Qwen/Qwen3-Omni-30B-A3B-Instruct --omni --port 8091
```
@@ -13,7 +13,7 @@ vLLM-Omni current recommends the steps in under setup through Docker Images.
vLLM-Omni is built based on vLLM. Please install it with command below.
```bash
uv pip install vllm==0.25.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.25.0/rocm723
uv pip install vllm==0.26.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.26.0/rocm723
```
#### Installation of vLLM-Omni
@@ -34,13 +34,13 @@ uv pip install onnxruntime-rocm
# --8<-- [start:build-wheel-from-source]
#### Installation of vLLM
If you do not need to modify source code of vLLM, you can directly install the stable 0.25.0 release version of the library
If you do not need to modify source code of vLLM, you can directly install the stable 0.26.0 release version of the library
```bash
uv pip install vllm==0.25.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.25.0/rocm723
uv pip install vllm==0.26.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.26.0/rocm723
```
The pre-built 0.25.0 vLLM wheel targets ROCm 7.2.3. If you need a different ROCm stack or want to reuse an existing PyTorch installation, build vLLM from source instead.
The pre-built 0.26.0 vLLM wheel targets ROCm 7.2.3. If you need a different ROCm stack or want to reuse an existing PyTorch installation, build vLLM from source instead.
#### Installation of vLLM-Omni
Since vllm-omni is rapidly evolving, it's recommended to install it from source
@@ -58,7 +58,7 @@ If you want to check, modify or debug with source code of vLLM, install the libr
```bash
git clone https://github.com/vllm-project/vllm.git
cd vllm
git checkout v0.25.0
git checkout v0.26.0
python3 -m pip install -r requirements/rocm.txt
python3 setup.py develop
```
@@ -130,7 +130,7 @@ docker run --rm \
-v ~/.cache/huggingface:/root/.cache/huggingface \
--env "HF_TOKEN=$HF_TOKEN" \
-p 8091:8091 \
vllm/vllm-omni-rocm:v0.25.0 \
vllm/vllm-omni-rocm:v0.26.0 \
--model Qwen/Qwen3-Omni-30B-A3B-Instruct --omni --port 8091
```
@@ -149,7 +149,7 @@ docker run --rm -it \
-v ~/.cache/huggingface:/root/.cache/huggingface \
--env "HF_TOKEN=$HF_TOKEN" \
--entrypoint bash \
vllm/vllm-omni-rocm:v0.25.0
vllm/vllm-omni-rocm:v0.26.0
```
# --8<-- [end:pre-built-images]
+3 -3
View File
@@ -19,10 +19,10 @@ uv venv --python 3.12 --seed
source .venv/bin/activate
# On CUDA
uv pip install vllm==0.25.0 --torch-backend=auto
uv pip install vllm==0.26.0 --torch-backend=auto
# On ROCm
uv pip install vllm==0.25.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.25.0/rocm723
uv pip install vllm==0.26.0+rocm723 --extra-index-url https://wheels.vllm.ai/rocm/0.26.0/rocm723
git clone https://github.com/vllm-project/vllm-omni.git
cd vllm-omni
@@ -34,7 +34,7 @@ For additional installation methods — please see the [installation guide](inst
!!! note
It is important to install the same major & minor version of vLLM and vLLM Omni, otherwise things may not work as expected. If the versions are misaligned, you will see a warning when you import vLLM Omni.
If you are seeing strange behavior with the `vllm` command not handling the `--omni` flag correctly, you most likely have a version mismatch with vLLM < `0.25.0` and vLLM Omni `0.25.0`, as vLLM Omni no longer hijacks the vLLM entrypoint. Updating vLLM should resolve this issue.
If you are seeing strange behavior with the `vllm` command not handling the `--omni` flag correctly, you most likely have a version mismatch with vLLM < `0.26.0` and vLLM Omni `0.26.0`, as vLLM Omni no longer hijacks the vLLM entrypoint. Updating vLLM should resolve this issue.
## Offline Inference
+29 -24
View File
@@ -100,8 +100,9 @@ def _decode_kv_cache(
head_size: int = 128,
dtype: torch.dtype = torch.float16,
) -> torch.Tensor:
# vLLM >=0.23.0 KV cache layout: key/value live on dim 1.
return torch.zeros((num_blocks, 2, block_size, num_kv_heads, head_size), dtype=dtype)
# vLLM >=0.23.0 KV cache layout: (num_blocks, num_kv_heads, block_size,
# 2*head_size) with K and V packed in the last dimension.
return torch.zeros((num_blocks, num_kv_heads, block_size, 2 * head_size), dtype=dtype)
def test_fish_kvcache_enabled_by_default(monkeypatch):
@@ -221,10 +222,11 @@ def _vllm_kv_cache_shape(num_blocks, block_size, num_kv_heads, head_size):
def test_fish_kvcache_backend_unbinds_kv_on_vllm_cache_layout(monkeypatch):
# vLLM >=0.23.0 lays the KV cache out as (num_blocks, 2, block_size,
# num_kv_heads, head_size). The backend must unbind on dim 1 so each of
# key/value comes back as the 4-D (num_blocks, block_size, num_kv_heads,
# head_size) tensor the decode kernel expects.
# vLLM >=0.23.0 lays out the KV cache as (num_blocks, num_kv_heads,
# block_size, 2*head_size) with K and V packed in the last dimension.
# The backend must split on the content dim so each of key/value comes
# back as the 4-D (num_blocks, block_size, num_kv_heads, head_size)
# tensor the decode kernel expects.
monkeypatch.setenv("VLLM_OMNI_FISH_KVCACHE_ATTN", "1")
monkeypatch.setattr(fish_kvcache_backend, "is_available", lambda: True)
monkeypatch.setattr(fish_kvcache_backend, "load_error", lambda: None)
@@ -232,7 +234,8 @@ def test_fish_kvcache_backend_unbinds_kv_on_vllm_cache_layout(monkeypatch):
num_blocks, block_size, num_kv_heads, head_size = 8, 16, 8, 128
cache_shape = _vllm_kv_cache_shape(num_blocks, block_size, num_kv_heads, head_size)
assert cache_shape == (num_blocks, 2, block_size, num_kv_heads, head_size)
# Upstream now returns a 4-D packed shape: (B, H, N, 2*D).
assert cache_shape == (num_blocks, num_kv_heads, block_size, 2 * head_size)
captured = {}
@@ -262,20 +265,21 @@ def test_fish_kvcache_backend_unbinds_kv_on_vllm_cache_layout(monkeypatch):
def test_fish_kvcache_vllm_cache_unbinds_then_falls_back_until_contiguous(monkeypatch):
# End-to-end with the real vLLM cache layout and the real (unmocked) guard.
#
# The point of the unbind(1) fix: unbind(0) raised "too many values to
# unpack" on the 5-D vLLM 0.23.0 layout, so the request crashed outright.
# unbind(1) makes the request *work* again -- it unpacks cleanly and the
# backend gracefully falls back to the original attention with correct
# output. This test guards that fix and must NOT depend on the fast path
# firing.
# The point of the transpose+split fix: unbind(1) on the old 5-D layout
# raised "too many values to unpack" on the vLLM 0.23.0 layout, and the
# upstream now packs K/V into the last dimension (4-D shape). The
# transpose(1, 2) + split correctly unpacks into separate key/value tensors
# and the backend gracefully falls back to the original attention with
# correct output. This test guards that fix and must NOT depend on the fast
# path firing.
#
# It also documents a known limitation (tracked separately): unbind(1) on
# the interleaved (num_blocks, 2, ...) layout yields *non-contiguous*
# key/value views, which the guard rejects on the is_contiguous() check.
# So the dimensions are compatible (4-D, block_size=16, head_size=128) --
# the only thing keeping the fast path from engaging is contiguity, not
# shape. Making the fast path actually fire would require a .contiguous()
# copy and is out of scope here.
# It also documents a known limitation (tracked separately):
# transpose(1, 2) on the interleaved (B, H, N, 2*D) layout yields
# *non-contiguous* key/value views, which the guard rejects on the
# is_contiguous() check. So the dimensions are compatible (4-D,
# block_size=16, head_size=128) -- the only thing keeping the fast path
# from engaging is contiguity, not shape. Making the fast path actually
# fire would require a .contiguous() copy and is out of scope here.
fish_kvcache_backend.reset_fish_kvcache_attn_stats()
monkeypatch.setenv("VLLM_OMNI_FISH_KVCACHE_ATTN", "1")
monkeypatch.setattr(fish_kvcache_backend, "is_available", lambda: True)
@@ -300,13 +304,14 @@ def test_fish_kvcache_vllm_cache_unbinds_then_falls_back_until_contiguous(monkey
output = torch.zeros_like(query)
kv_cache = torch.zeros(cache_shape, dtype=torch.float16)
# Root cause record: unbind(1) gives the right 4-D shape but a non-contiguous view.
key_cache, value_cache = kv_cache.unbind(1)
# Root cause record: transpose(1,2) + split gives the right 4-D shape but
# a non-contiguous view.
key_cache, value_cache = kv_cache.transpose(1, 2).split(head_size, dim=-1)
assert tuple(key_cache.shape) == (num_blocks, block_size, num_kv_heads, head_size)
assert not key_cache.is_contiguous()
# unbind(1) no longer crashes (unbind(0) would have raised here) and the
# request completes via the correct fallback path.
# transpose+split no longer crashes (unbind(1) would have raised here) and
# the request completes via the correct fallback path.
result = model.layers[0].self_attn.attn.impl.forward(None, query, None, None, kv_cache, _metadata(), output)
assert result is output
@@ -73,6 +73,9 @@ def _run_resumable_segment_stop(
sched._update_request_with_output.side_effect = stop_request
sched._handle_stopped_request.return_value = session_finished
# vLLM 0.26 returns (kv_xfer_params, ec_xfer_params); an unconfigured
# MagicMock iterates empty and fails to unpack at the call site.
sched._free_request.return_value = (None, None)
sched.chunk_transfer_adapter = None
sched.running = [session]
sched.waiting_for_transfer_free = set()
@@ -137,6 +140,23 @@ def test_resumable_session_terminal_is_not_marked_as_segment_boundary() -> None:
assert output.is_segment_finished is False
def test_update_from_output_settles_in_flight_tokens() -> None:
"""vLLM 0.26: schedule() increments num_in_flight_tokens per scheduled
token; update_from_output must decrement it symmetrically. If the
decrement is dropped the counter grows monotonically and both readers
(allocate_slots, _connector_finished) clamp
max(0, num_computed_tokens - num_in_flight_tokens) to zero forever,
silently freezing sliding-window block freeing.
"""
session = _make_request()
session.status = RequestStatus.RUNNING
session.num_in_flight_tokens = 1 # as left by schedule() for this step
_run_resumable_segment_stop(session)
assert session.num_in_flight_tokens == 0
def test_running_decode_step_without_inter_stage_payload_does_not_raise() -> None:
"""A decode step that neither stops nor carries an inter-stage payload.
@@ -76,8 +76,9 @@ def test_finish_requests_purges_finished_request_from_running(
)
def fake_finish_requests(self, request_ids, finished_status):
# super() returning the abort tuple but leaving self.running untouched
return [(finished.request_id, 0)]
# vLLM 0.26 super() returns the finished Request objects but here
# leaves self.running untouched to simulate the missed removal.
return [finished]
monkeypatch.setattr(scheduler_mod.VLLMScheduler, "finish_requests", fake_finish_requests)
@@ -88,7 +89,7 @@ def test_finish_requests_purges_finished_request_from_running(
)
assert scheduler.running == []
assert result == [(finished.request_id, 0)]
assert result == [finished]
@pytest.mark.parametrize(("scheduler_cls", "scheduler_mod"), _SCHEDULER_PARAMS)
@@ -111,7 +112,7 @@ def test_finish_requests_purges_untracked_request_from_running(
)
def fake_finish_requests(self, request_ids, finished_status):
return [(untracked.request_id, 0)]
return [untracked]
monkeypatch.setattr(scheduler_mod.VLLMScheduler, "finish_requests", fake_finish_requests)
@@ -122,7 +123,7 @@ def test_finish_requests_purges_untracked_request_from_running(
)
assert scheduler.running == []
assert result == [(untracked.request_id, 0)]
assert result == [untracked]
@pytest.mark.parametrize(("scheduler_cls", "scheduler_mod"), _SCHEDULER_PARAMS)
@@ -136,7 +137,7 @@ def test_finish_requests_leaves_healthy_running_intact(
is conservative and won't aggressively drop healthy entries.
"""
alive = _StubRequest("req-alive", RequestStatus.RUNNING)
leaving_id = "req-leaving"
leaving = _StubRequest("req-leaving", RequestStatus.FINISHED_STOPPED)
scheduler = _make_scheduler(
scheduler_cls,
requests={alive.request_id: alive},
@@ -145,13 +146,13 @@ def test_finish_requests_leaves_healthy_running_intact(
)
def fake_finish_requests(self, request_ids, finished_status):
return [(leaving_id, 0)]
return [leaving]
monkeypatch.setattr(scheduler_mod.VLLMScheduler, "finish_requests", fake_finish_requests)
scheduler_cls.finish_requests(
scheduler,
[leaving_id],
[leaving.request_id],
RequestStatus.FINISHED_STOPPED,
)
@@ -53,7 +53,11 @@ def test_finish_requests_cleans_input_coordinator_for_finished_ids(
def fake_finish_requests(self, request_ids, finished_status):
assert request_ids == ["req-a", "req-b"]
assert finished_status == RequestStatus.FINISHED_STOPPED
return [("req-a", 0), ("req-b", 1)]
# vLLM 0.26 returns the finished Request objects, not (req_id, client_index) tuples.
return [
SimpleNamespace(request_id="req-a", client_index=0),
SimpleNamespace(request_id="req-b", client_index=1),
]
monkeypatch.setattr(scheduler_mod.VLLMScheduler, "finish_requests", fake_finish_requests)
@@ -63,7 +67,7 @@ def test_finish_requests_cleans_input_coordinator_for_finished_ids(
RequestStatus.FINISHED_STOPPED,
)
assert result == [("req-a", 0), ("req-b", 1)]
assert [(r.request_id, r.client_index) for r in result] == [("req-a", 0), ("req-b", 1)]
assert coordinator.freed == ["req-a", "req-b"]
@@ -82,7 +86,7 @@ def test_ar_free_request_cleans_input_coordinator_on_normal_free() -> None:
result = OmniARScheduler._free_request(scheduler, DummyRequest())
assert result is None
assert result == (None, None)
assert coordinator.freed == ["req-free"]
assert scheduler._omits_kv_transfer_cache == {}
assert scheduler._new_prompt_len_snapshot == {}
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import logging
import os
from types import SimpleNamespace
@@ -338,27 +339,43 @@ def test_transformer_load_weights_round_trip():
assert torch.allclose(reloaded[native_name], expected)
def test_transformer_load_weights_warns_for_unexpected_and_unloaded(caplog):
def test_transformer_load_weights_warns_for_unexpected_and_unloaded():
from vllm_omni.diffusion.models.boogu_image.boogu_image_transformer import (
BooguImageTransformer2DModel,
)
model = BooguImageTransformer2DModel(od_config=_tiny_od_config())
native_params = dict(model.named_parameters())
loaded_name = next(iter(native_params))
checkpoint_name = _native_to_checkpoint_name(loaded_name)
# vllm_omni's logger hierarchy hangs off vLLM's `vllm` logger, which sets
# propagate=False, so records never reach the root logger that pytest's
# caplog listens on. Capture at the emitting logger instead.
messages: list[str] = []
loaded = model.load_weights(
[
(checkpoint_name, torch.randn_like(native_params[loaded_name])),
("unexpected.weight", torch.ones(1)),
]
)
class _Capture(logging.Handler):
def emit(self, record: logging.LogRecord) -> None:
messages.append(record.getMessage())
log = logging.getLogger("vllm_omni.diffusion.models.boogu_image.boogu_image_transformer")
handler = _Capture(level=logging.WARNING)
log.addHandler(handler)
try:
model = BooguImageTransformer2DModel(od_config=_tiny_od_config())
native_params = dict(model.named_parameters())
loaded_name = next(iter(native_params))
checkpoint_name = _native_to_checkpoint_name(loaded_name)
loaded = model.load_weights(
[
(checkpoint_name, torch.randn_like(native_params[loaded_name])),
("unexpected.weight", torch.ones(1)),
]
)
finally:
log.removeHandler(handler)
text = "\n".join(messages)
assert loaded == {loaded_name}
assert "Skipping unexpected checkpoint weight unexpected.weight" in caplog.text
assert "Model parameters not loaded from checkpoint" in caplog.text
assert next(name for name in native_params if name != loaded_name) in caplog.text
assert "Skipping unexpected checkpoint weight unexpected.weight" in text
assert "Model parameters not loaded from checkpoint" in text
assert next(name for name in native_params if name != loaded_name) in text
def test_transformer_forward_t2i_shape():
@@ -252,6 +252,37 @@ def test_save_async_uses_confirmed_tokens_for_async_scheduler_watermark(build_ad
assert len(adapter._pending_save_reqs) == 1
def test_send_single_request_terminal_chunk_still_flushes_processor(build_adapter, monkeypatch):
"""A terminal stop is not a segment boundary (#5383), but the producer-side
processor must still receive the flush signal on the terminal chunk.
Passing only ``is_segment_finished`` starved processors of their final
accumulated payload once terminal stops stopped setting it (#5413: the
downstream stage got ``meta.finished`` with the tail data missing).
"""
adapter, connector = build_adapter(stage_id=0)
request = _req("req-terminal", RequestStatus.FINISHED_STOPPED, external_req_id="ext-terminal")
seen_flush_flags = []
def recording_processor(**kwargs):
seen_flush_flags.append(kwargs["is_finished"])
return OmniPayloadStruct(
codes=CodesStruct(audio=torch.tensor([1, 2, 3], dtype=torch.long)),
)
adapter.custom_process_next_stage_input_func = recording_processor
monkeypatch.setattr(adapter, "cleanup", lambda *a, **kw: None)
adapter._send_single_request(
{"multimodal_output": None, "request": request, "is_finished": True, "is_segment_finished": False}
)
assert seen_flush_flags == [True]
sent_payload = connector.put.call_args.kwargs["data"]
assert bool(sent_payload.meta.finished.item()) is True
assert bool(sent_payload.meta.is_segment_finished.item()) is False
def test_send_single_request_struct_without_meta_does_not_crash(build_adapter, monkeypatch):
"""Producer may return a struct with ``meta=None`` (e.g. payload that
carries only ``embed`` or ``codes``). The sender's ``meta is not None``
@@ -780,6 +811,10 @@ def test_restore_queues_skips_requests_missing_from_scheduler_requests(build_ada
class _HashableRequest(SimpleNamespace):
"""SimpleNamespace that can be added to a set (needed by scheduler internals)."""
# vLLM 0.26: update_from_output settles this counter for every scheduled
# request; real Requests initialise it to 0 (vllm/v1/request.py).
num_in_flight_tokens = 0
def __hash__(self):
return hash(self.request_id)
@@ -833,7 +868,7 @@ def test_generation_scheduler_calls_cleanup_on_finished(monkeypatch, mocker: Moc
scheduler.requests = {"req-s1": request}
scheduler._handle_stopped_request = mocker.MagicMock(return_value=True)
scheduler._free_request = mocker.MagicMock(return_value=None)
scheduler._free_request = mocker.MagicMock(return_value=(None, None))
scheduler._get_routed_experts = mocker.MagicMock(return_value=None)
scheduler.running = [request]
scheduler.waiting = mocker.MagicMock()
@@ -918,7 +953,7 @@ def test_ar_scheduler_defers_cleanup_and_queues_save_on_finished(mocker: MockerF
scheduler._update_request_with_output = mocker.MagicMock(return_value=([], True))
scheduler._process_kv_transfer_trigger = mocker.MagicMock(return_value=False)
scheduler._handle_stopped_request = mocker.MagicMock(return_value=True)
scheduler._free_request = mocker.MagicMock(return_value=None)
scheduler._free_request = mocker.MagicMock(return_value=(None, None))
scheduler._get_routed_experts = mocker.MagicMock(return_value=None)
scheduler.running = [request]
scheduler.waiting = mocker.MagicMock()
@@ -1047,7 +1082,7 @@ def _build_deferred_finish_scheduler(mocker, *, running, pending_finish_reqs):
scheduler._pending_finish_reqs = list(pending_finish_reqs)
scheduler._handle_stopped_request = mocker.MagicMock(return_value=True)
scheduler._free_request = mocker.MagicMock(return_value=None)
scheduler._free_request = mocker.MagicMock(return_value=(None, None))
scheduler._get_routed_experts = mocker.MagicMock(return_value=None)
scheduler.running = list(running)
scheduler.waiting = mocker.MagicMock()
@@ -1103,7 +1138,7 @@ def test_deferred_finish_emits_finished_output(mocker: MockerFixture):
running=[request],
pending_finish_reqs=[request],
)
scheduler._free_request.return_value = {"mock": "kv_params"}
scheduler._free_request.return_value = ({"mock": "kv_params"}, None)
result = OmniGenerationScheduler.update_from_output(scheduler, sched_out, model_out)
@@ -330,6 +330,31 @@ def test_normalize_layer_kv_rejects_invalid_inputs(kv_config, common_constants,
assert normalized is None
def test_normalize_layer_kv_unpacks_packed_layout(common_constants):
"""The vLLM >= 0.23 packed layout (num_blocks, n_heads, block_size,
2*head_dim) is unpacked into (num_blocks, block_size, n_heads, head_dim)
key/value blocks when block_size is provided."""
block_size = common_constants["block_size"]
num_heads = common_constants["num_heads"]
head_dim = common_constants["head_dim"]
req_id = common_constants["req_id"]
num_blocks = 10
layer_kv = torch.randn(num_blocks, num_heads, block_size, 2 * head_dim)
# Without block_size the 4-D packed layout is not recognized.
assert normalize_layer_kv(layer_kv, req_id=req_id, layer_idx=0) is None
normalized = normalize_layer_kv(layer_kv, req_id=req_id, layer_idx=0, block_size=block_size)
assert normalized is not None
key_blocks, value_blocks = normalized
assert key_blocks.shape == (num_blocks, block_size, num_heads, head_dim)
assert value_blocks.shape == (num_blocks, block_size, num_heads, head_dim)
reference = layer_kv.transpose(1, 2)
assert torch.equal(key_blocks, reference[..., :head_dim])
assert torch.equal(value_blocks, reference[..., head_dim:])
def test_manager_reception(kv_config, mock_connector, common_constants):
"""Test reception and injection logic in OmniKVTransferManager."""
num_layers = common_constants["num_layers"]
@@ -58,7 +58,7 @@ def _compose_request(model_name: str, text: str, voice: str) -> dict:
@pytest.mark.parametrize("omni_runner_function", [_OMNI_RUNNER_PARAM], indirect=True)
@hardware_test(res={"cuda": "L4"}, num_cards=1)
@hardware_test(res={"cuda": "L4", "xpu": "B60"}, num_cards=1)
def test_voxtral_tts_offline_basic(omni_runner_function: OmniRunner) -> None:
"""Offline sync path; function-scoped runner so AsyncOmni test does not overlap a live Omni."""
omni = omni_runner_function.omni
@@ -30,7 +30,7 @@ pytestmark = [
]
@hardware_test(res={"cuda": "L4"}, num_cards=1)
@hardware_test(res={"cuda": "L4", "xpu": "B60"}, num_cards=1)
def test_speech_english_basic(omni_server, openai_client) -> None:
"""Test basic English TTS generation."""
openai_client.send_audio_speech_request(
@@ -46,7 +46,7 @@ def test_speech_english_basic(omni_server, openai_client) -> None:
)
@hardware_test(res={"cuda": "L4"}, num_cards=1)
@hardware_test(res={"cuda": "L4", "xpu": "B60"}, num_cards=1)
def test_speech_english_streaming(omni_server, openai_client) -> None:
"""Test basic streaming English TTS generation (PCM via streaming API)."""
openai_client.send_audio_speech_request(
+50
View File
@@ -1813,6 +1813,56 @@ async def test_stage_pool_submit_update_refreshes_output_processor_state() -> No
@pytest.mark.asyncio
async def test_handle_streaming_update_unknown_request_is_dropped() -> None:
"""A streaming update for an untracked request must not resurrect it.
Updates always follow the first-chunk add_request through the same
ordered submission queue, so an unknown id can only mean the request
already finished or was aborted (e.g. the client disconnected).
Falling back to add_request created headless sessions that kept
cycling through the stages (issue #4271).
"""
class RecordingPool:
def __init__(self) -> None:
self.calls: list[tuple[str, Any]] = []
async def submit_update(self, request_id, req_state, request, *, prompt_text=None) -> int:
self.calls.append((request_id, prompt_text))
return 0
pool = RecordingPool()
orchestrator = object.__new__(Orchestrator)
orchestrator.async_chunk = False
orchestrator.request_states = {}
orchestrator.stage_pools = [pool]
add_request_calls: list[str] = []
async def record_add_request(msg) -> None:
add_request_calls.append(msg.request_id)
orchestrator._handle_add_request = record_add_request
await orchestrator._handle_streaming_update(
StageSubmissionMessage(
type="streaming_update",
request_id="req-unknown",
prompt=SimpleNamespace(request_id="req-unknown", prompt_token_ids=[1], resumable=True),
original_prompt={"prompt": "segment-2"},
output_prompt_text="segment-2",
sampling_params_list=[_sampling_params()],
final_stage_id=0,
preprocess_ms=0.0,
request_timestamp=time.time(),
enqueue_ts=time.perf_counter(),
)
)
assert pool.calls == []
assert add_request_calls == []
assert "req-unknown" not in orchestrator.request_states
async def test_handle_streaming_update_passes_prompt_text_to_stage_pool() -> None:
class RecordingPool:
def __init__(self) -> None:
@@ -201,6 +201,7 @@ def _assert_realtime_smoke(result: dict) -> None:
def _assert_realtime_accuracy(
result: dict,
whisper_model_size: str = "large-v3",
threshold: float = 0.8,
) -> None:
"""Assert that whisper transcription of audio output matches model text.
@@ -214,6 +215,12 @@ def _assert_realtime_accuracy(
variability even though audio generation was correct. large-v3
transcribes these clips reliably, so a failure here now points
at the model, not the ASR grader.
threshold: Minimum cosine similarity (with length penalty) required to
pass. Default 0.8. Do not lower per-callsite without data:
at 0.35 the assertion no longer detects real audio
regressions. If a variant genuinely needs a different gate
(e.g. whisper partial transcripts under async_chunk), propose
it in its own PR with measurements.
"""
final_text = (result["transcription_text"] or "").strip()
assert final_text, "Expected non-empty transcription (model text stream)"
@@ -223,8 +230,9 @@ def _assert_realtime_accuracy(
assert whisper_text, "Whisper returned empty string for synthesized output audio"
sim = cosine_similarity_text(whisper_text.lower(), final_text.lower())
assert sim > 0.8, (
f"Output audio transcript should match model text (sim={sim:.3f}): "
assert sim > threshold, (
f"Output audio transcript should match model text (sim={sim:.3f}, "
f"threshold={threshold}): "
f"whisper={whisper_text!r}, model_text={final_text!r}"
)
+5 -22
View File
@@ -305,21 +305,11 @@ def mock_get_config(monkeypatch):
"""Auto-mock get_config and related model loading functions."""
import sys
fake_tokenizer = _ns()
fake_tokenizer.encode = lambda *args, **kwargs: [1, 2, 3]
fake_tokenizer.decode = lambda *args, **kwargs: "test"
def _mock_init_tokenizer_from_configs(model_config=None, **kwargs):
return fake_tokenizer
monkeypatch.setattr(
"vllm.transformers_utils.tokenizer.init_tokenizer_from_configs",
_mock_init_tokenizer_from_configs,
raising=False,
)
tokenizer_module_path = "vllm.transformers_utils.tokenizer"
if tokenizer_module_path in sys.modules:
setattr(sys.modules[tokenizer_module_path], "init_tokenizer_from_configs", _mock_init_tokenizer_from_configs)
# TODO(pd-unskip): the tokenizer mock that lived here targeted
# vllm.transformers_utils.tokenizer.init_tokenizer_from_configs, which was
# deleted in vLLM 0.26 (the raising=False setattr made it a silent no-op).
# When the module-level skip is lifted, mock whatever tokenizer seam the
# revived PD entrypoint code actually uses.
def _mock_length_from_prompt_token_ids_or_embeds(prompt_token_ids=None, prompt_embeds=None):
if prompt_token_ids is not None:
@@ -344,13 +334,6 @@ def mock_get_config(monkeypatch):
_mock_length_from_prompt_token_ids_or_embeds,
)
monkeypatch.setattr(
"vllm_omni.entrypoints.async_omni.init_tokenizer_from_configs", _mock_init_tokenizer_from_configs, raising=False
)
async_omni_path = "vllm_omni.entrypoints.async_omni"
if async_omni_path in sys.modules:
setattr(sys.modules[async_omni_path], "init_tokenizer_from_configs", _mock_init_tokenizer_from_configs)
fake_hf_config = _ns()
fake_hf_config.model_type = "qwen2_5_omni"
@@ -0,0 +1,88 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Unit tests for MiniCPM-o 4.5 code2wav model-dir resolution (#5442).
In hub/CI deployments ``model_config.model`` is a repo id rather than a local
directory, so asset lookups must resolve to the downloaded snapshot. The
resolution must stay lazy: constructing the model with a fake path (as the
CPU unit tests do) must not touch the hub.
"""
from __future__ import annotations
from types import SimpleNamespace
import huggingface_hub
import pytest
from vllm_omni.model_executor.models.minicpmo_4_5.minicpmo_4_5_code2wav import (
MiniCPMO45Code2Wav,
_resolve_model_dir,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def _no_hub(monkeypatch):
def _fail(*args, **kwargs): # pragma: no cover - must not be reached
raise AssertionError("snapshot_download must not be called here")
monkeypatch.setattr(huggingface_hub, "snapshot_download", _fail)
def test_local_directory_is_returned_unchanged(tmp_path, monkeypatch):
_no_hub(monkeypatch)
assert _resolve_model_dir(str(tmp_path)) == str(tmp_path)
def test_repo_id_resolves_via_snapshot_download(tmp_path, monkeypatch):
calls = {}
def _fake_snapshot_download(model_ref, revision=None, allow_patterns=None):
calls["model_ref"] = model_ref
calls["revision"] = revision
calls["allow_patterns"] = allow_patterns
return str(tmp_path / "snapshot")
monkeypatch.setattr(huggingface_hub, "snapshot_download", _fake_snapshot_download)
resolved = _resolve_model_dir("openbmb/MiniCPM-o-4_5", revision="abc123")
assert resolved == str(tmp_path / "snapshot")
assert calls["model_ref"] == "openbmb/MiniCPM-o-4_5"
assert calls["revision"] == "abc123"
assert calls["allow_patterns"] == ["assets/*"]
def test_snapshot_download_failure_propagates(monkeypatch):
def _raise(*args, **kwargs):
raise FileNotFoundError("offline and not cached")
monkeypatch.setattr(huggingface_hub, "snapshot_download", _raise)
with pytest.raises(FileNotFoundError):
_resolve_model_dir("openbmb/MiniCPM-o-4_5")
def test_init_with_fake_path_does_not_resolve(monkeypatch):
"""Mirrors the CPU-test construction: fake model path, no ``revision``."""
_no_hub(monkeypatch)
config = SimpleNamespace(
model_config=SimpleNamespace(
model="/fake/model",
stage_connector_config=None,
)
)
model = MiniCPMO45Code2Wav(vllm_config=config)
assert model.model_path == "/fake/model"
assert model._default_prompt_wav == "/fake/model/assets/HT_ref_audio.wav"
def test_default_prompt_wav_follows_resolved_model_path(monkeypatch):
_no_hub(monkeypatch)
config = SimpleNamespace(
model_config=SimpleNamespace(
model="openbmb/MiniCPM-o-4_5",
stage_connector_config=None,
)
)
model = MiniCPMO45Code2Wav(vllm_config=config)
model.model_path = "/resolved/snapshot"
assert model._default_prompt_wav == "/resolved/snapshot/assets/HT_ref_audio.wav"
@@ -15,6 +15,7 @@ import functools
import pytest
import torch
from pytest_mock import MockerFixture
from vllm.multimodal.utils import set_mm_embedding_modality
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@@ -117,13 +118,30 @@ class TestCheckInterleavedAudioVideo:
# ---------------------------------------------------------------------------
def make_mock_model(mocker: MockerFixture, hidden: int = 8):
@functools.lru_cache(maxsize=1)
def _omni_qwen2_5_thinker_cls():
"""vllm_omni's thinker override, loaded lazily like the upstream one."""
from vllm_omni.model_executor.models.qwen2_5_omni.qwen2_5_omni_thinker import (
Qwen2_5OmniThinkerForConditionalGeneration,
)
return Qwen2_5OmniThinkerForConditionalGeneration
def make_mock_model(mocker: MockerFixture, hidden: int = 8, thinker_cls=None):
"""
Return a minimal mock of Qwen2_5OmniThinkerForConditionalGeneration
that has enough structure to run embed_input_ids.
``thinker_cls`` selects whose ``embed_input_ids`` the mock dispatches to.
It defaults to upstream's. Only the interleaved case passes omni's override:
omni's non-interleaved path falls through to a zero-arg ``super()`` call,
which raises TypeError when ``self`` is a Mock rather than an instance.
"""
m = _qwen2_5_omni_thinker_mod()
Qwen2_5OmniThinkerForConditionalGeneration = m.Qwen2_5OmniThinkerForConditionalGeneration
if thinker_cls is None:
thinker_cls = Qwen2_5OmniThinkerForConditionalGeneration
model = mocker.Mock(spec=Qwen2_5OmniThinkerForConditionalGeneration)
@@ -155,9 +173,7 @@ def make_mock_model(mocker: MockerFixture, hidden: int = 8):
is_multimodal=is_multimodal,
)
model.embed_input_ids = lambda *a, **kw: m.Qwen2_5OmniThinkerForConditionalGeneration.embed_input_ids( # noqa: E501
model, *a, **kw
)
model.embed_input_ids = lambda *a, **kw: thinker_cls.embed_input_ids(model, *a, **kw)
model._super_embed_input_ids = fake_super_embed
@@ -171,11 +187,11 @@ def build_mm_embeds(audio_n, image_n, video_n, hidden, audio_val=10.0, image_val
"""
embs = []
if audio_n:
embs.append(torch.full((audio_n, hidden), audio_val))
embs.append(set_mm_embedding_modality(torch.full((audio_n, hidden), audio_val), "audio"))
if image_n:
embs.append(torch.full((image_n, hidden), image_val))
embs.append(set_mm_embedding_modality(torch.full((image_n, hidden), image_val), "image"))
if video_n:
embs.append(torch.full((video_n, hidden), video_val))
embs.append(set_mm_embedding_modality(torch.full((video_n, hidden), video_val), "video"))
return embs
@@ -259,6 +275,11 @@ class TestEmbedInputIds:
"""
Interleaved (use_audio_in_video): video chunks interleaved with audio.
Video embeddings must go to video positions, audio to audio positions.
Runs against **omni's** thinker override, not upstream's: this is the
only unit-level coverage of omni's call into
``merge_interleaved_embeddings``, whose upstream signature dropped
``num_video``/``num_audio`` in vLLM #46213.
"""
hidden = 8
audio_val, video_val = 10.0, 30.0
@@ -270,11 +291,11 @@ class TestEmbedInputIds:
audio_n = sum(audio_chunks) # 6
mm_embeds = [
torch.full((video_n, hidden), video_val),
torch.full((audio_n, hidden), audio_val),
set_mm_embedding_modality(torch.full((video_n, hidden), video_val), "video"),
set_mm_embedding_modality(torch.full((audio_n, hidden), audio_val), "audio"),
]
model, _ = make_mock_model(mocker, hidden)
model, _ = make_mock_model(mocker, hidden, thinker_cls=_omni_qwen2_5_thinker_cls())
result = model.embed_input_ids(input_ids, mm_embeds, is_multimodal=is_multimodal)
video_pos = (input_ids == VIDEO_TOKEN_ID).nonzero(as_tuple=True)[0]
@@ -308,8 +329,8 @@ class TestMergeInterleavedEmbeddings:
inputs_embeds = torch.zeros(len(input_ids), hidden)
mm_embeds = [
torch.full((num_video, hidden), 30.0),
torch.full((num_audio, hidden), 10.0),
set_mm_embedding_modality(torch.full((num_video, hidden), 30.0), "video"),
set_mm_embedding_modality(torch.full((num_audio, hidden), 10.0), "audio"),
]
result = m.merge_interleaved_embeddings(
@@ -318,8 +339,6 @@ class TestMergeInterleavedEmbeddings:
is_video,
is_audio,
is_multimodal,
num_video,
num_audio,
)
video_pos = is_video.nonzero(as_tuple=True)[0]
@@ -84,6 +84,7 @@ class _CpuFusedMoE3DWithLoRA(FusedMoE3DWithLoRA):
self.tp_size = 1
self.tp_rank = 0
self._w13_slices = 1
self.enable_moe_shared_loras = False
def _save_peft_adapter(tmp_path: Path) -> Path:
+3
View File
@@ -69,6 +69,9 @@ def test_speech_extra_params_reach_model_sampler_as_sampling_metadata(monkeypatc
vocab_size=1024,
block_sizes=[1],
kernel_block_sizes=[1],
# vLLM 0.26: one entry per KV cache group; max_model_len=8 with
# block_sizes=[1] means one group of 8 blocks.
max_num_blocks_per_req=[8],
)
input_batch.add_request(
CachedRequestState(
@@ -4,7 +4,7 @@ from types import SimpleNamespace
import pytest
import torch
from vllm.config import CacheConfig
from vllm.v1.worker.gpu_model_runner import EMPTY_MODEL_RUNNER_OUTPUT
from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT
import vllm_omni.worker.gpu_generation_model_runner as gen_runner_module
from vllm_omni.worker.gpu_generation_model_runner import (
+1 -1
View File
@@ -1245,7 +1245,7 @@ class TestRankAwareKVRouting(unittest.TestCase):
class TestAttachOmniConnectorOutput(unittest.TestCase):
def test_wraps_empty_model_runner_output_when_signals_exist(self):
from vllm.v1.worker.gpu_model_runner import EMPTY_MODEL_RUNNER_OUTPUT
from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT
host = MixinHost()
host.get_omni_connector_output = lambda: OmniConnectorOutput(chunk_ready_req_ids={"req-1"})
+8 -2
View File
@@ -27,8 +27,14 @@ except ModuleNotFoundError as exc: # pragma: no cover - optional dependency
patch = None # type: ignore
# Register custom configs (AutoConfig, AutoTokenizer) as early as possible.
from vllm_omni.transformers_utils import configs as _configs # noqa: F401, E402
from vllm_omni.transformers_utils import parsers as _parsers # noqa: F401, E402
try:
from vllm_omni.transformers_utils import configs as _configs # noqa: F401, E402
from vllm_omni.transformers_utils import parsers as _parsers # noqa: F401, E402
except ModuleNotFoundError as exc: # pragma: no cover - optional dependency
if exc.name != "vllm":
raise
# Allow importing vllm_omni without vllm (e.g., documentation builds)
pass
from .config import OmniModelConfig
+5 -5
View File
@@ -234,11 +234,11 @@ def _forward_with_fish_kvcache(
if attn_metadata is not None and not attn_metadata.use_cascade:
num_actual_tokens = attn_metadata.num_actual_tokens
# vLLM >=0.23.0 lays out the KV cache as
# (num_blocks, 2, block_size, num_kv_heads, head_size), so key/value
# live on dim 1 (matches FlashAttentionImpl's kv_cache.unbind(1)).
# The pre-0.23 layout had them on dim 0; unbind(0) here would split
# along num_blocks and raise "too many values to unpack".
key_cache, value_cache = kv_cache.unbind(1)
# (num_blocks, num_kv_heads, block_size, 2*head_size) with K and V
# packed in the last dimension. Match FlashAttentionImpl's own
# unpacking: transpose(1, 2) then split on the content dim.
head_size = kv_cache.shape[-1] // 2
key_cache, value_cache = kv_cache.transpose(1, 2).split(head_size, dim=-1)
q = query[:num_actual_tokens]
out = output[:num_actual_tokens]
active_batch = int(q.shape[0])
+23 -12
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
from collections import defaultdict
from collections.abc import Iterable
from time import time
from typing import Any
@@ -341,10 +342,18 @@ class OmniARScheduler(OmniSchedulerMixin, VLLMScheduler):
stopped_preempted_reqs: set[Request] = set()
for req_id, num_tokens_scheduled in num_scheduled_tokens.items():
assert num_tokens_scheduled > 0
request = self.requests.get(req_id)
if request is not None:
# vLLM 0.26: settle the in-flight tokens counted in schedule().
# Must happen before the skips below — failed-KV-load and
# already-finished requests were incremented too, and the two
# readers (allocate_slots, _connector_finished) clamp with
# max(0, computed - in_flight), so a leaked counter silently
# freezes sliding-window block freeing.
request.num_in_flight_tokens -= num_tokens_scheduled
if failed_kv_load_req_ids and req_id in failed_kv_load_req_ids:
# Skip requests that were recovered from KV load failure
continue
request = self.requests.get(req_id)
if request is None or request.is_finished():
# The request is already finished. This can happen if the
# request is aborted while the model is executing it (e.g.,
@@ -451,7 +460,7 @@ class OmniARScheduler(OmniSchedulerMixin, VLLMScheduler):
request.spec_token_ids = []
request._output_token_ids.clear()
if finished:
kv_transfer_params = self._free_request(request)
kv_transfer_params, _ = self._free_request(request)
if status_before_stop == RequestStatus.RUNNING:
stopped_running_reqs.add(request)
elif status_before_stop == RequestStatus.WAITING_FOR_CHUNK:
@@ -642,7 +651,7 @@ class OmniARScheduler(OmniSchedulerMixin, VLLMScheduler):
return engine_core_outputs
def finish_requests(self, request_ids: Any, finished_status: RequestStatus) -> list[tuple[str, int]]:
def finish_requests(self, request_ids: str | Iterable[str] | None, finished_status: RequestStatus) -> list[Request]:
"""Handles the finish signal from outside the scheduler.
For example, the API server can abort a request when the client
@@ -651,8 +660,8 @@ class OmniARScheduler(OmniSchedulerMixin, VLLMScheduler):
If request_ids is None, all requests will be finished.
Returns:
Tuple of (req_id, client_index) for requests that were aborted. Will not
include any that were already finished.
The Request objects that were aborted. Will not include any that
were already finished.
"""
# TODO(yrr): chunk transfer adapter & input_coordinator unified to one
if self.chunk_transfer_adapter:
@@ -688,8 +697,8 @@ class OmniARScheduler(OmniSchedulerMixin, VLLMScheduler):
input_coordinator = getattr(self, "input_coordinator", None)
if input_coordinator is not None:
for request_id, _ in finished:
self._free_input_coordinator_request(request_id)
for request in finished:
self._free_input_coordinator_request(request.request_id)
return finished
def _update_request_as_session(self, session: Request, update: StreamingUpdate) -> None:
@@ -740,7 +749,9 @@ class OmniARScheduler(OmniSchedulerMixin, VLLMScheduler):
if hasattr(update, "model_intermediate_buffer"):
session.model_intermediate_buffer = update.model_intermediate_buffer
def _free_request(self, request: Request, delay_free_blocks: bool = False) -> dict[str, Any] | None:
def _free_request(
self, request: Request, delay_free_blocks: bool = False
) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
# TODO(wzliu)! for offline mode, we should not end process until all data is transferred
"""Mark a request as finished and free its resources."""
assert request.is_finished()
@@ -779,10 +790,10 @@ class OmniARScheduler(OmniSchedulerMixin, VLLMScheduler):
logger.debug(f"[Omni] Request {request_id} finished but transfer is still ACTIVE. Waiting.")
self.waiting_for_transfer_free.add(request_id)
kv_xfer_params = None
return kv_xfer_params
return kv_xfer_params, None
elif request_id in self.waiting_for_transfer_free:
# Blocks held until KV extraction completes in a future step.
return None
return None, None
else:
logger.debug(
f"[Omni] Request {request_id} finished and transfer no longer ACTIVE (extracted/acked). "
@@ -819,14 +830,14 @@ class OmniARScheduler(OmniSchedulerMixin, VLLMScheduler):
if isinstance(add_info, dict):
add_info.update(kv_xfer_params)
return kv_xfer_params
return kv_xfer_params, None
# 3. Standard Freeing
delay_free_blocks |= connector_delay_free_blocks
if not delay_free_blocks:
self._free_blocks(request)
return kv_xfer_params
return kv_xfer_params, None
finally:
self._free_input_coordinator_request(request_id)
@@ -2,6 +2,7 @@ from __future__ import annotations
import time
from collections import defaultdict
from collections.abc import Iterable
from typing import Any
from vllm.compilation.cuda_graph import CUDAGraphStat
@@ -348,7 +349,7 @@ class OmniGenerationScheduler(OmniSchedulerMixin, VLLMScheduler):
return self._wrap_omni_scheduler_output(scheduler_output)
def finish_requests(self, request_ids, finished_status: RequestStatus) -> list[tuple[str, int]]:
def finish_requests(self, request_ids: str | Iterable[str] | None, finished_status: RequestStatus) -> list[Request]:
"""Handles the finish signal from outside the scheduler.
For example, the API server can abort a request when the client
@@ -357,8 +358,8 @@ class OmniGenerationScheduler(OmniSchedulerMixin, VLLMScheduler):
If request_ids is None, all requests will be finished.
Returns:
Tuple of (req_id, client_index) for requests that were aborted. Will not
include any that were already finished.
The Request objects that were aborted. Will not include any that
were already finished.
"""
if self.chunk_transfer_adapter:
@@ -382,11 +383,13 @@ class OmniGenerationScheduler(OmniSchedulerMixin, VLLMScheduler):
self._purge_finished_from_running()
if self.input_coordinator is not None:
for request_id, _ in finished:
self._free_input_coordinator_request(request_id)
for request in finished:
self._free_input_coordinator_request(request.request_id)
return finished
def _free_request(self, request: Request, delay_free_blocks: bool = False) -> dict[str, Any] | None:
def _free_request(
self, request: Request, delay_free_blocks: bool = False
) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
if self.input_coordinator is None:
return super()._free_request(request, delay_free_blocks)
@@ -432,10 +435,18 @@ class OmniGenerationScheduler(OmniSchedulerMixin, VLLMScheduler):
stopped_preempted_reqs: set[Request] = set()
for req_id, num_tokens_scheduled in num_scheduled_tokens.items():
assert num_tokens_scheduled > 0
request = self.requests.get(req_id)
if request is not None:
# vLLM 0.26: settle the in-flight tokens counted in schedule().
# Must happen before the skips below — failed-KV-load and
# already-finished requests were incremented too, and the two
# readers (allocate_slots, _connector_finished) clamp with
# max(0, computed - in_flight), so a leaked counter silently
# freezes sliding-window block freeing.
request.num_in_flight_tokens -= num_tokens_scheduled
if failed_kv_load_req_ids and req_id in failed_kv_load_req_ids:
# Skip requests that were recovered from KV load failure
continue
request = self.requests.get(req_id)
if request is None or request.is_finished():
# Request may already be finished (e.g., aborted during
# execution / pipeline parallelism / async scheduling).
@@ -510,7 +521,7 @@ class OmniGenerationScheduler(OmniSchedulerMixin, VLLMScheduler):
if self.chunk_transfer_adapter:
self.chunk_transfer_adapter.segment_finished_requests.discard(req_id)
if finished:
kv_transfer_params = self._free_request(request)
kv_transfer_params, _ = self._free_request(request)
if self.chunk_transfer_adapter is not None:
self.chunk_transfer_adapter.cleanup(
request.request_id,
@@ -576,7 +587,7 @@ class OmniGenerationScheduler(OmniSchedulerMixin, VLLMScheduler):
is_segment_finished = not finished
kv_transfer_params = None
if finished:
kv_transfer_params = self._free_request(request)
kv_transfer_params, _ = self._free_request(request)
if self.chunk_transfer_adapter is not None:
self.chunk_transfer_adapter.cleanup(
request.request_id,
+11
View File
@@ -69,3 +69,14 @@ stages:
config_format: mistral
load_format: mistral
skip_mm_profiling: true
platforms:
xpu:
stages:
- stage_id: 0
enforce_eager: true
gpu_memory_utilization: 0.7
distributed_executor_backend: "mp"
- stage_id: 1
gpu_memory_utilization: 0.28
distributed_executor_backend: "mp"
@@ -956,11 +956,10 @@ class Cosmos3OmniDiffusersPipeline(
self.weights_sources = [
DiffusersPipelineLoader.ComponentSource(
model_or_path=model_path,
subfolder=None,
subfolder="transformer",
revision=None,
prefix="transformer.",
fall_back_to_pt=True,
allow_patterns_overrides=["transformer/*.safetensors"],
),
]
@@ -384,7 +384,7 @@ class TokenizerWrapper:
drop_last_break = True
break
if source.get("front_boi", use_front_boi_token):
token_seq.append(self.boi_token_id) # Use patched boi for Janus, otherwise useing default <boi>
token_seq.append(self.boi_token_id) # Use patched boi for Janus, otherwise using default <boi>
extra_token_pos["boi"].append(token_count)
token_count += 1
token_count = self._add_image_meta_info_token(
@@ -1023,7 +1023,7 @@ class OmniKVTransferManager:
value_cache: list[torch.Tensor | None] = [None] * num_layers
for layer_idx, layer_kv in enumerate(kv_caches):
kv_pair = normalize_layer_kv(layer_kv, req_id=req_id, layer_idx=layer_idx)
kv_pair = normalize_layer_kv(layer_kv, req_id=req_id, layer_idx=layer_idx, block_size=block_size)
if kv_pair is None:
continue
key_blocks, value_blocks = kv_pair
@@ -317,7 +317,13 @@ class OmniChunkTransferAdapter(OmniTransferAdapterBase):
multimodal_output=multimodal_output,
request=request,
# Existing processors use is_finished as a flush signal.
is_finished=is_segment_finished,
# Terminal stops no longer count as segment boundaries
# (is_segment_finished is False when the request finishes,
# see #5383), but the processor must still flush its
# accumulated tail on the terminal chunk — otherwise the
# downstream stage receives the finished marker without
# the final payload (#5413).
is_finished=is_segment_finished or is_finished,
)
except Exception as e:
@@ -402,6 +402,7 @@ def normalize_layer_kv(
*,
req_id: str = "",
layer_idx: int = -1,
block_size: int | None = None,
) -> tuple[torch.Tensor, torch.Tensor] | None:
"""Normalize one layer KV cache to a ``(key_blocks, value_blocks)`` tuple.
@@ -424,6 +425,10 @@ def normalize_layer_kv(
dim-0 selects key / value.
* **Stacked tensor** ``[num_blocks, 2, block_size, n_heads, head_dim]`` –
dim-1 selects key / value.
* **Packed tensor** ``[num_blocks, n_heads, block_size, 2 * head_dim]`` –
vLLM >= 0.23 FlashAttention packs K and V into the last (content)
dimension. Only recognized when *block_size* is provided and matches
dim 2, to avoid misreading other 4-D layouts.
* **Tuple** ``(key_tensor, value_tensor)`` – returned as-is after
validation.
@@ -431,13 +436,29 @@ def normalize_layer_kv(
layer_kv: The raw KV cache (tensor or tuple) for the layer.
req_id: Request ID used only for diagnostic log messages.
layer_idx: Layer index used only for diagnostic log messages.
block_size: The paged-attention block size, required to recognize
the packed 4-D layout.
Returns:
``(key_blocks, value_blocks)`` if *layer_kv* is valid, ``None``
otherwise.
"""
if isinstance(layer_kv, torch.Tensor):
if layer_kv.ndim >= 3 and layer_kv.shape[0] == 2:
if (
layer_kv.ndim == 4
and block_size is not None
and layer_kv.shape[2] == block_size
and layer_kv.shape[-1] % 2 == 0
):
# vLLM >= 0.23 FlashAttention layout: (num_blocks, n_heads,
# block_size, 2 * head_dim). Unpack the same way
# FlashAttentionImpl does: move block_size before heads, then
# split the content dim into the K and V halves. Checked before
# the stacked layouts (which are 5-D in practice) so that a
# 2-KV-head packed tensor is not misread as a dim-1 stack.
head_dim = layer_kv.shape[-1] // 2
key_blocks, value_blocks = layer_kv.transpose(1, 2).split(head_dim, dim=-1)
elif layer_kv.ndim >= 3 and layer_kv.shape[0] == 2:
key_blocks = layer_kv[0]
value_blocks = layer_kv[1]
elif layer_kv.ndim >= 3 and layer_kv.shape[1] == 2:
@@ -446,7 +467,8 @@ def normalize_layer_kv(
else:
logger.warning(
f"Layer {layer_idx} for request {req_id} has invalid stacked KV shape: "
f"expected [2, ...] or [..., 2, ...] at dim 0/1, got {tuple(layer_kv.shape)}"
f"expected [2, ...], [..., 2, ...] at dim 0/1, or packed "
f"[num_blocks, n_heads, block_size, 2*head_dim], got {tuple(layer_kv.shape)}"
)
return None
elif isinstance(layer_kv, tuple):
+7 -15
View File
@@ -681,24 +681,16 @@ class Orchestrator:
final_stage_id = msg.final_stage_id
req_state = self.request_states.get(request_id)
if req_state is None:
# Streaming updates always follow the first-chunk add_request
# through the same ordered submission queue, so an unknown id
# here can only mean the request already finished or was
# aborted (e.g. the client disconnected). Re-adding it would
# resurrect a headless session that keeps cycling through the
# stages with nobody consuming its outputs (issue #4271).
logger.warning(
"[Orchestrator] streaming_update for unknown req=%s, falling back to add_request",
"[Orchestrator] streaming_update for unknown req=%s; dropping (request finished or aborted)",
request_id,
)
fallback_msg = StageSubmissionMessage(
type="add_request",
request_id=msg.request_id,
prompt=msg.prompt,
original_prompt=msg.original_prompt,
output_prompt_text=msg.output_prompt_text,
sampling_params_list=msg.sampling_params_list,
final_stage_id=msg.final_stage_id,
final_output_stage_ids=msg.final_output_stage_ids,
preprocess_ms=msg.preprocess_ms,
request_timestamp=msg.request_timestamp,
enqueue_ts=msg.enqueue_ts,
)
await self._handle_add_request(fallback_msg)
return
if msg.sampling_params_list:
+11 -1
View File
@@ -628,6 +628,13 @@ class AsyncOmni(EngineClient, OmniBase):
logger.debug(f"[AsyncOmni] Request {request_id} completed")
# The input pump can outlive a normally-completed generation
# (e.g. a realtime client that keeps streaming after the final
# output); a live pump keeps issuing streaming updates for a
# request the orchestrator no longer tracks.
if input_stream_task is not None and not input_stream_task.done():
input_stream_task.cancel()
self._log_summary_and_cleanup(request_id)
except (asyncio.CancelledError, GeneratorExit):
@@ -1059,7 +1066,10 @@ class AsyncOmni(EngineClient, OmniBase):
"""Submit request IDs to be aborted to the engine."""
await self.engine.abort_async(request_ids)
for rid in request_ids:
self.request_states.pop(rid, None)
state = self.request_states.pop(rid, None)
input_stream_task = getattr(state, "input_stream_task", None)
if input_stream_task is not None and not input_stream_task.done():
input_stream_task.cancel()
if self.log_stats:
logger.info("[AsyncOmni] Aborted request(s) %s", ",".join(request_ids))
@@ -138,6 +138,7 @@ class RealtimeConnection(VllmRealtimeConnection):
is_streaming=True,
)
result_gen = None
try:
result_gen = self.engine.generate(
prompt=streaming_input_gen,
@@ -196,6 +197,16 @@ class RealtimeConnection(VllmRealtimeConnection):
logger.exception("Error in generation: %s", e)
await self.send_error(str(e), "processing_error")
finally:
# Close the generator explicitly so AsyncOmni.generate's cleanup
# (input-pump cancellation and engine-side abort) runs now rather
# than whenever the event loop garbage-collects the async
# generator; the delay window is where a disconnected session
# keeps cycling through the stages (issue #4271).
if result_gen is not None:
try:
await result_gen.aclose()
except Exception:
logger.exception("Failed to close realtime result generator")
# Always send terminal event so clients don't hang forever.
if self._is_connected and not audio_done_sent:
try:
@@ -206,4 +217,10 @@ class RealtimeConnection(VllmRealtimeConnection):
self.audio_queue.get_nowait()
async def send_json(self, payload: dict):
await self.websocket.send_text(json.dumps(payload))
try:
await self.websocket.send_text(json.dumps(payload))
except Exception:
# A failed send means the client is gone; flag it so the
# generation loop stops instead of retrying into a dead socket.
self._is_connected = False
raise
@@ -71,6 +71,11 @@ class ARDiffusionRequestAdapter:
self.num_preemptions = 0
# vLLM watermark gate reads this; map the request lifecycle onto it.
self.status = RequestStatus.WAITING
# vLLM 0.26 KVCacheManager.allocate_slots reads this when computing
# remove_skipped_blocks. Always 0 here: an AR-Diffusion request only
# advances num_computed_tokens on on_chunk_committed(), so there are
# never optimistically-counted in-flight tokens to subtract.
self.num_in_flight_tokens = 0
@property
def num_computed_tokens(self) -> int:
@@ -30,6 +30,20 @@ from .batched_token2wav import (
logger = init_logger(__name__)
def _resolve_model_dir(model_ref: str, revision: str | None = None) -> str:
"""Resolve ``model_ref`` to a local directory containing the repo assets.
``model_config.model`` is a filesystem path in local deployments but a
Hugging Face repo id in hub/CI deployments; the prompt-audio and
token2wav asset lookups need a real directory either way.
"""
if Path(model_ref).is_dir():
return model_ref
from huggingface_hub import snapshot_download
return snapshot_download(model_ref, revision=revision, allow_patterns=["assets/*"])
def _batch_error(reason: str, **details: Any) -> RuntimeError:
payload = {"reason": reason, **details}
return RuntimeError(f"MiniCPMO45Code2WavBatchError {json.dumps(payload, sort_keys=True)}")
@@ -118,6 +132,7 @@ class MiniCPMO45Code2Wav(nn.Module):
del prefix
self.vllm_config = vllm_config
self.model_path = str(vllm_config.model_config.model)
self._model_revision = getattr(vllm_config.model_config, "revision", None)
self.backend: BatchedToken2Wav | None = None
self._states: dict[str, _RequestState] = {}
self._owned_prompt_wavs: dict[str, tuple[str, str]] = {}
@@ -126,12 +141,13 @@ class MiniCPMO45Code2Wav(nn.Module):
if self._min_batch_size < 1:
raise ValueError("MiniCPM-o Code2Wav code2wav_min_batch_size must be >= 1")
self._default_prompt_id = str(extra.get("prompt_cache_id", "HT_ref_audio"))
self._default_prompt_wav = str(
extra.get(
"prompt_wav",
Path(self.model_path) / "assets" / "HT_ref_audio.wav",
)
)
self._prompt_wav_override = extra.get("prompt_wav")
@property
def _default_prompt_wav(self) -> str:
if self._prompt_wav_override is not None:
return str(self._prompt_wav_override)
return str(Path(self.model_path) / "assets" / "HT_ref_audio.wav")
def _extra_config(self) -> dict[str, Any]:
model_config = getattr(self.vllm_config, "model_config", None)
@@ -600,6 +616,10 @@ class MiniCPMO45Code2Wav(nn.Module):
from stepaudio2.token2wav import Token2wav
extra = self._extra_config()
# Hub repo ids only need to become local directories once the vocoder
# assets are actually read; unit tests construct this model with fake
# paths and must not trigger a hub download (#5442).
self.model_path = _resolve_model_dir(self.model_path, self._model_revision)
prompt_path = Path(self._default_prompt_wav)
if not prompt_path.is_file():
raise FileNotFoundError(f"MiniCPM-o Code2Wav prompt audio not found: {prompt_path}")
@@ -105,11 +105,6 @@ from vllm.multimodal.inputs import (
MultiModalKwargsItems,
NestedTensors,
)
try:
from vllm.multimodal.inputs import ModalityData, MultiModalDataDict
except ImportError:
from vllm.multimodal.parse import ModalityData, MultiModalDataDict
from vllm.multimodal.parse import (
AudioItem,
AudioProcessorItems,
@@ -132,15 +127,10 @@ from vllm.multimodal.processing import (
)
from vllm.sequence import IntermediateTensors
try:
from vllm.transformers_utils.tokenizer import encode_tokens as _vllm_encode_tokens
except ImportError:
_vllm_encode_tokens = None
# vllm.transformers_utils.tokenizer no longer exists in upstream vLLM;
# _encode_tokens uses tokenizer.encode() as the only code path.
def _encode_tokens(tokenizer: Any, prompt: str) -> list[int]:
if _vllm_encode_tokens is not None:
return _vllm_encode_tokens(tokenizer, prompt)
return tokenizer.encode(prompt, add_special_tokens=False)
@@ -1,6 +1,6 @@
"""CUDA Graph acceleration for the MOSS Audio Tokenizer codec decoder.
Captures MossAudioTokenizerModel._decode_frame for a set of fixed frame-count
Captures MossAudioTokenizerModel._decode for a set of fixed frame-count
bucket sizes, then replays the captured graph at inference time to eliminate
kernel-launch overhead. Inputs that exceed all captured sizes fall back to
eager execution transparently.
@@ -24,7 +24,7 @@ logger = init_logger(__name__)
class MossTTSCUDAGraphCodecWrapper:
"""CUDA Graph wrapper for MossAudioTokenizerModel._decode_frame.
"""CUDA Graph wrapper for MossAudioTokenizerModel._decode.
Graphs are keyed by padded_T (int). On each call the actual T is
bucket-matched to the smallest pre-captured size >= T. The static code
@@ -99,7 +99,7 @@ class MossTTSCUDAGraphCodecWrapper:
dummy_codes = torch.zeros(nq, 1, size, dtype=torch.long, device=device)
dummy_lengths = torch.tensor([size], dtype=torch.long, device=device)
with torch.no_grad():
_ = self.model._decode_frame(dummy_codes, dummy_lengths)
_ = self.model._decode(dummy_codes, dummy_lengths)
torch.accelerator.synchronize(device)
@@ -132,13 +132,13 @@ class MossTTSCUDAGraphCodecWrapper:
# Extra eager warmup inside capture to ensure all kernels are compiled.
with torch.no_grad():
_ = self.model._decode_frame(static_codes, static_lengths)
_ = self.model._decode(static_codes, static_lengths)
torch.accelerator.synchronize(device)
graph = CUDAGraph()
with torch.no_grad():
with torch.cuda.graph(graph, pool=current_platform.get_global_graph_pool()):
static_out = self.model._decode_frame(static_codes, static_lengths)
static_out = self.model._decode(static_codes, static_lengths)
self.graphs[size] = graph
self.static_codes[size] = static_codes
@@ -1342,8 +1342,6 @@ class Qwen2_5OmniThinkerForConditionalGeneration(
is_video,
is_audio,
is_multimodal,
num_video,
num_audio,
)
# Default: standard merge (no interleaving), same as parent class
@@ -114,6 +114,7 @@ from vllm.multimodal.processing.processor import (
PromptUpdate,
PromptUpdateDetails,
)
from vllm.multimodal.utils import set_mm_embedding_modality
from vllm.sequence import IntermediateTensors
from vllm.transformers_utils.processor import cached_processor_from_config
@@ -1420,6 +1421,13 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
visual_dim = embeddings.shape[-1] // (multiscale_len + 1)
multi_dim = visual_dim * multiscale_len
embeddings_main, embeddings_multiscale = torch.split(embeddings, [visual_dim, multi_dim], dim=-1)
# torch.split returns fresh views, so the `modality` tag the
# encoder gather attached (gpu_model_runner._gather_mm_embeddings)
# does not survive. merge_interleaved_embeddings groups by that
# tag since vLLM #46213, so carry it across the split.
modality = getattr(embeddings, "modality", None)
if modality is not None:
set_mm_embedding_modality(embeddings_main, modality)
multimodal_embeddings[index] = embeddings_main
multimodal_embeddings_multiscale.append(embeddings_multiscale)
if not is_interleaved:
@@ -1457,8 +1465,6 @@ class Qwen3OmniMoeThinkerForConditionalGeneration(
is_video,
is_audio,
is_mm_device,
num_video,
num_audio,
)
# Default: standard merge (no interleaving), same as parent class.
+10
View File
@@ -287,6 +287,16 @@ class CudaOmniPlatform(OmniPlatform, CudaPlatformBase):
def get_device_name(cls, device_id: int = 0) -> str:
return torch.cuda.get_device_name(device_id)
@classmethod
def get_device_total_memory(cls, device_id: int = 0) -> int:
device_props = torch.cuda.get_device_properties(device_id)
return device_props.total_memory
@classmethod
def is_fully_connected(cls, device_ids: list[int]) -> bool:
logger.debug("NVLink detection not available on CudaOmniPlatform; assuming no NVLink.")
return False
@classmethod
def get_default_ir_op_priority(cls, vllm_config: VllmConfig) -> IrOpPriorityConfig:
"""Prefer ``vllm_c`` CUDA kernels over ``native`` for diffusion IR ops."""
@@ -1,106 +0,0 @@
# Voxtral TTS — Intel XPU (AR → audio tokenizer). Matches CUDA stage config knobs where noted.
async_chunk: true
stage_args:
- stage_id: 0
stage_type: llm
runtime:
process: true
devices: "0"
max_batch_size: 1
engine_args:
max_num_seqs: 32
model_stage: audio_generation
model_arch: VoxtralTTSForConditionalGeneration
worker_type: ar
worker_cls: vllm_omni.platforms.xpu.worker.xpu_ar_worker.XPUARWorker
scheduler_cls: vllm_omni.core.sched.omni_ar_scheduler.OmniARScheduler
gpu_memory_utilization: 0.7
enforce_eager: true
trust_remote_code: true
async_scheduling: true
engine_output_type: latent
enable_prefix_caching: false
tokenizer_mode: mistral
config_format: mistral
load_format: mistral
skip_mm_profiling: true
enable_chunked_prefill: false
max_model_len: 4096
distributed_executor_backend: "mp"
custom_process_next_stage_input_func: vllm_omni.model_executor.stage_input_processors.voxtral_tts.generator2tokenizer_async_chunk
output_connectors:
to_stage_1: connector_of_shared_memory
is_comprehension: true
final_output: false
final_output_type: text
default_sampling_params:
temperature: 0.0
top_p: 1.0
top_k: -1
max_tokens: 2048
seed: 42
detokenize: True
repetition_penalty: 1.1
- stage_id: 1
stage_type: llm
runtime:
process: true
devices: "0"
max_batch_size: 1
engine_args:
model_stage: audio_tokenizer
model_arch: VoxtralTTSForConditionalGeneration
worker_type: generation
worker_cls: vllm_omni.platforms.xpu.worker.xpu_generation_worker.XPUGenerationWorker
scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
async_scheduling: false
max_num_seqs: 1
gpu_memory_utilization: 0.28
enforce_eager: true
trust_remote_code: true
enable_prefix_caching: false
skip_mm_profiling: true
engine_output_type: audio
tokenizer_mode: mistral
config_format: mistral
load_format: mistral
max_num_batched_tokens: 16384
max_model_len: 16384
distributed_executor_backend: "mp"
engine_input_source: [0]
is_comprehension: false
final_output: true
final_output_type: audio
input_connectors:
from_stage_0: connector_of_shared_memory
tts_args:
max_instructions_length: 500
default_sampling_params:
temperature: 0.9
top_p: 0.8
top_k: 40
max_tokens: 2048
seed: 42
detokenize: True
repetition_penalty: 1.05
runtime:
enabled: true
connectors:
connector_of_shared_memory:
name: SharedMemoryConnector
extra:
codec_streaming: true
connector_get_sleep_s: 0.01
connector_get_max_wait_first_chunk: 3000
connector_get_max_wait: 300
codec_chunk_frames: 25
codec_chunk_frames_at_begin: 5
codec_left_context_frames: 25
edges:
- from: 0
to: 1
+2 -3
View File
@@ -22,9 +22,10 @@ from vllm.distributed.kv_transfer import get_kv_transfer_group, has_kv_transfer_
from vllm.distributed.parallel_state import get_pp_group, get_tp_group
from vllm.forward_context import set_forward_context
from vllm.logger import init_logger
from vllm.sequence import IntermediateTensors
from vllm.utils.platform_utils import is_pin_memory_available
from vllm.v1.core.sched.output import GrammarOutput, SchedulerOutput
from vllm.v1.outputs import AsyncModelRunnerOutput, make_empty_encoder_model_runner_output
from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT, AsyncModelRunnerOutput, make_empty_encoder_model_runner_output
from vllm.v1.spec_decode.dflash import DFlashProposer
from vllm.v1.spec_decode.draft_model import DraftModelProposer
from vllm.v1.spec_decode.eagle import EagleProposer
@@ -33,9 +34,7 @@ from vllm.v1.spec_decode.gemma4 import Gemma4Proposer
from vllm.v1.structured_output.utils import apply_grammar_bitmask
from vllm.v1.utils import record_function_or_nullcontext
from vllm.v1.worker.gpu_model_runner import (
EMPTY_MODEL_RUNNER_OUTPUT,
AsyncGPUModelRunnerOutput,
IntermediateTensors,
)
from vllm.v1.worker.ubatch_utils import maybe_create_ubatch_slices
from vllm.v1.worker.utils import is_residual_scattered_for_sp
@@ -18,9 +18,10 @@ from vllm.distributed.ec_transfer import get_ec_transfer, has_ec_transfer
from vllm.distributed.kv_transfer import get_kv_transfer_group, has_kv_transfer_group
from vllm.distributed.parallel_state import get_pp_group
from vllm.forward_context import set_forward_context
from vllm.sequence import IntermediateTensors
from vllm.utils.math_utils import cdiv
from vllm.v1.core.sched.output import GrammarOutput, SchedulerOutput
from vllm.v1.outputs import AsyncModelRunnerOutput, make_empty_encoder_model_runner_output
from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT, AsyncModelRunnerOutput, make_empty_encoder_model_runner_output
from vllm.v1.spec_decode.dflash import DFlashProposer
from vllm.v1.spec_decode.draft_model import DraftModelProposer
from vllm.v1.spec_decode.eagle import EagleProposer
@@ -28,9 +29,7 @@ from vllm.v1.spec_decode.extract_hidden_states import ExtractHiddenStatesPropose
from vllm.v1.spec_decode.gemma4 import Gemma4Proposer
from vllm.v1.utils import record_function_or_nullcontext
from vllm.v1.worker.gpu_model_runner import (
EMPTY_MODEL_RUNNER_OUTPUT,
AsyncGPUModelRunnerOutput,
IntermediateTensors,
PerLayerAttnMetadata,
)
from vllm.v1.worker.ubatch_utils import maybe_create_ubatch_slices
+2 -1
View File
@@ -13,6 +13,7 @@ from vllm.logger import init_logger
from vllm.model_executor.models.interfaces import supports_mrope
from vllm.model_executor.models.interfaces_base import VllmModelForPooling
from vllm.sampling_params import SamplingType
from vllm.sequence import IntermediateTensors
from vllm.tracing import instrument
from vllm.utils.import_utils import LazyLoader
from vllm.utils.math_utils import cdiv
@@ -26,7 +27,7 @@ from vllm.v1.spec_decode.ngram_proposer_gpu import (
update_scheduler_for_invalid_drafts,
)
from vllm.v1.worker.gpu_input_batch import CachedRequestState
from vllm.v1.worker.gpu_model_runner import GPUModelRunner, IntermediateTensors, PerLayerAttnMetadata
from vllm.v1.worker.gpu_model_runner import GPUModelRunner, PerLayerAttnMetadata
from vllm.v1.worker.ubatch_utils import maybe_create_ubatch_slices
from vllm_omni.core.prefix_cache import OmniTensorPrefixCache
@@ -1575,7 +1575,7 @@ class OmniConnectorModelRunnerMixin:
from copy import copy
from vllm.v1.worker.gpu_model_runner import EMPTY_MODEL_RUNNER_OUTPUT
from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT
wrapped = copy(result if result is not None else EMPTY_MODEL_RUNNER_OUTPUT)
wrapped.omni_connector_output = omni_output