main
py 1,372 lines 52.6 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from collections.abc import Callable
5 from dataclasses import replace
6
7 import torch
8 from einops import rearrange
9 from torch import nn
10 from torch.nn.parameter import Parameter
11
12 from vllm import _custom_ops as ops
13 from vllm.compilation.breakable_cudagraph import eager_break_during_capture
14 from vllm.config import VllmConfig
15 from vllm.distributed import divide, get_tensor_model_parallel_rank
16 from vllm.forward_context import get_forward_context
17 from vllm.logger import init_logger
18 from vllm.model_executor.layers.linear import (
19 ColumnParallelLinear,
20 MergedColumnParallelLinear,
21 RowParallelLinear,
22 )
23 from vllm.model_executor.layers.mamba.gdn.base import GatedDeltaNetAttention
24 from vllm.model_executor.layers.mamba.kda_checkpoint import (
25 FlashKDAPrefillCheckpointExporter,
26 kda_prefill_checkpoint_alignment,
27 )
28 from vllm.model_executor.layers.mamba.mamba_utils import (
29 MambaStateDtypeCalculator,
30 MambaStateShapeCalculator,
31 is_conv_state_dim_first,
32 )
33 from vllm.model_executor.layers.mamba.ops.causal_conv1d import (
34 causal_conv1d_fn,
35 causal_conv1d_update,
36 )
37 from vllm.model_executor.layers.mamba.ops.gather_initial_states import (
38 gather_initial_states,
39 )
40 from vllm.model_executor.layers.quantization.modelopt import (
41 ModelOptMixedPrecisionConfig,
42 )
43 from vllm.model_executor.model_loader.weight_utils import (
44 default_weight_loader,
45 sharded_weight_loader,
46 )
47 from vllm.model_executor.parameter import BasevLLMParameter, BlockQuantScaleParameter
48 from vllm.model_executor.utils import set_weight_attrs
49 from vllm.models.kimi_k3.nvidia.kda_metadata import (
50 KimiK3KDAAttentionBackend,
51 KimiK3KDAMetadata,
52 )
53 from vllm.platforms import current_platform
54 from vllm.third_party.flash_linear_attention.ops.kda import FusedRMSNormGated
55 from vllm.transformers_utils.configs.kimi_linear import KimiLinearConfig
56 from vllm.utils.flashinfer import (
57 flashinfer_fused_kda_decode,
58 flashinfer_packed_fused_kda_decode,
59 flashinfer_recurrent_kda,
60 has_flashinfer_fused_kda_decode,
61 has_flashinfer_packed_fused_kda_decode,
62 has_flashinfer_recurrent_kda,
63 )
64 from vllm.v1.attention.backend import AttentionBackend
65 from vllm.v1.kv_cache_interface import MambaSpec
66 from vllm.v1.worker.workspace import current_workspace_manager
67
68 logger = init_logger(__name__)
69
70 _KDA_GATE_LOGBOUND_MIN = -5.0
71
72
73 def a_log_weight_loader(
74 shard_axis: int,
75 ) -> Callable[[torch.Tensor, torch.Tensor], None]:
76 """Load KDA A_log stored as either old 4D or current 1D weights."""
77
78 def loader(param: torch.Tensor, loaded_weight: torch.Tensor) -> None:
79 tp_rank = get_tensor_model_parallel_rank()
80 shard_size = param.data.shape[shard_axis]
81 start_idx = tp_rank * shard_size
82
83 if loaded_weight.dim() == 4:
84 assert loaded_weight.shape[:2] == (1, 1), (
85 f"Expected old A_log shape (1, 1, H, 1), got {loaded_weight.shape}"
86 )
87 assert loaded_weight.shape[-1] == 1, (
88 f"Expected old A_log last dim to be 1, got {loaded_weight.shape}"
89 )
90 loaded_weight = loaded_weight.view(loaded_weight.shape[2])
91
92 loaded_weight = loaded_weight.narrow(shard_axis, start_idx, shard_size)
93 return default_weight_loader(param, loaded_weight)
94
95 return loader
96
97
98 class _KimiGDNMergedColumnParallelLinear(MergedColumnParallelLinear):
99 """Merged projection with one output replicated across TP ranks."""
100
101 def __init__(
102 self,
103 input_size: int,
104 output_sizes: list[int],
105 replicated_shard_id: int,
106 tp_size: int,
107 **kwargs,
108 ) -> None:
109 self.replicated_shard_id = replicated_shard_id
110 output_sizes = output_sizes.copy()
111 output_sizes[replicated_shard_id] *= tp_size
112 super().__init__(input_size, output_sizes, **kwargs)
113
114 def weight_loader(
115 self,
116 param: Parameter,
117 loaded_weight: torch.Tensor,
118 loaded_shard_id: tuple[int, ...] | int | None = None,
119 ) -> None:
120 tp_rank = self.tp_rank
121 param_tp_rank = getattr(param, "tp_rank", None)
122 replicate_block_scale = (
123 isinstance(param, BlockQuantScaleParameter)
124 and loaded_weight.shape[param.output_dim] < self.tp_size
125 )
126 if loaded_shard_id == self.replicated_shard_id or replicate_block_scale:
127 self.tp_rank = 0
128 if param_tp_rank is not None:
129 param.tp_rank = 0
130 try:
131 super().weight_loader(param, loaded_weight, loaded_shard_id)
132 finally:
133 self.tp_rank = tp_rank
134 if param_tp_rank is not None:
135 param.tp_rank = param_tp_rank
136
137 def weight_loader_v2(
138 self,
139 param: BasevLLMParameter,
140 loaded_weight: torch.Tensor,
141 loaded_shard_id: tuple[int, ...] | int | None = None,
142 ) -> None:
143 tp_rank = self.tp_rank
144 param_tp_rank = getattr(param, "tp_rank", None)
145 replicate_block_scale = (
146 isinstance(param, BlockQuantScaleParameter)
147 and loaded_weight.shape[param.output_dim] < self.tp_size
148 )
149 if loaded_shard_id == self.replicated_shard_id or replicate_block_scale:
150 self.tp_rank = 0
151 if param_tp_rank is not None:
152 param.tp_rank = 0
153 try:
154 super().weight_loader_v2(param, loaded_weight, loaded_shard_id)
155 finally:
156 self.tp_rank = tp_rank
157 if param_tp_rank is not None:
158 param.tp_rank = param_tp_rank
159
160
161 def is_fused_kda_decode_supported(
162 num_heads: int,
163 head_dim: int,
164 conv_width: int,
165 num_spec: int,
166 input_dtype: torch.dtype,
167 conv_state_dtype: torch.dtype,
168 recurrent_state_dtype: torch.dtype,
169 ) -> bool:
170 # The fused kernel handles both conv-state cache layouts (SD and DS); the
171 # inner strides are selected from the tensor at launch time.
172 if (
173 num_heads not in (12, 24, 48, 96)
174 or head_dim != 128
175 or conv_width != 4
176 or num_spec != 0
177 or input_dtype != torch.bfloat16
178 or conv_state_dtype != torch.bfloat16
179 or recurrent_state_dtype != torch.float32
180 or not hasattr(torch.ops._C, "fused_kda_decode")
181 ):
182 return False
183 # SM90 is architecture-specific; SM10x and SM12x use family binaries.
184 return (
185 current_platform.is_device_capability(90)
186 or current_platform.is_device_capability_family(100)
187 or current_platform.is_device_capability_family(120)
188 )
189
190
191 def is_flashinfer_fused_kda_decode_supported(
192 num_heads: int,
193 head_dim: int,
194 conv_width: int,
195 num_spec: int,
196 input_dtype: torch.dtype,
197 conv_state_dtype: torch.dtype,
198 recurrent_state_dtype: torch.dtype,
199 ) -> bool:
200 if not has_flashinfer_fused_kda_decode():
201 return False
202 capability = current_platform.get_device_capability()
203 if capability is None:
204 return False
205 compute_capability = (capability.major, capability.minor)
206 return (
207 compute_capability in ((10, 0), (10, 3))
208 and num_heads in (12, 24, 32, 48, 96)
209 and head_dim == 128
210 and conv_width == 4
211 and num_spec == 0
212 and input_dtype == torch.bfloat16
213 and conv_state_dtype == torch.bfloat16
214 and recurrent_state_dtype in (torch.float32, torch.bfloat16)
215 and not is_conv_state_dim_first()
216 )
217
218
219 def is_flashinfer_fused_kda_spec_decode_supported(
220 num_heads: int,
221 head_dim: int,
222 conv_width: int,
223 num_spec: int,
224 input_dtype: torch.dtype,
225 conv_state_dtype: torch.dtype,
226 recurrent_state_dtype: torch.dtype,
227 lower_bound: float | None,
228 use_recoverssm: bool,
229 ) -> bool:
230 return (
231 has_flashinfer_packed_fused_kda_decode()
232 and current_platform.is_device_capability_family(100)
233 and num_heads in (12, 24, 32, 48, 96)
234 and head_dim == 128
235 and conv_width == 4
236 and 1 <= num_spec <= 7
237 and input_dtype == torch.bfloat16
238 and conv_state_dtype == torch.bfloat16
239 and recurrent_state_dtype in (torch.float32, torch.bfloat16)
240 and lower_bound is not None
241 and lower_bound < 0
242 and not use_recoverssm
243 and not is_conv_state_dim_first()
244 )
245
246
247 def resolve_kda_spec_decode_backend(
248 backend: str,
249 num_heads: int,
250 head_dim: int,
251 conv_width: int,
252 num_spec: int,
253 input_dtype: torch.dtype,
254 conv_state_dtype: torch.dtype,
255 recurrent_state_dtype: torch.dtype,
256 lower_bound: float | None,
257 use_recoverssm: bool,
258 ) -> str:
259 if backend not in ("auto", "native", "flashinfer"):
260 raise ValueError(f"Unsupported KDA speculative decode backend: {backend}")
261 supported = is_flashinfer_fused_kda_spec_decode_supported(
262 num_heads,
263 head_dim,
264 conv_width,
265 num_spec,
266 input_dtype,
267 conv_state_dtype,
268 recurrent_state_dtype,
269 lower_bound,
270 use_recoverssm,
271 )
272 if backend == "flashinfer" and not supported:
273 raise RuntimeError(
274 "FlashInfer packed fused KDA decode requires its "
275 "packed_fused_kda_decode API, CUDA SM10x, bfloat16 inputs and "
276 "convolution state, float32 or bfloat16 recurrent state, D=128, "
277 "W=4, a bounded gate, 1-7 speculative tokens, a supported head "
278 "count, the SD convolution-state layout, and RecoverSSM disabled."
279 )
280 if supported and backend != "native":
281 logger.info_once("Using FlashInfer packed fused KDA speculative decode.")
282 return "flashinfer"
283 return "native"
284
285
286 def resolve_kda_decode_backend(
287 backend: str,
288 num_heads: int,
289 head_dim: int,
290 conv_width: int,
291 num_spec: int,
292 input_dtype: torch.dtype,
293 conv_state_dtype: torch.dtype,
294 recurrent_state_dtype: torch.dtype,
295 ) -> str:
296 if backend not in ("auto", "native", "flashinfer", "triton"):
297 raise ValueError(f"Unsupported KDA decode backend: {backend}")
298 if backend == "triton":
299 return "triton"
300
301 native_supported = is_fused_kda_decode_supported(
302 num_heads,
303 head_dim,
304 conv_width,
305 num_spec,
306 input_dtype,
307 conv_state_dtype,
308 recurrent_state_dtype,
309 )
310 if native_supported and backend in ("auto", "native"):
311 logger.info_once("Using native fused KDA decode backend.")
312 return "native"
313 if backend == "native":
314 raise RuntimeError(
315 "Native fused KDA decode requires a supported CUDA architecture, "
316 "bfloat16 activations and convolution state, float32 recurrent "
317 "state, head_dim=128, convolution width 4, and no speculation."
318 )
319
320 flashinfer_supported = is_flashinfer_fused_kda_decode_supported(
321 num_heads,
322 head_dim,
323 conv_width,
324 num_spec,
325 input_dtype,
326 conv_state_dtype,
327 recurrent_state_dtype,
328 )
329 if flashinfer_supported and backend == "flashinfer":
330 logger.info_once("Using FlashInfer fused KDA decode backend.")
331 return "flashinfer"
332 if backend == "flashinfer":
333 raise RuntimeError(
334 "FlashInfer fused KDA decode requires CUDA SM100 or SM103, "
335 "bfloat16 activations and convolution state, bfloat16 or float32 "
336 "recurrent state, head_dim=128, convolution width 4, no speculation."
337 )
338 return "triton"
339
340
341 def is_flashkda_supported(
342 head_dim: int,
343 input_dtype: torch.dtype,
344 recurrent_state_dtype: torch.dtype,
345 lower_bound: float | None,
346 ) -> bool:
347 if not current_platform.is_cuda():
348 return False
349 capability = current_platform.get_device_capability()
350 return (
351 capability is not None
352 and capability.major in (9, 10, 12)
353 and head_dim == 128
354 and input_dtype == torch.bfloat16
355 and recurrent_state_dtype in (torch.bfloat16, torch.float32)
356 and lower_bound is not None
357 )
358
359
360 def is_flashinfer_recurrent_kda_prefill_supported(
361 head_dim: int,
362 input_dtype: torch.dtype,
363 recurrent_state_dtype: torch.dtype,
364 lower_bound: float | None,
365 ) -> bool:
366 if not current_platform.is_cuda() or not has_flashinfer_recurrent_kda():
367 return False
368 capability = current_platform.get_device_capability()
369 if capability is None:
370 return False
371 compute_capability = (capability.major, capability.minor)
372 return (
373 compute_capability in ((10, 0), (10, 3))
374 and head_dim == 128
375 and input_dtype == torch.bfloat16
376 and recurrent_state_dtype == torch.bfloat16
377 and lower_bound is not None
378 )
379
380
381 def _flashkda_prefill(
382 q: torch.Tensor,
383 k: torch.Tensor,
384 v: torch.Tensor,
385 g: torch.Tensor,
386 beta: torch.Tensor,
387 A_log: torch.Tensor,
388 dt_bias: torch.Tensor,
389 lower_bound: float,
390 initial_state: torch.Tensor,
391 cu_seqlens: torch.Tensor,
392 out: torch.Tensor,
393 final_state: torch.Tensor,
394 workspace: torch.Tensor,
395 checkpoint_state: torch.Tensor | None = None,
396 checkpoint_offsets: torch.Tensor | None = None,
397 ) -> tuple[torch.Tensor, torch.Tensor]:
398 import vllm._flashkda_C # noqa: F401
399
400 # FlashKDA hardcodes dense Q/K/V/G strides. Beta may be row-strided because
401 # FlashKDA materializes its transposed [H, T] layout internally.
402 # TODO: Teach FlashKDA to consume beta in [T, H] layout directly instead
403 # of transposing it to contiguous [H, T] storage internally.
404 torch.ops._flashkda_C.fwd(
405 q.contiguous(),
406 k.contiguous(),
407 v.contiguous(),
408 g.contiguous(),
409 beta,
410 q.shape[-1] ** -0.5,
411 out,
412 workspace,
413 A_log.contiguous(),
414 dt_bias.view(-1, q.shape[-1]).contiguous(),
415 lower_bound,
416 initial_state.contiguous(),
417 final_state,
418 cu_seqlens.contiguous(),
419 checkpoint_state,
420 checkpoint_offsets.contiguous() if checkpoint_offsets is not None else None,
421 )
422 return out, final_state
423
424
425 def _flashinfer_kda_prefill(
426 q: torch.Tensor,
427 k: torch.Tensor,
428 v: torch.Tensor,
429 raw_g: torch.Tensor,
430 raw_beta: torch.Tensor,
431 A_log: torch.Tensor,
432 dt_bias: torch.Tensor,
433 lower_bound: float,
434 initial_state: torch.Tensor,
435 cu_seqlens: torch.Tensor,
436 out: torch.Tensor,
437 seq_order: torch.Tensor | None = None,
438 prefill_workspace: object | None = None,
439 ) -> tuple[torch.Tensor, torch.Tensor]:
440 output, _ = flashinfer_recurrent_kda(
441 q=q.contiguous(),
442 k=k.contiguous(),
443 v=v.contiguous(),
444 g=raw_g.contiguous(),
445 beta=raw_beta.contiguous(),
446 A_log=A_log.contiguous(),
447 dt_bias=dt_bias.contiguous(),
448 scale=q.shape[-1] ** -0.5,
449 initial_state=initial_state.contiguous(),
450 output_final_state=False,
451 use_qk_l2norm_in_kernel=True,
452 use_gate_in_kernel=True,
453 lower_bound=lower_bound,
454 cu_seqlens=cu_seqlens.contiguous(),
455 output=out,
456 beta_is_logit=True,
457 seq_order=seq_order,
458 prefill_workspace=prefill_workspace,
459 )
460 return output, initial_state
461
462
463 def resolve_kda_prefill_backend(
464 backend: str,
465 head_dim: int,
466 input_dtype: torch.dtype,
467 recurrent_state_dtype: torch.dtype,
468 lower_bound: float | None,
469 ) -> str:
470 if backend not in ("auto", "triton", "flashkda", "flashinfer"):
471 raise ValueError(f"Unsupported KDA prefill backend: {backend}")
472 flashinfer_supported = is_flashinfer_recurrent_kda_prefill_supported(
473 head_dim,
474 input_dtype,
475 recurrent_state_dtype,
476 lower_bound,
477 )
478 flashkda_supported = is_flashkda_supported(
479 head_dim,
480 input_dtype,
481 recurrent_state_dtype,
482 lower_bound,
483 )
484 if backend == "flashinfer" and not flashinfer_supported:
485 raise RuntimeError(
486 "FlashInfer KDA prefill requires CUDA SM100 or SM103, bfloat16 "
487 "activations and recurrent state, head_dim=128, a bounded KDA "
488 "gate, and flashinfer-python 0.6.18 or newer."
489 )
490 if backend == "flashkda" and not flashkda_supported:
491 raise RuntimeError(
492 "FlashKDA requires CUDA SM90/SM10x/SM12x, bfloat16 input, "
493 "bfloat16 or float32 recurrent state, head_dim=128, and a bounded "
494 "KDA gate."
495 )
496 if flashinfer_supported and backend == "flashinfer":
497 logger.info_once("Using FlashInfer KDA prefill backend.")
498 return "flashinfer"
499 if flashkda_supported and backend in ("auto", "flashkda"):
500 logger.info_once("Using FlashKDA KDA prefill backend.")
501 return "flashkda"
502 return "triton"
503
504
505 def _make_decode_conv1d_weight_loader(
506 dims: list[int],
507 tp_size: int,
508 tp_rank: int,
509 decode_conv1d_weight: torch.Tensor | None,
510 ) -> Callable[..., None]:
511 sharded_dims = [dim // tp_size for dim in dims]
512
513 def weight_loader(
514 param: torch.Tensor,
515 loaded_weight: torch.Tensor,
516 loaded_shard_id: int,
517 ) -> None:
518 if loaded_weight.dim() == 2:
519 loaded_weight = loaded_weight.unsqueeze(1)
520 shard_size = sharded_dims[loaded_shard_id]
521 source_start = tp_rank * shard_size
522 target_start = sum(sharded_dims[:loaded_shard_id])
523 loaded_shard = loaded_weight[source_start : source_start + shard_size]
524 param.data[target_start : target_start + shard_size].copy_(loaded_shard)
525 if decode_conv1d_weight is not None and not param.is_meta:
526 decode_conv1d_weight[loaded_shard_id].copy_(
527 loaded_shard.squeeze(1).transpose(0, 1)
528 )
529
530 return weight_loader
531
532
533 def _make_decode_norm_weight_loader(
534 decode_norm_weight: torch.Tensor,
535 ) -> Callable[..., None]:
536 def weight_loader(param: torch.Tensor, loaded_weight: torch.Tensor) -> None:
537 default_weight_loader(param, loaded_weight)
538 if not param.is_meta:
539 decode_norm_weight.copy_(param.data)
540
541 return weight_loader
542
543
544 class KimiK3DeltaAttention(GatedDeltaNetAttention):
545 def get_attn_backend(self) -> type[AttentionBackend]:
546 return KimiK3KDAAttentionBackend
547
548 def get_state_dtype(
549 self,
550 ) -> tuple[torch.dtype, ...]:
551 if self.model_config is None or self.cache_config is None:
552 raise ValueError("model_config and cache_config must be set")
553 base_dtypes = MambaStateDtypeCalculator.kda_state_dtype(
554 self.model_config.dtype,
555 self.cache_config.mamba_cache_dtype,
556 self.cache_config.mamba_ssm_cache_dtype,
557 )
558 if self.cache_config.use_kda_recoverssm:
559 return MambaStateDtypeCalculator.append_kda_recoverssm_record(
560 base_dtypes, self.model_config.dtype
561 )
562 return base_dtypes
563
564 def get_state_shape(
565 self,
566 ) -> tuple[tuple[int, ...], ...]:
567 base_shapes = MambaStateShapeCalculator.kda_state_shape(
568 self.tp_size,
569 self.num_heads,
570 self.head_dim,
571 conv_kernel_size=self.conv_size,
572 num_spec=self.num_spec,
573 )
574 if self.cache_config.use_kda_recoverssm:
575 return MambaStateShapeCalculator.append_kda_recoverssm_record(
576 base_shapes,
577 self.num_heads,
578 self.head_dim,
579 tp_world_size=self.tp_size,
580 spec_query_len=1 + self.num_spec,
581 )
582 return base_shapes
583
584 def __init__(
585 self,
586 config: KimiLinearConfig,
587 vllm_config: VllmConfig,
588 prefix: str = "",
589 aux_stream: torch.cuda.Stream | None = None,
590 run_gemm_rs_ar: bool = False,
591 ) -> None:
592 super().__init__(config, vllm_config, prefix)
593 self.use_recoverssm = self.cache_config.use_kda_recoverssm
594 if self.cache_config.use_replayssm and not self.use_recoverssm:
595 raise ValueError(
596 "Kimi-K3 supports --use-replayssm only with speculative decoding"
597 )
598 self.spec_query_len = 1 + self.num_spec
599
600 kda_config = config.linear_attn_config # type: ignore[attr-defined]
601 assert kda_config is not None, "linear_attn_config must be set"
602 self.head_dim = kda_config["head_dim"]
603 self.num_heads = kda_config["num_heads"]
604 assert self.num_heads % self.tp_size == 0
605 self.local_num_heads = divide(self.num_heads, self.tp_size)
606 self.projection_size = self.head_dim * self.num_heads
607 self.local_projection_size = divide(self.projection_size, self.tp_size)
608 self.conv_size = kda_config["short_conv_kernel_size"]
609 self.gate_lower_bound: float | None = kda_config.get("gate_lower_bound", None)
610 if self.gate_lower_bound is not None:
611 assert _KDA_GATE_LOGBOUND_MIN <= self.gate_lower_bound < 0, (
612 "KDA gate lower bound must be in "
613 f"[{_KDA_GATE_LOGBOUND_MIN}, 0). "
614 f"Got {self.gate_lower_bound}."
615 )
616 assert kda_config.get("use_full_rank_gate", False), (
617 "KimiK3DeltaAttention requires a full-rank gate"
618 )
619 self._projection_aux_stream = aux_stream
620 self._projection_events = (
621 (torch.cuda.Event(), torch.cuda.Event()) if aux_stream is not None else None
622 )
623 self._projection_overlap_max_tokens = 0
624
625 # Keep f_a before the narrow beta shard, then align each TP-local row.
626 qkvg_output_sizes = [self.projection_size] * 4
627 in_proj_output_sizes = qkvg_output_sizes + [
628 self.head_dim,
629 self.num_heads,
630 ]
631 local_output_size = (
632 4 * self.local_projection_size + self.head_dim + self.local_num_heads
633 )
634 in_proj_prefix = f"{prefix}.in_proj_qkvgfab"
635 alignment = (
636 128
637 if isinstance(self.quant_config, ModelOptMixedPrecisionConfig)
638 and self.quant_config._resolve_quant_algo(in_proj_prefix) == "FP8_PB_WO"
639 else 16
640 )
641 self.in_proj_padding = -local_output_size % alignment
642 if self.in_proj_padding:
643 in_proj_output_sizes.append(self.in_proj_padding * self.tp_size)
644 self.in_proj_qkvgfab = _KimiGDNMergedColumnParallelLinear(
645 self.hidden_size,
646 in_proj_output_sizes,
647 replicated_shard_id=4,
648 tp_size=self.tp_size,
649 bias=False,
650 quant_config=self.quant_config,
651 prefix=in_proj_prefix,
652 )
653 if self.in_proj_padding:
654 self.in_proj_qkvgfab.weight.data[-self.in_proj_padding :].zero_()
655
656 self.f_b_proj = ColumnParallelLinear(
657 self.head_dim,
658 self.projection_size,
659 bias=False,
660 quant_config=self.quant_config,
661 prefix=f"{prefix}.f_b_proj",
662 )
663 self.dt_bias = nn.Parameter(
664 torch.empty(self.local_projection_size, dtype=torch.float32)
665 )
666 set_weight_attrs(self.dt_bias, {"weight_loader": sharded_weight_loader(0)})
667
668 # One packed parameter and cache let decode run a single conv update.
669 # Prefill slices them back into Q/K/V to obtain dense outputs cheaply.
670 self.conv1d = ColumnParallelLinear(
671 input_size=self.conv_size,
672 output_size=3 * self.projection_size,
673 bias=False,
674 params_dtype=torch.float32,
675 prefix=f"{prefix}.conv1d",
676 )
677 self.conv1d.weight.data = self.conv1d.weight.data.unsqueeze(1)
678 # Keep a width-major copy for fused decode without changing the layout
679 # consumed by the prefill and fallback decode kernels.
680 conv_state_dtype, recurrent_state_dtype = self.get_state_dtype()[:2]
681 additional_config = vllm_config.additional_config
682 decode_backend = (
683 additional_config.get("kda_decode_backend", "auto")
684 if isinstance(additional_config, dict)
685 else "auto"
686 )
687 self.kda_decode_backend = resolve_kda_decode_backend(
688 decode_backend,
689 self.local_num_heads,
690 self.head_dim,
691 self.conv_size,
692 self.num_spec,
693 vllm_config.model_config.dtype,
694 conv_state_dtype,
695 recurrent_state_dtype,
696 )
697 spec_decode_backend = (
698 additional_config.get("kda_spec_decode_backend", "auto")
699 if isinstance(additional_config, dict)
700 else "auto"
701 )
702 self.kda_spec_decode_backend = resolve_kda_spec_decode_backend(
703 spec_decode_backend,
704 self.local_num_heads,
705 self.head_dim,
706 self.conv_size,
707 self.num_spec,
708 vllm_config.model_config.dtype,
709 conv_state_dtype,
710 recurrent_state_dtype,
711 self.gate_lower_bound,
712 self.use_recoverssm,
713 )
714 decode_conv1d_weight = None
715 if (
716 self.kda_decode_backend != "triton"
717 or self.kda_spec_decode_backend == "flashinfer"
718 ):
719 decode_conv1d_weight = torch.empty(
720 3,
721 self.conv_size,
722 self.local_projection_size,
723 dtype=self.conv1d.weight.dtype,
724 device=self.conv1d.weight.device,
725 )
726 self.register_buffer(
727 "decode_conv1d_weight", decode_conv1d_weight, persistent=False
728 )
729 delattr(self.conv1d.weight, "weight_loader")
730 set_weight_attrs(
731 self.conv1d.weight,
732 {
733 "weight_loader": _make_decode_conv1d_weight_loader(
734 [self.projection_size] * 3,
735 self.tp_size,
736 self.tp_rank,
737 decode_conv1d_weight,
738 )
739 },
740 )
741
742 self.A_log = nn.Parameter(
743 torch.empty(self.local_num_heads, dtype=torch.float32)
744 )
745 set_weight_attrs(self.A_log, {"weight_loader": a_log_weight_loader(0)})
746
747 backend = (
748 additional_config.get("kda_prefill_backend", "auto")
749 if isinstance(additional_config, dict)
750 else "auto"
751 )
752 self.kda_prefill_backend = resolve_kda_prefill_backend(
753 backend,
754 self.head_dim,
755 vllm_config.model_config.dtype,
756 recurrent_state_dtype,
757 self.gate_lower_bound,
758 )
759 self._flashkda_buffer_specs: (
760 tuple[tuple[tuple[int, ...], torch.dtype], ...] | None
761 ) = None
762 self._flashinfer_kda_output_spec: tuple[tuple[int, ...], torch.dtype] | None = (
763 None
764 )
765 self._checkpoint_exporter: FlashKDAPrefillCheckpointExporter | None = None
766 if self.kda_prefill_backend == "flashkda":
767 T = vllm_config.scheduler_config.max_num_batched_tokens
768 N = vllm_config.scheduler_config.max_num_seqs
769 H, D = self.local_num_heads, self.head_dim
770 import vllm._flashkda_C # noqa: F401
771
772 workspace_size = torch.ops._flashkda_C.get_workspace_size(T, H, N)
773 self._flashkda_buffer_specs = (
774 ((1, T, H, D), self.model_config.dtype),
775 ((N, H, D, D), self.get_state_dtype()[1]),
776 ((N, H, D, D), self.get_state_dtype()[1]),
777 ((workspace_size,), torch.uint8),
778 )
779 self._checkpoint_exporter = FlashKDAPrefillCheckpointExporter()
780 elif self.kda_prefill_backend == "flashinfer":
781 T = vllm_config.scheduler_config.max_num_batched_tokens
782 H, D = self.local_num_heads, self.head_dim
783 self._flashinfer_kda_output_spec = (
784 (1, T, H, D),
785 self.model_config.dtype,
786 )
787
788 self.o_norm = FusedRMSNormGated(self.head_dim, activation="sigmoid")
789 decode_norm_weight = None
790 if decode_conv1d_weight is not None:
791 decode_norm_weight = torch.empty(
792 self.head_dim,
793 dtype=torch.float32,
794 device=self.o_norm.weight.device,
795 )
796 self.register_buffer("decode_norm_weight", decode_norm_weight, persistent=False)
797 if decode_norm_weight is not None:
798 # Upcast once while loading; direct BF16 norm weights slow the
799 # fully fused decode kernel.
800 if hasattr(self.o_norm.weight, "weight_loader"):
801 delattr(self.o_norm.weight, "weight_loader")
802 set_weight_attrs(
803 self.o_norm.weight,
804 {"weight_loader": _make_decode_norm_weight_loader(decode_norm_weight)},
805 )
806 self.o_proj = RowParallelLinear(
807 self.projection_size,
808 self.hidden_size,
809 bias=False,
810 quant_config=self.quant_config,
811 prefix=f"{prefix}.o_proj",
812 )
813 self.gemm_rs_ar = None
814 if run_gemm_rs_ar:
815 from vllm.model_executor.kernels.linear.cute_dsl.gemm_rs_ar import (
816 get_gemm_rs_ar,
817 )
818
819 gemm_rs_ar = get_gemm_rs_ar()
820 if gemm_rs_ar.can_run(self.o_proj):
821 self.gemm_rs_ar = gemm_rs_ar
822 else:
823 gemm_rs_ar.warn_incompatible_projection()
824 compilation_config = vllm_config.compilation_config
825 if prefix in compilation_config.static_forward_context:
826 raise ValueError(f"Duplicate layer name: {prefix}")
827 compilation_config.static_forward_context[prefix] = self
828
829 def get_kv_cache_spec(self, vllm_config: VllmConfig) -> MambaSpec:
830 spec = super().get_kv_cache_spec(vllm_config)
831 assert isinstance(spec, MambaSpec)
832 alignment = kda_prefill_checkpoint_alignment(self.kda_prefill_backend)
833 return replace(
834 spec,
835 num_prefill_checkpoint_blocks=int(alignment is not None),
836 prefill_checkpoint_alignment=alignment,
837 )
838
839 def forward(
840 self,
841 hidden_states: torch.Tensor,
842 positions: torch.Tensor,
843 ) -> torch.Tensor:
844 num_tokens = hidden_states.size(0)
845 projection_events = self._projection_events
846 projection_aux_stream = self._projection_aux_stream
847 if (
848 0 < num_tokens <= self._projection_overlap_max_tokens
849 and hidden_states.stride() == (self.hidden_size, 1)
850 and projection_events is not None
851 and projection_aux_stream is not None
852 and torch.cuda.is_current_stream_capturing()
853 ):
854 from vllm.models.kimi_k3.nvidia.low_latency_gemm import (
855 run_kda_projection_overlap,
856 )
857
858 projected_qkvg, g1, beta = run_kda_projection_overlap(
859 hidden_states,
860 self.in_proj_qkvgfab.weight,
861 self.f_b_proj.weight,
862 projection_aux_stream,
863 projection_events,
864 )
865 mixed_qkv, g_proj_states = projected_qkvg.split(
866 [3 * self.local_projection_size, self.local_projection_size],
867 dim=-1,
868 )
869 else:
870 projected_qkvgfab = self.in_proj_qkvgfab(hidden_states)[0]
871 split_sizes = [
872 3 * self.local_projection_size,
873 self.local_projection_size,
874 self.head_dim,
875 self.local_num_heads,
876 ]
877 if self.in_proj_padding:
878 split_sizes.append(self.in_proj_padding)
879 projected = projected_qkvgfab.split(split_sizes, dim=-1)
880 mixed_qkv, g_proj_states, f_a, beta = projected[:4]
881 g1 = self.f_b_proj(f_a)[0]
882
883 beta = beta.unsqueeze(0)
884 g1 = rearrange(g1, "n (h d) -> 1 n h d", d=self.head_dim)
885 g2 = rearrange(g_proj_states, "... (h d) -> ... h d", d=self.head_dim)
886 core_attn_out = torch.empty(
887 (1, num_tokens, self.local_num_heads, self.head_dim),
888 dtype=hidden_states.dtype,
889 device=hidden_states.device,
890 )
891 self._forward(
892 mixed_qkv=mixed_qkv,
893 g1=g1,
894 g2=g2,
895 beta=beta,
896 core_attn_out=core_attn_out,
897 )
898 core_attn_out = rearrange(core_attn_out, "1 n h d -> n (h d)")
899 if self.gemm_rs_ar is not None and self.gemm_rs_ar.should_run(core_attn_out):
900 return self.gemm_rs_ar.apply(core_attn_out, self.o_proj)
901 return self.o_proj(core_attn_out)[0]
902
903 @eager_break_during_capture
904 def _forward(
905 self,
906 mixed_qkv: torch.Tensor,
907 g1: torch.Tensor,
908 g2: torch.Tensor,
909 beta: torch.Tensor,
910 core_attn_out: torch.Tensor,
911 ) -> None:
912 forward_context = get_forward_context()
913 attn_metadata_raw = forward_context.attn_metadata
914 if attn_metadata_raw is None:
915 return
916
917 from vllm.models.kimi_k3.nvidia.ops.third_party.kda import (
918 chunk_kda_with_fused_gate,
919 fused_recurrent_kda,
920 fused_recurrent_kda_packed_decode,
921 )
922
923 assert isinstance(attn_metadata_raw, dict)
924 attn_metadata_narrowed = attn_metadata_raw.get(self.prefix)
925 if attn_metadata_narrowed is None:
926 return
927 assert isinstance(attn_metadata_narrowed, KimiK3KDAMetadata)
928 m = attn_metadata_narrowed
929 has_initial_state = m.has_initial_state
930 non_spec_query_start_loc = m.non_spec_query_start_loc
931 non_spec_state_indices_tensor = m.non_spec_state_indices_tensor
932 spec_token_indx = m.spec_token_indx
933 non_spec_token_indx = m.non_spec_token_indx
934 spec_token_start = m.spec_token_start
935 non_spec_token_start = m.non_spec_token_start
936 spec_state_indices_tensor = m.spec_state_indices_tensor
937 spec_query_start_loc = m.spec_query_start_loc
938 num_accepted_tokens = m.num_accepted_tokens
939 num_actual_tokens = m.num_actual_tokens
940 checkpoint = m.checkpoint
941 has_spec_decode = m.num_spec_decodes > 0
942 mixed_qkv = mixed_qkv[:num_actual_tokens]
943 g1 = g1[:, :num_actual_tokens]
944 beta = beta[:, :num_actual_tokens]
945
946 conv_state, recurrent_state, *recoverssm_records = self.kv_cache
947 # The convolution kernels consume (..., dim, width - 1).
948 if not is_conv_state_dim_first():
949 conv_state = conv_state.transpose(-1, -2)
950
951 if (
952 self.kda_spec_decode_backend == "flashinfer"
953 and has_spec_decode
954 and m.num_prefills == 0
955 and m.num_decodes == 0
956 ):
957 assert self.decode_conv1d_weight is not None
958 assert self.decode_norm_weight is not None
959 assert spec_state_indices_tensor is not None
960 assert spec_query_start_loc is not None
961 assert num_accepted_tokens is not None
962 assert self.gate_lower_bound is not None
963 # "Packed" is FlashInfer's name for the varlen T>1 speculative path.
964 flashinfer_packed_fused_kda_decode(
965 x=mixed_qkv,
966 weight=self.decode_conv1d_weight,
967 conv_state=conv_state,
968 raw_gate=g1,
969 raw_beta=beta,
970 A_log=self.A_log,
971 dt_bias=self.dt_bias,
972 state_indices=spec_state_indices_tensor,
973 query_start_loc=spec_query_start_loc,
974 num_accepted_tokens=num_accepted_tokens,
975 state=recurrent_state,
976 output_gate=g2[:num_actual_tokens],
977 norm_weight=self.decode_norm_weight,
978 lower_bound=self.gate_lower_bound,
979 norm_eps=self.o_norm.eps,
980 output=core_attn_out[:, :num_actual_tokens],
981 )
982 return
983
984 if (
985 self.kda_decode_backend != "triton"
986 and self.decode_conv1d_weight is not None
987 and self.decode_norm_weight is not None
988 and not has_spec_decode
989 and m.num_prefills == 0
990 and m.num_decodes > 0
991 ):
992 assert non_spec_state_indices_tensor is not None
993 state_indices = non_spec_state_indices_tensor[:num_actual_tokens]
994 if self.kda_decode_backend == "flashinfer":
995 flashinfer_fused_kda_decode(
996 x=mixed_qkv,
997 weight=self.decode_conv1d_weight,
998 conv_state=conv_state,
999 raw_gate=g1,
1000 raw_beta=beta,
1001 A_log=self.A_log,
1002 dt_bias=self.dt_bias,
1003 state_indices=state_indices,
1004 state=recurrent_state,
1005 output_gate=g2[:num_actual_tokens],
1006 norm_weight=self.decode_norm_weight,
1007 lower_bound=self.gate_lower_bound,
1008 norm_eps=self.o_norm.eps,
1009 output=core_attn_out[:, :num_actual_tokens],
1010 )
1011 else:
1012 ops.fused_kda_decode(
1013 x=mixed_qkv,
1014 weight=self.decode_conv1d_weight,
1015 bias=self.conv1d.bias,
1016 conv_state=conv_state,
1017 raw_g=g1,
1018 raw_beta=beta,
1019 A_log=self.A_log,
1020 dt_bias=self.dt_bias,
1021 state_indices=state_indices,
1022 state=recurrent_state,
1023 out=core_attn_out[:, :num_actual_tokens],
1024 lower_bound=self.gate_lower_bound,
1025 output_gate=g2[:num_actual_tokens],
1026 norm_weight=self.decode_norm_weight,
1027 norm_eps=self.o_norm.eps,
1028 )
1029 return
1030
1031 conv_weights = self.conv1d.weight.view(
1032 self.conv1d.weight.size(0), self.conv1d.weight.size(2)
1033 )
1034 q_conv_weight, k_conv_weight, v_conv_weight = conv_weights.split(
1035 self.local_projection_size, dim=0
1036 )
1037 q_conv_state, k_conv_state, v_conv_state = conv_state.split(
1038 self.local_projection_size, dim=-2
1039 )
1040
1041 # Separate multi-query speculative tokens from prefill/plain decode.
1042 if has_spec_decode:
1043 if m.num_prefills == 0 and m.num_decodes == 0:
1044 mixed_qkv_spec = mixed_qkv
1045 g1_spec, beta_spec = g1, beta
1046 mixed_qkv_ns = g1_ns = beta_ns = None
1047 elif spec_token_start is not None:
1048 assert non_spec_token_start is not None
1049 spec_end = spec_token_start + m.num_spec_decode_tokens
1050 non_spec_end = (
1051 non_spec_token_start + m.num_prefill_tokens + m.num_decode_tokens
1052 )
1053 spec_slice = slice(spec_token_start, spec_end)
1054 non_spec_slice = slice(non_spec_token_start, non_spec_end)
1055 mixed_qkv_spec = mixed_qkv[spec_slice]
1056 g1_spec, beta_spec = g1[:, spec_slice], beta[:, spec_slice]
1057 mixed_qkv_ns = mixed_qkv[non_spec_slice]
1058 g1_ns, beta_ns = g1[:, non_spec_slice], beta[:, non_spec_slice]
1059 else:
1060 assert spec_token_indx is not None
1061 assert non_spec_token_indx is not None
1062 mixed_qkv_spec = mixed_qkv.index_select(0, spec_token_indx)
1063 g1_spec = g1.index_select(1, spec_token_indx)
1064 beta_spec = beta.index_select(1, spec_token_indx)
1065 mixed_qkv_ns = mixed_qkv.index_select(0, non_spec_token_indx)
1066 g1_ns = g1.index_select(1, non_spec_token_indx)
1067 beta_ns = beta.index_select(1, non_spec_token_indx)
1068 else:
1069 mixed_qkv_spec = g1_spec = beta_spec = None
1070 mixed_qkv_ns, g1_ns, beta_ns = mixed_qkv, g1, beta
1071
1072 # Spec-decode multi-query path.
1073 core_attn_out_spec = None
1074 if has_spec_decode:
1075 assert spec_state_indices_tensor is not None
1076 assert spec_query_start_loc is not None
1077 spec_conv_indices = spec_state_indices_tensor[:, 0][: m.num_spec_decodes]
1078 spec_max_query_len = (
1079 self.spec_query_len
1080 if self.use_recoverssm
1081 else spec_state_indices_tensor.size(-1)
1082 )
1083 spec_conv_out = torch.empty_like(mixed_qkv_spec)
1084 mixed_qkv_spec = causal_conv1d_update(
1085 mixed_qkv_spec,
1086 conv_state,
1087 conv_weights,
1088 self.conv1d.bias,
1089 activation="silu",
1090 conv_state_indices=spec_conv_indices,
1091 num_accepted_tokens=num_accepted_tokens,
1092 query_start_loc=spec_query_start_loc,
1093 max_query_len=spec_max_query_len,
1094 validate_data=False,
1095 out=spec_conv_out,
1096 )
1097 q_spec, k_spec, v_spec = (
1098 rearrange(x, "n (h d) -> 1 n h d", d=self.head_dim)
1099 for x in mixed_qkv_spec.split(self.local_projection_size, dim=-1)
1100 )
1101 spec_cu_seqlens = spec_query_start_loc[: m.num_spec_decodes + 1]
1102 spec_out = (
1103 core_attn_out[:, : q_spec.shape[1]]
1104 if m.num_prefills == 0 and m.num_decodes == 0
1105 else None
1106 )
1107 if spec_token_start is not None:
1108 spec_out = core_attn_out[:, spec_slice]
1109 if self.use_recoverssm:
1110 from vllm.models.kimi_k3.nvidia.ops.recoverssm import (
1111 kda_recoverssm_verify,
1112 )
1113
1114 if len(recoverssm_records) != 2:
1115 raise ValueError(
1116 "KDA RecoverSSM requires correction and key/gate buffers"
1117 )
1118 core_attn_out_spec = kda_recoverssm_verify(
1119 q=q_spec,
1120 k=k_spec,
1121 v=v_spec,
1122 raw_g=g1_spec,
1123 raw_beta=beta_spec,
1124 A_log=self.A_log,
1125 dt_bias=self.dt_bias,
1126 lower_bound=self.gate_lower_bound,
1127 checkpoint_state=recurrent_state,
1128 correction_cache=recoverssm_records[0],
1129 kg_cache=recoverssm_records[1],
1130 query_start_loc=spec_cu_seqlens,
1131 state_indices=spec_state_indices_tensor[: m.num_spec_decodes, 0],
1132 spec_query_len=self.spec_query_len,
1133 out=spec_out,
1134 )
1135 else:
1136 core_attn_out_spec, _ = fused_recurrent_kda(
1137 q=q_spec,
1138 k=k_spec,
1139 v=v_spec,
1140 raw_g=g1_spec,
1141 raw_beta=beta_spec,
1142 A_log=self.A_log,
1143 dt_bias=self.dt_bias,
1144 lower_bound=self.gate_lower_bound,
1145 initial_state=recurrent_state,
1146 cu_seqlens=spec_cu_seqlens,
1147 ssm_state_indices=spec_state_indices_tensor,
1148 num_accepted_tokens=num_accepted_tokens,
1149 out=spec_out,
1150 )
1151
1152 # Prefill or plain-decode path.
1153 core_attn_out_non_spec = None
1154 if mixed_qkv_ns is not None:
1155 assert g1_ns is not None and beta_ns is not None
1156 non_spec_out = None
1157 if non_spec_token_start is not None:
1158 non_spec_out = core_attn_out[:, non_spec_slice]
1159 if m.num_prefills > 0:
1160 q_ns, k_ns, v_ns = mixed_qkv_ns.split(
1161 self.local_projection_size, dim=-1
1162 )
1163
1164 # Separate convolution calls accept row-strided packed inputs
1165 # and produce dense Q/K/V without an additional V copy.
1166 def _prefill_conv(
1167 x: torch.Tensor,
1168 state: torch.Tensor,
1169 weight: torch.Tensor,
1170 ) -> torch.Tensor:
1171 return causal_conv1d_fn(
1172 x.transpose(0, 1),
1173 weight,
1174 None,
1175 activation="silu",
1176 conv_states=state,
1177 has_initial_state=has_initial_state,
1178 cache_indices=non_spec_state_indices_tensor,
1179 query_start_loc=non_spec_query_start_loc,
1180 metadata=m,
1181 ).transpose(0, 1)
1182
1183 q_ns = _prefill_conv(q_ns, q_conv_state, q_conv_weight)
1184 k_ns = _prefill_conv(k_ns, k_conv_state, k_conv_weight)
1185 v_ns = _prefill_conv(v_ns, v_conv_state, v_conv_weight)
1186 q_ns, k_ns, v_ns = (
1187 rearrange(x, "n (h d) -> 1 n h d", d=self.head_dim)
1188 for x in (q_ns, k_ns, v_ns)
1189 )
1190
1191 assert non_spec_state_indices_tensor is not None
1192 assert has_initial_state is not None
1193 initial_state = gather_initial_states(
1194 recurrent_state,
1195 non_spec_state_indices_tensor,
1196 has_initial_state,
1197 )
1198 if self.kda_prefill_backend == "flashkda":
1199 assert self.gate_lower_bound is not None
1200 assert self._flashkda_buffer_specs is not None
1201 workspace_out, final_state, checkpoint_state, workspace = (
1202 current_workspace_manager().get_simultaneous(
1203 *self._flashkda_buffer_specs
1204 )
1205 )
1206 flashkda_out = (
1207 workspace_out if has_spec_decode else core_attn_out
1208 )[:, : q_ns.shape[1]]
1209 if non_spec_out is not None:
1210 flashkda_out = non_spec_out
1211 if checkpoint is not None:
1212 assert non_spec_query_start_loc is not None
1213 num_sequences = initial_state.shape[0]
1214 assert checkpoint.checkpoint_offsets.shape == (num_sequences,)
1215 final_state = final_state[:num_sequences]
1216 checkpoint_state = checkpoint_state[:num_sequences]
1217 checkpoint_offsets = checkpoint.checkpoint_offsets
1218 _flashkda_prefill(
1219 q=q_ns,
1220 k=k_ns,
1221 v=v_ns,
1222 g=g1_ns,
1223 beta=beta_ns,
1224 A_log=self.A_log,
1225 dt_bias=self.dt_bias,
1226 lower_bound=self.gate_lower_bound,
1227 initial_state=initial_state,
1228 cu_seqlens=non_spec_query_start_loc,
1229 out=flashkda_out,
1230 final_state=final_state,
1231 workspace=workspace,
1232 checkpoint_state=checkpoint_state,
1233 checkpoint_offsets=checkpoint_offsets,
1234 )
1235 core_attn_out_non_spec = flashkda_out
1236 last_recurrent_state = final_state
1237 assert self._checkpoint_exporter is not None
1238 self._checkpoint_exporter.export(
1239 checkpoint,
1240 raw_qkv=mixed_qkv_ns,
1241 conv_state=conv_state,
1242 recurrent_checkpoint=checkpoint_state,
1243 recurrent_state=recurrent_state,
1244 cu_seqlens=non_spec_query_start_loc,
1245 )
1246 else:
1247 (
1248 core_attn_out_non_spec,
1249 last_recurrent_state,
1250 ) = _flashkda_prefill(
1251 q=q_ns,
1252 k=k_ns,
1253 v=v_ns,
1254 g=g1_ns,
1255 beta=beta_ns,
1256 A_log=self.A_log,
1257 dt_bias=self.dt_bias,
1258 lower_bound=self.gate_lower_bound,
1259 initial_state=initial_state,
1260 cu_seqlens=non_spec_query_start_loc,
1261 out=flashkda_out,
1262 final_state=final_state[: initial_state.shape[0]],
1263 workspace=workspace,
1264 )
1265 elif self.kda_prefill_backend == "flashinfer":
1266 assert self.gate_lower_bound is not None
1267 assert m.flashinfer_prefill_query_start_loc is not None
1268 if q_ns.shape[1] > initial_state.shape[0]:
1269 assert m.flashinfer_prefill_seq_order is not None
1270 flashinfer_out = core_attn_out[:, : q_ns.shape[1]]
1271 if non_spec_out is not None:
1272 flashinfer_out = non_spec_out
1273 elif has_spec_decode:
1274 assert self._flashinfer_kda_output_spec is not None
1275 (workspace_out,) = current_workspace_manager().get_simultaneous(
1276 self._flashinfer_kda_output_spec
1277 )
1278 flashinfer_out = workspace_out[:, : q_ns.shape[1]]
1279 (
1280 core_attn_out_non_spec,
1281 last_recurrent_state,
1282 ) = _flashinfer_kda_prefill(
1283 q=q_ns,
1284 k=k_ns,
1285 v=v_ns,
1286 raw_g=g1_ns,
1287 raw_beta=beta_ns,
1288 A_log=self.A_log,
1289 dt_bias=self.dt_bias,
1290 lower_bound=self.gate_lower_bound,
1291 initial_state=initial_state,
1292 cu_seqlens=m.flashinfer_prefill_query_start_loc,
1293 out=flashinfer_out,
1294 seq_order=m.flashinfer_prefill_seq_order,
1295 )
1296 else:
1297 (
1298 core_attn_out_non_spec,
1299 last_recurrent_state,
1300 ) = chunk_kda_with_fused_gate(
1301 q=q_ns,
1302 k=k_ns,
1303 v=v_ns,
1304 raw_g=g1_ns,
1305 raw_beta=beta_ns,
1306 A_log=self.A_log,
1307 g_bias=self.dt_bias,
1308 lower_bound=self.gate_lower_bound,
1309 initial_state=initial_state,
1310 output_final_state=True,
1311 use_qk_l2norm_in_kernel=True,
1312 cu_seqlens=non_spec_query_start_loc,
1313 out=non_spec_out,
1314 )
1315 recurrent_state[non_spec_state_indices_tensor] = (
1316 last_recurrent_state.to(recurrent_state.dtype)
1317 )
1318 else:
1319 # Pure non-speculative decode.
1320 assert non_spec_state_indices_tensor is not None
1321 decode_conv_indices = non_spec_state_indices_tensor[
1322 : mixed_qkv_ns.size(0)
1323 ]
1324 packed_conv_out = torch.empty_like(mixed_qkv_ns)
1325 mixed_qkv_ns = causal_conv1d_update(
1326 mixed_qkv_ns,
1327 conv_state,
1328 conv_weights,
1329 self.conv1d.bias,
1330 activation="silu",
1331 conv_state_indices=decode_conv_indices,
1332 validate_data=True,
1333 out=packed_conv_out,
1334 )
1335 (
1336 core_attn_out_non_spec,
1337 _,
1338 ) = fused_recurrent_kda_packed_decode(
1339 mixed_qkv=mixed_qkv_ns,
1340 raw_g=g1_ns,
1341 raw_beta=beta_ns,
1342 A_log=self.A_log,
1343 dt_bias=self.dt_bias,
1344 lower_bound=self.gate_lower_bound,
1345 initial_state=recurrent_state,
1346 state_indices=decode_conv_indices,
1347 out=non_spec_out,
1348 )
1349
1350 # Restore the scheduler's original token order for mixed batches.
1351 if core_attn_out_spec is not None and core_attn_out_non_spec is not None:
1352 if spec_token_start is None:
1353 assert spec_token_indx is not None
1354 assert non_spec_token_indx is not None
1355 core_attn_out.index_copy_(1, spec_token_indx, core_attn_out_spec)
1356 core_attn_out.index_copy_(
1357 1, non_spec_token_indx, core_attn_out_non_spec
1358 )
1359 elif core_attn_out_non_spec is not None:
1360 if (
1361 self.kda_prefill_backend not in ("flashkda", "flashinfer")
1362 or m.num_prefills == 0
1363 ):
1364 # TODO: decode kernels write directly to core_attn_out
1365 core_attn_out[0, :num_actual_tokens] = core_attn_out_non_spec[
1366 0, :num_actual_tokens
1367 ]
1368 else:
1369 assert core_attn_out_spec is not None
1370 # Triton normalizes in place, so this is a self-copy with no device
1371 # work. Keep it for the out-of-place native implementation.
1372 core_attn_out.copy_(self.o_norm(core_attn_out, g2))