main
py 698 lines 29.4 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from dataclasses import replace
5
6 import torch
7 from einops import rearrange
8 from torch import nn
9
10 from vllm import _custom_ops as ops
11 from vllm.compilation.breakable_cudagraph import eager_break_during_capture
12 from vllm.config import VllmConfig
13 from vllm.distributed import divide
14 from vllm.forward_context import get_forward_context
15 from vllm.logger import init_logger
16 from vllm.model_executor.layers.linear import (
17 ColumnParallelLinear,
18 RowParallelLinear,
19 )
20 from vllm.model_executor.layers.mamba.gdn.base import GatedDeltaNetAttention
21
22 # Generic KDA helpers, shared with Kimi-Linear. They are neither ROCm- nor
23 # K3-specific, so they are imported rather than duplicated. (nvidia/kda.py keeps
24 # its own copies; duplicating here instead would be a one-line change.)
25 from vllm.model_executor.layers.mamba.gdn.kimi_gdn_linear_attn import (
26 _KDA_GATE_LOGBOUND_MIN,
27 _KimiGDNMergedColumnParallelLinear,
28 _make_fused_conv1d_weight_loader,
29 a_log_weight_loader,
30 )
31 from vllm.model_executor.layers.mamba.mamba_utils import (
32 MambaStateDtypeCalculator,
33 MambaStateShapeCalculator,
34 is_conv_state_dim_first,
35 )
36 from vllm.model_executor.layers.mamba.ops.causal_conv1d import (
37 causal_conv1d_fn,
38 causal_conv1d_update,
39 )
40 from vllm.model_executor.model_loader.weight_utils import sharded_weight_loader
41 from vllm.model_executor.utils import set_weight_attrs
42 from vllm.models.kimi_k3.amd.kda_metadata import KimiK3ROCmKDABackend
43 from vllm.models.kimi_k3.amd.ops.kda_checkpoint import (
44 KimiK3ROCmKDAPrefillCheckpointExporter,
45 )
46 from vllm.models.kimi_k3.amd.ops.kda_chunk import (
47 KDA_CHECKPOINT_ALIGNMENT,
48 is_fused_kda_chunk_supported,
49 )
50 from vllm.models.kimi_k3.amd.ops.kda_decode import (
51 is_fused_kda_decode_supported,
52 make_decode_conv1d_weight_loader,
53 make_decode_norm_weight_loader,
54 )
55 from vllm.models.kimi_k3.amd.ops.kda_prefill import chunk_kda_prefill
56 from vllm.models.kimi_k3.amd.ops.third_party.kda import (
57 fused_recurrent_kda,
58 fused_recurrent_kda_packed_decode,
59 )
60 from vllm.third_party.flash_linear_attention.ops.kda import FusedRMSNormGated
61 from vllm.transformers_utils.configs.kimi_linear import KimiLinearConfig
62 from vllm.v1.attention.backend import AttentionBackend
63 from vllm.v1.attention.backends.gdn_attn import GDNAttentionMetadata
64 from vllm.v1.kv_cache_interface import MambaSpec
65
66 logger = init_logger(__name__)
67
68
69 class KimiK3DeltaAttention(GatedDeltaNetAttention):
70 def get_attn_backend(self) -> type[AttentionBackend]:
71 return KimiK3ROCmKDABackend
72
73 def get_state_dtype(self) -> tuple[torch.dtype, torch.dtype]:
74 if self.model_config is None or self.cache_config is None:
75 raise ValueError("model_config and cache_config must be set")
76 return MambaStateDtypeCalculator.kda_state_dtype(
77 self.model_config.dtype, self.cache_config.mamba_cache_dtype
78 )
79
80 def get_state_shape(self) -> tuple[tuple[int, ...], tuple[int, ...]]:
81 return MambaStateShapeCalculator.kda_state_shape(
82 self.tp_size,
83 self.num_heads,
84 self.head_dim,
85 conv_kernel_size=self.conv_size,
86 num_spec=self.num_spec,
87 )
88
89 def get_kv_cache_spec(self, vllm_config: VllmConfig) -> MambaSpec:
90 spec = super().get_kv_cache_spec(vllm_config)
91 assert isinstance(spec, MambaSpec)
92 # Only the fused path exports the checkpoint snapshots
93 can_checkpoint = (
94 self.use_prefill_checkpoint and self.use_fused_chunk and self.use_safe_gate
95 )
96 if can_checkpoint and vllm_config.cache_config.mamba_cache_mode == "align":
97 logger.info_once("Kimi-K3 KDA prefill checkpoint enabled.")
98 return replace(
99 spec,
100 num_prefill_checkpoint_blocks=int(can_checkpoint),
101 prefill_checkpoint_alignment=(
102 KDA_CHECKPOINT_ALIGNMENT if can_checkpoint else None
103 ),
104 )
105
106 def __init__(
107 self,
108 config: KimiLinearConfig,
109 vllm_config: VllmConfig,
110 prefix: str = "",
111 ) -> None:
112 super().__init__(config, vllm_config, prefix)
113
114 kda_config = config.linear_attn_config # type: ignore[attr-defined]
115 assert kda_config is not None, "linear_attn_config must be set"
116 assert kda_config.get("use_full_rank_gate", False), (
117 "KimiK3DeltaAttention requires use_full_rank_gate; the low-rank "
118 "gate path belongs to the shared Kimi-Linear layer."
119 )
120
121 self.head_dim = kda_config["head_dim"]
122 self.num_heads = kda_config["num_heads"]
123 assert self.num_heads % self.tp_size == 0
124 self.local_num_heads = divide(self.num_heads, self.tp_size)
125
126 self.projection_size = self.head_dim * self.num_heads
127 self.local_projection_size = divide(self.projection_size, self.tp_size)
128 self.conv_size = kda_config["short_conv_kernel_size"]
129 self.use_full_rank_gate = True
130
131 # Keep f_a before the narrow beta shard, then pad each TP-local row to
132 # select the aligned BF16 GEMM path. The padding also avoids an Inductor
133 # correctness issue seen with the row-strided G view.
134 qkvg_output_sizes = [self.projection_size] * 4
135 in_proj_output_sizes = qkvg_output_sizes + [self.head_dim, self.num_heads]
136 local_output_size = (
137 4 * self.local_projection_size + self.head_dim + self.local_num_heads
138 )
139 self.in_proj_padding = -local_output_size % 16
140 if self.in_proj_padding:
141 in_proj_output_sizes.append(self.in_proj_padding * self.tp_size)
142
143 self.in_proj_qkvgfab = _KimiGDNMergedColumnParallelLinear(
144 self.hidden_size,
145 in_proj_output_sizes,
146 replicated_shard_id=4,
147 tp_size=self.tp_size,
148 bias=False,
149 quant_config=self.quant_config,
150 prefix=f"{prefix}.in_proj_qkvgfab",
151 )
152 if self.in_proj_padding:
153 self.in_proj_qkvgfab.weight.data[-self.in_proj_padding :].zero_()
154
155 self.f_b_proj = ColumnParallelLinear(
156 self.head_dim,
157 self.projection_size,
158 bias=False,
159 quant_config=self.quant_config,
160 prefix=f"{prefix}.f_b_proj",
161 )
162 self.dt_bias = nn.Parameter(
163 torch.empty(self.local_projection_size, dtype=torch.float32)
164 )
165 set_weight_attrs(self.dt_bias, {"weight_loader": sharded_weight_loader(0)})
166
167 # One packed parameter and cache let decode run a single conv update.
168 # Prefill slices them back into Q/K/V to obtain dense outputs cheaply.
169 self.conv1d = ColumnParallelLinear(
170 input_size=self.conv_size,
171 output_size=3 * self.projection_size,
172 bias=False,
173 params_dtype=torch.float32,
174 prefix=f"{prefix}.conv1d",
175 )
176 self.conv1d.weight.data = self.conv1d.weight.data.unsqueeze(1)
177 delattr(self.conv1d.weight, "weight_loader")
178 # ROCm can fuse the whole decode step (conv + recurrence + gated norm)
179 # into one kernel, which wants a width-major fp32 conv weight staged at
180 # load time. Everything else keeps the [channel, width] layout.
181 conv_state_dtype, _ = self.get_state_dtype()
182 decode_conv1d_weight = None
183 if is_fused_kda_decode_supported(
184 self.local_num_heads,
185 self.head_dim,
186 self.conv_size,
187 self.num_spec,
188 vllm_config.model_config.dtype,
189 conv_state_dtype,
190 ):
191 logger.info_once("Fused KDA decode kernel (conv+KDA+norm) is enabled.")
192 decode_conv1d_weight = torch.empty(
193 3,
194 self.conv_size,
195 self.local_projection_size,
196 dtype=self.conv1d.weight.dtype,
197 device=self.conv1d.weight.device,
198 )
199 self.register_buffer(
200 "decode_conv1d_weight", decode_conv1d_weight, persistent=False
201 )
202 if decode_conv1d_weight is None:
203 conv1d_weight_loader = _make_fused_conv1d_weight_loader(
204 [self.projection_size] * 3,
205 self.tp_size,
206 self.tp_rank,
207 )
208 else:
209 conv1d_weight_loader = make_decode_conv1d_weight_loader(
210 [self.projection_size] * 3,
211 self.tp_size,
212 self.tp_rank,
213 decode_conv1d_weight,
214 )
215 set_weight_attrs(self.conv1d.weight, {"weight_loader": conv1d_weight_loader})
216
217 self.A_log = nn.Parameter(
218 torch.empty(self.local_num_heads, dtype=torch.float32)
219 )
220 set_weight_attrs(self.A_log, {"weight_loader": a_log_weight_loader(0)})
221
222 self.gate_lower_bound: float | None = kda_config.get("gate_lower_bound", None)
223 if self.gate_lower_bound is not None:
224 assert _KDA_GATE_LOGBOUND_MIN <= self.gate_lower_bound < 0, (
225 "KDA gate lower bound must be in "
226 f"[{_KDA_GATE_LOGBOUND_MIN}, 0). "
227 f"Got {self.gate_lower_bound}."
228 )
229 self.use_safe_gate = self.gate_lower_bound is not None
230
231 additional_config = vllm_config.additional_config
232 backend = (
233 additional_config.get("kda_prefill_backend", "auto")
234 if isinstance(additional_config, dict)
235 else "auto"
236 )
237 assert backend in ("auto", "triton", "fused"), (
238 "The ROCm Kimi-K3 KDA prefill backend must be one of "
239 f"'auto', 'triton' or 'fused', got {backend!r}."
240 )
241 if backend == "fused" and not is_fused_kda_chunk_supported():
242 raise RuntimeError(
243 "The fused KDA chunk kernel requires gfx950 and a build that "
244 "includes it."
245 )
246 self.use_fused_chunk = backend == "fused" or (
247 backend == "auto" and is_fused_kda_chunk_supported()
248 )
249 logger.info_once(
250 "Kimi-K3 KDA prefill backend: %s",
251 "fused" if self.use_fused_chunk else "triton",
252 )
253 # TODO: Remove the extra config arg once tested
254 self.use_prefill_checkpoint = (
255 additional_config.get("kda_prefill_checkpoint", True)
256 if isinstance(additional_config, dict)
257 else True
258 )
259 self._checkpoint_exporter = KimiK3ROCmKDAPrefillCheckpointExporter(
260 state_len=self.conv_size - 1
261 )
262
263 self.o_norm = FusedRMSNormGated(self.head_dim, activation="sigmoid")
264 decode_norm_weight = None
265 if decode_conv1d_weight is not None:
266 # Upcast once at load time; a BF16 norm weight slows the fused
267 # decode kernel's epilogue.
268 decode_norm_weight = torch.empty(
269 self.head_dim,
270 dtype=torch.float32,
271 device=self.o_norm.weight.device,
272 )
273 if hasattr(self.o_norm.weight, "weight_loader"):
274 delattr(self.o_norm.weight, "weight_loader")
275 set_weight_attrs(
276 self.o_norm.weight,
277 {"weight_loader": make_decode_norm_weight_loader(decode_norm_weight)},
278 )
279 self.register_buffer("decode_norm_weight", decode_norm_weight, persistent=False)
280 self.o_proj = RowParallelLinear(
281 self.projection_size,
282 self.hidden_size,
283 bias=False,
284 quant_config=self.quant_config,
285 prefix=f"{prefix}.o_proj",
286 )
287
288 compilation_config = vllm_config.compilation_config
289 if prefix in compilation_config.static_forward_context:
290 raise ValueError(f"Duplicate layer name: {prefix}")
291 compilation_config.static_forward_context[prefix] = self
292
293 def rearrange_mixed_qkv(
294 self, mixed_qkv: torch.Tensor
295 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
296 seq_len = mixed_qkv.shape[0]
297 qkv = mixed_qkv.view(seq_len, 3, self.local_num_heads, self.head_dim)
298 # Materialize all three row-strided inputs with one token-major to
299 # QKV-major permutation. Each unbound tensor is then contiguous.
300 qkv = qkv.permute(1, 0, 2, 3).contiguous().unsqueeze(1)
301 return qkv.unbind(0)
302
303 def forward(
304 self,
305 hidden_states: torch.Tensor,
306 positions: torch.Tensor,
307 ) -> torch.Tensor:
308 num_tokens = hidden_states.size(0)
309 projected_qkvgfab = self.in_proj_qkvgfab(hidden_states)[0]
310
311 split_sizes = [
312 3 * self.local_projection_size,
313 self.local_projection_size,
314 self.head_dim,
315 self.local_num_heads,
316 ]
317 if self.in_proj_padding:
318 split_sizes.append(self.in_proj_padding)
319 projected = projected_qkvgfab.split(split_sizes, dim=-1)
320 mixed_qkv, g_proj_states, f_a, beta = projected[:4]
321
322 g1 = self.f_b_proj(f_a)[0]
323 beta = beta.unsqueeze(0)
324 g1 = rearrange(g1, "n (h d) -> 1 n h d", d=self.head_dim)
325 g2 = rearrange(g_proj_states, "... (h d) -> ... h d", d=self.head_dim)
326
327 core_attn_out = torch.empty(
328 (1, num_tokens, self.local_num_heads, self.head_dim),
329 dtype=hidden_states.dtype,
330 device=hidden_states.device,
331 )
332
333 self._forward(
334 mixed_qkv=mixed_qkv,
335 g1=g1,
336 g2=g2,
337 beta=beta,
338 core_attn_out=core_attn_out,
339 )
340 core_attn_out = rearrange(core_attn_out, "1 n h d -> n (h d)")
341 return self.o_proj(core_attn_out)[0]
342
343 @eager_break_during_capture
344 def _forward(
345 self,
346 mixed_qkv: torch.Tensor,
347 g1: torch.Tensor,
348 g2: torch.Tensor,
349 beta: torch.Tensor,
350 core_attn_out: torch.Tensor,
351 ) -> None:
352 forward_context = get_forward_context()
353 attn_metadata_raw = forward_context.attn_metadata
354
355 if attn_metadata_raw is None:
356 return
357
358 assert isinstance(attn_metadata_raw, dict)
359 attn_metadata_narrowed = attn_metadata_raw.get(self.prefix)
360 if attn_metadata_narrowed is None:
361 return
362 assert isinstance(attn_metadata_narrowed, GDNAttentionMetadata)
363 m = attn_metadata_narrowed
364 has_initial_state = m.has_initial_state
365 non_spec_query_start_loc = m.non_spec_query_start_loc
366 non_spec_state_indices_tensor = m.non_spec_state_indices_tensor
367 spec_sequence_masks = m.spec_sequence_masks
368 spec_token_indx = m.spec_token_indx
369 non_spec_token_indx = m.non_spec_token_indx
370 spec_state_indices_tensor = m.spec_state_indices_tensor
371 spec_query_start_loc = m.spec_query_start_loc
372 num_accepted_tokens = m.num_accepted_tokens
373 num_actual_tokens = m.num_actual_tokens
374 mixed_qkv = mixed_qkv[:num_actual_tokens]
375 g1 = g1[:, :num_actual_tokens]
376 beta = beta[:, :num_actual_tokens]
377
378 constant_caches = self.kv_cache
379
380 conv_state, recurrent_state = constant_caches
381 # conv_state must be (..., dim, width-1) for the conv kernels.
382 # DS layout stores it that way directly; SD layout needs a transpose.
383 if not is_conv_state_dim_first():
384 conv_state = conv_state.transpose(-1, -2)
385
386 if (
387 self.decode_conv1d_weight is not None
388 and self.decode_norm_weight is not None
389 and spec_sequence_masks is None
390 and m.num_prefills == 0
391 and m.num_decodes > 0
392 ):
393 assert non_spec_state_indices_tensor is not None
394 ops.fused_kda_decode(
395 x=mixed_qkv,
396 weight=self.decode_conv1d_weight,
397 bias=self.conv1d.bias,
398 conv_state=conv_state,
399 raw_g=g1,
400 raw_beta=beta,
401 A_log=self.A_log,
402 dt_bias=self.dt_bias,
403 state_indices=non_spec_state_indices_tensor[:num_actual_tokens],
404 state=recurrent_state,
405 out=core_attn_out[:, :num_actual_tokens],
406 lower_bound=self.gate_lower_bound,
407 output_gate=g2[:num_actual_tokens],
408 norm_weight=self.decode_norm_weight,
409 norm_eps=self.o_norm.eps,
410 )
411 return
412
413 conv_weights = self.conv1d.weight.view(
414 self.conv1d.weight.size(0), self.conv1d.weight.size(2)
415 )
416 q_conv_weight, k_conv_weight, v_conv_weight = conv_weights.split(
417 self.local_projection_size, dim=0
418 )
419 q_conv_state, k_conv_state, v_conv_state = conv_state.split(
420 self.local_projection_size, dim=-2
421 )
422
423 # Split tokens into the multi-query spec-decode part and the remaining
424 # (prefill / plain decode) part.
425 if spec_sequence_masks is not None:
426 if m.num_prefills == 0 and m.num_decodes == 0:
427 mixed_qkv_spec = mixed_qkv
428 g1_spec, beta_spec = g1, beta
429 mixed_qkv_ns = g1_ns = beta_ns = None
430 else:
431 mixed_qkv_spec = mixed_qkv.index_select(0, spec_token_indx)
432 g1_spec = g1.index_select(1, spec_token_indx)
433 beta_spec = beta.index_select(1, spec_token_indx)
434 mixed_qkv_ns = mixed_qkv.index_select(0, non_spec_token_indx)
435 g1_ns = g1.index_select(1, non_spec_token_indx)
436 beta_ns = beta.index_select(1, non_spec_token_indx)
437 else:
438 mixed_qkv_spec = g1_spec = beta_spec = None
439 mixed_qkv_ns, g1_ns, beta_ns = mixed_qkv, g1, beta
440
441 # ---------- spec-decode multi-query path ----------
442 core_attn_out_spec = None
443 if spec_sequence_masks is not None:
444 assert spec_state_indices_tensor is not None
445 assert spec_query_start_loc is not None
446 spec_conv_indices = spec_state_indices_tensor[:, 0][: m.num_spec_decodes]
447 spec_max_query_len = spec_state_indices_tensor.size(-1)
448
449 # Sibling beta and, for full-rank gates, output-gate views remain
450 # live, so write the convolution output separately.
451 spec_conv_out = torch.empty(
452 mixed_qkv_spec.shape,
453 dtype=mixed_qkv_spec.dtype,
454 device=mixed_qkv_spec.device,
455 )
456 mixed_qkv_spec = causal_conv1d_update(
457 mixed_qkv_spec,
458 conv_state,
459 conv_weights,
460 self.conv1d.bias,
461 activation="silu",
462 conv_state_indices=spec_conv_indices,
463 num_accepted_tokens=num_accepted_tokens,
464 query_start_loc=spec_query_start_loc,
465 max_query_len=spec_max_query_len,
466 validate_data=False,
467 out=spec_conv_out,
468 )
469 q_spec, k_spec, v_spec = (
470 rearrange(x, "n (h d) -> 1 n h d", d=self.head_dim)
471 for x in mixed_qkv_spec.split(self.local_projection_size, dim=-1)
472 )
473 spec_cu_seqlens = spec_query_start_loc[: m.num_spec_decodes + 1]
474 uniform_sequence_length = m.uniform_spec_sequence_length
475 # Token padding can make the packed tensor larger than the
476 # request-local metadata represented by this length.
477 if (
478 uniform_sequence_length is not None
479 and q_spec.shape[1] != m.num_spec_decodes * uniform_sequence_length
480 ):
481 uniform_sequence_length = None
482 # Spec-only batches write directly into core_attn_out.
483 spec_out = (
484 core_attn_out[:, : q_spec.shape[1]]
485 if m.num_prefills == 0 and m.num_decodes == 0
486 else None
487 )
488 core_attn_out_spec, _ = fused_recurrent_kda(
489 q=q_spec,
490 k=k_spec,
491 v=v_spec,
492 raw_g=g1_spec,
493 raw_beta=beta_spec,
494 A_log=self.A_log,
495 dt_bias=self.dt_bias,
496 lower_bound=self.gate_lower_bound,
497 initial_state=recurrent_state,
498 cu_seqlens=spec_cu_seqlens,
499 ssm_state_indices=spec_state_indices_tensor,
500 num_accepted_tokens=num_accepted_tokens,
501 uniform_sequence_length=uniform_sequence_length,
502 out=spec_out,
503 )
504
505 # ---------- non-spec path (prefill or plain decode) ----------
506 core_attn_out_non_spec = None
507 if mixed_qkv_ns is not None:
508 assert g1_ns is not None and beta_ns is not None
509 if m.num_prefills > 0:
510 q_ns, k_ns, v_ns = mixed_qkv_ns.split(
511 self.local_projection_size, dim=-1
512 )
513
514 # Packed prefill conv would require copying V solely to make
515 # it dense for KDA. Separate calls accept the strided inputs
516 # and produce dense Q/K/V without that extra traffic.
517 # TODO: Use packed conv once every KDA prefill backend accepts
518 # row-strided Q/K/V directly.
519 def _prefill_conv(
520 x: torch.Tensor,
521 state: torch.Tensor,
522 weight: torch.Tensor,
523 ) -> torch.Tensor:
524 return causal_conv1d_fn(
525 x.transpose(0, 1),
526 weight,
527 None,
528 activation="silu",
529 conv_states=state,
530 has_initial_state=has_initial_state,
531 cache_indices=non_spec_state_indices_tensor,
532 query_start_loc=non_spec_query_start_loc,
533 metadata=m,
534 ).transpose(0, 1)
535
536 q_ns = _prefill_conv(q_ns, q_conv_state, q_conv_weight)
537 k_ns = _prefill_conv(k_ns, k_conv_state, k_conv_weight)
538 v_ns = _prefill_conv(v_ns, v_conv_state, v_conv_weight)
539 q_ns, k_ns, v_ns = (
540 rearrange(x, "n (h d) -> 1 n h d", d=self.head_dim)
541 for x in (q_ns, k_ns, v_ns)
542 )
543
544 assert non_spec_state_indices_tensor is not None
545 assert has_initial_state is not None
546
547 # Decode-first, so peel the length-1 decodes to the recurrent
548 # kernel. A spec batch interleaves its non-spec rows, so the
549 # peel cannot apply and the chunk kernel takes them.
550 core_attn_out_decode = None
551 split_non_spec = spec_sequence_masks is None and m.num_decodes > 0
552 # Without a spec split the non-spec tokens are exactly
553 # core_attn_out[:, :num_actual_tokens] in order, so both kernels
554 # can write their slice straight into it. With one, the tokens
555 # are interleaved and have to be scattered by index afterwards.
556 direct = spec_sequence_masks is None
557 if split_non_spec:
558 assert non_spec_query_start_loc is not None
559 nd_tok = m.num_decode_tokens
560 core_attn_out_decode, _ = fused_recurrent_kda(
561 q=q_ns[:, :nd_tok],
562 k=k_ns[:, :nd_tok],
563 v=v_ns[:, :nd_tok],
564 raw_g=g1_ns[:, :nd_tok],
565 raw_beta=beta_ns[:, :nd_tok],
566 A_log=self.A_log,
567 dt_bias=self.dt_bias,
568 lower_bound=self.gate_lower_bound,
569 initial_state=recurrent_state,
570 cu_seqlens=non_spec_query_start_loc[: m.num_decodes + 1],
571 ssm_state_indices=non_spec_state_indices_tensor[
572 : m.num_decodes
573 ],
574 out=core_attn_out[:, :nd_tok] if direct else None,
575 )
576 q_ns = q_ns[:, nd_tok:]
577 k_ns = k_ns[:, nd_tok:]
578 v_ns = v_ns[:, nd_tok:]
579 g1_ns = g1_ns[:, nd_tok:]
580 beta_ns = beta_ns[:, nd_tok:]
581 prefill_query_start_loc = m.prefill_query_start_loc
582 prefill_state_indices = m.prefill_state_indices
583 prefill_has_initial_state = m.prefill_has_initial_state
584 assert prefill_query_start_loc is not None
585 assert prefill_state_indices is not None
586 assert prefill_has_initial_state is not None
587 else:
588 prefill_query_start_loc = non_spec_query_start_loc
589 prefill_state_indices = non_spec_state_indices_tensor
590 prefill_has_initial_state = has_initial_state
591 nd_tok = 0
592
593 checkpoint = getattr(m, "checkpoint", None)
594 checkpoint_state = None
595 checkpoint_offsets = None
596 checkpoint_state_indices = None
597 if checkpoint is not None:
598 assert prefill_query_start_loc is not None
599 checkpoint_state = recurrent_state
600 checkpoint_offsets = checkpoint.checkpoint_offsets
601 checkpoint_state_indices = checkpoint.state_indices
602 self._checkpoint_exporter.export(
603 checkpoint,
604 raw_qkv=mixed_qkv_ns[nd_tok:],
605 conv_state=conv_state,
606 cu_seqlens=prefill_query_start_loc,
607 )
608
609 (
610 core_attn_out_non_spec,
611 _,
612 ) = chunk_kda_prefill(
613 q=q_ns,
614 k=k_ns,
615 v=v_ns,
616 raw_g=g1_ns,
617 raw_beta=beta_ns,
618 A_log=self.A_log,
619 g_bias=self.dt_bias,
620 lower_bound=self.gate_lower_bound,
621 state_cache=recurrent_state,
622 state_indices=prefill_state_indices,
623 has_initial_state=prefill_has_initial_state,
624 use_qk_l2norm_in_kernel=True,
625 cu_seqlens=prefill_query_start_loc,
626 chunk_indices=m.chunk_indices,
627 chunk_offsets=m.chunk_offsets,
628 use_fused_chunk=self.use_fused_chunk,
629 out=core_attn_out[:, nd_tok:num_actual_tokens] if direct else None,
630 checkpoint_state=checkpoint_state,
631 checkpoint_offsets=checkpoint_offsets,
632 checkpoint_state_indices=checkpoint_state_indices,
633 )
634 # chunk_kda_prefill updates `recurrent_state` in place, so
635 # there is no final state to write back here.
636
637 if direct:
638 # Both slices already landed in core_attn_out.
639 core_attn_out_non_spec = core_attn_out[:, :num_actual_tokens]
640 elif split_non_spec:
641 # Restore decode-first token order for the merge below.
642 core_attn_out_non_spec = torch.cat(
643 [core_attn_out_decode, core_attn_out_non_spec], dim=1
644 )
645
646 else:
647 # pure-decode non-spec batch
648 assert non_spec_state_indices_tensor is not None
649 decode_conv_indices = non_spec_state_indices_tensor[
650 : mixed_qkv_ns.size(0)
651 ]
652 # Sibling beta and, for full-rank gates, output-gate views
653 # remain live, so write the conv output separately.
654 packed_conv_out = torch.empty(
655 mixed_qkv_ns.shape,
656 dtype=mixed_qkv_ns.dtype,
657 device=mixed_qkv_ns.device,
658 )
659 mixed_qkv_ns = causal_conv1d_update(
660 mixed_qkv_ns,
661 conv_state,
662 conv_weights,
663 self.conv1d.bias,
664 activation="silu",
665 conv_state_indices=decode_conv_indices,
666 validate_data=True,
667 out=packed_conv_out,
668 )
669 core_attn_out_non_spec, _ = fused_recurrent_kda_packed_decode(
670 mixed_qkv=mixed_qkv_ns,
671 raw_g=g1_ns,
672 raw_beta=beta_ns,
673 A_log=self.A_log,
674 dt_bias=self.dt_bias,
675 lower_bound=self.gate_lower_bound,
676 initial_state=recurrent_state,
677 state_indices=decode_conv_indices,
678 )
679
680 # ---------- merge spec and non-spec outputs ----------
681 if core_attn_out_spec is not None and core_attn_out_non_spec is not None:
682 # Mixed batches require indexed placement in the original order.
683 merged = torch.empty(
684 (1, num_actual_tokens, *core_attn_out_spec.shape[2:]),
685 dtype=core_attn_out_spec.dtype,
686 device=core_attn_out_spec.device,
687 )
688 merged.index_copy_(1, spec_token_indx, core_attn_out_spec)
689 merged.index_copy_(1, non_spec_token_indx, core_attn_out_non_spec)
690 core_attn_out[0, :num_actual_tokens] = merged[0, :num_actual_tokens]
691 elif core_attn_out_non_spec is not None:
692 if core_attn_out_non_spec.data_ptr() != core_attn_out.data_ptr():
693 core_attn_out[0, :num_actual_tokens] = core_attn_out_non_spec[
694 0, :num_actual_tokens
695 ]
696 else:
697 assert core_attn_out_spec is not None
698 core_attn_out.copy_(self.o_norm(core_attn_out, g2))