Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
298042732c | ||
|
|
29dcdec66d | ||
|
|
9eaca72382 | ||
|
|
51c14eeaed | ||
|
|
e73245c60f | ||
|
|
f7cdc45977 | ||
|
|
46f10478dc |
No files matched your search
@@ -0,0 +1,114 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from vllm.v1.request import RequestStatus
|
||||||
|
|
||||||
|
import vllm_omni.core.sched.omni_ar_scheduler as scheduler_mod
|
||||||
|
from vllm_omni.model_executor.models.voxcpm2.scheduler import VoxCPM2OmniARAsyncScheduler
|
||||||
|
|
||||||
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||||
|
|
||||||
|
|
||||||
|
class _MockQueue:
|
||||||
|
def __init__(self, items: list | None = None) -> None:
|
||||||
|
self._items = list(items or [])
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
return bool(self._items)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._items)
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self._items)
|
||||||
|
|
||||||
|
def add_request(self, request) -> None:
|
||||||
|
self._items.append(request)
|
||||||
|
|
||||||
|
def prepend_requests(self, requests) -> None:
|
||||||
|
self._items = list(requests) + self._items
|
||||||
|
|
||||||
|
|
||||||
|
class _MockRequest:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
request_id: str,
|
||||||
|
*,
|
||||||
|
status: RequestStatus = RequestStatus.RUNNING,
|
||||||
|
num_prompt_tokens: int = 4,
|
||||||
|
num_computed_tokens: int = 4,
|
||||||
|
num_output_placeholders: int = 0,
|
||||||
|
) -> None:
|
||||||
|
self.request_id = request_id
|
||||||
|
self.status = status
|
||||||
|
self.num_prompt_tokens = num_prompt_tokens
|
||||||
|
self.num_computed_tokens = num_computed_tokens
|
||||||
|
self.num_output_placeholders = num_output_placeholders
|
||||||
|
|
||||||
|
def is_finished(self) -> bool:
|
||||||
|
return RequestStatus.is_finished(self.status)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_scheduler(*, enable_unified_decode_graph: bool = True) -> VoxCPM2OmniARAsyncScheduler:
|
||||||
|
sched = VoxCPM2OmniARAsyncScheduler.__new__(VoxCPM2OmniARAsyncScheduler)
|
||||||
|
runtime_config = SimpleNamespace(enable_unified_decode_graph=enable_unified_decode_graph)
|
||||||
|
sched.vllm_config = SimpleNamespace(
|
||||||
|
model_config=SimpleNamespace(
|
||||||
|
hf_config=SimpleNamespace(voxcpm2_runtime_config=runtime_config),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return sched
|
||||||
|
|
||||||
|
|
||||||
|
def test_voxcpm2_unified_decode_graph_defers_waiting_when_decode_ready() -> None:
|
||||||
|
scheduler = _make_scheduler()
|
||||||
|
scheduler.running = [_MockRequest("decode")]
|
||||||
|
scheduler.waiting = _MockQueue([_MockRequest("prefill", status=RequestStatus.WAITING)])
|
||||||
|
|
||||||
|
assert scheduler._should_defer_waiting_for_unified_decode_graph()
|
||||||
|
|
||||||
|
|
||||||
|
def test_voxcpm2_unified_decode_graph_does_not_defer_without_decode_ready() -> None:
|
||||||
|
scheduler = _make_scheduler()
|
||||||
|
scheduler.running = [_MockRequest("prefill-running", num_prompt_tokens=8, num_computed_tokens=4)]
|
||||||
|
scheduler.waiting = _MockQueue([_MockRequest("waiting", status=RequestStatus.WAITING)])
|
||||||
|
|
||||||
|
assert not scheduler._should_defer_waiting_for_unified_decode_graph()
|
||||||
|
|
||||||
|
|
||||||
|
def test_voxcpm2_unified_decode_graph_does_not_defer_when_disabled() -> None:
|
||||||
|
scheduler = _make_scheduler(enable_unified_decode_graph=False)
|
||||||
|
scheduler.running = [_MockRequest("decode")]
|
||||||
|
scheduler.waiting = _MockQueue([_MockRequest("prefill", status=RequestStatus.WAITING)])
|
||||||
|
|
||||||
|
assert not scheduler._should_defer_waiting_for_unified_decode_graph()
|
||||||
|
|
||||||
|
|
||||||
|
def test_unified_decode_graph_deferral_restores_waiting_queue(monkeypatch) -> None:
|
||||||
|
scheduler = _make_scheduler()
|
||||||
|
scheduler.running = [_MockRequest("decode")]
|
||||||
|
original_waiting_req = _MockRequest("waiting", status=RequestStatus.WAITING)
|
||||||
|
deferred_by_upstream = _MockRequest("deferred-by-upstream", status=RequestStatus.WAITING)
|
||||||
|
original_waiting = _MockQueue([original_waiting_req])
|
||||||
|
scheduler.waiting = original_waiting
|
||||||
|
scheduler.policy = "fcfs"
|
||||||
|
scheduler.chunk_transfer_adapter = None
|
||||||
|
scheduler.input_coordinator = None
|
||||||
|
scheduler._consume_pending_connector_output = lambda model_mode: None
|
||||||
|
|
||||||
|
monkeypatch.setattr(scheduler_mod, "create_request_queue", lambda _policy: _MockQueue())
|
||||||
|
|
||||||
|
def fake_upstream_schedule(self, throttle_prefills: bool = False):
|
||||||
|
assert self.waiting is not original_waiting
|
||||||
|
assert not self.waiting
|
||||||
|
self.waiting.add_request(deferred_by_upstream)
|
||||||
|
raise RuntimeError("stop before output wrapping")
|
||||||
|
|
||||||
|
monkeypatch.setattr(scheduler_mod.VLLMScheduler, "schedule", fake_upstream_schedule)
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError, match="stop before output wrapping"):
|
||||||
|
scheduler.schedule()
|
||||||
|
|
||||||
|
assert scheduler.waiting._items == [deferred_by_upstream, original_waiting_req]
|
||||||
@@ -7,7 +7,7 @@ and are supported by Qwen-Image-Layered model.
|
|||||||
Kept cases (maximum feature coverage, no cpu-offload):
|
Kept cases (maximum feature coverage, no cpu-offload):
|
||||||
sp_001 : cache_dit + Ulysses-SP 2 (2×H100)
|
sp_001 : cache_dit + Ulysses-SP 2 (2×H100)
|
||||||
cfg_parallel_002 : cache_dit + CFG-Parallel 2 (2×H100)
|
cfg_parallel_002 : cache_dit + CFG-Parallel 2 (2×H100)
|
||||||
layers_guard_001 : layerwise offload + layers=3 (1×H100, issue #1969 guard)
|
layers_guard_001 : layerwise offload + layers=2 (1×H100, issue #1969 guard)
|
||||||
|
|
||||||
Total distinct features covered: cache_dit, Ulysses-SP, CFG-Parallel, layerwise-offload.
|
Total distinct features covered: cache_dit, Ulysses-SP, CFG-Parallel, layerwise-offload.
|
||||||
"""
|
"""
|
||||||
@@ -116,8 +116,8 @@ LAYERS_GUARD_CASES = [
|
|||||||
model=MODEL,
|
model=MODEL,
|
||||||
server_args=["--enable-layerwise-offload"],
|
server_args=["--enable-layerwise-offload"],
|
||||||
),
|
),
|
||||||
3,
|
2,
|
||||||
id="layers_guard_001_layers3",
|
id="layers_guard_001_layers2",
|
||||||
marks=SINGLE_CARD_FEATURE_MARKS,
|
marks=SINGLE_CARD_FEATURE_MARKS,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1244,7 +1244,7 @@ def test_parameter_validation():
|
|||||||
|
|
||||||
# Invalid layers for layered models (must stay within the backend-supported range)
|
# Invalid layers for layered models (must stay within the backend-supported range)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ImageGenerationRequest(prompt="test", layers=2)
|
ImageGenerationRequest(prompt="test", layers=1)
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ImageGenerationRequest(prompt="test", layers=11)
|
ImageGenerationRequest(prompt="test", layers=11)
|
||||||
@@ -1663,13 +1663,13 @@ def test_image_edit_invalid_layers(async_omni_test_client):
|
|||||||
files=[("image", img_bytes)],
|
files=[("image", img_bytes)],
|
||||||
data={
|
data={
|
||||||
"prompt": "test",
|
"prompt": "test",
|
||||||
"layers": 2,
|
"layers": 1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
detail = response.json()["detail"]
|
detail = response.json()["detail"]
|
||||||
assert "Invalid layers" in detail
|
assert "Invalid layers" in detail
|
||||||
assert "layers must be between 3 and 10 inclusive" in detail
|
assert "layers must be between 2 and 10 inclusive" in detail
|
||||||
|
|
||||||
# Test layers above the supported range
|
# Test layers above the supported range
|
||||||
response = async_omni_test_client.post(
|
response = async_omni_test_client.post(
|
||||||
@@ -1683,7 +1683,7 @@ def test_image_edit_invalid_layers(async_omni_test_client):
|
|||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
detail = response.json()["detail"]
|
detail = response.json()["detail"]
|
||||||
assert "Invalid layers" in detail
|
assert "Invalid layers" in detail
|
||||||
assert "layers must be between 3 and 10 inclusive" in detail
|
assert "layers must be between 2 and 10 inclusive" in detail
|
||||||
|
|
||||||
|
|
||||||
def test_image_edit_resolution_and_size_conflict(async_omni_test_client):
|
def test_image_edit_resolution_and_size_conflict(async_omni_test_client):
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ from vllm_omni.entrypoints.openai.serving_speech import (
|
|||||||
OmniOpenAIServingSpeech,
|
OmniOpenAIServingSpeech,
|
||||||
_create_wav_header,
|
_create_wav_header,
|
||||||
)
|
)
|
||||||
|
from vllm_omni.entrypoints.openai.tts_adapters.base import PreparedRequest
|
||||||
from vllm_omni.model_executor.models.fish_speech.prompt_utils import (
|
from vllm_omni.model_executor.models.fish_speech.prompt_utils import (
|
||||||
FISH_TEXT_ONLY_SYSTEM_PROMPT,
|
FISH_TEXT_ONLY_SYSTEM_PROMPT,
|
||||||
build_fish_voice_clone_prompt_ids,
|
build_fish_voice_clone_prompt_ids,
|
||||||
@@ -3343,6 +3344,40 @@ class TestTTSAsyncOffloading:
|
|||||||
qwen3_tts_server._build_tts_params.assert_called_once()
|
qwen3_tts_server._build_tts_params.assert_called_once()
|
||||||
qwen3_tts_server._estimate_prompt_len_async.assert_awaited_once()
|
qwen3_tts_server._estimate_prompt_len_async.assert_awaited_once()
|
||||||
|
|
||||||
|
def test_prepare_speech_generation_uses_adapter_model_type_label(
|
||||||
|
self,
|
||||||
|
voxtral_server,
|
||||||
|
mocker: MockerFixture,
|
||||||
|
):
|
||||||
|
"""Adapter model_type should replace the legacy _tts_model_type label ladder."""
|
||||||
|
legacy_tts_model_type = "dummy_tts"
|
||||||
|
adapter_model_type = "adapter_dummy_tts"
|
||||||
|
|
||||||
|
class FakeAdapter:
|
||||||
|
def validate(self, request):
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def build(self, request, sampling_params_list, has_inline_ref_audio):
|
||||||
|
return PreparedRequest(
|
||||||
|
prompt={"prompt": request.input},
|
||||||
|
tts_params={},
|
||||||
|
model_type=adapter_model_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
voxtral_server._tts_model_type = legacy_tts_model_type
|
||||||
|
mocker.patch.object(voxtral_server, "_get_tts_adapter", return_value=FakeAdapter())
|
||||||
|
log_info = mocker.patch("vllm_omni.entrypoints.openai.serving_speech.logger.info")
|
||||||
|
|
||||||
|
asyncio.run(voxtral_server._prepare_speech_generation(OpenAICreateSpeechRequest(input="hello")))
|
||||||
|
|
||||||
|
assert adapter_model_type != legacy_tts_model_type
|
||||||
|
assert any(
|
||||||
|
call.args
|
||||||
|
and call.args[0] == "TTS speech request %s: text=%r, model=%s"
|
||||||
|
and call.args[3] == adapter_model_type
|
||||||
|
for call in log_info.call_args_list
|
||||||
|
)
|
||||||
|
|
||||||
def test_prepare_speech_generation_treats_sse_as_streaming(self, qwen3_tts_server, mocker: MockerFixture):
|
def test_prepare_speech_generation_treats_sse_as_streaming(self, qwen3_tts_server, mocker: MockerFixture):
|
||||||
"""stream_format=sse should request delta-style multimodal outputs."""
|
"""stream_format=sse should request delta-style multimodal outputs."""
|
||||||
qwen3_tts_server._validate_tts_request = mocker.MagicMock(return_value=None)
|
qwen3_tts_server._validate_tts_request = mocker.MagicMock(return_value=None)
|
||||||
|
|||||||
@@ -633,6 +633,18 @@ def get_deploy_config_path(rel_path: str) -> str:
|
|||||||
return str(_DEPLOY_DIR / rel_path)
|
return str(_DEPLOY_DIR / rel_path)
|
||||||
|
|
||||||
|
|
||||||
|
def get_deploy_config_stage(rel_path: str, stage_id: int) -> dict[str, Any]:
|
||||||
|
"""Return one stage entry from a deploy yaml by ``stage_id``."""
|
||||||
|
with open(get_deploy_config_path(rel_path), encoding="utf-8") as f:
|
||||||
|
cfg = yaml.safe_load(f) or {}
|
||||||
|
|
||||||
|
stage_key = "stages" if "stages" in cfg else "stage_args"
|
||||||
|
for stage in cfg.get(stage_key, []):
|
||||||
|
if stage.get("stage_id") == stage_id:
|
||||||
|
return stage
|
||||||
|
raise KeyError(f"No stage_id={stage_id} in deploy config {rel_path!r}")
|
||||||
|
|
||||||
|
|
||||||
def _get_config_value_by_path(config_dict: dict, path: str) -> Any:
|
def _get_config_value_by_path(config_dict: dict, path: str) -> Any:
|
||||||
"""Read a dot-separated path from a nested dict (e.g. ``engine_args.load_format``)."""
|
"""Read a dot-separated path from a nested dict (e.g. ``engine_args.load_format``)."""
|
||||||
current: Any = config_dict
|
current: Any = config_dict
|
||||||
@@ -707,6 +719,7 @@ def stage_config_path_for_run_level(stage_config_path: str | None, run_level: st
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"get_deploy_config_path",
|
"get_deploy_config_path",
|
||||||
|
"get_deploy_config_stage",
|
||||||
"modify_stage_config",
|
"modify_stage_config",
|
||||||
"stage_config_path_for_run_level",
|
"stage_config_path_for_run_level",
|
||||||
]
|
]
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||||
|
"""Static regression checks for VoxCPM2 runner-assisted unified graph hooks.
|
||||||
|
|
||||||
|
These tests intentionally avoid importing torch/vLLM so they can run in a
|
||||||
|
lightweight local checkout. Runtime audio quality still requires a CUDA test.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parents[4]
|
||||||
|
RUNNER = REPO_ROOT / "vllm_omni" / "worker" / "gpu_ar_model_runner.py"
|
||||||
|
RUNNER_ASSISTED_METADATA = REPO_ROOT / "vllm_omni" / "worker" / "runner_assisted_metadata.py"
|
||||||
|
TALKER = REPO_ROOT / "vllm_omni" / "model_executor" / "models" / "voxcpm2" / "voxcpm2_talker.py"
|
||||||
|
VOXCPM2_PIPELINE = REPO_ROOT / "vllm_omni" / "model_executor" / "models" / "voxcpm2" / "pipeline.py"
|
||||||
|
VOXCPM2_SCHEDULER = REPO_ROOT / "vllm_omni" / "model_executor" / "models" / "voxcpm2" / "scheduler.py"
|
||||||
|
OMNI_AR_SCHEDULER = REPO_ROOT / "vllm_omni" / "core" / "sched" / "omni_ar_scheduler.py"
|
||||||
|
DEPLOY = REPO_ROOT / "vllm_omni" / "deploy" / "voxcpm2.yaml"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ar_runner_exposes_runner_assisted_full_metadata_hook():
|
||||||
|
source = RUNNER.read_text()
|
||||||
|
|
||||||
|
assert "Models without this hook keep the normal runner path" in source
|
||||||
|
assert "get_runner_assisted_full_attention_metadata_request" in source
|
||||||
|
assert "set_runner_assisted_full_attention_metadata_context" in source
|
||||||
|
assert "runner_assisted_full_attn" in source
|
||||||
|
assert "runner_assisted_full_attn_capture" in source
|
||||||
|
assert "pad_attn = cudagraph_mode == CUDAGraphMode.FULL or runner_assisted_full_attn" in source
|
||||||
|
assert "for_cudagraph_capture=runner_assisted_full_attn_capture" in source
|
||||||
|
assert "cudagraph_runtime_mode=(" in source
|
||||||
|
assert "CUDAGraphMode.FULL if runner_assisted_full_attn else cudagraph_mode" in source
|
||||||
|
refresh_source = source[source.index("def _refresh_runner_assisted_full_attention_metadata_buffers") :]
|
||||||
|
refresh_source = refresh_source[: refresh_source.index("def _set_runner_assisted_full_attention_metadata_context")]
|
||||||
|
assert "num_computed_tokens_cpu" in refresh_source
|
||||||
|
assert "num_scheduled_tokens_np" in refresh_source
|
||||||
|
assert "optimistic_seq_lens_cpu[:num_reqs]=1" not in "".join(refresh_source.split())
|
||||||
|
padding_source = source[
|
||||||
|
source.index("runner_assisted_full_attn_request = self._get_runner_assisted_full_attention_metadata_request") :
|
||||||
|
]
|
||||||
|
padding_source = padding_source[: padding_source.index("ubatch_slices, ubatch_slices_padded")]
|
||||||
|
assert "runner_assisted_full_attn_request.num_reqs_padded" in padding_source
|
||||||
|
assert "runner_assisted_full_attn_request.for_cudagraph_capture" in padding_source
|
||||||
|
assert "num_reqs_padded * max_num_scheduled_tokens" in padding_source
|
||||||
|
|
||||||
|
|
||||||
|
def test_ar_runner_without_model_hook_stays_on_normal_path():
|
||||||
|
source = RUNNER.read_text()
|
||||||
|
contract_source = RUNNER_ASSISTED_METADATA.read_text()
|
||||||
|
request_source = source[source.index("def _get_runner_assisted_full_attention_metadata_request") :]
|
||||||
|
request_source = request_source[
|
||||||
|
: request_source.index("def _refresh_runner_assisted_full_attention_metadata_buffers")
|
||||||
|
]
|
||||||
|
compact_request_source = "".join(request_source.split())
|
||||||
|
|
||||||
|
assert "Models without this hook keep the normal runner path" in request_source
|
||||||
|
assert "class RunnerAssistedAttentionMetadataProvider(Protocol)" in contract_source
|
||||||
|
assert "class RunnerAssistedFullAttentionMetadataRequest(NamedTuple)" in contract_source
|
||||||
|
assert "tuple[int, bool]" not in contract_source
|
||||||
|
assert 'hook=getattr(self.model,"get_runner_assisted_full_attention_metadata_request",None)' in (
|
||||||
|
compact_request_source
|
||||||
|
)
|
||||||
|
assert "ifnotcallable(hook):returnNone" in compact_request_source
|
||||||
|
assert ".tolist()" not in compact_request_source
|
||||||
|
assert "isinstance(request,RunnerAssistedFullAttentionMetadataRequest)" in compact_request_source
|
||||||
|
assert "request.num_reqs_padded" in request_source
|
||||||
|
assert "request.for_cudagraph_capture" in request_source
|
||||||
|
assert "exceptException" not in compact_request_source
|
||||||
|
|
||||||
|
set_context_source = source[source.index("def _set_runner_assisted_full_attention_metadata_context") :]
|
||||||
|
set_context_source = set_context_source[: set_context_source.index("def _deferred_prefix_cache_mm_keys")]
|
||||||
|
assert "except Exception" not in set_context_source
|
||||||
|
|
||||||
|
|
||||||
|
def test_voxcpm2_graph_paths_fail_closed_and_preserve_deterministic_noise():
|
||||||
|
source = TALKER.read_text()
|
||||||
|
compact_source = "".join(source.split())
|
||||||
|
|
||||||
|
assert "_voxcpm2_compile_without_inductor_cudagraphs" in source
|
||||||
|
assert "def _compile_without_inductor_cudagraphs" not in source
|
||||||
|
assert "_voxcpm2_unwrap_torch_compile" in source
|
||||||
|
assert "_orig_mod" in source
|
||||||
|
compile_source = source[source.index("def _voxcpm2_compile_without_inductor_cudagraphs") :]
|
||||||
|
compile_source = compile_source[: compile_source.index("def _setup_torch_compile")]
|
||||||
|
assert "torch._inductor.config" not in compile_source
|
||||||
|
assert '"triton.cudagraphs": False' in compile_source
|
||||||
|
assert '"triton.cudagraph_trees": False' in compile_source
|
||||||
|
|
||||||
|
assert "self._enable_unified_decode_graph=(use_cuda_graph" in compact_source
|
||||||
|
assert "andnotself._deterministic_cfm_noise" in compact_source
|
||||||
|
assert "decode_tail_graph" not in source
|
||||||
|
|
||||||
|
unified_source = source[source.index("def _forward_unified_decode") :]
|
||||||
|
unified_source = unified_source[: unified_source.index("# -------------------- vllm hooks")]
|
||||||
|
assert "except Exception:" not in unified_source
|
||||||
|
assert "_failed_unified_graph_sizes" not in source
|
||||||
|
assert "self._enable_unified_decode_graph = False" not in unified_source
|
||||||
|
assert "_forward_unified_decode_fallback" not in source
|
||||||
|
assert "capture_failed" not in compact_source
|
||||||
|
assert "record_fallback" not in source
|
||||||
|
assert "fallbacks" not in source
|
||||||
|
assert "**info_dict: Unpack[VoxCPM2PreprocessInput]" in source
|
||||||
|
assert "**info: Unpack[VoxCPM2PostprocessInput]" in source
|
||||||
|
assert "reference_audio: Any" not in source
|
||||||
|
assert "prompt_audio: Any" not in source
|
||||||
|
assert "def _nullify_volatile_metadata(ctx: _ForwardContextLike) -> _ForwardContextLike" in source
|
||||||
|
assert "class _PrefillInputs(NamedTuple)" in source
|
||||||
|
assert "-> _PrefillInputs" in source
|
||||||
|
assert 'inputs["audio_feat"]' not in source
|
||||||
|
assert 'inputs["text_token"]' not in source
|
||||||
|
|
||||||
|
capture_source = source[source.index("def _capture_unified_decode_graph") :]
|
||||||
|
capture_source = capture_source[: capture_source.index("def _unified_decode_graph_skip_reason")]
|
||||||
|
assert "_voxcpm2_compile_unified_capture_estimator" in capture_source
|
||||||
|
assert "_voxcpm2_compile_unified_capture_feat_encoder" in capture_source
|
||||||
|
graph_body = capture_source[capture_source.index("with torch.cuda.graph") :]
|
||||||
|
assert "g.cfm_noise.normal_()" not in graph_body
|
||||||
|
assert "g.cfm_noise.normal_()" in unified_source
|
||||||
|
|
||||||
|
assert "_pre_capture_unified_graphs" not in source
|
||||||
|
assert "_unified_graph_pre_capture_sizes" not in source
|
||||||
|
assert "unified_decode_graph_pre_capture_sizes" not in source
|
||||||
|
|
||||||
|
|
||||||
|
def test_voxcpm2_batch_unified_graph_requires_runner_metadata_marker():
|
||||||
|
source = TALKER.read_text()
|
||||||
|
compact_source = "".join(source.split())
|
||||||
|
|
||||||
|
assert "enable_runner_assisted_unified_decode_graph" not in source
|
||||||
|
assert "allow_unified_decode_graph_batch_attention" not in source
|
||||||
|
assert "get_runner_assisted_full_attention_metadata_request" in source
|
||||||
|
assert "set_runner_assisted_full_attention_metadata_context" in source
|
||||||
|
assert "RunnerAssistedFullAttentionMetadataRequest" in source
|
||||||
|
assert "_runner_assisted_unified_decode_graph_active" in source
|
||||||
|
assert "runner_full_metadata_missing" in source
|
||||||
|
assert "_build_unified_graph_bucket_sizes" in source
|
||||||
|
assert "_select_unified_graph_bucket_size" in source
|
||||||
|
|
||||||
|
capture_source = source[source.index("def _capture_unified_decode_graph") :]
|
||||||
|
capture_source = capture_source[: capture_source.index("def _unified_decode_graph_skip_reason")]
|
||||||
|
assert "override_forward_context" in capture_source
|
||||||
|
assert "_nullify_volatile_metadata" in capture_source
|
||||||
|
assert "capture_context = override_forward_context(self._nullify_volatile_metadata(ctx))" in capture_source
|
||||||
|
assert "ifsize>1:continue" not in compact_source
|
||||||
|
forward_source = source[source.index("def _forward_unified_decode") :]
|
||||||
|
forward_source = forward_source[: forward_source.index("# -------------------- vllm hooks")]
|
||||||
|
assert "graph_size = self._select_unified_graph_bucket_size(num_reqs)" in forward_source
|
||||||
|
assert "self._unified_graphs[graph_size]" in forward_source
|
||||||
|
assert "g.input_embeds[num_reqs:graph_size].zero_()" not in forward_source
|
||||||
|
assert "padded rows as valid duplicates" in forward_source
|
||||||
|
assert "g.input_embeds[last : last + 1].expand" in forward_source
|
||||||
|
assert "ifnum_reqs>1andnotself._runner_assisted_unified_decode_graph_active" in compact_source
|
||||||
|
assert "andnotcfg.enable_runner_assisted_unified_decode_graph" not in compact_source
|
||||||
|
needs_source = source[source.index("def get_runner_assisted_full_attention_metadata_request") :]
|
||||||
|
needs_source = needs_source[: needs_source.index("def set_runner_assisted_full_attention_metadata_context")]
|
||||||
|
assert "_select_unified_graph_bucket_size(num_reqs)" in needs_source
|
||||||
|
assert "_should_use_decode_graph(num_reqs)" not in needs_source
|
||||||
|
assert "RunnerAssistedFullAttentionMetadataRequest(" in needs_source
|
||||||
|
|
||||||
|
|
||||||
|
def test_voxcpm2_unified_skip_preserves_segmented_decode_graphs():
|
||||||
|
source = TALKER.read_text()
|
||||||
|
compact_source = "".join(source.split())
|
||||||
|
forward_source = source[source.index("def forward(") :]
|
||||||
|
forward_source = forward_source[: forward_source.index("# -------------------- prefill / decode helpers")]
|
||||||
|
|
||||||
|
assert "use_segmented_decode_graph" in forward_source
|
||||||
|
assert "unified_skip_reasonisnotNone" in compact_source
|
||||||
|
assert "notself._enable_unified_decode_graphorunified_skip_reasonisnotNone" in compact_source
|
||||||
|
assert "ifuse_segmented_decode_graph:" in compact_source
|
||||||
|
assert "anduse_segmented_decode_graph" in compact_source
|
||||||
|
|
||||||
|
|
||||||
|
def test_voxcpm2_scheduler_policy_stays_model_local():
|
||||||
|
common_source = OMNI_AR_SCHEDULER.read_text()
|
||||||
|
voxcpm2_scheduler_source = VOXCPM2_SCHEDULER.read_text()
|
||||||
|
pipeline_source = VOXCPM2_PIPELINE.read_text()
|
||||||
|
|
||||||
|
assert "VoxCPM2TalkerForConditionalGeneration" not in common_source
|
||||||
|
assert "voxcpm2_runtime_config" not in common_source
|
||||||
|
assert "pure_decode_graph" not in common_source
|
||||||
|
assert "_schedule_with_optional_waiting_deferral" not in common_source
|
||||||
|
assert "def _should_defer_waiting_admission(self) -> bool:" in common_source
|
||||||
|
|
||||||
|
assert "class VoxCPM2OmniARAsyncScheduler(OmniARAsyncScheduler)" in voxcpm2_scheduler_source
|
||||||
|
assert "voxcpm2_runtime_config" in voxcpm2_scheduler_source
|
||||||
|
assert "_should_defer_waiting_for_unified_decode_graph" in voxcpm2_scheduler_source
|
||||||
|
assert "def schedule(" not in voxcpm2_scheduler_source
|
||||||
|
assert "create_request_queue" not in voxcpm2_scheduler_source
|
||||||
|
assert "original_waiting.prepend_requests(deferred_waiting)" not in voxcpm2_scheduler_source
|
||||||
|
assert "return self._should_defer_waiting_for_unified_decode_graph()" in voxcpm2_scheduler_source
|
||||||
|
assert "vllm_omni.model_executor.models.voxcpm2.scheduler.VoxCPM2OmniARAsyncScheduler" in pipeline_source
|
||||||
|
|
||||||
|
|
||||||
|
def test_voxcpm2_deploy_defaults_to_full_unified_graph_only():
|
||||||
|
source = DEPLOY.read_text()
|
||||||
|
|
||||||
|
assert "max_num_seqs: 8" in source
|
||||||
|
assert "enable_unified_decode_graph: true" in source
|
||||||
|
assert "unified_decode_graph_max_batch_size: 8" in source
|
||||||
|
assert "unified_decode_graph_pre_capture_sizes" not in source
|
||||||
|
assert "enable_runner_assisted_unified_decode_graph" not in source
|
||||||
|
assert "allow_unified_decode_graph_batch_attention" not in source
|
||||||
|
assert "decode_graph_capture_policy" not in source
|
||||||
@@ -14,7 +14,7 @@ from unittest.mock import patch
|
|||||||
import pytest
|
import pytest
|
||||||
from transformers import PretrainedConfig, Qwen3OmniMoeConfig
|
from transformers import PretrainedConfig, Qwen3OmniMoeConfig
|
||||||
|
|
||||||
from tests.helpers.stage_config import get_deploy_config_path
|
from tests.helpers.stage_config import get_deploy_config_path, get_deploy_config_stage
|
||||||
from vllm_omni.config.config_factory import StageConfigFactory
|
from vllm_omni.config.config_factory import StageConfigFactory
|
||||||
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES, register_pipeline
|
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES, register_pipeline
|
||||||
from vllm_omni.config.stage_config import (
|
from vllm_omni.config.stage_config import (
|
||||||
@@ -571,6 +571,7 @@ class TestStagePipelineConfig:
|
|||||||
assert s.final_output is False
|
assert s.final_output is False
|
||||||
assert s.sampling_constraints == {}
|
assert s.sampling_constraints == {}
|
||||||
assert s.engine_output_type is None
|
assert s.engine_output_type is None
|
||||||
|
assert s.scheduler_cls is None
|
||||||
|
|
||||||
|
|
||||||
class TestPipelineConfigNew:
|
class TestPipelineConfigNew:
|
||||||
@@ -739,6 +740,15 @@ stages:
|
|||||||
assert deploy.stages[0].compilation_config == {"pass_config": {"fuse_allreduce_rms": False}}
|
assert deploy.stages[0].compilation_config == {"pass_config": {"fuse_allreduce_rms": False}}
|
||||||
assert "compilation_config" not in deploy.stages[0].engine_extras
|
assert "compilation_config" not in deploy.stages[0].engine_extras
|
||||||
|
|
||||||
|
def test_load_voxcpm2_deploy_config_preserves_engine_extras(self):
|
||||||
|
deploy_path = get_deploy_config_path("voxcpm2.yaml")
|
||||||
|
raw_stage = get_deploy_config_stage("voxcpm2.yaml", 0)
|
||||||
|
expected_runtime_config = raw_stage["engine_extras"]["hf_overrides"]["voxcpm2_runtime_config"]
|
||||||
|
|
||||||
|
deploy = load_deploy_config(deploy_path)
|
||||||
|
runtime_config = deploy.stages[0].engine_extras["hf_overrides"]["voxcpm2_runtime_config"]
|
||||||
|
assert runtime_config == expected_runtime_config
|
||||||
|
|
||||||
def test_merge_pipeline_deploy(self):
|
def test_merge_pipeline_deploy(self):
|
||||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||||
if not deploy_path.exists():
|
if not deploy_path.exists():
|
||||||
@@ -800,6 +810,26 @@ stages:
|
|||||||
|
|
||||||
assert stages[0].yaml_runtime["requires_multimodal_data"] is True
|
assert stages[0].yaml_runtime["requires_multimodal_data"] is True
|
||||||
|
|
||||||
|
def test_merge_pipeline_deploy_preserves_pipeline_scheduler_cls(self):
|
||||||
|
scheduler_cls = "tests.fake.CustomScheduler"
|
||||||
|
pipeline = PipelineConfig(
|
||||||
|
model_type="test_scheduler",
|
||||||
|
model_arch="TestModel",
|
||||||
|
stages=(
|
||||||
|
StagePipelineConfig(
|
||||||
|
stage_id=0,
|
||||||
|
model_stage="ar",
|
||||||
|
execution_type=StageExecutionType.LLM_AR,
|
||||||
|
scheduler_cls=scheduler_cls,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
deploy = DeployConfig(async_chunk=False, stages=[StageDeployConfig(stage_id=0)])
|
||||||
|
|
||||||
|
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||||
|
|
||||||
|
assert stages[0].scheduler_cls == scheduler_cls
|
||||||
|
|
||||||
def test_mixed_schema_preserves_flat_fields(self):
|
def test_mixed_schema_preserves_flat_fields(self):
|
||||||
"""Ensure flat fields are not dropped when engine_args are present."""
|
"""Ensure flat fields are not dropped when engine_args are present."""
|
||||||
fake_config = {
|
fake_config = {
|
||||||
@@ -928,6 +958,54 @@ stages:
|
|||||||
assert "foo" in stage.engine_extras
|
assert "foo" in stage.engine_extras
|
||||||
assert stage.engine_extras["foo"] == {"a": 111, "b": {"d": 9, "e": 199}, "c": 3}
|
assert stage.engine_extras["foo"] == {"a": 111, "b": {"d": 9, "e": 199}, "c": 3}
|
||||||
|
|
||||||
|
def test_explicit_engine_extras_merge_with_flat_and_engine_args(self):
|
||||||
|
"""Explicit engine_extras should be preserved with existing pass-through styles."""
|
||||||
|
fake_config = {
|
||||||
|
"stages": [
|
||||||
|
{
|
||||||
|
"stage_id": 0,
|
||||||
|
"engine_extras": {
|
||||||
|
"hf_overrides": {
|
||||||
|
"runtime_config": {
|
||||||
|
"explicit_only": True,
|
||||||
|
"shared": "explicit",
|
||||||
|
"nested": {"a": 1},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hf_overrides": {
|
||||||
|
"runtime_config": {
|
||||||
|
"flat_only": True,
|
||||||
|
"shared": "flat",
|
||||||
|
"nested": {"b": 2},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"engine_args": {
|
||||||
|
"hf_overrides": {
|
||||||
|
"runtime_config": {
|
||||||
|
"engine_args_only": True,
|
||||||
|
"shared": "engine_args",
|
||||||
|
"nested": {"c": 3},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
with patch("vllm_omni.config.stage_config.resolve_deploy_yaml", return_value=fake_config):
|
||||||
|
deploy = load_deploy_config("dummy.yaml")
|
||||||
|
|
||||||
|
assert deploy.stages[0].engine_extras["hf_overrides"] == {
|
||||||
|
"runtime_config": {
|
||||||
|
"engine_args_only": True,
|
||||||
|
"explicit_only": True,
|
||||||
|
"flat_only": True,
|
||||||
|
"nested": {"a": 1, "b": 2, "c": 3},
|
||||||
|
"shared": "engine_args",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def test_deep_merge_does_not_mutate_inputs(self):
|
def test_deep_merge_does_not_mutate_inputs(self):
|
||||||
"""Merging engine_args must not mutate the base stage dict."""
|
"""Merging engine_args must not mutate the base stage dict."""
|
||||||
fake_config = {
|
fake_config = {
|
||||||
@@ -1588,6 +1666,197 @@ class TestAuraOmniDeploy:
|
|||||||
assert stages[3].yaml_engine_args["model_arch"] == "Qwen3TTSCode2Wav"
|
assert stages[3].yaml_engine_args["model_arch"] == "Qwen3TTSCode2Wav"
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeployCliOverrideFlow:
|
||||||
|
"""Test deploy-YAML baselines overridden by CLI runtime overrides."""
|
||||||
|
|
||||||
|
def test_diffusion_deploy_fields_can_be_overridden_by_cli(self, tmp_path):
|
||||||
|
deploy_path = tmp_path / "diffusion_stage.yaml"
|
||||||
|
deploy_path.write_text(
|
||||||
|
"""
|
||||||
|
async_chunk: false
|
||||||
|
stages:
|
||||||
|
- stage_id: 0
|
||||||
|
devices: "4,5"
|
||||||
|
parallel_config:
|
||||||
|
pipeline_parallel_size: 1
|
||||||
|
data_parallel_size: 1
|
||||||
|
tensor_parallel_size: 1
|
||||||
|
enable_expert_parallel: false
|
||||||
|
sequence_parallel_size: 1
|
||||||
|
ulysses_degree: 1
|
||||||
|
ring_degree: 1
|
||||||
|
cfg_parallel_size: 1
|
||||||
|
vae_patch_parallel_size: 1
|
||||||
|
use_hsdp: false
|
||||||
|
hsdp_shard_size: -1
|
||||||
|
hsdp_replicate_size: 1
|
||||||
|
engine_args:
|
||||||
|
cache_backend: cache_dit
|
||||||
|
diffusion_attention_backend: FLASH_ATTN
|
||||||
|
diffusion_kv_cache_dtype: auto
|
||||||
|
step_execution: false
|
||||||
|
vae_use_tiling: false
|
||||||
|
enable_cpu_offload: false
|
||||||
|
max_generated_image_size: 1048576
|
||||||
|
tts_max_instructions_length: 1000
|
||||||
|
""",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
pipeline = PipelineConfig(
|
||||||
|
model_type="test_diffusion",
|
||||||
|
stages=(
|
||||||
|
StagePipelineConfig(
|
||||||
|
stage_id=0,
|
||||||
|
model_stage="dit",
|
||||||
|
execution_type=StageExecutionType.DIFFUSION,
|
||||||
|
final_output=True,
|
||||||
|
final_output_type="image",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
deploy = load_deploy_config(deploy_path)
|
||||||
|
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||||
|
stage = stages[0]
|
||||||
|
|
||||||
|
assert stage.yaml_engine_args["parallel_config"]["pipeline_parallel_size"] == 1
|
||||||
|
assert stage.yaml_engine_args["parallel_config"]["data_parallel_size"] == 1
|
||||||
|
assert stage.yaml_engine_args["parallel_config"]["tensor_parallel_size"] == 1
|
||||||
|
assert stage.yaml_engine_args["parallel_config"]["enable_expert_parallel"] is False
|
||||||
|
assert stage.yaml_engine_args["parallel_config"]["sequence_parallel_size"] == 1
|
||||||
|
assert stage.yaml_engine_args["parallel_config"]["ulysses_degree"] == 1
|
||||||
|
assert stage.yaml_engine_args["parallel_config"]["ring_degree"] == 1
|
||||||
|
assert stage.yaml_engine_args["parallel_config"]["cfg_parallel_size"] == 1
|
||||||
|
assert stage.yaml_engine_args["parallel_config"]["vae_patch_parallel_size"] == 1
|
||||||
|
assert stage.yaml_engine_args["cache_backend"] == "cache_dit"
|
||||||
|
assert stage.yaml_engine_args["parallel_config"]["use_hsdp"] is False
|
||||||
|
assert stage.yaml_engine_args["parallel_config"]["hsdp_shard_size"] == -1
|
||||||
|
assert stage.yaml_engine_args["parallel_config"]["hsdp_replicate_size"] == 1
|
||||||
|
assert stage.yaml_engine_args["diffusion_attention_backend"] == "FLASH_ATTN"
|
||||||
|
assert stage.yaml_engine_args["diffusion_kv_cache_dtype"] == "auto"
|
||||||
|
assert stage.yaml_engine_args["step_execution"] is False
|
||||||
|
assert stage.yaml_engine_args["vae_use_tiling"] is False
|
||||||
|
assert stage.yaml_engine_args["enable_cpu_offload"] is False
|
||||||
|
assert stage.yaml_engine_args["max_generated_image_size"] == 1048576
|
||||||
|
assert stage.yaml_engine_args["tts_max_instructions_length"] == 1000
|
||||||
|
|
||||||
|
stage.runtime_overrides = StageConfigFactory._merge_cli_overrides(
|
||||||
|
stage,
|
||||||
|
{
|
||||||
|
"pipeline_parallel_size": 2,
|
||||||
|
"data_parallel_size": 3,
|
||||||
|
"tensor_parallel_size": 4,
|
||||||
|
"enable_expert_parallel": True,
|
||||||
|
"sequence_parallel_size": 24,
|
||||||
|
"ulysses_degree": 2,
|
||||||
|
"ring_degree": 4,
|
||||||
|
"cfg_parallel_size": 2,
|
||||||
|
"vae_patch_parallel_size": 2,
|
||||||
|
"cache_backend": "tea_cache",
|
||||||
|
"use_hsdp": True,
|
||||||
|
"hsdp_shard_size": 8,
|
||||||
|
"hsdp_replicate_size": 2,
|
||||||
|
"diffusion_attention_backend": "SAGE_ATTN",
|
||||||
|
"diffusion_kv_cache_dtype": "fp8",
|
||||||
|
"step_execution": True,
|
||||||
|
"vae_use_tiling": True,
|
||||||
|
"enable_cpu_offload": True,
|
||||||
|
"max_generated_image_size": 2097152,
|
||||||
|
"tts_max_instructions_length": 2000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
omega_config = stage.to_omegaconf()
|
||||||
|
|
||||||
|
assert omega_config.engine_args.cache_backend == "tea_cache"
|
||||||
|
assert omega_config.engine_args.diffusion_attention_backend == "SAGE_ATTN"
|
||||||
|
assert omega_config.engine_args.diffusion_kv_cache_dtype == "fp8"
|
||||||
|
assert omega_config.engine_args.step_execution is True
|
||||||
|
assert omega_config.engine_args.vae_use_tiling is True
|
||||||
|
assert omega_config.engine_args.enable_cpu_offload is True
|
||||||
|
assert omega_config.engine_args.max_generated_image_size == 2097152
|
||||||
|
assert omega_config.engine_args.tts_max_instructions_length == 2000
|
||||||
|
assert omega_config.engine_args.parallel_config.pipeline_parallel_size == 2
|
||||||
|
assert omega_config.engine_args.parallel_config.data_parallel_size == 3
|
||||||
|
assert omega_config.engine_args.parallel_config.tensor_parallel_size == 4
|
||||||
|
assert omega_config.engine_args.parallel_config.enable_expert_parallel is True
|
||||||
|
assert omega_config.engine_args.parallel_config.sequence_parallel_size == 24
|
||||||
|
assert omega_config.engine_args.parallel_config.ulysses_degree == 2
|
||||||
|
assert omega_config.engine_args.parallel_config.ring_degree == 4
|
||||||
|
assert omega_config.engine_args.parallel_config.cfg_parallel_size == 2
|
||||||
|
assert omega_config.engine_args.parallel_config.vae_patch_parallel_size == 2
|
||||||
|
assert omega_config.engine_args.parallel_config.use_hsdp is True
|
||||||
|
assert omega_config.engine_args.parallel_config.hsdp_shard_size == 8
|
||||||
|
assert omega_config.engine_args.parallel_config.hsdp_replicate_size == 2
|
||||||
|
|
||||||
|
def test_llm_deploy_fields_can_be_overridden_by_cli(self, tmp_path):
|
||||||
|
deploy_path = tmp_path / "llm_stage.yaml"
|
||||||
|
deploy_path.write_text(
|
||||||
|
"""
|
||||||
|
async_chunk: false
|
||||||
|
stages:
|
||||||
|
- stage_id: 0
|
||||||
|
devices: "0,1"
|
||||||
|
engine_args:
|
||||||
|
tensor_parallel_size: 1
|
||||||
|
enable_expert_parallel: false
|
||||||
|
gpu_memory_utilization: 0.5
|
||||||
|
max_num_seqs: 16
|
||||||
|
max_num_batched_tokens: 1024
|
||||||
|
max_model_len: 4096
|
||||||
|
enforce_eager: false
|
||||||
|
""",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
pipeline = PipelineConfig(
|
||||||
|
model_type="test_llm",
|
||||||
|
stages=(
|
||||||
|
StagePipelineConfig(
|
||||||
|
stage_id=0,
|
||||||
|
model_stage="thinker",
|
||||||
|
execution_type=StageExecutionType.LLM_AR,
|
||||||
|
final_output=True,
|
||||||
|
final_output_type="text",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
deploy = load_deploy_config(deploy_path)
|
||||||
|
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||||
|
stage = stages[0]
|
||||||
|
|
||||||
|
assert stage.yaml_engine_args["tensor_parallel_size"] == 1
|
||||||
|
assert stage.yaml_engine_args["enable_expert_parallel"] is False
|
||||||
|
assert stage.yaml_engine_args["gpu_memory_utilization"] == 0.5
|
||||||
|
assert stage.yaml_engine_args["max_num_seqs"] == 16
|
||||||
|
assert stage.yaml_engine_args["max_num_batched_tokens"] == 1024
|
||||||
|
assert stage.yaml_engine_args["max_model_len"] == 4096
|
||||||
|
assert stage.yaml_engine_args["enforce_eager"] is False
|
||||||
|
|
||||||
|
stage.runtime_overrides = StageConfigFactory._merge_cli_overrides(
|
||||||
|
stage,
|
||||||
|
{
|
||||||
|
"tensor_parallel_size": 2,
|
||||||
|
"enable_expert_parallel": True,
|
||||||
|
"gpu_memory_utilization": 0.9,
|
||||||
|
"max_num_seqs": 32,
|
||||||
|
"max_num_batched_tokens": 2048,
|
||||||
|
"max_model_len": 8192,
|
||||||
|
"enforce_eager": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
omega_config = stage.to_omegaconf()
|
||||||
|
|
||||||
|
assert omega_config.engine_args.tensor_parallel_size == 2
|
||||||
|
assert omega_config.engine_args.enable_expert_parallel is True
|
||||||
|
assert omega_config.engine_args.gpu_memory_utilization == 0.9
|
||||||
|
assert omega_config.engine_args.max_num_seqs == 32
|
||||||
|
assert omega_config.engine_args.max_num_batched_tokens == 2048
|
||||||
|
assert omega_config.engine_args.max_model_len == 8192
|
||||||
|
assert omega_config.engine_args.enforce_eager is True
|
||||||
|
|
||||||
|
|
||||||
class TestSentinelDefaultPrecedence:
|
class TestSentinelDefaultPrecedence:
|
||||||
"""Caller-typed (non-None) values win over YAML; None values fall through
|
"""Caller-typed (non-None) values win over YAML; None values fall through
|
||||||
to YAML / dataclass defaults (#3035)."""
|
to YAML / dataclass defaults (#3035)."""
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from collections.abc import Sequence
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -10,6 +11,7 @@ from vllm_omni.worker.gpu_ar_model_runner import (
|
|||||||
GPUARModelRunner,
|
GPUARModelRunner,
|
||||||
OmniAsyncGPUModelRunnerOutput,
|
OmniAsyncGPUModelRunnerOutput,
|
||||||
)
|
)
|
||||||
|
from vllm_omni.worker.runner_assisted_metadata import RunnerAssistedFullAttentionMetadataRequest
|
||||||
|
|
||||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||||
|
|
||||||
@@ -58,6 +60,101 @@ def test_sparse_mm_req_ids_requires_sparse_audio_marker():
|
|||||||
assert GPUARModelRunner._sparse_mm_req_ids({"meta.req_id": ["r1"], "meta.sparse_audio": ["1"]}) == ["r1"]
|
assert GPUARModelRunner._sparse_mm_req_ids({"meta.req_id": ["r1"], "meta.sparse_audio": ["1"]}) == ["r1"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_runner_assisted_full_attention_metadata_request_is_opt_in():
|
||||||
|
runner = object.__new__(GPUARModelRunner)
|
||||||
|
runner.model = object()
|
||||||
|
runner.scheduler_config = SimpleNamespace(max_num_seqs=16)
|
||||||
|
|
||||||
|
request = runner._get_runner_assisted_full_attention_metadata_request(
|
||||||
|
req_ids=["r1", "r2"],
|
||||||
|
num_reqs=2,
|
||||||
|
num_reqs_padded=4,
|
||||||
|
num_scheduled_tokens_np=np.array([1, 1], dtype=np.int32),
|
||||||
|
num_computed_tokens_cpu=np.array([5, 6], dtype=np.int32),
|
||||||
|
max_num_scheduled_tokens=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert request is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_runner_assisted_full_attention_metadata_request_and_context_hooks():
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
class Model:
|
||||||
|
def get_runner_assisted_full_attention_metadata_request(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
req_ids: Sequence[str],
|
||||||
|
num_reqs: int,
|
||||||
|
num_scheduled_tokens: Sequence[int],
|
||||||
|
num_computed_tokens: Sequence[int],
|
||||||
|
max_num_scheduled_tokens: int,
|
||||||
|
) -> RunnerAssistedFullAttentionMetadataRequest:
|
||||||
|
calls.append(
|
||||||
|
(
|
||||||
|
"request",
|
||||||
|
{
|
||||||
|
"req_ids": list(req_ids),
|
||||||
|
"num_reqs": num_reqs,
|
||||||
|
"num_scheduled_tokens": [int(n) for n in num_scheduled_tokens],
|
||||||
|
"num_computed_tokens": [int(n) for n in num_computed_tokens],
|
||||||
|
"max_num_scheduled_tokens": max_num_scheduled_tokens,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return RunnerAssistedFullAttentionMetadataRequest(
|
||||||
|
num_reqs_padded=12,
|
||||||
|
for_cudagraph_capture=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_runner_assisted_full_attention_metadata_context(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
enabled: bool,
|
||||||
|
num_reqs: int = 0,
|
||||||
|
) -> None:
|
||||||
|
calls.append(("context", {"enabled": enabled, "num_reqs": num_reqs}))
|
||||||
|
|
||||||
|
runner = object.__new__(GPUARModelRunner)
|
||||||
|
runner.model = Model()
|
||||||
|
runner.scheduler_config = SimpleNamespace(max_num_seqs=8)
|
||||||
|
|
||||||
|
request = runner._get_runner_assisted_full_attention_metadata_request(
|
||||||
|
req_ids=["r1", "r2"],
|
||||||
|
num_reqs=2,
|
||||||
|
num_reqs_padded=4,
|
||||||
|
num_scheduled_tokens_np=np.array([1, 1], dtype=np.int32),
|
||||||
|
num_computed_tokens_cpu=np.array([5, 6], dtype=np.int32),
|
||||||
|
max_num_scheduled_tokens=1,
|
||||||
|
)
|
||||||
|
context_enabled = runner._set_runner_assisted_full_attention_metadata_context(
|
||||||
|
enabled=True,
|
||||||
|
num_reqs=2,
|
||||||
|
)
|
||||||
|
context_disabled = runner._set_runner_assisted_full_attention_metadata_context(enabled=False)
|
||||||
|
|
||||||
|
assert request == RunnerAssistedFullAttentionMetadataRequest(
|
||||||
|
num_reqs_padded=8,
|
||||||
|
for_cudagraph_capture=True,
|
||||||
|
)
|
||||||
|
assert context_enabled
|
||||||
|
assert context_disabled
|
||||||
|
assert calls == [
|
||||||
|
(
|
||||||
|
"request",
|
||||||
|
{
|
||||||
|
"req_ids": ["r1", "r2"],
|
||||||
|
"num_reqs": 2,
|
||||||
|
"num_scheduled_tokens": [1, 1],
|
||||||
|
"num_computed_tokens": [5, 6],
|
||||||
|
"max_num_scheduled_tokens": 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
("context", {"enabled": True, "num_reqs": 2}),
|
||||||
|
("context", {"enabled": False, "num_reqs": 0}),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_omni_async_gpu_model_runner_output_builds_lazily_once():
|
def test_omni_async_gpu_model_runner_output_builds_lazily_once():
|
||||||
async_output = object.__new__(OmniAsyncGPUModelRunnerOutput)
|
async_output = object.__new__(OmniAsyncGPUModelRunnerOutput)
|
||||||
calls = []
|
calls = []
|
||||||
@@ -357,6 +454,47 @@ def test_async_snapshot_payload_omits_hidden_when_model_opts_out():
|
|||||||
assert payload["multimodal_outputs"]["codes"]["audio"].tolist() == [[1]]
|
assert payload["multimodal_outputs"]["codes"]["audio"].tolist() == [[1]]
|
||||||
|
|
||||||
|
|
||||||
|
def test_runner_assisted_full_attention_metadata_refresh_pads_buffers():
|
||||||
|
class QueryStartLoc:
|
||||||
|
def __init__(self):
|
||||||
|
self.np = np.full(5, -1, dtype=np.int32)
|
||||||
|
self.copied = False
|
||||||
|
|
||||||
|
def copy_to_gpu(self):
|
||||||
|
self.copied = True
|
||||||
|
|
||||||
|
class BlockTable:
|
||||||
|
def __init__(self):
|
||||||
|
self.commits = []
|
||||||
|
|
||||||
|
def commit_block_table(self, num_reqs_padded):
|
||||||
|
self.commits.append(num_reqs_padded)
|
||||||
|
|
||||||
|
runner = object.__new__(GPUARModelRunner)
|
||||||
|
block_table = BlockTable()
|
||||||
|
runner.input_batch = SimpleNamespace(
|
||||||
|
num_computed_tokens_cpu=torch.tensor([10, 20, 99, 99], dtype=torch.int32),
|
||||||
|
block_table=block_table,
|
||||||
|
)
|
||||||
|
runner.optimistic_seq_lens_cpu = torch.zeros(4, dtype=torch.int32)
|
||||||
|
runner.seq_lens = torch.empty(4, dtype=torch.int32)
|
||||||
|
runner.query_pos = SimpleNamespace(np=np.empty(4, dtype=np.int32))
|
||||||
|
runner.query_start_loc = QueryStartLoc()
|
||||||
|
runner._get_cumsum_and_arange = lambda scheduled, _query_pos: np.cumsum(scheduled, dtype=np.int32)
|
||||||
|
|
||||||
|
runner._refresh_runner_assisted_full_attention_metadata_buffers(
|
||||||
|
num_reqs=2,
|
||||||
|
num_reqs_padded=4,
|
||||||
|
num_scheduled_tokens_np=np.array([1, 2], dtype=np.int32),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert runner.optimistic_seq_lens_cpu.tolist() == [11, 22, 0, 0]
|
||||||
|
assert runner.seq_lens.tolist() == [11, 22, 0, 0]
|
||||||
|
assert runner.query_start_loc.np.tolist() == [0, 1, 3, 3, 3]
|
||||||
|
assert runner.query_start_loc.copied
|
||||||
|
assert block_table.commits == [4]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("query_start_loc_attr", ["method", "tensor_attr"])
|
@pytest.mark.parametrize("query_start_loc_attr", ["method", "tensor_attr"])
|
||||||
def test_sample_tokens_tail_only_prefix_cache_uses_staged_cpu_hidden_states(monkeypatch, query_start_loc_attr):
|
def test_sample_tokens_tail_only_prefix_cache_uses_staged_cpu_hidden_states(monkeypatch, query_start_loc_attr):
|
||||||
runner = object.__new__(GPUARModelRunner)
|
runner = object.__new__(GPUARModelRunner)
|
||||||
|
|||||||
@@ -1264,7 +1264,7 @@ if "daily-omni" not in OPENAI_COMPATIBLE_BACKENDS:
|
|||||||
# Prevent import order from causing patch failures
|
# Prevent import order from causing patch failures
|
||||||
from vllm.benchmarks import serve
|
from vllm.benchmarks import serve
|
||||||
from vllm.benchmarks.lib.ready_checker import wait_for_endpoint
|
from vllm.benchmarks.lib.ready_checker import wait_for_endpoint
|
||||||
from vllm.benchmarks.serve import TaskType, _merge_overrides, calculate_metrics_for_embeddings, get_request
|
from vllm.benchmarks.serve import TaskType, calculate_metrics_for_embeddings, get_request
|
||||||
|
|
||||||
from vllm_omni.benchmarks.metrics.metrics import (
|
from vllm_omni.benchmarks.metrics.metrics import (
|
||||||
MultiModalsBenchmarkMetrics,
|
MultiModalsBenchmarkMetrics,
|
||||||
@@ -1276,6 +1276,19 @@ from vllm_omni.benchmarks.metrics.metrics import (
|
|||||||
benchmark_old = serve.benchmark
|
benchmark_old = serve.benchmark
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_overrides(base: dict | None, overrides: dict | None) -> dict | None:
|
||||||
|
"""Merge benchmark extra_body with per-request overrides.
|
||||||
|
|
||||||
|
vLLM 0.24 removed the private helper from ``vllm.benchmarks.serve``.
|
||||||
|
Keep the same shallow-merge behavior here, with request overrides winning.
|
||||||
|
"""
|
||||||
|
if not base and not overrides:
|
||||||
|
return None
|
||||||
|
merged = dict(base or {})
|
||||||
|
merged.update(overrides or {})
|
||||||
|
return merged
|
||||||
|
|
||||||
|
|
||||||
async def benchmark(
|
async def benchmark(
|
||||||
task_type: TaskType,
|
task_type: TaskType,
|
||||||
endpoint_type: str,
|
endpoint_type: str,
|
||||||
|
|||||||
@@ -63,7 +63,12 @@ def build_stage_runtime_overrides(
|
|||||||
if internal_keys is None:
|
if internal_keys is None:
|
||||||
from vllm_omni.engine.arg_utils import SHARED_FIELDS, internal_blacklist_keys
|
from vllm_omni.engine.arg_utils import SHARED_FIELDS, internal_blacklist_keys
|
||||||
|
|
||||||
internal_keys = internal_blacklist_keys() | SHARED_FIELDS
|
# Some fields are modeled as orchestrator-owned for top-level CLI
|
||||||
|
# parsing, but are also legitimate deploy-time stage overrides. Keep
|
||||||
|
# the default blacklist for true orchestrator/shared fields while
|
||||||
|
# allowing any field explicitly represented by the deploy schema to
|
||||||
|
# continue flowing into per-stage overrides.
|
||||||
|
internal_keys = (internal_blacklist_keys() | SHARED_FIELDS) - deploy_runtime_override_keys()
|
||||||
|
|
||||||
result: dict[str, Any] = {}
|
result: dict[str, Any] = {}
|
||||||
|
|
||||||
@@ -223,6 +228,7 @@ class StagePipelineConfig:
|
|||||||
prompt_expand_func: str | None = None
|
prompt_expand_func: str | None = None
|
||||||
cfg_kv_collect_func: str | None = None
|
cfg_kv_collect_func: str | None = None
|
||||||
omni_kv_config: dict[str, Any] | None = None
|
omni_kv_config: dict[str, Any] | None = None
|
||||||
|
scheduler_cls: str | None = None
|
||||||
# Model subdirectory indirections: for multi-component HF repos where the
|
# Model subdirectory indirections: for multi-component HF repos where the
|
||||||
# stage's config/tokenizer lives in a subdirectory (e.g. GLM-Image's AR
|
# stage's config/tokenizer lives in a subdirectory (e.g. GLM-Image's AR
|
||||||
# config is in ``vision_language_encoder/``). Consumed at stage-init time
|
# config is in ``vision_language_encoder/``). Consumed at stage-init time
|
||||||
@@ -303,7 +309,7 @@ class StageDeployConfig:
|
|||||||
the top level of ``DeployConfig`` and propagated to every stage.
|
the top level of ``DeployConfig`` and propagated to every stage.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# === Omni fields ===
|
# === Omni stage wrapper fields ===
|
||||||
# Stage identity and Omni runtime placement.
|
# Stage identity and Omni runtime placement.
|
||||||
stage_id: int
|
stage_id: int
|
||||||
devices: str | None = None
|
devices: str | None = None
|
||||||
@@ -316,22 +322,33 @@ class StageDeployConfig:
|
|||||||
default_sampling_params: dict[str, Any] | None = None
|
default_sampling_params: dict[str, Any] | None = None
|
||||||
subtalker_sampling_params: dict[str, Any] | None = None
|
subtalker_sampling_params: dict[str, Any] | None = None
|
||||||
|
|
||||||
# === vLLM EngineArgs fields ===
|
# === Generic stage engine fields ===
|
||||||
# Parallelism and scheduler/memory capacity.
|
# Parallelism, scheduler, and memory-capacity controls.
|
||||||
tensor_parallel_size: int | None = None
|
tensor_parallel_size: int | None = None
|
||||||
|
enable_expert_parallel: bool | None = None
|
||||||
gpu_memory_utilization: float | None = None
|
gpu_memory_utilization: float | None = None
|
||||||
max_num_seqs: int | None = None
|
max_num_seqs: int | None = None
|
||||||
max_num_batched_tokens: int | None = None
|
max_num_batched_tokens: int | None = None
|
||||||
max_model_len: int | None = None
|
max_model_len: int | None = None
|
||||||
|
|
||||||
# Execution, scheduling, and KV/cache behavior.
|
# Generic execution, scheduling, and KV/cache behavior.
|
||||||
enforce_eager: bool | None = None
|
enforce_eager: bool | None = None
|
||||||
async_scheduling: bool | None = None
|
async_scheduling: bool | None = None
|
||||||
disable_hybrid_kv_cache_manager: bool | None = None
|
disable_hybrid_kv_cache_manager: bool | None = None
|
||||||
mm_processor_cache_gb: float | None = None
|
mm_processor_cache_gb: float | None = None
|
||||||
|
|
||||||
# Diffusion parallel_config deploy override fields.
|
# Generic compilation, profiling, tokenizer/config parsing, and model
|
||||||
enable_expert_parallel: bool | None = None
|
# loading controls.
|
||||||
|
compilation_config: dict[str, Any] | None = None
|
||||||
|
profiler_config: dict[str, Any] | None = None
|
||||||
|
skip_mm_profiling: bool | None = None
|
||||||
|
enable_flashinfer_autotune: bool | None = None
|
||||||
|
config_format: str | None = None
|
||||||
|
load_format: str | None = None
|
||||||
|
tokenizer_mode: str | None = None
|
||||||
|
|
||||||
|
# === Diffusion stage runtime fields ===
|
||||||
|
# Diffusion parallel_config deploy/runtime override fields.
|
||||||
ulysses_degree: int | None = None
|
ulysses_degree: int | None = None
|
||||||
ulysses_mode: str | None = None
|
ulysses_mode: str | None = None
|
||||||
ring_degree: int | None = None
|
ring_degree: int | None = None
|
||||||
@@ -343,16 +360,44 @@ class StageDeployConfig:
|
|||||||
hsdp_shard_size: int | None = None
|
hsdp_shard_size: int | None = None
|
||||||
hsdp_replicate_size: int | None = None
|
hsdp_replicate_size: int | None = None
|
||||||
|
|
||||||
# Compilation, profiling, tokenizer/config parsing, and model loading.
|
# Diffusion model loading and adapter construction.
|
||||||
compilation_config: dict[str, Any] | None = None
|
model_class_name: str | None = None
|
||||||
profiler_config: dict[str, Any] | None = None
|
diffusion_load_format: str | None = None
|
||||||
skip_mm_profiling: bool | None = None
|
diffusers_load_kwargs: dict[str, Any] | None = None
|
||||||
enable_flashinfer_autotune: bool | None = None
|
diffusers_call_kwargs: dict[str, Any] | None = None
|
||||||
config_format: str | None = None
|
diffusion_quantization_config: str | None = None
|
||||||
load_format: str | None = None
|
diffusion_attention_backend: str | None = None
|
||||||
tokenizer_mode: str | None = None
|
diffusion_attention_config: dict[str, Any] | None = None
|
||||||
|
|
||||||
# Pass-through vLLM EngineArgs fields that are not represented above.
|
# Diffusion execution, cache, and VAE behavior.
|
||||||
|
cache_backend: str | None = None
|
||||||
|
cache_config: dict[str, Any] | None = None
|
||||||
|
enable_cache_dit_summary: bool | None = None
|
||||||
|
step_execution: bool | None = None
|
||||||
|
vae_use_slicing: bool | None = None
|
||||||
|
vae_use_tiling: bool | None = None
|
||||||
|
boundary_ratio: float | None = None
|
||||||
|
flow_shift: float | None = None
|
||||||
|
diffusion_kv_cache_dtype: str | None = None
|
||||||
|
diffusion_kv_cache_skip_steps: str | None = None
|
||||||
|
diffusion_kv_cache_skip_layers: str | None = None
|
||||||
|
auxiliary_text_encoder: str | None = None
|
||||||
|
|
||||||
|
# Runtime optimizations used by diffusion loading/execution.
|
||||||
|
enable_multithread_weight_load: bool | None = None
|
||||||
|
num_weight_load_threads: int | None = None
|
||||||
|
enable_cpu_offload: bool | None = None
|
||||||
|
enable_layerwise_offload: bool | None = None
|
||||||
|
|
||||||
|
# Diffusion-specific debug and observability knobs.
|
||||||
|
enable_diffusion_pipeline_profiler: bool | None = None
|
||||||
|
|
||||||
|
# Modality/service constraints consumed outside the core engine config.
|
||||||
|
max_generated_image_size: int | None = None
|
||||||
|
tts_max_instructions_length: int | None = None
|
||||||
|
|
||||||
|
# === Pass-through stage engine fields ===
|
||||||
|
# Pass-through stage engine args that are not represented above.
|
||||||
engine_extras: dict[str, Any] = field(default_factory=dict)
|
engine_extras: dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
@@ -409,10 +454,22 @@ _STAGE_RESERVED_KEYS = frozenset(
|
|||||||
_STAGE_DEPLOY_FIELDS = {f.name: f for f in fields(StageDeployConfig) if f.name not in _STAGE_RESERVED_KEYS}
|
_STAGE_DEPLOY_FIELDS = {f.name: f for f in fields(StageDeployConfig) if f.name not in _STAGE_RESERVED_KEYS}
|
||||||
|
|
||||||
|
|
||||||
|
def deploy_runtime_override_keys() -> frozenset[str]:
|
||||||
|
"""Return deploy-schema fields that are valid CLI/runtime overrides.
|
||||||
|
|
||||||
|
These keys form the positive contract for stage override propagation:
|
||||||
|
stage-scoped deploy knobs plus top-level pipeline-wide engine settings.
|
||||||
|
They must remain overridable even if they are also modeled on
|
||||||
|
``OrchestratorArgs`` for top-level CLI parsing.
|
||||||
|
"""
|
||||||
|
return frozenset(_STAGE_DEPLOY_FIELDS) | frozenset(_PIPELINE_WIDE_ENGINE_FIELDS)
|
||||||
|
|
||||||
|
|
||||||
def _parse_stage_deploy(stage_data: dict[str, Any]) -> StageDeployConfig:
|
def _parse_stage_deploy(stage_data: dict[str, Any]) -> StageDeployConfig:
|
||||||
"""Parse a single stage entry from deploy YAML into StageDeployConfig."""
|
"""Parse a single stage entry from deploy YAML into StageDeployConfig."""
|
||||||
# Get the non-reserved keys for this stage
|
# Get the non-reserved keys for this stage
|
||||||
flat_args = {k: v for k, v in stage_data.items() if k not in _STAGE_RESERVED_KEYS}
|
flat_args = {k: v for k, v in stage_data.items() if k not in _STAGE_RESERVED_KEYS}
|
||||||
|
explicit_engine_extras = dict(stage_data.get("engine_extras") or {})
|
||||||
runtime_cfg = dict(stage_data.get("runtime", {}))
|
runtime_cfg = dict(stage_data.get("runtime", {}))
|
||||||
devices = runtime_cfg.get("devices", stage_data.get("devices"))
|
devices = runtime_cfg.get("devices", stage_data.get("devices"))
|
||||||
num_replicas = runtime_cfg.get("num_replicas", stage_data.get("num_replicas", 1))
|
num_replicas = runtime_cfg.get("num_replicas", stage_data.get("num_replicas", 1))
|
||||||
@@ -440,7 +497,7 @@ def _parse_stage_deploy(stage_data: dict[str, Any]) -> StageDeployConfig:
|
|||||||
kwargs["output_connectors"] = stage_data.get("output_connectors")
|
kwargs["output_connectors"] = stage_data.get("output_connectors")
|
||||||
kwargs["input_connectors"] = stage_data.get("input_connectors")
|
kwargs["input_connectors"] = stage_data.get("input_connectors")
|
||||||
kwargs["default_sampling_params"] = stage_data.get("default_sampling_params")
|
kwargs["default_sampling_params"] = stage_data.get("default_sampling_params")
|
||||||
kwargs["engine_extras"] = flat_args
|
kwargs["engine_extras"] = _get_recursively_merged_dict(explicit_engine_extras, flat_args)
|
||||||
return StageDeployConfig(**kwargs)
|
return StageDeployConfig(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
@@ -840,7 +897,7 @@ def merge_pipeline_deploy(
|
|||||||
final_output=ps.final_output,
|
final_output=ps.final_output,
|
||||||
final_output_type=ps.final_output_type,
|
final_output_type=ps.final_output_type,
|
||||||
worker_type=worker_type,
|
worker_type=worker_type,
|
||||||
scheduler_cls=_scheduler_path(sched_cls),
|
scheduler_cls=ps.scheduler_cls or _scheduler_path(sched_cls),
|
||||||
hf_config_name=ps.hf_config_name,
|
hf_config_name=ps.hf_config_name,
|
||||||
is_comprehension=ps.owns_tokenizer,
|
is_comprehension=ps.owns_tokenizer,
|
||||||
yaml_engine_args=engine_args,
|
yaml_engine_args=engine_args,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from vllm.distributed.kv_transfer.kv_connector.v1.metrics import KVConnectorStat
|
|||||||
from vllm.logger import init_logger
|
from vllm.logger import init_logger
|
||||||
from vllm.v1.core.sched.async_scheduler import AsyncScheduler as AsyncVLLMScheduler
|
from vllm.v1.core.sched.async_scheduler import AsyncScheduler as AsyncVLLMScheduler
|
||||||
from vllm.v1.core.sched.output import SchedulerOutput
|
from vllm.v1.core.sched.output import SchedulerOutput
|
||||||
|
from vllm.v1.core.sched.request_queue import create_request_queue
|
||||||
from vllm.v1.core.sched.scheduler import Scheduler as VLLMScheduler
|
from vllm.v1.core.sched.scheduler import Scheduler as VLLMScheduler
|
||||||
from vllm.v1.core.sched.utils import remove_all
|
from vllm.v1.core.sched.utils import remove_all
|
||||||
from vllm.v1.engine import EngineCoreEventType, EngineCoreOutput, EngineCoreOutputs, FinishReason
|
from vllm.v1.engine import EngineCoreEventType, EngineCoreOutput, EngineCoreOutputs, FinishReason
|
||||||
@@ -133,6 +134,9 @@ class OmniARScheduler(OmniSchedulerMixin, VLLMScheduler):
|
|||||||
self._omits_kv_transfer_cache[rid] = result
|
self._omits_kv_transfer_cache[rid] = result
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _should_defer_waiting_admission(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
def _process_kv_transfer_trigger(self, request: Request, new_token_ids: list[int]) -> bool:
|
def _process_kv_transfer_trigger(self, request: Request, new_token_ids: list[int]) -> bool:
|
||||||
"""
|
"""
|
||||||
Check triggers and process side effects (marking transfer).
|
Check triggers and process side effects (marking transfer).
|
||||||
@@ -222,9 +226,19 @@ class OmniARScheduler(OmniSchedulerMixin, VLLMScheduler):
|
|||||||
self.waiting, self.running, scheduler_requests=self.requests
|
self.waiting, self.running, scheduler_requests=self.requests
|
||||||
)
|
)
|
||||||
|
|
||||||
|
original_waiting = None
|
||||||
|
if self._should_defer_waiting_admission():
|
||||||
|
original_waiting = self.waiting
|
||||||
|
self.waiting = create_request_queue(self.policy)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
scheduler_output = super().schedule(throttle_prefills)
|
scheduler_output = super().schedule(throttle_prefills)
|
||||||
finally:
|
finally:
|
||||||
|
if original_waiting is not None:
|
||||||
|
deferred_waiting = list(self.waiting)
|
||||||
|
if deferred_waiting:
|
||||||
|
original_waiting.prepend_requests(deferred_waiting)
|
||||||
|
self.waiting = original_waiting
|
||||||
if self.chunk_transfer_adapter:
|
if self.chunk_transfer_adapter:
|
||||||
# Add request waiting for chunk to the waiting and running queue
|
# Add request waiting for chunk to the waiting and running queue
|
||||||
self.chunk_transfer_adapter.restore_queues(
|
self.chunk_transfer_adapter.restore_queues(
|
||||||
|
|||||||
@@ -8,16 +8,17 @@ dtype: bfloat16
|
|||||||
|
|
||||||
stages:
|
stages:
|
||||||
- stage_id: 0
|
- stage_id: 0
|
||||||
max_num_seqs: 32
|
max_num_seqs: 8
|
||||||
gpu_memory_utilization: 0.9
|
gpu_memory_utilization: 0.9
|
||||||
# Right-size the KV cache instead of letting it fill gpu_memory_utilization.
|
# Right-size the KV cache instead of letting it fill gpu_memory_utilization.
|
||||||
# max_num_seqs(32) * max_model_len(4096) ~= 131k tokens; 6 GiB holds ~175k
|
# max_num_seqs(8) * max_model_len(4096) ~= 32k tokens; 6 GiB leaves headroom
|
||||||
# (42x concurrency, above the 32-seq scheduler cap, so no throughput loss).
|
# for longer live requests while keeping VoxCPM2 TTS decode batches small
|
||||||
|
# enough to avoid the high ITL observed with large admission caps.
|
||||||
# The remaining VRAM stays free for the VoxCPM2 diffusion side-path
|
# The remaining VRAM stays free for the VoxCPM2 diffusion side-path
|
||||||
# (LocDiT / AudioVAE / code2wav + batch CUDA-graph pools), which allocates
|
# (LocDiT / AudioVAE / code2wav + batch CUDA-graph pools), which allocates
|
||||||
# outside vLLM's KV accounting during inference. Keeps peak ~13 GiB on any
|
# outside vLLM's KV accounting during inference. Keeps peak ~13 GiB on any
|
||||||
# card and avoids per-rebase OOM tuning on small CI GPUs.
|
# card and avoids per-rebase OOM tuning on small CI GPUs.
|
||||||
# Assumes prefix caching off and max_num_seqs*max_model_len <= ~175k tokens;
|
# Assumes prefix caching off and typical live decode concurrency <= ~8;
|
||||||
# raise this cap if either grows or enable_prefix_caching is turned on.
|
# raise this cap if either grows or enable_prefix_caching is turned on.
|
||||||
kv_cache_memory_bytes: 6442450944 # 6 GiB
|
kv_cache_memory_bytes: 6442450944 # 6 GiB
|
||||||
enforce_eager: true
|
enforce_eager: true
|
||||||
@@ -39,6 +40,9 @@ stages:
|
|||||||
enable_loc_dit_fused_mlp: true
|
enable_loc_dit_fused_mlp: true
|
||||||
enable_loc_dit_zero_dt_cache: true
|
enable_loc_dit_zero_dt_cache: true
|
||||||
enable_loc_dit_skip_qkv_contig: true
|
enable_loc_dit_skip_qkv_contig: true
|
||||||
|
enable_loc_dit_reduce_overhead_no_cg: true
|
||||||
|
enable_unified_decode_graph: true
|
||||||
|
unified_decode_graph_max_batch_size: 8
|
||||||
default_sampling_params:
|
default_sampling_params:
|
||||||
temperature: 0.0
|
temperature: 0.0
|
||||||
top_p: 1.0
|
top_p: 1.0
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -49,7 +48,9 @@ from vllm_omni.diffusion.models.ming_flash_omni.ming_zimage_transformer import (
|
|||||||
MingZImageTransformer2DModel,
|
MingZImageTransformer2DModel,
|
||||||
)
|
)
|
||||||
from vllm_omni.diffusion.models.z_image.pipeline_z_image import ZImagePipeline
|
from vllm_omni.diffusion.models.z_image.pipeline_z_image import ZImagePipeline
|
||||||
|
from vllm_omni.diffusion.request import OmniDiffusionRequest
|
||||||
from vllm_omni.diffusion.worker.request_batch import DiffusionRequestBatch
|
from vllm_omni.diffusion.worker.request_batch import DiffusionRequestBatch
|
||||||
|
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
|
||||||
from vllm_omni.model_executor.model_loader.weight_utils import (
|
from vllm_omni.model_executor.model_loader.weight_utils import (
|
||||||
download_weights_from_hf_specific,
|
download_weights_from_hf_specific,
|
||||||
)
|
)
|
||||||
@@ -58,38 +59,6 @@ from vllm_omni.transformers_utils.configs.ming_flash_omni import MingImageGenCon
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class _ZPipelineSamplingParams:
|
|
||||||
"""Typed shim satisfying the attributes ZImagePipeline.forward reads
|
|
||||||
from ``req.sampling_params``. Unlike SimpleNamespace this fails loudly
|
|
||||||
at construction if a required field is missing."""
|
|
||||||
|
|
||||||
height: int
|
|
||||||
width: int
|
|
||||||
num_inference_steps: int
|
|
||||||
guidance_scale: float
|
|
||||||
generator: torch.Generator | None = None
|
|
||||||
strength: float | None = None
|
|
||||||
sigmas: list[float] | None = None
|
|
||||||
max_sequence_length: int = 512
|
|
||||||
guidance_rescale: float | None = None
|
|
||||||
num_outputs_per_prompt: int = 1
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class _ZPipelineRequest:
|
|
||||||
"""Typed shim satisfying the attributes ZImagePipeline.forward reads
|
|
||||||
from ``req``."""
|
|
||||||
|
|
||||||
request_id: str
|
|
||||||
sampling_params: _ZPipelineSamplingParams
|
|
||||||
prompts: list[dict[str, Any]] = field(default_factory=lambda: [{"prompt": "", "negative_prompt": ""}])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def num_reqs(self) -> int:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
class MingImagePipeline(ZImagePipeline):
|
class MingImagePipeline(ZImagePipeline):
|
||||||
"""Ming-flash-omni-2.0 text-to-image diffusion pipeline.
|
"""Ming-flash-omni-2.0 text-to-image diffusion pipeline.
|
||||||
|
|
||||||
@@ -123,6 +92,10 @@ class MingImagePipeline(ZImagePipeline):
|
|||||||
self.device = self._execution_device # Ming convention alias
|
self.device = self._execution_device # Ming convention alias
|
||||||
self._dtype = dtype
|
self._dtype = dtype
|
||||||
|
|
||||||
|
# Request-scoped conditioning handed to the inherited encode_prompt override
|
||||||
|
self._pending_prompt_embeds: list[torch.Tensor] | None = None
|
||||||
|
self._pending_negative_prompt_embeds: list[torch.Tensor] | None = None
|
||||||
|
|
||||||
# Ming's per-checkpoint image-gen configuration. We cannot rely on
|
# Ming's per-checkpoint image-gen configuration. We cannot rely on
|
||||||
# ``od_config.hf_config.image_gen_config`` because the diffusion
|
# ``od_config.hf_config.image_gen_config`` because the diffusion
|
||||||
# stage is started with ``hf_config_name: thinker_config`` (the
|
# stage is started with ``hf_config_name: thinker_config`` (the
|
||||||
@@ -273,6 +246,21 @@ class MingImagePipeline(ZImagePipeline):
|
|||||||
latent = self.vae.encode(ref).latent_dist.mode()
|
latent = self.vae.encode(ref).latent_dist.mode()
|
||||||
return (latent - self.vae.config.shift_factor) * self.vae.config.scaling_factor
|
return (latent - self.vae.config.shift_factor) * self.vae.config.scaling_factor
|
||||||
|
|
||||||
|
def encode_prompt(self, *args, **kwargs): # noqa: ARG002
|
||||||
|
"""Return Ming's precomputed conditioning instead of encoding text.
|
||||||
|
|
||||||
|
NOTE: Ming has no Z-Image text_encoder; its conditioning (cap_feats, optionally ByT5-augmented)
|
||||||
|
is computed in forward and stashed on `self._pending_*` immediately before
|
||||||
|
the `super().forward` call, so we simply hand it back here.
|
||||||
|
"""
|
||||||
|
if self._pending_prompt_embeds is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"MingImagePipeline.encode_prompt called without pending "
|
||||||
|
"conditioning; it must run within MingImagePipeline.forward."
|
||||||
|
)
|
||||||
|
|
||||||
|
return self._pending_prompt_embeds, self._pending_negative_prompt_embeds
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Forward
|
# Forward
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@@ -416,17 +404,27 @@ class MingImagePipeline(ZImagePipeline):
|
|||||||
negative_prompt_embeds = [negative_cap_feats[i] for i in range(negative_cap_feats.shape[0])]
|
negative_prompt_embeds = [negative_cap_feats[i] for i in range(negative_cap_feats.shape[0])]
|
||||||
else:
|
else:
|
||||||
negative_prompt_embeds = [self.condition_encoder.zero_negative(e) for e in prompt_embeds]
|
negative_prompt_embeds = [self.condition_encoder.zero_negative(e) for e in prompt_embeds]
|
||||||
|
self._pending_prompt_embeds = prompt_embeds
|
||||||
|
self._pending_negative_prompt_embeds = negative_prompt_embeds
|
||||||
|
|
||||||
z_sp = _ZPipelineSamplingParams(
|
# Build a real single-request DiffusionRequestBatch;
|
||||||
|
# the prompt text itself is a neutral placeholder here.
|
||||||
|
z_sp = OmniDiffusionSamplingParams(
|
||||||
height=height,
|
height=height,
|
||||||
width=width,
|
width=width,
|
||||||
num_inference_steps=num_inference_steps,
|
num_inference_steps=num_inference_steps,
|
||||||
guidance_scale=guidance_scale,
|
guidance_scale=guidance_scale,
|
||||||
generator=generator,
|
generator=generator,
|
||||||
|
output_type="pt",
|
||||||
)
|
)
|
||||||
z_req = _ZPipelineRequest(
|
z_req = DiffusionRequestBatch(
|
||||||
request_id=req.request_id or "ming-imagegen",
|
requests=[
|
||||||
sampling_params=z_sp,
|
OmniDiffusionRequest(
|
||||||
|
prompt={"prompt": ""},
|
||||||
|
sampling_params=z_sp,
|
||||||
|
request_id=req.request_id or "ming-imagegen",
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Reference image (img2img) → VAE-encoded latent published on the
|
# Reference image (img2img) → VAE-encoded latent published on the
|
||||||
@@ -446,21 +444,12 @@ class MingImagePipeline(ZImagePipeline):
|
|||||||
None if ref_latent is None else tuple(ref_latent.shape),
|
None if ref_latent is None else tuple(ref_latent.shape),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
outputs: DiffusionOutput = super().forward(
|
outputs: DiffusionOutput = super().forward(z_req)
|
||||||
z_req,
|
|
||||||
prompt=None,
|
|
||||||
height=height,
|
|
||||||
width=width,
|
|
||||||
num_inference_steps=num_inference_steps,
|
|
||||||
guidance_scale=guidance_scale,
|
|
||||||
prompt_embeds=prompt_embeds,
|
|
||||||
negative_prompt_embeds=negative_prompt_embeds,
|
|
||||||
generator=generator,
|
|
||||||
output_type="pt",
|
|
||||||
return_dict=True,
|
|
||||||
)
|
|
||||||
finally:
|
finally:
|
||||||
set_forward_context_ref_latent(None)
|
set_forward_context_ref_latent(None)
|
||||||
|
# Drop request-scoped conditioning so we don't retain GPU tensors.
|
||||||
|
self._pending_prompt_embeds = None
|
||||||
|
self._pending_negative_prompt_embeds = None
|
||||||
|
|
||||||
raw = outputs.output
|
raw = outputs.output
|
||||||
if not isinstance(raw, torch.Tensor):
|
if not isinstance(raw, torch.Tensor):
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import io
|
|||||||
import PIL.Image
|
import PIL.Image
|
||||||
|
|
||||||
SUPPORTED_LAYERED_RESOLUTIONS = (640, 1024)
|
SUPPORTED_LAYERED_RESOLUTIONS = (640, 1024)
|
||||||
SUPPORTED_LAYERED_LAYERS_RANGE = range(3, 11)
|
SUPPORTED_LAYERED_LAYERS_RANGE = range(2, 11)
|
||||||
|
|
||||||
|
|
||||||
def parse_size(size_str: str) -> tuple[int, int]:
|
def parse_size(size_str: str) -> tuple[int, int]:
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class ImageGenerationRequest(BaseModel):
|
|||||||
user: str | None = Field(default=None, description="User identifier for tracking")
|
user: str | None = Field(default=None, description="User identifier for tracking")
|
||||||
layers: int | None = Field(
|
layers: int | None = Field(
|
||||||
default=None,
|
default=None,
|
||||||
description="Number of output layers for layered image models. Supported range: 3-10.",
|
description="Number of output layers for layered image models. Supported range: 2-10.",
|
||||||
)
|
)
|
||||||
|
|
||||||
@field_validator("size")
|
@field_validator("size")
|
||||||
|
|||||||
@@ -3513,11 +3513,13 @@ class OmniOpenAIServingSpeech(OpenAIServing, AudioMixin):
|
|||||||
# apply uploaded speakers inside validate(), which sets request.ref_audio
|
# apply uploaded speakers inside validate(), which sets request.ref_audio
|
||||||
# in place. The builders need to know whether the caller supplied audio
|
# in place. The builders need to know whether the caller supplied audio
|
||||||
# inline vs. via an uploaded voice.
|
# inline vs. via an uploaded voice.
|
||||||
|
model_type: str | None = None
|
||||||
has_inline_ref_audio = request.ref_audio is not None
|
has_inline_ref_audio = request.ref_audio is not None
|
||||||
if self._tts_model_type == "ming_flash_omni_tts":
|
if self._tts_model_type == "ming_flash_omni_tts":
|
||||||
# ming_flash_omni is intentionally NOT migrated onto the adapter
|
# ming_flash_omni is intentionally NOT migrated onto the adapter
|
||||||
# framework in this PR (it has no registered adapter); keep it on the
|
# framework in this PR (it has no registered adapter); keep it on the
|
||||||
# legacy inline dispatch so serving still works.
|
# legacy inline dispatch so serving still works.
|
||||||
|
model_type = "ming_flash_omni_tts"
|
||||||
validation_error = self._validate_ming_flash_omni_tts_request(request)
|
validation_error = self._validate_ming_flash_omni_tts_request(request)
|
||||||
if validation_error:
|
if validation_error:
|
||||||
raise ValueError(validation_error)
|
raise ValueError(validation_error)
|
||||||
@@ -3531,6 +3533,7 @@ class OmniOpenAIServingSpeech(OpenAIServing, AudioMixin):
|
|||||||
prepared = await adapter.build(request, sampling_params_list, has_inline_ref_audio)
|
prepared = await adapter.build(request, sampling_params_list, has_inline_ref_audio)
|
||||||
prompt = prepared.prompt
|
prompt = prepared.prompt
|
||||||
tts_params = prepared.tts_params
|
tts_params = prepared.tts_params
|
||||||
|
model_type = prepared.model_type
|
||||||
qwen3_ref_audio_warmup_artifact_key = prepared.warmup_artifact_key
|
qwen3_ref_audio_warmup_artifact_key = prepared.warmup_artifact_key
|
||||||
else:
|
else:
|
||||||
# Qwen omni models (Qwen3-Omni, Qwen2.5-Omni) use a "talker"
|
# Qwen omni models (Qwen3-Omni, Qwen2.5-Omni) use a "talker"
|
||||||
@@ -3554,36 +3557,11 @@ class OmniOpenAIServingSpeech(OpenAIServing, AudioMixin):
|
|||||||
tts_params = {}
|
tts_params = {}
|
||||||
prompt = {"prompt": request.input}
|
prompt = {"prompt": request.input}
|
||||||
|
|
||||||
if self._tts_model_type == "step_audio2":
|
if model_type is None:
|
||||||
model_type = "step_audio2"
|
if self._is_tts:
|
||||||
elif self._is_fish_speech:
|
model_type = tts_params.get("task_type", ["unknown"])[0]
|
||||||
model_type = "fish_speech"
|
else:
|
||||||
elif self._tts_model_type == "covo_audio":
|
model_type = "generic"
|
||||||
model_type = "covo_audio"
|
|
||||||
elif self._tts_model_type == "voxtral_tts":
|
|
||||||
model_type = "voxtral_tts"
|
|
||||||
elif self._tts_model_type == "cosyvoice3":
|
|
||||||
model_type = "cosyvoice3"
|
|
||||||
elif self._tts_model_type == "voxcpm2":
|
|
||||||
model_type = "voxcpm2"
|
|
||||||
elif self._tts_model_type == "ming_flash_omni_tts":
|
|
||||||
model_type = "ming_flash_omni_tts"
|
|
||||||
elif self._tts_model_type == "ming_tts":
|
|
||||||
model_type = "ming_tts"
|
|
||||||
elif self._tts_model_type == "moss_tts_nano":
|
|
||||||
model_type = "moss_tts_nano"
|
|
||||||
elif self._tts_model_type == "moss_tts":
|
|
||||||
model_type = "moss_tts"
|
|
||||||
elif self._tts_model_type == "higgs_audio_v2":
|
|
||||||
model_type = "higgs_audio_v2"
|
|
||||||
elif self._tts_model_type == "glm_tts":
|
|
||||||
model_type = "glm_tts"
|
|
||||||
elif self._tts_model_type == "indextts2":
|
|
||||||
model_type = "indextts2"
|
|
||||||
elif self._is_tts:
|
|
||||||
model_type = tts_params.get("task_type", ["unknown"])[0]
|
|
||||||
else:
|
|
||||||
model_type = "generic"
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"TTS speech request %s: text=%r, model=%s",
|
"TTS speech request %s: text=%r, model=%s",
|
||||||
request_id,
|
request_id,
|
||||||
|
|||||||
@@ -587,9 +587,9 @@ class OmniBagelForConditionalGeneration(BagelForConditionalGeneration):
|
|||||||
input_ids: torch.Tensor | None,
|
input_ids: torch.Tensor | None,
|
||||||
positions: torch.Tensor | None,
|
positions: torch.Tensor | None,
|
||||||
inputs_embeds: torch.Tensor | None,
|
inputs_embeds: torch.Tensor | None,
|
||||||
req_ids: list[str],
|
req_ids: Sequence[str],
|
||||||
num_computed_tokens: list[int],
|
num_computed_tokens: Sequence[int],
|
||||||
num_scheduled_tokens: list[int],
|
num_scheduled_tokens: Sequence[int],
|
||||||
input_ids_buffer: torch.Tensor | None = None,
|
input_ids_buffer: torch.Tensor | None = None,
|
||||||
) -> tuple[torch.Tensor | None, torch.Tensor | None]:
|
) -> tuple[torch.Tensor | None, torch.Tensor | None]:
|
||||||
"""Restore input_ids so _adjust_positions_for_img2img can locate
|
"""Restore input_ids so _adjust_positions_for_img2img can locate
|
||||||
@@ -599,7 +599,7 @@ class OmniBagelForConditionalGeneration(BagelForConditionalGeneration):
|
|||||||
input_ids = input_ids_buffer
|
input_ids = input_ids_buffer
|
||||||
return input_ids, positions
|
return input_ids, positions
|
||||||
|
|
||||||
def flush_pending_metadata(self, req_ids: list[str]) -> None:
|
def flush_pending_metadata(self, req_ids: Sequence[str]) -> None:
|
||||||
"""Map pending metadata (batch order) to req_ids after forward().
|
"""Map pending metadata (batch order) to req_ids after forward().
|
||||||
|
|
||||||
Guard: if a request already has metadata with ``image_shape``
|
Guard: if a request already has metadata with ``image_shape``
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ VOXCPM2_PIPELINE = PipelineConfig(
|
|||||||
final_output_type="audio",
|
final_output_type="audio",
|
||||||
owns_tokenizer=True,
|
owns_tokenizer=True,
|
||||||
engine_output_type="audio",
|
engine_output_type="audio",
|
||||||
|
scheduler_cls="vllm_omni.model_executor.models.voxcpm2.scheduler.VoxCPM2OmniARAsyncScheduler",
|
||||||
sampling_constraints={
|
sampling_constraints={
|
||||||
"detokenize": False,
|
"detokenize": False,
|
||||||
"stop_token_ids": [1],
|
"stop_token_ids": [1],
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from vllm.v1.request import RequestStatus
|
||||||
|
|
||||||
|
from vllm_omni.core.sched.omni_ar_scheduler import OmniARAsyncScheduler
|
||||||
|
|
||||||
|
|
||||||
|
class VoxCPM2OmniARAsyncScheduler(OmniARAsyncScheduler):
|
||||||
|
"""VoxCPM2 scheduler variant for full unified decode graph serving.
|
||||||
|
|
||||||
|
VoxCPM2's full unified decode graph only applies to pure decode batches.
|
||||||
|
When a decode-ready request is already running, this scheduler defers new
|
||||||
|
waiting admissions for the current tick so the decode batch can stay on the
|
||||||
|
VoxCPM2 graph path. This is a model-local serving policy, not a generic AR
|
||||||
|
scheduler rule.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _unified_decode_graph_enabled(self) -> bool:
|
||||||
|
hf_config = getattr(self.vllm_config.model_config, "hf_config", None)
|
||||||
|
runtime_config = getattr(hf_config, "voxcpm2_runtime_config", None)
|
||||||
|
if isinstance(runtime_config, dict):
|
||||||
|
return bool(runtime_config.get("enable_unified_decode_graph", False))
|
||||||
|
return bool(getattr(runtime_config, "enable_unified_decode_graph", False))
|
||||||
|
|
||||||
|
def _should_defer_waiting_for_unified_decode_graph(self) -> bool:
|
||||||
|
if not self._unified_decode_graph_enabled():
|
||||||
|
return False
|
||||||
|
if not self.waiting or not self.running:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for request in self.running:
|
||||||
|
if getattr(request, "status", None) != RequestStatus.RUNNING or request.is_finished():
|
||||||
|
continue
|
||||||
|
if self._get_confirmed_num_computed_tokens(request) >= request.num_prompt_tokens:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _should_defer_waiting_admission(self) -> bool:
|
||||||
|
return self._should_defer_waiting_for_unified_decode_graph()
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -621,8 +621,8 @@ class NPUARModelRunner(OmniNPUModelRunner):
|
|||||||
positions=positions,
|
positions=positions,
|
||||||
inputs_embeds=inputs_embeds,
|
inputs_embeds=inputs_embeds,
|
||||||
req_ids=req_ids[:num_reqs],
|
req_ids=req_ids[:num_reqs],
|
||||||
num_computed_tokens=[int(self.input_batch.num_computed_tokens_cpu[i]) for i in range(num_reqs)],
|
num_computed_tokens=self.input_batch.num_computed_tokens_cpu[:num_reqs],
|
||||||
num_scheduled_tokens=[int(num_scheduled_tokens_np[i]) for i in range(num_reqs)],
|
num_scheduled_tokens=num_scheduled_tokens_np[:num_reqs],
|
||||||
input_ids_buffer=self.input_ids.gpu[:num_tokens_padded],
|
input_ids_buffer=self.input_ids.gpu[:num_tokens_padded],
|
||||||
)
|
)
|
||||||
# -------------------------------------- Omni-new -------------------------------------------------
|
# -------------------------------------- Omni-new -------------------------------------------------
|
||||||
@@ -690,8 +690,9 @@ class NPUARModelRunner(OmniNPUModelRunner):
|
|||||||
with record_function_or_nullcontext("post process"):
|
with record_function_or_nullcontext("post process"):
|
||||||
# -------------------------------------- Omni-new -------------------------------------------------
|
# -------------------------------------- Omni-new -------------------------------------------------
|
||||||
# [Omni] Map pending ropes metadata to req_ids.
|
# [Omni] Map pending ropes metadata to req_ids.
|
||||||
if hasattr(self.model, "flush_pending_metadata"):
|
flush_pending_metadata = getattr(self.model, "flush_pending_metadata", None)
|
||||||
self.model.flush_pending_metadata(list(req_ids))
|
if callable(flush_pending_metadata):
|
||||||
|
flush_pending_metadata(req_ids[:num_reqs])
|
||||||
|
|
||||||
hidden_states, multimodal_outputs = self.extract_multimodal_outputs(hidden_states)
|
hidden_states, multimodal_outputs = self.extract_multimodal_outputs(hidden_states)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import gc
|
import gc
|
||||||
import threading
|
import threading
|
||||||
from collections.abc import Callable, Mapping
|
from collections.abc import Callable, Mapping, Sequence
|
||||||
from contextlib import nullcontext
|
from contextlib import nullcontext
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from dataclasses import replace
|
from dataclasses import replace
|
||||||
@@ -46,6 +46,7 @@ from vllm_omni.outputs import OmniModelRunnerOutput
|
|||||||
from vllm_omni.utils.mm_outputs import build_mm_cpu, partition_payload_list, to_payload_element
|
from vllm_omni.utils.mm_outputs import build_mm_cpu, partition_payload_list, to_payload_element
|
||||||
from vllm_omni.worker.gpu_model_runner import OmniGPUModelRunner
|
from vllm_omni.worker.gpu_model_runner import OmniGPUModelRunner
|
||||||
from vllm_omni.worker.omni_connector_model_runner_mixin import OmniConnectorModelRunnerMixin
|
from vllm_omni.worker.omni_connector_model_runner_mixin import OmniConnectorModelRunnerMixin
|
||||||
|
from vllm_omni.worker.runner_assisted_metadata import RunnerAssistedFullAttentionMetadataRequest
|
||||||
|
|
||||||
logger = init_logger(__name__)
|
logger = init_logger(__name__)
|
||||||
|
|
||||||
@@ -588,6 +589,93 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
|||||||
model = getattr(self, "model", None)
|
model = getattr(self, "model", None)
|
||||||
return bool(getattr(model, "requires_full_prefix_cached_hidden_states", True))
|
return bool(getattr(model, "requires_full_prefix_cached_hidden_states", True))
|
||||||
|
|
||||||
|
def _get_runner_assisted_full_attention_metadata_request(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
req_ids: Sequence[str],
|
||||||
|
num_reqs: int,
|
||||||
|
num_reqs_padded: int,
|
||||||
|
num_scheduled_tokens_np: np.ndarray,
|
||||||
|
num_computed_tokens_cpu: torch.Tensor | np.ndarray,
|
||||||
|
max_num_scheduled_tokens: int,
|
||||||
|
) -> RunnerAssistedFullAttentionMetadataRequest | None:
|
||||||
|
# Models without this hook keep the normal runner path. VoxCPM2 uses
|
||||||
|
# it to ask the runner for padded FULL attention metadata while keeping
|
||||||
|
# graph policy and lifecycle in the model layer.
|
||||||
|
hook = getattr(self.model, "get_runner_assisted_full_attention_metadata_request", None)
|
||||||
|
if not callable(hook):
|
||||||
|
return None
|
||||||
|
num_computed_tokens = num_computed_tokens_cpu
|
||||||
|
if hasattr(num_computed_tokens, "numpy"):
|
||||||
|
num_computed_tokens = num_computed_tokens.numpy()
|
||||||
|
request = hook(
|
||||||
|
req_ids=req_ids,
|
||||||
|
num_reqs=num_reqs,
|
||||||
|
num_scheduled_tokens=num_scheduled_tokens_np[:num_reqs],
|
||||||
|
num_computed_tokens=num_computed_tokens,
|
||||||
|
max_num_scheduled_tokens=max_num_scheduled_tokens,
|
||||||
|
)
|
||||||
|
if request is None:
|
||||||
|
return None
|
||||||
|
if not isinstance(request, RunnerAssistedFullAttentionMetadataRequest):
|
||||||
|
raise TypeError(
|
||||||
|
"runner-assisted full attention metadata hook must return "
|
||||||
|
"RunnerAssistedFullAttentionMetadataRequest or None, got "
|
||||||
|
f"{type(request).__name__}"
|
||||||
|
)
|
||||||
|
padded_num_reqs = max(
|
||||||
|
num_reqs_padded,
|
||||||
|
min(int(request.num_reqs_padded), self.scheduler_config.max_num_seqs),
|
||||||
|
)
|
||||||
|
return RunnerAssistedFullAttentionMetadataRequest(
|
||||||
|
num_reqs_padded=padded_num_reqs,
|
||||||
|
for_cudagraph_capture=bool(request.for_cudagraph_capture),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _refresh_runner_assisted_full_attention_metadata_buffers(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
num_reqs: int,
|
||||||
|
num_reqs_padded: int,
|
||||||
|
num_scheduled_tokens_np: np.ndarray,
|
||||||
|
) -> None:
|
||||||
|
num_computed_tokens = self.input_batch.num_computed_tokens_cpu[:num_reqs]
|
||||||
|
if hasattr(num_computed_tokens, "numpy"):
|
||||||
|
num_computed_tokens = num_computed_tokens.numpy()
|
||||||
|
np.add(
|
||||||
|
num_computed_tokens,
|
||||||
|
num_scheduled_tokens_np,
|
||||||
|
out=self.optimistic_seq_lens_cpu[:num_reqs].numpy(),
|
||||||
|
)
|
||||||
|
self.optimistic_seq_lens_cpu[num_reqs:num_reqs_padded].fill_(0)
|
||||||
|
self.seq_lens.copy_(self.optimistic_seq_lens_cpu, non_blocking=True)
|
||||||
|
|
||||||
|
cum_num_tokens = self._get_cumsum_and_arange(
|
||||||
|
num_scheduled_tokens_np,
|
||||||
|
self.query_pos.np,
|
||||||
|
)
|
||||||
|
self.query_start_loc.np[0] = 0
|
||||||
|
self.query_start_loc.np[1 : num_reqs + 1] = cum_num_tokens
|
||||||
|
self.query_start_loc.np[num_reqs + 1 : num_reqs_padded + 1].fill(cum_num_tokens[-1])
|
||||||
|
self.query_start_loc.copy_to_gpu()
|
||||||
|
|
||||||
|
self.input_batch.block_table.commit_block_table(num_reqs_padded)
|
||||||
|
|
||||||
|
def _set_runner_assisted_full_attention_metadata_context(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
enabled: bool,
|
||||||
|
num_reqs: int = 0,
|
||||||
|
) -> bool:
|
||||||
|
hook = getattr(self.model, "set_runner_assisted_full_attention_metadata_context", None)
|
||||||
|
if not callable(hook):
|
||||||
|
return False
|
||||||
|
hook(
|
||||||
|
enabled=enabled,
|
||||||
|
num_reqs=num_reqs,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
def _deferred_prefix_cache_mm_keys(self) -> set[str]:
|
def _deferred_prefix_cache_mm_keys(self) -> set[str]:
|
||||||
"""Model-declared multimodal keys whose prefix-cache writes are deferred."""
|
"""Model-declared multimodal keys whose prefix-cache writes are deferred."""
|
||||||
model = getattr(self, "model", None)
|
model = getattr(self, "model", None)
|
||||||
@@ -1028,6 +1116,26 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
|||||||
)
|
)
|
||||||
num_tokens_padded = batch_desc.num_tokens
|
num_tokens_padded = batch_desc.num_tokens
|
||||||
num_reqs_padded = batch_desc.num_reqs if batch_desc.num_reqs is not None else num_reqs
|
num_reqs_padded = batch_desc.num_reqs if batch_desc.num_reqs is not None else num_reqs
|
||||||
|
num_computed_tokens_cpu = self.input_batch.num_computed_tokens_cpu[:num_reqs]
|
||||||
|
|
||||||
|
runner_assisted_full_attn_request = self._get_runner_assisted_full_attention_metadata_request(
|
||||||
|
req_ids=req_ids[:num_reqs],
|
||||||
|
num_reqs=num_reqs,
|
||||||
|
num_reqs_padded=num_reqs_padded,
|
||||||
|
num_scheduled_tokens_np=num_scheduled_tokens_np,
|
||||||
|
num_computed_tokens_cpu=num_computed_tokens_cpu,
|
||||||
|
max_num_scheduled_tokens=max_num_scheduled_tokens,
|
||||||
|
)
|
||||||
|
runner_assisted_full_attn_capture = False
|
||||||
|
if runner_assisted_full_attn_request is not None:
|
||||||
|
num_reqs_padded = runner_assisted_full_attn_request.num_reqs_padded
|
||||||
|
runner_assisted_full_attn_capture = runner_assisted_full_attn_request.for_cudagraph_capture
|
||||||
|
num_tokens_padded = max(
|
||||||
|
num_tokens_padded,
|
||||||
|
num_reqs_padded * max_num_scheduled_tokens,
|
||||||
|
)
|
||||||
|
runner_assisted_full_attn = runner_assisted_full_attn_request is not None
|
||||||
|
|
||||||
ubatch_slices, ubatch_slices_padded = maybe_create_ubatch_slices(
|
ubatch_slices, ubatch_slices_padded = maybe_create_ubatch_slices(
|
||||||
should_ubatch,
|
should_ubatch,
|
||||||
num_scheduled_tokens_np,
|
num_scheduled_tokens_np,
|
||||||
@@ -1035,8 +1143,7 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
|||||||
num_reqs_padded,
|
num_reqs_padded,
|
||||||
self.parallel_config.num_ubatches,
|
self.parallel_config.num_ubatches,
|
||||||
)
|
)
|
||||||
|
pad_attn = cudagraph_mode == CUDAGraphMode.FULL or runner_assisted_full_attn
|
||||||
pad_attn = cudagraph_mode == CUDAGraphMode.FULL
|
|
||||||
|
|
||||||
use_spec_decode = len(scheduler_output.scheduled_spec_decode_tokens) > 0
|
use_spec_decode = len(scheduler_output.scheduled_spec_decode_tokens) > 0
|
||||||
ubatch_slices_attn = ubatch_slices_padded if pad_attn else ubatch_slices
|
ubatch_slices_attn = ubatch_slices_padded if pad_attn else ubatch_slices
|
||||||
@@ -1058,11 +1165,18 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
|||||||
num_tokens_unpadded=num_tokens_unpadded,
|
num_tokens_unpadded=num_tokens_unpadded,
|
||||||
ubatch_slices=ubatch_slices_padded,
|
ubatch_slices=ubatch_slices_padded,
|
||||||
)
|
)
|
||||||
|
if runner_assisted_full_attn:
|
||||||
|
self._refresh_runner_assisted_full_attention_metadata_buffers(
|
||||||
|
num_reqs=num_reqs,
|
||||||
|
num_reqs_padded=num_reqs_padded,
|
||||||
|
num_scheduled_tokens_np=num_scheduled_tokens_np,
|
||||||
|
)
|
||||||
|
|
||||||
|
attn_num_reqs = num_reqs_padded if runner_assisted_full_attn else num_reqs
|
||||||
attn_metadata, spec_decode_common_attn_metadata = self._build_attention_metadata(
|
attn_metadata, spec_decode_common_attn_metadata = self._build_attention_metadata(
|
||||||
num_tokens=num_tokens_unpadded,
|
num_tokens=num_tokens_unpadded,
|
||||||
num_tokens_padded=num_tokens_padded if pad_attn else None,
|
num_tokens_padded=num_tokens_padded if pad_attn else None,
|
||||||
num_reqs=num_reqs,
|
num_reqs=attn_num_reqs,
|
||||||
num_reqs_padded=num_reqs_padded if pad_attn else None,
|
num_reqs_padded=num_reqs_padded if pad_attn else None,
|
||||||
max_query_len=max_num_scheduled_tokens,
|
max_query_len=max_num_scheduled_tokens,
|
||||||
ubatch_slices=ubatch_slices_attn,
|
ubatch_slices=ubatch_slices_attn,
|
||||||
@@ -1071,6 +1185,7 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
|||||||
num_scheduled_tokens=scheduler_output.num_scheduled_tokens,
|
num_scheduled_tokens=scheduler_output.num_scheduled_tokens,
|
||||||
cascade_attn_prefix_lens=cascade_attn_prefix_lens,
|
cascade_attn_prefix_lens=cascade_attn_prefix_lens,
|
||||||
slot_mappings=slot_mappings_by_group,
|
slot_mappings=slot_mappings_by_group,
|
||||||
|
for_cudagraph_capture=runner_assisted_full_attn_capture,
|
||||||
)
|
)
|
||||||
self._maybe_attach_attention_metadata_extensions(
|
self._maybe_attach_attention_metadata_extensions(
|
||||||
attn_metadata=attn_metadata,
|
attn_metadata=attn_metadata,
|
||||||
@@ -1079,6 +1194,7 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
|||||||
max_query_len=max_num_scheduled_tokens,
|
max_query_len=max_num_scheduled_tokens,
|
||||||
pad_attn=pad_attn,
|
pad_attn=pad_attn,
|
||||||
num_scheduled_tokens_np=num_scheduled_tokens_np,
|
num_scheduled_tokens_np=num_scheduled_tokens_np,
|
||||||
|
for_cudagraph_capture=runner_assisted_full_attn_capture,
|
||||||
)
|
)
|
||||||
|
|
||||||
(
|
(
|
||||||
@@ -1092,14 +1208,15 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
|||||||
|
|
||||||
# Let the model adjust inputs before forward (e.g. restore input_ids
|
# Let the model adjust inputs before forward (e.g. restore input_ids
|
||||||
# for multimodal position detection, fix decode position offsets).
|
# for multimodal position detection, fix decode position offsets).
|
||||||
if hasattr(self.model, "prepare_runner_inputs"):
|
prepare_runner_inputs = getattr(self.model, "prepare_runner_inputs", None)
|
||||||
input_ids, positions = self.model.prepare_runner_inputs(
|
if callable(prepare_runner_inputs):
|
||||||
|
input_ids, positions = prepare_runner_inputs(
|
||||||
input_ids=input_ids,
|
input_ids=input_ids,
|
||||||
positions=positions,
|
positions=positions,
|
||||||
inputs_embeds=inputs_embeds,
|
inputs_embeds=inputs_embeds,
|
||||||
req_ids=req_ids[:num_reqs],
|
req_ids=req_ids[:num_reqs],
|
||||||
num_computed_tokens=[int(self.input_batch.num_computed_tokens_cpu[i]) for i in range(num_reqs)],
|
num_computed_tokens=self.input_batch.num_computed_tokens_cpu[:num_reqs],
|
||||||
num_scheduled_tokens=[int(num_scheduled_tokens_np[i]) for i in range(num_reqs)],
|
num_scheduled_tokens=num_scheduled_tokens_np[:num_reqs],
|
||||||
input_ids_buffer=self.input_ids.gpu[:num_tokens_padded],
|
input_ids_buffer=self.input_ids.gpu[:num_tokens_padded],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1108,46 +1225,60 @@ class GPUARModelRunner(OmniGPUModelRunner, OmniConnectorModelRunnerMixin):
|
|||||||
# with CUDA graph capture.
|
# with CUDA graph capture.
|
||||||
if self.calculate_kv_scales:
|
if self.calculate_kv_scales:
|
||||||
cudagraph_mode = CUDAGraphMode.NONE
|
cudagraph_mode = CUDAGraphMode.NONE
|
||||||
|
runner_assisted_full_attn = False
|
||||||
# Mark KV scales as calculated after the first forward pass
|
# Mark KV scales as calculated after the first forward pass
|
||||||
self.calculate_kv_scales = False
|
self.calculate_kv_scales = False
|
||||||
|
|
||||||
|
runner_assisted_context_enabled = False
|
||||||
|
if runner_assisted_full_attn:
|
||||||
|
runner_assisted_context_enabled = self._set_runner_assisted_full_attention_metadata_context(
|
||||||
|
enabled=True,
|
||||||
|
num_reqs=num_reqs,
|
||||||
|
)
|
||||||
|
runner_assisted_full_attn = runner_assisted_context_enabled
|
||||||
|
|
||||||
# Run the model.
|
# Run the model.
|
||||||
# Use persistent buffers for CUDA graphs.
|
# Use persistent buffers for CUDA graphs.
|
||||||
# When spec decode is enabled, defer connector finalization
|
# When spec decode is enabled, defer connector finalization
|
||||||
# (wait_for_save + clear metadata) until after draft model runs.
|
# (wait_for_save + clear metadata) until after draft model runs.
|
||||||
defer_kv_connector_finalize = self.speculative_config is not None
|
defer_kv_connector_finalize = self.speculative_config is not None
|
||||||
with (
|
try:
|
||||||
nullcontext(),
|
with (
|
||||||
set_forward_context(
|
nullcontext(),
|
||||||
attn_metadata,
|
set_forward_context(
|
||||||
self.vllm_config,
|
attn_metadata,
|
||||||
num_tokens=num_tokens_padded,
|
self.vllm_config,
|
||||||
num_tokens_across_dp=num_tokens_across_dp,
|
num_tokens=num_tokens_padded,
|
||||||
cudagraph_runtime_mode=cudagraph_mode,
|
num_tokens_across_dp=num_tokens_across_dp,
|
||||||
batch_descriptor=batch_desc,
|
cudagraph_runtime_mode=(CUDAGraphMode.FULL if runner_assisted_full_attn else cudagraph_mode),
|
||||||
ubatch_slices=ubatch_slices_padded,
|
batch_descriptor=batch_desc,
|
||||||
slot_mapping=slot_mappings, # OMNI: required for KV cache operations
|
ubatch_slices=ubatch_slices_padded,
|
||||||
),
|
slot_mapping=slot_mappings, # OMNI: required for KV cache operations
|
||||||
record_function_or_nullcontext("gpu_model_runner: forward"),
|
),
|
||||||
self.maybe_get_kv_connector_output(
|
record_function_or_nullcontext("gpu_model_runner: forward"),
|
||||||
scheduler_output,
|
self.maybe_get_kv_connector_output(
|
||||||
defer_finalize=defer_kv_connector_finalize,
|
scheduler_output,
|
||||||
) as kv_connector_output,
|
defer_finalize=defer_kv_connector_finalize,
|
||||||
):
|
) as kv_connector_output,
|
||||||
model_output = self._model_forward(
|
):
|
||||||
input_ids=input_ids,
|
model_output = self._model_forward(
|
||||||
positions=positions,
|
input_ids=input_ids,
|
||||||
intermediate_tensors=intermediate_tensors,
|
positions=positions,
|
||||||
inputs_embeds=inputs_embeds,
|
intermediate_tensors=intermediate_tensors,
|
||||||
**model_kwargs,
|
inputs_embeds=inputs_embeds,
|
||||||
sampling_metadata=self.input_batch.sampling_metadata,
|
**model_kwargs,
|
||||||
logits_index=logits_indices,
|
sampling_metadata=self.input_batch.sampling_metadata,
|
||||||
sampler=self.sampler,
|
logits_index=logits_indices,
|
||||||
)
|
sampler=self.sampler,
|
||||||
|
)
|
||||||
|
|
||||||
# [Omni] Map pending ropes metadata to req_ids.
|
# [Omni] Map pending ropes metadata to req_ids.
|
||||||
if hasattr(self.model, "flush_pending_metadata"):
|
flush_pending_metadata = getattr(self.model, "flush_pending_metadata", None)
|
||||||
self.model.flush_pending_metadata(list(req_ids))
|
if callable(flush_pending_metadata):
|
||||||
|
flush_pending_metadata(req_ids[:num_reqs])
|
||||||
|
finally:
|
||||||
|
if runner_assisted_context_enabled:
|
||||||
|
self._set_runner_assisted_full_attention_metadata_context(enabled=False)
|
||||||
|
|
||||||
with record_function_or_nullcontext("gpu_model_runner: postprocess"):
|
with record_function_or_nullcontext("gpu_model_runner: postprocess"):
|
||||||
if self.use_aux_hidden_state_outputs:
|
if self.use_aux_hidden_state_outputs:
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
from typing import NamedTuple, Protocol
|
||||||
|
|
||||||
|
|
||||||
|
class RunnerAssistedFullAttentionMetadataRequest(NamedTuple):
|
||||||
|
num_reqs_padded: int
|
||||||
|
for_cudagraph_capture: bool
|
||||||
|
|
||||||
|
|
||||||
|
class RunnerAssistedAttentionMetadataProvider(Protocol):
|
||||||
|
"""Optional model hook for runner-built full attention metadata."""
|
||||||
|
|
||||||
|
def get_runner_assisted_full_attention_metadata_request(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
req_ids: Sequence[str],
|
||||||
|
num_reqs: int,
|
||||||
|
num_scheduled_tokens: Sequence[int],
|
||||||
|
num_computed_tokens: Sequence[int],
|
||||||
|
max_num_scheduled_tokens: int,
|
||||||
|
) -> RunnerAssistedFullAttentionMetadataRequest | None: ...
|
||||||
|
|
||||||
|
def set_runner_assisted_full_attention_metadata_context(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
enabled: bool,
|
||||||
|
num_reqs: int = 0,
|
||||||
|
) -> None: ...
|
||||||
Reference in new issue
Block a user