main
py 2,151 lines 85.1 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Kimi-K3 multimodal model implementation for vLLM."""
4
5 import math
6 from collections.abc import Hashable, Iterable
7 from typing import Any, cast
8
9 import torch
10 from torch import nn
11
12 import vllm.envs as envs
13 from vllm.config import VllmConfig
14 from vllm.distributed import (
15 get_ep_group,
16 get_pp_group,
17 get_tensor_model_parallel_world_size,
18 )
19 from vllm.forward_context import get_forward_context, is_forward_context_available
20 from vllm.logger import init_logger
21 from vllm.model_executor.layers.activation import SiluAndMul, SituAndMul
22 from vllm.model_executor.layers.fused_moe import (
23 FusedMoEFactory,
24 fused_moe_make_expert_params_mapping,
25 )
26 from vllm.model_executor.layers.fused_moe.router.base_router import (
27 eplb_map_to_physical_and_record,
28 )
29 from vllm.model_executor.layers.fused_moe.router.gate_linear import GateLinear
30 from vllm.model_executor.layers.fused_moe.router.grouped_topk_router import (
31 fused_grouped_topk,
32 )
33 from vllm.model_executor.layers.layernorm import RMSNorm
34 from vllm.model_executor.layers.linear import (
35 MergedColumnParallelLinear,
36 ReplicatedLinear,
37 RowParallelLinear,
38 )
39 from vllm.model_executor.layers.logits_processor import LogitsProcessor
40 from vllm.model_executor.layers.mamba.gdn.kimi_gdn_linear_attn import (
41 KimiGatedDeltaNetAttention as KimiLinearGatedDeltaNetAttention,
42 )
43 from vllm.model_executor.layers.mamba.mamba_utils import (
44 MambaStateCopyFunc,
45 MambaStateCopyFuncCalculator,
46 MambaStateDtypeCalculator,
47 MambaStateShapeCalculator,
48 )
49 from vllm.model_executor.layers.quantization import QuantizationConfig
50 from vllm.model_executor.layers.quantization.compressed_tensors import (
51 compressed_tensors,
52 )
53 from vllm.model_executor.layers.vocab_parallel_embedding import (
54 ParallelLMHead,
55 VocabParallelEmbedding,
56 )
57 from vllm.model_executor.model_loader.weight_utils import (
58 default_weight_loader,
59 maybe_remap_kv_scale_name,
60 )
61 from vllm.model_executor.models.interfaces import (
62 EagleModelMixin,
63 HasInnerState,
64 IsHybrid,
65 MambaStateShapes,
66 MixtureOfExperts,
67 SupportsEagle3,
68 SupportsEncoderCudaGraph,
69 SupportsMultiModal,
70 SupportsPP,
71 SupportsQuant,
72 SupportsReplaySSM,
73 )
74 from vllm.model_executor.models.kimi_k25 import KimiK25MediaPixelInputs
75 from vllm.model_executor.models.kimi_k25_vit import (
76 KimiK25MultiModalProjector,
77 MoonViT3dPretrainedModel,
78 vision_tower_forward,
79 )
80 from vllm.model_executor.models.utils import (
81 AutoWeightsLoader,
82 PPMissingLayer,
83 WeightsMapper,
84 init_vllm_registered_model,
85 is_pp_missing_parameter,
86 make_layers,
87 maybe_prefix,
88 spec_decode_needs_target_embed,
89 )
90 from vllm.model_executor.models.vision import is_vit_use_data_parallel
91 from vllm.models.common.ops.sequence_parallel import (
92 sp_all_gather,
93 sp_padding_mask,
94 sp_reduce_scatter,
95 sp_shard,
96 )
97 from vllm.models.deepseek_v4.nvidia.model import (
98 DeepseekV4MegaMoEExperts,
99 DeepseekV4MLP,
100 )
101 from vllm.models.deepseek_v4.nvidia.ops.prepare_megamoe import prepare_megamoe_inputs
102 from vllm.models.kimi_k3.nvidia.kda import KimiK3DeltaAttention
103 from vllm.models.kimi_k3.nvidia.latent_moe_runner import (
104 LatentMoERunner,
105 )
106 from vllm.models.kimi_k3.nvidia.low_latency_gemm import (
107 enable_kimi_k3_low_latency_gemm,
108 )
109 from vllm.models.kimi_k3.nvidia.mla import MultiHeadLatentAttention
110 from vllm.models.kimi_k3.nvidia.ops import attn_res
111 from vllm.multimodal import MULTIMODAL_REGISTRY
112 from vllm.multimodal.inputs import NestedTensors
113 from vllm.platforms import current_platform
114 from vllm.sequence import IntermediateTensors
115 from vllm.transformers_utils.configs.kimi_k3 import KimiK3Config
116 from vllm.transformers_utils.configs.kimi_linear import KimiLinearConfig
117 from vllm.utils.math_utils import cdiv
118 from vllm.utils.multi_stream_utils import maybe_execute_in_parallel
119 from vllm.utils.torch_utils import aux_stream, is_meta_module
120 from vllm.v1.worker.ubatching import dbo_current_ubatch_id
121
122 from ..common.mm_preprocess import (
123 KimiK3DummyInputsBuilder,
124 KimiK3MultiModalProcessor,
125 KimiK3ProcessingInfo,
126 )
127
128 logger = init_logger(__name__)
129
130 # Token-count cutoff for overlapping the MoE router gate with the routed-expert
131 # down projection on a separate CUDA stream (latent MoE). At or below this many
132 # tokens the launch-bound decode path benefits from multi-stream overlap; above
133 # it the GEMMs saturate the device and the cross-stream sync is pure overhead,
134 # so it falls back to sequential.
135 _ROUTED_DOWN_PROJ_STREAM_TOKEN_THRESHOLD = 256
136
137
138 def shard_sequence_parallel_mlp(
139 hidden_size: int,
140 intermediate_size: int,
141 use_sequence_parallel: bool,
142 eligible: bool,
143 ) -> bool:
144 """Whether to TP-shard a sequence-parallel MLP instead of replicating it.
145
146 Opt-in via ``VLLM_KIMI_K3_SHARD_SP_SHARED_EXPERT``; see :class:`KimiMLP` for
147 the trade-off and :mod:`vllm.envs` for when it is worth enabling.
148 """
149 enabled = envs.VLLM_KIMI_K3_SHARD_SP_SHARED_EXPERT
150 if not (use_sequence_parallel and eligible and enabled):
151 return False
152 tp_size = get_tensor_model_parallel_world_size()
153 return (
154 tp_size > 1 and intermediate_size % tp_size == 0 and hidden_size % tp_size == 0
155 )
156
157
158 def maybe_init_gemm_rs_ar(vllm_config: VllmConfig, use_sequence_parallel: bool) -> bool:
159 # Both feature flags may be enabled; the worker's static SP topology binds
160 # its singleton to exactly one mode.
161 all_reduce = not use_sequence_parallel
162 mode = "GEMM-AR" if all_reduce else "GEMM-RS"
163 enabled = envs.VLLM_KIMI_K3_GEMM_AR if all_reduce else envs.VLLM_ENABLE_GEMM_RS
164 if not enabled:
165 return False
166
167 # The kernel module pulls in cute_dsl, so import it only once the flag
168 # asks for it.
169 from vllm.model_executor.kernels.linear.cute_dsl.gemm_rs_ar import (
170 maybe_init_gemm_rs_ar as maybe_init_shared_gemm_rs_ar,
171 )
172
173 config = vllm_config.model_config.hf_text_config
174 if not maybe_init_shared_gemm_rs_ar(
175 vllm_config, N=config.hidden_size, all_reduce=all_reduce
176 ):
177 return False
178 flag = "VLLM_KIMI_K3_GEMM_AR" if all_reduce else "VLLM_ENABLE_GEMM_RS"
179 logger.info_once("To disable %s, set %s=0.", mode, flag)
180 return True
181
182
183 class KimiMLP(nn.Module):
184 """Dense / shared-expert MLP, optionally TP-sharded under sequence parallel.
185
186 Under sequence parallelism each rank owns a distinct slice of the tokens, so
187 by default both projections are replicated (``disable_tp``) and the block
188 needs no collective. That makes every rank stream the entire weight to serve
189 its own token shard.
190
191 With ``VLLM_KIMI_K3_SHARD_SP_SHARED_EXPERT`` the weights are TP-sharded
192 instead. A rank then holds only a slice of the intermediate dim, so it
193 cannot finish its own tokens alone: ``forward`` all-gathers the full token
194 set, computes this rank's partial, and reduce-scatters. The reduce-scatter
195 sums across TP and restores the sequence sharding in one collective, so the
196 block still ends with one collective per direction.
197 """
198
199 def __init__(
200 self,
201 hidden_size: int,
202 intermediate_size: int,
203 hidden_act: str,
204 quant_config: QuantizationConfig | None = None,
205 reduce_results: bool = True,
206 use_sequence_parallel: bool = False,
207 can_shard_sequence_parallel: bool = False,
208 run_gemm_rs_ar: bool = False,
209 prefix: str = "",
210 activation_situ_beta: float | None = None,
211 activation_situ_linear_beta: float | None = None,
212 ) -> None:
213 super().__init__()
214
215 self.shard_sequence_parallel = shard_sequence_parallel_mlp(
216 hidden_size,
217 intermediate_size,
218 use_sequence_parallel,
219 can_shard_sequence_parallel,
220 )
221 replicate = use_sequence_parallel and not self.shard_sequence_parallel
222
223 self.gate_up_proj = MergedColumnParallelLinear(
224 hidden_size,
225 [intermediate_size] * 2,
226 bias=False,
227 quant_config=quant_config,
228 disable_tp=replicate,
229 prefix=f"{prefix}.gate_up_proj",
230 )
231 self.down_proj = RowParallelLinear(
232 intermediate_size,
233 hidden_size,
234 bias=False,
235 quant_config=quant_config,
236 # Sharded sequence parallel reduces via the reduce-scatter in
237 # forward(), which also restores the sequence sharding.
238 reduce_results=False if self.shard_sequence_parallel else reduce_results,
239 disable_tp=replicate,
240 prefix=f"{prefix}.down_proj",
241 )
242 self.gemm_rs_ar = None
243 # RS requires sequence sharding; AR operates on replicated tokens.
244 use_gemm_rs_ar = self.shard_sequence_parallel or (
245 not use_sequence_parallel and reduce_results
246 )
247 if use_gemm_rs_ar and run_gemm_rs_ar:
248 from vllm.model_executor.kernels.linear.cute_dsl.gemm_rs_ar import (
249 get_gemm_rs_ar,
250 )
251
252 gemm_rs_ar = get_gemm_rs_ar()
253 if gemm_rs_ar.can_run(self.down_proj):
254 self.gemm_rs_ar = gemm_rs_ar
255 else:
256 gemm_rs_ar.warn_incompatible_projection()
257 if hidden_act == "silu":
258 self.act_fn = SiluAndMul()
259 elif hidden_act == "situ":
260 self.act_fn = SituAndMul(
261 beta=activation_situ_beta or 1.0,
262 linear_beta=activation_situ_linear_beta,
263 )
264 else:
265 raise ValueError(
266 f"Unsupported activation: {hidden_act}. "
267 "Only silu and situ are supported."
268 )
269
270 def forward(self, x):
271 if self.shard_sequence_parallel:
272 # Each rank holds a weight shard but only its own tokens, so it
273 # cannot finish those tokens alone: gather the full token set,
274 # compute this rank's partial for all of them, then reduce-scatter,
275 # which sums across TP and restores the sequence sharding.
276 x = sp_all_gather(x)
277 gate_up, _ = self.gate_up_proj(x)
278 x = self.act_fn(gate_up)
279
280 if self.gemm_rs_ar is not None and self.gemm_rs_ar.should_run(x):
281 return self.gemm_rs_ar.apply(x, self.down_proj)
282
283 x, _ = self.down_proj(x)
284 if self.shard_sequence_parallel:
285 x = sp_reduce_scatter(x)
286 return x
287
288
289 class KimiRoutedOutputTransform(nn.Module):
290 def __init__(
291 self,
292 norm: RMSNorm | None,
293 up_proj: ReplicatedLinear,
294 ) -> None:
295 super().__init__()
296 self.norm = norm
297 self.up_proj = up_proj
298
299 def forward(
300 self,
301 hidden_states: torch.Tensor,
302 residual: torch.Tensor | None = None,
303 ) -> torch.Tensor:
304 """Project the routed latent back to the hidden dim.
305
306 Args:
307 hidden_states: Routed expert output in latent space.
308 residual: Optional tensor of the up-projection's output shape to
309 accumulate into. It is consumed in the GEMM's beta-add
310 epilogue, so adding it costs no extra kernel.
311
312 """
313 if self.norm is not None:
314 hidden_states = self.norm(hidden_states)
315 if residual is not None:
316 return residual.addmm_(hidden_states, self.up_proj.weight.t())
317 hidden_states, _ = self.up_proj(hidden_states)
318 return hidden_states
319
320
321 class KimiK3MegaMoEExperts(DeepseekV4MegaMoEExperts):
322 """Kimi K3 adapter for the DeepGEMM MegaMoE kernel."""
323
324 _kimi_symm_buffer_cache: dict[tuple[object, ...], object] = {}
325 _synchronized_ep_groups: set[tuple[int, int]] = set()
326
327 def __init__(
328 self,
329 *args,
330 activation: str,
331 activation_beta: float | None,
332 activation_linear_beta: float | None,
333 **kwargs,
334 ):
335 super().__init__(*args, **kwargs)
336 self.activation = activation
337 self.activation_beta = activation_beta
338 self.activation_linear_beta = activation_linear_beta
339 self.register_buffer("_mega_l1_packed", None, persistent=False)
340 self.register_buffer("_mega_l1_scale", None, persistent=False)
341 self.register_buffer("_mega_l2_packed", None, persistent=False)
342 self.register_buffer("_mega_l2_scale", None, persistent=False)
343
344 def synchronize_first_launch(self) -> None:
345 ep_group = get_ep_group()
346 device = torch.accelerator.current_device_index()
347 key = (id(ep_group.cpu_group), device)
348 if key in self._synchronized_ep_groups:
349 return
350 torch.accelerator.synchronize()
351 torch.distributed.barrier(group=ep_group.cpu_group)
352 self._synchronized_ep_groups.add(key)
353
354 def finalize_weights(self, shared_experts: DeepseekV4MLP | None = None) -> None:
355 if self._transformed_l1_weights is not None:
356 return
357
358 # Weight cache IPC engine: the daemon exported the transformed
359 # buffers; reuse them zero-copy and drop the raw packed params.
360 if self._mega_l1_packed is not None:
361 self._transformed_l1_weights = (self._mega_l1_packed, self._mega_l1_scale)
362 self._transformed_l2_weights = (self._mega_l2_packed, self._mega_l2_scale)
363 self._drop_raw_mega_weights()
364 return
365
366 self._check_runtime_supported()
367 from vllm.utils.deep_gemm import _import_deep_gemm
368
369 deep_gemm = _import_deep_gemm()
370 w13_scale = deep_gemm.transform_sf_into_required_layout(
371 self._ue8m0_uint8_to_float(self.w13_weight_scale.data).contiguous(),
372 2 * self.intermediate_size,
373 self.hidden_size,
374 (1, 32),
375 self.num_local_experts,
376 )
377 w2_scale = deep_gemm.transform_sf_into_required_layout(
378 self._ue8m0_uint8_to_float(self.w2_weight_scale.data).contiguous(),
379 self.hidden_size,
380 self.intermediate_size,
381 (1, 32),
382 self.num_local_experts,
383 )
384 self._transformed_l1_weights, self._transformed_l2_weights = (
385 deep_gemm.transform_weights_for_mega_moe(
386 (self.w13_weight.data.view(torch.int8).contiguous(), w13_scale),
387 (self.w2_weight.data.view(torch.int8).contiguous(), w2_scale),
388 activation=self.activation,
389 )
390 )
391 l1_packed, l1_scale = self._transformed_l1_weights
392 l2_packed, l2_scale = self._transformed_l2_weights
393 self.register_buffer("_mega_l1_packed", l1_packed, persistent=False)
394 self.register_buffer("_mega_l1_scale", l1_scale, persistent=False)
395 self.register_buffer("_mega_l2_packed", l2_packed, persistent=False)
396 self.register_buffer("_mega_l2_scale", l2_scale, persistent=False)
397 self._drop_raw_mega_weights()
398
399 def _drop_raw_mega_weights(self) -> None:
400 self.w13_weight = None
401 self.w13_weight_scale = None
402 self.w2_weight = None
403 self.w2_weight_scale = None
404
405 def get_symm_buffer(self):
406 from vllm.utils.deep_gemm import _import_deep_gemm
407
408 deep_gemm = _import_deep_gemm()
409 group = get_ep_group().device_group
410 device = torch.accelerator.current_device_index()
411 key = (
412 id(group),
413 device,
414 self.num_experts,
415 self.max_num_tokens,
416 self.top_k,
417 self.hidden_size,
418 self.intermediate_size,
419 self.activation,
420 )
421 symm_buffer = self._kimi_symm_buffer_cache.get(key)
422 if symm_buffer is None:
423 symm_buffer = deep_gemm.get_symm_buffer_for_mega_moe(
424 group,
425 self.num_experts,
426 self.max_num_tokens,
427 self.top_k,
428 self.hidden_size,
429 self.intermediate_size,
430 activation=self.activation,
431 )
432 self._kimi_symm_buffer_cache[key] = symm_buffer
433 return symm_buffer
434
435 def forward(
436 self,
437 hidden_states: torch.Tensor,
438 topk_weights: torch.Tensor,
439 topk_ids: torch.Tensor,
440 *,
441 activation_clamp: float | None,
442 fast_math: bool = True,
443 ) -> torch.Tensor:
444 self.synchronize_first_launch()
445 if hidden_states.shape[0] > self.max_num_tokens:
446 raise ValueError(
447 f"Kimi K3 MegaMoE got {hidden_states.shape[0]} tokens, "
448 f"but its symmetric buffer supports {self.max_num_tokens}."
449 )
450 y = torch.empty_like(hidden_states, dtype=torch.bfloat16)
451 from vllm.utils.deep_gemm import _import_deep_gemm
452
453 deep_gemm = _import_deep_gemm()
454 symm_buffer = self.get_symm_buffer()
455 num_tokens = hidden_states.shape[0]
456 is_padding = None
457 if envs.VLLM_MOE_SKIP_PADDING and is_forward_context_available():
458 is_padding = get_forward_context().is_padding
459 if is_padding is not None:
460 is_padding = is_padding[:num_tokens]
461
462 if self.capture_fn is not None:
463 self.capture_fn(topk_ids)
464
465 eplb_state = self.eplb_state
466 if eplb_state.logical_to_physical_map is not None:
467 assert eplb_state.expert_load_view is not None
468 assert eplb_state.logical_replica_count is not None
469 assert eplb_state.should_record_tensor is not None
470 if is_padding is not None:
471 topk_ids = torch.where(is_padding.unsqueeze(1), -1, topk_ids)
472 topk_ids = eplb_map_to_physical_and_record(
473 topk_ids=topk_ids,
474 expert_load_view=eplb_state.expert_load_view,
475 logical_to_physical_map=eplb_state.logical_to_physical_map,
476 logical_replica_count=eplb_state.logical_replica_count,
477 record_enabled=eplb_state.should_record_tensor,
478 num_unpadded_tokens=eplb_state.num_unpadded_tokens_tensors[
479 dbo_current_ubatch_id()
480 ]
481 if eplb_state.num_unpadded_tokens_tensors is not None
482 else None,
483 )
484
485 prepare_megamoe_inputs(
486 hidden_states,
487 topk_weights,
488 topk_ids,
489 symm_buffer.x[:num_tokens],
490 symm_buffer.x_sf[:num_tokens],
491 symm_buffer.topk_idx[:num_tokens],
492 symm_buffer.topk_weights[:num_tokens],
493 is_padding=is_padding,
494 )
495 self.finalize_weights()
496 assert self._transformed_l1_weights is not None
497 assert self._transformed_l2_weights is not None
498 deep_gemm.fp8_fp4_mega_moe(
499 y,
500 self._transformed_l1_weights,
501 self._transformed_l2_weights,
502 symm_buffer,
503 activation_clamp=activation_clamp,
504 activation=self.activation,
505 # DeepGEMM names the SiTU gate tanh scale `activation_alpha` and the
506 # linear/up tanh scale `activation_beta`; beta=0 leaves up untouched.
507 activation_alpha=self.activation_beta or 1.0,
508 activation_beta=self.activation_linear_beta or 0.0,
509 fast_math=fast_math,
510 )
511 return y
512
513
514 def make_kimi_k3_mega_moe_expert_params_mapping(
515 num_experts: int,
516 ) -> list[tuple[str, str, int, str]]:
517 mapping = []
518 for expert_id in range(num_experts):
519 for shard_id in ("w1", "w2", "w3"):
520 param_prefix = "w13" if shard_id in ("w1", "w3") else "w2"
521 for suffix in ("weight_packed", "weight_scale"):
522 param_suffix = "weight" if suffix == "weight_packed" else suffix
523 mapping.append(
524 (
525 f"experts.{param_prefix}_{param_suffix}",
526 f"experts.{expert_id}.{shard_id}.{suffix}",
527 expert_id,
528 shard_id,
529 )
530 )
531 return mapping
532
533
534 class KimiMoE(nn.Module):
535 def __init__(
536 self,
537 config: KimiLinearConfig,
538 vllm_config: VllmConfig,
539 quant_config: QuantizationConfig | None = None,
540 prefix: str = "",
541 layer_idx: int = 0,
542 use_sequence_parallel: bool = False,
543 run_gemm_rs_ar: bool = False,
544 ):
545 super().__init__()
546 hidden_size = config.hidden_size
547 moe_intermediate_size = config.moe_intermediate_size
548 num_experts = config.num_experts
549 num_experts_per_token = config.num_experts_per_token
550 assert moe_intermediate_size is not None
551 assert num_experts is not None
552 assert num_experts_per_token is not None
553 moe_renormalize = config.moe_renormalize
554 routed_expert_hidden_size = config.routed_expert_hidden_size
555 self.use_latent_moe = routed_expert_hidden_size is not None
556 self.moe_hidden_size = (
557 routed_expert_hidden_size
558 if routed_expert_hidden_size is not None
559 else hidden_size
560 )
561 self.latent_moe_use_norm = config.latent_moe_use_norm
562 self.tp_size = get_tensor_model_parallel_world_size()
563 self.routed_scaling_factor = config.routed_scaling_factor
564 self.moe_renormalize = moe_renormalize
565 self.use_grouped_topk = config.use_grouped_topk
566 self.num_expert_group = config.num_expert_group
567 self.topk_group = config.topk_group
568 self.moe_router_activation_func = config.moe_router_activation_func
569 self.num_shared_experts = config.num_shared_experts
570 self.layer_idx = layer_idx
571 self.use_mega_moe = (
572 vllm_config.kernel_config.moe_backend == "deep_gemm_mega_moe"
573 )
574 if self.use_mega_moe and not vllm_config.parallel_config.enable_expert_parallel:
575 raise NotImplementedError(
576 "Kimi K3 MegaMoE requires expert parallel. Enable it with "
577 "--enable-expert-parallel."
578 )
579 if self.use_mega_moe and config.hidden_act != "situ":
580 raise ValueError("Kimi K3 MegaMoE requires SITU activation.")
581 if self.use_mega_moe and not self.use_latent_moe:
582 raise ValueError("Kimi K3 MegaMoE requires latent MoE projections.")
583 if self.use_mega_moe and not self.use_grouped_topk:
584 raise ValueError("Kimi K3 MegaMoE requires grouped top-k routing.")
585 if self.use_mega_moe and (self.num_expert_group != 1 or self.topk_group != 1):
586 raise NotImplementedError(
587 "Kimi K3 MegaMoE currently requires one expert group."
588 )
589 self.padded_moe_intermediate_size = moe_intermediate_size
590 min_moe_intermediate_per_partition = getattr(
591 config, "min_moe_intermediate_per_partition", 256
592 )
593 if self.tp_size > 1 and not vllm_config.parallel_config.enable_expert_parallel:
594 moe_intermediate_per_partition = moe_intermediate_size // self.tp_size
595 if moe_intermediate_per_partition < min_moe_intermediate_per_partition:
596 self.padded_moe_intermediate_size = (
597 min_moe_intermediate_per_partition * self.tp_size
598 )
599 activation_situ_beta = (
600 config.activation_situ_beta if config.hidden_act == "situ" else None
601 )
602 activation_situ_linear_beta = (
603 config.activation_situ_linear_beta if config.hidden_act == "situ" else None
604 )
605
606 # Route with fp32 logits for numerically stable expert selection.
607 self.gate = GateLinear(
608 input_size=hidden_size,
609 output_size=num_experts,
610 bias=False,
611 out_dtype=torch.float32,
612 prefix=f"{prefix}.gate",
613 )
614
615 self.gate.e_score_correction_bias = nn.Parameter(
616 torch.empty(num_experts, dtype=torch.float32)
617 )
618
619 if self.num_shared_experts is not None:
620 shared_intermediate_size = moe_intermediate_size * self.num_shared_experts
621 self.shared_experts = KimiMLP(
622 hidden_size=config.hidden_size,
623 intermediate_size=shared_intermediate_size,
624 hidden_act=config.hidden_act,
625 quant_config=quant_config,
626 reduce_results=False,
627 use_sequence_parallel=use_sequence_parallel,
628 # Only the MegaMoE path calls the shared experts directly; the
629 # FusedMoE path below hands them to the runner, which fuses
630 # their reduction and assumes the replicated layout.
631 can_shard_sequence_parallel=self.use_mega_moe,
632 run_gemm_rs_ar=run_gemm_rs_ar,
633 prefix=f"{prefix}.shared_experts",
634 activation_situ_beta=activation_situ_beta,
635 activation_situ_linear_beta=activation_situ_linear_beta,
636 )
637 else:
638 self.shared_experts = None
639
640 self.routed_expert_down_proj: ReplicatedLinear | None
641 self.routed_expert_norm: RMSNorm | None
642 self.routed_expert_up_proj: ReplicatedLinear | None
643 self.routed_output_transform: KimiRoutedOutputTransform | None
644 if self.use_latent_moe:
645 self.routed_expert_down_proj = ReplicatedLinear(
646 hidden_size,
647 self.moe_hidden_size,
648 bias=False,
649 quant_config=quant_config,
650 prefix=f"{prefix}.routed_expert_down_proj",
651 )
652 self.routed_expert_norm = (
653 RMSNorm(self.moe_hidden_size, eps=config.rms_norm_eps)
654 if self.latent_moe_use_norm
655 else None
656 )
657 # Replicated up-proj: the full weight lives on every rank and
658 # produces the full hidden dim locally. This lets LatentMoERunner
659 # fuse the latent and shared reductions into a single all-reduce
660 # (concat the two partials, reduce once), then run the up-proj and
661 # shared add locally with no further collective.
662 self.routed_expert_up_proj = ReplicatedLinear(
663 self.moe_hidden_size,
664 hidden_size,
665 bias=False,
666 quant_config=quant_config,
667 prefix=f"{prefix}.routed_expert_up_proj",
668 )
669
670 self.routed_output_transform = KimiRoutedOutputTransform(
671 self.routed_expert_norm, self.routed_expert_up_proj
672 )
673 # Auxiliary CUDA stream to overlap the router gate with the routed
674 # down projection on decode-sized batches (gated by
675 # _ROUTED_DOWN_PROJ_STREAM_TOKEN_THRESHOLD).
676 self._down_proj_stream: torch.cuda.Stream | None = aux_stream()
677 self._down_proj_events = (torch.cuda.Event(), torch.cuda.Event())
678 else:
679 self.routed_expert_down_proj = None
680 self.routed_expert_norm = None
681 self.routed_expert_up_proj = None
682 self.routed_output_transform = None
683
684 if self.use_mega_moe:
685 ep_group = get_ep_group()
686 ep_size = ep_group.world_size
687 ep_rank = ep_group.rank_in_group
688 if num_experts % ep_size != 0:
689 raise ValueError(
690 f"Kimi K3 num_experts={num_experts} must be divisible by "
691 f"EP size {ep_size}."
692 )
693 num_local_experts = num_experts // ep_size
694 self.experts = KimiK3MegaMoEExperts(
695 vllm_config,
696 num_experts=num_experts,
697 num_local_experts=num_local_experts,
698 experts_start_idx=ep_rank * num_local_experts,
699 top_k=num_experts_per_token,
700 hidden_size=self.moe_hidden_size,
701 intermediate_size=self.padded_moe_intermediate_size,
702 prefix=f"{prefix}.experts",
703 activation="situ",
704 activation_beta=activation_situ_beta,
705 activation_linear_beta=activation_situ_linear_beta,
706 )
707 else:
708 self.experts = FusedMoEFactory(
709 shared_experts=self.shared_experts,
710 num_experts=num_experts,
711 top_k=num_experts_per_token,
712 hidden_size=self.moe_hidden_size,
713 intermediate_size=self.padded_moe_intermediate_size,
714 activation=config.hidden_act,
715 activation_situ_beta=activation_situ_beta,
716 activation_situ_linear_beta=activation_situ_linear_beta,
717 renormalize=moe_renormalize,
718 quant_config=quant_config,
719 use_grouped_topk=config.use_grouped_topk,
720 num_expert_group=config.num_expert_group,
721 topk_group=config.topk_group,
722 prefix=f"{prefix}.experts",
723 scoring_func=config.moe_router_activation_func,
724 e_score_correction_bias=self.gate.e_score_correction_bias,
725 routed_scaling_factor=self.routed_scaling_factor,
726 # Down projection runs outside MoERunner so it can overlap the
727 # router gate on the aux stream (see forward()); the original
728 # hidden states are passed to forward() as shared_experts_input
729 # so shared experts still see the untransformed input.
730 routed_input_transform=None,
731 routed_output_transform=self.routed_output_transform,
732 is_sequence_parallel=use_sequence_parallel,
733 skip_padding=True,
734 runner_cls=LatentMoERunner if self.use_latent_moe else None,
735 )
736 if self.padded_moe_intermediate_size != moe_intermediate_size:
737 w13_weight = getattr(self.experts, "w13_weight", None)
738 if w13_weight is None:
739 w13_weight = getattr(self.experts, "w13_weight_packed", None)
740 w2_weight = getattr(self.experts, "w2_weight", None)
741 if w2_weight is None:
742 w2_weight = getattr(self.experts, "w2_weight_packed", None)
743 if w13_weight is not None:
744 w13_weight.data.zero_()
745 if w2_weight is not None:
746 w2_weight.data.zero_()
747 self.experts.moe_config.intermediate_size_per_partition_unpadded = (
748 moe_intermediate_size // self.tp_size
749 )
750
751 def _maybe_overlap_router_and_down_proj(
752 self, hidden_states: torch.Tensor
753 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]:
754 """Compute the routed-expert down projection alongside the router,
755 overlapping them on separate CUDA streams when latent MoE is enabled.
756
757 The router gate and the down projection both read ``hidden_states``, so
758 the gate runs on the default stream and the down projection on the aux
759 stream, joined via ``maybe_execute_in_parallel``. For MegaMoE the
760 grouped top-k selection consumes only the gate logits, so it also runs
761 on the default stream and overlaps the down projection.
762
763 Returns:
764 ``(routed_hidden_states, router_output, topk_ids)``.
765 ``routed_hidden_states`` is the down-projected latent (or the
766 original ``hidden_states`` when latent MoE is disabled). For MegaMoE
767 ``router_output`` holds the grouped top-k weights and ``topk_ids``
768 the selected experts; otherwise ``router_output`` holds the raw gate
769 logits and ``topk_ids`` is ``None``.
770
771 """
772
773 def _router(
774 hidden_states: torch.Tensor,
775 ) -> tuple[torch.Tensor, torch.Tensor | None]:
776 router_logits, _ = self.gate(hidden_states)
777 if not self.use_mega_moe:
778 return router_logits, None
779 return fused_grouped_topk(
780 hidden_states=hidden_states,
781 gating_output=router_logits,
782 topk=self.experts.top_k,
783 renormalize=self.moe_renormalize,
784 e_score_correction_bias=self.gate.e_score_correction_bias.data,
785 num_expert_group=self.num_expert_group,
786 topk_group=self.topk_group,
787 scoring_func=self.moe_router_activation_func,
788 routed_scaling_factor=self.routed_scaling_factor,
789 )
790
791 down_proj = self.routed_expert_down_proj
792 if down_proj is None:
793 router_output, topk_ids = _router(hidden_states)
794 return hidden_states, router_output, topk_ids
795 num_tokens = hidden_states.shape[0]
796 (router_output, topk_ids), (routed_hidden_states, _) = (
797 maybe_execute_in_parallel(
798 lambda: _router(hidden_states),
799 lambda: down_proj(hidden_states),
800 self._down_proj_events[0],
801 self._down_proj_events[1],
802 self._down_proj_stream
803 if num_tokens <= _ROUTED_DOWN_PROJ_STREAM_TOKEN_THRESHOLD
804 else None,
805 )
806 )
807 return routed_hidden_states, router_output, topk_ids
808
809 def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
810 num_tokens, hidden_size = hidden_states.shape
811 hidden_states = hidden_states.view(-1, hidden_size)
812 # Overlap the gate with the routed down projection; the returned hidden
813 # states are already down-projected. Keep the original ``hidden_states``
814 # for the shared experts.
815 routed_hidden_states, router_output, topk_ids = (
816 self._maybe_overlap_router_and_down_proj(hidden_states)
817 )
818 if self.use_mega_moe:
819 assert self.routed_output_transform is not None
820 assert topk_ids is not None
821 final_hidden_states = self.experts(
822 routed_hidden_states,
823 router_output,
824 topk_ids,
825 activation_clamp=None,
826 )
827 # The shared output is folded into the up-projection GEMM's beta-add
828 # epilogue, so combining the two branches costs no extra kernel.
829 shared_output = (
830 self.shared_experts(hidden_states)
831 if self.shared_experts is not None
832 else None
833 )
834 final_hidden_states = self.routed_output_transform(
835 final_hidden_states, residual=shared_output
836 )
837 else:
838 # Routed experts consume the down-projected latent; shared experts
839 # (inside MoERunner) get the original hidden states via
840 # shared_experts_input.
841 final_hidden_states = self.experts(
842 hidden_states=routed_hidden_states,
843 router_logits=router_output,
844 shared_experts_input=hidden_states,
845 )
846 return final_hidden_states.view(num_tokens, hidden_size)
847
848
849 class KimiDecoderLayer(nn.Module):
850 def __init__(
851 self,
852 config: KimiLinearConfig,
853 vllm_config: VllmConfig,
854 prefix: str = "",
855 aux_stream: torch.cuda.Stream | None = None,
856 run_gemm_rs_ar: bool = False,
857 ) -> None:
858 super().__init__()
859 self.hidden_size = config.hidden_size
860 self.layer_idx = int(prefix.rsplit(".", 1)[1])
861
862 self.is_moe = config.is_moe
863 layer_idx = self.layer_idx
864 cache_config = vllm_config.cache_config
865 quant_config = vllm_config.quant_config
866 parallel_config = vllm_config.parallel_config
867 self.is_moe_layer = (
868 self.is_moe
869 and config.num_experts is not None
870 and layer_idx >= config.first_k_dense_replace
871 and layer_idx % config.moe_layer_freq == 0
872 )
873
874 use_mega_moe = vllm_config.kernel_config.moe_backend == "deep_gemm_mega_moe"
875 self.use_sequence_parallel = (
876 parallel_config.pipeline_parallel_size == 1
877 and parallel_config.enable_expert_parallel
878 and parallel_config.tensor_parallel_size > 1
879 and (use_mega_moe or parallel_config.data_parallel_size > 1)
880 )
881 if config.is_kda_layer(layer_idx):
882 kda_config = config.linear_attn_config
883 assert kda_config is not None
884 # This class also serves standalone Kimi-Linear through the model
885 # registry. Only Kimi-K3's full-rank gate uses the private KDA path.
886 if kda_config.get("use_full_rank_gate", False):
887 self.self_attn = KimiK3DeltaAttention(
888 config,
889 vllm_config,
890 prefix=f"{prefix}.self_attn",
891 aux_stream=aux_stream,
892 run_gemm_rs_ar=run_gemm_rs_ar,
893 )
894 else:
895 self.self_attn = KimiLinearGatedDeltaNetAttention(
896 config,
897 vllm_config,
898 prefix=f"{prefix}.self_attn",
899 )
900 else:
901 qk_nope_head_dim = config.qk_nope_head_dim
902 qk_rope_head_dim = config.qk_rope_head_dim
903 v_head_dim = config.v_head_dim
904 kv_lora_rank = config.kv_lora_rank
905 mla_use_nope = config.mla_use_nope
906 assert qk_nope_head_dim is not None
907 assert qk_rope_head_dim is not None
908 assert v_head_dim is not None
909 assert kv_lora_rank is not None
910 assert mla_use_nope, "Kimi-K3 MLA (MultiHeadLatentAttention) is NoPE-only"
911 # q_lora_rank may be None (Kimi-Linear): the MLA layer then uses an
912 # uncompressed q_proj instead of the fused q-LoRA front-end.
913 self.self_attn = MultiHeadLatentAttention(
914 config=config,
915 hidden_size=self.hidden_size,
916 num_heads=config.num_attention_heads,
917 qk_nope_head_dim=qk_nope_head_dim,
918 qk_rope_head_dim=qk_rope_head_dim,
919 v_head_dim=v_head_dim,
920 q_lora_rank=config.q_lora_rank,
921 kv_lora_rank=kv_lora_rank,
922 use_output_gate=bool(config.mla_use_output_gate),
923 cache_config=cache_config,
924 quant_config=quant_config,
925 prefix=f"{prefix}.self_attn",
926 aux_stream=aux_stream,
927 run_gemm_rs_ar=run_gemm_rs_ar,
928 )
929
930 if self.use_sequence_parallel:
931 self.self_attn.o_proj.reduce_results = False
932
933 if self.is_moe_layer:
934 self.block_sparse_moe = KimiMoE(
935 config=config,
936 vllm_config=vllm_config,
937 quant_config=quant_config,
938 prefix=f"{prefix}.block_sparse_moe",
939 layer_idx=layer_idx,
940 use_sequence_parallel=self.use_sequence_parallel,
941 run_gemm_rs_ar=run_gemm_rs_ar,
942 )
943 self.mlp = self.block_sparse_moe
944 else:
945 self.mlp = KimiMLP(
946 hidden_size=self.hidden_size,
947 intermediate_size=config.intermediate_size,
948 hidden_act=config.hidden_act,
949 quant_config=quant_config,
950 prefix=f"{prefix}.mlp",
951 use_sequence_parallel=self.use_sequence_parallel,
952 can_shard_sequence_parallel=True,
953 run_gemm_rs_ar=run_gemm_rs_ar,
954 activation_situ_beta=config.activation_situ_beta,
955 activation_situ_linear_beta=config.activation_situ_linear_beta,
956 )
957 self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
958 self.post_attention_layernorm = RMSNorm(
959 config.hidden_size, eps=config.rms_norm_eps
960 )
961
962 attn_res_block_size = config.attn_res_block_size
963 self.use_attn_res = attn_res_block_size is not None
964 if self.use_attn_res:
965 assert attn_res_block_size is not None
966 self.attn_res_block_size = attn_res_block_size
967 self.is_block_write_layer = layer_idx % self.attn_res_block_size == 0
968 self.block_write_idx = layer_idx // self.attn_res_block_size
969 self.prev_valid_blocks = cdiv(layer_idx, self.attn_res_block_size)
970 self.self_attention_res_norm = RMSNorm(
971 config.hidden_size, eps=config.rms_norm_eps
972 )
973 self.mlp_res_norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
974 self.self_attention_res_proj = ReplicatedLinear(
975 config.hidden_size,
976 1,
977 bias=False,
978 quant_config=None,
979 prefix=f"{prefix}.self_attention_res_proj",
980 )
981 self.mlp_res_proj = ReplicatedLinear(
982 config.hidden_size,
983 1,
984 bias=False,
985 quant_config=None,
986 prefix=f"{prefix}.mlp_res_proj",
987 )
988
989 def _run_self_attn(
990 self,
991 positions: torch.Tensor,
992 hidden_states: torch.Tensor,
993 ) -> torch.Tensor:
994 return self.self_attn(
995 hidden_states=hidden_states,
996 positions=positions,
997 )
998
999 def _pre_attn_norm(
1000 self,
1001 hidden_states: torch.Tensor | None,
1002 residual: torch.Tensor | None,
1003 prefix_sum: torch.Tensor | None,
1004 ) -> tuple[torch.Tensor, torch.Tensor | None, torch.Tensor]:
1005 if not self.use_attn_res:
1006 assert hidden_states is not None
1007 if residual is None:
1008 residual = hidden_states
1009 hidden_states = self.input_layernorm(hidden_states)
1010 else:
1011 hidden_states, residual = self.input_layernorm(hidden_states, residual)
1012 return hidden_states, prefix_sum, residual
1013
1014 assert prefix_sum is not None
1015 assert residual is not None
1016 hidden_states = attn_res(
1017 prefix_sum,
1018 hidden_states,
1019 residual,
1020 self.self_attention_res_norm.weight,
1021 self.self_attention_res_proj.weight.squeeze(0),
1022 self.input_layernorm.weight,
1023 num_blocks=self.prev_valid_blocks,
1024 block_write_idx=(self.block_write_idx if self.is_block_write_layer else -1),
1025 eps=self.self_attention_res_norm.variance_epsilon,
1026 output_norm_eps=self.input_layernorm.variance_epsilon,
1027 )
1028 return hidden_states, prefix_sum, residual
1029
1030 def _post_attn_norm(
1031 self,
1032 hidden_states: torch.Tensor,
1033 residual: torch.Tensor,
1034 prefix_sum: torch.Tensor | None,
1035 ) -> tuple[torch.Tensor, torch.Tensor | None, torch.Tensor]:
1036 if not self.use_attn_res:
1037 hidden_states, residual = self.post_attention_layernorm(
1038 hidden_states, residual
1039 )
1040 return hidden_states, prefix_sum, residual
1041
1042 assert prefix_sum is not None
1043 if self.is_block_write_layer:
1044 prefix_sum = hidden_states
1045 prefix_delta = None
1046 else:
1047 prefix_delta = hidden_states
1048 mlp_valid_blocks = self.prev_valid_blocks + self.is_block_write_layer
1049 hidden_states = attn_res(
1050 prefix_sum,
1051 prefix_delta,
1052 residual,
1053 self.mlp_res_norm.weight,
1054 self.mlp_res_proj.weight.squeeze(0),
1055 self.post_attention_layernorm.weight,
1056 num_blocks=mlp_valid_blocks,
1057 block_write_idx=-1,
1058 eps=self.mlp_res_norm.variance_epsilon,
1059 output_norm_eps=self.post_attention_layernorm.variance_epsilon,
1060 )
1061 return hidden_states, prefix_sum, residual
1062
1063 def forward(
1064 self,
1065 positions: torch.Tensor,
1066 hidden_states: torch.Tensor | None,
1067 residual: torch.Tensor | None,
1068 prefix_sum: torch.Tensor | None = None,
1069 **kwargs,
1070 ) -> tuple[torch.Tensor, torch.Tensor | None, torch.Tensor]:
1071 hidden_states, prefix_sum, residual = self._pre_attn_norm(
1072 hidden_states, residual, prefix_sum
1073 )
1074 assert hidden_states is not None
1075
1076 M = None
1077 if self.use_sequence_parallel:
1078 hidden_states = sp_all_gather(hidden_states)
1079 # Remove SP padding before attention.
1080 hidden_states = hidden_states[: positions.shape[0]]
1081 M = hidden_states.shape[0]
1082
1083 # Attention.
1084 hidden_states = self._run_self_attn(positions, hidden_states)
1085
1086 # GEMM-RS returns the local sequence shard; standard O-proj preserves M.
1087 if self.use_sequence_parallel and hidden_states.shape[0] == M:
1088 # Add SP padding if needed, and then perform reduce scatter.
1089 hidden_states = sp_reduce_scatter(hidden_states)
1090
1091 hidden_states, prefix_sum, residual = self._post_attn_norm(
1092 hidden_states, residual, prefix_sum
1093 )
1094
1095 # MoE/MLP.
1096 hidden_states = self.mlp(hidden_states)
1097 return hidden_states, prefix_sum, residual
1098
1099
1100 class KimiLinearModel(nn.Module, EagleModelMixin, SupportsQuant):
1101 packed_modules_mapping = {
1102 "gate_up_proj": ["gate_proj", "up_proj"],
1103 "in_proj_qkvgfab": ["q_proj", "k_proj", "v_proj", "b_proj", "f_a_proj"],
1104 "conv1d": ["q_conv1d", "k_conv1d", "v_conv1d"],
1105 "fused_qkv_a_proj": ["q_a_proj", "kv_a_proj_with_mqa"],
1106 "fused_qkv_a_g_proj": ["q_a_proj", "kv_a_proj_with_mqa", "g_proj"],
1107 }
1108
1109 supports_aux_hidden_states_over_pp = True
1110
1111 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1112 super().__init__()
1113
1114 config = vllm_config.model_config.hf_text_config
1115 self.config = config
1116 self.attn_res_block_size: int | None = config.attn_res_block_size
1117 self.use_attn_res = self.attn_res_block_size is not None
1118 parallel_config = vllm_config.parallel_config
1119 use_mega_moe = vllm_config.kernel_config.moe_backend == "deep_gemm_mega_moe"
1120 self.use_sequence_parallel = (
1121 parallel_config.pipeline_parallel_size == 1
1122 and parallel_config.enable_expert_parallel
1123 and parallel_config.tensor_parallel_size > 1
1124 and (use_mega_moe or parallel_config.data_parallel_size > 1)
1125 )
1126
1127 self.vocab_size = config.vocab_size
1128
1129 # GEMM-RS/AR uses NCCL symmetric-memory multicast, which requires all
1130 # TP ranks to belong to one NVLink domain.
1131 self.run_gemm_rs_ar = maybe_init_gemm_rs_ar(
1132 vllm_config, self.use_sequence_parallel
1133 )
1134
1135 if get_pp_group().is_first_rank or spec_decode_needs_target_embed(vllm_config):
1136 self.embed_tokens = VocabParallelEmbedding(
1137 config.vocab_size,
1138 config.hidden_size,
1139 prefix=f"{prefix}.embed_tokens",
1140 )
1141 else:
1142 self.embed_tokens = PPMissingLayer()
1143
1144 # Aux stream for overlapping the MLA g_proj output-gate GEMM with the
1145 # attention front-end (DeepseekV4 convention: created at the model
1146 # level and threaded into each attention layer).
1147 aux_stream = torch.cuda.Stream()
1148
1149 def get_layer(prefix: str):
1150 return KimiDecoderLayer(
1151 config,
1152 vllm_config,
1153 prefix,
1154 aux_stream=aux_stream,
1155 run_gemm_rs_ar=self.run_gemm_rs_ar,
1156 )
1157
1158 self.start_layer, self.end_layer, self.layers = make_layers(
1159 config.num_hidden_layers,
1160 get_layer,
1161 prefix=f"{prefix}.layers",
1162 )
1163 self.num_attn_res_blocks = (
1164 cdiv(self.end_layer, self.attn_res_block_size)
1165 if self.attn_res_block_size is not None
1166 else 0
1167 )
1168
1169 if get_pp_group().is_last_rank:
1170 self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
1171 if self.use_attn_res:
1172 self.output_attn_res_norm = RMSNorm(
1173 config.hidden_size, eps=config.rms_norm_eps
1174 )
1175 self.output_attn_res_proj = ReplicatedLinear(
1176 config.hidden_size,
1177 1,
1178 bias=False,
1179 quant_config=None,
1180 prefix=f"{prefix}.output_attn_res_proj",
1181 )
1182 else:
1183 self.norm = PPMissingLayer()
1184 if self.use_attn_res:
1185 self.output_attn_res_norm = PPMissingLayer()
1186 self.output_attn_res_proj = PPMissingLayer()
1187
1188 world_size = get_tensor_model_parallel_world_size()
1189 assert config.num_attention_heads % world_size == 0, (
1190 "num_attention_heads must be divisible by world_size"
1191 )
1192
1193 def make_empty_intermediate_tensors(
1194 self,
1195 batch_size: int,
1196 dtype: torch.dtype,
1197 device: torch.device,
1198 ) -> IntermediateTensors:
1199 residual_shape: tuple[int, ...] = (batch_size, self.config.hidden_size)
1200 if self.use_attn_res:
1201 assert self.attn_res_block_size is not None
1202 residual_shape = (
1203 batch_size,
1204 cdiv(self.start_layer, self.attn_res_block_size),
1205 self.config.hidden_size,
1206 )
1207 return IntermediateTensors(
1208 {
1209 "hidden_states": torch.zeros(
1210 (batch_size, self.config.hidden_size), dtype=dtype, device=device
1211 ),
1212 "residual": torch.zeros(residual_shape, dtype=dtype, device=device),
1213 }
1214 )
1215
1216 def _set_aux_hidden_state_layers(self, layers: tuple[int, ...]) -> None:
1217 super()._set_aux_hidden_state_layers(layers)
1218 if self.use_attn_res and self._aux_attn_res_stream:
1219 pp = get_pp_group()
1220 if not pp.is_last_rank and self.end_layer in self.aux_hidden_state_layers:
1221 raise ValueError(
1222 f"Auxiliary layer {self.end_layer} cannot end a non-final PP "
1223 "stage when VLLM_KIMI_K3_AUX_ATTN_RES_STREAM=1"
1224 )
1225 if self.use_attn_res:
1226 logger.info_once(
1227 "Kimi-K3 aux hidden capture: layers=%s mode=%s "
1228 "(VLLM_KIMI_K3_AUX_ATTN_RES_STREAM=%d)",
1229 self.aux_hidden_state_layers,
1230 "attn_res_stream" if self._aux_attn_res_stream else "prefix_only",
1231 int(self._aux_attn_res_stream),
1232 )
1233
1234 @property
1235 def _aux_attn_res_stream(self) -> bool:
1236 return envs.VLLM_KIMI_K3_AUX_ATTN_RES_STREAM
1237
1238 def _capture_aux_hidden_stream(
1239 self,
1240 layer_idx: int,
1241 prefix_sum: torch.Tensor,
1242 pending_mlp_out: torch.Tensor | None,
1243 block_residual: torch.Tensor,
1244 ) -> torch.Tensor:
1245 """Return the AttnRes stream after ``layer_idx``."""
1246 prefix = prefix_sum if pending_mlp_out is None else prefix_sum + pending_mlp_out
1247 if not (self._aux_attn_res_stream and self.use_attn_res):
1248 return prefix
1249
1250 if layer_idx + 1 < self.end_layer:
1251 consumer = self.layers[layer_idx + 1]
1252 score_norm = consumer.self_attention_res_norm
1253 score_proj = consumer.self_attention_res_proj
1254 num_blocks = consumer.prev_valid_blocks
1255 elif get_pp_group().is_last_rank:
1256 score_norm = self.output_attn_res_norm
1257 score_proj = self.output_attn_res_proj
1258 num_blocks = self.num_attn_res_blocks
1259 else:
1260 raise RuntimeError("Auxiliary AttnRes capture crossed a PP boundary")
1261
1262 return attn_res(
1263 prefix,
1264 None,
1265 block_residual,
1266 score_norm.weight,
1267 score_proj.weight.squeeze(0),
1268 None,
1269 num_blocks=num_blocks,
1270 block_write_idx=-1,
1271 eps=score_norm.variance_epsilon,
1272 output_norm_eps=0.0,
1273 )
1274
1275 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
1276 return self.embed_tokens(input_ids)
1277
1278 def forward(
1279 self,
1280 input_ids: torch.Tensor | None,
1281 positions: torch.Tensor,
1282 intermediate_tensors: IntermediateTensors | None,
1283 inputs_embeds: torch.Tensor | None = None,
1284 **kwargs,
1285 ) -> torch.Tensor | IntermediateTensors | tuple[torch.Tensor, list[torch.Tensor]]:
1286 if get_pp_group().is_first_rank:
1287 if inputs_embeds is not None:
1288 hidden_states = inputs_embeds
1289 else:
1290 hidden_states = self.embed_input_ids(input_ids)
1291 residual = None
1292 else:
1293 assert intermediate_tensors is not None
1294 hidden_states = intermediate_tensors["hidden_states"]
1295 residual = intermediate_tensors["residual"]
1296 assert hidden_states is not None
1297
1298 full_num_tokens = positions.shape[0]
1299 if self.use_sequence_parallel:
1300 if envs.VLLM_MOE_SKIP_PADDING and is_forward_context_available():
1301 forward_context = get_forward_context()
1302 forward_context.is_padding = sp_padding_mask(
1303 forward_context.is_padding, hidden_states
1304 )
1305 hidden_states = sp_shard(hidden_states)
1306 assert residual is None, "Currently, SP is not supported with PP"
1307
1308 remote_aux = self.collect_remote_aux_hidden_states(intermediate_tensors)
1309
1310 # sharded aux hidden states when sp is enabled
1311 aux_hidden_states: list[torch.Tensor] = []
1312 if (
1313 get_pp_group().is_first_rank
1314 and self.start_layer in self.aux_hidden_state_layers
1315 ):
1316 if self.use_attn_res or residual is None:
1317 aux_hidden_states.append(hidden_states)
1318 else:
1319 aux_hidden_states.append(hidden_states + residual)
1320
1321 prefix_sum = None
1322 if self.use_attn_res:
1323 block_residual = hidden_states.new_empty(
1324 hidden_states.size(0),
1325 self.num_attn_res_blocks,
1326 hidden_states.size(1),
1327 )
1328 if residual is not None:
1329 block_residual[:, : residual.size(1), :].copy_(residual)
1330 prefix_sum = hidden_states
1331 hidden_states = None
1332 residual = block_residual
1333
1334 for layer_idx, layer in enumerate(
1335 self.layers[self.start_layer : self.end_layer],
1336 start=self.start_layer,
1337 ):
1338 hidden_states, prefix_sum, residual = layer(
1339 positions=positions,
1340 hidden_states=hidden_states,
1341 prefix_sum=prefix_sum,
1342 residual=residual,
1343 )
1344 if (layer_idx + 1) in self.aux_hidden_state_layers:
1345 if self.use_attn_res:
1346 assert prefix_sum is not None
1347 assert residual is not None
1348 aux_hidden_state = self._capture_aux_hidden_stream(
1349 layer_idx, prefix_sum, hidden_states, residual
1350 )
1351 else:
1352 assert residual is not None
1353 aux_hidden_state = hidden_states + residual
1354
1355 aux_hidden_states.append(aux_hidden_state)
1356
1357 assert hidden_states is not None
1358 assert residual is not None
1359 if not get_pp_group().is_last_rank:
1360 assert not self.use_sequence_parallel, (
1361 "Currently, SP is not supported with PP"
1362 )
1363 if prefix_sum is not None:
1364 hidden_states = hidden_states + prefix_sum
1365 return IntermediateTensors(
1366 {
1367 "hidden_states": hidden_states,
1368 "residual": residual,
1369 **self.pack_local_aux_hidden_states(aux_hidden_states),
1370 }
1371 )
1372
1373 if self.use_attn_res:
1374 assert prefix_sum is not None
1375 hidden_states = attn_res(
1376 prefix_sum,
1377 hidden_states,
1378 residual,
1379 self.output_attn_res_norm.weight,
1380 self.output_attn_res_proj.weight.squeeze(0),
1381 None,
1382 num_blocks=self.num_attn_res_blocks,
1383 block_write_idx=-1,
1384 eps=self.output_attn_res_norm.variance_epsilon,
1385 output_norm_eps=0.0,
1386 )
1387 else:
1388 hidden_states = hidden_states + residual
1389
1390 if self.use_sequence_parallel:
1391 if aux_hidden_states:
1392 hidden_size = hidden_states.shape[-1]
1393 packed_hidden_states = torch.cat(
1394 [hidden_states, *aux_hidden_states], dim=-1
1395 )
1396 packed_hidden_states = sp_all_gather(packed_hidden_states)
1397 packed_hidden_states = packed_hidden_states[:full_num_tokens]
1398 hidden_states, *aux_hidden_states = packed_hidden_states.split(
1399 hidden_size, dim=-1
1400 )
1401 else:
1402 hidden_states = sp_all_gather(hidden_states)
1403 hidden_states = hidden_states[:full_num_tokens]
1404
1405 # NOTE: the final norm is applied in compute_logits instead of here, so
1406 # the MTP draft model receives the pre-norm hidden states.
1407 aux_hidden_states = remote_aux + aux_hidden_states
1408 if aux_hidden_states:
1409 return hidden_states, aux_hidden_states
1410 return hidden_states
1411
1412 def load_weights(
1413 self,
1414 weights: Iterable[
1415 tuple[str, torch.Tensor] | tuple[str, torch.Tensor, dict[str, Any]]
1416 ],
1417 ) -> set[str]:
1418 kda_config = self.config.linear_attn_config
1419 use_full_rank_gate = bool(
1420 kda_config and kda_config.get("use_full_rank_gate", False)
1421 )
1422 beta_shard_id = 5 if use_full_rank_gate else 3
1423 stacked_params_mapping = [
1424 # (param_name, shard_name, shard_id)
1425 (".in_proj_qkvgfab", ".q_proj", 0),
1426 (".in_proj_qkvgfab", ".k_proj", 1),
1427 (".in_proj_qkvgfab", ".v_proj", 2),
1428 (".in_proj_qkvgfab", ".b_proj", beta_shard_id),
1429 (".in_proj_qkvgfab", ".f_a_proj", 4),
1430 (".conv1d", ".q_conv1d", 0),
1431 (".conv1d", ".k_conv1d", 1),
1432 (".conv1d", ".v_conv1d", 2),
1433 (".gate_up_proj", ".gate_proj", 0),
1434 (".gate_up_proj", ".up_proj", 1),
1435 ]
1436 if use_full_rank_gate:
1437 stacked_params_mapping.append((".in_proj_qkvgfab", ".g_proj", 3))
1438 if getattr(self.config, "q_lora_rank", None) is not None:
1439 if self.config.mla_use_output_gate:
1440 stacked_params_mapping += [
1441 (".fused_qkv_a_g_proj", ".q_a_proj", 0),
1442 (".fused_qkv_a_g_proj", ".kv_a_proj_with_mqa", 1),
1443 (".fused_qkv_a_g_proj", ".g_proj", 2),
1444 ]
1445 else:
1446 stacked_params_mapping += [
1447 (".fused_qkv_a_proj", ".q_a_proj", 0),
1448 (".fused_qkv_a_proj", ".kv_a_proj_with_mqa", 1),
1449 ]
1450 use_mega_moe = any(
1451 module.use_mega_moe
1452 for module in self.modules()
1453 if isinstance(module, KimiMoE)
1454 )
1455 if self.config.is_moe and use_mega_moe:
1456 expert_params_mapping = make_kimi_k3_mega_moe_expert_params_mapping(
1457 self.config.num_experts
1458 )
1459 elif self.config.is_moe:
1460 # Params for weights, fp8 weight scales, fp8 activation scales
1461 # (param_name, weight_name, expert_id, shard_id)
1462 expert_params_mapping = fused_moe_make_expert_params_mapping(
1463 self,
1464 ckpt_gate_proj_name="w1",
1465 ckpt_down_proj_name="w2",
1466 ckpt_up_proj_name="w3",
1467 num_experts=self.config.num_experts,
1468 )
1469 else:
1470 expert_params_mapping = []
1471 params_dict = dict(self.named_parameters())
1472
1473 # Under the MXFP4 quant interface the routed experts register unpacked
1474 # params (``w13_weight``), while the compressed-tensors checkpoint names
1475 # them ``.weight_packed``. Rebind so the expert mapping resolves; scales
1476 # already share the ``.weight_scale`` suffix.
1477 experts_unpacked = not use_mega_moe and not any(
1478 n.endswith("w13_weight_packed") for n in params_dict
1479 )
1480 loaded_params: set[str] = set()
1481 for args in weights:
1482 name, loaded_weight = args[0], args[1]
1483 kwargs: dict[str, Any] = args[2] if len(args) > 2 else {}
1484 if "rotary_emb.inv_freq" in name:
1485 continue
1486 if experts_unpacked and name.endswith(".weight_packed"):
1487 name = name.replace(".weight_packed", ".weight")
1488
1489 spec_layer = get_spec_layer_idx_from_weight_name(self.config, name)
1490 if spec_layer is not None:
1491 continue # skip spec decode layers for main model
1492 if "rotary_emb.cos_cached" in name or "rotary_emb.sin_cached" in name:
1493 # Models trained using ColossalAI may include these tensors in
1494 # the checkpoint. Skip them.
1495 continue
1496 for param_name, weight_name, shard_id in stacked_params_mapping:
1497 if weight_name not in name:
1498 continue
1499 # We have mlp.experts[0].gate_proj in the checkpoint.
1500 # Since we handle the experts below in expert_params_mapping,
1501 # we need to skip here BEFORE we update the name, otherwise
1502 # name will be updated to mlp.experts[0].gate_up_proj, which
1503 # will then be updated below in expert_params_mapping
1504 # for mlp.experts[0].gate_gate_up_proj, which breaks load.
1505 if ("mlp.experts." in name) and name not in params_dict:
1506 continue
1507 name_mapped = name.replace(weight_name, param_name)
1508 # Packed projections are only present on compatible layers.
1509 if name_mapped not in params_dict:
1510 continue
1511 name = name_mapped
1512 # Skip loading extra bias for GPTQ models.
1513 if name.endswith(".bias") and name not in params_dict:
1514 continue
1515 if is_pp_missing_parameter(name, self):
1516 continue
1517 param = params_dict[name]
1518 weight_loader = param.weight_loader
1519 weight_loader(param, loaded_weight, shard_id)
1520 break
1521 else:
1522 for (
1523 expert_param_name,
1524 expert_weight_name,
1525 expert_id,
1526 expert_shard_id,
1527 ) in expert_params_mapping:
1528 if expert_weight_name not in name:
1529 continue
1530 name = name.replace(expert_weight_name, expert_param_name)
1531 if is_pp_missing_parameter(name, self):
1532 continue
1533 param = params_dict[name]
1534 weight_loader = param.weight_loader
1535 weight_loader(
1536 param,
1537 loaded_weight,
1538 name,
1539 expert_id=expert_id,
1540 shard_id=expert_shard_id,
1541 )
1542 break
1543 else:
1544 # Skip loading extra bias for GPTQ models.
1545 if (
1546 name.endswith(".bias")
1547 and name not in params_dict
1548 and not self.config.is_linear_attn
1549 ): # noqa: E501
1550 continue
1551 # Remapping the name of FP8 kv-scale.
1552 remapped_name = maybe_remap_kv_scale_name(name, params_dict)
1553 if remapped_name is None:
1554 continue
1555 name = remapped_name
1556 if is_pp_missing_parameter(name, self):
1557 continue
1558
1559 param = params_dict[name]
1560 weight_loader = getattr(
1561 param, "weight_loader", default_weight_loader
1562 )
1563 weight_loader(param, loaded_weight, **kwargs)
1564 loaded_params.add(name)
1565 return loaded_params
1566
1567 def finalize_mega_moe_weights(self) -> None:
1568 for module in self.modules():
1569 if isinstance(module, KimiMoE) and module.use_mega_moe:
1570 module.experts.finalize_weights()
1571
1572
1573 class KimiLinearForCausalLM(
1574 nn.Module,
1575 HasInnerState,
1576 SupportsPP,
1577 MixtureOfExperts,
1578 IsHybrid,
1579 SupportsEagle3,
1580 SupportsReplaySSM,
1581 ):
1582 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1583 super().__init__()
1584 self.model_config = vllm_config.model_config
1585 self.vllm_config = vllm_config
1586 self.config = self.model_config.hf_config
1587 quant_config = vllm_config.quant_config
1588 self.quant_config = quant_config
1589 self.model = KimiLinearModel(
1590 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
1591 )
1592 if get_pp_group().is_last_rank:
1593 self.lm_head = ParallelLMHead(
1594 self.config.vocab_size,
1595 self.config.hidden_size,
1596 quant_config=quant_config,
1597 prefix=maybe_prefix(prefix, "lm_head"),
1598 )
1599 else:
1600 self.lm_head = PPMissingLayer()
1601 enable_kimi_k3_low_latency_gemm(self, self.model_config.dtype)
1602 logit_scale = getattr(self.config, "logit_scale", 1.0)
1603 self.logits_processor = LogitsProcessor(
1604 self.config.vocab_size, scale=logit_scale
1605 )
1606
1607 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
1608 return self.model.embed_input_ids(input_ids)
1609
1610 def make_empty_intermediate_tensors(
1611 self,
1612 batch_size: int,
1613 dtype: torch.dtype,
1614 device: torch.device,
1615 ) -> IntermediateTensors:
1616 return self.model.make_empty_intermediate_tensors(batch_size, dtype, device)
1617
1618 def forward( # type: ignore[override]
1619 self,
1620 input_ids: torch.Tensor | None,
1621 positions: torch.Tensor,
1622 intermediate_tensors: IntermediateTensors | None = None,
1623 inputs_embeds: torch.Tensor | None = None,
1624 **kwargs,
1625 ) -> torch.Tensor | IntermediateTensors | tuple[torch.Tensor, list[torch.Tensor]]:
1626 return self.model(
1627 input_ids, positions, intermediate_tensors, inputs_embeds, **kwargs
1628 )
1629
1630 @classmethod
1631 def get_mamba_state_dtype_from_config(
1632 cls,
1633 vllm_config: "VllmConfig",
1634 ) -> tuple[torch.dtype, ...]:
1635 dtypes = MambaStateDtypeCalculator.kda_state_dtype(
1636 vllm_config.model_config.dtype,
1637 vllm_config.cache_config.mamba_cache_dtype,
1638 vllm_config.cache_config.mamba_ssm_cache_dtype,
1639 )
1640 if vllm_config.cache_config.use_kda_recoverssm:
1641 dtypes = MambaStateDtypeCalculator.append_kda_recoverssm_record(
1642 dtypes, vllm_config.model_config.dtype
1643 )
1644 return dtypes
1645
1646 @classmethod
1647 def get_mamba_state_shape_from_config(
1648 cls, vllm_config: "VllmConfig"
1649 ) -> MambaStateShapes:
1650 parallel_config = vllm_config.parallel_config
1651 hf_config = vllm_config.model_config.hf_config
1652 tp_size = parallel_config.tensor_parallel_size
1653 num_spec = (
1654 vllm_config.speculative_config.num_speculative_tokens
1655 if vllm_config.speculative_config
1656 else 0
1657 )
1658 shapes = MambaStateShapeCalculator.kda_state_shape(
1659 tp_size,
1660 hf_config.linear_attn_config["num_heads"],
1661 hf_config.linear_attn_config["head_dim"],
1662 conv_kernel_size=hf_config.linear_attn_config["short_conv_kernel_size"],
1663 num_spec=num_spec,
1664 )
1665 if vllm_config.cache_config.use_kda_recoverssm:
1666 return MambaStateShapeCalculator.append_kda_recoverssm_record(
1667 shapes,
1668 hf_config.linear_attn_config["num_heads"],
1669 hf_config.linear_attn_config["head_dim"],
1670 tp_world_size=tp_size,
1671 spec_query_len=1 + num_spec,
1672 )
1673 return shapes
1674
1675 @classmethod
1676 def get_mamba_state_copy_func(
1677 cls,
1678 ) -> tuple[MambaStateCopyFunc, MambaStateCopyFunc]:
1679 return MambaStateCopyFuncCalculator.kda_state_copy_func()
1680
1681 def compute_logits(
1682 self,
1683 hidden_states: torch.Tensor,
1684 ) -> torch.Tensor | None:
1685 # The model's final norm is applied here (not at the end of forward) so
1686 # that the pre-norm hidden states can be fed to the MTP draft model.
1687 hidden_states = self.model.norm(hidden_states, None)
1688 return self.logits_processor(self.lm_head, hidden_states)
1689
1690 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1691 loader = AutoWeightsLoader(self)
1692 return loader.load_weights(weights)
1693
1694 def process_weights_after_loading(self) -> None:
1695 # A parent AutoWeightsLoader may invoke load_weights repeatedly for
1696 # non-contiguous streamed prefixes. Finalize only after the full stream.
1697 self.model.finalize_mega_moe_weights()
1698 # The fused MultiHeadLatentAttention's process_weights_after_loading
1699 # (W_UK_T / W_UV absorption) is driven by the loader's generic post-load
1700 # hook for any AttentionLayerBase, so no manual trigger is needed here.
1701
1702
1703 def get_spec_layer_idx_from_weight_name(
1704 config: KimiLinearConfig, weight_name: str
1705 ) -> int | None:
1706 if hasattr(config, "num_nextn_predict_layers") and (
1707 config.num_nextn_predict_layers > 0
1708 ):
1709 layer_idx = config.num_hidden_layers
1710 for i in range(config.num_nextn_predict_layers):
1711 # Match regardless of the surrounding prefix. The name may arrive as
1712 # ``model.layers.{i}.``, a bare ``layers.{i}.`` (after AutoWeightsLoader
1713 # has stripped the ``model.`` prefix in the main model), or with the
1714 # multimodal ``language_model.model.layers.{i}.`` prefix.
1715 if f"layers.{layer_idx + i}." in weight_name:
1716 return layer_idx + i
1717 return None
1718
1719
1720 @MULTIMODAL_REGISTRY.register_processor(
1721 KimiK3MultiModalProcessor,
1722 info=KimiK3ProcessingInfo,
1723 dummy_inputs=KimiK3DummyInputsBuilder,
1724 )
1725 class KimiK3ForConditionalGeneration(
1726 nn.Module,
1727 SupportsMultiModal,
1728 SupportsEncoderCudaGraph,
1729 SupportsPP,
1730 SupportsQuant,
1731 SupportsEagle3,
1732 HasInnerState,
1733 IsHybrid,
1734 SupportsReplaySSM,
1735 ):
1736 """Kimi-K3 model with Kimi-K2.5 vision and KimiLinear text."""
1737
1738 supports_encoder_tp_data = True
1739
1740 hf_to_vllm_mapper = WeightsMapper(
1741 orig_to_new_prefix={
1742 "language_model.layers.": "language_model.model.layers.",
1743 "mm_projector.proj.0": "mm_projector.linear_1",
1744 "mm_projector.proj.2": "mm_projector.linear_2",
1745 }
1746 )
1747
1748 @classmethod
1749 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
1750 if modality == "image":
1751 return "<|kimi_image_placeholder|>"
1752 raise ValueError(f"Unsupported modality: {modality}")
1753
1754 def __init__(
1755 self,
1756 vllm_config: VllmConfig,
1757 prefix: str = "",
1758 ) -> None:
1759 super().__init__()
1760 model_config = vllm_config.model_config
1761 config: KimiK3Config = model_config.hf_config
1762 self.config = config
1763 self.model_config = model_config
1764 quant_config = vllm_config.quant_config
1765
1766 multimodal_config = model_config.multimodal_config
1767 assert multimodal_config is not None
1768 self.use_data_parallel = is_vit_use_data_parallel(
1769 config.vision_config.num_attention_heads
1770 )
1771 self.hidden_size = config.text_config.hidden_size
1772 self.device = current_platform.current_device()
1773
1774 with self._mark_tower_model(vllm_config, "image"):
1775 self.vision_tower = MoonViT3dPretrainedModel(
1776 config.vision_config,
1777 quant_config=self._maybe_ignore_quant_config(quant_config),
1778 prefix=maybe_prefix(prefix, "vision_tower"),
1779 )
1780 if is_meta_module(self.vision_tower):
1781 pass
1782 elif self._maybe_ignore_quant_config(quant_config) is not None:
1783 self.vision_tower = self.vision_tower.to(device=self.device)
1784 else:
1785 self.vision_tower = self.vision_tower.to(
1786 device=self.device, dtype=model_config.dtype
1787 )
1788
1789 vision_attn = self.vision_tower.encoder.blocks[0].attn
1790 if vision_attn.is_flash_attn_backend and vision_attn._fa_version == 4:
1791 from vllm.models.kimi_k3.nvidia.ops.vision_fa4_warmup import (
1792 KimiK3VisionFA4WarmupConfig,
1793 register_kimi_k3_vision_fa4_warmup,
1794 )
1795
1796 merge_height, merge_width = config.vision_config.merge_kernel_size
1797 mm_config = model_config.get_multimodal_config()
1798 assert mm_config is not None
1799 register_kimi_k3_vision_fa4_warmup(
1800 KimiK3VisionFA4WarmupConfig(
1801 num_heads=vision_attn.num_heads,
1802 head_dim=vision_attn.head_size,
1803 dtype=vision_attn.dtype,
1804 max_batch_size=(
1805 vllm_config.scheduler_config.max_num_seqs
1806 * mm_config.get_limit_per_prompt("image")
1807 ),
1808 max_seqlen=(
1809 vllm_config.scheduler_config.max_num_encoder_input_tokens
1810 * merge_height
1811 * merge_width
1812 ),
1813 )
1814 )
1815
1816 self.mm_projector = KimiK25MultiModalProjector(
1817 config=config.vision_config,
1818 use_data_parallel=self.use_data_parallel,
1819 quant_config=self._maybe_ignore_quant_config(quant_config),
1820 prefix=maybe_prefix(prefix, "mm_projector"),
1821 )
1822 if not is_meta_module(self.mm_projector):
1823 self.mm_projector = self.mm_projector.to(
1824 device=self.device, dtype=model_config.dtype
1825 )
1826
1827 self.quant_config = quant_config
1828 with self._mark_language_model(vllm_config):
1829 self.language_model = init_vllm_registered_model(
1830 vllm_config=vllm_config,
1831 hf_config=config.text_config,
1832 prefix=maybe_prefix(prefix, "language_model"),
1833 architectures=["KimiLinearForCausalLM"],
1834 )
1835 self.make_empty_intermediate_tensors = ( # type: ignore[method-assign]
1836 self.language_model.make_empty_intermediate_tensors
1837 )
1838 self.media_placeholder: int = self.config.media_placeholder_token_id
1839
1840 # -- SupportsEncoderCudaGraph protocol methods --
1841
1842 def get_encoder_cudagraph_config(self):
1843 from vllm.v1.worker.encoder_cudagraph_defs import EncoderCudaGraphConfig
1844
1845 return EncoderCudaGraphConfig(
1846 modalities=["image"],
1847 buffer_keys=[
1848 "pixel_values",
1849 "pos_embeds",
1850 "rope_freqs_cis",
1851 "cu_seqlens",
1852 "max_seqlen",
1853 "sequence_lengths",
1854 "merge_gather_idx",
1855 ],
1856 out_hidden_size=self.hidden_size,
1857 )
1858
1859 def get_encoder_cudagraph_budget_range(
1860 self, vllm_config: VllmConfig
1861 ) -> tuple[int, int]:
1862 min_budget = 64
1863 max_budget = min(
1864 vllm_config.scheduler_config.max_num_batched_tokens,
1865 self.model_config.max_model_len,
1866 )
1867 return min_budget, max_budget
1868
1869 @staticmethod
1870 def _get_grid_thws(mm_kwargs: dict[str, Any]) -> list[list[int]]:
1871 grid_thws = mm_kwargs["grid_thws"]
1872 if not isinstance(grid_thws, list):
1873 grid_thws = grid_thws.tolist()
1874 return grid_thws
1875
1876 @staticmethod
1877 def _get_pixel_values(mm_kwargs: dict[str, Any]) -> torch.Tensor:
1878 pixel_values = mm_kwargs["pixel_values"]
1879 if isinstance(pixel_values, list):
1880 pixel_values = torch.cat(pixel_values)
1881 if pixel_values.ndim in (3, 5):
1882 pixel_values = pixel_values.reshape(
1883 pixel_values.shape[0] * pixel_values.shape[1],
1884 *pixel_values.shape[2:],
1885 )
1886 return pixel_values
1887
1888 def get_encoder_cudagraph_item_specs(self, mm_kwargs: dict[str, Any]):
1889 from vllm.v1.worker.encoder_cudagraph_defs import EncoderItemSpec
1890
1891 kh, kw = self.config.vision_config.merge_kernel_size
1892 return [
1893 EncoderItemSpec(
1894 input_size=t * h * w,
1895 output_tokens=(h // kh) * (w // kw),
1896 )
1897 for t, h, w in self._get_grid_thws(mm_kwargs)
1898 ]
1899
1900 def select_encoder_cudagraph_items(
1901 self, mm_kwargs: dict[str, Any], indices: list[int]
1902 ) -> dict[str, Any]:
1903 grid_thws = self._get_grid_thws(mm_kwargs)
1904 pixel_values = self._get_pixel_values(mm_kwargs)
1905 source_grid = mm_kwargs["grid_thws"]
1906
1907 if not indices:
1908 empty_grid = (
1909 source_grid[:0] if isinstance(source_grid, torch.Tensor) else []
1910 )
1911 return {"pixel_values": pixel_values[:0], "grid_thws": empty_grid}
1912
1913 patch_counts = [t * h * w for t, h, w in grid_thws]
1914 offsets = [0]
1915 for count in patch_counts:
1916 offsets.append(offsets[-1] + count)
1917 selected_pixel_values = torch.cat(
1918 [pixel_values[offsets[i] : offsets[i + 1]] for i in indices]
1919 )
1920 grid_device = (
1921 source_grid.device if isinstance(source_grid, torch.Tensor) else None
1922 )
1923 selected_grid = torch.tensor(
1924 [grid_thws[i] for i in indices],
1925 dtype=torch.long,
1926 device=grid_device,
1927 )
1928 return {"pixel_values": selected_pixel_values, "grid_thws": selected_grid}
1929
1930 def prepare_encoder_cudagraph_capture_inputs(
1931 self,
1932 token_budget: int,
1933 max_batch_size: int,
1934 max_frames_per_batch: int,
1935 device: torch.device,
1936 dtype: torch.dtype,
1937 path: str = "default",
1938 axis_keys: tuple[Hashable, ...] | None = None,
1939 ):
1940 from vllm.v1.worker.encoder_cudagraph_defs import (
1941 EncoderCudaGraphCaptureInputs,
1942 )
1943
1944 kh, kw = self.config.vision_config.merge_kernel_size
1945 per_item_output = (token_budget + max_batch_size - 1) // max_batch_size
1946 rope = self.vision_tower.encoder.rope_2d
1947 max_output_width = rope.max_width // kw
1948 max_output_height = rope.max_height // kh
1949 output_width = min(math.ceil(math.sqrt(per_item_output)), max_output_width)
1950 output_height = (per_item_output + output_width - 1) // output_width
1951 if output_height > max_output_height:
1952 output_height = max_output_height
1953 output_width = (per_item_output + output_height - 1) // output_height
1954 if output_width > max_output_width:
1955 raise ValueError(
1956 f"Encoder CUDA graph budget {token_budget} exceeds K3 RoPE "
1957 f"capacity for max_batch_size={max_batch_size}"
1958 )
1959 grid_thws = [
1960 [1, output_height * kh, output_width * kw] for _ in range(max_batch_size)
1961 ]
1962
1963 patch_size: int | tuple[int, int] = self.config.vision_config.patch_size
1964 if isinstance(patch_size, int):
1965 patch_size = (patch_size, patch_size)
1966 total_patches = sum(t * h * w for t, h, w in grid_thws)
1967 pixel_values = torch.randn(
1968 total_patches,
1969 3,
1970 patch_size[0],
1971 patch_size[1],
1972 device=device,
1973 dtype=dtype,
1974 )
1975 metadata = self.vision_tower.prepare_encoder_cudagraph_metadata(
1976 grid_thws,
1977 max_batch_size=max_batch_size,
1978 max_seqlen_override=max(
1979 token_budget * kh * kw,
1980 max(t * h * w for t, h, w in grid_thws),
1981 ),
1982 device=device,
1983 )
1984 return EncoderCudaGraphCaptureInputs(
1985 values=metadata | {"pixel_values": pixel_values}
1986 )
1987
1988 def prepare_encoder_cudagraph_replay_buffers(
1989 self,
1990 mm_kwargs: dict[str, Any],
1991 max_batch_size: int,
1992 max_frames_per_batch: int,
1993 path: str = "default",
1994 ):
1995 from vllm.v1.worker.encoder_cudagraph_defs import (
1996 EncoderCudaGraphReplayBuffers,
1997 )
1998
1999 pixel_values = self._get_pixel_values(mm_kwargs)
2000 metadata = self.vision_tower.prepare_encoder_cudagraph_metadata(
2001 self._get_grid_thws(mm_kwargs),
2002 max_batch_size=max_batch_size,
2003 device=pixel_values.device,
2004 )
2005 return EncoderCudaGraphReplayBuffers(
2006 values=metadata | {"pixel_values": pixel_values}
2007 )
2008
2009 def _project_encoder_features(self, image_features: torch.Tensor) -> torch.Tensor:
2010 projector_dtype = next(self.mm_projector.parameters()).dtype
2011 if image_features.dtype != projector_dtype:
2012 image_features = image_features.to(projector_dtype)
2013 output = self.mm_projector(image_features)
2014 return output.reshape(-1, output.shape[-1])
2015
2016 def encoder_cudagraph_forward(
2017 self,
2018 values: dict[str, torch.Tensor],
2019 path: str = "default",
2020 ) -> torch.Tensor:
2021 pixel_values = values.pop("pixel_values")
2022 image_features = self.vision_tower(pixel_values, None, encoder_metadata=values)
2023 return self._project_encoder_features(image_features)
2024
2025 def encoder_eager_forward(
2026 self,
2027 mm_kwargs: dict[str, Any],
2028 path: str = "default",
2029 ) -> torch.Tensor:
2030 image_features = self.vision_tower(
2031 self._get_pixel_values(mm_kwargs).to(
2032 next(self.vision_tower.parameters()).dtype
2033 ),
2034 self._get_grid_thws(mm_kwargs),
2035 )
2036 return self._project_encoder_features(torch.cat(image_features))
2037
2038 def _maybe_ignore_quant_config(
2039 self, quant_config: QuantizationConfig | None
2040 ) -> QuantizationConfig | None:
2041 if isinstance(quant_config, compressed_tensors.CompressedTensorsConfig):
2042 return None
2043 return quant_config
2044
2045 def _parse_and_validate_media_input(
2046 self, **kwargs: object
2047 ) -> KimiK25MediaPixelInputs | None:
2048 pixel_values = kwargs.pop("pixel_values", None)
2049 grid_thws = kwargs.pop("grid_thws", None)
2050 if pixel_values is None:
2051 return None
2052
2053 if isinstance(pixel_values, list):
2054 pixel_values = torch.cat(cast(list[torch.Tensor], pixel_values), dim=0)
2055 if not isinstance(pixel_values, torch.Tensor):
2056 raise TypeError(
2057 "pixel_values must be a tensor or a list of tensors, "
2058 f"got {type(pixel_values)}"
2059 )
2060
2061 if len(pixel_values.shape) == 5 or len(pixel_values.shape) == 3:
2062 pixel_values = pixel_values.reshape(
2063 pixel_values.shape[0] * pixel_values.shape[1], *pixel_values.shape[2:]
2064 )
2065
2066 target_dtype = next(self.vision_tower.parameters()).dtype
2067 pixel_values = pixel_values.to(target_dtype)
2068 assert isinstance(grid_thws, torch.Tensor), (
2069 f"expect grid_thws to be a tensor, got {type(grid_thws)}"
2070 )
2071 grid_thws = grid_thws.reshape(-1, grid_thws.shape[-1])
2072 assert grid_thws.ndim == 2 and grid_thws.size(1) == 3, (
2073 f"unexpected shape for grid_thws: {grid_thws.shape}"
2074 )
2075
2076 return KimiK25MediaPixelInputs(
2077 type="pixel_values",
2078 pixel_values=pixel_values,
2079 grid_thws=grid_thws,
2080 )
2081
2082 def _process_media_input(
2083 self, media_input: KimiK25MediaPixelInputs
2084 ) -> list[torch.Tensor]:
2085 media_features = vision_tower_forward(
2086 self.vision_tower,
2087 media_input["pixel_values"],
2088 media_input["grid_thws"],
2089 mm_projector=self.mm_projector,
2090 use_data_parallel=self.use_data_parallel,
2091 )
2092 return media_features
2093
2094 def embed_multimodal(self, **kwargs: object) -> NestedTensors | None:
2095 media_input = self._parse_and_validate_media_input(**kwargs)
2096 if media_input is None:
2097 return None
2098 return self._process_media_input(media_input)
2099
2100 def forward( # type: ignore[override]
2101 self,
2102 input_ids: torch.Tensor,
2103 positions: torch.Tensor,
2104 intermediate_tensors: IntermediateTensors | None = None,
2105 inputs_embeds: torch.Tensor | None = None,
2106 **kwargs: object,
2107 ) -> torch.Tensor | IntermediateTensors | tuple[torch.Tensor, list[torch.Tensor]]:
2108 if intermediate_tensors is not None:
2109 inputs_embeds = None
2110 return self.language_model(
2111 input_ids=input_ids,
2112 positions=positions,
2113 intermediate_tensors=intermediate_tensors,
2114 inputs_embeds=inputs_embeds,
2115 )
2116
2117 def compute_logits(self, hidden_states: torch.Tensor, **kwargs) -> torch.Tensor:
2118 return self.language_model.compute_logits(hidden_states)
2119
2120 def copy_inputs_before_cuda_graphs(self, input_buffers, **kwargs):
2121 return self.language_model.mamba_cache.copy_inputs_before_cuda_graphs(
2122 input_buffers, **kwargs
2123 )
2124
2125 def get_seqlen_agnostic_capture_inputs(self, batch_size: int):
2126 return self.language_model.mamba_cache.get_seqlen_agnostic_capture_inputs(
2127 batch_size
2128 )
2129
2130 @classmethod
2131 def get_mamba_state_dtype_from_config(cls, vllm_config: VllmConfig):
2132 text_config = vllm_config.model_config.hf_config.text_config
2133 temp_vllm_config = vllm_config.with_hf_config(text_config)
2134 return KimiLinearForCausalLM.get_mamba_state_dtype_from_config(temp_vllm_config)
2135
2136 @classmethod
2137 def get_mamba_state_shape_from_config(cls, vllm_config: VllmConfig):
2138 text_config = vllm_config.model_config.hf_config.text_config
2139 temp_vllm_config = vllm_config.with_hf_config(text_config)
2140 return KimiLinearForCausalLM.get_mamba_state_shape_from_config(temp_vllm_config)
2141
2142 @classmethod
2143 def get_mamba_state_copy_func(cls):
2144 return KimiLinearForCausalLM.get_mamba_state_copy_func()
2145
2146 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
2147 loader = AutoWeightsLoader(self)
2148 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
2149
2150 def process_weights_after_loading(self) -> None:
2151 self.language_model.process_weights_after_loading()