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