Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
pytestmark = [
pytest.mark.unit,
pytest.mark.sglang,
pytest.mark.gpu_1,
pytest.mark.gpu_0,
pytest.mark.pre_merge,
pytest.mark.profiled_vram_gib(0),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
pytestmark = [
pytest.mark.unit,
pytest.mark.sglang,
pytest.mark.gpu_1,
pytest.mark.gpu_0,
pytest.mark.pre_merge,
pytest.mark.profiled_vram_gib(0),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@
pytestmark = [
pytest.mark.unit,
pytest.mark.sglang,
pytest.mark.gpu_1,
pytest.mark.gpu_0,
# Registers the tokenizer in the session predownload manifest (tests/conftest.py)
# so it stays fetchable after a worker's predownload test flips HF_HUB_OFFLINE.
pytest.mark.model("Qwen/Qwen3-0.6B"),
pytest.mark.pre_merge,
pytest.mark.profiled_vram_gib(0),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
pytestmark = [
pytest.mark.unit,
pytest.mark.sglang,
pytest.mark.gpu_1,
pytest.mark.gpu_0,
# This file builds a real tokenizer at module scope; declare the model so
# Registers the tokenizer in the session predownload manifest (tests/conftest.py)
# so it stays fetchable after a worker's predownload test flips HF_HUB_OFFLINE.
pytest.mark.model("Qwen/Qwen3-0.6B"),
pytest.mark.pre_merge,
pytest.mark.profiled_vram_gib(0),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,16 @@ def _resolve_qwen3_tool_parser_class():
return Qwen3CoderToolParser


# Needs vllm packages (gpu_1 container), but does not allocate GPU VRAM.
# Needs vllm packages, but never touches a GPU.
pytestmark = [
pytest.mark.unit,
pytest.mark.vllm,
pytest.mark.gpu_1,
pytest.mark.gpu_0,
# This file builds a real tokenizer. The marker declares it in the session
# predownload manifest (tests/conftest.py), which is what keeps it fetchable
# on a lane that predownloads and flips HF_HUB_OFFLINE; the CPU lane has no
# predownload consumer today, so there it is fetched live.
pytest.mark.model("Qwen/Qwen3-0.6B"),
pytest.mark.xpu_1,
pytest.mark.pre_merge,
pytest.mark.profiled_vram_gib(0),
Expand Down
2 changes: 1 addition & 1 deletion components/src/dynamo/sglang/tests/test_fpm_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
pytestmark = [
pytest.mark.unit,
pytest.mark.sglang,
pytest.mark.gpu_1, # needs sglang packages installed
pytest.mark.gpu_0,
pytest.mark.profiled_vram_gib(0),
pytest.mark.pre_merge,
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
pytestmark = [
pytest.mark.unit,
pytest.mark.sglang,
pytest.mark.gpu_1, # sglang tests run on GPU-enabled workers
pytest.mark.gpu_0,
# These are sub-second unit tests. A generous cap so a hang here fails
# this test instead of stalling the whole session.
pytest.mark.timeout(60),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
pytestmark = [
pytest.mark.unit,
pytest.mark.sglang,
pytest.mark.gpu_1, # sglang tests run on GPU-enabled workers
pytest.mark.gpu_0,
pytest.mark.profiled_vram_gib(0),
pytest.mark.pre_merge,
]
Expand Down Expand Up @@ -93,6 +93,9 @@ def test_get_frames_as_tensor_returns_nhwc_uint8_cpu(decoder) -> None:
@pytest.mark.skipif(
not torch.cuda.is_available(), reason="pinned host memory requires CUDA"
)
# The one test here that touches CUDA. It stays consistent with the file's
# gpu_0 mark because gpu_0 means "no GPU required": on the CPU-only lane it
# skips itself, and on the GPU job's gpu_0 stage it runs and guards pinning.
def test_frames_are_pinned_on_cuda(decoder) -> None:
"""Pinned memory is the performance contract of this return type.

Expand Down
32 changes: 31 additions & 1 deletion components/src/dynamo/sglang/tests/test_sglang_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
pytest.mark.unit,
pytest.mark.sglang,
pytest.mark.core,
pytest.mark.gpu_1, # needs sglang & GPU packages installed but does not actually use GPU
pytest.mark.gpu_0,
pytest.mark.profiled_vram_gib(0), # These unit tests do not actually use GPU VRAM
pytest.mark.pre_merge,
]
Expand All @@ -67,6 +67,36 @@
mock_sglang_cli = make_cli_args_fixture("dynamo.sglang")


@pytest.fixture(autouse=True)
def _cpu_engine_when_no_accelerator(monkeypatch):
"""Honor the file's gpu_0 contract on hosts with no accelerator.

Many tests here run parse_args, and SGLang's ServerArgs.__post_init__
auto-detects a device only when --device is not given; on a host with no
accelerator the detection walks every platform and ends in
NotImplementedError (get_device -> SRTPlatform(unknown), verified on the
amd64 image with the GPU masked). SGLANG_USE_CPU_ENGINE=1 is SGLang's
public knob that makes the detection return "cpu". Set it only when CUDA
is absent so GPU hosts keep exercising the real detection path, and clear
the lru_caches on both probes so a worker that cached the no-env result
while running another file's tests cannot poison this one (and vice
versa on teardown, via the second clear).
"""
import torch

if torch.cuda.is_available():
yield
return
from sglang.srt.utils import common as _sgl_common

monkeypatch.setenv("SGLANG_USE_CPU_ENGINE", "1")
_sgl_common.is_cpu.cache_clear()
_sgl_common.get_device.cache_clear()
yield
_sgl_common.is_cpu.cache_clear()
_sgl_common.get_device.cache_clear()
Comment thread
dmitry-tokarev-nv marked this conversation as resolved.


def _make_sglang_config(**overrides):
config = DynamoSGLangConfig()
config.use_sglang_tokenizer = False
Expand Down
50 changes: 50 additions & 0 deletions components/src/dynamo/vllm/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,56 @@ def pytest_ignore_collect(collection_path, config):
return None


_PLATFORM_UNSET = object()


@pytest.fixture
def vllm_cpu_platform_when_no_accelerator():
"""Pin vLLM's CpuPlatform on hosts where vLLM recognizes no accelerator.

Tests that build the vLLM engine argument parser reach
``DeviceConfig.__post_init__``, which resolves ``current_platform`` when
``device`` is left at ``"auto"`` and raises ``RuntimeError: Failed to infer
device type`` if the resolved platform has an empty ``device_type``. With no
accelerator every builtin platform plugin declines and the resolver falls
back to ``UnspecifiedPlatform``, whose ``device_type`` is exactly that. The
parser instantiates each config dataclass to compute its argparse default,
so this fires while *building* the parser -- passing ``--device cpu`` cannot
avoid it.

Gating on ``device_type`` rather than on ``torch.cuda`` makes this a no-op
wherever vLLM already recognizes the hardware, so both CUDA and XPU runners
keep exercising real detection (these modules are also marked ``xpu_1``).

``vllm.platforms`` resolves ``current_platform`` lazily through a module
``__getattr__``, and assigning the name writes a real module-dict entry that
shadows the hook. That entry, not the ``__setattr__`` the module also
defines, is what makes the pin visible: the interpreter never calls a
module-level ``__setattr__``, since PEP 562 covers only ``__getattr__`` and
``__dir__``. Teardown therefore *deletes* the entry to re-arm the lazy hook
-- assigning the previous value back would leave the resolver shadowed for
every later test on this xdist worker.
"""
import vllm.platforms as vllm_platforms
from vllm.platforms import current_platform

if current_platform.device_type:
yield
return

from vllm.platforms.cpu import CpuPlatform

previous = vllm_platforms.__dict__.get("current_platform", _PLATFORM_UNSET)
vllm_platforms.current_platform = CpuPlatform()
try:
yield
finally:
if previous is _PLATFORM_UNSET:
del vllm_platforms.current_platform
else:
vllm_platforms.current_platform = previous


def make_cli_args_fixture(module_name: str):
"""Create a pytest fixture for mocking CLI arguments for vllm backend."""

Expand Down
5 changes: 4 additions & 1 deletion components/src/dynamo/vllm/tests/omni/test_omni_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
pytestmark = [
pytest.mark.unit,
pytest.mark.vllm,
pytest.mark.gpu_1,
pytest.mark.gpu_0,
# Building the vLLM argument parser resolves a device; on an accelerator-less
# host that raises unless a platform is pinned first.
pytest.mark.usefixtures("vllm_cpu_platform_when_no_accelerator"),
pytest.mark.xpu_1,
pytest.mark.pre_merge,
pytest.mark.profiled_vram_gib(0),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
pytestmark = [
pytest.mark.unit,
pytest.mark.vllm,
pytest.mark.gpu_1,
pytest.mark.gpu_0,
pytest.mark.pre_merge,
pytest.mark.profiled_vram_gib(0),
pytest.mark.timeout(180), # 0-GiB unit tests, floor 180s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
pytestmark = [
pytest.mark.unit,
pytest.mark.vllm,
pytest.mark.gpu_1,
pytest.mark.gpu_0,
pytest.mark.pre_merge,
pytest.mark.profiled_vram_gib(0),
pytest.mark.timeout(180), # 0-GiB unit tests, floor 180s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
pytestmark = [
pytest.mark.unit,
pytest.mark.vllm,
pytest.mark.gpu_1,
pytest.mark.gpu_0,
pytest.mark.pre_merge,
pytest.mark.profiled_vram_gib(0),
pytest.mark.timeout(180), # 0-GiB unit tests, floor 180s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
pytestmark = [
pytest.mark.unit,
pytest.mark.vllm,
pytest.mark.gpu_1,
pytest.mark.gpu_0,
pytest.mark.xpu_1,
pytest.mark.pre_merge,
pytest.mark.profiled_vram_gib(0),
Expand Down
7 changes: 4 additions & 3 deletions components/src/dynamo/vllm/tests/test_vllm_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@
pytest.mark.unit,
pytest.mark.vllm,
pytest.mark.core,
# gpu_1 not gpu_0: vLLM DeviceConfig(device='auto') fails on CPU-only arm64
# runners with "Failed to infer device type" even for mock tests.
pytest.mark.gpu_1,
pytest.mark.gpu_0,
# Building the vLLM argument parser resolves a device; on an accelerator-less
# host that raises unless a platform is pinned first.
pytest.mark.usefixtures("vllm_cpu_platform_when_no_accelerator"),
pytest.mark.xpu_1,
pytest.mark.profiled_vram_gib(0),
pytest.mark.timeout(180), # 0-GiB unit tests, floor 180s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
pytest.mark.unit,
pytest.mark.vllm,
pytest.mark.core,
# gpu_1 not gpu_0: vLLM DeviceConfig(device='auto') fails on CPU-only arm64
# runners with "Failed to infer device type" even for mock tests.
pytest.mark.gpu_1,
pytest.mark.gpu_0,
pytest.mark.xpu_1,
pytest.mark.profiled_vram_gib(0),
pytest.mark.timeout(180), # 0-GiB unit tests, floor 180s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@
pytest.mark.gpu_1,
pytest.mark.model(VLLM_MM_MODEL),
pytest.mark.requested_vllm_kv_cache_bytes(1_719_075_000),
pytest.mark.profiled_vram_gib(18.7),
# Measured solo peak, which is what profiled_vram_gib means. The KV cap
# above pins the footprint, so this matches test_vllm_mm_router_e2e.py --
# same model, same cap, same 7.6 GiB. The previous 18.7 exceeded the
# multi-process budget, which made every test in this file exclusive.
pytest.mark.profiled_vram_gib(7.6),
]


Expand Down
6 changes: 5 additions & 1 deletion tests/mm_router/test_router_rust_mm_router_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@
pytest.mark.gpu_1,
pytest.mark.model(VLLM_MM_MODEL),
pytest.mark.requested_vllm_kv_cache_bytes(1_719_075_000),
pytest.mark.profiled_vram_gib(18.7),
# Measured solo peak, which is what profiled_vram_gib means. The KV cap
# above pins the footprint, so this matches test_vllm_mm_router_e2e.py --
# same model, same cap, same 7.6 GiB. The previous 18.7 exceeded the
# multi-process budget, which made every test in this file exclusive.
pytest.mark.profiled_vram_gib(7.6),
]


Expand Down
Loading