@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 4e68cc9b6a
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 14, 00:30 UTC

[Model] Introduce Kimi Linear to vLLM (#27809)

Signed-off-by: lizhiyuan <lizhiyuan@moonshot.cn> Signed-off-by: Zhiyuan Li <uniartisan2017@gmail.com>

Zhiyuan Li committed Oct 30, 2025 at 21:02 UTC 4e68cc9b6aa2b9cfe8d799c2b1cd156a01bca438
15 files changed +1326 -49
docs/models/supported_models.md
+1
@@ -382,6 +382,7 @@ th {
382 | `InternLM3ForCausalLM` | InternLM3 | `internlm/internlm3-8b-instruct`, etc. | ✅︎ | ✅︎ |
383 | `JAISLMHeadModel` | Jais | `inceptionai/jais-13b`, `inceptionai/jais-13b-chat`, `inceptionai/jais-30b-v3`, `inceptionai/jais-30b-chat-v3`, etc. | | ✅︎ |
384 | `JambaForCausalLM` | Jamba | `ai21labs/AI21-Jamba-1.5-Large`, `ai21labs/AI21-Jamba-1.5-Mini`, `ai21labs/Jamba-v0.1`, etc. | ✅︎ | ✅︎ |
385 +| `KimiLinearForCausalLM` | Kimi-Linear-48B-A3B-Base, Kimi-Linear-48B-A3B-Instruct | `moonshotai/Kimi-Linear-48B-A3B-Base`, `moonshotai/Kimi-Linear-48B-A3B-Instruct` | | ✅︎ |
386 | `Lfm2ForCausalLM` | LFM2 | `LiquidAI/LFM2-1.2B`, `LiquidAI/LFM2-700M`, `LiquidAI/LFM2-350M`, etc. | ✅︎ | ✅︎ |
387 | `Lfm2MoeForCausalLM` | LFM2MoE | `LiquidAI/LFM2-8B-A1B-preview`, etc. | ✅︎ | ✅︎ |
388 | `LlamaForCausalLM` | Llama 3.1, Llama 3, Llama 2, LLaMA, Yi | `meta-llama/Meta-Llama-3.1-405B-Instruct`, `meta-llama/Meta-Llama-3.1-70B`, `meta-llama/Meta-Llama-3-70B-Instruct`, `meta-llama/Llama-2-70b-hf`, `01-ai/Yi-34B`, etc. | ✅︎ | ✅︎ |
tests/models/registry.py
+3
@@ -296,6 +296,9 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
296 "random": "ai21labs/Jamba-tiny-random",
297 },
298 ),
299 + "KimiLinearForCausalLM": _HfExamplesInfo(
300 + "moonshotai/Kimi-Linear-48B-A3B-Instruct", trust_remote_code=True
301 + ),
302 "Lfm2ForCausalLM": _HfExamplesInfo("LiquidAI/LFM2-1.2B"),
303 "Lfm2MoeForCausalLM": _HfExamplesInfo(
304 "LiquidAI/LFM2-8B-A1B", min_transformers_version="4.58"
vllm/config/compilation.py
+1
@@ -453,6 +453,7 @@ class CompilationConfig:
453 "vllm::linear_attention",
454 "vllm::plamo2_mamba_mixer",
455 "vllm::gdn_attention",
456 + "vllm::kda_attention",
457 "vllm::sparse_attn_indexer",
458 ]
459
vllm/config/model.py
+1
@@ -1236,6 +1236,7 @@ class ModelConfig:
1236 "deepseek_v32",
1237 "deepseek_mtp",
1238 "kimi_k2",
1239 + "kimi_linear",
1240 "longcat_flash",
1241 ):
1242 return self.hf_text_config.kv_lora_rank is not None
vllm/model_executor/layers/fla/ops/kda.py
+1 -1
@@ -1304,7 +1304,7 @@ def kda_gate_fwd_kernel(
1304 tl.store(y_ptr, b_y.to(y.dtype.element_ty), boundary_check=(0, 1))
1305
1306
1307 -def kda_gate_fwd(
1307 +def fused_kda_gate(
1308 g: torch.Tensor,
1309 A: torch.Tensor,
1310 head_k_dim: int,
vllm/model_executor/layers/kda.py new
+426
@@ -0,0 +1,426 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +import torch
5 +from einops import rearrange
6 +from torch import nn
7 +
8 +from vllm.attention import AttentionBackend
9 +from vllm.attention.backends.abstract import AttentionMetadata
10 +from vllm.config import CacheConfig, ModelConfig, get_current_vllm_config
11 +from vllm.distributed import (
12 + divide,
13 + get_tensor_model_parallel_rank,
14 + get_tensor_model_parallel_world_size,
15 +)
16 +from vllm.forward_context import ForwardContext, get_forward_context
17 +from vllm.logger import init_logger
18 +from vllm.model_executor.model_loader.weight_utils import sharded_weight_loader
19 +from vllm.model_executor.utils import set_weight_attrs
20 +from vllm.utils.torch_utils import direct_register_custom_op
21 +from vllm.v1.attention.backends.gdn_attn import GDNAttentionMetadata
22 +
23 +from .fla.ops.kda import (
24 + FusedRMSNormGated,
25 + chunk_kda,
26 + fused_kda_gate,
27 + fused_recurrent_kda,
28 +)
29 +from .linear import (
30 + ColumnParallelLinear,
31 + ReplicatedLinear,
32 + RowParallelLinear,
33 +)
34 +from .mamba.abstract import MambaBase
35 +from .mamba.mamba_utils import MambaStateDtypeCalculator, MambaStateShapeCalculator
36 +from .mamba.ops.causal_conv1d import causal_conv1d_fn, causal_conv1d_update
37 +from .quantization.base_config import QuantizationConfig
38 +
39 +logger = init_logger(__name__)
40 +
41 +
42 +def kda_attention(
43 + hidden_states: torch.Tensor,
44 + output: torch.Tensor,
45 + layer_name: str,
46 +) -> None:
47 + forward_context: ForwardContext = get_forward_context()
48 + self = forward_context.no_compile_layers[layer_name]
49 + self._forward(hidden_states=hidden_states, output=output)
50 +
51 +
52 +def kda_attention_fake(
53 + hidden_states: torch.Tensor,
54 + output: torch.Tensor,
55 + layer_name: str,
56 +) -> None:
57 + return
58 +
59 +
60 +direct_register_custom_op(
61 + op_name="kda_attention",
62 + op_func=kda_attention,
63 + mutates_args=["output"],
64 + fake_impl=kda_attention_fake,
65 +)
66 +
67 +
68 +class KimiDeltaAttention(nn.Module, MambaBase):
69 + @property
70 + def mamba_type(self) -> str:
71 + return "linear_attention"
72 +
73 + def get_attn_backend(self) -> type["AttentionBackend"]:
74 + from vllm.v1.attention.backends.gdn_attn import GDNAttentionBackend
75 +
76 + return GDNAttentionBackend
77 +
78 + def get_state_dtype(
79 + self,
80 + ) -> tuple[torch.dtype, torch.dtype, torch.dtype, torch.dtype]:
81 + if self.model_config is None or self.cache_config is None:
82 + raise ValueError("model_config and cache_config must be set")
83 + return MambaStateDtypeCalculator.kda_state_dtype(
84 + self.model_config.dtype, self.cache_config.mamba_cache_dtype
85 + )
86 +
87 + def get_state_shape(
88 + self,
89 + ) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...], tuple[int, ...]]:
90 + return MambaStateShapeCalculator.kda_state_shape(
91 + self.tp_size, self.num_heads, self.head_dim, conv_kernel_size=self.conv_size
92 + )
93 +
94 + def __init__(
95 + self,
96 + layer_idx: int,
97 + hidden_size: int,
98 + quant_config: QuantizationConfig | None = None,
99 + cache_config: CacheConfig | None = None,
100 + model_config: ModelConfig | None = None,
101 + rms_norm_eps: float = 1e-5,
102 + prefix: str = "",
103 + **kwargs,
104 + ) -> None:
105 + super().__init__()
106 + self.tp_size = get_tensor_model_parallel_world_size()
107 + self.tp_rank = get_tensor_model_parallel_rank()
108 + self.hidden_size = hidden_size
109 + self.model_config = model_config
110 + self.cache_config = cache_config
111 + if model_config is None:
112 + raise ValueError("model_config must be provided")
113 + kda_config = model_config.linear_attn_config
114 + self.head_dim = kda_config["head_dim"]
115 + self.num_heads = kda_config["num_heads"]
116 + self.layer_idx = layer_idx
117 + self.prefix = prefix
118 + assert self.num_heads % self.tp_size == 0
119 + self.local_num_heads = divide(self.num_heads, self.tp_size)
120 +
121 + projection_size = self.head_dim * self.num_heads
122 + self.conv_size = kda_config["short_conv_kernel_size"]
123 +
124 + self.q_proj = ColumnParallelLinear(
125 + self.hidden_size,
126 + projection_size,
127 + bias=False,
128 + quant_config=quant_config,
129 + prefix=f"{prefix}.q_proj",
130 + )
131 + self.k_proj = ColumnParallelLinear(
132 + self.hidden_size,
133 + projection_size,
134 + bias=False,
135 + quant_config=quant_config,
136 + prefix=f"{prefix}.k_proj",
137 + )
138 + self.v_proj = ColumnParallelLinear(
139 + self.hidden_size,
140 + projection_size,
141 + bias=False,
142 + quant_config=quant_config,
143 + prefix=f"{prefix}.v_proj",
144 + )
145 +
146 + self.f_a_proj = ReplicatedLinear(
147 + self.hidden_size,
148 + self.head_dim,
149 + bias=False,
150 + quant_config=quant_config,
151 + prefix=f"{prefix}.f_a_proj",
152 + )
153 +
154 + self.f_b_proj = ColumnParallelLinear(
155 + self.head_dim,
156 + projection_size,
157 + bias=False,
158 + quant_config=quant_config,
159 + prefix=f"{prefix}.f_b_proj",
160 + )
161 + self.dt_bias = nn.Parameter(
162 + torch.empty(divide(projection_size, self.tp_size), dtype=torch.float32)
163 + )
164 +
165 + set_weight_attrs(self.dt_bias, {"weight_loader": sharded_weight_loader(0)})
166 +
167 + self.b_proj = ColumnParallelLinear(
168 + self.hidden_size,
169 + self.num_heads,
170 + bias=False,
171 + quant_config=quant_config,
172 + prefix=f"{prefix}.b_proj",
173 + )
174 +
175 + self.q_conv1d = ColumnParallelLinear(
176 + input_size=self.conv_size,
177 + output_size=projection_size,
178 + bias=False,
179 + params_dtype=torch.float32,
180 + prefix=f"{prefix}.q_conv1d",
181 + )
182 + self.k_conv1d = ColumnParallelLinear(
183 + input_size=self.conv_size,
184 + output_size=projection_size,
185 + bias=False,
186 + params_dtype=torch.float32,
187 + prefix=f"{prefix}.k_conv1d",
188 + )
189 + self.v_conv1d = ColumnParallelLinear(
190 + input_size=self.conv_size,
191 + output_size=projection_size,
192 + bias=False,
193 + params_dtype=torch.float32,
194 + prefix=f"{prefix}.v_conv1d",
195 + )
196 + # unsqueeze to fit conv1d weights shape into the linear weights shape.
197 + # Can't do this in `weight_loader` since it already exists in
198 + # `ColumnParallelLinear` and `set_weight_attrs`
199 + # doesn't allow to override it
200 + self.q_conv1d.weight.data = self.q_conv1d.weight.data.unsqueeze(1)
201 + self.k_conv1d.weight.data = self.k_conv1d.weight.data.unsqueeze(1)
202 + self.v_conv1d.weight.data = self.v_conv1d.weight.data.unsqueeze(1)
203 +
204 + self.A_log = nn.Parameter(
205 + torch.empty(1, 1, self.local_num_heads, 1, dtype=torch.float32)
206 + )
207 + set_weight_attrs(self.A_log, {"weight_loader": sharded_weight_loader(2)})
208 +
209 + self.g_a_proj = ReplicatedLinear(
210 + self.hidden_size,
211 + self.head_dim,
212 + bias=False,
213 + quant_config=quant_config,
214 + prefix=f"{prefix}.g_a_proj",
215 + )
216 + self.g_b_proj = ColumnParallelLinear(
217 + self.head_dim,
218 + projection_size,
219 + bias=False,
220 + quant_config=quant_config,
221 + prefix=f"{prefix}.g_b_proj",
222 + )
223 + self.o_norm = FusedRMSNormGated(
224 + self.head_dim, eps=rms_norm_eps, activation="sigmoid"
225 + )
226 + self.o_proj = RowParallelLinear(
227 + projection_size,
228 + self.hidden_size,
229 + bias=False,
230 + quant_config=quant_config,
231 + prefix=f"{prefix}.o_proj",
232 + )
233 +
234 + compilation_config = get_current_vllm_config().compilation_config
235 + if prefix in compilation_config.static_forward_context:
236 + raise ValueError(f"Duplicate layer name: {prefix}")
237 + compilation_config.static_forward_context[prefix] = self
238 +
239 + def forward(
240 + self,
241 + hidden_states: torch.Tensor,
242 + positions: torch.Tensor,
243 + output: torch.Tensor,
244 + ) -> None:
245 + return torch.ops.vllm.kda_attention(
246 + hidden_states,
247 + output,
248 + self.prefix,
249 + )
250 +
251 + def _forward(
252 + self,
253 + hidden_states: torch.Tensor,
254 + output: torch.Tensor,
255 + ) -> None:
256 + forward_context = get_forward_context()
257 + attn_metadata: AttentionMetadata = forward_context.attn_metadata
258 +
259 + if attn_metadata is None:
260 + # V1 profile run
261 + # Mimic the memory allocation in the real run
262 + q = torch.empty_like(hidden_states)
263 + k = torch.empty_like(hidden_states)
264 + v = torch.empty_like(hidden_states)
265 + g = hidden_states.new_empty(
266 + hidden_states.size(0),
267 + self.local_num_heads,
268 + self.head_dim,
269 + dtype=torch.float32,
270 + )
271 + beta = torch.empty(
272 + hidden_states.size(0), self.local_num_heads, dtype=torch.float32
273 + )
274 + core_attn_out = torch.empty_like(hidden_states)
275 + return
276 +
277 + assert isinstance(attn_metadata, dict)
278 + attn_metadata = attn_metadata[self.prefix]
279 + assert isinstance(attn_metadata, GDNAttentionMetadata)
280 + has_initial_state = attn_metadata.has_initial_state
281 + non_spec_query_start_loc = attn_metadata.non_spec_query_start_loc
282 + non_spec_state_indices_tensor = attn_metadata.non_spec_state_indices_tensor # noqa: E501
283 + constant_caches = self.kv_cache[forward_context.virtual_engine]
284 +
285 + (conv_state_q, conv_state_k, conv_state_v, recurrent_state) = constant_caches
286 + # deal with strides
287 + conv_state_q = conv_state_q.transpose(-1, -2)
288 + conv_state_k = conv_state_k.transpose(-1, -2)
289 + conv_state_v = conv_state_v.transpose(-1, -2)
290 +
291 + q_proj_states = self.q_proj(hidden_states)[0]
292 + k_proj_states = self.k_proj(hidden_states)[0]
293 + v_proj_states = self.v_proj(hidden_states)[0]
294 +
295 + q_conv_weights = self.q_conv1d.weight.view(
296 + self.q_conv1d.weight.size(0), self.q_conv1d.weight.size(2)
297 + )
298 + k_conv_weights = self.k_conv1d.weight.view(
299 + self.k_conv1d.weight.size(0), self.k_conv1d.weight.size(2)
300 + )
301 + v_conv_weights = self.v_conv1d.weight.view(
302 + self.v_conv1d.weight.size(0), self.v_conv1d.weight.size(2)
303 + )
304 + if attn_metadata.num_prefills > 0:
305 + q_proj_states = q_proj_states.transpose(0, 1)
306 + k_proj_states = k_proj_states.transpose(0, 1)
307 + v_proj_states = v_proj_states.transpose(0, 1)
308 + q = causal_conv1d_fn(
309 + q_proj_states,
310 + q_conv_weights,
311 + self.q_conv1d.bias,
312 + activation="silu",
313 + conv_states=conv_state_q,
314 + has_initial_state=has_initial_state,
315 + cache_indices=non_spec_state_indices_tensor,
316 + query_start_loc=non_spec_query_start_loc,
317 + metadata=attn_metadata,
318 + ).transpose(0, 1)
319 + k = causal_conv1d_fn(
320 + k_proj_states,
321 + k_conv_weights,
322 + self.k_conv1d.bias,
323 + activation="silu",
324 + conv_states=conv_state_k,
325 + has_initial_state=has_initial_state,
326 + cache_indices=non_spec_state_indices_tensor,
327 + query_start_loc=non_spec_query_start_loc,
328 + metadata=attn_metadata,
329 + ).transpose(0, 1)
330 + v = causal_conv1d_fn(
331 + v_proj_states,
332 + v_conv_weights,
333 + self.v_conv1d.bias,
334 + activation="silu",
335 + conv_states=conv_state_v,
336 + has_initial_state=has_initial_state,
337 + cache_indices=non_spec_state_indices_tensor,
338 + query_start_loc=non_spec_query_start_loc,
339 + metadata=attn_metadata,
340 + ).transpose(0, 1)
341 + else:
342 + decode_conv_indices = non_spec_state_indices_tensor[
343 + : attn_metadata.num_decodes
344 + ]
345 + q = causal_conv1d_update(
346 + q_proj_states,
347 + conv_state_q,
348 + q_conv_weights,
349 + self.q_conv1d.bias,
350 + activation="silu",
351 + conv_state_indices=decode_conv_indices,
352 + validate_data=True,
353 + )
354 + k = causal_conv1d_update(
355 + k_proj_states,
356 + conv_state_k,
357 + k_conv_weights,
358 + self.k_conv1d.bias,
359 + activation="silu",
360 + conv_state_indices=decode_conv_indices,
361 + validate_data=True,
362 + )
363 + v = causal_conv1d_update(
364 + v_proj_states,
365 + conv_state_v,
366 + v_conv_weights,
367 + self.v_conv1d.bias,
368 + activation="silu",
369 + conv_state_indices=decode_conv_indices,
370 + validate_data=True,
371 + )
372 +
373 + q, k, v = map(
374 + lambda x: rearrange(x, "n (h d) -> 1 n h d", d=self.head_dim), (q, k, v)
375 + )
376 +
377 + beta = self.b_proj(hidden_states)[0].float().sigmoid()
378 +
379 + g = self.f_b_proj(self.f_a_proj(hidden_states)[0])[0]
380 + g = fused_kda_gate(g, self.A_log, self.head_dim, g_bias=self.dt_bias)
381 +
382 + beta = beta.unsqueeze(0)
383 + g = g.unsqueeze(0)
384 +
385 + if attn_metadata.num_prefills > 0:
386 + zero_idx = non_spec_state_indices_tensor[~has_initial_state]
387 + recurrent_state[zero_idx] = 0
388 + initial_state = recurrent_state[non_spec_state_indices_tensor].contiguous()
389 + (
390 + core_attn_out_non_spec,
391 + last_recurrent_state,
392 + ) = chunk_kda(
393 + q=q,
394 + k=k,
395 + v=v,
396 + g=g,
397 + beta=beta,
398 + initial_state=initial_state,
399 + output_final_state=True,
400 + use_qk_l2norm_in_kernel=True,
401 + cu_seqlens=non_spec_query_start_loc,
402 + )
403 + # Init cache
404 + recurrent_state[non_spec_state_indices_tensor] = last_recurrent_state
405 + else:
406 + (
407 + core_attn_out_non_spec,
408 + last_recurrent_state,
409 + ) = fused_recurrent_kda(
410 + q=q,
411 + k=k,
412 + v=v,
413 + g=g,
414 + beta=beta,
415 + initial_state=recurrent_state,
416 + use_qk_l2norm_in_kernel=True,
417 + cu_seqlens=non_spec_query_start_loc,
418 + ssm_state_indices=non_spec_state_indices_tensor,
419 + )
420 +
421 + g_proj_states = self.g_b_proj(self.g_a_proj(hidden_states)[0])[0]
422 + g = rearrange(g_proj_states, "... (h d) -> ... h d", d=self.head_dim)
423 + core_attn_out = self.o_norm(core_attn_out_non_spec, g)
424 + core_attn_out = rearrange(core_attn_out, "1 n h d -> n (h d)")
425 +
426 + output[:] = self.o_proj(core_attn_out)[0]
vllm/model_executor/layers/mamba/mamba_utils.py
+41
@@ -80,6 +80,15 @@ class MambaStateDtypeCalculator:
80 state_dtype = get_kv_cache_torch_dtype(mamba_cache_dtype, model_dtype)
81 return (state_dtype, state_dtype)
82
83 + @classmethod
84 + def kda_state_dtype(
85 + cls,
86 + model_dtype: ModelDType | torch.dtype,
87 + mamba_cache_dtype: MambaDType,
88 + ):
89 + state_dtype = get_kv_cache_torch_dtype(mamba_cache_dtype, model_dtype)
90 + return (state_dtype, state_dtype, state_dtype, torch.float32)
91 +
92
93 class MambaStateShapeCalculator:
94 @classmethod
@@ -182,3 +191,35 @@ class MambaStateShapeCalculator:
191 head_v_dim,
192 )
193 return conv_state_shape, temporal_state_shape
194 +
195 + @classmethod
196 + def kda_state_shape(
197 + cls,
198 + tp_world_size: int,
199 + num_heads: int,
200 + head_dim: int,
201 + num_k_heads: int | None = None,
202 + head_k_dim: int | None = None,
203 + conv_kernel_size: int = 4,
204 + num_spec: int = 0,
205 + ) -> tuple[tuple[int, int], tuple[int, int], tuple[int, int], tuple[int, int, int]]:
206 + if num_k_heads is None:
207 + num_k_heads = num_heads
208 + if head_k_dim is None:
209 + head_k_dim = head_dim
210 +
211 + proj_size = num_heads * head_dim
212 + proj_k_size = num_k_heads * head_k_dim
213 +
214 + conv_state_shape = (divide(proj_size, tp_world_size), conv_kernel_size - 1)
215 + conv_state_k_shape = (divide(proj_k_size, tp_world_size), conv_kernel_size - 1)
216 + recurrent_state_shape = (divide(num_heads, tp_world_size), head_dim, head_dim)
217 +
218 + conv_state_shape = conv_state_shape[1], conv_state_shape[0]
219 + conv_state_k_shape = conv_state_k_shape[1], conv_state_k_shape[0]
220 + return (
221 + conv_state_shape,
222 + conv_state_k_shape,
223 + conv_state_k_shape,
224 + recurrent_state_shape,
225 + )
vllm/model_executor/layers/mla.py
+4 -3
@@ -147,9 +147,10 @@ class MultiHeadLatentAttentionWrapper(CustomOp):
147 # Add head dim of 1 to k_pe
148 k_pe = k_pe.unsqueeze(1)
149
150 - q[..., self.qk_nope_head_dim :], k_pe = self.rotary_emb(
151 - positions, q[..., self.qk_nope_head_dim :], k_pe
152 - )
150 + if self.rotary_emb is not None:
151 + q[..., self.qk_nope_head_dim :], k_pe = self.rotary_emb(
152 + positions, q[..., self.qk_nope_head_dim :], k_pe
153 + )
154
155 if self.indexer and self.is_sparse:
156 _topk_indices = self.indexer(hidden_states, q_c, positions, self.rotary_emb)
vllm/model_executor/models/config.py
+25 -26
@@ -1,6 +1,7 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from copy import deepcopy
4 +from math import lcm
5 from typing import TYPE_CHECKING
6
7 import vllm.envs as envs
@@ -8,7 +9,7 @@ from vllm.logger import init_logger
9 from vllm.model_executor.models import ModelRegistry
10 from vllm.utils.math_utils import cdiv, round_up
11 from vllm.utils.torch_utils import STR_DTYPE_TO_TORCH_DTYPE
11 -from vllm.v1.kv_cache_interface import FullAttentionSpec, MambaSpec
12 +from vllm.v1.kv_cache_interface import FullAttentionSpec, MambaSpec, MLAAttentionSpec
13
14 if TYPE_CHECKING:
15 from vllm.config import VllmConfig
@@ -347,12 +348,28 @@ class HybridAttentionMambaModelConfig(VerifyAndUpdateConfig):
348 kv_cache_dtype = STR_DTYPE_TO_TORCH_DTYPE[cache_config.cache_dtype]
349
350 # get attention page size (for 1 token)
350 - attn_page_size_1_token = FullAttentionSpec(
351 - block_size=1,
352 - num_kv_heads=model_config.get_num_kv_heads(parallel_config),
353 - head_size=model_config.get_head_size(),
354 - dtype=kv_cache_dtype,
355 - ).page_size_bytes
351 + # Attention backend constraints:
352 + # - FlashAttention (FA) requires block size to be multiple of 16
353 + # - MLA (Multi-head Latent Attention) requires larger alignment:
354 + # * CUTLASS_MLA backend: kernel_block_size 128 alignment
355 + # * Other MLA backends: kernel_block_size 64 alignment
356 + if model_config.use_mla:
357 + use_cutlass_mla = envs.VLLM_ATTENTION_BACKEND == "CUTLASS_MLA"
358 + kernel_block_alignment_size = 128 if use_cutlass_mla else 64
359 + attn_page_size_1_token = MLAAttentionSpec(
360 + block_size=1,
361 + num_kv_heads=model_config.get_num_kv_heads(parallel_config),
362 + head_size=model_config.get_head_size(),
363 + dtype=kv_cache_dtype,
364 + ).page_size_bytes
365 + else:
366 + kernel_block_alignment_size = 16
367 + attn_page_size_1_token = FullAttentionSpec(
368 + block_size=1,
369 + num_kv_heads=model_config.get_num_kv_heads(parallel_config),
370 + head_size=model_config.get_head_size(),
371 + dtype=kv_cache_dtype,
372 + ).page_size_bytes
373
374 model_cls, _ = ModelRegistry.resolve_model_cls(
375 model_config.architecture,
@@ -372,17 +389,6 @@ class HybridAttentionMambaModelConfig(VerifyAndUpdateConfig):
389 if mamba_page_size == 0:
390 return
391
375 - # Attention backend constraints:
376 - # - FlashAttention (FA) requires block size to be multiple of 16
377 - # - MLA (Multi-head Latent Attention) requires larger alignment:
378 - # * CUTLASS_MLA backend: 128-byte alignment
379 - # * Other MLA backends: 64-byte alignment
380 - if model_config.use_mla:
381 - use_cutlass_mla = envs.VLLM_ATTENTION_BACKEND == "CUTLASS_MLA"
382 - kernel_block_alignment_size = 128 if use_cutlass_mla else 64
383 - else:
384 - kernel_block_alignment_size = 16
385 -
392 if cache_config.enable_prefix_caching:
393 # With prefix caching, select attention block size to
394 # optimize for mamba kernel performance
@@ -400,15 +406,8 @@ class HybridAttentionMambaModelConfig(VerifyAndUpdateConfig):
406 # easily by changing the way we layout chunks in the
407 # mamba2 kernels.
408
403 - from math import gcd
404 -
405 - def lcm(a, b):
406 - return a * b // gcd(a, b)
407 -
408 - base_chunk_size = mamba_block_size or model_config.get_mamba_chunk_size()
409 -
409 + base_chunk_size = model_config.get_mamba_chunk_size()
410 attn_tokens_per_mamba_state = cdiv(mamba_page_size, attn_page_size_1_token)
411 -
411 chunk_size = lcm(base_chunk_size, kernel_block_alignment_size)
412 attn_block_size = chunk_size * cdiv(attn_tokens_per_mamba_state, chunk_size)
413 cache_config.mamba_block_size = attn_block_size
vllm/model_executor/models/kimi_linear.py new
+663
@@ -0,0 +1,663 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +from collections.abc import Iterable
5 +from typing import Any
6 +
7 +import torch
8 +from torch import nn
9 +
10 +from vllm.compilation.decorators import support_torch_compile
11 +from vllm.config import CacheConfig, ModelConfig, ParallelConfig, VllmConfig
12 +from vllm.distributed import (
13 + get_pp_group,
14 + get_tensor_model_parallel_world_size,
15 + tensor_model_parallel_all_reduce,
16 +)
17 +from vllm.logger import init_logger
18 +from vllm.model_executor.layers.activation import SiluAndMul
19 +from vllm.model_executor.layers.fused_moe import FusedMoE
20 +from vllm.model_executor.layers.kda import KimiDeltaAttention
21 +from vllm.model_executor.layers.layernorm import RMSNorm
22 +from vllm.model_executor.layers.linear import (
23 + ColumnParallelLinear,
24 + MergedColumnParallelLinear,
25 + QKVParallelLinear,
26 + ReplicatedLinear,
27 + RowParallelLinear,
28 +)
29 +from vllm.model_executor.layers.logits_processor import LogitsProcessor
30 +from vllm.model_executor.layers.mamba.mamba_utils import (
31 + MambaStateDtypeCalculator,
32 + MambaStateShapeCalculator,
33 +)
34 +from vllm.model_executor.layers.mla import MLAModules, MultiHeadLatentAttentionWrapper
35 +from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
36 +from vllm.model_executor.layers.vocab_parallel_embedding import (
37 + ParallelLMHead,
38 + VocabParallelEmbedding,
39 +)
40 +from vllm.model_executor.model_loader.weight_utils import (
41 + default_weight_loader,
42 + maybe_remap_kv_scale_name,
43 +)
44 +from vllm.sequence import IntermediateTensors
45 +from vllm.transformers_utils.configs.kimi_linear import KimiLinearConfig
46 +
47 +from .interfaces import HasInnerState, IsHybrid, MixtureOfExperts, SupportsPP
48 +from .utils import (
49 + PPMissingLayer,
50 + is_pp_missing_parameter,
51 + make_layers,
52 + maybe_prefix,
53 +)
54 +
55 +logger = init_logger(__name__)
56 +
57 +
58 +class KimiMLP(nn.Module):
59 + def __init__(
60 + self,
61 + hidden_size: int,
62 + intermediate_size: int,
63 + hidden_act: str,
64 + quant_config: QKVParallelLinear | None = None,
65 + reduce_results: bool = True,
66 + prefix: str = "",
67 + ) -> None:
68 + super().__init__()
69 +
70 + self.gate_up_proj = MergedColumnParallelLinear(
71 + hidden_size,
72 + [intermediate_size] * 2,
73 + bias=False,
74 + quant_config=quant_config,
75 + prefix=f"{prefix}.gate_up_proj",
76 + )
77 + self.down_proj = RowParallelLinear(
78 + intermediate_size,
79 + hidden_size,
80 + bias=False,
81 + quant_config=quant_config,
82 + reduce_results=reduce_results,
83 + prefix=f"{prefix}.down_proj",
84 + )
85 + if hidden_act != "silu":
86 + raise ValueError(
87 + f"Unsupported activation: {hidden_act}. Only silu is supported for now."
88 + )
89 + self.act_fn = SiluAndMul()
90 +
91 + def forward(self, x):
92 + gate_up, _ = self.gate_up_proj(x)
93 + x = self.act_fn(gate_up)
94 + x, _ = self.down_proj(x)
95 + return x
96 +
97 +
98 +class KimiMoE(nn.Module):
99 + def __init__(
100 + self,
101 + config: KimiLinearConfig,
102 + quant_config: QuantizationConfig | None = None,
103 + prefix: str = "",
104 + layer_idx: int = 0,
105 + ):
106 + super().__init__()
107 + hidden_size = config.hidden_size
108 + intermediate_size = config.intermediate_size
109 + moe_intermediate_size = config.moe_intermediate_size
110 + num_experts = config.num_experts
111 + moe_renormalize = config.moe_renormalize
112 + self.tp_size = get_tensor_model_parallel_world_size()
113 + self.routed_scaling_factor = config.routed_scaling_factor
114 + self.num_shared_experts = config.num_shared_experts
115 + self.layer_idx = layer_idx
116 +
117 + if config.hidden_act != "silu":
118 + raise ValueError(
119 + f"Unsupported activation: {config.hidden_act}. "
120 + "Only silu is supported for now."
121 + )
122 +
123 + # Gate always runs at half / full precision for now.
124 + self.gate = ReplicatedLinear(
125 + hidden_size,
126 + num_experts,
127 + bias=False,
128 + quant_config=None,
129 + prefix=f"{prefix}.gate",
130 + )
131 +
132 + self.gate.e_score_correction_bias = nn.Parameter(torch.empty(num_experts))
133 +
134 + self.experts = FusedMoE(
135 + num_experts=num_experts,
136 + top_k=config.num_experts_per_token,
137 + hidden_size=hidden_size,
138 + intermediate_size=moe_intermediate_size,
139 + reduce_results=False,
140 + renormalize=moe_renormalize,
141 + quant_config=quant_config,
142 + use_grouped_topk=config.use_grouped_topk,
143 + num_expert_group=config.num_expert_group,
144 + topk_group=config.topk_group,
145 + prefix=f"{prefix}.experts",
146 + scoring_func=config.moe_router_activation_func,
147 + e_score_correction_bias=self.gate.e_score_correction_bias,
148 + )
149 +
150 + if self.num_shared_experts is not None:
151 + intermediate_size = moe_intermediate_size * self.num_shared_experts
152 + self.shared_experts = KimiMLP(
153 + hidden_size=config.hidden_size,
154 + intermediate_size=intermediate_size,
155 + hidden_act=config.hidden_act,
156 + quant_config=quant_config,
157 + reduce_results=False,
158 + )
159 +
160 + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
161 + num_tokens, hidden_size = hidden_states.shape
162 + hidden_states = hidden_states.view(-1, hidden_size)
163 + if self.num_shared_experts is not None:
164 + shared_output = self.shared_experts(hidden_states)
165 + router_logits, _ = self.gate(hidden_states)
166 + final_hidden_states = (
167 + self.experts(hidden_states=hidden_states, router_logits=router_logits)
168 + * self.routed_scaling_factor
169 + )
170 + if shared_output is not None:
171 + final_hidden_states = final_hidden_states + shared_output
172 +
173 + if self.tp_size > 1:
174 + final_hidden_states = tensor_model_parallel_all_reduce(final_hidden_states)
175 + return final_hidden_states.view(num_tokens, hidden_size)
176 +
177 +
178 +class KimiMLAAttention(nn.Module):
179 + """
180 + Main reference: DeepseekV2 vllm Implementation
181 + """
182 +
183 + def __init__(
184 + self,
185 + config: KimiLinearConfig,
186 + hidden_size: int,
187 + num_heads: int,
188 + qk_nope_head_dim: int,
189 + qk_rope_head_dim: int,
190 + v_head_dim: int,
191 + q_lora_rank: int | None,
192 + kv_lora_rank: int,
193 + rope_theta: float = 10000,
194 + use_nope: bool = False,
195 + rope_scaling: dict[str, Any] | None = None,
196 + cache_config: CacheConfig | None = None,
197 + quant_config: QuantizationConfig | None = None,
198 + prefix: str = "",
199 + **kwargs,
200 + ) -> None:
201 + super().__init__()
202 + self.hidden_size = hidden_size
203 + self.qk_nope_head_dim = qk_nope_head_dim
204 + self.qk_rope_head_dim = qk_rope_head_dim
205 + self.qk_head_dim = qk_nope_head_dim + qk_rope_head_dim
206 + self.v_head_dim = v_head_dim
207 + self.q_lora_rank = q_lora_rank
208 + self.kv_lora_rank = kv_lora_rank
209 + self.num_heads = num_heads
210 + tp_size = get_tensor_model_parallel_world_size()
211 + self.num_local_heads = num_heads // tp_size
212 + self.scaling = self.qk_head_dim**-0.5
213 + self.rope_theta = rope_theta
214 + self.use_nope = use_nope
215 + assert self.use_nope is True
216 + assert self.q_lora_rank is None
217 + assert rope_scaling is None
218 + assert num_heads % tp_size == 0
219 + self.kv_a_proj_with_mqa = ReplicatedLinear(
220 + self.hidden_size,
221 + self.kv_lora_rank + self.qk_rope_head_dim,
222 + bias=False,
223 + quant_config=quant_config,
224 + prefix=f"{prefix}.kv_a_proj_with_mqa",
225 + )
226 + self.q_proj = ColumnParallelLinear(
227 + self.hidden_size,
228 + self.num_heads * self.qk_head_dim,
229 + bias=False,
230 + quant_config=quant_config,
231 + prefix=f"{prefix}.q_proj",
232 + )
233 + self.kv_a_layernorm = RMSNorm(
234 + self.kv_lora_rank,
235 + eps=config.rms_norm_eps,
236 + )
237 + self.kv_b_proj = ColumnParallelLinear(
238 + self.kv_lora_rank,
239 + self.num_heads * (self.qk_nope_head_dim + self.v_head_dim),
240 + bias=False,
241 + quant_config=quant_config,
242 + prefix=f"{prefix}.kv_b_proj",
243 + )
244 + self.o_proj = RowParallelLinear(
245 + self.num_heads * self.v_head_dim,
246 + self.hidden_size,
247 + bias=False,
248 + quant_config=quant_config,
249 + prefix=f"{prefix}.o_proj",
250 + )
251 +
252 + mla_modules = MLAModules(
253 + kv_a_layernorm=self.kv_a_layernorm,
254 + kv_b_proj=self.kv_b_proj,
255 + rotary_emb=None,
256 + o_proj=self.o_proj,
257 + fused_qkv_a_proj=None,
258 + kv_a_proj_with_mqa=self.kv_a_proj_with_mqa,
259 + q_a_layernorm=None,
260 + q_b_proj=None,
261 + q_proj=self.q_proj,
262 + indexer=None,
263 + is_sparse=False,
264 + topk_indices_buffer=None,
265 + )
266 + self.mla_attn = MultiHeadLatentAttentionWrapper(
267 + self.hidden_size,
268 + self.num_local_heads,
269 + self.scaling,
270 + self.qk_nope_head_dim,
271 + self.qk_rope_head_dim,
272 + self.v_head_dim,
273 + self.q_lora_rank,
274 + self.kv_lora_rank,
275 + mla_modules,
276 + cache_config,
277 + quant_config,
278 + prefix,
279 + )
280 +
281 + def forward(
282 + self,
283 + positions: torch.Tensor,
284 + hidden_states: torch.Tensor,
285 + output: torch.Tensor,
286 + ) -> None:
287 + output[:] = self.mla_attn(positions, hidden_states)
288 +
289 +
290 +class KimiDecoderLayer(nn.Module):
291 + def __init__(
292 + self,
293 + config: KimiLinearConfig,
294 + layer_idx: int,
295 + cache_config: CacheConfig | None = None,
296 + quant_config: QuantizationConfig | None = None,
297 + parallel_config: ParallelConfig | None = None,
298 + model_config: ModelConfig | None = None,
299 + prefix: str = "",
300 + **kwargs,
301 + ) -> None:
302 + super().__init__()
303 + self.hidden_size = config.hidden_size
304 +
305 + self.is_moe = config.is_moe
306 +
307 + if config.is_kda_layer(layer_idx):
308 + self.self_attn = KimiDeltaAttention(
309 + layer_idx=layer_idx,
310 + hidden_size=config.hidden_size,
311 + quant_config=quant_config,
312 + cache_config=cache_config,
313 + model_config=config,
314 + prefix=f"{prefix}.self_attn",
315 + )
316 + else:
317 + self.self_attn = KimiMLAAttention(
318 + layer_idx=layer_idx,
319 + hidden_size=self.hidden_size,
320 + num_heads=config.num_attention_heads,
321 + quant_config=quant_config,
322 + cache_config=cache_config,
323 + model_config=model_config,
324 + prefix=f"{prefix}.self_attn",
325 + config=config,
326 + qk_nope_head_dim=config.qk_nope_head_dim,
327 + qk_rope_head_dim=config.qk_rope_head_dim,
328 + v_head_dim=config.v_head_dim,
329 + q_lora_rank=config.q_lora_rank,
330 + kv_lora_rank=config.kv_lora_rank,
331 + use_nope=config.mla_use_nope,
332 + )
333 +
334 + if (
335 + self.is_moe
336 + and config.num_experts is not None
337 + and layer_idx >= config.first_k_dense_replace
338 + and layer_idx % config.moe_layer_freq == 0
339 + ):
340 + self.block_sparse_moe = KimiMoE(
341 + config=config,
342 + quant_config=quant_config,
343 + prefix=f"{prefix}.mlp",
344 + )
345 + self.mlp = self.block_sparse_moe
346 + else:
347 + self.mlp = KimiMLP(
348 + hidden_size=self.hidden_size,
349 + intermediate_size=config.intermediate_size,
350 + hidden_act=config.hidden_act,
351 + quant_config=quant_config,
352 + prefix=f"{prefix}.mlp",
353 + )
354 + self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
355 + self.post_attention_layernorm = RMSNorm(
356 + config.hidden_size, eps=config.rms_norm_eps
357 + )
358 +
359 + def forward(
360 + self,
361 + positions: torch.Tensor,
362 + hidden_states: torch.Tensor,
363 + residual: torch.Tensor | None,
364 + **kwargs,
365 + ) -> tuple[torch.Tensor, torch.Tensor]:
366 + # Self Attention
367 + if residual is None:
368 + residual = hidden_states
369 + hidden_states = self.input_layernorm(hidden_states)
370 + else:
371 + hidden_states, residual = self.input_layernorm(hidden_states, residual)
372 +
373 + attn_output = torch.empty_like(hidden_states)
374 + self.self_attn(
375 + hidden_states=hidden_states,
376 + positions=positions,
377 + output=attn_output,
378 + )
379 + hidden_states = attn_output
380 +
381 + # Fully Connected
382 + hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
383 + hidden_states = self.mlp(hidden_states)
384 + return hidden_states, residual
385 +
386 +
387 +@support_torch_compile
388 +class KimiLinearModel(nn.Module):
389 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
390 + super().__init__()
391 +
392 + config = vllm_config.model_config.hf_text_config
393 + model_config = vllm_config.model_config
394 + cache_config = vllm_config.cache_config
395 + quant_config = vllm_config.quant_config
396 + parallel_config = vllm_config.parallel_config
397 + self.config = config
398 +
399 + self.padding_idx = config.pad_token_id
400 + self.vocab_size = config.vocab_size
401 +
402 + if get_pp_group().is_first_rank:
403 + self.embed_tokens = VocabParallelEmbedding(
404 + config.vocab_size,
405 + config.hidden_size,
406 + prefix=f"{prefix}.embed_tokens",
407 + )
408 + else:
409 + self.embed_tokens = PPMissingLayer()
410 +
411 + extra_kwargs = {}
412 +
413 + def get_layer(prefix: str):
414 + layer_idx = int(prefix.rsplit(".", 1)[1])
415 + return KimiDecoderLayer(
416 + config,
417 + layer_idx,
418 + cache_config,
419 + quant_config,
420 + parallel_config,
421 + model_config,
422 + prefix,
423 + **extra_kwargs,
424 + )
425 +
426 + self.start_layer, self.end_layer, self.layers = make_layers(
427 + config.num_hidden_layers,
428 + get_layer,
429 + prefix=f"{prefix}.layers",
430 + )
431 +
432 + if get_pp_group().is_last_rank:
433 + self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
434 + else:
435 + self.norm = PPMissingLayer()
436 +
437 + world_size = get_tensor_model_parallel_world_size()
438 + assert config.num_attention_heads % world_size == 0, (
439 + "num_attention_heads must be divisible by world_size"
440 + )
441 +
442 + def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
443 + return self.embed_tokens(input_ids)
444 +
445 + def forward(
446 + self,
447 + input_ids: torch.Tensor | None,
448 + positions: torch.Tensor,
449 + intermediate_tensors: IntermediateTensors | None,
450 + inputs_embeds: torch.Tensor | None = None,
451 + **kwargs,
452 + ) -> torch.Tensor:
453 + if get_pp_group().is_first_rank:
454 + if inputs_embeds is not None:
455 + hidden_states = inputs_embeds
456 + else:
457 + hidden_states = self.get_input_embeddings(input_ids)
458 + residual = None
459 + else:
460 + assert intermediate_tensors is not None
461 + hidden_states = intermediate_tensors["hidden_states"]
462 + residual = intermediate_tensors["residual"]
463 +
464 + for _, layer in enumerate(self.layers[self.start_layer : self.end_layer]):
465 + hidden_states, residual = layer(
466 + positions=positions,
467 + hidden_states=hidden_states,
468 + residual=residual,
469 + )
470 +
471 + if not get_pp_group().is_last_rank:
472 + return IntermediateTensors(
473 + {"hidden_states": hidden_states, "residual": residual}
474 + )
475 +
476 + hidden_states, _ = self.norm(hidden_states, residual)
477 + return hidden_states
478 +
479 +
480 +class KimiLinearForCausalLM(
481 + nn.Module, HasInnerState, SupportsPP, MixtureOfExperts, IsHybrid
482 +):
483 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
484 + super().__init__()
485 + self.model_config = vllm_config.model_config
486 + self.vllm_config = vllm_config
487 + self.config = self.model_config.hf_config
488 + quant_config = vllm_config.quant_config
489 + self.quant_config = quant_config
490 + self.model = KimiLinearModel(
491 + vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
492 + )
493 + if get_pp_group().is_last_rank:
494 + self.lm_head = ParallelLMHead(
495 + self.config.vocab_size,
496 + self.config.hidden_size,
497 + quant_config=quant_config,
498 + prefix=maybe_prefix(prefix, "lm_head"),
499 + )
500 + else:
501 + self.lm_head = PPMissingLayer()
502 + logit_scale = getattr(self.config, "logit_scale", 1.0)
503 + self.logits_processor = LogitsProcessor(
504 + self.config.vocab_size, scale=logit_scale
505 + )
506 +
507 + def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
508 + return self.model.get_input_embeddings(input_ids)
509 +
510 + def forward(
511 + self,
512 + input_ids: torch.Tensor,
513 + positions: torch.Tensor,
514 + intermediate_tensors: IntermediateTensors | None = None,
515 + inputs_embeds: torch.Tensor | None = None,
516 + **kwargs,
517 + ) -> torch.Tensor | IntermediateTensors:
518 + hidden_states = self.model(
519 + input_ids, positions, intermediate_tensors, inputs_embeds, **kwargs
520 + )
521 + return hidden_states
522 +
523 + @classmethod
524 + def get_mamba_state_dtype_from_config(
525 + cls,
526 + vllm_config: "VllmConfig",
527 + ) -> tuple[torch.dtype, torch.dtype, torch.dtype, torch.dtype]:
528 + return MambaStateDtypeCalculator.kda_state_dtype(
529 + vllm_config.model_config.dtype, vllm_config.cache_config.mamba_cache_dtype
530 + )
531 +
532 + @classmethod
533 + def get_mamba_state_shape_from_config(
534 + cls, vllm_config: "VllmConfig"
535 + ) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...], tuple[int, ...]]:
536 + parallel_config = vllm_config.parallel_config
537 + hf_config = vllm_config.model_config.hf_config
538 + tp_size = parallel_config.tensor_parallel_size
539 + num_spec = (
540 + vllm_config.speculative_config.num_speculative_tokens
541 + if vllm_config.speculative_config
542 + else 0
543 + )
544 + return MambaStateShapeCalculator.kda_state_shape(
545 + tp_size,
546 + hf_config.linear_attn_config["num_heads"],
547 + hf_config.linear_attn_config["head_dim"],
548 + conv_kernel_size=hf_config.linear_attn_config["short_conv_kernel_size"],
549 + num_spec=num_spec,
550 + )
551 +
552 + def compute_logits(
553 + self,
554 + hidden_states: torch.Tensor,
555 + ) -> torch.Tensor | None:
556 + return self.logits_processor(self.lm_head, hidden_states)
557 +
558 + def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
559 + stacked_params_mapping = [
560 + # (param_name, shard_name, shard_id)
561 + (".gate_up_proj", ".gate_proj", 0),
562 + (".gate_up_proj", ".up_proj", 1),
563 + ]
564 + if self.config.is_moe:
565 + # Params for weights, fp8 weight scales, fp8 activation scales
566 + # (param_name, weight_name, expert_id, shard_id)
567 + expert_params_mapping = FusedMoE.make_expert_params_mapping(
568 + ckpt_gate_proj_name="w1",
569 + ckpt_down_proj_name="w2",
570 + ckpt_up_proj_name="w3",
571 + num_experts=self.config.num_experts,
572 + )
573 + else:
574 + expert_params_mapping = []
575 + params_dict = dict(self.named_parameters())
576 + loaded_params: set[str] = set()
577 + for args in weights:
578 + name, loaded_weight = args[:2]
579 + kwargs = args[2] if len(args) > 2 else {}
580 + if "rotary_emb.inv_freq" in name:
581 + continue
582 +
583 + spec_layer = get_spec_layer_idx_from_weight_name(self.config, name)
584 + if spec_layer is not None:
585 + continue # skip spec decode layers for main model
586 + if "rotary_emb.cos_cached" in name or "rotary_emb.sin_cached" in name:
587 + # Models trained using ColossalAI may include these tensors in
588 + # the checkpoint. Skip them.
589 + continue
590 + for param_name, weight_name, shard_id in stacked_params_mapping:
591 + if weight_name not in name:
592 + continue
593 + # We have mlp.experts[0].gate_proj in the checkpoint.
594 + # Since we handle the experts below in expert_params_mapping,
595 + # we need to skip here BEFORE we update the name, otherwise
596 + # name will be updated to mlp.experts[0].gate_up_proj, which
597 + # will then be updated below in expert_params_mapping
598 + # for mlp.experts[0].gate_gate_up_proj, which breaks load.
599 + if ("mlp.experts." in name) and name not in params_dict:
600 + continue
601 + name = name.replace(weight_name, param_name)
602 + # Skip loading extra bias for GPTQ models.
603 + if name.endswith(".bias") and name not in params_dict:
604 + continue
605 + if is_pp_missing_parameter(name, self):
606 + continue
607 + param = params_dict[name]
608 + weight_loader = param.weight_loader
609 + weight_loader(param, loaded_weight, shard_id)
610 + break
611 + else:
612 + for idx, (param_name, weight_name, expert_id, shard_id) in enumerate(
613 + expert_params_mapping
614 + ):
615 + if weight_name not in name:
616 + continue
617 + name = name.replace(weight_name, param_name)
618 + if is_pp_missing_parameter(name, self):
619 + continue
620 + param = params_dict[name]
621 + weight_loader = param.weight_loader
622 + weight_loader(
623 + param,
624 + loaded_weight,
625 + name,
626 + expert_id=expert_id,
627 + shard_id=shard_id,
628 + )
629 + break
630 + else:
631 + # Skip loading extra bias for GPTQ models.
632 + if (
633 + name.endswith(".bias")
634 + and name not in params_dict
635 + and not self.config.is_linear_attn
636 + ): # noqa: E501
637 + continue
638 + # Remapping the name of FP8 kv-scale.
639 + name = maybe_remap_kv_scale_name(name, params_dict)
640 + if name is None:
641 + continue
642 + if is_pp_missing_parameter(name, self):
643 + continue
644 +
645 + param = params_dict[name]
646 + weight_loader = getattr(
647 + param, "weight_loader", default_weight_loader
648 + )
649 + weight_loader(param, loaded_weight, **kwargs)
650 + loaded_params.add(name)
651 +
652 +
653 +def get_spec_layer_idx_from_weight_name(
654 + config: KimiLinearConfig, weight_name: str
655 +) -> int | None:
656 + if hasattr(config, "num_nextn_predict_layers") and (
657 + config.num_nextn_predict_layers > 0
658 + ):
659 + layer_idx = config.num_hidden_layers
660 + for i in range(config.num_nextn_predict_layers):
661 + if weight_name.startswith(f"model.layers.{layer_idx + i}."):
662 + return layer_idx + i
663 + return None
vllm/model_executor/models/registry.py
+1
@@ -118,6 +118,7 @@ _TEXT_GENERATION_MODELS = {
118 "InternLM3ForCausalLM": ("llama", "LlamaForCausalLM"),
119 "JAISLMHeadModel": ("jais", "JAISLMHeadModel"),
120 "JambaForCausalLM": ("jamba", "JambaForCausalLM"),
121 + "KimiLinearForCausalLM": ("kimi_linear", "KimiLinearForCausalLM"), # noqa: E501
122 "Lfm2ForCausalLM": ("lfm2", "Lfm2ForCausalLM"),
123 "Lfm2MoeForCausalLM": ("lfm2_moe", "Lfm2MoeForCausalLM"),
124 "LlamaForCausalLM": ("llama", "LlamaForCausalLM"),
vllm/transformers_utils/config.py
+1
@@ -79,6 +79,7 @@ _CONFIG_REGISTRY: dict[str, type[PretrainedConfig]] = LazyConfigDict(
79 deepseek_v3="DeepseekV3Config",
80 deepseek_v32="DeepseekV3Config",
81 flex_olmo="FlexOlmoConfig",
82 + kimi_linear="KimiLinearConfig",
83 kimi_vl="KimiVLConfig",
84 Llama_Nemotron_Nano_VL="Nemotron_Nano_VL_Config",
85 RefinedWeb="RWConfig", # For tiiuae/falcon-40b(-instruct)
vllm/transformers_utils/configs/__init__.py
+2
@@ -19,6 +19,7 @@ from vllm.transformers_utils.configs.eagle import EAGLEConfig
19 from vllm.transformers_utils.configs.falcon import RWConfig
20 from vllm.transformers_utils.configs.flex_olmo import FlexOlmoConfig
21 from vllm.transformers_utils.configs.jais import JAISConfig
22 +from vllm.transformers_utils.configs.kimi_linear import KimiLinearConfig
23 from vllm.transformers_utils.configs.kimi_vl import KimiVLConfig
24 from vllm.transformers_utils.configs.lfm2_moe import Lfm2MoeConfig
25 from vllm.transformers_utils.configs.medusa import MedusaConfig
@@ -54,6 +55,7 @@ __all__ = [
55 "MiDashengLMConfig",
56 "MLPSpeculatorConfig",
57 "MoonViTConfig",
58 + "KimiLinearConfig",
59 "KimiVLConfig",
60 "NemotronConfig",
61 "NemotronHConfig",
vllm/transformers_utils/configs/kimi_linear.py new
+144
@@ -0,0 +1,144 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +from transformers.configuration_utils import PretrainedConfig
5 +
6 +from vllm.logger import init_logger
7 +
8 +logger = init_logger(__name__)
9 +
10 +
11 +class KimiLinearConfig(PretrainedConfig):
12 + model_type = "kimi_linear"
13 + keys_to_ignore_at_inference = ["past_key_values"]
14 +
15 + def __init__(
16 + self,
17 + model_type="kimi_linear",
18 + vocab_size=163840,
19 + hidden_size=4096,
20 + head_dim=None,
21 + intermediate_size=11008,
22 + num_hidden_layers=32,
23 + num_attention_heads=32,
24 + num_key_value_heads=None,
25 + hidden_act="silu",
26 + initializer_range=0.02,
27 + rms_norm_eps=1e-6,
28 + use_cache=True,
29 + pad_token_id=0,
30 + bos_token_id=1,
31 + eos_token_id=2,
32 + rope_theta=10000.0,
33 + rope_scaling=None,
34 + tie_word_embeddings=False,
35 + moe_intermediate_size: int | None = None,
36 + moe_renormalize: bool = True,
37 + moe_router_activation_func: str = "sigmoid",
38 + num_experts: int | None = None,
39 + num_experts_per_token: int | None = None,
40 + num_shared_experts: int = 0,
41 + routed_scaling_factor: float = 1.0,
42 + first_k_dense_replace: int = 0,
43 + moe_layer_freq: int = 1,
44 + use_grouped_topk: bool = True,
45 + num_expert_group: int = 1,
46 + topk_group: int = 1,
47 + q_lora_rank: int | None = None,
48 + kv_lora_rank: int | None = None,
49 + qk_nope_head_dim: int | None = None,
50 + qk_rope_head_dim: int | None = None,
51 + v_head_dim: int | None = None,
52 + mla_use_nope: bool | None = False,
53 + num_nextn_predict_layers: int = 0,
54 + linear_attn_config: dict | None = None,
55 + **kwargs,
56 + ):
57 + self.model_type = model_type
58 + self.vocab_size = vocab_size
59 + self.hidden_size = hidden_size
60 + self.head_dim = (
61 + head_dim if head_dim is not None else hidden_size // num_attention_heads
62 + )
63 + self.intermediate_size = intermediate_size
64 + self.num_hidden_layers = num_hidden_layers
65 + self.num_attention_heads = num_attention_heads
66 +
67 + # for backward compatibility
68 + if num_key_value_heads is None:
69 + num_key_value_heads = num_attention_heads
70 +
71 + self.num_key_value_heads = num_key_value_heads
72 + self.hidden_act = hidden_act
73 + self.initializer_range = initializer_range
74 + self.rms_norm_eps = rms_norm_eps
75 + self.use_cache = use_cache
76 + self.rope_theta = rope_theta
77 + self.rope_scaling = rope_scaling
78 +
79 + self.q_lora_rank = q_lora_rank
80 + self.kv_lora_rank = kv_lora_rank
81 + self.qk_nope_head_dim = qk_nope_head_dim
82 + self.qk_rope_head_dim = qk_rope_head_dim
83 + self.v_head_dim = v_head_dim
84 + self.mla_use_nope = mla_use_nope
85 + # moe config
86 + self.num_experts = num_experts
87 + self.num_experts_per_token = num_experts_per_token
88 + self.moe_renormalize = moe_renormalize
89 + self.num_shared_experts = num_shared_experts
90 + self.routed_scaling_factor = routed_scaling_factor
91 + self.moe_router_activation_func = moe_router_activation_func
92 + assert self.moe_router_activation_func in ("softmax", "sigmoid")
93 + self.moe_intermediate_size = moe_intermediate_size
94 + self.first_k_dense_replace = first_k_dense_replace
95 + self.moe_layer_freq = moe_layer_freq
96 + self.use_grouped_topk = use_grouped_topk
97 + self.num_expert_group = num_expert_group
98 + self.topk_group = topk_group
99 + self.num_nextn_predict_layers = num_nextn_predict_layers
100 +
101 + if linear_attn_config is not None:
102 + assert linear_attn_config["kda_layers"] is not None
103 + assert linear_attn_config["full_attn_layers"] is not None
104 + self.linear_attn_config = linear_attn_config
105 +
106 + super().__init__(
107 + pad_token_id=pad_token_id,
108 + bos_token_id=bos_token_id,
109 + eos_token_id=eos_token_id,
110 + tie_word_embeddings=tie_word_embeddings,
111 + **kwargs,
112 + )
113 +
114 + @property
115 + def is_mla(self):
116 + return (
117 + self.q_lora_rank is not None
118 + or self.kv_lora_rank is not None
119 + or self.qk_nope_head_dim is not None
120 + or self.qk_rope_head_dim is not None
121 + or self.v_head_dim is not None
122 + or self.mla_use_nope is True
123 + )
124 +
125 + @property
126 + def is_moe(self):
127 + return self.num_experts is not None
128 +
129 + @property
130 + def is_linear_attn(self) -> bool:
131 + return not (
132 + self.linear_attn_config is None
133 + or (
134 + isinstance(self.linear_attn_config, dict)
135 + and self.linear_attn_config["kda_layers"] is not None
136 + and len(self.linear_attn_config["kda_layers"]) == 0
137 + )
138 + )
139 +
140 + def is_kda_layer(self, layer_idx: int):
141 + return (
142 + self.linear_attn_config is not None
143 + and (layer_idx + 1) in self.linear_attn_config["kda_layers"]
144 + )
vllm/v1/worker/gpu_model_runner.py
+12 -19
@@ -8,6 +8,7 @@ from collections import defaultdict
8 from collections.abc import Iterator
9 from contextlib import contextmanager
10 from copy import deepcopy
11 +from functools import reduce
12 from itertools import product
13 from typing import TYPE_CHECKING, Any, NamedTuple, TypeAlias, cast
14
@@ -4134,26 +4135,18 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
4135
4136 def calculate_reorder_batch_threshold(self) -> None:
4137 """
4137 - Check that if any backends reorder batches; that the reordering
4138 - is compatible (e.g., decode threshold is the same)
4138 + Choose the minimum reorder batch threshold from all attention groups.
4139 + Backends should be able to support lower threshold then what they request
4140 + just may have a performance penalty due to that backend treating decodes
4141 + as prefills.
4142 """
4140 - for group in self._attn_group_iterator():
4141 - attn_metadata_builder_i = group.get_metadata_builder()
4142 -
4143 - # check that if any backends reorder batches; that the reordering
4144 - # is compatible (e.g., decode threshold is the same)
4145 - reorder_batch_threshold_i = attn_metadata_builder_i.reorder_batch_threshold
4146 - if reorder_batch_threshold_i is not None:
4147 - if self.reorder_batch_threshold is not None:
4148 - if reorder_batch_threshold_i != self.reorder_batch_threshold:
4149 - raise ValueError(
4150 - f"Attention backend reorders decodes with "
4151 - f"threshold {reorder_batch_threshold_i} but other "
4152 - f"backend uses threshold "
4153 - f"{self.reorder_batch_threshold}"
4154 - )
4155 - else:
4156 - self.reorder_batch_threshold = reorder_batch_threshold_i
4143 + min_none_high = lambda a, b: a if b is None else b if a is None else min(a, b)
4144 +
4145 + reorder_batch_thresholds = [
4146 + group.get_metadata_builder().reorder_batch_threshold
4147 + for group in self._attn_group_iterator()
4148 + ]
4149 + self.reorder_batch_threshold = reduce(min_none_high, reorder_batch_thresholds)
4150
4151 def _find_compatible_block_sizes(
4152 self,