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
8 changes: 7 additions & 1 deletion tests/engine/test_arg_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from vllm.config import AttentionConfig, CompilationConfig, ModelConfig, config
from vllm.engine.arg_utils import (
PREFIX_CACHE_RETENTION_INTERVAL_UNSET,
EngineArgs,
_expand_json_human_readable_numbers,
contains_type,
Expand Down Expand Up @@ -471,7 +472,12 @@ def test_prefix_cache_default():
# should be None by default (depends on model).
engine_args = EngineArgs.from_cli_args(args=args)
assert engine_args.enable_prefix_caching is None
assert engine_args.prefix_cache_retention_interval == 0
# Left as an unresolved sentinel; create_engine_config resolves it against
# the model and speculative-decoding configuration.
assert (
engine_args.prefix_cache_retention_interval
is PREFIX_CACHE_RETENTION_INTERVAL_UNSET
)

# with flag to turn it on.
args = parser.parse_args(["--enable-prefix-caching"])
Expand Down
40 changes: 40 additions & 0 deletions tests/v1/engine/test_engine_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import pytest

from vllm.config import ModelConfig, SpeculativeConfig
from vllm.engine.arg_utils import EngineArgs
from vllm.usage.usage_lib import UsageContext
from vllm.utils.argparse_utils import FlexibleArgumentParser
Expand Down Expand Up @@ -53,6 +54,45 @@ def test_prefix_caching_from_cli():
assert vllm_config.cache_config.prefix_cache_retention_interval == 64


@pytest.mark.parametrize(
("has_inner_state", "use_eagle", "explicit", "expected"),
[
pytest.param(True, True, "unset", None, id="mamba-eagle-dense"),
pytest.param(True, True, 0, 0, id="mamba-eagle-explicit-zero"),
pytest.param(True, True, None, None, id="mamba-eagle-explicit-none"),
pytest.param(True, True, 64, 64, id="mamba-eagle-explicit-interval"),
pytest.param(True, False, "unset", 0, id="mamba-without-eagle"),
pytest.param(False, True, "unset", 0, id="eagle-without-mamba"),
pytest.param(False, False, "unset", 0, id="plain-model"),
],
)
def test_prefix_cache_retention_interval_default_resolution(
monkeypatch, has_inner_state, use_eagle, explicit, expected
):
"""An unset ``prefix_cache_retention_interval`` resolves to dense (None)
for Mamba models with EAGLE-style speculative decoding — sparse retention
(0) leaves no reachable Mamba state checkpoints under EAGLE, so prefix
caching never hits — and to 0 otherwise. Explicit values are respected."""
monkeypatch.setattr(
ModelConfig, "has_inner_state", property(lambda self: has_inner_state)
)
if use_eagle:
spec_config = SpeculativeConfig(model="ngram", num_speculative_tokens=1)
spec_config.method = "eagle"
monkeypatch.setattr(
EngineArgs,
"create_speculative_config",
lambda self, **kwargs: spec_config,
)
engine_kwargs = (
{} if explicit == "unset" else {"prefix_cache_retention_interval": explicit}
)
vllm_config = EngineArgs(
model="Qwen/Qwen3-0.6B", **engine_kwargs
).create_engine_config()
assert vllm_config.cache_config.prefix_cache_retention_interval == expected


@pytest.mark.skipif(_xxhash is None, reason="xxhash not installed")
def test_prefix_caching_xxhash_from_cli():
parser = EngineArgs.add_cli_args(FlexibleArgumentParser())
Expand Down
79 changes: 74 additions & 5 deletions vllm/engine/arg_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
TYPE_CHECKING,
Annotated,
Any,
ClassVar,
Literal,
TypeAlias,
TypeVar,
Expand Down Expand Up @@ -103,7 +104,7 @@
ExpertPlacementStrategy,
)
from vllm.config.scheduler import SchedulerPolicy
from vllm.config.utils import get_field
from vllm.config.utils import get_field, get_from_deprecated_env_if_set
from vllm.config.vllm import OptimizationLevel, PerformanceMode
from vllm.logger import init_logger, suppress_logging
from vllm.platforms import CpuArchEnum, current_platform
Expand Down Expand Up @@ -166,6 +167,40 @@ def _optional_type(val: str) -> T | None:
return _optional_type


class _UnsetType:
"""Sentinel marking that an argument was not provided by the user."""

_instance: ClassVar["_UnsetType | None"] = None

def __new__(cls) -> "_UnsetType":
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance

def __repr__(self) -> str:
return "UNSET"

