main
py 244 lines 8.04 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import functools
4
5 import torch
6
7 from vllm.config import CacheConfig, VllmConfig
8 from vllm.forward_context import ForwardContext, get_forward_context
9 from vllm.logger import init_logger
10 from vllm.model_executor.custom_op import CustomOp
11 from vllm.model_executor.layers.attention import Attention
12 from vllm.utils.math_utils import cdiv
13 from vllm.utils.torch_utils import (
14 LayerNameType,
15 _encode_layer_name,
16 _resolve_layer_name,
17 direct_register_custom_op,
18 )
19 from vllm.v1.attention.backend import (
20 AttentionBackend,
21 AttentionMetadata,
22 AttentionType,
23 CommonAttentionMetadata,
24 subclass_attention_backend,
25 )
26 from vllm.v1.attention.ops.triton_reshape_and_cache_flash import (
27 triton_reshape_and_cache_flash_diffkv,
28 )
29 from vllm.v1.attention.selector import get_attn_backend
30 from vllm.v1.kv_cache_interface import (
31 AttentionSpec,
32 KVCacheSpec,
33 SinkFullAttentionSpec,
34 get_kv_quant_mode,
35 )
36
37 logger = init_logger(__name__)
38
39
40 @functools.lru_cache
41 def create_static_sink_attention_backend(
42 underlying_attn_backend: type[AttentionBackend],
43 sink_len: int = 0,
44 ) -> type[AttentionBackend]:
45 prefix = "StaticSink_"
46 underlying_builder = underlying_attn_backend.get_builder_cls()
47
48 class StaticSinkAttentionBuilder(underlying_builder): # type: ignore
49 def __init__(
50 self,
51 kv_cache_spec: AttentionSpec,
52 layer_names: list[str],
53 vllm_config: VllmConfig,
54 device: torch.device,
55 ):
56 super().__init__(kv_cache_spec, layer_names, vllm_config, device)
57 model_config = vllm_config.model_config
58 scheduler_config = vllm_config.scheduler_config
59 self.sink_len = sink_len
60 self.block_size = vllm_config.cache_config.block_size
61 self.num_sink_blocks = self.sink_len // vllm_config.cache_config.block_size
62 self.max_num_blocks = cdiv(
63 model_config.max_model_len, vllm_config.cache_config.block_size
64 )
65 self.block_table_with_sink = torch.zeros(
66 (
67 scheduler_config.max_num_seqs,
68 self.max_num_blocks + self.num_sink_blocks,
69 ),
70 device=device,
71 dtype=torch.int32,
72 )
73 self.block_table_with_sink[:, : self.num_sink_blocks] = torch.arange(
74 1,
75 self.num_sink_blocks + 1,
76 device=device,
77 dtype=torch.int32,
78 )
79
80 def build(
81 self,
82 common_prefix_len: int,
83 common_attn_metadata: CommonAttentionMetadata,
84 fast_build: bool = False,
85 ) -> AttentionMetadata:
86 common_attn_metadata.seq_lens[:] = (
87 common_attn_metadata.seq_lens + self.sink_len
88 )
89 common_attn_metadata.seq_lens[
90 common_attn_metadata.seq_lens == self.sink_len
91 ] = 0
92 common_attn_metadata.max_seq_len = (
93 common_attn_metadata.max_seq_len + self.sink_len
94 )
95 max_num_blocks = cdiv(common_attn_metadata.max_seq_len, self.block_size)
96 num_reqs = common_attn_metadata.num_reqs
97 self.block_table_with_sink[
98 :num_reqs, self.num_sink_blocks : self.num_sink_blocks + max_num_blocks
99 ] = common_attn_metadata.block_table_tensor[:, :max_num_blocks]
100 common_attn_metadata.block_table_tensor = self.block_table_with_sink[
101 :num_reqs
102 ]
103
104 return super().build(common_prefix_len, common_attn_metadata, fast_build)
105
106 attn_backend = subclass_attention_backend(
107 name_prefix=prefix,
108 attention_backend_cls=underlying_attn_backend,
109 builder_cls=StaticSinkAttentionBuilder,
110 )
111
112 return attn_backend
113
114
115 @CustomOp.register("static_sink_attention")
116 class StaticSinkAttention(Attention, CustomOp):
117 """Attention with static sink tokens."""
118
119 def __init__(
120 self,
121 num_heads: int,
122 head_size: int,
123 scale: float,
124 sink_len: int,
125 attn_backend: type[AttentionBackend] | None = None,
126 cache_config: CacheConfig | None = None,
127 **kwargs,
128 ):
129 dtype = torch.get_default_dtype()
130
131 if cache_config is not None:
132 kv_cache_dtype = cache_config.cache_dtype
133 else:
134 kv_cache_dtype = "auto"
135
136 if attn_backend is not None:
137 underlying_attn_backend = attn_backend
138 else:
139 underlying_attn_backend = get_attn_backend(head_size, dtype, kv_cache_dtype)
140 attn_backend = create_static_sink_attention_backend(
141 underlying_attn_backend, # type: ignore[arg-type]
142 sink_len=sink_len,
143 )
144 Attention.__init__(
145 self=self,
146 num_heads=num_heads,
147 head_size=head_size,
148 scale=scale,
149 cache_config=cache_config,
150 attn_backend=attn_backend,
151 **kwargs,
152 )
153 self.sink_len = sink_len
154 self.sink_populated = False
155 self.sink_key = None
156 self.sink_value = None
157
158 def update_sink_kv(self, sink_key, sink_value) -> None:
159 self.sink_key = sink_key
160 self.sink_value = sink_value
161
162 def forward_native(
163 self,
164 query: torch.Tensor,
165 key: torch.Tensor,
166 value: torch.Tensor,
167 output_shape: torch.Size | None = None,
168 ) -> torch.Tensor:
169 assert self.sink_key is not None and self.sink_value is not None, (
170 "sink_key and sink_value have not been prepared"
171 )
172 if not self.sink_populated:
173 self_kv_cache = self.kv_cache
174 torch.ops.vllm.maybe_populate_sink(
175 self_kv_cache, _encode_layer_name(self.layer_name)
176 )
177
178 return super().forward(query, key, value, output_shape)
179
180 def forward_cuda(
181 self,
182 query: torch.Tensor,
183 key: torch.Tensor,
184 value: torch.Tensor,
185 output_shape: torch.Size | None = None,
186 ) -> torch.Tensor:
187 return self.forward_native(query, key, value, output_shape)
188
189 def forward(self, *args, **kwargs):
190 return self._forward_method(*args, **kwargs)
191
192 def populate_sink_kv(self, self_kv_cache):
193 sink_kv_slot_mapping = torch.arange(
194 self.block_size,
195 self.sink_len + self.block_size,
196 device=torch.accelerator.current_device_index(),
197 dtype=torch.long,
198 )
199 triton_reshape_and_cache_flash_diffkv(
200 self.sink_key,
201 self.sink_value,
202 self_kv_cache,
203 sink_kv_slot_mapping,
204 self.kv_cache_dtype,
205 self._k_scale,
206 self._v_scale,
207 )
208 # We only populate the sink_key and sink_value once
209 self.sink_populated = True
210
211 def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec:
212 # Block size may get updated after model loading, refresh it
213 self.block_size = vllm_config.cache_config.block_size
214 # Should not be called for enc-dec or encoder-only attention.
215 assert self.attn_type == AttentionType.DECODER
216
217 return SinkFullAttentionSpec(
218 block_size=self.block_size,
219 num_kv_heads=self.num_kv_heads,
220 head_size=self.head_size,
221 head_size_v=self.head_size_v,
222 sink_len=self.sink_len,
223 dtype=self.kv_cache_torch_dtype,
224 kv_quant_mode=get_kv_quant_mode(self.kv_cache_dtype),
225 )
226
227
228 def maybe_populate_sink(
229 self_kv_cache: torch.Tensor,
230 layer_name: LayerNameType,
231 ) -> None:
232 layer_name = _resolve_layer_name(layer_name)
233 forward_context: ForwardContext = get_forward_context()
234 self = forward_context.no_compile_layers[layer_name]
235 if self.sink_populated or self_kv_cache.numel() == 0:
236 return
237 self.populate_sink_kv(self_kv_cache)
238
239
240 direct_register_custom_op(
241 op_name="maybe_populate_sink",
242 op_func=maybe_populate_sink,
243 mutates_args=["self_kv_cache"],
244 )