main
py 328 lines 12.5 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from __future__ import annotations
4
5 from typing import cast
6
7 import torch
8 from torch import nn
9
10 from vllm.compilation.breakable_cudagraph import eager_break_during_capture
11 from vllm.config import VllmConfig, get_current_vllm_config
12 from vllm.distributed import get_tensor_model_parallel_world_size
13 from vllm.forward_context import get_forward_context
14 from vllm.model_executor.layers.attention_layer_base import AttentionLayerBase
15 from vllm.model_executor.layers.linear import (
16 MergedColumnParallelLinear,
17 RowParallelLinear,
18 )
19 from vllm.model_executor.layers.quantization import QuantizationConfig
20 from vllm.utils.torch_utils import (
21 canonicalize_singleton_dim_strides,
22 kv_cache_dtype_str_to_dtype,
23 )
24 from vllm.v1.attention.backend import AttentionBackend
25 from vllm.v1.attention.backends.flash_attn import (
26 FlashAttentionBackend,
27 FlashAttentionMetadata,
28 )
29 from vllm.v1.kv_cache_interface import (
30 FullAttentionSpec,
31 KVCacheSpec,
32 SlidingWindowSpec,
33 )
34
35 from ..configs import InklingModelConfig
36 from .layernorm import InklingRMSNorm
37 from .ops.fa4_rel_attention import (
38 bucket_max_seqlen_q,
39 inkling_fa4_num_splits,
40 inkling_fa4_rel_attention,
41 )
42 from .ops.fa4_warmup import InklingFA4WarmupConfig, register_fa4_warmup
43 from .ops.qkvr_prep import fused_qkvr_prep
44 from .sconv_swa_attn import _K, _V, InklingConvState, InklingSconvMetadata
45 from .short_conv import InklingShortConv
46
47
48 def compute_log_scaling_tau(
49 positions: torch.Tensor, n_floor: int, alpha: float
50 ) -> torch.Tensor:
51 effective_n = (positions + 1).to(torch.float32)
52 return 1.0 + alpha * torch.log(torch.clamp(effective_n / float(n_floor), min=1.0))
53
54
55 class RelLogitsProj(nn.Module):
56 """Project the per-head relative branch ``r`` to per-distance logits."""
57
58 def __init__(self, d_rel: int, rel_extent: int) -> None:
59 super().__init__()
60 self.d_rel = d_rel
61 self.rel_extent = rel_extent
62 self.proj = nn.Parameter(torch.empty(d_rel, rel_extent), requires_grad=False)
63
64 def forward(self, r_out: torch.Tensor) -> torch.Tensor:
65 # r_out: (T, num_heads, d_rel) -> (T, num_heads, rel_extent)
66 return torch.einsum("thd,de->the", r_out, self.proj)
67
68
69 class InklingAttention(nn.Module, AttentionLayerBase):
70 def __init__(
71 self,
72 config: InklingModelConfig,
73 *,
74 num_heads: int,
75 num_kv_heads: int,
76 head_dim: int,
77 rel_extent: int,
78 local_extent: int,
79 is_local: bool,
80 prefix: str,
81 quant_config: QuantizationConfig | None = None,
82 conv_owner: InklingConvState,
83 ) -> None:
84 super().__init__()
85 self.prefix = prefix
86 self.is_local = is_local
87 self.hidden_size = config.hidden_size
88 self.head_dim = head_dim
89 self.d_rel = config.d_rel
90 self.log_scaling_n_floor = config.log_scaling_n_floor
91 self.log_scaling_alpha = config.log_scaling_alpha
92 # q/k are per-head RMS-normed (unit norm), so Inkling scales by 1/head_dim.
93 self.scaling = 1.0 / head_dim
94
95 tp_size = get_tensor_model_parallel_world_size()
96 self.num_total_heads = num_heads
97 self.num_total_kv_heads = num_kv_heads
98 assert self.num_total_heads % tp_size == 0
99 self.num_heads = self.num_total_heads // tp_size
100 if self.num_total_kv_heads >= tp_size:
101 assert self.num_total_kv_heads % tp_size == 0
102 else:
103 assert tp_size % self.num_total_kv_heads == 0
104 self.num_kv_heads = max(1, self.num_total_kv_heads // tp_size)
105 # When tp_size > num_kv_heads the K/V projections are padded up to
106 # tp_size heads so each rank gets at least one (GQA replication).
107 kv_total_for_sizing = max(self.num_total_kv_heads, tp_size)
108
109 self.qkvr = MergedColumnParallelLinear(
110 input_size=config.hidden_size,
111 output_sizes=[
112 head_dim * self.num_total_heads,
113 head_dim * kv_total_for_sizing,
114 head_dim * kv_total_for_sizing,
115 self.d_rel * self.num_total_heads,
116 ],
117 bias=config.q_bias,
118 quant_config=quant_config,
119 prefix=f"{prefix}.qkvr",
120 )
121 self.wo_ud = RowParallelLinear(
122 input_size=head_dim * self.num_total_heads,
123 output_size=config.hidden_size,
124 bias=config.o_bias,
125 quant_config=quant_config,
126 # reduce_results=False: the partial output is all-reduced below
127 # (one-shot custom AR) so the attention-output sconv can run on the
128 # full hidden width fused with the residual add + rmsnorm.
129 reduce_results=False,
130 prefix=f"{prefix}.wo_ud",
131 )
132 self.rel_extent = local_extent if is_local else rel_extent
133 self.local_extent = local_extent if is_local else None
134 self.rel_logits_proj = RelLogitsProj(self.d_rel, self.rel_extent)
135 self.q_norm = InklingRMSNorm(head_dim, eps=config.rms_norm_eps)
136 self.k_norm = InklingRMSNorm(head_dim, eps=config.rms_norm_eps)
137
138 # Short convolution on the K/V streams (per-head-width, TP sharded),
139 # applied after the qkvr projection and before q/k norm.
140 kv_conv_dim = self.num_kv_heads * head_dim
141 self.conv_owner = conv_owner
142 self.k_sconv = InklingShortConv(
143 kv_conv_dim, config.sconv_kernel_size, owner=conv_owner, stream_idx=_K
144 )
145 self.v_sconv = InklingShortConv(
146 kv_conv_dim, config.sconv_kernel_size, owner=conv_owner, stream_idx=_V
147 )
148
149 # FA4 left/right window; right=0 keeps it causal. local_extent-1 mirrors
150 # the source (sliding_window_size - 1).
151 self.window_size: tuple[int, int] = (
152 (local_extent - 1, 0) if is_local else (-1, -1)
153 )
154 # Static per-layer-type KV length bound for the split heuristic: local
155 # layers never see more than the sliding window.
156 vllm_config = get_current_vllm_config()
157 self._max_kv_len = (
158 local_extent if is_local else vllm_config.model_config.max_model_len
159 )
160
161 # ---- KV-cache wiring (reuse FlashAttentionBackend for metadata) ----
162 cache_config = vllm_config.cache_config
163 self.kv_cache_dtype = (
164 cache_config.cache_dtype if cache_config is not None else "auto"
165 )
166 self.kv_cache_torch_dtype = kv_cache_dtype_str_to_dtype(
167 self.kv_cache_dtype, vllm_config.model_config
168 )
169 self.register_buffer("k_scale", torch.ones((), dtype=torch.float32))
170 self.register_buffer("v_scale", torch.ones((), dtype=torch.float32))
171
172 compilation_config = vllm_config.compilation_config
173 if prefix in compilation_config.static_forward_context:
174 raise ValueError(f"Duplicate layer name: {prefix}")
175 compilation_config.static_forward_context[prefix] = self
176 self.kv_cache = torch.tensor([]) # replaced by bind_kv_cache
177
178 register_fa4_warmup(
179 InklingFA4WarmupConfig(
180 num_heads=self.num_heads,
181 num_kv_heads=self.num_kv_heads,
182 head_dim=self.head_dim,
183 rel_extent=self.rel_extent,
184 window_size=self.window_size,
185 is_local=self.is_local,
186 max_kv_len=self._max_kv_len,
187 dtype=vllm_config.model_config.dtype,
188 kv_dtype=self.kv_cache_torch_dtype,
189 block_size=vllm_config.cache_config.block_size,
190 max_num_reqs=vllm_config.scheduler_config.max_num_seqs,
191 max_num_batched_tokens=(
192 vllm_config.scheduler_config.max_num_batched_tokens
193 ),
194 )
195 )
196
197 def get_attn_backend(self) -> type[AttentionBackend]:
198 return FlashAttentionBackend
199
200 def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec:
201 block_size = vllm_config.cache_config.block_size
202 if self.is_local:
203 assert self.local_extent is not None
204 return SlidingWindowSpec(
205 block_size=block_size,
206 num_kv_heads=self.num_kv_heads,
207 head_size=self.head_dim,
208 dtype=self.kv_cache_torch_dtype,
209 sliding_window=self.local_extent,
210 )
211 return FullAttentionSpec(
212 block_size=block_size,
213 num_kv_heads=self.num_kv_heads,
214 head_size=self.head_dim,
215 dtype=self.kv_cache_torch_dtype,
216 )
217
218 def _split_kv_cache(self) -> tuple[torch.Tensor, torch.Tensor]:
219 key_cache, value_cache = self.kv_cache.transpose(1, 2).split(
220 self.head_dim, dim=-1
221 )
222 return (
223 canonicalize_singleton_dim_strides(key_cache),
224 canonicalize_singleton_dim_strides(value_cache),
225 )
226
227 def forward(
228 self,
229 positions: torch.Tensor,
230 hidden_states: torch.Tensor,
231 log_scaling: torch.Tensor | None = None,
232 ) -> torch.Tensor:
233 num_tokens = hidden_states.shape[0]
234 qkvr, _ = self.qkvr(hidden_states)
235
236 attn_metadata = get_forward_context().attn_metadata
237 attn_output = torch.empty(
238 (num_tokens, self.num_heads, self.head_dim),
239 dtype=qkvr.dtype,
240 device=qkvr.device,
241 )
242 if not isinstance(attn_metadata, dict):
243 attn_output.zero_()
244 else:
245 conv_meta = attn_metadata[self.conv_owner.prefix]
246 md = attn_metadata[self.prefix]
247 assert isinstance(conv_meta, InklingSconvMetadata)
248 fa_md = cast(FlashAttentionMetadata, md)
249 assert self.kv_cache.numel() > 0
250 assert self.conv_owner.kv_cache.numel() > 0
251 # One launch: K/V sconv (conv-cache insert + conv + residual),
252 # Q/K per-head rmsnorm, and the attention KV-cache write. K/V are
253 # consumed via the KV cache; only normed q is materialized.
254 key_cache, value_cache = self._split_kv_cache()
255 off_k, _ = self.conv_owner.stream_ranges[_K]
256 off_v, _ = self.conv_owner.stream_ranges[_V]
257 q, rel_logits = fused_qkvr_prep(
258 qkvr,
259 self.k_sconv.weight.squeeze(1),
260 self.v_sconv.weight.squeeze(1),
261 self.q_norm.weight,
262 self.k_norm.weight,
263 self.rel_logits_proj.proj,
264 self.q_norm.variance_epsilon,
265 self.num_heads,
266 self.num_kv_heads,
267 self.head_dim,
268 self.d_rel,
269 self.conv_owner.kv_cache,
270 key_cache,
271 value_cache,
272 positions,
273 conv_meta.block_table,
274 conv_meta.seq_idx,
275 conv_meta.slot_mapping,
276 conv_meta.query_start,
277 fa_md.slot_mapping,
278 off_k,
279 off_v,
280 self.conv_owner.cache_block_size,
281 log_scaling if not self.is_local else None,
282 )
283 q = q.view(num_tokens, self.num_heads, self.head_dim)
284 self._attention(q, rel_logits, attn_output)
285
286 flat = attn_output.view(num_tokens, -1)
287 output, _ = self.wo_ud(flat)
288 return output
289
290 @eager_break_during_capture
291 def _attention(
292 self,
293 q: torch.Tensor,
294 rel_logits: torch.Tensor,
295 output: torch.Tensor,
296 ) -> None:
297 attn_metadata = get_forward_context().attn_metadata
298 assert isinstance(attn_metadata, dict)
299 md = cast(FlashAttentionMetadata, attn_metadata[self.prefix])
300
301 nt = md.num_actual_tokens
302 key_cache, value_cache = self._split_kv_cache()
303 max_seqlen_q = bucket_max_seqlen_q(md.max_query_len)
304 num_splits = inkling_fa4_num_splits(
305 is_local=self.is_local,
306 batch_size=md.seq_lens.shape[0],
307 max_query_len=max_seqlen_q,
308 num_heads=self.num_heads,
309 num_kv_heads=self.num_kv_heads,
310 max_kv_len=md.max_seq_len,
311 )
312 inkling_fa4_rel_attention(
313 q[:nt],
314 key_cache,
315 value_cache,
316 block_table=md.block_table,
317 cache_seqlens=md.seq_lens,
318 cu_seqlens_q=md.query_start_loc,
319 max_seqlen_q=max_seqlen_q,
320 softmax_scale=self.scaling,
321 causal=True,
322 window_size=self.window_size,
323 rel_extent=self.rel_extent,
324 rel_logits=rel_logits[:nt],
325 num_splits=num_splits,
326 max_kv_len=md.max_seq_len,
327 out=output[:nt],
328 )