def __reduce__(self) -> tuple[type, tuple]:
return (_UnsetType, ())


PREFIX_CACHE_RETENTION_INTERVAL_UNSET = _UnsetType()
"""Default of ``EngineArgs.prefix_cache_retention_interval`` when neither the
CLI flag nor a programmatic value was provided, so that the default can be
resolved against the model and speculative-decoding configuration."""


def _default_prefix_cache_retention_interval() -> int | None:
env_value = get_from_deprecated_env_if_set(
"VLLM_PREFIX_CACHE_RETENTION_INTERVAL",
"v0.29",
"prefix_cache_retention_interval",
)
if env_value is not None:
return int(env_value)
return cast("int | None", PREFIX_CACHE_RETENTION_INTERVAL_UNSET)


def union_dict_and_str(val: str) -> str | dict[str, str] | None:
if not re.match(r"(?s)^\s*{.*}\s*$", val):
return str(val)
Expand Down Expand Up @@ -527,8 +562,12 @@ class EngineArgs:
prefix_caching_hash_algo: PrefixCachingHashAlgo = (
CacheConfig.prefix_caching_hash_algo
)
prefix_cache_retention_interval: int | None = get_field(
CacheConfig, "prefix_cache_retention_interval"
# Defaults to a sentinel (or the deprecated env var when set) so that an
# unset value can be told apart from an explicit one; resolved in
# create_engine_config (0, or dense checkpoints for Mamba models with
# EAGLE-style speculative decoding).
prefix_cache_retention_interval: int | None = dataclasses.field(
default_factory=_default_prefix_cache_retention_interval
)
disable_sliding_window: bool = ModelConfig.disable_sliding_window
disable_cascade_attn: bool = ModelConfig.disable_cascade_attn
Expand Down Expand Up @@ -1251,7 +1290,10 @@ def add_cli_args(parser: FlexibleArgumentParser) -> FlexibleArgumentParser:
)
cache_group.add_argument(
"--prefix-cache-retention-interval",
**cache_kwargs["prefix_cache_retention_interval"],
**{
**cache_kwargs["prefix_cache_retention_interval"],
"default": PREFIX_CACHE_RETENTION_INTERVAL_UNSET,
},
)
cache_group.add_argument(
"--kv-cache-dtype-skip-layers", **cache_kwargs["kv_cache_dtype_skip_layers"]
Expand Down Expand Up @@ -2033,6 +2075,17 @@ def create_engine_config(
"enable_prefix_caching must be set by this point"
)

retention_interval_unset = (
self.prefix_cache_retention_interval
is PREFIX_CACHE_RETENTION_INTERVAL_UNSET
)
# When unset, apply the CacheConfig default (0) for now; it is
# resolved further below once speculative_config is known. The
# deprecated VLLM_PREFIX_CACHE_RETENTION_INTERVAL env var, when set,
# was already applied by the field's default factory.
retention_interval = (
0 if retention_interval_unset else self.prefix_cache_retention_interval
)
cache_config = CacheConfig(
block_size=self.block_size, # type: ignore[arg-type]
gpu_memory_utilization=self.gpu_memory_utilization,
Expand All @@ -2043,7 +2096,7 @@ def create_engine_config(
sliding_window=sliding_window,
enable_prefix_caching=self.enable_prefix_caching,
prefix_caching_hash_algo=self.prefix_caching_hash_algo,
prefix_cache_retention_interval=self.prefix_cache_retention_interval,
prefix_cache_retention_interval=retention_interval,
kv_cache_dtype_skip_layers=self.kv_cache_dtype_skip_layers,
kv_sharing_fast_prefill=self.kv_sharing_fast_prefill,
mamba_cache_dtype=self.mamba_cache_dtype,
Expand Down Expand Up @@ -2337,6 +2390,22 @@ def create_engine_config(
)
diffusion_config = self.create_diffusion_config()

if (
retention_interval_unset
and model_config.has_inner_state
and speculative_config is not None
and speculative_config.use_eagle()
):
# The default sparse retention (0) keeps only the latest replay
# boundary, which EAGLE's tail-block drop makes unreachable for
# Mamba state checkpoints, so prefix caching never hits. Default
# to dense checkpoints instead.
cache_config.prefix_cache_retention_interval = None
logger.info_once(
"Mamba model with EAGLE speculative decoding: defaulting "
"prefix_cache_retention_interval to dense checkpointing."
)

self._set_default_max_num_seqs_and_batched_tokens_args(
usage_context,
model_config,
Expand Down
Loading