"v1/attention/backend.py and selector.py: the backend interfaces and the selector"
Code version: vLLM @ 3e44c0d · 2026-10-22
Purpose#
These two files define what an attention backend must provide (backend.py) and how a layer gets one (selector.py).
| Files | vllm/v1/attention/backend.py, 1,150 lines; vllm/v1/attention/selector.py, 250 lines. |
|---|---|
| Main classes | AttentionBackend (vllm/v1/attention/backend.py:59-379), CommonAttentionMetadata (vllm/v1/attention/backend.py:389-554), AttentionMetadataBuilder (vllm/v1/attention/backend.py:592-761), AttentionImpl (vllm/v1/attention/backend.py:890-1031), MLAAttentionImpl (vllm/v1/attention/backend.py:1034-1128); get_attn_backend (vllm/v1/attention/selector.py:109-203). |
| Runs in | Each worker: the selector while the model is built; the builders at KV cache init and in every step; the impls in every step. |
| Called by | Attention and MLAAttention constructors (selector), attn_utils.py of Model Runner V2 (builders), the unified attention ops (impls). |
| Implemented by | The backend modules in vllm/v1/attention/backends/, for example flash_attn.py, and out-of-tree backends. |
| Not in this file | The backend enum and its registry (backends/registry.py), the platform priority lists (How vLLM chooses an attention backend), the KV cache layout resolver (backends/utils.py), kernels. |
Where it sits#
Who calls it#
- Selector.
Attention.__init__callsget_attn_backend(vllm/model_executor/layers/attention/attention.py:349-360). The MLA layer does the same withuse_mla=True. - Builders.
AttentionGroup.create_metadata_buildersinstantiates them (vllm/v1/worker/utils.py:330-339);build_attn_metadatacallsbuild(vllm/v1/worker/gpu/attn_utils.py:574-576). - Impls. The unified ops call
impl.forwardandimpl.do_kv_cache_update("model_executor/layers/attention/attention.py: the Attention layer and its custom ops"). - Platforms. CUDA calls
validate_configurationon each candidate (vllm/platforms/cuda.py:389-417).
What it calls#
current_platform.get_attn_backend_clsandresolve_obj_by_qualname(selector,vllm/v1/attention/selector.py:218-228).get_kv_quant_modefor the impl's KV quantization mode (vllm/v1/attention/backend.py:895-898).ops.concat_and_cache_mlain the MLA cache write (vllm/v1/attention/backend.py:1118-1128).
Key classes and functions#
| Name | Lines | Role |
|---|---|---|
AttentionType | vllm/v1/attention/backend.py:36-49 | decoder, encoder, encoder_only, encoder_decoder; strings for torch.compile. |
MultipleOf | vllm/v1/attention/backend.py:52-56 | Kernel block sizes "any multiple of N". |
AttentionBackend | vllm/v1/attention/backend.py:59-379 | Static description: names, classes, supports_*, validate_configuration, layouts. |
CommonAttentionMetadata | vllm/v1/attention/backend.py:389-554 | Backend-neutral batch description per KV cache group. |
AttentionCGSupport | vllm/v1/attention/backend.py:557-569 | ALWAYS, UNIFORM_BATCH, UNIFORM_SINGLE_TOKEN_DECODE, NEVER. |
max_decode_query_len | vllm/v1/attention/backend.py:572-589 | Widest query that counts as a decode: 1 + num_speculative_tokens. |
AttentionMetadataBuilder | vllm/v1/attention/backend.py:592-761 | build, capture and drafting variants, CUDA graph support. |
AttentionLayer | vllm/v1/attention/backend.py:764-782 | Protocol: the scales and forward that an impl may read from its layer. |
AttentionImplBase | vllm/v1/attention/backend.py:785-887 | Shared attributes; DCP/PCP ranks set in __new__. |
AttentionImpl | vllm/v1/attention/backend.py:890-1031 | forward plus optional fused-op hooks. |
MLAAttentionImpl | vllm/v1/attention/backend.py:1034-1128 | forward_mha (prefill), forward_mqa (decode), shared cache write. |
subclass_attention_backend(_with_overrides) | vllm/v1/attention/backend.py:1131-1150 | Make a backend subclass with another builder or other members. |
AttentionSelectorConfig | vllm/v1/attention/selector.py:25-65 | The hashable set of facts that the platform sees. |
get_attn_spec_kind | vllm/v1/attention/selector.py:68-106 | Map a layer to a KVCacheSpecKind for per-kind overrides. |
get_attn_backend, _cached_get_attn_backend | vllm/v1/attention/selector.py:109-228 | Collect facts, pick the requested backend, ask the platform once per fact set. |
get_mamba_attn_backend | vllm/v1/attention/selector.py:231-250 | The same for Mamba-style layers, from their own enum. |
Walk through the main methods#
AttentionBackend.validate_configuration#
) -> list[str]:
invalid_reasons = []
if not cls.supports_head_size(head_size):
invalid_reasons.append("head_size not supported")
if not cls.supports_dtype(dtype):
invalid_reasons.append("dtype not supported")
if not cls.supports_kv_cache_dtype(kv_cache_dtype):
invalid_reasons.append("kv_cache_dtype not supported")
if not cls.supports_block_size(block_size):
invalid_reasons.append("block_size not supported")
if use_mm_prefix and not cls.supports_mm_prefix():
invalid_reasons.append(
"partial multimodal token full attention not supported"
)
if use_mla != cls.is_mla():
if use_mla:
invalid_reasons.append("MLA not supported")
else:
invalid_reasons.append("non-MLA not supported")
if has_sink and not cls.supports_sink():
invalid_reasons.append("attention sinks not supported")
if use_sparse != cls.is_sparse():
if use_sparse:
invalid_reasons.append("sparse not supported")
else:
invalid_reasons.append("non-sparse not supported")
if use_per_head_quant_scales and not cls.supports_per_head_quant_scales():
invalid_reasons.append("per-head quant scales not supported")
if not cls.supports_compute_capability(device_capability):
invalid_reasons.append("compute capability not supported")
if not cls.supports_attn_type(attn_type):
invalid_reasons.append(f"attention type {attn_type} not supported")Each failed check adds a human-readable reason; an empty list means "valid". The rest of the method checks sliding window, R-SWA, non-causal attention, batch invariance, the KV connector, PCP, DCP and adaptive verification, then the backend's own supports_combination (vllm/v1/attention/backend.py:332-369). The page How vLLM chooses an attention backend shows how CUDA uses the reasons.
AttentionMetadataBuilder.__init__ and build#
class AttentionMetadataBuilder(ABC, Generic[M]):
# Does this backend/builder support CUDA Graphs for attention (default: no).
# Do not access directly. Call get_cudagraph_support() instead.
_cudagraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.NEVER
# Does this backend/builder reorder the batch?
# If not, set this to None. Otherwise set it to the query
# length that will be pulled into the front of the batch.
reorder_batch_threshold: int | None = None
# Does this backend/builder support updating the block table in existing
# metadata
supports_update_block_table: bool = False
# Whether the builder constructor requires the block-table width.
requires_block_table_width: ClassVar[bool] = False
# Whether update_draft_decode_metadata() can regenerate all decode metadata
# from persistent device buffers with capture-safe ops only. Lets a
# speculator record the refresh inside its draft decode CUDA graph, so no
# eager build() is needed between steps or between the batches that replay
# it, whether the graph holds one draft forward or a multi-step loop.
supports_draft_decode_metadata_update: bool = False
@abstractmethod
def __init__(
self,
kv_cache_spec: "KVCacheSpec",
layer_names: list[str],
vllm_config: "VllmConfig",
device: torch.device,
):
self.kv_cache_spec = kv_cache_spec
self.layer_names = layer_names
self.vllm_config = vllm_config
self.device = device
- Line 595. CUDA graph support defaults to
NEVER; read it throughget_cudagraph_support()(line 594). - Line 599.
reorder_batch_threshold: the query length up to which the builder treats a request as a decode. - Lines 602, 604, 610. Feature flags: block-table update, block-table width, capture-safe draft updates.
- Lines 613-624. The constructor gets the KV cache spec, the layer names of the group, the config and the device.
build(common_prefix_len, common_attn_metadata, fast_build=False) is abstract (vllm/v1/attention/backend.py:686-704). build_for_cudagraph_capture and build_for_drafting call it by default (lines 720-751).
AttentionImpl.forward#
@abstractmethod
def __init__(
self,
num_heads: int,
head_size: int,
scale: float,
num_kv_heads: int | None = None,
alibi_slopes: list[float] | None = None,
sliding_window: int | None = None,
kv_cache_dtype: str = "auto",
logits_soft_cap: float | None = None,
attn_type: str = AttentionType.DECODER,
kv_sharing_target_layer_name: str | None = None,
) -> None:
raise NotImplementedError
@abstractmethod
def forward(
self,
layer: AttentionLayer,
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
kv_cache: torch.Tensor,
attn_metadata: T,
output: torch.Tensor,
output_scale: torch.Tensor | None = None,
output_block_scale: torch.Tensor | None = None,
) -> torch.Tensor:
raise NotImplementedErrorThe constructor takes the same ten arguments that Attention.__init__ passes. forward gets the layer (for its scales), the tensors, the KV cache view, the metadata and the output buffer. The fused hooks (do_rope_and_kv_cache_update and others, lines 973-1031) are used only by Inductor fusion passes when the impl says it supports them.
get_attn_backend (selector.py)#
attn_type = attn_type or AttentionType.DECODER
attn_selector_config = AttentionSelectorConfig(
head_size=head_size,
dtype=dtype,
kv_cache_dtype=cast(CacheDType | None, kv_cache_dtype),
block_size=block_size,
use_mla=use_mla,
has_sink=has_sink,
use_sparse=use_sparse,
use_mm_prefix=use_mm_prefix,
use_per_head_quant_scales=use_per_head_quant_scales,
attn_type=attn_type,
has_sliding_window=has_sliding_window,
use_non_causal=vllm_config.attention_config.use_non_causal,
use_batch_invariant=envs.VLLM_BATCH_INVARIANT,
use_kv_connector=use_kv_connector,
use_pcp=vllm_config.parallel_config.prefill_context_parallel_size > 1,
use_adaptive_verification=use_adaptive_verification,
use_dcp=vllm_config.parallel_config.decode_context_parallel_size > 1,
use_rswa=(
vllm_config.model_config is not None
and vllm_config.model_config.rswa_window is not None
),
)
# A per-KV-group override (keyed by KVCacheSpecKind) takes precedence over
# the global backend; kinds not present in the map fall back to it.
attention_config = vllm_config.attention_config
backend = attention_config.backend
if attention_config.backend_per_kind:
kind = get_attn_spec_kind(
use_mla=use_mla,
has_sliding_window=has_sliding_window,
attn_type=attn_type,
)
backend = attention_config.backend_per_kind.get(kind.value, backend)
# The KV cache layout is resolved across all of the model's backends at once
# in get_kv_cache_spec(); a single selection cannot see its peers.
return _cached_get_attn_backend(
backend=backend,
attn_selector_config=attn_selector_config,
num_heads=num_heads,
_run_kv_cache_dtype=(
cache_config.cache_dtype if cache_config is not None else None
),
)- Lines 158-180. The selector config mixes the layer's facts with run-wide facts: KV connector, PCP, DCP, batch invariance, adaptive verification and R-SWA.
- Lines 184-192. A per-kind override wins over the global backend.
- Lines 196-203. The cached call. The run-wide KV cache dtype is part of the cache key.
The block size goes into the config only when the user set --block-size (vllm/v1/attention/selector.py:134-138). _cached_get_attn_backend is decorated with @cache, so each set of facts asks the platform once per process (vllm/v1/attention/selector.py:206-228).
Deep dive#
The sections below give the full detail. Open them one at a time, or use Open all deep dives at the top of the page.
Deep diveCommonAttentionMetadata helpers
naive_query_lens():query_start_loc[1:] - query_start_loc[:-1], "naive" because it assumes that one query ends where the next starts (vllm/v1/attention/backend.py:482-484).compute_num_computed_tokens():seq_lens - query_lenson the device, cached (vllm/v1/attention/backend.py:489-494).token_to_req_indices(buffer): the request index of each token, built from the devicequery_start_loc, cached (vllm/v1/attention/backend.py:496-518).unpadded(num_actual_tokens, num_actual_reqs): slice every field back to the real sizes, marked "remove once we have FULL-CG spec-decode support" (vllm/v1/attention/backend.py:520-554).
Deep diveAttentionImplBase.__new__ and context parallelism
Every impl gets its DCP and PCP rank and world size in __new__, so subclasses need not call a base constructor (vllm/v1/attention/backend.py:853-878). need_to_return_lse_for_decode is true only with DCP > 1 and an impl that can return the softmax LSE. The comment on lse_base_on_e warns that a wrong base "silently corrupts the cross-shard softmax denominator" (vllm/v1/attention/backend.py:808-818).
Deep diveWriting a backend: the minimum
- A subclass of
AttentionBackendwithget_name,get_impl_clsandget_builder_cls(vllm/v1/attention/backend.py:81-94), plus thesupports_*answers that differ from the defaults. - A metadata dataclass and a builder with
__init__andbuild; set_cudagraph_supportif graphs are safe. - An impl with
__init__andforward; ifforward_includes_kv_cache_update = False, alsodo_kv_cache_update. - Registration through
register_backendand selection by the platform or--attention-backend(How vLLM chooses an attention backend).
(Derived from the abstract members of this file.)
Deep diveError paths
| Situation | Result | Where |
|---|---|---|
| unknown KV cache dtype string | AssertionError "Invalid kv_cache_dtype" | vllm/v1/attention/selector.py:123-127 |
| the platform returns no class name | ValueError "Invalid attention backend for ..." | vllm/v1/attention/selector.py:223-226 |
Mamba backend without batch invariance under VLLM_BATCH_INVARIANT | RuntimeError | vllm/v1/attention/selector.py:245-249 |
a builder without update_block_table asked to update | NotImplementedError | vllm/v1/attention/backend.py:706-718 |
Related concepts#
- Attention backends: backend, metadata builder and implementation: the three classes in action, with the KV cache layout.
- How vLLM chooses an attention backend: the platform side of the selector.
- torch.compile and CUDA graphs in vLLM: how
AttentionCGSupportlowers the CUDA graph mode.
Check yourself#
- 1Which three static methods must every backend implement?easy
Show answer
get_name,get_impl_clsandget_builder_cls(vllm/v1/attention/backend.py:81-94). - 2Why does the selector passmedium
_run_kv_cache_dtypeto the cached function although the platform can read the config?Show answer
So the cache key changes when the run-wide KV cache dtype changes. The comment says some platform selectors inspect it (
vllm/v1/attention/selector.py:214-215). - 3What doeshard
max_decode_query_lenreturn with 4 speculative tokens on Model Runner V2, and who uses it?Show answer
5 (
1 + 4); the extra term for parallel drafting applies only to Model Runner V1 (vllm/v1/attention/backend.py:584-589). Builders that run spec decoding as decodes raise theirreorder_batch_thresholdto it (vllm/v1/attention/backend.py:666-678).