Files
vLLM-4x-RTX-3090/patches/0028-envs-tests-register-the-fork-s-knobs-by-cache-identi.patch
T
MOVZX 777e910dd0 Initial commit
Signed-off-by: MOVZX <movzx@yahoo.com>
2026-10-15 01:01:52 +07:00

1846 lines
82 KiB
Diff

From 3d563a05b7ad5e4d616b1760ab443bc8785dbc58 Mon Sep 17 00:00:00 2001
From: halt95 <halt95@users.noreply.github.com>
Date: Tue, 22 Sep 2026 04:20:50 -0400
Subject: [PATCH 028/103] envs, tests: register the fork's knobs by cache
identity; lint; gpu test marker
- Every fork knob is registered in envs.py and classified as a compile/capture
factor or runtime-only; envs.py documents that capture identity and AOT
cache identity are different things.
- ruff findings cleared on lines this fork authored; upstream lines untouched.
- A `gpu` pytest marker so the GPU-free tier can be selected, not just skipped.
- Duplicate knob-table entries from two branches reconciled onto one
off|warn|raise normaliser.
---
pyproject.toml | 1 +
tests/conftest.py | 19 ++
.../qwen4_exp/test_host_uva_embedding.py | 6 +-
tests/test_envs.py | 129 +++++++++++
.../test_multiproc_executor_parent_death.py | 10 +-
.../v1/worker/gpu/test_block_table_guards.py | 11 +-
.../gpu/test_guard_reader_nonblocking.py | 81 ++++---
.../test_guard_telemetry_defaults_drain.py | 15 +-
.../worker/gpu/test_guard_telemetry_replay.py | 4 +-
.../gpu/test_pp_deferred_mamba_block_table.py | 46 ++--
.../worker/gpu/test_pp_deferred_mamba_gpu.py | 32 +--
.../gpu/test_qsa_ring_skip_telemetry.py | 86 ++++---
.../gpu/test_qsa_rope_position_guard.py | 151 ++++++++----
tests/v1/worker/test_grammar_fail_closed.py | 3 +-
vllm/envs.py | 219 +++++++++++++++++-
vllm/models/qwen4_exp/nvidia/qsa.py | 7 +-
vllm/v1/core/sched/e2_alloc_log.py | 148 +++++++++---
vllm/v1/ple_offload/test_fault.py | 6 +-
vllm/v1/worker/guard_telemetry.py | 37 +--
vllm/v1/worker/mamba_utils.py | 2 +-
vllm_ple_test_fault/__init__.py | 15 +-
21 files changed, 770 insertions(+), 258 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 1a77433..6a72b74 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -114,6 +114,7 @@ markers = [
"cpu_test: mark test as CPU-only test",
"split: run this test as part of a split",
"distributed: run this test only in distributed GPU tests",
+ "gpu: requires a CUDA device; auto-skipped when none is present, and deselectable as a tier with -m 'not gpu'",
"optional: optional tests that are automatically skipped, include --optional to run them",
]
diff --git a/tests/conftest.py b/tests/conftest.py
index 5fd594a..982caee 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1600,6 +1600,25 @@ def pytest_addoption(parser):
def pytest_collection_modifyitems(config, items):
+ # Flash-Next fork (v2.2): one place that decides what a GPU test is.
+ # `@pytest.mark.gpu` auto-skips without a device and makes the GPU-free tier
+ # SELECTABLE as `-m "not gpu"`, which a per-test skipif cannot do -- a
+ # skipif still reports the item as a skip rather than removing it.
+ #
+ # What this does NOT do: it does not make collection
+ # GPU-dependency-free. This hook runs after every module has been imported
+ # and every item collected, so a module whose import reaches CUDA, Triton or
+ # an absent vLLM extension still fails at COLLECTION, before any marker is
+ # consulted. Making the CPU tier collect cleanly is a separate job: defer
+ # those imports, or run the tier on its declared supported platform.
+ # The marker is also not authorization to use a busy shared device --
+ # test_pp_deferred_mamba_gpu.py keeps its separate VLLM_E2_GPU_TESTS opt-in.
+ if not torch.cuda.is_available():
+ skip_gpu = pytest.mark.skip(reason="no CUDA device (pytest marker: gpu)")
+ for item in items:
+ if "gpu" in item.keywords:
+ item.add_marker(skip_gpu)
+
if config.getoption("--optional"):
# --optional given in cli: do not skip optional tests
return
diff --git a/tests/models/qwen4_exp/test_host_uva_embedding.py b/tests/models/qwen4_exp/test_host_uva_embedding.py
index e55d5d5..4edc157 100644
--- a/tests/models/qwen4_exp/test_host_uva_embedding.py
+++ b/tests/models/qwen4_exp/test_host_uva_embedding.py
@@ -19,7 +19,6 @@ from vllm.model_executor.layers.vocab_parallel_embedding import (
host_embed_table_enabled,
)
-
VOCAB = 67
HIDDEN = 16
@@ -479,10 +478,7 @@ def test_u4_outer_post_load_preserves_pinned_uva_storage(monkeypatch):
loaded_weight = _weight(vocab, hidden)
local_table_bytes = layer.weight.numel() * layer.weight.element_size()
- assert (
- layer.weight_loader.__func__
- is VocabParallelEmbedding.weight_loader
- )
+ assert layer.weight_loader.__func__ is VocabParallelEmbedding.weight_loader
layer.weight_loader(layer.weight, loaded_weight)
model = nn.Module()
diff --git a/tests/test_envs.py b/tests/test_envs.py
index 1ef40cd..3e65518 100644
--- a/tests/test_envs.py
+++ b/tests/test_envs.py
@@ -2,6 +2,7 @@
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import os
+from pathlib import Path
from unittest.mock import patch
import pytest
@@ -587,3 +588,131 @@ class TestVllmMaxNSequences:
with pytest.raises(VLLMValidationError, match="n must be at most 128"):
SamplingParams(n=129)
+
+
+# ---------------------------------------------------------------------------
+# Flash-Next fork: the cache-factor contract (v2.2 fork knobs)
+#
+# compile_factors() is a deny-list, so a knob's classification is invisible
+# unless something asserts it. These tests are that assertion. They are CPU-only
+# and import nothing beyond vllm.envs, so they belong to the GPU-free tier.
+#
+# AOT identity vs CAPTURE identity: compile_factors() keys only the first. A
+# knob listed as hashed is genuinely protected across processes; a knob that is
+# only a capture factor is hashed conservatively and its capture safety still
+# rests on not changing the value between capture and replay in one process.
+# These tests check the AOT side, which is the side this file can check.
+#
+# Each row is (name, value to set, value the getter must then return). The
+# expected value matters: a getter that ignored the environment would still
+# satisfy a bare membership check, so every row proves the entry is live.
+# ---------------------------------------------------------------------------
+
+_FORK_HASHED = [
+ ("VLLM_HOST_EMBED_TABLE", "1", True),
+ ("VLLM_PLE_FP8_GLOBAL_SCALE", "1", True),
+ ("VLLM_QSA_KV_CLIP_COUNT", "7", "7"),
+ ("VLLM_QSA_KV_COLLECT", "/var/tmp/qsa-absmax", "/var/tmp/qsa-absmax"),
+ # Capture-configuration factor read in C++ (topk.cu exact_medium_enabled);
+ # the Python entry is a raw passthrough, never a second parser.
+ ("MERLIN_TOPK_EXACT_MEDIUM", "0", "0"),
+]
+
+_FORK_IGNORED = [
+ ("VLLM_QSA_KV_SCALES", "/var/tmp/scales.json", "/var/tmp/scales.json"),
+ # "WARN" also checks the .strip().lower() mirror of the read site.
+ ("VLLM_E2_GUARD_MODE", " WARN ", "warn"),
+ ("VLLM_E2_GUARD_INTERVAL", "0.25", "0.25"),
+ ("VLLM_E2_MEMSTATS", "2", "2"),
+ ("VLLM_E2_MEMSTATS_DIR", "/var/tmp/memstats", "/var/tmp/memstats"),
+ ("VLLM_E2_SCHED_LOG", "/var/tmp/sched.jsonl", "/var/tmp/sched.jsonl"),
+ ("VLLM_E2_ZERO_OWNERSHIP_CHECK", "1", True),
+ ("VLLM_PLE_OFFLOAD_HOME_DEVICE", "2", "2"),
+ ("VLLM_PLE_OFFLOAD_READY_TIMEOUT", "1800", 1800.0),
+ ("VLLM_PLE_OFFLOAD_WAIT_TIMEOUT", "45", 45.0),
+ ("VLLM_PLE_PROGRESS_DEADLINE_S", "30", 30.0),
+ ("VLLM_PLE_STARTUP_DEADLINE_S", "60", 60.0),
+ ("VLLM_PLE_TEST_FAULT", "cut=exc_in_release,ordinal=3,rank=any", None),
+ ("VLLM_PLE_TEST_FAULT_GATE", "/var/tmp/gate", "/var/tmp/gate"),
+ ("VLLM_PLE_TEST_FAULT_LOG", "/var/tmp/fault.log", "/var/tmp/fault.log"),
+ # Read sites arrive with the slot-ownership assertions.
+ ("VLLM_E2_PLE_FLAG_CHECK", "1", "warn"),
+ ("VLLM_E2_MAMBA_SLOT_OWNERSHIP", " RAISE ", "raise"),
+]
+
+# Not in this repository at all: a reference-host-side patch stack. Named in
+# the TODO block in envs.py and deliberately unregistered until someone reads
+# their real read sites and classifies both identities.
+_FORK_UNAUDITED = [
+ "MERLIN_FULL_K",
+ "MERLIN_QSA_SORT",
+ "MERLIN_TIE_DET",
+ "MERLIN_TIE_RECENT",
+ "MERLIN_PIN_BSM",
+]
+
+
+def _fresh_env_value(name: str):
+ if hasattr(envs.__getattr__, "cache_clear"):
+ envs.__getattr__.cache_clear()
+ return getattr(envs, name)
+
+
+@pytest.mark.parametrize(("name", "value", "expected"), _FORK_HASHED)
+def test_fork_knob_is_a_cache_factor(
+ name: str, value: str, expected, monkeypatch: pytest.MonkeyPatch
+):
+ """A knob that can change a compiled or captured artifact must be hashed,
+ and the hashed value must follow the environment."""
+ assert name in environment_variables
+ monkeypatch.setenv(name, value)
+ assert _fresh_env_value(name) == expected
+ factors = envs.compile_factors()
+ assert name in factors
+ assert factors[name] == expected
+
+
+@pytest.mark.parametrize(("name", "value", "expected"), _FORK_IGNORED)
+def test_fork_runtime_knob_is_not_a_cache_factor(
+ name: str, value: str, expected, monkeypatch: pytest.MonkeyPatch
+):
+ """A path, timeout, log cadence or host-side assertion must never
+ invalidate the compile cache -- while still being read."""
+ assert name in environment_variables
+ monkeypatch.setenv(name, value)
+ if expected is not None:
+ assert _fresh_env_value(name) == expected
+ assert name not in envs.compile_factors()
+
+
+@pytest.mark.parametrize("name", _FORK_UNAUDITED)
+def test_unaudited_host_side_knob_stays_listed_until_it_is_classified(name: str):
+ """The TODO block in envs.py is a gate, not a note.
+
+ Registering one of these without classifying it -- which means deleting it
+ from that block once its AOT and capture identity are written down against
+ its real read site -- is the exact failure the fork's env rule
+ exists to prevent, so it fails here.
+ """
+ source = Path(envs.__file__).read_text(encoding="utf-8")
+ listed = name in source
+ if name in environment_variables:
+ assert not listed, (
+ f"{name} is registered but still named in the envs.py TODO block: "
+ "classify its AOT and capture identity from its real read site and "
+ "remove it from that block"
+ )
+ else:
+ assert listed, (
+ f"{name} is neither registered nor named in the envs.py TODO block; "
+ "every fork knob must be accounted for"
+ )
+
+
+def test_fork_cache_factor_lists_are_disjoint_and_registered():
+ """Negative control for the two tables above."""
+ hashed = {n for n, _, _ in _FORK_HASHED}
+ ignored = {n for n, _, _ in _FORK_IGNORED}
+ assert not hashed & ignored
+ for name in hashed | ignored:
+ assert name in environment_variables, name
diff --git a/tests/v1/executor/test_multiproc_executor_parent_death.py b/tests/v1/executor/test_multiproc_executor_parent_death.py
index c39718f..111fca6 100644
--- a/tests/v1/executor/test_multiproc_executor_parent_death.py
+++ b/tests/v1/executor/test_multiproc_executor_parent_death.py
@@ -70,9 +70,7 @@ def _run_fake_worker_main(
self.peer_response_handles = []
def monitor_death_pipe(self, pipe, shutdown_requested):
- return real_worker_proc.monitor_death_pipe(
- self, pipe, shutdown_requested
- )
+ return real_worker_proc.monitor_death_pipe(self, pipe, shutdown_requested)
def worker_busy_loop(self):
if block_mode == "gil-spin":
@@ -271,11 +269,7 @@ def _wait_for_state(
def _wait_for_exit(pid: int, start_time: str, deadline: float) -> float | None:
while time.monotonic() < deadline:
identity = _proc_identity(pid)
- if (
- identity is None
- or identity[0] in {"X", "Z"}
- or identity[1] != start_time
- ):
+ if identity is None or identity[0] in {"X", "Z"} or identity[1] != start_time:
return time.monotonic()
time.sleep(0.01)
return None
diff --git a/tests/v1/worker/gpu/test_block_table_guards.py b/tests/v1/worker/gpu/test_block_table_guards.py
index d15093c..b4f0450 100644
--- a/tests/v1/worker/gpu/test_block_table_guards.py
+++ b/tests/v1/worker/gpu/test_block_table_guards.py
@@ -20,9 +20,7 @@ from vllm.v1.attention.backends.utils import PAD_SLOT_ID
from vllm.v1.worker import guard_telemetry
from vllm.v1.worker.gpu.block_table import BlockTables
-pytestmark = pytest.mark.skipif(
- not torch.cuda.is_available(), reason="CUDA required"
-)
+pytestmark = pytest.mark.gpu
MAX_REQS = 8
BLOCKS = 4
@@ -67,8 +65,7 @@ def test_gather_and_slot_mapping_launch_and_are_correct():
# A legal batch must leave both counters at zero. A guard that fires here
# is reporting a masked fault on correct metadata, which is worse than no
# guard -- see the row-bound defect.
- for site in (guard_telemetry.SITE_BT_GATHER,
- guard_telemetry.SITE_SLOT_MAPPING):
+ for site in (guard_telemetry.SITE_BT_GATHER, guard_telemetry.SITE_SLOT_MAPPING):
ctr = guard_telemetry.counter(site, device)
assert int(ctr[0].item()) == 0, guard_telemetry.snapshot()
@@ -80,7 +77,7 @@ def test_gather_guard_fires_and_zeroes_the_row():
bt = _tables(device)
idx = torch.arange(NUM_REQS, dtype=torch.int32, device=device)
- idx[2] = MAX_REQS + 3 # a request row that does not exist
+ idx[2] = MAX_REQS + 3 # a request row that does not exist
(gathered,) = bt.gather_block_tables(idx, num_reqs_padded=NUM_REQS)
torch.cuda.synchronize()
@@ -103,7 +100,7 @@ def test_slot_mapping_guard_pads_the_bad_request():
bt = _tables(device)
idx = torch.arange(NUM_REQS, dtype=torch.int32, device=device)
- idx[1] = -1 # the freed-slot sentinel
+ idx[1] = -1 # the freed-slot sentinel
qsl = torch.tensor([0, 4, 8, 12, 16], dtype=torch.int32, device=device)
pos = torch.arange(NUM_TOKENS, dtype=torch.int64, device=device)
slots = bt.compute_slot_mappings(idx, qsl, pos, num_tokens_padded=NUM_TOKENS)
diff --git a/tests/v1/worker/gpu/test_guard_reader_nonblocking.py b/tests/v1/worker/gpu/test_guard_reader_nonblocking.py
index 94721e1..4fc7995 100644
--- a/tests/v1/worker/gpu/test_guard_reader_nonblocking.py
+++ b/tests/v1/worker/gpu/test_guard_reader_nonblocking.py
@@ -1,11 +1,15 @@
# SPDX-License-Identifier: Apache-2.0
-"""the instrumentation readers must never wait on the compute stream.
-
-The first cadence reader did a synchronous pageable ``tolist()`` from its thread; the driver held the CUDA context lock
-while that copy waited for the stream, and a pending PLE pull-transport wait on the stream (whose producer needs the
-same lock) deadlocked the worker. These tests park a kernel on the current (compute) stream and require a reader tick
-to return while it is still running. On the pre-fix reader the first GPU test blocks for the kernel's duration.
+"""v2 reader fix: the instrumentation readers must never wait on
+the compute stream.
+
+The earlier cadence reader did a synchronous pageable ``tolist()`` from its thread;
+the driver held the CUDA context lock while that copy waited for the stream,
+and a pending PLE pull-transport wait on the stream (whose producer needs the
+same lock) deadlocked the worker. These tests park a kernel on the current
+(compute) stream and require a reader tick to return while it is still running.
+On the pre-fix reader the first GPU test blocks for the kernel's duration.
"""
+
import threading
import time
@@ -14,7 +18,9 @@ import torch
from vllm.v1.worker import guard_telemetry
-SLEEP_CYCLES = 4_000_000_000 # ~2 s on a 3090 (torch.cuda._sleep spins for this many cycles)
+SLEEP_CYCLES = (
+ 4_000_000_000 # ~2 s on a 3090 (torch.cuda._sleep spins for this many cycles)
+)
def test_cpu_tensor_reads_directly():
@@ -27,7 +33,8 @@ def test_cpu_tensor_reads_directly():
def test_register_refuses_to_run_inside_a_capture(monkeypatch):
- """Creating the side stream inside a cudagraph capture is not capture-safe."""
+ """Creating the side stream inside a cudagraph capture is
+ not capture-safe."""
r = guard_telemetry.NonBlockingReader()
monkeypatch.setattr(torch.cuda, "is_current_stream_capturing", lambda: True)
if not torch.cuda.is_available():
@@ -38,17 +45,18 @@ def test_register_refuses_to_run_inside_a_capture(monkeypatch):
def test_report_waits_for_the_confirming_tick_cpu():
- """A count rise seen in copy k is reported from copy k+1 (maxima current by then)."""
+ """A count rise seen in copy k is reported from copy k+1
+ (maxima current by then)."""
device = torch.device("cpu")
guard_telemetry.reset_for_test()
- buf = guard_telemetry.counter(0, device) # allocates the cpu buffer
+ guard_telemetry.counter(0, device) # allocates the cpu buffer
full = guard_telemetry._buffers[device]
site = guard_telemetry.SITE_MAMBA_COPY
- full[site, 0] = 2 # count written, maxima not yet
- guard_telemetry._poll_once() # rise seen -> pending, nothing reported
+ full[site, 0] = 2 # count written, maxima not yet
+ guard_telemetry._poll_once() # rise seen -> pending, nothing reported
assert guard_telemetry._seen.get((device, site), 0) == 0
- full[site, 1] = 9 + guard_telemetry.GUARD_BIAS # maxima land before the next tick
- guard_telemetry._poll_once() # confirming tick reports count 2 with row 9
+ full[site, 1] = 9 + guard_telemetry.GUARD_BIAS # maxima land before the next tick
+ guard_telemetry._poll_once() # confirming tick reports count 2 with row 9
assert guard_telemetry._seen.get((device, site), 0) == 2
snap = guard_telemetry.snapshot()
key = f"{device}:{guard_telemetry.SITE_NAMES[site]}"
@@ -58,13 +66,16 @@ def test_report_waits_for_the_confirming_tick_cpu():
def test_module_reader_is_the_non_blocking_kind():
assert isinstance(guard_telemetry._nb_reader, guard_telemetry.NonBlockingReader)
- src = open(guard_telemetry.__file__, encoding="utf-8").read()
- body = src[src.index("def _poll_once"):src.index("def _arm(")]
- assert ".tolist()" not in body.replace("host.tolist()", ""), "the cadence reader must not read the device buffer synchronously"
+ with open(guard_telemetry.__file__, encoding="utf-8") as fh:
+ src = fh.read()
+ body = src[src.index("def _poll_once") : src.index("def _arm(")]
+ assert ".tolist()" not in body.replace("host.tolist()", ""), (
+ "the cadence reader must not read the device buffer synchronously"
+ )
assert "poll_one(" in body
-@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
+@pytest.mark.gpu
def test_reader_tick_returns_while_the_compute_stream_is_busy():
device = torch.device("cuda", torch.cuda.current_device())
guard_telemetry.prealloc(device)
@@ -83,15 +94,23 @@ def test_reader_tick_returns_while_the_compute_stream_is_busy():
t1 = time.monotonic()
torch.cuda.synchronize(device)
busy = time.monotonic() - t1
- # The tick assertion comes first: a blocking reader spends the whole sleep inside the tick and would then make
- # the busy control read ~0 (negative control).
- assert tick < 0.25, f"reader tick took {tick:.3f}s while the compute stream was busy: it waited on the stream (reader/compute-stream deadlock)"
- assert tick + busy > 0.5, f"control failed: the parked kernel finished in {tick + busy:.3f}s, the test cannot discriminate"
-
-
-@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
+ # The tick assertion comes first: a blocking reader spends the whole
+ # sleep inside the tick and would then make the busy control read ~0
+ # (negative control).
+ assert tick < 0.25, (
+ f"reader tick took {tick:.3f}s while the compute stream was busy: "
+ f"it waited on the stream (reader/compute-stream deadlock)"
+ )
+ assert tick + busy > 0.5, (
+ f"control failed: the parked kernel finished in {tick + busy:.3f}s, "
+ f"the test cannot discriminate"
+ )
+
+
+@pytest.mark.gpu
def test_reader_tick_from_a_thread_returns_while_the_compute_stream_is_busy():
- """The production shape: the tick runs on the reader thread while the main thread owns the stream."""
+ """The production shape: the tick runs on the reader thread while the
+ main thread owns the stream."""
device = torch.device("cuda", torch.cuda.current_device())
guard_telemetry.prealloc(device)
guard_telemetry.reset_for_test()
@@ -110,13 +129,17 @@ def test_reader_tick_from_a_thread_returns_while_the_compute_stream_is_busy():
threading.Thread(target=tick, name="test-guard-reader", daemon=True).start()
finished = done.wait(timeout=1.0)
torch.cuda.synchronize(device)
- assert finished, "the reader tick did not return within 1 s while the compute stream was busy (reader/compute-stream deadlock)"
+ assert finished, (
+ "the reader tick did not return within 1 s while the compute stream "
+ "was busy (reader/compute-stream deadlock)"
+ )
assert elapsed["tick"] < 0.25
-@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
+@pytest.mark.gpu
def test_hits_written_before_a_stall_are_still_reported():
- """Non-blocking must not mean lossy: a count written on the compute stream is reported once the copy lands."""
+ """Non-blocking must not mean lossy: a count written on the compute
+ stream is reported once the copy lands."""
device = torch.device("cuda", torch.cuda.current_device())
guard_telemetry.prealloc(device)
guard_telemetry.reset_for_test()
diff --git a/tests/v1/worker/gpu/test_guard_telemetry_defaults_drain.py b/tests/v1/worker/gpu/test_guard_telemetry_defaults_drain.py
index 6799a80..606292e 100644
--- a/tests/v1/worker/gpu/test_guard_telemetry_defaults_drain.py
+++ b/tests/v1/worker/gpu/test_guard_telemetry_defaults_drain.py
@@ -13,15 +13,12 @@
"""
import importlib
-import os
import sys
import pytest
import torch
-pytestmark = pytest.mark.skipif(
- not torch.cuda.is_available(), reason="CUDA required"
-)
+pytestmark = pytest.mark.gpu
def _fresh(monkeypatch, **env):
@@ -36,7 +33,7 @@ def _fresh(monkeypatch, **env):
def test_release_defaults_with_env_absent(monkeypatch):
gt = _fresh(monkeypatch)
assert gt._MODE == "off" # v2: default off
- assert gt._INTERVAL == pytest.approx(0.05)
+ assert pytest.approx(0.05) == gt._INTERVAL
@pytest.mark.parametrize("bad", ["nan", "inf", "-inf", "0", "-1", "abc"])
@@ -46,7 +43,9 @@ def test_bad_interval_rejected_at_import(monkeypatch, bad):
def test_reader_loss_arms_the_stop(monkeypatch):
- gt = _fresh(monkeypatch, VLLM_E2_GUARD_MODE="warn") # the reader exists only when opted in (default off)
+ gt = _fresh(
+ monkeypatch, VLLM_E2_GUARD_MODE="warn"
+ ) # the reader exists only when opted in (default off)
device = torch.device("cuda:0")
gt.prealloc(device)
gt.reset_for_test()
@@ -64,7 +63,9 @@ def test_reader_loss_arms_the_stop(monkeypatch):
def test_drain_reports_last_step_hit_and_raises_for_fatal_site(monkeypatch):
- gt = _fresh(monkeypatch, VLLM_E2_GUARD_MODE="warn") # warn mode (opt-in since v2.0.1): a plain site must NOT raise
+ gt = _fresh(
+ monkeypatch, VLLM_E2_GUARD_MODE="warn"
+ ) # warn mode (opt-in): a plain site must NOT raise
device = torch.device("cuda:0")
gt.prealloc(device)
gt.reset_for_test()
diff --git a/tests/v1/worker/gpu/test_guard_telemetry_replay.py b/tests/v1/worker/gpu/test_guard_telemetry_replay.py
index 52327ab..a987a02 100644
--- a/tests/v1/worker/gpu/test_guard_telemetry_replay.py
+++ b/tests/v1/worker/gpu/test_guard_telemetry_replay.py
@@ -27,9 +27,7 @@ from vllm.third_party.flash_linear_attention.ops import (
)
from vllm.v1.worker import guard_telemetry
-pytestmark = pytest.mark.skipif(
- not torch.cuda.is_available(), reason="CUDA required"
-)
+pytestmark = pytest.mark.gpu
REPLAYS = 5
diff --git a/tests/v1/worker/gpu/test_pp_deferred_mamba_block_table.py b/tests/v1/worker/gpu/test_pp_deferred_mamba_block_table.py
index 30bed0a..df904ff 100644
--- a/tests/v1/worker/gpu/test_pp_deferred_mamba_block_table.py
+++ b/tests/v1/worker/gpu/test_pp_deferred_mamba_block_table.py
@@ -42,6 +42,9 @@ from vllm.v1.worker import mamba_utils as mamba_utils_mod
from vllm.v1.worker.gpu.model_states.mamba_hybrid import MambaHybridModelState
from vllm.v1.worker.mamba_utils import MambaSpecDecodeGPUContext
+# Module-level singleton so the helper default is not a call (ruff B008).
+_CPU = torch.device("cpu")
+
MAX_NUM_REQS = 8
MAX_NUM_BLOCKS = 6
MAMBA_BLOCK_SIZE = 16
@@ -57,12 +60,8 @@ _COPY_FUNCS: MambaStateCopyFuncsByType = {
# Captured before any monkeypatching so the replay binds against the real
# kernel signature; a launch that stops matching it fails loudly here.
-_POSTPROCESS_SIG = inspect.signature(
- mamba_utils_mod.postprocess_mamba_fused_kernel.fn
-)
-_PRECOPY_SIG = inspect.signature(
- mamba_utils_mod.precopy_mamba_align_fused_kernel.fn
-)
+_POSTPROCESS_SIG = inspect.signature(mamba_utils_mod.postprocess_mamba_fused_kernel.fn)
+_PRECOPY_SIG = inspect.signature(mamba_utils_mod.precopy_mamba_align_fused_kernel.fn)
class _MockCpuGpuBuffer:
@@ -163,7 +162,7 @@ def _register_block_tables(state: MambaHybridModelState, block_tables) -> bool:
def _build_scenario(
- device: torch.device = torch.device("cpu"),
+ device: torch.device = _CPU,
slot_mapping: list[int] = (0, 1, 2),
t1_mapping: list[int] = (2,),
register: bool = True,
@@ -291,7 +290,8 @@ def _replay_row(scenario, launch, batch_idx: int) -> tuple[str, int, dict]:
"src_eq_dst": src_block_idx == dest_block_idx,
}
- if bt_row_is_req:
+ if bt_row_is_req: # noqa: SIM108 -- the ternary ruff proposes nests two
+ # conditionals on one line; the block states the three cases plainly.
bt_row_idx = req_idx
else:
bt_row_idx = batch_idx if has_idx_mapping else req_idx
@@ -304,9 +304,7 @@ def _replay_row(scenario, launch, batch_idx: int) -> tuple[str, int, dict]:
def _block_ids(scenario, launch, batch_idx: int) -> dict:
name, bt_row_idx, predicate = _replay_row(scenario, launch, batch_idx)
- table = next(
- tbl for label, tbl in scenario.tables_by_ptr.values() if label == name
- )
+ table = next(tbl for label, tbl in scenario.tables_by_ptr.values() if label == name)
return {
"table": name,
"row": bt_row_idx,
@@ -321,9 +319,7 @@ def _block_ids(scenario, launch, batch_idx: int) -> dict:
def _run_deferred(scenario, monkeypatch: pytest.MonkeyPatch) -> _RecordingKernel:
recorder = _RecordingKernel()
- monkeypatch.setattr(
- mamba_utils_mod, "postprocess_mamba_fused_kernel", recorder
- )
+ monkeypatch.setattr(mamba_utils_mod, "postprocess_mamba_fused_kernel", recorder)
device = scenario.persistent.device
def t(values):
@@ -355,9 +351,9 @@ def _run_deferred(scenario, monkeypatch: pytest.MonkeyPatch) -> _RecordingKernel
def test_gather_port_matches_production_semantics() -> None:
"""Instrument check: the CPU gather port must reproduce block_table.py."""
- persistent = torch.arange(
- MAX_NUM_REQS * MAX_NUM_BLOCKS, dtype=torch.int32
- ).reshape(MAX_NUM_REQS, MAX_NUM_BLOCKS)
+ persistent = torch.arange(MAX_NUM_REQS * MAX_NUM_BLOCKS, dtype=torch.int32).reshape(
+ MAX_NUM_REQS, MAX_NUM_BLOCKS
+ )
dst = torch.full_like(persistent, -7)
num_blocks = [2] * MAX_NUM_REQS
@@ -439,9 +435,7 @@ def test_precopy_still_uses_the_same_step_gather(
"""
scenario = _build_scenario()
recorder = _RecordingKernel()
- monkeypatch.setattr(
- mamba_utils_mod, "precopy_mamba_align_fused_kernel", recorder
- )
+ monkeypatch.setattr(mamba_utils_mod, "precopy_mamba_align_fused_kernel", recorder)
device = scenario.persistent.device
z = torch.zeros(MAX_NUM_REQS, dtype=torch.int32, device=device)
@@ -528,14 +522,10 @@ def test_align_postprocess_fails_closed_without_persistent_tables(
assert scenario.ctx.has_persistent_block_tables is False
recorder = _RecordingKernel()
- monkeypatch.setattr(
- mamba_utils_mod, "postprocess_mamba_fused_kernel", recorder
- )
+ monkeypatch.setattr(mamba_utils_mod, "postprocess_mamba_fused_kernel", recorder)
device = scenario.persistent.device
before = scenario.ctx.num_accepted_tokens_out.clone()
- num_accepted = torch.full(
- (MAX_NUM_REQS,), 7, dtype=torch.int32, device=device
- )
+ num_accepted = torch.full((MAX_NUM_REQS,), 7, dtype=torch.int32, device=device)
with pytest.raises(RuntimeError, match="persistent per-req-slot block tables"):
scenario.ctx.run_fused_postprocess_align(
@@ -564,9 +554,7 @@ def test_precopy_still_works_without_persistent_tables(
scenario = _build_scenario(register=False)
assert scenario.ctx.has_persistent_block_tables is False
recorder = _RecordingKernel()
- monkeypatch.setattr(
- mamba_utils_mod, "precopy_mamba_align_fused_kernel", recorder
- )
+ monkeypatch.setattr(mamba_utils_mod, "precopy_mamba_align_fused_kernel", recorder)
device = scenario.persistent.device
z = torch.zeros(MAX_NUM_REQS, dtype=torch.int32, device=device)
diff --git a/tests/v1/worker/gpu/test_pp_deferred_mamba_gpu.py b/tests/v1/worker/gpu/test_pp_deferred_mamba_gpu.py
index f036227..376494e 100644
--- a/tests/v1/worker/gpu/test_pp_deferred_mamba_gpu.py
+++ b/tests/v1/worker/gpu/test_pp_deferred_mamba_gpu.py
@@ -22,7 +22,8 @@ possible coordinates resolve to four different physical blocks:
gathered[batch_idx=0] first cut -> r2's blocks (the victim)
gathered[req_idx=5] wrong both -> never written, block 0
-Requires CUDA **and** an explicit opt-in: set ``VLLM_E2_GPU_TESTS=1``. The reference host
+Requires CUDA **and** an explicit opt-in: set ``VLLM_E2_GPU_TESTS=1``. The
+reference host
always reports CUDA available, and the reference host spends most of its time running
long serving runs and sanitizer runs that should not share a device with a
test suite, so `torch.cuda.is_available()` is not a sufficient gate here. Run
@@ -51,7 +52,7 @@ from vllm.v1.kv_cache_interface import KVCacheConfig, KVCacheGroupSpec, MambaSpe
from vllm.v1.worker.mamba_utils import MambaSpecDecodeGPUContext
pytestmark = [
- pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required"),
+ pytest.mark.gpu,
pytest.mark.skipif(
os.environ.get("VLLM_E2_GPU_TESTS") != "1",
reason="set VLLM_E2_GPU_TESTS=1 to run GPU tests (shared GPU host)",
@@ -162,9 +163,7 @@ def _build(slot_mapping=(5, 1, 2), t1_mapping=(2,)) -> _Fixture:
device = torch.device("cuda:0")
torch.manual_seed(1234)
- conv = torch.randn(
- TOTAL_BLOCKS, CONV_WIDTH, CONV_INNER, dtype=DTYPE, device=device
- )
+ conv = torch.randn(TOTAL_BLOCKS, CONV_WIDTH, CONV_INNER, dtype=DTYPE, device=device)
temporal = torch.randn(TOTAL_BLOCKS, TEMPORAL_DIM, dtype=DTYPE, device=device)
attention = MagicMock()
attention.kv_cache = [conv, temporal]
@@ -271,8 +270,7 @@ def _assert_bitwise(actual: torch.Tensor, expected: torch.Tensor, what: str) ->
lhs = actual.contiguous().view(torch.int16)
rhs = expected.contiguous().view(torch.int16)
assert torch.equal(lhs, rhs), (
- f"{what}: {int((lhs != rhs).sum().item())} of {lhs.numel()} 16-bit "
- f"words differ"
+ f"{what}: {int((lhs != rhs).sum().item())} of {lhs.numel()} 16-bit words differ"
)
@@ -365,9 +363,7 @@ def test_self_copy_exits_without_touching_state_and_resets_num_accepted():
return. The conjunction itself is covered by the next test.
"""
fx = _build()
- num_accepted = _launch(
- fx, [5, 1, 2], {5: (2, 1, 33), 1: (2, 0, 40), 2: (2, 0, 40)}
- )
+ num_accepted = _launch(fx, [5, 1, 2], {5: (2, 1, 33), 1: (2, 0, 40), 2: (2, 0, 40)})
_assert_only_blocks_changed(fx, set())
assert int(num_accepted[5].item()) == 1
@@ -391,9 +387,7 @@ def test_same_column_with_positive_bias_copies_and_still_resets():
positive bias moves bytes.
"""
fx = _build()
- num_accepted = _launch(
- fx, [5, 1, 2], {5: (4, 1, 33), 1: (2, 0, 40), 2: (2, 0, 40)}
- )
+ num_accepted = _launch(fx, [5, 1, 2], {5: (4, 1, 33), 1: (2, 0, 40), 2: (2, 0, 40)})
blk = phys(5, 1) # both the conv source and the destination
temporal_src = phys(5, 3) # src_col + bias = 3
@@ -460,9 +454,7 @@ def test_generation_masked_reuse_is_skipped_end_to_end():
mask_from_generation_change,
)
- mapping = mask_from_generation_change(
- idx_mapping=[5, 1, 2], reused_slot_rows=[1]
- )
+ mapping = mask_from_generation_change(idx_mapping=[5, 1, 2], reused_slot_rows=[1])
assert mapping == [5, -1, 2], mapping
fx = _build(slot_mapping=(5, 1, 2))
@@ -500,9 +492,7 @@ def test_same_step_identity_mapping_is_unaffected():
def test_align_launch_raises_without_persistent_tables():
"""Fail closed on real hardware too, before any kernel is enqueued."""
device = torch.device("cuda:0")
- conv = torch.randn(
- TOTAL_BLOCKS, CONV_WIDTH, CONV_INNER, dtype=DTYPE, device=device
- )
+ conv = torch.randn(TOTAL_BLOCKS, CONV_WIDTH, CONV_INNER, dtype=DTYPE, device=device)
temporal = torch.randn(TOTAL_BLOCKS, TEMPORAL_DIM, dtype=DTYPE, device=device)
attention = MagicMock()
attention.kv_cache = [conv, temporal]
@@ -530,9 +520,7 @@ def test_align_launch_raises_without_persistent_tables():
num_accepted_tokens_gpu=torch.full(
(MAX_NUM_REQS,), 4, dtype=torch.int32, device=device
),
- state_idx_gpu=torch.zeros(
- MAX_NUM_REQS, dtype=torch.int32, device=device
- ),
+ state_idx_gpu=torch.zeros(MAX_NUM_REQS, dtype=torch.int32, device=device),
new_num_computed_tokens_gpu=torch.full(
(MAX_NUM_REQS,), 48, dtype=torch.int32, device=device
),
diff --git a/tests/v1/worker/gpu/test_qsa_ring_skip_telemetry.py b/tests/v1/worker/gpu/test_qsa_ring_skip_telemetry.py
index 0468288..84f109d 100644
--- a/tests/v1/worker/gpu/test_qsa_ring_skip_telemetry.py
+++ b/tests/v1/worker/gpu/test_qsa_ring_skip_telemetry.py
@@ -23,8 +23,8 @@ from vllm.v1.worker import guard_telemetry
COMPRESS_RATIO = 4
HEAD_DIM = 128
N_HEADS = 4
-STATE_SIZE = 8 # ring capacity
-ROW_WIDTH = 140 # head dim + the int64 position triple in the tail
+STATE_SIZE = 8 # ring capacity
+ROW_WIDTH = 140 # head dim + the int64 position triple in the tail
MAX_MODEL_LEN = 4096
NUM_BLOCKS = 8
RING_CAPACITY = NUM_BLOCKS * STATE_SIZE
@@ -56,7 +56,9 @@ def _inputs(device, num_tokens=4):
MAX_MODEL_LEN, HEAD_DIM // 2, dtype=torch.bfloat16, device=device
)
ones = torch.ones(HEAD_DIM, dtype=torch.bfloat16, device=device)
- q_out = torch.empty(num_tokens, N_HEADS, HEAD_DIM, dtype=torch.bfloat16, device=device)
+ q_out = torch.empty(
+ num_tokens, N_HEADS, HEAD_DIM, dtype=torch.bfloat16, device=device
+ )
state_cache = torch.zeros(
NUM_BLOCKS, STATE_SIZE, 1, ROW_WIDTH, dtype=torch.bfloat16, device=device
)
@@ -72,23 +74,45 @@ def _inputs(device, num_tokens=4):
max(num_tokens // COMPRESS_RATIO, 1), 2, dtype=torch.int32, device=device
)
return dict(
- q=q, k=k, positions=positions, cos_sin_cache=cos_sin,
- q_norm_weight=ones, k_norm_weight=ones, eps=1e-6, q_out=q_out,
- state_cache=state_cache, state_slots=state_slots,
- state_block_table=state_table, query_start_loc=qsl,
- logical_positions=logical, compressed_cache=comp_cache,
- compressed_slots=comp_slots, k_work_metadata=kwb,
+ q=q,
+ k=k,
+ positions=positions,
+ cos_sin_cache=cos_sin,
+ q_norm_weight=ones,
+ k_norm_weight=ones,
+ eps=1e-6,
+ q_out=q_out,
+ state_cache=state_cache,
+ state_slots=state_slots,
+ state_block_table=state_table,
+ query_start_loc=qsl,
+ logical_positions=logical,
+ compressed_cache=comp_cache,
+ compressed_slots=comp_slots,
+ k_work_metadata=kwb,
)
def _launch(kw):
qpi.qsa_pre_indexer(
- kw["q"], kw["k"], kw["positions"], kw["cos_sin_cache"],
- kw["q_norm_weight"], kw["k_norm_weight"], kw["eps"], kw["q_out"],
- kw["state_cache"], kw["state_slots"], kw["state_block_table"],
- kw["query_start_loc"], kw["logical_positions"], kw["compressed_cache"],
- kw["compressed_slots"], kw["k_work_metadata"],
- compress_ratio=COMPRESS_RATIO, mrope_section=(11, 11, 10),
+ kw["q"],
+ kw["k"],
+ kw["positions"],
+ kw["cos_sin_cache"],
+ kw["q_norm_weight"],
+ kw["k_norm_weight"],
+ kw["eps"],
+ kw["q_out"],
+ kw["state_cache"],
+ kw["state_slots"],
+ kw["state_block_table"],
+ kw["query_start_loc"],
+ kw["logical_positions"],
+ kw["compressed_cache"],
+ kw["compressed_slots"],
+ kw["k_work_metadata"],
+ compress_ratio=COMPRESS_RATIO,
+ mrope_section=(11, 11, 10),
rope_pos_offset=HEAD_DIM,
)
@@ -121,20 +145,26 @@ def test_launcher_passes_the_ring_skip_counter(monkeypatch):
assert len(rec.launches) == 1
_grid, args, kwargs = rec.launches[0]
- kwargs = {k: v for k, v in kwargs.items()
- if k not in ("num_warps", "num_stages", "num_ctas", "grid")}
+ kwargs = {
+ k: v
+ for k, v in kwargs.items()
+ if k not in ("num_warps", "num_stages", "num_ctas", "grid")
+ }
bound = sig.bind(*args, **kwargs)
bound.apply_defaults()
skip_ctr = bound.arguments["ring_skip_ctr"]
assert skip_ctr.dtype == torch.int64 and skip_ctr.numel() == 3
- assert skip_ctr.data_ptr() == guard_telemetry.counter(
- guard_telemetry.SITE_QSA_RING_SKIP, device
- ).data_ptr()
+ assert (
+ skip_ctr.data_ptr()
+ == guard_telemetry.counter(
+ guard_telemetry.SITE_QSA_RING_SKIP, device
+ ).data_ptr()
+ )
for other in ("rope_guard_ctr", "guard_ctr"):
assert bound.arguments[other].data_ptr() != skip_ctr.data_ptr()
-@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
+@pytest.mark.gpu
def test_negative_control_valid_slots_count_zero():
device = torch.device("cuda", torch.cuda.current_device())
guard_telemetry.prealloc(device)
@@ -148,11 +178,11 @@ def test_negative_control_valid_slots_count_zero():
for t in range(kw["k"].shape[0]):
row = kw["state_cache"][0, t, 0, :]
assert torch.equal(row[:HEAD_DIM], kw["k"][t])
- tail = row[HEAD_DIM:HEAD_DIM + 12].contiguous().view(torch.int64)
+ tail = row[HEAD_DIM : HEAD_DIM + 12].contiguous().view(torch.int64)
assert tail.tolist() == [t, t, t]
-@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
+@pytest.mark.gpu
@pytest.mark.parametrize(
"bad_index,bad_slot",
[(2, -1), (1, RING_CAPACITY), (3, RING_CAPACITY + 5)],
@@ -184,11 +214,11 @@ def test_positive_control_one_skipped_token_is_counted_and_located(bad_index, ba
assert torch.count_nonzero(row) == 0, "skipped row was written"
else:
assert torch.equal(row[:HEAD_DIM], kw["k"][t])
- tail = row[HEAD_DIM:HEAD_DIM + 12].contiguous().view(torch.int64)
+ tail = row[HEAD_DIM : HEAD_DIM + 12].contiguous().view(torch.int64)
assert tail.tolist() == [t, t, t]
-@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
+@pytest.mark.gpu
def test_positive_control_two_skipped_tokens_accumulate():
device = torch.device("cuda", torch.cuda.current_device())
guard_telemetry.prealloc(device)
@@ -198,7 +228,9 @@ def test_positive_control_two_skipped_tokens_accumulate():
kw["state_slots"][3] = RING_CAPACITY + 1
_launch(kw)
torch.cuda.synchronize()
- n, row, val = guard_telemetry.counter(guard_telemetry.SITE_QSA_RING_SKIP, device).tolist()
+ n, row, val = guard_telemetry.counter(
+ guard_telemetry.SITE_QSA_RING_SKIP, device
+ ).tolist()
assert n == 2
- assert row - guard_telemetry.GUARD_BIAS == 3 # max token index
+ assert row - guard_telemetry.GUARD_BIAS == 3 # max token index
assert val - guard_telemetry.GUARD_BIAS == RING_CAPACITY + 1 # max slot value
diff --git a/tests/v1/worker/gpu/test_qsa_rope_position_guard.py b/tests/v1/worker/gpu/test_qsa_rope_position_guard.py
index 9539aa7..0628dd2 100644
--- a/tests/v1/worker/gpu/test_qsa_rope_position_guard.py
+++ b/tests/v1/worker/gpu/test_qsa_rope_position_guard.py
@@ -33,8 +33,8 @@ from vllm.v1.worker import guard_telemetry
COMPRESS_RATIO = 4
HEAD_DIM = 128
N_HEADS = 4
-STATE_SIZE = 8 # ring capacity
-ROW_WIDTH = 140 # head dim + the int64 position triple in the tail
+STATE_SIZE = 8 # ring capacity
+ROW_WIDTH = 140 # head dim + the int64 position triple in the tail
MAX_MODEL_LEN = 4096
NUM_BLOCKS = 8
@@ -65,7 +65,9 @@ def _inputs(device, num_tokens=4):
MAX_MODEL_LEN, HEAD_DIM // 2, dtype=torch.bfloat16, device=device
)
ones = torch.ones(HEAD_DIM, dtype=torch.bfloat16, device=device)
- q_out = torch.empty(num_tokens, N_HEADS, HEAD_DIM, dtype=torch.bfloat16, device=device)
+ q_out = torch.empty(
+ num_tokens, N_HEADS, HEAD_DIM, dtype=torch.bfloat16, device=device
+ )
state_cache = torch.zeros(
NUM_BLOCKS, STATE_SIZE, 1, ROW_WIDTH, dtype=torch.bfloat16, device=device
)
@@ -81,12 +83,22 @@ def _inputs(device, num_tokens=4):
max(num_tokens // COMPRESS_RATIO, 1), 2, dtype=torch.int32, device=device
)
return dict(
- q=q, k=k, positions=positions, cos_sin_cache=cos_sin,
- q_norm_weight=ones, k_norm_weight=ones, eps=1e-6, q_out=q_out,
- state_cache=state_cache, state_slots=state_slots,
- state_block_table=state_table, query_start_loc=qsl,
- logical_positions=logical, compressed_cache=comp_cache,
- compressed_slots=comp_slots, k_work_metadata=kwb,
+ q=q,
+ k=k,
+ positions=positions,
+ cos_sin_cache=cos_sin,
+ q_norm_weight=ones,
+ k_norm_weight=ones,
+ eps=1e-6,
+ q_out=q_out,
+ state_cache=state_cache,
+ state_slots=state_slots,
+ state_block_table=state_table,
+ query_start_loc=qsl,
+ logical_positions=logical,
+ compressed_cache=comp_cache,
+ compressed_slots=comp_slots,
+ k_work_metadata=kwb,
)
@@ -104,21 +116,35 @@ def test_launcher_passes_the_real_cos_sin_row_count(monkeypatch):
monkeypatch.setattr(qpi, "_qsa_pre_indexer_kernel", rec)
kw = _inputs(device)
qpi.qsa_pre_indexer(
- kw.pop("q"), kw.pop("k"), kw.pop("positions"), kw.pop("cos_sin_cache"),
- kw.pop("q_norm_weight"), kw.pop("k_norm_weight"), kw.pop("eps"),
- kw.pop("q_out"), kw.pop("state_cache"), kw.pop("state_slots"),
- kw.pop("state_block_table"), kw.pop("query_start_loc"),
- kw.pop("logical_positions"), kw.pop("compressed_cache"),
- kw.pop("compressed_slots"), kw.pop("k_work_metadata"),
- compress_ratio=COMPRESS_RATIO, mrope_section=(11, 11, 10),
+ kw.pop("q"),
+ kw.pop("k"),
+ kw.pop("positions"),
+ kw.pop("cos_sin_cache"),
+ kw.pop("q_norm_weight"),
+ kw.pop("k_norm_weight"),
+ kw.pop("eps"),
+ kw.pop("q_out"),
+ kw.pop("state_cache"),
+ kw.pop("state_slots"),
+ kw.pop("state_block_table"),
+ kw.pop("query_start_loc"),
+ kw.pop("logical_positions"),
+ kw.pop("compressed_cache"),
+ kw.pop("compressed_slots"),
+ kw.pop("k_work_metadata"),
+ compress_ratio=COMPRESS_RATIO,
+ mrope_section=(11, 11, 10),
rope_pos_offset=HEAD_DIM,
)
assert len(rec.launches) == 1
_grid, args, kwargs = rec.launches[0]
# Triton launch options are not kernel parameters.
- kwargs = {k: v for k, v in kwargs.items()
- if k not in ("num_warps", "num_stages", "num_ctas", "grid")}
+ kwargs = {
+ k: v
+ for k, v in kwargs.items()
+ if k not in ("num_warps", "num_stages", "num_ctas", "grid")
+ }
bound = sig.bind(*args, **kwargs)
bound.apply_defaults()
assert bound.arguments["cos_sin_rows"] == MAX_MODEL_LEN
@@ -134,12 +160,16 @@ def test_launcher_passes_the_real_cos_sin_row_count(monkeypatch):
req_ctr = bound.arguments["guard_ctr"]
for ctr in (rope_ctr, req_ctr):
assert ctr.dtype == torch.int64 and ctr.numel() == 3
- assert rope_ctr.data_ptr() == guard_telemetry.counter(
- guard_telemetry.SITE_QSA_ROPE_POS, device
- ).data_ptr()
- assert req_ctr.data_ptr() == guard_telemetry.counter(
- guard_telemetry.SITE_QSA_PRE_INDEXER, device
- ).data_ptr()
+ assert (
+ rope_ctr.data_ptr()
+ == guard_telemetry.counter(guard_telemetry.SITE_QSA_ROPE_POS, device).data_ptr()
+ )
+ assert (
+ req_ctr.data_ptr()
+ == guard_telemetry.counter(
+ guard_telemetry.SITE_QSA_PRE_INDEXER, device
+ ).data_ptr()
+ )
assert rope_ctr.data_ptr() != req_ctr.data_ptr()
@@ -151,13 +181,10 @@ def test_rope_site_aborts_the_stage_even_in_warn_mode():
still there to do it again. warn is the right default for a guard whose
masked value dies with the step; it is the wrong default for this one.
"""
- assert (
- guard_telemetry.SITE_QSA_ROPE_POS
- in guard_telemetry._ALWAYS_FATAL_SITES
- )
+ assert guard_telemetry.SITE_QSA_ROPE_POS in guard_telemetry._ALWAYS_FATAL_SITES
-@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
+@pytest.mark.gpu
def test_ring_filler_bytes_are_masked_and_counted():
"""Positive control: the ring-residue crash case, verbatim.
@@ -196,13 +223,17 @@ def test_ring_filler_bytes_are_masked_and_counted():
# start must be unaligned: 6 gives first_position 4 < chunk_start 6.
start = 6
assert start % COMPRESS_RATIO != 0, "an aligned chunk start never crosses"
- kw["logical_positions"] = (
- torch.arange(start, start + kw["logical_positions"].numel(),
- dtype=kw["logical_positions"].dtype, device=device)
+ kw["logical_positions"] = torch.arange(
+ start,
+ start + kw["logical_positions"].numel(),
+ dtype=kw["logical_positions"].dtype,
+ device=device,
)
chunk_end = start + kw["logical_positions"].numel() - 1
chunk_start = chunk_end - kw["logical_positions"].numel() + 1
- first_boundary = ((chunk_start + COMPRESS_RATIO) // COMPRESS_RATIO) * COMPRESS_RATIO - 1
+ first_boundary = (
+ (chunk_start + COMPRESS_RATIO) // COMPRESS_RATIO
+ ) * COMPRESS_RATIO - 1
first_position = first_boundary - (COMPRESS_RATIO - 1)
assert first_position < chunk_start, (
f"first_position {first_position} >= chunk_start {chunk_start}: this "
@@ -216,15 +247,27 @@ def test_ring_filler_bytes_are_masked_and_counted():
kw["k_work_metadata"][:, 1] = 0
qpi.qsa_pre_indexer(
- kw["q"], kw["k"], kw["positions"], kw["cos_sin_cache"],
- kw["q_norm_weight"], kw["k_norm_weight"], kw["eps"], kw["q_out"],
- kw["state_cache"], kw["state_slots"], kw["state_block_table"],
- kw["query_start_loc"], kw["logical_positions"], kw["compressed_cache"],
- kw["compressed_slots"], kw["k_work_metadata"],
- compress_ratio=COMPRESS_RATIO, mrope_section=(11, 11, 10),
+ kw["q"],
+ kw["k"],
+ kw["positions"],
+ kw["cos_sin_cache"],
+ kw["q_norm_weight"],
+ kw["k_norm_weight"],
+ kw["eps"],
+ kw["q_out"],
+ kw["state_cache"],
+ kw["state_slots"],
+ kw["state_block_table"],
+ kw["query_start_loc"],
+ kw["logical_positions"],
+ kw["compressed_cache"],
+ kw["compressed_slots"],
+ kw["k_work_metadata"],
+ compress_ratio=COMPRESS_RATIO,
+ mrope_section=(11, 11, 10),
rope_pos_offset=HEAD_DIM,
)
- torch.cuda.synchronize() # the pre-guard build faults here
+ torch.cuda.synchronize() # the pre-guard build faults here
ctr = guard_telemetry.counter(guard_telemetry.SITE_QSA_ROPE_POS, device)
n_masked = int(ctr[0].item())
@@ -239,7 +282,7 @@ def test_ring_filler_bytes_are_masked_and_counted():
)
-@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
+@pytest.mark.gpu
@pytest.mark.parametrize("axis,axis_id", [("t", 0), ("h", 1), ("w", 2)])
@pytest.mark.parametrize("sign", ["mixed", "negative_only"])
def test_a_single_bad_axis_is_caught_and_named(axis, axis_id, sign):
@@ -264,7 +307,7 @@ def test_a_single_bad_axis_is_caught_and_named(axis, axis_id, sign):
kw["positions"] = kw["positions"].clone()
if sign == "mixed":
kw["positions"][row, 0] = MAX_MODEL_LEN + 7 # out of range, positive
- kw["positions"][row, 1] = -5 # out of range, negative
+ kw["positions"][row, 1] = -5 # out of range, negative
expected_worst = MAX_MODEL_LEN + 7
else:
# NEGATIVE ONLY. Every offender below zero is the case
@@ -279,12 +322,24 @@ def test_a_single_bad_axis_is_caught_and_named(axis, axis_id, sign):
from vllm.models.qwen4_exp.nvidia.ops.qsa_pre_indexer import qsa_pre_indexer
qsa_pre_indexer(
- kw["q"], kw["k"], kw["positions"], kw["cos_sin_cache"],
- kw["q_norm_weight"], kw["k_norm_weight"], kw["eps"], kw["q_out"],
- kw["state_cache"], kw["state_slots"], kw["state_block_table"],
- kw["query_start_loc"], kw["logical_positions"], kw["compressed_cache"],
- kw["compressed_slots"], kw["k_work_metadata"],
- compress_ratio=COMPRESS_RATIO, mrope_section=(11, 11, 10),
+ kw["q"],
+ kw["k"],
+ kw["positions"],
+ kw["cos_sin_cache"],
+ kw["q_norm_weight"],
+ kw["k_norm_weight"],
+ kw["eps"],
+ kw["q_out"],
+ kw["state_cache"],
+ kw["state_slots"],
+ kw["state_block_table"],
+ kw["query_start_loc"],
+ kw["logical_positions"],
+ kw["compressed_cache"],
+ kw["compressed_slots"],
+ kw["k_work_metadata"],
+ compress_ratio=COMPRESS_RATIO,
+ mrope_section=(11, 11, 10),
rope_pos_offset=HEAD_DIM,
)
torch.cuda.synchronize()
diff --git a/tests/v1/worker/test_grammar_fail_closed.py b/tests/v1/worker/test_grammar_fail_closed.py
index 869cb98..eeb6839 100644
--- a/tests/v1/worker/test_grammar_fail_closed.py
+++ b/tests/v1/worker/test_grammar_fail_closed.py
@@ -19,7 +19,6 @@ import numpy as np
import pytest
import torch
-from vllm.platforms import current_platform
from vllm.utils.math_utils import cdiv
from vllm.v1.core.sched.output import GrammarOutput
from vllm.v1.worker.gpu.model_runner import GPUModelRunner
@@ -109,7 +108,7 @@ def _rejection_sampler(device: torch.device) -> RejectionSampler:
return sampler
-@pytest.mark.skipif(not current_platform.is_cuda(), reason="Requires CUDA")
+@pytest.mark.gpu
def test_acceptance_stops_at_the_last_masked_row():
device = torch.device("cuda")
input_batch = _input_batch(device)
diff --git a/vllm/envs.py b/vllm/envs.py
index 157dfaf..588ca59 100755
--- a/vllm/envs.py
+++ b/vllm/envs.py
@@ -304,6 +304,132 @@ if TYPE_CHECKING:
# AOT cache on it; the layer itself still reads os.environ directly.
VLLM_HOST_EMBED_TABLE: bool = False
VLLM_PLE_OFFLOAD_READY_TIMEOUT: float = 600.0
+ # --- Flash-Next fork knobs (v2.2) --------------------------------
+ # Every name below was read straight from os.environ at its use site, so
+ # envs.validate_environ() logged it as unknown on every boot that set one
+ # and compile_factors() could not see it. Registering makes the hash
+ # complete; the read sites are unchanged (they still parse and fail closed
+ # themselves, which registration must not weaken). Runtime-only entries are
+ # listed in compile_factors().ignored_factors with their reason.
+ #
+ # TWO IDENTITIES, NOT ONE. "Read once at startup"
+ # settles neither of them, so every entry below is labelled with both:
+ #
+ # AOT identity -- does the value change the torch.compile / AOT
+ # artifact? This is the ONLY identity compile_factors()
+ # keys. It protects against reusing another process's
+ # cached artifact.
+ # CAPTURE identity -- does the value change what a cudagraph capture bakes
+ # in (ops present, launch constants, shapes)? Nothing
+ # in this tree keys anything on capture identity: the
+ # env is read before capture, in-process, so capture
+ # identity is protected only by "do not change the
+ # value between capture and replay in one process".
+ #
+ # So a knob that is a capture factor but NOT an AOT factor gets no
+ # protection from being registered here. Registering it is a contract
+ # statement and a cross-process guard, not a mechanism. Where the two
+ # identities are not both established by reading the code, the knob is
+ # hashed anyway: a spurious cache miss costs a recompile, a missed one costs
+ # a wrong graph.
+ #
+ # COMPILE/CAPTURE FACTORS (hashed):
+ # AOT: yes. CAPTURE: yes. PLE table is FP8-with-global-scale ->
+ # Qwen4ExpPLEFp8EmbeddingMethod instead of the checkpoint's method:
+ # different weight dtype and a different embedding forward.
+ VLLM_PLE_FP8_GLOBAL_SCALE: bool = False
+ # CAPTURE: yes, established -- clip counting allocates persistent per-layer
+ # int64 counters and its increment ops are CAPTURED into the cudagraph (see
+ # the graph-safety note in models/qwen4_exp/nvidia/qsa.py); unset, those ops
+ # are absent. AOT: not established (the QSA forward is a custom op); hashed
+ # conservatively.
+ VLLM_QSA_KV_CLIP_COUNT: str = ""
+ # CAPTURE: n/a -- collection requires --enforce-eager, so an enabled run
+ # does not capture at all. AOT: adds absmax reduction ops to the attention
+ # forward; hashed conservatively.
+ VLLM_QSA_KV_COLLECT: str = ""
+ # CAPTURE: yes -- the resolved value is a launch constant baked into the
+ # graph at capture (csrc/libtorch_stable/persistent_topk.cuh:143-148).
+ # AOT: not established. Hashed conservatively.
+ # NOT A PYTHON READ SITE: the value is resolved once per process by
+ # exact_medium_enabled() in csrc/libtorch_stable/topk.cu:30-36, which is the
+ # single source of truth. This entry is a raw passthrough so nothing can
+ # mistake it for a second parser; do not use envs.MERLIN_TOPK_EXACT_MEDIUM
+ # to decide behaviour. Registered with the medium-path exact restart
+ # so the cache-factor contract is complete.
+ MERLIN_TOPK_EXACT_MEDIUM: str | None = None
+ #
+ # RUNTIME-ONLY (registered for validate_environ + a single cached read):
+ # Static K/V scale sidecar: fill_()ed into existing fp32 device buffers
+ # after load and before capture, and the kernel does tl.load(k_scale_ptr),
+ # so the value is never a constexpr and the graph is identical.
+ VLLM_QSA_KV_SCALES: str = ""
+ # Guard telemetry: mode raise|warn|off and the reader poll interval in
+ # seconds. counter() returns a real tensor in every mode, so the Triton
+ # arguments and the captured graph do not depend on either.
+ VLLM_E2_GUARD_MODE: str = "off"
+ VLLM_E2_GUARD_INTERVAL: str = "0.05"
+ # Debug allocator telemetry (poll seconds + output directory) and the
+ # scheduler allocation log path.
+ VLLM_E2_MEMSTATS: str = "0"
+ VLLM_E2_MEMSTATS_DIR: str = ""
+ VLLM_E2_SCHED_LOG: str = ""
+ # O(live blocks) host-side ownership assertion in the scheduler, which is
+ # plain Python and never compiled.
+ VLLM_E2_ZERO_OWNERSHIP_CHECK: bool = False
+ # PLE offload child placement: rewrites CUDA_VISIBLE_DEVICES for the child
+ # only (CUDA_VISIBLE_DEVICES is itself an ignored factor).
+ VLLM_PLE_OFFLOAD_HOME_DEVICE: str = ""
+ # PLE timeouts / deadlines, in seconds.
+ VLLM_PLE_OFFLOAD_WAIT_TIMEOUT: float = 120.0
+ VLLM_PLE_PROGRESS_DEADLINE_S: float = 300.0
+ VLLM_PLE_STARTUP_DEADLINE_S: float = 1800.0
+ # Test-only PLE fault injection: cut spec, marker log, bootstrap gate.
+ # AOT: no. CAPTURE: no. Read once at import by the vllm_ple_test_fault
+ # singleton, but the process that reads them is a real engine, so
+ # validate_environ sees them.
+ VLLM_PLE_TEST_FAULT: str | None = None
+ VLLM_PLE_TEST_FAULT_GATE: str | None = None
+ VLLM_PLE_TEST_FAULT_LOG: str | None = None
+ #
+ # --- Knobs registered ahead of their read sites (v2.2) -----------
+ # Registered ahead of their code so the MERGED tree is complete and
+ # validate_environ is quiet from the first boot (a
+ # cross-change knob table). Each mirrors its read site's
+ # parse; if a read site changes, the entry here must be reconciled.
+ # (Both read sites below are now live.)
+ # AOT: no. CAPTURE: no -- strictly host-side and outside capture.
+ # off|warn|raise (1 = warn), normalised by _e2_diagnostic_mode like the
+ # rest of the family. The mamba ownership check moved this gate off a plain
+ # boolean; reconciled here.
+ # Read site returns with the deferred PLE transport.
+ VLLM_E2_PLE_FLAG_CHECK: str = "off"
+ # AOT: no. CAPTURE: no -- strictly host-side. off|warn|raise (1 = warn).
+ # the mamba slot-ownership check, v1/worker/mamba_utils.py:74.
+ VLLM_E2_MAMBA_SLOT_OWNERSHIP: str = "off"
+ #
+ # --- TODO: knobs that are NOT in this tree at all ----------
+ # The tie-determinism toggles and the BSM pin live in a separate
+ # patch stack, not in v2.0.1 and not in this tree:
+ #
+ # MERLIN_FULL_K MERLIN_QSA_SORT MERLIN_TIE_DET MERLIN_TIE_RECENT
+ # MERLIN_PIN_BSM
+ #
+ # They are therefore UNAUDITED: nothing in this repository shows what they
+ # change, and this file will not classify code it cannot read. The rule they
+ # fall under is the standing one -- an os.environ (or std::getenv) knob that
+ # changes a compiled or captured artifact is not in the cache key unless it
+ # is registered here. From what is known of them (Marlin split-K, a
+ # persistent_topk ordering change and a cutoff tie-break) they are very
+ # likely capture factors and possibly AOT factors, which is exactly why a
+ # guess is not good enough.
+ #
+ # WHEN THE PATCHES LAND, for each name: register it below, label both
+ # identities from its actual read site, hash it unless exclusion is proven,
+ # and delete it from this block. tests/test_envs.py holds a test that fails
+ # if a name is registered while still listed here, so the classification
+ # cannot be skipped silently.
+ # ----------------------------------------------------------------------
VLLM_LOG_MODEL_INSPECTION: bool = False
VLLM_DEBUG_MFU_METRICS: bool = False
VLLM_WEIGHT_OFFLOADING_DISABLE_PIN_MEMORY: bool = False
@@ -2072,6 +2198,43 @@ environment_variables: dict[str, Callable[[], Any]] = {
"VLLM_PLE_OFFLOAD_READY_TIMEOUT": lambda: float(
os.getenv("VLLM_PLE_OFFLOAD_READY_TIMEOUT", "600")
),
+ # --- Flash-Next fork knobs (v2.2); see the TYPE_CHECKING block for
+ # which of these are compile/capture factors and which are ignored.
+ # Compile/capture factors.
+ "VLLM_PLE_FP8_GLOBAL_SCALE": lambda: (
+ os.getenv("VLLM_PLE_FP8_GLOBAL_SCALE", "") == "1"
+ ),
+ "VLLM_QSA_KV_CLIP_COUNT": lambda: os.getenv("VLLM_QSA_KV_CLIP_COUNT", "").strip(),
+ "VLLM_QSA_KV_COLLECT": lambda: os.getenv("VLLM_QSA_KV_COLLECT", "").strip(),
+ # Capture-configuration factor, read in C++; raw passthrough on purpose.
+ "MERLIN_TOPK_EXACT_MEDIUM": lambda: os.getenv("MERLIN_TOPK_EXACT_MEDIUM", None),
+ # Runtime only; all of the following are in compile_factors.ignored_factors.
+ "VLLM_QSA_KV_SCALES": lambda: os.getenv("VLLM_QSA_KV_SCALES", "").strip(),
+ "VLLM_E2_GUARD_MODE": lambda: (
+ os.getenv("VLLM_E2_GUARD_MODE", "off").strip().lower()
+ ),
+ "VLLM_E2_GUARD_INTERVAL": lambda: os.getenv("VLLM_E2_GUARD_INTERVAL", "0.05"),
+ "VLLM_E2_MEMSTATS": lambda: os.getenv("VLLM_E2_MEMSTATS", "0"),
+ "VLLM_E2_MEMSTATS_DIR": lambda: os.getenv("VLLM_E2_MEMSTATS_DIR", ""),
+ "VLLM_E2_SCHED_LOG": lambda: os.getenv("VLLM_E2_SCHED_LOG", ""),
+ "VLLM_E2_ZERO_OWNERSHIP_CHECK": lambda: (
+ os.getenv("VLLM_E2_ZERO_OWNERSHIP_CHECK", "") == "1"
+ ),
+ "VLLM_PLE_OFFLOAD_HOME_DEVICE": lambda: os.getenv(
+ "VLLM_PLE_OFFLOAD_HOME_DEVICE", ""
+ ).strip(),
+ "VLLM_PLE_OFFLOAD_WAIT_TIMEOUT": lambda: float(
+ os.getenv("VLLM_PLE_OFFLOAD_WAIT_TIMEOUT", "120")
+ ),
+ "VLLM_PLE_PROGRESS_DEADLINE_S": lambda: float(
+ os.getenv("VLLM_PLE_PROGRESS_DEADLINE_S", "300")
+ ),
+ "VLLM_PLE_STARTUP_DEADLINE_S": lambda: float(
+ os.getenv("VLLM_PLE_STARTUP_DEADLINE_S", "1800")
+ ),
+ "VLLM_PLE_TEST_FAULT": lambda: os.getenv("VLLM_PLE_TEST_FAULT", None),
+ "VLLM_PLE_TEST_FAULT_GATE": lambda: os.getenv("VLLM_PLE_TEST_FAULT_GATE", None),
+ "VLLM_PLE_TEST_FAULT_LOG": lambda: os.getenv("VLLM_PLE_TEST_FAULT_LOG", None),
# Log model inspection after loading.
# If enabled, logs a transformers-style hierarchical view of the model
# with quantization methods and attention backends.
@@ -2314,13 +2477,6 @@ def compile_factors() -> dict[str, object]:
"VLLM_DEBUG_DUMP_PATH",
"VLLM_PORT",
"VLLM_CACHE_ROOT",
- # PLE host-side diagnostics: both gate Python that runs outside any
- # capture and neither adds a node to a compiled graph, so hashing them
- # would invalidate every compile cache the moment a diagnostic boot is
- # taken -- which is exactly when the cache is wanted. Remove from this
- # set if either ever grows a device-side component.
- "VLLM_E2_PLE_FLAG_CHECK",
- "VLLM_E2_MAMBA_SLOT_OWNERSHIP",
# Runtime memory-plan persistence; does not affect compiled graphs.
"VLLM_ENABLE_STARTUP_PLAN",
# Location-only derived paths: where a cache/config directory lives
@@ -2397,6 +2553,55 @@ def compile_factors() -> dict[str, object]:
"LOCAL_RANK",
"CUDA_VISIBLE_DEVICES",
"NO_COLOR",
+ # --- Flash-Next fork knobs that are runtime-only (v2.2) ------
+ # Registering a name in environment_variables makes it a compile
+ # factor by default (this function is a deny-list), so every fork knob
+ # that cannot change a compiled or captured artifact is listed here
+ # with its reason. Hashing them would invalidate the whole AOT cache on
+ # a log path, a timeout or a guard-mode flip.
+ #
+ # Static QSA K/V scales: fill_()ed into existing fp32 device buffers
+ # after load and before capture; the kernel reads them with
+ # tl.load(k_scale_ptr), never as a constexpr.
+ "VLLM_QSA_KV_SCALES",
+ # Guard telemetry: counter() returns a real [3] int64 view in every
+ # mode, so the Triton arguments and the captured graph are identical;
+ # the mode only gates the side reader thread and the check_pending
+ # raise, and the interval is that thread's poll cadence.
+ "VLLM_E2_GUARD_MODE",
+ "VLLM_E2_GUARD_INTERVAL",
+ # Debug telemetry cadence and output locations.
+ "VLLM_E2_MEMSTATS",
+ "VLLM_E2_MEMSTATS_DIR",
+ "VLLM_E2_SCHED_LOG",
+ # Host-side scheduler assertion; the scheduler is never compiled.
+ "VLLM_E2_ZERO_OWNERSHIP_CHECK",
+ # Placement only: rewrites CUDA_VISIBLE_DEVICES for the PLE offload
+ # child, and CUDA_VISIBLE_DEVICES is already ignored above.
+ "VLLM_PLE_OFFLOAD_HOME_DEVICE",
+ # Timeouts and deadlines, in seconds. VLLM_PLE_OFFLOAD_READY_TIMEOUT
+ # was registered in the v2 host-embed commit and left hashed by
+ # oversight: serve-v2.sh sets it to 1800, so every boot that changed it
+ # missed the AOT cache for no reason.
+ "VLLM_PLE_OFFLOAD_READY_TIMEOUT",
+ "VLLM_PLE_OFFLOAD_WAIT_TIMEOUT",
+ "VLLM_PLE_PROGRESS_DEADLINE_S",
+ "VLLM_PLE_STARTUP_DEADLINE_S",
+ # Test-only fault injection: a cut spec, a marker log path and a
+ # bootstrap gate file. None of them reaches a kernel or a graph.
+ "VLLM_PLE_TEST_FAULT",
+ "VLLM_PLE_TEST_FAULT_GATE",
+ "VLLM_PLE_TEST_FAULT_LOG",
+ # Diagnostic knobs, both strictly host-side and outside capture, so
+ # neither AOT nor capture identity depends on them. Hashing
+ # them would invalidate every compile cache the moment a diagnostic
+ # boot is taken -- exactly when the cache is wanted. Remove from this
+ # set if either ever grows a device-side component.
+ # MERLIN_TOPK_EXACT_MEDIUM is deliberately NOT here: it is a capture
+ # configuration factor and is hashed conservatively.
+ "VLLM_E2_PLE_FLAG_CHECK",
+ "VLLM_E2_MAMBA_SLOT_OWNERSHIP",
+ # ------------------------------------------------------------------
}
from vllm.config.utils import normalize_value
diff --git a/vllm/models/qwen4_exp/nvidia/qsa.py b/vllm/models/qwen4_exp/nvidia/qsa.py
index adb057d..91f34de 100644
--- a/vllm/models/qwen4_exp/nvidia/qsa.py
+++ b/vllm/models/qwen4_exp/nvidia/qsa.py
@@ -162,7 +162,10 @@ if _qsa_clip_raw and not (math.isfinite(_qsa_clip_interval) and _qsa_clip_interv
_qsa_clip_counters: dict[str, torch.Tensor] = {}
# v2 reader fix: the reader must never block on the compute
# stream; see guard_telemetry.NonBlockingReader.
-from vllm.v1.worker.guard_telemetry import NonBlockingReader as _NonBlockingReader # noqa: E402
+from vllm.v1.worker.guard_telemetry import ( # noqa: E402
+ NonBlockingReader as _NonBlockingReader,
+)
+
_qsa_clip_nb = _NonBlockingReader()
_qsa_clip_reader_started = False
_qsa_clip_lock = threading.Lock()
@@ -217,7 +220,7 @@ def _qsa_clip_reader() -> None:
def _qsa_clip_count(
- layer: "Qwen4ExpQSAAttention",
+ layer: Qwen4ExpQSAAttention,
key: torch.Tensor,
value: torch.Tensor,
slot_mapping: torch.Tensor | None = None,
diff --git a/vllm/v1/core/sched/e2_alloc_log.py b/vllm/v1/core/sched/e2_alloc_log.py
index 0efcbd7..6fa2354 100644
--- a/vllm/v1/core/sched/e2_alloc_log.py
+++ b/vllm/v1/core/sched/e2_alloc_log.py
@@ -1,23 +1,45 @@
# SPDX-License-Identifier: Apache-2.0
-"""Debug-only scheduler allocation log for KV capacity probes (off unless VLLM_E2_SCHED_LOG=<path>).
+"""Debug-only scheduler allocation log for KV-capacity probes (off unless
+VLLM_E2_SCHED_LOG=<path>).
-One JSON line per scheduler update while any request exists, and one "fin" line per request as it is freed:
- {"t": wall, "m": monotonic, "step": n, "total": pool blocks, "free": free blocks, "running": r, "waiting": w,
+One JSON line per scheduler update while any request exists, and one "fin" line
+per request as it is freed:
+ {"t": wall, "m": monotonic, "step": n, "total": pool blocks,
+ "free": free blocks, "running": r, "waiting": w,
"tot": tokens scheduled this step,
- "reqs": [[id, prompt, computed, output, cached, preemptions, spec_scheduled, status, [blocks per group],
+ "reqs": [[id, prompt, computed, output, cached, preemptions,
+ spec_scheduled, status, [blocks per group],
tokens scheduled for this request this step]]}
- {"t": wall, "m": monotonic, "fin": [id, prompt, computed, output, cached, preemptions, status, [blocks per group]]}
-Blocks are counted per KV-cache group, null blocks excluded. A logging failure writes an "error" line and never
-raises into the scheduler. Unset = the scheduler only pays one env check per update.
+ {"t": wall, "m": monotonic,
+ "fin": [id, prompt, computed, output, cached, preemptions, status,
+ [blocks per group]]}
+Blocks are counted per KV-cache group, null blocks excluded. A logging failure
+writes an "error" line and never raises into the scheduler. Unset = the
+scheduler only pays one env check per update.
"""
+
import json
import os
import time
_PATH = os.environ.get("VLLM_E2_SCHED_LOG", "")
-_state = {"f": None, "step": 0, "flush": 0.0, "wrapped": False, "errors": 0, "checked": False}
-REQ_FIELDS = ("request_id", "num_prompt_tokens", "num_computed_tokens", "num_output_tokens", "num_preemptions",
- "status", "prefill_stats")
+_state = {
+ "f": None,
+ "step": 0,
+ "flush": 0.0,
+ "wrapped": False,
+ "errors": 0,
+ "checked": False,
+}
+REQ_FIELDS = (
+ "request_id",
+ "num_prompt_tokens",
+ "num_computed_tokens",
+ "num_output_tokens",
+ "num_preemptions",
+ "status",
+ "prefill_stats",
+)
def enabled() -> bool:
@@ -25,8 +47,10 @@ def enabled() -> bool:
def _blocks(sched, rid):
- return [sum(1 for b in m.req_to_blocks.get(rid, ()) if not b.is_null)
- for m in sched.kv_cache_manager.coordinator.single_type_managers]
+ return [
+ sum(1 for b in m.req_to_blocks.get(rid, ()) if not b.is_null)
+ for m in sched.kv_cache_manager.coordinator.single_type_managers
+ ]
def _status(r):
@@ -34,28 +58,41 @@ def _status(r):
def _cached(r):
- """Prefix-cache hit for this request. vLLM keeps it in `request.prefill_stats` (PrefillStats.num_cached_tokens =
- local + external); the output processor takes those stats after the first prefill (`take_prefill_stats` sets the
- attribute to None), so -1 means "no longer knowable from the scheduler", never a silent zero."""
+ """Prefix-cache hit for this request. vLLM keeps it in
+ `request.prefill_stats` (PrefillStats.num_cached_tokens = local +
+ external); the output processor takes those stats after the first prefill
+ (`take_prefill_stats` sets the attribute to None), so -1 means "no longer
+ knowable from the scheduler", never a silent zero."""
ps = getattr(r, "prefill_stats", None)
return int(getattr(ps, "num_cached_tokens", -1)) if ps is not None else -1
def _contract(r):
- """Once per process: every field this log reads must exist on the real Request object. A missing field is written
- once as a schema_error record — the evaluator fails closed on it — instead of turning every row into an error."""
+ """Once per process: every field this log reads must exist on the real Request
+ object. A missing field is written once as a schema_error record — the evaluator
+ fails closed on it — instead of turning every row into an error."""
if _state["checked"]:
return
_state["checked"] = True
missing = [f for f in REQ_FIELDS if not hasattr(r, f)]
if missing:
- _write({"t": round(time.time(), 4), "m": round(time.monotonic(), 4), "schema_error": missing}, force=True)
+ _write(
+ {
+ "t": round(time.time(), 4),
+ "m": round(time.monotonic(), 4),
+ "schema_error": missing,
+ },
+ force=True,
+ )
def _write(rec, force=False):
f = _state["f"]
if f is None:
- f = _state["f"] = open(_PATH, "a", buffering=1 << 16)
+ # noqa SIM115: one append handle for the process lifetime, kept in
+ # _state and flushed on a cadence; a context manager would reopen the
+ # log on every scheduler update.
+ f = _state["f"] = open(_PATH, "a", buffering=1 << 16) # noqa: SIM115
f.write(json.dumps(rec, separators=(",", ":")) + "\n")
if force or rec["t"] - _state["flush"] > 0.5:
f.flush()
@@ -65,10 +102,19 @@ def _write(rec, force=False):
def _error(e):
_state["errors"] += 1
if _state["errors"] <= 20:
- try:
- _write({"t": round(time.time(), 4), "m": round(time.monotonic(), 4),
- "error": f"{type(e).__name__}: {e}"[:300]}, force=True)
+ try: # noqa: SIM105
+ _write(
+ {
+ "t": round(time.time(), 4),
+ "m": round(time.monotonic(), 4),
+ "error": f"{type(e).__name__}: {e}"[:300],
+ },
+ force=True,
+ )
except Exception: # noqa: BLE001
+ # SIM105: this module imports json, os and time only, so that an
+ # enabled log costs the scheduler nothing it can see; the
+ # last-resort writer does not earn a contextlib import.
pass
@@ -77,10 +123,23 @@ def _wrap_free(sched):
def _free_request(request, *args, **kwargs):
try:
- _write({"t": round(time.time(), 4), "m": round(time.monotonic(), 4),
- "fin": [request.request_id, request.num_prompt_tokens, request.num_computed_tokens,
- request.num_output_tokens, _cached(request), request.num_preemptions,
- _status(request), _blocks(sched, request.request_id)]}, force=True)
+ _write(
+ {
+ "t": round(time.time(), 4),
+ "m": round(time.monotonic(), 4),
+ "fin": [
+ request.request_id,
+ request.num_prompt_tokens,
+ request.num_computed_tokens,
+ request.num_output_tokens,
+ _cached(request),
+ request.num_preemptions,
+ _status(request),
+ _blocks(sched, request.request_id),
+ ],
+ },
+ force=True,
+ )
except Exception as e: # noqa: BLE001 - the debug log must never break the scheduler
_error(e)
return orig(request, *args, **kwargs)
@@ -100,12 +159,35 @@ def log_step(sched, scheduler_output) -> None:
pool = sched.kv_cache_manager.block_pool
nsched = getattr(scheduler_output, "num_scheduled_tokens", None) or {}
_contract(next(iter(sched.requests.values())))
- rows = [[rid, r.num_prompt_tokens, r.num_computed_tokens, r.num_output_tokens, _cached(r),
- r.num_preemptions, len(spec.get(rid, ())), _status(r), _blocks(sched, rid), int(nsched.get(rid, 0))]
- for rid, r in sched.requests.items()]
- _write({"t": round(time.time(), 4), "m": round(time.monotonic(), 4), "step": _state["step"],
- "total": pool.num_gpu_blocks, "free": pool.get_num_free_blocks(),
- "running": len(sched.running), "waiting": len(sched.waiting),
- "tot": int(getattr(scheduler_output, "total_num_scheduled_tokens", 0) or 0), "reqs": rows})
+ rows = [
+ [
+ rid,
+ r.num_prompt_tokens,
+ r.num_computed_tokens,
+ r.num_output_tokens,
+ _cached(r),
+ r.num_preemptions,
+ len(spec.get(rid, ())),
+ _status(r),
+ _blocks(sched, rid),
+ int(nsched.get(rid, 0)),
+ ]
+ for rid, r in sched.requests.items()
+ ]
+ _write(
+ {
+ "t": round(time.time(), 4),
+ "m": round(time.monotonic(), 4),
+ "step": _state["step"],
+ "total": pool.num_gpu_blocks,
+ "free": pool.get_num_free_blocks(),
+ "running": len(sched.running),
+ "waiting": len(sched.waiting),
+ "tot": int(
+ getattr(scheduler_output, "total_num_scheduled_tokens", 0) or 0
+ ),
+ "reqs": rows,
+ }
+ )
except Exception as e: # noqa: BLE001
_error(e)
diff --git a/vllm/v1/ple_offload/test_fault.py b/vllm/v1/ple_offload/test_fault.py
index 3546a1f..9ae74f7 100644
--- a/vllm/v1/ple_offload/test_fault.py
+++ b/vllm/v1/ple_offload/test_fault.py
@@ -2,8 +2,12 @@
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Compatibility exports for the top-level test-only fault singleton."""
+from vllm_ple_test_fault import (
+ _FAULT as _FAULT,
+)
from vllm_ple_test_fault import ( # noqa: F401
FaultSpec as FaultSpec,
- _FAULT as _FAULT,
+)
+from vllm_ple_test_fault import (
_parse_fault_spec as _parse_fault_spec,
)
diff --git a/vllm/v1/worker/guard_telemetry.py b/vllm/v1/worker/guard_telemetry.py
index e95653e..adca930 100644
--- a/vllm/v1/worker/guard_telemetry.py
+++ b/vllm/v1/worker/guard_telemetry.py
@@ -174,9 +174,7 @@ SITE_NAMES = {
_MODE = os.environ.get("VLLM_E2_GUARD_MODE", "off").strip().lower()
if _MODE not in ("raise", "warn", "off"):
# fail closed: a set-but-broken gate must not silently disarm the instrument
- raise ValueError(
- f"VLLM_E2_GUARD_MODE must be raise|warn|off, got {_MODE!r}"
- )
+ raise ValueError(f"VLLM_E2_GUARD_MODE must be raise|warn|off, got {_MODE!r}")
def _parse_interval(text: str) -> float:
@@ -241,7 +239,7 @@ class NonBlockingReader:
"""
def __init__(self) -> None:
- self._streams: dict[torch.device, "torch.cuda.Stream"] = {}
+ self._streams: dict[torch.device, torch.cuda.Stream] = {}
# key -> [device_tensor, pinned_host_tensor | None, event | None, inflight]
self._jobs: dict[object, list] = {}
self._lock = threading.Lock()
@@ -266,7 +264,9 @@ class NonBlockingReader:
if dev not in self._streams:
with torch.cuda.device(dev):
self._streams[dev] = torch.cuda.Stream(device=dev)
- host = torch.empty(tensor.shape, dtype=tensor.dtype, device="cpu", pin_memory=True)
+ host = torch.empty(
+ tensor.shape, dtype=tensor.dtype, device="cpu", pin_memory=True
+ )
with torch.cuda.device(dev):
event = torch.cuda.Event()
self._jobs[key] = [tensor, host, event, False]
@@ -333,7 +333,8 @@ class NonBlockingReader:
_nb_reader = NonBlockingReader()
-# device -> the copy in which a count increase was first seen (reported on the next tick)
+# device -> the copy in which a count increase was first seen
+# (reported on the next tick)
_confirm_pending: dict[torch.device, list] = {}
# Sites that arm the pending raise even under VLLM_E2_GUARD_MODE=warn. warn is
@@ -442,12 +443,11 @@ def start_reader() -> None:
if _reader_started or _MODE == "off":
return
_reader_started = True
- threading.Thread(
- target=_reader, name="bounds-guard-reader", daemon=True
- ).start()
+ threading.Thread(target=_reader, name="bounds-guard-reader", daemon=True).start()
logger.info(
"bounds-guard reader started (mode=%s, interval=%.3gs).",
- _MODE, _INTERVAL,
+ _MODE,
+ _INTERVAL,
)
@@ -531,7 +531,11 @@ def _poll_once() -> None:
# by which time the maxima of that hit are written). The fatal
# decision is count-based either way; only its report waits a tick.
pend = _confirm_pending.get(device)
- rising = [site for site, (n, _r, _v) in enumerate(rows) if n > _seen.get((device, site), 0)]
+ rising = [
+ site
+ for site, (n, _r, _v) in enumerate(rows)
+ if n > _seen.get((device, site), 0)
+ ]
if pend is None:
if rising:
_confirm_pending[device] = rows
@@ -644,21 +648,18 @@ def drain(*, raise_on_fatal: bool = False) -> dict[str, list[int]]:
_report_rows(device, rows)
for site, (n_masked, _r, _v) in enumerate(rows):
if n_masked and (_MODE == "raise" or site in _ALWAYS_FATAL_SITES):
- fatal.append(
- f"{SITE_NAMES.get(site, site)} on {device}: {n_masked}"
- )
+ fatal.append(f"{SITE_NAMES.get(site, site)} on {device}: {n_masked}")
snap = snapshot()
if fatal:
logger.error(
"bounds-guard FINAL DRAIN: fatal-site hits %s (snapshot %s)",
- fatal, snap,
+ fatal,
+ snap,
)
if raise_on_fatal:
raise RuntimeError(f"bounds-guard FINAL DRAIN: fatal-site hits {fatal}")
else:
- logger.info(
- "bounds-guard FINAL DRAIN: no fatal-site hits (snapshot %s)", snap
- )
+ logger.info("bounds-guard FINAL DRAIN: no fatal-site hits (snapshot %s)", snap)
return snap
diff --git a/vllm/v1/worker/mamba_utils.py b/vllm/v1/worker/mamba_utils.py
index a6972f1..eeeadae 100644
--- a/vllm/v1/worker/mamba_utils.py
+++ b/vllm/v1/worker/mamba_utils.py
@@ -832,7 +832,7 @@ def postprocess_mamba_fused_kernel(
if src_block_idx == dest_block_idx and accept_token_bias == 0:
return
- if BT_ROW_IS_REQ:
+ if BT_ROW_IS_REQ: # noqa: SIM108 -- a ternary would drop this comment
# Persistent per-req-slot table: the row is the request's own slot, so
# it stays correct however many steps separate the gather from this
# launch (the PP-deferred post-process is enqueued at the top of
diff --git a/vllm_ple_test_fault/__init__.py b/vllm_ple_test_fault/__init__.py
index e8fe835..41830e0 100644
--- a/vllm_ple_test_fault/__init__.py
+++ b/vllm_ple_test_fault/__init__.py
@@ -145,16 +145,12 @@ class FaultSpec:
if bytes_sent is not None or frame_size is None:
return False
suffix = (
- f" bytes_written={int(bytes_written)}"
- f" frame_size={int(frame_size)}"
+ f" bytes_written={int(bytes_written)} frame_size={int(frame_size)}"
)
elif bytes_sent is not None:
if frame_size is None:
return False
- suffix = (
- f" bytes_sent={int(bytes_sent)}"
- f" frame_size={int(frame_size)}"
- )
+ suffix = f" bytes_sent={int(bytes_sent)} frame_size={int(frame_size)}"
elif frame_size is not None:
return False
message = (
@@ -306,7 +302,9 @@ def _parse_fault_spec(value: str | None) -> FaultSpec | None:
if value is None:
return None
if not value or value != value.strip():
- raise ValueError(f"{_ENV_NAME}: spec must be non-empty with no outer whitespace")
+ raise ValueError(
+ f"{_ENV_NAME}: spec must be non-empty with no outer whitespace"
+ )
fields: dict[str, str] = {}
for item in value.split(","):
@@ -355,8 +353,7 @@ def _parse_fault_spec(value: str | None) -> FaultSpec | None:
if role in {"offload", "bootstrap"} and not has_any_rank:
raise ValueError(
- f"{_ENV_NAME}: {role} cut {cut!r} requires rank=any; "
- "it has no TP/PP rank"
+ f"{_ENV_NAME}: {role} cut {cut!r} requires rank=any; it has no TP/PP rank"
)
if role == "gpu":
if pp_rank is not None and pp_rank != 0:
--
2.43.0