main
py 1,588 lines 58.2 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # Copyright 2026 MBZUAI-IFM.
5 # Copyright 2024 The Qwen team.
6 # Copyright 2023 The vLLM team.
7 # Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
8 #
9 # This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
10 # and OPT implementations in this library. It has been modified from its
11 # original forms to accommodate minor architectural differences compared
12 # to GPT-NeoX and OPT used by the Meta AI team that trained the model.
13 #
14 # Licensed under the Apache License, Version 2.0 (the "License");
15 # you may not use this file except in compliance with the License.
16 # You may obtain a copy of the License at
17 #
18 # http://www.apache.org/licenses/LICENSE-2.0
19 #
20 # Unless required by applicable law or agreed to in writing, software
21 # distributed under the License is distributed on an "AS IS" BASIS,
22 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 # See the License for the specific language governing permissions and
24 # limitations under the License.
25 """Inference-only K2Horizon model compatible with HuggingFace weights."""
26
27 import math
28 import typing
29 from collections.abc import Callable, Iterable
30 from itertools import islice
31 from typing import Any
32
33 import torch
34 import torch.nn.functional as F
35 from torch import nn
36
37 from vllm import _custom_ops as ops
38 from vllm.compilation.decorators import support_torch_compile
39 from vllm.config import CacheConfig, VllmConfig, get_current_vllm_config
40 from vllm.distributed import (
41 get_ep_group,
42 get_pp_group,
43 get_tensor_model_parallel_rank,
44 get_tensor_model_parallel_world_size,
45 tensor_model_parallel_all_gather,
46 )
47 from vllm.logger import init_logger
48 from vllm.model_executor.layers.activation import SiluAndMul
49 from vllm.model_executor.layers.attention import Attention
50 from vllm.model_executor.layers.fused_moe import (
51 FusedMoEFactory,
52 GateLinear,
53 fused_moe_make_expert_params_mapping,
54 )
55 from vllm.model_executor.layers.fused_moe.config import (
56 _get_config_dtype_str,
57 )
58 from vllm.model_executor.layers.fused_moe.fused_moe import (
59 _get_config_quant_dtype,
60 _prepare_expert_assignment,
61 dispatch_fused_moe_kernel,
62 try_get_optimal_moe_config,
63 )
64 from vllm.model_executor.layers.fused_moe.utils import (
65 moe_kernel_quantize_input,
66 )
67 from vllm.model_executor.layers.layernorm import RMSNorm
68 from vllm.model_executor.layers.linear import (
69 ColumnParallelLinear,
70 MergedColumnParallelLinear,
71 QKVParallelLinear,
72 ReplicatedLinear,
73 RowParallelLinear,
74 UnquantizedLinearMethod,
75 )
76 from vllm.model_executor.layers.logits_processor import LogitsProcessor
77 from vllm.model_executor.layers.quantization import QuantizationConfig
78 from vllm.model_executor.layers.rotary_embedding import get_rope
79 from vllm.model_executor.layers.vocab_parallel_embedding import (
80 ParallelLMHead,
81 VocabParallelEmbedding,
82 )
83 from vllm.model_executor.model_loader.weight_utils import (
84 default_weight_loader,
85 maybe_remap_kv_scale_name,
86 )
87 from vllm.model_executor.models.interfaces import (
88 EagleModelMixin,
89 MixtureOfExperts,
90 SupportsEagle,
91 SupportsEagle3,
92 SupportsLoRA,
93 SupportsPP,
94 )
95 from vllm.model_executor.models.utils import (
96 AutoWeightsLoader,
97 PPMissingLayer,
98 extract_layer_index,
99 is_pp_missing_parameter,
100 make_empty_intermediate_tensors_factory,
101 make_layers,
102 maybe_prefix,
103 sequence_parallel_chunk,
104 )
105 from vllm.sequence import IntermediateTensors
106 from vllm.triton_utils import tl
107
108 logger = init_logger(__name__)
109
110
111 def split_to_interleaved(x):
112 # Split halves: x0 x1 x2 x3 ... y0 y1 y2 y3 ...
113 # Interleaved: x0 y0 x1 y1 x2 y2 x3 y3 ...
114 return x.reshape(*x.shape[:-1], 2, -1).transpose(-1, -2).reshape(*x.shape[:-1], -1)
115
116
117 def interleaved_to_split(x):
118 # Interleaved: x0 y0 x1 y1 x2 y2 x3 y3 ...
119 # Split halves: x0 x1 x2 x3 ... y0 y1 y2 y3 ...
120 return x.reshape(*x.shape[:-1], -1, 2).transpose(-1, -2).reshape(*x.shape[:-1], -1)
121
122
123 def is_rope_weights_folding_supported(
124 qk_proj: nn.Module,
125 dual_chunk_attention_config: dict[str, Any] | None,
126 ) -> bool:
127 """Whether the partial-RoPE weight-fold path is safe for this attention.
128
129 Folding permutes the q/k projection's output channels at load time, which
130 only produces correct numerics when:
131
132 * the q/k projection is dense/unquantized and
133 * dual-chunk attention is disabled -- the P.RoPE equivalence has not been
134 validated with dual-chunk attention.
135
136 When either condition fails we fall back to the runtime channel-permutation
137 RoPE path instead of folding.
138 """
139 is_dense_qk_proj = isinstance(
140 getattr(qk_proj, "quant_method", None), UnquantizedLinearMethod
141 )
142 return is_dense_qk_proj and dual_chunk_attention_config is None
143
144
145 def apply_partial_rope(
146 rotary_emb,
147 positions: torch.Tensor,
148 q: torch.Tensor,
149 k: torch.Tensor,
150 num_heads: int,
151 num_kv_heads: int,
152 head_dim: int,
153 rope_head_dim: int,
154 fold_rope_weights: bool,
155 ) -> tuple[torch.Tensor, torch.Tensor]:
156 if fold_rope_weights or rope_head_dim == head_dim:
157 return rotary_emb(positions, q, k)
158
159 q = q.reshape(*q.shape[:-1], num_heads, head_dim)
160 k = k.reshape(*k.shape[:-1], num_kv_heads, head_dim)
161
162 q_rope, q_nope = torch.split(
163 split_to_interleaved(q),
164 split_size_or_sections=[rope_head_dim, head_dim - rope_head_dim],
165 dim=-1,
166 )
167 k_rope, k_nope = torch.split(
168 split_to_interleaved(k),
169 split_size_or_sections=[rope_head_dim, head_dim - rope_head_dim],
170 dim=-1,
171 )
172
173 q_rope, k_rope = rotary_emb(
174 positions, interleaved_to_split(q_rope), interleaved_to_split(k_rope)
175 )
176
177 q = interleaved_to_split(
178 torch.cat([split_to_interleaved(q_rope), q_nope], dim=-1)
179 ).reshape(*q.shape[:-2], -1)
180 k = interleaved_to_split(
181 torch.cat([split_to_interleaved(k_rope), k_nope], dim=-1)
182 ).reshape(*k.shape[:-2], -1)
183
184 return q, k
185
186
187 def _rope_weight_perm(head_dim: int, rope_head_dim: int) -> torch.Tensor:
188 """Per-head gather index that folds K2Horizon's partial-RoPE channel reordering.
189
190 The K2Horizon checkpoint stores each head's channels in a GPT-J-interleaved
191 convention with rope/nope channels interleaved. Applying this fixed
192 (position-independent) permutation to the q/k weight rows lets the runtime
193 use vLLM's *native* NeoX partial-RoPE path (``head_size = head_dim``,
194 ``rotary_dim = rope_head_dim``) with no per-forward permutes/all-gather.
195 """
196 D, R = head_dim, rope_head_dim
197 assert R % 2 == 0 and R <= D, (
198 f"partial NeoX RoPE requires an even rope_head_dim <= head_dim, "
199 f"got R={R}, D={D}"
200 )
201 h = D // 2
202 idx = torch.cat(
203 [
204 torch.arange(0, R // 2), # rope first-half channels
205 torch.arange(h, h + R // 2), # rope second-half channels
206 torch.arange(R // 2, h), # nope remainder (first half)
207 torch.arange(h + R // 2, D), # nope remainder (second half)
208 ]
209 )
210 return idx
211
212
213 def calc_router_weights(
214 router_logits: torch.Tensor,
215 e_score_correction_bias: torch.Tensor | None,
216 score_func: str,
217 top_k: int,
218 scaling_factor: float | None,
219 renormalize: bool | None = None,
220 ) -> tuple[torch.Tensor, torch.Tensor]:
221 if score_func == "softmax":
222 routing_scores = F.softmax(router_logits, dim=-1, dtype=torch.float32)
223 else:
224 assert score_func == "sigmoid", (
225 f"Unsupported router score function: {score_func}"
226 )
227 routing_scores = torch.sigmoid(router_logits.to(torch.float32))
228
229 selection_scores = routing_scores
230 if e_score_correction_bias is not None:
231 selection_scores = selection_scores + e_score_correction_bias.to(
232 selection_scores
233 )
234
235 selected_indices = torch.topk(selection_scores, top_k, dim=-1).indices
236 routing_weights = torch.gather(routing_scores, dim=-1, index=selected_indices)
237 if renormalize is None:
238 renormalize = top_k > 1
239 if renormalize:
240 routing_weights = routing_weights / routing_weights.sum(dim=-1, keepdim=True)
241 if scaling_factor is not None:
242 routing_weights = routing_weights * scaling_factor
243 return routing_weights.to(router_logits.dtype), selected_indices
244
245
246 def fused_mova_impl(
247 config,
248 hidden_states: torch.Tensor,
249 w1: torch.Tensor,
250 topk_weights: torch.Tensor,
251 topk_ids: torch.Tensor,
252 use_fp8_w8a8: bool = False,
253 use_int8_w8a8: bool = False,
254 use_int8_w8a16: bool = False,
255 use_int4_w4a16: bool = False,
256 ocp_mx_scheme: str | None = None,
257 per_channel_quant: bool = False,
258 global_num_experts: int = -1,
259 expert_map: torch.Tensor | None = None,
260 w1_scale: torch.Tensor | None = None,
261 w1_zp: torch.Tensor | None = None,
262 a1_scale: torch.Tensor | None = None,
263 block_shape: list[int] | None = None,
264 w1_bias: torch.Tensor | None = None,
265 ) -> torch.Tensor:
266 if ocp_mx_scheme is not None:
267 raise NotImplementedError(
268 f"Using ocp_mx_scheme={ocp_mx_scheme} in functional fused_experts call is "
269 "deprecated. Please use OCP_MXQuantizationEmulationTritonExperts."
270 )
271
272 # Check constraints.
273 if use_int4_w4a16:
274 assert hidden_states.size(1) // 2 == w1.size(2), "Hidden size mismatch"
275 else:
276 assert hidden_states.size(1) == w1.size(2), (
277 f"Hidden size mismatch {hidden_states.size(1)} != {w1.size(2)}"
278 )
279
280 assert topk_weights.size() == topk_ids.size(), "topk shape mismatch"
281 assert hidden_states.is_contiguous(), "Hidden_states must be contiguous"
282 assert w1.stride(-1) == 1, "Stride of last dimension must be 1"
283 assert hidden_states.dtype in [torch.float32, torch.float16, torch.bfloat16]
284
285 num_tokens = hidden_states.size(0)
286 E, N, K = w1.size()
287
288 if global_num_experts == -1:
289 global_num_experts = E
290 top_k_num = topk_ids.size(1)
291 M = num_tokens
292
293 # Note: for use_int8_w8a16 or use_int4_w4a16, the activations are
294 # quantized prior to calling fused_experts.
295 quant_dtype = _get_config_quant_dtype(
296 use_fp8_w8a8=use_fp8_w8a8,
297 use_int8_w8a8=use_int8_w8a8,
298 )
299
300 intermediate_cache1 = torch.empty(
301 (M, top_k_num, N), device=hidden_states.device, dtype=hidden_states.dtype
302 )
303
304 if hidden_states.dtype == torch.bfloat16:
305 compute_type = tl.bfloat16
306 elif hidden_states.dtype == torch.float16:
307 compute_type = tl.float16
308 elif hidden_states.dtype == torch.float32:
309 compute_type = tl.float32
310 else:
311 raise ValueError(f"Unsupported compute_type: {hidden_states.dtype}")
312
313 out_hidden_states = torch.empty(
314 (M, N),
315 device=hidden_states.device,
316 dtype=hidden_states.dtype,
317 )
318
319 qhidden_states, a1q_scale = moe_kernel_quantize_input(
320 A=hidden_states,
321 A_scale=a1_scale,
322 quant_dtype=quant_dtype,
323 per_act_token_quant=per_channel_quant,
324 block_shape=block_shape,
325 )
326
327 sorted_token_ids, expert_ids, num_tokens_post_padded = _prepare_expert_assignment(
328 topk_ids,
329 config,
330 num_tokens,
331 top_k_num,
332 global_num_experts,
333 expert_map,
334 use_int8_w8a16=use_int8_w8a16,
335 use_int4_w4a16=use_int4_w4a16,
336 block_shape=block_shape,
337 ignore_invalid_experts=True,
338 )
339
340 dispatch_fused_moe_kernel(
341 qhidden_states,
342 w1,
343 intermediate_cache1,
344 a1q_scale,
345 w1_scale,
346 w1_zp,
347 topk_weights,
348 sorted_token_ids,
349 expert_ids,
350 num_tokens_post_padded,
351 False, # apply_router_weight_on_input,
352 top_k_num,
353 config,
354 compute_type=compute_type,
355 use_fp8_w8a8=use_fp8_w8a8,
356 use_int8_w8a8=use_int8_w8a8,
357 use_int8_w8a16=use_int8_w8a16,
358 use_int4_w4a16=use_int4_w4a16,
359 per_channel_quant=per_channel_quant,
360 block_shape=block_shape,
361 B_bias=w1_bias,
362 )
363
364 intermediate_cache1 = F.silu(intermediate_cache1)
365 intermediate_cache1 *= topk_weights[:, :, None].to(intermediate_cache1.dtype)
366
367 ops.moe_sum(intermediate_cache1, out_hidden_states)
368
369 return out_hidden_states
370
371
372 class K2HorizonRMSNorm(RMSNorm):
373 def __init__(self, hidden_size: int, n_groups: int, eps: float = 1e-6):
374 assert hidden_size % n_groups == 0
375 super().__init__(hidden_size, eps=eps)
376 self.hidden_size = hidden_size
377 self.n_groups = n_groups
378 self.group_size = hidden_size // n_groups
379
380 def forward(self, x, residual=None):
381 if self.n_groups == 1:
382 return super().forward(x, residual)
383
384 if residual is not None:
385 x = x + residual
386 residual = x
387
388 orig_shape = x.shape
389 x_grouped = x.reshape(-1, self.n_groups, self.group_size).reshape(
390 -1, self.group_size
391 )
392 y_grouped = torch.empty_like(x_grouped)
393
394 # Reuse vLLM's RMSNorm CUDA kernel on each group.
395 ops.rms_norm(
396 y_grouped,
397 x_grouped,
398 torch.ones(self.group_size, dtype=x.dtype, device=x.device),
399 self.variance_epsilon,
400 )
401
402 y = y_grouped.reshape(orig_shape) * self.weight
403 return (y, residual) if residual is not None else y
404
405
406 class K2HorizonMLP(nn.Module):
407 def __init__(
408 self,
409 hidden_size: int,
410 intermediate_size: int,
411 hidden_act: str,
412 quant_config: QuantizationConfig | None = None,
413 reduce_results: bool = True,
414 expert_gate: torch.nn.Linear | None = None,
415 prefix: str = "",
416 ) -> None:
417 super().__init__()
418 self.gate_up_proj = MergedColumnParallelLinear(
419 hidden_size,
420 [intermediate_size] * 2,
421 bias=False,
422 quant_config=quant_config,
423 prefix=f"{prefix}.gate_up_proj",
424 )
425 self.down_proj = RowParallelLinear(
426 intermediate_size,
427 hidden_size,
428 bias=False,
429 quant_config=quant_config,
430 reduce_results=reduce_results,
431 prefix=f"{prefix}.down_proj",
432 )
433 if hidden_act != "silu":
434 raise ValueError(
435 f"Unsupported activation: {hidden_act}. Only silu is supported for now."
436 )
437 self.act_fn = SiluAndMul()
438 self.expert_gate = expert_gate
439
440 def forward(self, x):
441 gate_up, _ = self.gate_up_proj(x)
442 out = self.act_fn(gate_up)
443 out, _ = self.down_proj(out)
444
445 if self.expert_gate is not None:
446 out = F.sigmoid(self.expert_gate(x)[0]) * out
447
448 return out
449
450
451 class K2HorizonSparseMoeBlock(nn.Module):
452 def __init__(
453 self,
454 vllm_config: VllmConfig,
455 prefix: str = "",
456 ):
457 super().__init__()
458
459 config = vllm_config.model_config.hf_text_config
460 parallel_config = vllm_config.parallel_config
461 quant_config = vllm_config.quant_config
462
463 self.tp_size = get_tensor_model_parallel_world_size()
464
465 self.ep_group = get_ep_group().device_group
466 self.ep_rank = get_ep_group().rank_in_group
467 self.ep_size = self.ep_group.size()
468 self.n_routed_experts = config.num_experts
469
470 self.is_sequence_parallel = parallel_config.use_sequence_parallel_moe
471
472 if self.tp_size > config.num_experts:
473 raise ValueError(
474 f"Tensor parallel size {self.tp_size} is greater than "
475 f"the number of experts {config.num_experts}."
476 )
477
478 # Load balancing settings.
479 vllm_config = get_current_vllm_config()
480 eplb_config = vllm_config.parallel_config.eplb_config
481 self.enable_eplb = parallel_config.enable_eplb
482
483 self.n_logical_experts = self.n_routed_experts
484 self.n_redundant_experts = eplb_config.num_redundant_experts
485 self.n_physical_experts = self.n_logical_experts + self.n_redundant_experts
486 self.n_local_physical_experts = self.n_physical_experts // self.ep_size
487
488 self.physical_expert_start = self.ep_rank * self.n_local_physical_experts
489 self.physical_expert_end = (
490 self.physical_expert_start + self.n_local_physical_experts
491 )
492
493 self.gate = GateLinear(
494 config.hidden_size,
495 config.num_experts,
496 bias=config.moe_gate_bias,
497 skip_bias_add=True,
498 quant_config=quant_config,
499 prefix=f"{prefix}.gate",
500 )
501 if self.gate.bias is not None:
502 self.gate.bias = nn.Parameter(self.gate.bias.float(), requires_grad=False)
503
504 self.num_shared_experts = config.num_shared_experts
505 self.shared_experts: K2HorizonMLP | None
506 if config.num_shared_experts > 0:
507 self.shared_experts = K2HorizonMLP(
508 hidden_size=config.hidden_size,
509 intermediate_size=config.moe_intermediate_size
510 * config.num_shared_experts,
511 hidden_act=config.hidden_act,
512 quant_config=quant_config,
513 reduce_results=False,
514 expert_gate=None,
515 prefix=f"{prefix}.shared_experts",
516 )
517 else:
518 self.shared_experts = None
519
520 self.experts = FusedMoEFactory(
521 shared_experts=self.shared_experts,
522 gate=self.gate,
523 num_experts=self.n_routed_experts,
524 top_k=config.num_experts_per_tok,
525 hidden_size=config.hidden_size,
526 intermediate_size=config.moe_intermediate_size,
527 renormalize=config.norm_topk_prob,
528 quant_config=quant_config,
529 prefix=f"{prefix}.experts",
530 enable_eplb=self.enable_eplb,
531 num_redundant_experts=self.n_redundant_experts,
532 is_sequence_parallel=self.is_sequence_parallel,
533 scoring_func=config.router_score_func,
534 e_score_correction_bias=self.gate.bias,
535 routed_scaling_factor=config.router_scaling_factor,
536 )
537
538 def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
539 assert hidden_states.dim() <= 2, (
540 "K2HorizonSparseMoeBlock only supports 1D or 2D inputs"
541 )
542 is_input_1d = hidden_states.dim() == 1
543 num_tokens, hidden_dim = hidden_states.shape
544 hidden_states = hidden_states.view(-1, hidden_dim)
545
546 if self.is_sequence_parallel:
547 hidden_states = sequence_parallel_chunk(hidden_states)
548
549 final_hidden_states = self.experts(
550 hidden_states=hidden_states, router_logits=hidden_states
551 )
552
553 if self.is_sequence_parallel:
554 final_hidden_states = tensor_model_parallel_all_gather(
555 final_hidden_states, 0
556 )
557 final_hidden_states = final_hidden_states[:num_tokens]
558
559 # return to 1d if input is 1d
560 return final_hidden_states.squeeze(0) if is_input_1d else final_hidden_states
561
562
563 class K2HorizonAttention(nn.Module):
564 def __init__(
565 self,
566 hidden_size: int,
567 num_heads: int,
568 num_kv_heads: int,
569 rope_parameters: dict[str, Any],
570 rope_head_dim: int,
571 query_key_norm: bool,
572 max_position_embeddings: int = 8192,
573 head_dim: int | None = None,
574 rms_norm_eps: float = 1e-06,
575 qkv_bias: bool = False,
576 cache_config: CacheConfig | None = None,
577 quant_config: QuantizationConfig | None = None,
578 prefix: str = "",
579 dual_chunk_attention_config: dict[str, Any] | None = None,
580 gate_func: str | None = None,
581 ) -> None:
582 super().__init__()
583 self.hidden_size = hidden_size
584 tp_size = get_tensor_model_parallel_world_size()
585 self.total_num_heads = num_heads
586 assert self.total_num_heads % tp_size == 0
587 self.num_heads = self.total_num_heads // tp_size
588 self.total_num_kv_heads = num_kv_heads
589 if self.total_num_kv_heads >= tp_size:
590 # Number of KV heads is greater than TP size, so we partition
591 # the KV heads across multiple tensor parallel GPUs.
592 assert self.total_num_kv_heads % tp_size == 0
593 else:
594 # Number of KV heads is less than TP size, so we replicate
595 # the KV heads across multiple tensor parallel GPUs.
596 assert tp_size % self.total_num_kv_heads == 0
597 self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
598 self.head_dim = head_dim or (hidden_size // self.total_num_heads)
599 self.q_size = self.num_heads * self.head_dim
600 self.kv_size = self.num_kv_heads * self.head_dim
601 self.scaling = self.head_dim**-0.5
602 self.max_position_embeddings = max_position_embeddings
603 self.dual_chunk_attention_config = dual_chunk_attention_config
604
605 self.qkv_proj = QKVParallelLinear(
606 hidden_size,
607 self.head_dim,
608 self.total_num_heads,
609 self.total_num_kv_heads,
610 bias=qkv_bias,
611 quant_config=quant_config,
612 prefix=f"{prefix}.qkv_proj",
613 )
614
615 self.o_proj = RowParallelLinear(
616 self.total_num_heads * self.head_dim,
617 hidden_size,
618 bias=qkv_bias,
619 quant_config=quant_config,
620 prefix=f"{prefix}.o_proj",
621 )
622
623 self.rope_head_dim = rope_head_dim or self.head_dim
624 # Fold the partial-RoPE channel permutation into the q/k weights only
625 # when supported (dense projections + no dual-chunk attention);
626 # otherwise keep the runtime channel-permutation path
627 # (see ``is_rope_weights_folding_supported``).
628 self.fold_rope_weights = is_rope_weights_folding_supported(
629 self.qkv_proj, dual_chunk_attention_config
630 )
631 if self.fold_rope_weights:
632 rope_parameters = dict(rope_parameters)
633 rope_parameters["rope_dim"] = self.rope_head_dim
634 self.rotary_emb = get_rope(
635 self.head_dim,
636 max_position=max_position_embeddings,
637 is_neox_style=True,
638 rope_parameters=rope_parameters,
639 dual_chunk_attention_config=dual_chunk_attention_config,
640 )
641 else:
642 self.rotary_emb = get_rope(
643 self.rope_head_dim,
644 max_position=max_position_embeddings,
645 rope_parameters=rope_parameters,
646 dual_chunk_attention_config=dual_chunk_attention_config,
647 )
648 attn_kwargs: dict[str, Any] = (
649 {
650 "layer_idx": extract_layer_index(prefix),
651 "dual_chunk_attention_config": dual_chunk_attention_config,
652 }
653 if dual_chunk_attention_config
654 else {}
655 )
656 self.attn = Attention(
657 self.num_heads,
658 self.head_dim,
659 self.scaling,
660 num_kv_heads=self.num_kv_heads,
661 cache_config=cache_config,
662 quant_config=quant_config,
663 prefix=f"{prefix}.attn",
664 **attn_kwargs,
665 )
666
667 self.query_key_norm = query_key_norm
668 if self.query_key_norm:
669 self.q_norm = K2HorizonRMSNorm(
670 hidden_size=self.q_size, n_groups=self.num_heads, eps=rms_norm_eps
671 )
672 self.k_norm = K2HorizonRMSNorm(
673 hidden_size=self.kv_size, n_groups=self.num_kv_heads, eps=rms_norm_eps
674 )
675
676 self.gate_func = gate_func
677 if self.gate_func is not None:
678 self.gate_proj = ColumnParallelLinear(
679 hidden_size,
680 self.total_num_heads * self.head_dim,
681 bias=False,
682 quant_config=quant_config,
683 prefix=f"{prefix}.gate_proj",
684 )
685
686 self.tp_size = get_tensor_model_parallel_world_size()
687 self.tp_rank = get_tensor_model_parallel_rank()
688
689 def forward(
690 self,
691 positions: torch.Tensor,
692 hidden_states: torch.Tensor,
693 ) -> torch.Tensor:
694 qkv, _ = self.qkv_proj(hidden_states)
695 q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
696 if self.query_key_norm:
697 q = self.q_norm(q)
698 k = self.k_norm(k)
699
700 q, k = apply_partial_rope(
701 self.rotary_emb,
702 positions,
703 q,
704 k,
705 self.num_heads,
706 self.num_kv_heads,
707 self.head_dim,
708 self.rope_head_dim,
709 self.fold_rope_weights,
710 )
711
712 attn_output = self.attn(q, k, v)
713
714 if self.gate_func is not None:
715 gate, _ = self.gate_proj(hidden_states)
716 if self.gate_func == "silu":
717 gate = F.silu(gate)
718 else:
719 assert self.gate_func == "softplus"
720 gate = F.softplus(gate, beta=math.log(2))
721
722 attn_output = attn_output * gate
723
724 output, _ = self.o_proj(attn_output)
725 return output
726
727
728 class K2HorizonMoVAAttention(nn.Module):
729 def __init__(
730 self,
731 vllm_config: VllmConfig,
732 hidden_size: int,
733 num_heads: int,
734 num_kv_heads: int,
735 rope_parameters: dict[str, Any],
736 rope_head_dim: int,
737 query_key_norm: bool,
738 num_experts: int,
739 num_experts_per_tok: int,
740 moe_gate_bias: bool,
741 router_score_func: str,
742 router_scaling_factor: float,
743 max_position_embeddings: int = 8192,
744 head_dim: int | None = None,
745 rms_norm_eps: float = 1e-06,
746 qkv_bias: bool = False,
747 cache_config: CacheConfig | None = None,
748 quant_config: QuantizationConfig | None = None,
749 prefix: str = "",
750 dual_chunk_attention_config: dict[str, Any] | None = None,
751 gate_func: str | None = None,
752 ) -> None:
753 super().__init__()
754 self.hidden_size = hidden_size
755 tp_size = get_tensor_model_parallel_world_size()
756 self.total_num_heads = num_heads
757 assert self.total_num_heads % tp_size == 0
758 self.num_heads = self.total_num_heads // tp_size
759 self.total_num_kv_heads = num_kv_heads
760 if self.total_num_kv_heads >= tp_size:
761 # Number of KV heads is greater than TP size, so we partition
762 # the KV heads across multiple tensor parallel GPUs.
763 assert self.total_num_kv_heads % tp_size == 0
764 else:
765 # Number of KV heads is less than TP size, so we replicate
766 # the KV heads across multiple tensor parallel GPUs.
767 assert tp_size % self.total_num_kv_heads == 0
768
769 self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
770 self.head_dim = head_dim or (hidden_size // self.total_num_heads)
771 self.q_size = self.num_heads * self.head_dim
772 self.kv_size = self.num_kv_heads * self.head_dim
773 self.scaling = self.head_dim**-0.5
774 self.max_position_embeddings = max_position_embeddings
775 self.dual_chunk_attention_config = dual_chunk_attention_config
776
777 self.num_experts = num_experts
778 self.num_experts_per_tok = num_experts_per_tok
779 self.router_score_func = router_score_func
780 self.router_scaling_factor = router_scaling_factor
781
782 self.qk_proj = MergedColumnParallelLinear(
783 hidden_size,
784 [self.total_num_heads * self.head_dim, self.kv_size * tp_size],
785 bias=qkv_bias,
786 quant_config=quant_config,
787 prefix=f"{prefix}.qk_proj",
788 )
789
790 self.v_router = ReplicatedLinear(
791 hidden_size,
792 num_experts,
793 bias=moe_gate_bias,
794 skip_bias_add=True,
795 quant_config=quant_config,
796 prefix=f"{prefix}.v_router",
797 )
798 if self.v_router.bias is not None:
799 self.v_router.bias = nn.Parameter(
800 self.v_router.bias.float(), requires_grad=False
801 )
802
803 self.v_experts = nn.ModuleList(
804 [
805 ColumnParallelLinear(
806 hidden_size,
807 self.kv_size * tp_size,
808 bias=False,
809 quant_config=quant_config,
810 prefix=f"{prefix}.v_experts.{value}",
811 )
812 for value in range(num_experts)
813 ]
814 )
815
816 self.o_proj = RowParallelLinear(
817 self.total_num_heads * self.head_dim,
818 hidden_size,
819 bias=qkv_bias,
820 quant_config=quant_config,
821 prefix=f"{prefix}.o_proj",
822 )
823
824 self.rope_head_dim = rope_head_dim or self.head_dim
825 # Fold the partial-RoPE channel permutation into the q/k weights only
826 # when supported (dense projections + no dual-chunk attention);
827 # otherwise keep the runtime channel-permutation path
828 # (see ``is_rope_weights_folding_supported``).
829 self.fold_rope_weights = is_rope_weights_folding_supported(
830 self.qk_proj, dual_chunk_attention_config
831 )
832 if self.fold_rope_weights:
833 rope_parameters = dict(rope_parameters)
834 rope_parameters["rope_dim"] = self.rope_head_dim
835 self.rotary_emb = get_rope(
836 self.head_dim,
837 max_position=max_position_embeddings,
838 is_neox_style=True,
839 rope_parameters=rope_parameters,
840 dual_chunk_attention_config=dual_chunk_attention_config,
841 )
842 else:
843 self.rotary_emb = get_rope(
844 self.rope_head_dim,
845 max_position=max_position_embeddings,
846 rope_parameters=rope_parameters,
847 dual_chunk_attention_config=dual_chunk_attention_config,
848 )
849 attn_kwargs: dict[str, Any] = (
850 {
851 "layer_idx": extract_layer_index(prefix),
852 "dual_chunk_attention_config": dual_chunk_attention_config,
853 }
854 if dual_chunk_attention_config
855 else {}
856 )
857 self.attn = Attention(
858 self.num_heads,
859 self.head_dim,
860 self.scaling,
861 num_kv_heads=self.num_kv_heads,
862 cache_config=cache_config,
863 quant_config=quant_config,
864 prefix=f"{prefix}.attn",
865 **attn_kwargs,
866 )
867
868 self.query_key_norm = query_key_norm
869 if self.query_key_norm:
870 self.q_norm = K2HorizonRMSNorm(
871 hidden_size=self.q_size, n_groups=self.num_heads, eps=rms_norm_eps
872 )
873 self.k_norm = K2HorizonRMSNorm(
874 hidden_size=self.kv_size, n_groups=self.num_kv_heads, eps=rms_norm_eps
875 )
876
877 self.gate_func = gate_func
878 if self.gate_func is not None:
879 self.gate_proj = ColumnParallelLinear(
880 hidden_size,
881 self.total_num_heads * self.head_dim,
882 bias=False,
883 quant_config=quant_config,
884 prefix=f"{prefix}.gate_proj",
885 )
886
887 self.tp_size = get_tensor_model_parallel_world_size()
888 self.tp_rank = get_tensor_model_parallel_rank()
889
890 w1_shape = torch.Size(
891 [
892 self.num_experts,
893 self.total_num_kv_heads * self.head_dim,
894 hidden_size,
895 ]
896 )
897 config_dtype = _get_config_dtype_str(
898 use_fp8_w8a8=False,
899 use_int8_w8a16=False,
900 use_int4_w4a16=False,
901 dtype=vllm_config.model_config.dtype,
902 )
903 self.fused_mova_config = try_get_optimal_moe_config(
904 w1_shape=w1_shape,
905 w2_shape=torch.Size([w1_shape[0], w1_shape[2], w1_shape[1]]),
906 top_k=self.num_experts_per_tok,
907 dtype=config_dtype,
908 M=vllm_config.scheduler_config.max_num_batched_tokens,
909 block_shape=None,
910 )
911
912 def compute_mova_v_sparse(self, hidden_states):
913 router_logits, _ = self.v_router(hidden_states)
914
915 routing_weights, selected_values = calc_router_weights(
916 router_logits=router_logits,
917 e_score_correction_bias=self.v_router.bias,
918 score_func=self.router_score_func,
919 top_k=self.num_experts_per_tok,
920 scaling_factor=self.router_scaling_factor,
921 )
922
923 w1 = torch.stack(
924 [expert.weight for expert in self.v_experts], dim=0
925 ).contiguous()
926
927 v = fused_mova_impl(
928 config=self.fused_mova_config,
929 hidden_states=hidden_states.contiguous(),
930 w1=w1,
931 topk_weights=routing_weights.contiguous(),
932 topk_ids=selected_values.contiguous(),
933 global_num_experts=self.num_experts,
934 expert_map=None,
935 )
936
937 return v
938
939 def forward(
940 self,
941 positions: torch.Tensor,
942 hidden_states: torch.Tensor,
943 ) -> torch.Tensor:
944 qk, _ = self.qk_proj(hidden_states)
945 q, k = qk.split([self.q_size, self.kv_size], dim=-1)
946 v = self.compute_mova_v_sparse(hidden_states)
947
948 if self.query_key_norm:
949 q = self.q_norm(q)
950 k = self.k_norm(k)
951
952 q, k = apply_partial_rope(
953 self.rotary_emb,
954 positions,
955 q,
956 k,
957 self.num_heads,
958 self.num_kv_heads,
959 self.head_dim,
960 self.rope_head_dim,
961 self.fold_rope_weights,
962 )
963
964 attn_output = self.attn(q, k, v)
965
966 if self.gate_func is not None:
967 gate, _ = self.gate_proj(hidden_states)
968 if self.gate_func == "silu":
969 gate = F.silu(gate)
970 else:
971 assert self.gate_func == "softplus"
972 gate = F.softplus(gate, beta=math.log(2))
973
974 attn_output = attn_output * gate
975
976 output, _ = self.o_proj(attn_output)
977 return output
978
979
980 class K2HorizonDecoderLayer(nn.Module):
981 def __init__(self, vllm_config: VllmConfig, prefix: str = "") -> None:
982 super().__init__()
983
984 config = vllm_config.model_config.hf_text_config
985 cache_config = vllm_config.cache_config
986 quant_config = vllm_config.quant_config
987
988 self.hidden_size = config.hidden_size
989 max_position_embeddings = getattr(config, "max_position_embeddings", 8192)
990 dual_chunk_attention_config = getattr(
991 config, "dual_chunk_attention_config", None
992 )
993
994 # `mlp_only_layers` in the config.
995 layer_idx = extract_layer_index(prefix)
996 mlp_only_layers = (
997 [] if not hasattr(config, "mlp_only_layers") else config.mlp_only_layers
998 )
999 is_sparse_layer = (layer_idx not in mlp_only_layers) and (
1000 config.num_experts > 0 and (layer_idx + 1) % config.decoder_sparse_step == 0
1001 )
1002
1003 if is_sparse_layer and config.mova_num_experts > 0:
1004 self.self_attn = K2HorizonMoVAAttention(
1005 vllm_config=vllm_config,
1006 hidden_size=self.hidden_size,
1007 num_heads=config.num_attention_heads,
1008 num_kv_heads=config.num_key_value_heads,
1009 rope_parameters=config.rope_parameters,
1010 rope_head_dim=config.rope_head_dim,
1011 query_key_norm=config.query_key_norm,
1012 max_position_embeddings=max_position_embeddings,
1013 rms_norm_eps=config.rms_norm_eps,
1014 qkv_bias=config.attention_bias,
1015 head_dim=config.head_dim,
1016 gate_func=config.attention_gate_func,
1017 cache_config=cache_config,
1018 quant_config=quant_config,
1019 prefix=f"{prefix}.self_attn",
1020 dual_chunk_attention_config=dual_chunk_attention_config,
1021 num_experts=config.mova_num_experts,
1022 num_experts_per_tok=config.mova_num_experts_per_tok,
1023 moe_gate_bias=config.moe_gate_bias,
1024 router_score_func=config.router_score_func,
1025 router_scaling_factor=config.router_scaling_factor,
1026 )
1027 else:
1028 self.self_attn = K2HorizonAttention(
1029 hidden_size=self.hidden_size,
1030 num_heads=config.num_attention_heads,
1031 num_kv_heads=config.num_key_value_heads,
1032 rope_parameters=config.rope_parameters,
1033 rope_head_dim=config.rope_head_dim,
1034 query_key_norm=config.query_key_norm,
1035 max_position_embeddings=max_position_embeddings,
1036 rms_norm_eps=config.rms_norm_eps,
1037 qkv_bias=config.attention_bias,
1038 head_dim=config.head_dim,
1039 gate_func=config.attention_gate_func,
1040 cache_config=cache_config,
1041 quant_config=quant_config,
1042 prefix=f"{prefix}.self_attn",
1043 dual_chunk_attention_config=dual_chunk_attention_config,
1044 )
1045
1046 if is_sparse_layer:
1047 self.mlp = K2HorizonSparseMoeBlock(
1048 vllm_config=vllm_config, prefix=f"{prefix}.mlp"
1049 )
1050 else:
1051 self.mlp = K2HorizonMLP(
1052 hidden_size=config.hidden_size,
1053 intermediate_size=config.intermediate_size,
1054 hidden_act=config.hidden_act,
1055 quant_config=quant_config,
1056 prefix=f"{prefix}.mlp",
1057 )
1058
1059 self.input_layernorm = K2HorizonRMSNorm(
1060 hidden_size=config.hidden_size,
1061 n_groups=config.layernorm_num_groups,
1062 eps=config.rms_norm_eps,
1063 )
1064
1065 self.post_attention_layernorm = K2HorizonRMSNorm(
1066 hidden_size=config.hidden_size,
1067 n_groups=config.layernorm_num_groups,
1068 eps=config.rms_norm_eps,
1069 )
1070
1071 def forward(
1072 self,
1073 positions: torch.Tensor,
1074 hidden_states: torch.Tensor,
1075 residual: torch.Tensor | None,
1076 ) -> tuple[torch.Tensor, torch.Tensor]:
1077 # Self Attention
1078 if residual is None:
1079 residual = hidden_states
1080 hidden_states = self.input_layernorm(hidden_states)
1081 else:
1082 hidden_states, residual = self.input_layernorm(hidden_states, residual)
1083
1084 hidden_states = self.self_attn(positions=positions, hidden_states=hidden_states)
1085
1086 # Fully Connected
1087 hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
1088 hidden_states = self.mlp(hidden_states)
1089 return hidden_states, residual
1090
1091
1092 @support_torch_compile
1093 class K2HorizonModel(nn.Module, EagleModelMixin):
1094 def __init__(
1095 self,
1096 *,
1097 vllm_config: VllmConfig,
1098 prefix: str = "",
1099 decoder_layer_type: type[torch.nn.Module] = K2HorizonDecoderLayer,
1100 ):
1101 super().__init__()
1102
1103 config = vllm_config.model_config.hf_text_config
1104 quant_config = vllm_config.quant_config
1105 parallel_config = vllm_config.parallel_config
1106 eplb_config = parallel_config.eplb_config
1107 self.num_redundant_experts = eplb_config.num_redundant_experts
1108
1109 self.vocab_size = config.vocab_size
1110 self.config = config
1111 self.quant_config = quant_config
1112 self.embed_tokens = VocabParallelEmbedding(
1113 config.vocab_size,
1114 config.hidden_size,
1115 quant_config=quant_config,
1116 prefix=f"{prefix}.embed_tokens",
1117 )
1118 self.start_layer, self.end_layer, self.layers = make_layers(
1119 config.num_hidden_layers,
1120 lambda prefix: decoder_layer_type(vllm_config=vllm_config, prefix=prefix),
1121 prefix=f"{prefix}.layers",
1122 )
1123
1124 self.norm = K2HorizonRMSNorm(
1125 hidden_size=config.hidden_size,
1126 n_groups=config.layernorm_num_groups,
1127 eps=config.rms_norm_eps,
1128 )
1129
1130 self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
1131 ["hidden_states", "residual"], config.hidden_size
1132 )
1133
1134 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
1135 return self.embed_tokens(input_ids)
1136
1137 def forward(
1138 self,
1139 input_ids: torch.Tensor | None,
1140 positions: torch.Tensor,
1141 intermediate_tensors: IntermediateTensors | None = None,
1142 inputs_embeds: torch.Tensor | None = None,
1143 ) -> torch.Tensor | IntermediateTensors | tuple[torch.Tensor, list[torch.Tensor]]:
1144 if get_pp_group().is_first_rank:
1145 if inputs_embeds is not None:
1146 hidden_states = inputs_embeds
1147 else:
1148 hidden_states = self.embed_input_ids(input_ids)
1149 residual = None
1150 else:
1151 assert intermediate_tensors is not None
1152 hidden_states = intermediate_tensors["hidden_states"]
1153 residual = intermediate_tensors["residual"]
1154
1155 aux_hidden_states = self._maybe_add_hidden_state(
1156 [], self.start_layer, hidden_states, residual
1157 )
1158 for layer_idx, layer in enumerate(
1159 islice(self.layers, self.start_layer, self.end_layer),
1160 start=self.start_layer,
1161 ):
1162 hidden_states, residual = layer(positions, hidden_states, residual)
1163 self._maybe_add_hidden_state(
1164 aux_hidden_states, layer_idx + 1, hidden_states, residual
1165 )
1166
1167 if not get_pp_group().is_last_rank:
1168 return IntermediateTensors(
1169 {"hidden_states": hidden_states, "residual": residual}
1170 )
1171 hidden_states, _ = self.norm(hidden_states, residual)
1172
1173 # Return auxiliary hidden states if collected
1174 if len(aux_hidden_states) > 0:
1175 return hidden_states, aux_hidden_states
1176 return hidden_states
1177
1178 def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
1179 # Params for weights, fp8 weight scales, fp8 activation scales
1180 # (param_name, weight_name, expert_id, shard_id)
1181 return fused_moe_make_expert_params_mapping(
1182 self,
1183 ckpt_gate_proj_name="gate_proj",
1184 ckpt_down_proj_name="down_proj",
1185 ckpt_up_proj_name="up_proj",
1186 num_experts=self.config.num_experts,
1187 num_redundant_experts=self.num_redundant_experts,
1188 )
1189
1190 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1191 stacked_params_mapping = [
1192 # (param_name, shard_name, shard_id)
1193 ("qkv_proj", "q_proj", "q"),
1194 ("qkv_proj", "k_proj", "k"),
1195 ("qkv_proj", "v_proj", "v"),
1196 ("gate_up_proj", "gate_proj", 0),
1197 ("gate_up_proj", "up_proj", 1),
1198 ]
1199
1200 # Per-head gather index used to fold K2Horizon's partial-RoPE channel
1201 # reordering into the q_proj/k_proj rows and the q_norm/k_norm scales,
1202 # so the runtime can use vLLM's native partial NeoX RoPE.
1203 head_dim = self.config.head_dim or (
1204 self.config.hidden_size // self.config.num_attention_heads
1205 )
1206 rope_head_dim = self.config.rope_head_dim or head_dim
1207 rope_perm_idx = _rope_weight_perm(head_dim, rope_head_dim)
1208
1209 # Attention modules whose q/k projections are dense: only these fold the
1210 # RoPE channel permutation into their q/k weights, biases and q/k norm
1211 # scales at load time. Attentions with packed/quantized q/k projections
1212 # keep the runtime channel-permutation path, so their weights must be
1213 # loaded verbatim (folding would misalign per-channel scales).
1214 rope_fold_prefixes = tuple(
1215 f"{module_name}."
1216 for module_name, module in self.named_modules()
1217 if isinstance(module, (K2HorizonAttention, K2HorizonMoVAAttention))
1218 and getattr(module, "fold_rope_weights", False)
1219 )
1220
1221 # Skip loading extra parameters for GPTQ/modelopt models.
1222 # ignore_suffixes = (
1223 # ".bias",
1224 # "_bias",
1225 # ".weight_scale",
1226 # "_weight_scale",
1227 # ".input_scale",
1228 # "_input_scale",
1229 # )
1230 ignore_suffixes = ()
1231
1232 params_dict = dict(self.named_parameters())
1233
1234 loaded_params: set[str] = set()
1235 expert_params_mapping = self.get_expert_mapping()
1236 for name, loaded_weight in weights:
1237 if "scale" in name or "zero_point" in name:
1238 remapped_name = maybe_remap_kv_scale_name(name, params_dict)
1239 if remapped_name is None:
1240 continue
1241 name = remapped_name
1242
1243 # QK norm weights
1244 if name.endswith(".self_attn.q_norm.weight") or name.endswith(
1245 ".self_attn.k_norm.weight"
1246 ):
1247 if is_pp_missing_parameter(name, self):
1248 continue
1249 if name not in params_dict:
1250 continue
1251
1252 # Fold the per-head RoPE channel permutation into the norm scales.
1253 if self.config.query_key_norm and name.startswith(rope_fold_prefixes):
1254 loaded_weight = (
1255 loaded_weight.view(-1, head_dim)[:, rope_perm_idx]
1256 .reshape(-1)
1257 .contiguous()
1258 )
1259
1260 param = params_dict[name]
1261 tp_rank = get_tensor_model_parallel_rank()
1262 tp_size = get_tensor_model_parallel_world_size()
1263
1264 if loaded_weight.shape != param.shape:
1265 if name.endswith(".self_attn.q_norm.weight"):
1266 loaded_weight = loaded_weight.chunk(tp_size, dim=0)[tp_rank]
1267 else:
1268 num_kv_heads = self.config.num_key_value_heads
1269 if num_kv_heads >= tp_size:
1270 loaded_weight = loaded_weight.chunk(tp_size, dim=0)[tp_rank]
1271 else:
1272 num_kv_head_replicas = tp_size // num_kv_heads
1273 kv_rank = tp_rank // num_kv_head_replicas
1274 loaded_weight = loaded_weight.chunk(num_kv_heads, dim=0)[
1275 kv_rank
1276 ]
1277
1278 weight_loader: Callable[..., Any] = getattr(
1279 param, "weight_loader", default_weight_loader
1280 )
1281 weight_loader(param, loaded_weight)
1282 loaded_params.add(name)
1283 continue
1284
1285 # MoVA v experts
1286 if ".self_attn.v_experts." in name:
1287 if is_pp_missing_parameter(name, self):
1288 continue
1289 if name not in params_dict:
1290 continue
1291
1292 param = params_dict[name]
1293 tp_rank = get_tensor_model_parallel_rank()
1294 tp_size = get_tensor_model_parallel_world_size()
1295 num_kv_heads = self.config.num_key_value_heads
1296
1297 if loaded_weight.shape != param.shape:
1298 if num_kv_heads >= tp_size:
1299 loaded_weight = loaded_weight.chunk(tp_size, dim=0)[tp_rank]
1300 else:
1301 num_kv_head_replicas = tp_size // num_kv_heads
1302 kv_rank = tp_rank // num_kv_head_replicas
1303 loaded_weight = loaded_weight.chunk(num_kv_heads, dim=0)[
1304 kv_rank
1305 ]
1306
1307 default_weight_loader(param, loaded_weight)
1308 loaded_params.add(name)
1309 continue
1310
1311 for param_name, weight_name, shard_id in stacked_params_mapping:
1312 # Skip non-stacked layers and experts (experts handled below).
1313 if weight_name not in name:
1314 continue
1315 # We have mlp.experts[0].gate_proj in the checkpoint.
1316 # Since we handle the experts below in expert_params_mapping,
1317 # we need to skip here BEFORE we update the name, otherwise
1318 # name will be updated to mlp.experts[0].gate_up_proj, which
1319 # will then be updated below in expert_params_mapping
1320 # for mlp.experts[0].gate_gate_up_proj, which breaks load.
1321 if "mlp.experts" in name:
1322 continue
1323 if ".self_attn." in name and param_name == "gate_up_proj":
1324 assert weight_name == "gate_proj"
1325 continue
1326
1327 # Fold the per-head RoPE channel permutation into the dense
1328 # q_proj / k_proj rows (W' = P W), and biases, before TP
1329 # sharding.
1330 if shard_id in ("q", "k") and name.startswith(rope_fold_prefixes):
1331 if name.endswith(".weight"):
1332 hidden = loaded_weight.shape[-1]
1333 loaded_weight = (
1334 loaded_weight.view(-1, head_dim, hidden)[
1335 :, rope_perm_idx, :
1336 ]
1337 .reshape(-1, hidden)
1338 .contiguous()
1339 )
1340 elif self.config.attention_bias and name.endswith(".bias"):
1341 loaded_weight = (
1342 loaded_weight.view(-1, head_dim)[:, rope_perm_idx]
1343 .reshape(-1)
1344 .contiguous()
1345 )
1346
1347 if (
1348 ".self_attn." in name
1349 and param_name == "qkv_proj"
1350 and name.replace(weight_name, param_name) not in params_dict
1351 ):
1352 qk_name = name.replace(weight_name, "qk_proj")
1353 if is_pp_missing_parameter(qk_name, self):
1354 continue
1355 if qk_name not in params_dict:
1356 continue
1357
1358 assert weight_name in ["q_proj", "k_proj"]
1359 if weight_name == "k_proj":
1360 tp_size = get_tensor_model_parallel_world_size()
1361 num_kv_heads = self.config.num_key_value_heads
1362 if num_kv_heads < tp_size:
1363 num_kv_head_replicas = tp_size // num_kv_heads
1364 loaded_weight = (
1365 loaded_weight.reshape(
1366 num_kv_heads, -1, *loaded_weight.shape[1:]
1367 )
1368 .repeat_interleave(num_kv_head_replicas, dim=0)
1369 .reshape(-1, *loaded_weight.shape[1:])
1370 )
1371
1372 param = params_dict[qk_name]
1373 weight_loader = getattr(
1374 param, "weight_loader", default_weight_loader
1375 )
1376 weight_loader(
1377 param, loaded_weight, 0 if weight_name == "q_proj" else 1
1378 )
1379 loaded_params.add(qk_name)
1380 break
1381
1382 name = name.replace(weight_name, param_name)
1383
1384 # Skip loading extra parameters for GPTQ/modelopt models.
1385 if name.endswith(ignore_suffixes) and name not in params_dict:
1386 continue
1387
1388 # Skip layers on other devices.
1389 if is_pp_missing_parameter(name, self):
1390 continue
1391 if name.endswith("scale"):
1392 # Remapping the name of FP8 kv-scale.
1393 remapped_name = maybe_remap_kv_scale_name(name, params_dict)
1394 if remapped_name is None:
1395 continue
1396 name = remapped_name
1397 if name not in params_dict:
1398 continue
1399
1400 param = params_dict[name]
1401 weight_loader = getattr(param, "weight_loader", default_weight_loader)
1402 if weight_loader == default_weight_loader:
1403 weight_loader(param, loaded_weight)
1404 else:
1405 weight_loader(param, loaded_weight, shard_id)
1406 break
1407 else:
1408 is_expert_weight = False
1409 for mapping in expert_params_mapping:
1410 param_name, weight_name, expert_id, shard_id = mapping
1411 if weight_name not in name:
1412 continue
1413
1414 # Anyway, this is an expert weight and should not be
1415 # attempted to load as other weights later
1416 is_expert_weight = True
1417
1418 # Do not modify `name` since the loop may continue here
1419 # Instead, create a new variable
1420 name_mapped = name.replace(weight_name, param_name)
1421
1422 if is_pp_missing_parameter(name_mapped, self):
1423 continue
1424
1425 # Skip loading extra parameters for GPTQ/modelopt models.
1426 if (
1427 name_mapped.endswith(ignore_suffixes)
1428 and name_mapped not in params_dict
1429 ):
1430 continue
1431
1432 param = params_dict[name_mapped]
1433 # We should ask the weight loader to return success or not
1434 # here since otherwise we may skip experts with other
1435 # available replicas.
1436 weight_loader = typing.cast(
1437 Callable[..., bool], param.weight_loader
1438 )
1439 success = weight_loader(
1440 param,
1441 loaded_weight,
1442 name_mapped,
1443 shard_id=shard_id,
1444 expert_id=expert_id,
1445 return_success=True,
1446 )
1447 if success:
1448 name = name_mapped
1449 break
1450 else:
1451 if is_expert_weight:
1452 # We've checked that this is an expert weight
1453 # However it's not mapped locally to this rank
1454 # So we simply skip it
1455 continue
1456
1457 # Skip loading extra parameters for GPTQ/modelopt models.
1458 if name.endswith(ignore_suffixes) and name not in params_dict:
1459 continue
1460 # Skip layers on other devices.
1461 if is_pp_missing_parameter(name, self):
1462 continue
1463 if name not in params_dict:
1464 continue
1465 param = params_dict[name]
1466 weight_loader = getattr(
1467 param, "weight_loader", default_weight_loader
1468 )
1469 weight_loader(param, loaded_weight)
1470 loaded_params.add(name)
1471
1472 return loaded_params
1473
1474
1475 class K2HorizonForCausalLM(
1476 nn.Module, SupportsPP, SupportsLoRA, SupportsEagle, SupportsEagle3, MixtureOfExperts
1477 ):
1478 packed_modules_mapping = {
1479 "qkv_proj": [
1480 "q_proj",
1481 "k_proj",
1482 "v_proj",
1483 ],
1484 "qk_proj": [
1485 "q_proj",
1486 "k_proj",
1487 ],
1488 }
1489
1490 embedding_modules = {
1491 "embed_tokens": "input_embeddings",
1492 "lm_head": "output_embeddings",
1493 }
1494
1495 fall_back_to_pt_during_load = False
1496
1497 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1498 super().__init__()
1499 config = vllm_config.model_config.hf_text_config
1500 quant_config = vllm_config.quant_config
1501 self.config = config
1502 self.quant_config = quant_config
1503 # Only perform the following mapping when K2HorizonMLP exists
1504 if getattr(config, "mlp_only_layers", []):
1505 self.packed_modules_mapping["gate_up_proj"] = ["gate_proj", "up_proj"]
1506 self.model = K2HorizonModel(
1507 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
1508 )
1509 self.lm_head = ParallelLMHead(
1510 config.vocab_size,
1511 config.hidden_size,
1512 quant_config=quant_config,
1513 prefix=maybe_prefix(prefix, "lm_head"),
1514 )
1515 if self.config.tie_word_embeddings:
1516 self.lm_head.weight = self.model.embed_tokens.weight
1517 self.logits_processor = LogitsProcessor(config.vocab_size)
1518 self.make_empty_intermediate_tensors = (
1519 self.model.make_empty_intermediate_tensors
1520 )
1521
1522 # Set MoE hyperparameters
1523 self.expert_weights = []
1524
1525 self.moe_layers = []
1526 example_layer = None
1527 for layer in self.model.layers:
1528 if isinstance(layer, PPMissingLayer):
1529 continue
1530
1531 assert isinstance(layer, K2HorizonDecoderLayer)
1532 if isinstance(layer.mlp, K2HorizonSparseMoeBlock):
1533 example_layer = layer.mlp
1534 self.moe_layers.append(layer.mlp.experts)
1535
1536 self.num_moe_layers = len(self.moe_layers)
1537 if example_layer is not None:
1538 self.num_logical_experts = example_layer.n_logical_experts
1539 self.num_physical_experts = example_layer.n_physical_experts
1540 self.num_local_physical_experts = example_layer.n_local_physical_experts
1541 self.num_routed_experts = example_layer.n_routed_experts
1542 self.num_redundant_experts = example_layer.n_redundant_experts
1543
1544 def update_physical_experts_metadata(
1545 self,
1546 num_physical_experts: int,
1547 num_local_physical_experts: int,
1548 ) -> None:
1549 assert self.num_local_physical_experts == num_local_physical_experts
1550 self.num_physical_experts = num_physical_experts
1551 self.num_local_physical_experts = num_local_physical_experts
1552 self.num_redundant_experts = num_physical_experts - self.num_logical_experts
1553 for layer in self.model.layers:
1554 if isinstance(layer.mlp, K2HorizonSparseMoeBlock):
1555 moe = layer.mlp
1556 moe.n_local_physical_experts = num_local_physical_experts
1557 moe.n_physical_experts = num_physical_experts
1558 moe.n_redundant_experts = self.num_redundant_experts
1559 moe.experts.update_expert_map()
1560
1561 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
1562 return self.model.embed_input_ids(input_ids)
1563
1564 def forward(
1565 self,
1566 input_ids: torch.Tensor | None,
1567 positions: torch.Tensor,
1568 intermediate_tensors: IntermediateTensors | None = None,
1569 inputs_embeds: torch.Tensor | None = None,
1570 ) -> torch.Tensor | IntermediateTensors:
1571 hidden_states = self.model(
1572 input_ids, positions, intermediate_tensors, inputs_embeds
1573 )
1574 return hidden_states
1575
1576 def compute_logits(
1577 self,
1578 hidden_states: torch.Tensor,
1579 ) -> torch.Tensor | None:
1580 logits = self.logits_processor(self.lm_head, hidden_states)
1581 return logits
1582
1583 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1584 loader = AutoWeightsLoader(self)
1585 return loader.load_weights(weights)
1586
1587 def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
1588 return self.model.get_expert_mapping()