2026-07-23 00:01:45 +08:00
|
|
|
import contextlib
|
|
|
|
|
from types import SimpleNamespace
|
|
|
|
|
|
2026-02-09 16:49:45 +08:00
|
|
|
import pytest
|
2026-01-22 16:57:37 +08:00
|
|
|
import torch
|
2026-07-23 00:01:45 +08:00
|
|
|
from vllm.config import CacheConfig
|
2026-07-27 23:35:49 +08:00
|
|
|
from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT
|
2026-01-22 16:57:37 +08:00
|
|
|
|
2026-07-23 00:01:45 +08:00
|
|
|
import vllm_omni.worker.gpu_generation_model_runner as gen_runner_module
|
2026-06-01 17:35:07 +08:00
|
|
|
from vllm_omni.worker.gpu_generation_model_runner import (
|
|
|
|
|
ExecuteModelState,
|
|
|
|
|
GPUGenerationModelRunner,
|
|
|
|
|
)
|
2026-01-22 16:57:37 +08:00
|
|
|
|
2026-02-09 16:49:45 +08:00
|
|
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
|
|
|
|
|
2026-01-22 16:57:37 +08:00
|
|
|
|
|
|
|
|
class _DummyInputBatch:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.req_ids = ["req-1"]
|
|
|
|
|
self.req_id_to_index = {"req-1": 0}
|
|
|
|
|
self.num_reqs = 1
|
|
|
|
|
self.vocab_size = 10
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_runner(multimodal_outputs):
|
|
|
|
|
runner = object.__new__(GPUGenerationModelRunner)
|
2026-06-01 17:35:07 +08:00
|
|
|
runner.execute_model_state = ExecuteModelState(
|
|
|
|
|
None,
|
2026-01-22 16:57:37 +08:00
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
multimodal_outputs,
|
2026-02-13 18:05:07 +08:00
|
|
|
None,
|
2026-01-22 16:57:37 +08:00
|
|
|
)
|
|
|
|
|
runner.kv_connector_output = None
|
|
|
|
|
runner.input_batch = _DummyInputBatch()
|
|
|
|
|
runner.use_async_scheduling = False
|
|
|
|
|
runner.device = torch.device("cpu")
|
|
|
|
|
runner.supports_mm_inputs = False
|
2026-03-09 17:29:38 +08:00
|
|
|
runner.speculative_config = None
|
2026-05-16 18:18:43 +08:00
|
|
|
runner.routed_experts_initialized = False
|
2026-06-29 23:43:36 +08:00
|
|
|
runner._async_chunk = False
|
2026-01-22 16:57:37 +08:00
|
|
|
return runner
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_sample_tokens_tensor_output():
|
|
|
|
|
multimodal_outputs = torch.randn(1, 2, 3)
|
|
|
|
|
runner = _make_runner(multimodal_outputs)
|
|
|
|
|
|
|
|
|
|
output = GPUGenerationModelRunner.sample_tokens(runner)
|
|
|
|
|
|
2026-06-12 12:49:14 +05:30
|
|
|
assert len(output.multimodal_outputs) == 1
|
|
|
|
|
assert output.multimodal_outputs[0]["model_outputs"].shape == (2, 3)
|
2026-01-22 16:57:37 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_sample_tokens_list_output():
|
|
|
|
|
multimodal_outputs = [torch.randn(2, 1)]
|
|
|
|
|
runner = _make_runner(multimodal_outputs)
|
|
|
|
|
|
|
|
|
|
output = GPUGenerationModelRunner.sample_tokens(runner)
|
|
|
|
|
|
2026-06-12 12:49:14 +05:30
|
|
|
assert len(output.multimodal_outputs) == 1
|
|
|
|
|
assert output.multimodal_outputs[0]["model_outputs"].shape == (2, 1)
|
2026-01-22 16:57:37 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_sample_tokens_list_allows_none_output():
|
|
|
|
|
multimodal_outputs = [None]
|
|
|
|
|
runner = _make_runner(multimodal_outputs)
|
|
|
|
|
|
|
|
|
|
output = GPUGenerationModelRunner.sample_tokens(runner)
|
|
|
|
|
|
2026-06-12 12:49:14 +05:30
|
|
|
assert len(output.multimodal_outputs) == 1
|
|
|
|
|
assert output.multimodal_outputs[0]["model_outputs"] is None
|
2026-01-22 16:57:37 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_sample_tokens_dict_output():
|
|
|
|
|
multimodal_outputs = {"audio": torch.randn(1, 4), "unused": None}
|
|
|
|
|
runner = _make_runner(multimodal_outputs)
|
|
|
|
|
|
|
|
|
|
output = GPUGenerationModelRunner.sample_tokens(runner)
|
|
|
|
|
|
2026-06-12 12:49:14 +05:30
|
|
|
assert len(output.multimodal_outputs) == 1
|
|
|
|
|
assert "audio" in output.multimodal_outputs[0]
|
|
|
|
|
assert "unused" not in output.multimodal_outputs[0]
|
|
|
|
|
assert output.multimodal_outputs[0]["audio"].shape == (1, 4)
|
2026-07-23 00:01:45 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class _StubSchedulerOutput:
|
|
|
|
|
def __init__(self, total_num_scheduled_tokens):
|
|
|
|
|
self.total_num_scheduled_tokens = total_num_scheduled_tokens
|
|
|
|
|
self.num_scheduled_tokens = {"req-1": total_num_scheduled_tokens}
|
|
|
|
|
self.finished_req_ids = set()
|
|
|
|
|
self.kv_connector_metadata = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_guard_runner():
|
|
|
|
|
# Stubbed far enough that a span escaping the guard reaches the real
|
|
|
|
|
# `_prepare_inputs`, i.e. fails the way the reported crash does.
|
|
|
|
|
runner = object.__new__(GPUGenerationModelRunner)
|
|
|
|
|
runner.execute_model_state = None
|
|
|
|
|
runner.routed_experts_initialized = False
|
|
|
|
|
runner.speculative_config = None
|
|
|
|
|
runner.model_config = SimpleNamespace(async_chunk=False)
|
|
|
|
|
runner.cache_config = CacheConfig()
|
|
|
|
|
runner.input_batch = _DummyInputBatch()
|
|
|
|
|
runner.model = object()
|
|
|
|
|
runner._update_states = lambda scheduler_output: None
|
|
|
|
|
runner.synchronize_input_prep = contextlib.nullcontext
|
|
|
|
|
runner.attach_omni_connector_output = lambda result: result
|
|
|
|
|
return runner
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("total", [-1, -512, 0])
|
|
|
|
|
def test_execute_model_skips_non_positive_scheduled_span(monkeypatch, total):
|
|
|
|
|
"""#5196: a negative span is truthy, so it used to reach `_prepare_inputs`,
|
|
|
|
|
whose `assert total_num_scheduled_tokens > 0` killed the stage EngineCore."""
|
|
|
|
|
monkeypatch.setattr(gen_runner_module, "has_kv_transfer_group", lambda: False)
|
|
|
|
|
runner = _make_guard_runner()
|
|
|
|
|
|
|
|
|
|
output = GPUGenerationModelRunner.execute_model(runner, _StubSchedulerOutput(total))
|
|
|
|
|
|
|
|
|
|
assert output is EMPTY_MODEL_RUNNER_OUTPUT
|