main
py 158 lines 5.71 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from abc import ABC, abstractmethod
5 from dataclasses import dataclass, replace
6
7 import torch
8
9 from vllm.config import VllmConfig
10 from vllm.utils.math_utils import cdiv
11 from vllm.utils.torch_utils import async_tensor_h2d
12 from vllm.v1.attention.backend import CommonAttentionMetadata
13 from vllm.v1.attention.backends.utils import NULL_BLOCK_ID
14 from vllm.v1.kv_cache_interface import (
15 MambaSpec,
16 get_mamba_prefill_checkpoint_position,
17 is_mamba_prefill_checkpoint_valid,
18 )
19
20
21 def compute_mamba_prefill_checkpoints(
22 seq_lens: list[int],
23 query_lens: list[int],
24 hash_block_size: int,
25 mamba_block_size: int,
26 checkpoint_alignment: int | None,
27 drop_eagle_block: bool,
28 ) -> tuple[list[int], list[int]]:
29 """Per-row internal prefill checkpoint offsets and cache block columns.
30
31 Backends call this instead of re-deriving the rules, so they decline in
32 lockstep with the scheduler and ``MambaManager``: allocating a checkpoint
33 block without writing it leaves the prefix cache serving uninitialized
34 state.
35
36 Returns:
37 ``(offsets, cols)``: the checkpoint's offset into each row's query and
38 its block-table column. ``0`` and ``-1`` mean the row has none.
39
40 """
41 offsets: list[int] = []
42 cols: list[int] = []
43 for seq_len, query_len in zip(seq_lens, query_lens):
44 query_start = seq_len - query_len
45 position = get_mamba_prefill_checkpoint_position(
46 seq_len, hash_block_size, drop_eagle_block=drop_eagle_block
47 )
48 valid = is_mamba_prefill_checkpoint_valid(
49 query_start=query_start,
50 query_end=seq_len,
51 checkpoint_position=position,
52 hash_block_size=hash_block_size,
53 mamba_block_size=mamba_block_size,
54 checkpoint_alignment=checkpoint_alignment,
55 )
56 offsets.append(position - query_start if valid else 0)
57 cols.append(cdiv(seq_len, mamba_block_size) - 2 if valid else -1)
58 return offsets, cols
59
60
61 def _gather_checkpoint_state_indices(
62 block_table: torch.Tensor, request_rows: torch.Tensor, block_cols: torch.Tensor
63 ) -> torch.Tensor:
64 return torch.where(
65 block_cols >= 0, block_table[request_rows, block_cols], NULL_BLOCK_ID
66 )
67
68
69 @dataclass
70 class MambaPrefillCheckpointMetadata:
71 checkpoint_offsets: torch.Tensor
72 state_indices: torch.Tensor
73 # Gather indices into the block table, kept so that another KV cache group
74 # with the same spec can re-derive its own ``state_indices``.
75 request_rows: torch.Tensor | None = None
76 block_cols: torch.Tensor | None = None
77
78 def regather_state_indices(
79 self, block_table: torch.Tensor
80 ) -> "MambaPrefillCheckpointMetadata":
81 assert self.request_rows is not None and self.block_cols is not None
82 return replace(
83 self,
84 state_indices=_gather_checkpoint_state_indices(
85 block_table, self.request_rows, self.block_cols
86 ),
87 )
88
89
90 class MambaPrefillCheckpointBuilder:
91 """Build backend-neutral prefill checkpoint locations."""
92
93 def __init__(self, vllm_config: VllmConfig, kv_cache_spec: MambaSpec) -> None:
94 self.vllm_config = vllm_config
95 self.kv_cache_spec = kv_cache_spec
96
97 def build(
98 self,
99 m: CommonAttentionMetadata,
100 request_rows: list[int],
101 ) -> MambaPrefillCheckpointMetadata | None:
102 if self.vllm_config.cache_config.mamba_cache_mode != "align":
103 return None
104 if self.kv_cache_spec.num_prefill_checkpoint_blocks == 0:
105 return None
106 assert m.seq_lens_cpu_upper_bound is not None
107 all_query_lens = m.query_start_loc_cpu.diff().tolist()
108 query_lens = [all_query_lens[row] for row in request_rows]
109 seq_lens = m.seq_lens_cpu_upper_bound.tolist()
110 block_size = self.kv_cache_spec.block_size
111 hash_block_size = self.vllm_config.cache_config.prefix_match_unit or block_size
112 speculative_config = self.vllm_config.speculative_config
113 drop_eagle_block = (
114 speculative_config is not None and speculative_config.use_eagle_block_drop()
115 )
116 checkpoint_offsets, checkpoint_cols = compute_mamba_prefill_checkpoints(
117 [seq_lens[row] for row in request_rows],
118 query_lens,
119 hash_block_size=hash_block_size,
120 mamba_block_size=block_size,
121 checkpoint_alignment=self.kv_cache_spec.prefill_checkpoint_alignment,
122 drop_eagle_block=drop_eagle_block,
123 )
124 if not any(checkpoint_offsets):
125 return None
126 checkpoint_offsets_tensor = async_tensor_h2d(
127 checkpoint_offsets,
128 dtype=torch.int32,
129 device=m.query_start_loc.device,
130 )
131 request_rows_tensor = async_tensor_h2d(
132 request_rows, dtype=torch.int64, device=m.query_start_loc.device
133 )
134 checkpoint_cols_tensor = async_tensor_h2d(
135 checkpoint_cols, dtype=torch.int64, device=m.query_start_loc.device
136 )
137 return MambaPrefillCheckpointMetadata(
138 checkpoint_offsets_tensor,
139 _gather_checkpoint_state_indices(
140 m.block_table_tensor, request_rows_tensor, checkpoint_cols_tensor
141 ),
142 request_rows=request_rows_tensor,
143 block_cols=checkpoint_cols_tensor,
144 )
145
146
147 class MambaPrefillCheckpointExporter(ABC):
148 """Export a mid-prefill checkpoint into backend-specific paged states."""
149
150 @abstractmethod
151 def export(
152 self,
153 checkpoint: MambaPrefillCheckpointMetadata,
154 *args,
155 **kwargs,
156 ) -> None:
157 """Write checkpoint state into the paged cache."""
158 raise NotImplementedError