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