@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 845adb3ec6
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, 05:30 UTC

[Model] Add LongCat-Flash (#23991)

Signed-off-by: yangxurui <yangxurui@meituan.com> Co-authored-by: yangxurui <yangxurui@meituan.com>

XuruiYang committed Sep 25, 2025 at 12:53 UTC 845adb3ec6d7123a4f4251c121d406524aa76548
31 files changed +1357 -66
csrc/moe/moe_align_sum_kernels.cu
+8 -2
@@ -44,6 +44,9 @@ __global__ void moe_align_block_size_kernel(
44
45 for (size_t i = tid; i < numel; i += stride) {
46 int expert_id = topk_ids[i];
47 + if (expert_id >= num_experts) {
48 + continue;
49 + }
50 int warp_idx = expert_id / experts_per_warp;
51 int expert_offset = expert_id % experts_per_warp;
52 atomicAdd(&shared_counts[warp_idx * experts_per_warp + expert_offset], 1);
@@ -95,12 +98,15 @@ template <typename scalar_t>
98 __global__ void count_and_sort_expert_tokens_kernel(
99 const scalar_t* __restrict__ topk_ids,
100 int32_t* __restrict__ sorted_token_ids, int32_t* __restrict__ cumsum_buffer,
98 - size_t numel) {
101 + size_t numel, int32_t num_experts) {
102 const size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
103 const size_t stride = blockDim.x * gridDim.x;
104
105 for (size_t i = tid; i < numel; i += stride) {
106 int32_t expert_id = topk_ids[i];
107 + if (expert_id >= num_experts) {
108 + continue;
109 + }
110 int32_t rank_post_pad = atomicAdd(&cumsum_buffer[expert_id], 1);
111 sorted_token_ids[rank_post_pad] = i;
112 }
@@ -269,7 +275,7 @@ void moe_align_block_size(torch::Tensor topk_ids, int64_t num_experts,
275 sort_kernel<<<actual_blocks, block_threads, 0, stream>>>(
276 topk_ids.data_ptr<scalar_t>(),
277 sorted_token_ids.data_ptr<int32_t>(),
272 - cumsum_buffer.data_ptr<int32_t>(), topk_ids.numel());
278 + cumsum_buffer.data_ptr<int32_t>(), topk_ids.numel(), num_experts);
279 }
280 });
281 }
docs/models/supported_models.md
+1
@@ -428,6 +428,7 @@ th {
428 | `MiniMaxM1ForCausalLM` | MiniMax-Text | `MiniMaxAI/MiniMax-M1-40k`, `MiniMaxAI/MiniMax-M1-80k`, etc. | | | ✅︎ |
429 | `MiniMaxText01ForCausalLM` | MiniMax-Text | `MiniMaxAI/MiniMax-Text-01`, etc. | | | ✅︎ |
430 | `Zamba2ForCausalLM` | Zamba2 | `Zyphra/Zamba2-7B-instruct`, `Zyphra/Zamba2-2.7B-instruct`, `Zyphra/Zamba2-1.2B-instruct`, etc. | | | ✅︎ |
431 +| `LongcatFlashForCausalLM` | LongCat-Flash | `meituan-longcat/LongCat-Flash-Chat`, `meituan-longcat/LongCat-Flash-Chat-FP8` | ✅︎ |✅︎ | ✅︎ |
432
433 Some models are supported only via the [Transformers backend](#transformers). The purpose of the table below is to acknowledge models which we officially support in this way. The logs will say that the Transformers backend is being used, and you will see no warning that this is fallback behaviour. This means that, if you have issues with any of the models listed below, please [make an issue](https://github.com/vllm-project/vllm/issues/new/choose) and we'll do our best to fix it!
434
tests/kernels/moe/test_flashinfer.py
+2 -2
@@ -138,7 +138,7 @@ def test_flashinfer_per_tensor_moe_fp8_no_graph(
138 td = TestData.make_moe_tensors_8bit(m, k, n, e, reorder=True)
139
140 score = torch.randn((m, e), device="cuda", dtype=torch.bfloat16)
141 - topk_weights, topk_ids = FusedMoE.select_experts(
141 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
142 hidden_states=td.hidden_states,
143 router_logits=score,
144 use_grouped_topk=False,
@@ -206,7 +206,7 @@ def test_flashinfer_cutlass_moe_fp8_no_graph(
206 td = TestData.make_moe_tensors_8bit(m, k, n, e, reorder=False)
207
208 score = torch.randn((m, e), device="cuda", dtype=torch.bfloat16)
209 - topk_weights, topk_ids = FusedMoE.select_experts(
209 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
210 hidden_states=td.hidden_states,
211 router_logits=score,
212 use_grouped_topk=False,
tests/models/registry.py
+6
@@ -273,6 +273,8 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
273 is_available_online=False),
274 "Llama4ForCausalLM": _HfExamplesInfo("meta-llama/Llama-4-Scout-17B-16E-Instruct", # noqa: E501
275 is_available_online=False),
276 + "LongcatFlashForCausalLM": _HfExamplesInfo
277 + ("meituan-longcat/LongCat-Flash-Chat", trust_remote_code=True),
278 "MambaForCausalLM": _HfExamplesInfo("state-spaces/mamba-130m-hf"),
279 "Mamba2ForCausalLM": _HfExamplesInfo("mistralai/Mamba-Codestral-7B-v0.1",
280 min_transformers_version="4.55.3",
@@ -639,6 +641,10 @@ _SPECULATIVE_DECODING_EXAMPLE_MODELS = {
641 speculative_model="zai-org/GLM-4.5",
642 min_transformers_version="4.54",
643 is_available_online=False),
644 + "LongCatFlashMTPModel": _HfExamplesInfo(
645 + "meituan-longcat/LongCat-Flash-Chat",
646 + trust_remote_code=True,
647 + speculative_model="meituan-longcat/LongCat-Flash-Chat"),
648 "MiMoMTPModel": _HfExamplesInfo("XiaomiMiMo/MiMo-7B-RL",
649 trust_remote_code=True,
650 speculative_model="XiaomiMiMo/MiMo-7B-RL"),
tests/models/utils.py
+9 -3
@@ -428,9 +428,8 @@ def dummy_hf_overrides(
428 num_hidden_layers = (3 if model_arch
429 == "Gemma3nForConditionalGeneration" else 1)
430
431 - text_config.update({
431 + update_dict = {
432 "num_layers": num_layers,
433 - "num_hidden_layers": num_hidden_layers,
433 "num_experts": num_experts,
434 "num_experts_per_tok": 2,
435 "num_local_experts": num_experts,
@@ -440,7 +439,14 @@ def dummy_hf_overrides(
439 "n_routed_experts": num_experts,
440 # For Gemma-3n
441 "num_kv_shared_layers": 1,
443 - })
442 + }
443 +
444 + # Update num_hidden_layers for non-Longcat architectures
445 + if model_arch != "LongcatFlashForCausalLM" \
446 + and model_arch != "LongCatFlashMTPModel":
447 + update_dict["num_hidden_layers"] = num_hidden_layers
448 +
449 + text_config.update(update_dict)
450
451 if hasattr(hf_config, "vision_config"):
452 hf_config.vision_config.update({
tests/test_routing_simulator.py
+1 -1
@@ -96,7 +96,7 @@ def test_routing_strategy_integration(monkeypatch, device):
96 envs.environment_variables[env_name] = lambda s=strategy: s
97
98 # Test the select_experts method
99 - topk_weights, topk_ids = FusedMoE.select_experts(
99 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
100 hidden_states=hidden_states,
101 router_logits=router_logits,
102 top_k=top_k,
vllm/config/model.py
+5 -1
@@ -1131,7 +1131,8 @@ class ModelConfig:
1131 if not hasattr(self.hf_text_config, "model_type"):
1132 return False
1133 elif self.hf_text_config.model_type in \
1134 - ('deepseek_v2', 'deepseek_v3', 'deepseek_mtp', 'kimi_k2'):
1134 + ('deepseek_v2', 'deepseek_v3', 'deepseek_mtp',
1135 + 'kimi_k2', 'longcat_flash'):
1136 return self.hf_text_config.kv_lora_rank is not None
1137 elif self.hf_text_config.model_type == 'eagle':
1138 # if the model is an EAGLE module, check for the
@@ -1257,6 +1258,9 @@ class ModelConfig:
1258 or self.hf_config.model_type == "qwen3_next_mtp"):
1259 total_num_hidden_layers = getattr(self.hf_text_config,
1260 "num_nextn_predict_layers", 0)
1261 + elif (self.hf_config.model_type == "longcat_flash_mtp"):
1262 + total_num_hidden_layers = getattr(self.hf_text_config,
1263 + "num_nextn_predict_layers", 1)
1264 else:
1265 total_num_hidden_layers = getattr(self.hf_text_config,
1266 "num_hidden_layers", 0)
vllm/config/speculative.py
+19 -2
@@ -31,7 +31,8 @@ logger = init_logger(__name__)
31
32 SpeculativeMethod = Literal["ngram", "eagle", "eagle3", "medusa",
33 "mlp_speculator", "draft_model", "deepseek_mtp",
34 - "ernie_mtp", "qwen3_next_mtp", "mimo_mtp"]
34 + "ernie_mtp", "qwen3_next_mtp", "mimo_mtp",
35 + "longcat_flash_mtp"]
36
37
38 @config
@@ -186,6 +187,13 @@ class SpeculativeConfig:
187 "n_predict": n_predict,
188 "architectures": ["Qwen3NextMTP"]
189 })
190 + if hf_config.model_type == "longcat_flash":
191 + hf_config.model_type = "longcat_flash_mtp"
192 + n_predict = getattr(hf_config, "num_nextn_predict_layers", 1)
193 + hf_config.update({
194 + "n_predict": n_predict,
195 + "architectures": ["LongCatFlashMTPModel"]
196 + })
197
198 return hf_config
199
@@ -332,6 +340,15 @@ class SpeculativeConfig:
340 "one layer. Might need some code changes " \
341 "to support multiple layers."
342 )
343 + elif (self.draft_model_config.hf_config.model_type
344 + in ("longcat_flash_mtp")):
345 + self.method = "longcat_flash_mtp"
346 + if self.num_speculative_tokens > 1:
347 + logger.warning(
348 + "LongCat MTP models only have " \
349 + "one layer. Might need some code changes " \
350 + "to support multiple layers."
351 + )
352 else:
353 self.method = "draft_model"
354 raise NotImplementedError(
@@ -548,7 +565,7 @@ class SpeculativeConfig:
565
566 def use_eagle(self) -> bool:
567 return self.method in ("eagle", "eagle3", "deepseek_mtp", "ernie_mtp",
551 - "qwen3_next_mtp")
568 + "qwen3_next_mtp", "longcat_flash_mtp")
569
570 def __repr__(self) -> str:
571 method = self.method
vllm/model_executor/layers/fused_moe/fused_moe.py
+89
@@ -664,6 +664,76 @@ def invoke_fused_moe_kernel(A: torch.Tensor,
664 )
665
666
667 +@triton.jit
668 +def compute_identity_kernel(
669 + top_k: int,
670 + hidden_states_ptr: tl.tensor,
671 + expert_scales_ptr: tl.tensor,
672 + num_tokens: int,
673 + output_ptr: tl.tensor,
674 + hidden_dim: int,
675 + scales_stride: int,
676 + BLOCK_SIZE: tl.constexpr,
677 +) -> None:
678 + pid = tl.program_id(0)
679 +
680 + batch_id = pid // (hidden_dim // BLOCK_SIZE)
681 + dim_offset = pid % (hidden_dim // BLOCK_SIZE) * BLOCK_SIZE
682 +
683 + if batch_id >= num_tokens or dim_offset >= hidden_dim:
684 + return
685 +
686 + h = tl.load(hidden_states_ptr + batch_id * hidden_dim + dim_offset +
687 + tl.arange(0, BLOCK_SIZE),
688 + mask=(dim_offset + tl.arange(0, BLOCK_SIZE)) < hidden_dim)
689 +
690 + result = tl.zeros([BLOCK_SIZE], dtype=tl.float32)
691 + for i in range(top_k):
692 + scale = tl.load(expert_scales_ptr + batch_id * scales_stride + i)
693 + result += h * scale
694 +
695 + tl.store(output_ptr + batch_id * hidden_dim + dim_offset +
696 + tl.arange(0, BLOCK_SIZE),
697 + result,
698 + mask=(dim_offset + tl.arange(0, BLOCK_SIZE)) < hidden_dim)
699 +
700 +
701 +def zero_experts_compute_triton(expert_indices: torch.Tensor,
702 + expert_scales: torch.Tensor, num_experts: int,
703 + zero_expert_type: str,
704 + hidden_states: torch.Tensor) -> torch.Tensor:
705 + N = expert_indices.numel()
706 + top_k = expert_indices.size(-1)
707 + grid = lambda meta: (triton.cdiv(N, meta['BLOCK_SIZE']), )
708 +
709 + if zero_expert_type == "identity":
710 + zero_expert_mask = expert_indices < num_experts
711 + zero_expert_scales = expert_scales.clone()
712 + zero_expert_scales[zero_expert_mask] = 0.0
713 +
714 + normal_expert_mask = expert_indices >= num_experts
715 + expert_indices[normal_expert_mask] = 0
716 + expert_scales[normal_expert_mask] = 0.0
717 +
718 + output = torch.zeros_like(hidden_states).to(hidden_states.device)
719 + hidden_dim = hidden_states.size(-1)
720 + num_tokens = hidden_states.size(0)
721 +
722 + grid = lambda meta: (num_tokens * (hidden_dim // meta['BLOCK_SIZE']), )
723 + compute_identity_kernel[grid](
724 + top_k,
725 + hidden_states,
726 + zero_expert_scales,
727 + num_tokens,
728 + output,
729 + hidden_dim,
730 + zero_expert_scales.stride(0),
731 + BLOCK_SIZE=256,
732 + )
733 +
734 + return output
735 +
736 +
737 # Adapted from: https://github.com/sgl-project/sglang/pull/2628
738 def get_config_file_name(E: int,
739 N: int,
@@ -940,6 +1010,25 @@ def fused_topk(
1010 return topk_weights, topk_ids, token_expert_indices
1011
1012
1013 +def fused_topk_bias(
1014 + hidden_states: torch.Tensor,
1015 + gating_output: torch.Tensor,
1016 + e_score_correction_bias: torch.Tensor,
1017 + topk: int,
1018 + renormalize: bool,
1019 +):
1020 + n_routed_experts = gating_output.shape[-1]
1021 + scores = gating_output.softmax(dim=-1)
1022 + scores_for_choice = scores.view(
1023 + -1, n_routed_experts) + e_score_correction_bias.unsqueeze(0)
1024 + topk_indices = torch.topk(scores_for_choice, k=topk, dim=-1,
1025 + sorted=False)[1]
1026 + topk_weights = scores.gather(1, topk_indices)
1027 + if renormalize:
1028 + topk_weights = topk_weights / topk_weights.sum(dim=-1, keepdim=True)
1029 + return topk_weights.to(torch.float32), topk_indices.to(torch.int32)
1030 +
1031 +
1032 # This is used by the Deepseek-V2 and Deepseek-V3 model
1033 @torch.compile(dynamic=True, backend=current_platform.simple_compile_backend)
1034 def grouped_topk(
vllm/model_executor/layers/fused_moe/layer.py
+72 -15
@@ -24,6 +24,8 @@ from vllm.model_executor.custom_op import CustomOp
24 from vllm.model_executor.layers.fused_moe.config import (
25 FUSED_MOE_UNQUANTIZED_CONFIG, FusedMoEConfig, FusedMoEParallelConfig,
26 FusedMoEQuantConfig, biased_moe_quant_config)
27 +from vllm.model_executor.layers.fused_moe.fused_moe import (
28 + zero_experts_compute_triton)
29 # yapf: enable
30 from vllm.model_executor.layers.fused_moe.modular_kernel import (
31 FusedMoEActivationFormat, FusedMoEModularKernel,
@@ -548,7 +550,10 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
550 logical_replica_count: Optional[torch.Tensor] = None,
551 ) -> Union[torch.Tensor, tuple[torch.Tensor, torch.Tensor]]:
552
551 - topk_weights, topk_ids = FusedMoE.select_experts(
553 + zero_expert_num = getattr(layer, 'zero_expert_num', 0)
554 + zero_expert_type = getattr(layer, 'zero_expert_type', None)
555 +
556 + topk_weights, topk_ids, zero_expert_result = FusedMoE.select_experts(
557 hidden_states=x,
558 router_logits=router_logits,
559 use_grouped_topk=use_grouped_topk,
@@ -565,11 +570,14 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
570 expert_map=expert_map,
571 expert_load_view=expert_load_view,
572 logical_to_physical_map=logical_to_physical_map,
568 - logical_replica_count=logical_replica_count)
573 + logical_replica_count=logical_replica_count,
574 + global_num_experts=global_num_experts,
575 + zero_expert_num=zero_expert_num,
576 + zero_expert_type=zero_expert_type)
577
578 if self.rocm_aiter_moe_enabled:
579 assert self.fused_experts is None
572 - return self.rocm_aiter_fused_experts(
580 + result = self.rocm_aiter_fused_experts(
581 hidden_states=x,
582 w1=layer.w13_weight,
583 w2=layer.w2_weight,
@@ -591,7 +599,7 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
599 if self.moe.has_bias:
600 raise ValueError(
601 "FusedMoEModularKernel does not support bias.")
594 - return self.fused_experts(
602 + result = self.fused_experts(
603 hidden_states=x,
604 w1=layer.w13_weight,
605 w2=layer.w2_weight,
@@ -605,7 +613,7 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
613 )
614 else:
615 assert fused_experts is not None
608 - return fused_experts(
616 + result = fused_experts(
617 hidden_states=x,
618 w1=layer.w13_weight,
619 w2=layer.w2_weight,
@@ -619,6 +627,13 @@ class UnquantizedFusedMoEMethod(FusedMoEMethodBase, CustomOp):
627 expert_map=expert_map,
628 )
629
630 + if zero_expert_num != 0 and zero_expert_type is not None:
631 + assert not isinstance(result, tuple), \
632 + "Shared + zero experts are mutually exclusive not yet supported"
633 + return result, zero_expert_result
634 + else:
635 + return result
636 +
637 def forward_cpu(
638 self,
639 layer: torch.nn.Module,
@@ -942,6 +957,8 @@ class FusedMoE(CustomOp):
957 num_redundant_experts: int = 0,
958 has_bias: bool = False,
959 is_sequence_parallel=False,
960 + zero_expert_num: Optional[int] = 0,
961 + zero_expert_type: Optional[str] = None,
962 ):
963 super().__init__()
964 if params_dtype is None:
@@ -976,6 +993,8 @@ class FusedMoE(CustomOp):
993 vllm_parallel_config=vllm_config.parallel_config))
994
995 self.global_num_experts = num_experts + num_redundant_experts
996 + self.zero_expert_num = zero_expert_num
997 + self.zero_expert_type = zero_expert_type
998
999 # Round up hidden size if needed.
1000 hidden_size = maybe_roundup_hidden_size(hidden_size, moe_in_dtype,
@@ -1656,25 +1675,30 @@ class FusedMoE(CustomOp):
1675 expert_load_view: Optional[torch.Tensor] = None,
1676 logical_to_physical_map: Optional[torch.Tensor] = None,
1677 logical_replica_count: Optional[torch.Tensor] = None,
1659 - ) -> tuple[torch.Tensor, torch.Tensor]:
1678 + global_num_experts: Optional[int] = None,
1679 + zero_expert_num: Optional[int] = None,
1680 + zero_expert_type: Optional[str] = None,
1681 + ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
1682 """
1683 Route the input hidden states to the top-k experts based on the
1684 router logits.
1685
1686 Returns:
1665 - (topk_weights, topk_ids) (tuple[torch.Tensor, torch.Tensor]):
1666 - The weights and *global physical* expert ids of the top-k experts.
1687 + (topk_weights, topk_ids, zero_expert_result)
1688 + (tuple[torch.Tensor, torch.Tensor, torch.Tensor]):
1689 + The weights, expert ids, and zero expert computation result.
1690
1691 **Compatibility**: When EPLB is not enabled, the returned ids are
1692 equivalent to global logical ids, so should be compatible with
1693 plain MoE implementations without redundant experts.
1694 """
1672 - from vllm.model_executor.layers.fused_moe.fused_moe import fused_topk
1695 + from vllm.model_executor.layers.fused_moe.fused_moe import (
1696 + fused_topk, fused_topk_bias)
1697
1698 # Check if we should use a routing simulation strategy
1699 routing_strategy = envs.VLLM_MOE_ROUTING_SIMULATION_STRATEGY
1700 if routing_strategy != "":
1677 - return RoutingSimulator.simulate_routing(
1701 + topk_weights, topk_ids = RoutingSimulator.simulate_routing(
1702 hidden_states=hidden_states,
1703 router_logits=router_logits,
1704 strategy_name=routing_strategy,
@@ -1697,6 +1721,16 @@ class FusedMoE(CustomOp):
1721 e_score_correction_bias=e_score_correction_bias)
1722 if indices_type is not None:
1723 topk_ids = topk_ids.to(dtype=indices_type)
1724 + elif e_score_correction_bias is not None:
1725 + topk_weights, topk_ids = fused_topk_bias(
1726 + hidden_states=hidden_states,
1727 + gating_output=router_logits,
1728 + e_score_correction_bias=e_score_correction_bias.data,
1729 + topk=top_k,
1730 + renormalize=renormalize,
1731 + )
1732 + if routed_scaling_factor is not None:
1733 + topk_weights *= routed_scaling_factor
1734 elif custom_routing_function is None:
1735 topk_weights, topk_ids, token_expert_indices = fused_topk(
1736 hidden_states=hidden_states,
@@ -1729,7 +1763,20 @@ class FusedMoE(CustomOp):
1763
1764 assert topk_ids.dtype == indices_type or indices_type is None
1765
1732 - return topk_weights, topk_ids
1766 + # Compute zero expert result if needed
1767 + if (zero_expert_num is not None and zero_expert_num > 0
1768 + and zero_expert_type is not None
1769 + and global_num_experts is not None):
1770 + zero_expert_result = zero_experts_compute_triton(
1771 + expert_indices=topk_ids,
1772 + expert_scales=topk_weights,
1773 + num_experts=global_num_experts,
1774 + zero_expert_type=zero_expert_type,
1775 + hidden_states=hidden_states,
1776 + )
1777 + else:
1778 + zero_expert_result = None
1779 + return topk_weights, topk_ids, zero_expert_result
1780
1781 def must_reduce_shared_expert_outputs(self) -> bool:
1782 """
@@ -1878,6 +1925,11 @@ class FusedMoE(CustomOp):
1925 assert self.shared_experts is None or isinstance(
1926 final_hidden_states, tuple)
1927
1928 + if isinstance(final_hidden_states, tuple):
1929 + final_hidden_states, zero_expert_result = final_hidden_states
1930 + if zero_expert_result is not None:
1931 + final_hidden_states += zero_expert_result
1932 +
1933 if not skip_result_store:
1934 if self.shared_experts is None:
1935 full_fused_final_hidden_states[
@@ -1992,6 +2044,9 @@ class FusedMoE(CustomOp):
2044 shared_output,
2045 final_hidden_states,
2046 )
2047 + elif self.zero_expert_num is not None and self.zero_expert_num > 0:
2048 + assert isinstance(final_hidden_states, tuple)
2049 + final_hidden_states, zero_expert_result = final_hidden_states
2050
2051 def reduce_output(states: torch.Tensor,
2052 do_combine: bool = True) -> torch.Tensor:
@@ -2003,14 +2058,16 @@ class FusedMoE(CustomOp):
2058
2059 return states
2060
2006 - if self.shared_experts is None:
2007 - assert not isinstance(final_hidden_states, tuple)
2008 - return reduce_output(final_hidden_states)
2009 - else:
2061 + if self.shared_experts is not None:
2062 return (
2063 reduce_output(final_hidden_states[0], do_combine=False),
2064 reduce_output(final_hidden_states[1]),
2065 )
2066 + elif self.zero_expert_num is not None and self.zero_expert_num > 0:
2067 + assert isinstance(final_hidden_states, torch.Tensor)
2068 + return reduce_output(final_hidden_states) + zero_expert_result
2069 + else:
2070 + return reduce_output(final_hidden_states)
2071
2072 @classmethod
2073 def make_expert_params_mapping(
vllm/model_executor/layers/mla.py
-1
@@ -103,7 +103,6 @@ class MultiHeadLatentAttention(CustomOp):
103 )
104
105 self.prefix = prefix
106 - self.debug_layer_idx = int(self.prefix.split(".")[-2])
106
107 def forward_native(
108 self,
vllm/model_executor/layers/quantization/awq_marlin.py
+1 -1
@@ -520,7 +520,7 @@ class AWQMoEMethod(FusedMoEMethodBase):
520
521 assert activation == "silu", "Only SiLU activation is supported."
522
523 - topk_weights, topk_ids = FusedMoE.select_experts(
523 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
524 hidden_states=x,
525 router_logits=router_logits,
526 use_grouped_topk=use_grouped_topk,
vllm/model_executor/layers/quantization/bitsandbytes.py
+1 -1
@@ -486,7 +486,7 @@ class BitsAndBytesMoEMethod(FusedMoEMethodBase):
486 if enable_eplb:
487 raise NotImplementedError(
488 "EPLB not supported for `BitsAndBytesMoEMethod` yet.")
489 - topk_weights, topk_ids = FusedMoE.select_experts(
489 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
490 hidden_states=x,
491 router_logits=router_logits,
492 use_grouped_topk=use_grouped_topk,
vllm/model_executor/layers/quantization/compressed_tensors/compressed_tensors_moe.py
+5 -5
@@ -385,7 +385,7 @@ class CompressedTensorsW4A4MoeMethod(CompressedTensorsMoEMethod):
385 "`CompressedTensorsW4A4MoeMethod` yet.")
386 assert activation == "silu", "Only SiLU activation is supported."
387
388 - topk_weights, topk_ids = FusedMoE.select_experts(
388 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
389 hidden_states=x,
390 router_logits=router_logits,
391 use_grouped_topk=use_grouped_topk,
@@ -934,7 +934,7 @@ class CompressedTensorsW8A8Fp8MoEMethod(CompressedTensorsMoEMethod):
934 "EPLB not supported for "
935 "`CompressedTensorsW8A8Fp8MoEMethod` yet.")
936
937 - topk_weights, topk_ids = FusedMoE.select_experts(
937 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
938 hidden_states=x,
939 router_logits=router_logits,
940 use_grouped_topk=use_grouped_topk,
@@ -1195,7 +1195,7 @@ class CompressedTensorsW8A8Int8MoEMethod(CompressedTensorsMoEMethod):
1195
1196 from vllm.model_executor.layers.fused_moe import fused_experts
1197
1198 - topk_weights, topk_ids = FusedMoE.select_experts(
1198 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
1199 hidden_states=x,
1200 router_logits=router_logits,
1201 use_grouped_topk=use_grouped_topk,
@@ -1502,7 +1502,7 @@ class CompressedTensorsWNA16MarlinMoEMethod(CompressedTensorsMoEMethod):
1502 assert activation == "silu", (
1503 f"{activation} not supported for Marlin MoE.")
1504
1505 - topk_weights, topk_ids = FusedMoE.select_experts(
1505 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
1506 hidden_states=x,
1507 router_logits=router_logits,
1508 use_grouped_topk=use_grouped_topk,
@@ -1747,7 +1747,7 @@ class CompressedTensorsWNA16MoEMethod(CompressedTensorsMoEMethod):
1747
1748 from vllm.model_executor.layers.fused_moe import fused_experts
1749
1750 - topk_weights, topk_ids = FusedMoE.select_experts(
1750 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
1751 hidden_states=x,
1752 router_logits=router_logits,
1753 use_grouped_topk=use_grouped_topk,
vllm/model_executor/layers/quantization/experts_int8.py
+1 -1
@@ -146,7 +146,7 @@ class ExpertsInt8MoEMethod(FusedMoEMethodBase):
146
147 from vllm.model_executor.layers.fused_moe import fused_experts
148
149 - topk_weights, topk_ids = FusedMoE.select_experts(
149 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
150 hidden_states=x,
151 router_logits=router_logits,
152 use_grouped_topk=use_grouped_topk,
vllm/model_executor/layers/quantization/fp8.py
+29 -9
@@ -18,6 +18,8 @@ from vllm.model_executor.layers.fused_moe import (
18 FusedMoeWeightScaleSupported)
19 from vllm.model_executor.layers.fused_moe.config import (
20 FusedMoEQuantConfig, fp8_w8a8_moe_quant_config)
21 +from vllm.model_executor.layers.fused_moe.layer import (
22 + UnquantizedFusedMoEMethod)
23 from vllm.model_executor.layers.linear import (LinearBase, LinearMethodBase,
24 UnquantizedLinearMethod)
25 from vllm.model_executor.layers.quantization import QuantizationMethods
@@ -174,6 +176,10 @@ class Fp8Config(QuantizationConfig):
176 return UnquantizedLinearMethod()
177 return Fp8LinearMethod(self)
178 elif isinstance(layer, FusedMoE):
179 + if is_layer_skipped(prefix=prefix,
180 + ignored_layers=self.ignored_layers,
181 + fused_mapping=self.packed_modules_mapping):
182 + return UnquantizedFusedMoEMethod(layer.moe_config)
183 return Fp8MoEMethod(self, layer)
184 elif isinstance(layer, Attention):
185 return Fp8KVCacheMethod(self)
@@ -927,6 +933,7 @@ class Fp8MoEMethod(FusedMoEMethodBase):
933 logical_to_physical_map: Optional[torch.Tensor] = None,
934 logical_replica_count: Optional[torch.Tensor] = None,
935 ) -> Union[torch.Tensor, tuple[torch.Tensor, torch.Tensor]]:
936 +
937 if enable_eplb:
938 assert expert_load_view is not None
939 assert logical_to_physical_map is not None
@@ -943,8 +950,7 @@ class Fp8MoEMethod(FusedMoEMethodBase):
950 import vllm.model_executor.layers.fused_moe.flashinfer_trtllm_moe # noqa: E501, F401
951 assert (renormalize and use_grouped_topk
952 and custom_routing_function is None)
946 -
947 - return torch.ops.vllm.flashinfer_fused_moe_blockscale_fp8(
953 + result = torch.ops.vllm.flashinfer_fused_moe_blockscale_fp8(
954 routing_logits=router_logits.to(torch.float32),
955 routing_bias=e_score_correction_bias,
956 x=x,
@@ -965,7 +971,7 @@ class Fp8MoEMethod(FusedMoEMethodBase):
971 else:
972 assert (not renormalize
973 and custom_routing_function is not None)
968 - return apply_flashinfer_per_tensor_scale_fp8(
974 + result = apply_flashinfer_per_tensor_scale_fp8(
975 layer=layer,
976 hidden_states=x,
977 router_logits=router_logits,
@@ -976,7 +982,10 @@ class Fp8MoEMethod(FusedMoEMethodBase):
982 topk_group=topk_group,
983 apply_router_weight_on_input=apply_router_weight_on_input)
984
979 - topk_weights, topk_ids = FusedMoE.select_experts(
985 + zero_expert_num = getattr(layer, 'zero_expert_num', 0)
986 + zero_expert_type = getattr(layer, 'zero_expert_type', None)
987 +
988 + select_result = FusedMoE.select_experts(
989 hidden_states=x,
990 router_logits=router_logits,
991 use_grouped_topk=use_grouped_topk,
@@ -994,17 +1003,22 @@ class Fp8MoEMethod(FusedMoEMethodBase):
1003 expert_load_view=expert_load_view,
1004 logical_to_physical_map=logical_to_physical_map,
1005 logical_replica_count=logical_replica_count,
1006 + global_num_experts=global_num_experts,
1007 + zero_expert_num=zero_expert_num,
1008 + zero_expert_type=zero_expert_type,
1009 )
1010
1011 #
1012 # Note: the order of checks is important since self.fused_experts
1013 # can override fused_experts or cutlass but not rocm or marlin.
1014 #
1015 + topk_weights, topk_ids, zero_expert_result = select_result
1016 +
1017 if self.rocm_aiter_moe_enabled:
1018 from vllm.model_executor.layers.fused_moe.rocm_aiter_fused_moe import ( # noqa: E501
1019 rocm_aiter_fused_experts)
1020 assert self.fused_experts is None
1007 - return rocm_aiter_fused_experts(
1021 + result = rocm_aiter_fused_experts(
1022 x,
1023 layer.w13_weight,
1024 layer.w2_weight,
@@ -1018,7 +1032,7 @@ class Fp8MoEMethod(FusedMoEMethodBase):
1032 assert activation == "silu", (
1033 f"{activation} not supported for Marlin MoE.")
1034 assert self.fused_experts is None
1021 - return torch.ops.vllm.fused_marlin_moe(
1035 + result = torch.ops.vllm.fused_marlin_moe(
1036 x,
1037 layer.w13_weight,
1038 layer.w2_weight,
@@ -1035,7 +1049,7 @@ class Fp8MoEMethod(FusedMoEMethodBase):
1049 expert_map=expert_map,
1050 workspace=layer.workspace)
1051 elif self.fused_experts:
1038 - return self.fused_experts(
1052 + result = self.fused_experts(
1053 hidden_states=x,
1054 w1=layer.w13_weight,
1055 w2=layer.w2_weight,
@@ -1055,7 +1069,7 @@ class Fp8MoEMethod(FusedMoEMethodBase):
1069 assert scoring_func == 'sigmoid', (
1070 f"Expected 'sigmoid' scoring func but got {scoring_func}")
1071
1058 - return flashinfer_cutlass_moe_fp8(
1072 + result = flashinfer_cutlass_moe_fp8(
1073 x,
1074 layer,
1075 topk_weights,
@@ -1068,7 +1082,7 @@ class Fp8MoEMethod(FusedMoEMethodBase):
1082 )
1083 else:
1084 from vllm.model_executor.layers.fused_moe import fused_experts
1071 - return fused_experts(
1085 + result = fused_experts(
1086 hidden_states=x,
1087 w1=layer.w13_weight,
1088 w2=layer.w2_weight,
@@ -1083,6 +1097,12 @@ class Fp8MoEMethod(FusedMoEMethodBase):
1097 allow_deep_gemm=self.allow_deep_gemm,
1098 allow_cutlass_block_scaled_grouped_gemm=(
1099 self.allow_cutlass_block_scaled_grouped_gemm))
1100 + if zero_expert_num != 0 and zero_expert_type is not None:
1101 + assert not isinstance(result, tuple), \
1102 + "Shared + zero experts are mutually exclusive not yet supported"
1103 + return result, zero_expert_result
1104 + else:
1105 + return result
1106
1107
1108 class Fp8KVCacheMethod(BaseKVCacheMethod):
vllm/model_executor/layers/quantization/gguf.py
+1 -1
@@ -555,7 +555,7 @@ class GGUFMoEMethod(FusedMoEMethodBase):
555 "Apply router weight on input is not supported for"
556 "fused GGUF MoE method.")
557
558 - topk_weights, topk_ids = FusedMoE.select_experts(
558 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
559 hidden_states=x,
560 router_logits=router_logits,
561 use_grouped_topk=use_grouped_topk,
vllm/model_executor/layers/quantization/gptq_marlin.py
+1 -1
@@ -669,7 +669,7 @@ class GPTQMarlinMoEMethod(FusedMoEMethodBase):
669
670 assert activation == "silu", "Only SiLU activation is supported."
671
672 - topk_weights, topk_ids = FusedMoE.select_experts(
672 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
673 hidden_states=x,
674 router_logits=router_logits,
675 use_grouped_topk=use_grouped_topk,
vllm/model_executor/layers/quantization/modelopt.py
+2 -2
@@ -543,7 +543,7 @@ class ModelOptFp8MoEMethod(FusedMoEMethodBase):
543 apply_router_weight_on_input=apply_router_weight_on_input)
544
545 # Expert selection
546 - topk_weights, topk_ids = FusedMoE.select_experts(
546 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
547 hidden_states=x,
548 router_logits=router_logits,
549 use_grouped_topk=use_grouped_topk,
@@ -1491,7 +1491,7 @@ class ModelOptNvFp4FusedMoE(FusedMoEMethodBase):
1491 )[0]
1492 return out
1493
1494 - topk_weights, topk_ids = FusedMoE.select_experts(
1494 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
1495 hidden_states=x,
1496 router_logits=router_logits,
1497 use_grouped_topk=use_grouped_topk,
vllm/model_executor/layers/quantization/moe_wna16.py
+1 -1
@@ -332,7 +332,7 @@ class MoeWNA16Method(FusedMoEMethodBase):
332
333 from vllm.model_executor.layers.fused_moe import fused_experts
334 assert activation == "silu", "Only SiLU activation is supported."
335 - topk_weights, topk_ids = FusedMoE.select_experts(
335 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
336 hidden_states=x,
337 router_logits=router_logits,
338 use_grouped_topk=use_grouped_topk,
vllm/model_executor/layers/quantization/mxfp4.py
+3 -3
@@ -718,7 +718,7 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
718
719 assert isinstance(self.fused_experts, mk.FusedMoEModularKernel)
720
721 - topk_weights, topk_ids = FusedMoE.select_experts(
721 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
722 hidden_states=x,
723 router_logits=router_logits,
724 use_grouped_topk=use_grouped_topk,
@@ -783,7 +783,7 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
783 raise NotImplementedError("EPLB is not supported for mxfp4")
784
785 if self.mxfp4_backend == Mxfp4Backend.MARLIN:
786 - topk_weights, topk_ids = FusedMoE.select_experts(
786 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
787 hidden_states=x,
788 router_logits=router_logits,
789 use_grouped_topk=use_grouped_topk,
@@ -894,7 +894,7 @@ class Mxfp4MoEMethod(FusedMoEMethodBase):
894 or self.mxfp4_backend == Mxfp4Backend.SM90_FI_MXFP4_BF16):
895 from vllm.utils.flashinfer import flashinfer_cutlass_fused_moe
896
897 - topk_weights, topk_ids = FusedMoE.select_experts(
897 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
898 hidden_states=x,
899 router_logits=router_logits,
900 use_grouped_topk=use_grouped_topk,
vllm/model_executor/layers/quantization/quark/quark_moe.py
+2 -2
@@ -329,7 +329,7 @@ class QuarkW8A8Fp8MoEMethod(QuarkMoEMethod):
329 raise NotImplementedError(
330 "EPLB not supported for `QuarkW8A8Fp8MoEMethod` yet.")
331
332 - topk_weights, topk_ids = FusedMoE.select_experts(
332 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
333 hidden_states=x,
334 router_logits=router_logits,
335 use_grouped_topk=use_grouped_topk,
@@ -531,7 +531,7 @@ class QuarkW4A4MXFp4MoEMethod(QuarkMoEMethod):
531
532 from vllm.model_executor.layers.fused_moe import fused_experts
533
534 - topk_weights, topk_ids = FusedMoE.select_experts(
534 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
535 hidden_states=x,
536 router_logits=router_logits,
537 use_grouped_topk=use_grouped_topk,
vllm/model_executor/layers/quantization/rtn.py
+1 -1
@@ -318,7 +318,7 @@ class RTNMoEMethod(FusedMoEMethodBase):
318
319 from vllm.model_executor.layers.fused_moe import fused_experts
320
321 - topk_weights, topk_ids = FusedMoE.select_experts(
321 + topk_weights, topk_ids, _ = FusedMoE.select_experts(
322 hidden_states=x,
323 router_logits=router_logits,
324 use_grouped_topk=use_grouped_topk,
vllm/model_executor/layers/quantization/utils/quant_utils.py
+5
@@ -292,6 +292,11 @@ def is_layer_skipped(
292 f"Detected some but not all shards of {prefix} "
293 "are quantized. All shards of fused layers "
294 "to have the same precision.")
295 + elif "experts" in prefix:
296 + return any([
297 + prefix in layer_name for layer_name in ignored_layers
298 + if "experts" in layer_name
299 + ])
300 else:
301 is_skipped = prefix in ignored_layers
302
vllm/model_executor/models/longcat_flash.py new
+712
@@ -0,0 +1,712 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# Apache License, Version 2.0:
4 +# Licensed under the Apache License, Version 2.0 (the "License");
5 +# you may not use this file except in compliance with the License.
6 +# You may obtain a copy of the License at
7 +#
8 +# http://www.apache.org/licenses/LICENSE-2.0
9 +#
10 +# Unless required by applicable law or agreed to in writing, software
11 +# distributed under the License is distributed on an "AS IS" BASIS,
12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 +# See the License for the specific language governing permissions and
14 +# limitations under the License.
15 +#
16 +# MIT License:
17 +# Permission is hereby granted, free of charge, to any person obtaining a copy
18 +# of this software and associated documentation files (the "Software"), to deal
19 +# in the Software without restriction, including without limitation the rights
20 +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
21 +# copies of the Software, and to permit persons to whom the Software is
22 +# furnished to do so, subject to the following conditions:
23 +#
24 +# The above copyright notice and this permission notice shall be included in all
25 +# copies or substantial portions of the Software.
26 +#
27 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28 +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29 +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30 +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31 +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32 +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33 +# SOFTWARE.
34 +"""Inference-only Flash model compatible with HuggingFace weights."""
35 +import typing
36 +from collections.abc import Callable, Iterable
37 +from typing import Optional, Union
38 +
39 +import torch
40 +from torch import nn
41 +from transformers import PretrainedConfig
42 +
43 +from vllm.compilation.decorators import support_torch_compile
44 +from vllm.config import CacheConfig, VllmConfig
45 +from vllm.distributed import get_pp_group
46 +from vllm.logger import init_logger
47 +from vllm.model_executor.layers.activation import SiluAndMul
48 +from vllm.model_executor.layers.fused_moe import FusedMoE
49 +from vllm.model_executor.layers.layernorm import RMSNorm
50 +from vllm.model_executor.layers.linear import (MergedColumnParallelLinear,
51 + ReplicatedLinear,
52 + RowParallelLinear)
53 +from vllm.model_executor.layers.logits_processor import LogitsProcessor
54 +from vllm.model_executor.layers.quantization import QuantizationConfig
55 +from vllm.model_executor.layers.quantization.utils.int8_utils import (
56 + block_dequant)
57 +from vllm.model_executor.layers.vocab_parallel_embedding import (
58 + ParallelLMHead, VocabParallelEmbedding)
59 +from vllm.model_executor.model_loader.weight_utils import default_weight_loader
60 +from vllm.model_executor.models.deepseek_v2 import DeepseekV2MLAAttention
61 +from vllm.sequence import IntermediateTensors
62 +
63 +from .interfaces import SupportsLoRA, SupportsPP
64 +from .utils import (PPMissingLayer, is_pp_missing_parameter,
65 + make_empty_intermediate_tensors_factory, make_layers,
66 + maybe_prefix)
67 +
68 +logger = init_logger(__name__)
69 +
70 +
71 +class FlashConfig(PretrainedConfig):
72 + """Flash model configuration."""
73 + model_type = "longcat_flash"
74 + keys_to_ignore_at_inference = ["past_key_values"]
75 +
76 + def __init__(
77 + self,
78 + vocab_size=131072,
79 + hidden_size=4096,
80 + intermediate_size=8192,
81 + num_layers=28,
82 + num_hidden_layers=None,
83 + num_attention_heads=96,
84 + num_key_value_heads=128,
85 + ep_size=1,
86 + kv_lora_rank=512,
87 + q_lora_rank=1536,
88 + qk_rope_head_dim=64,
89 + v_head_dim=128,
90 + qk_nope_head_dim=128,
91 + num_experts_per_tok=None,
92 + norm_topk_prob=False,
93 + max_position_embeddings=8192,
94 + initializer_range=0.02,
95 + rms_norm_eps=1e-05,
96 + use_cache=True,
97 + pad_token_id=None,
98 + bos_token_id=100000,
99 + eos_token_id=100001,
100 + pretraining_tp=1,
101 + tie_word_embeddings=False,
102 + rope_theta=1000000.0,
103 + rope_scaling=None,
104 + attention_bias=False,
105 + attention_dropout=0.0,
106 + mla_scale_q_lora=False,
107 + mla_scale_kv_lora=False,
108 + torch_dtype="bfloat16",
109 + params_dtype="bfloat16",
110 + router_dtype="float32",
111 + router_bias=False,
112 + topk_method=None,
113 + routed_scaling_factor=None,
114 + zero_expert_num=0,
115 + zero_expert_type=None,
116 + nextn_use_scmoe=False,
117 + **kwargs,
118 + ):
119 + super().__init__(
120 + pad_token_id=pad_token_id,
121 + bos_token_id=bos_token_id,
122 + eos_token_id=eos_token_id,
123 + tie_word_embeddings=tie_word_embeddings,
124 + torch_dtype=torch_dtype,
125 + params_dtype=params_dtype,
126 + router_dtype=router_dtype,
127 + topk_method=topk_method,
128 + router_bias=router_bias,
129 + nextn_use_scmoe=nextn_use_scmoe,
130 + **kwargs,
131 + )
132 + self.vocab_size = vocab_size
133 + self.max_position_embeddings = max_position_embeddings
134 + self.hidden_size = hidden_size
135 + self.num_hidden_layers = (num_hidden_layers if num_hidden_layers
136 + is not None else num_layers)
137 + self.num_attention_heads = num_attention_heads
138 + self.ep_size = ep_size
139 + self.kv_lora_rank = kv_lora_rank
140 + self.q_lora_rank = q_lora_rank
141 + self.qk_rope_head_dim = qk_rope_head_dim
142 + self.v_head_dim = v_head_dim
143 + self.qk_nope_head_dim = qk_nope_head_dim
144 + self.num_experts_per_tok = num_experts_per_tok
145 + self.norm_topk_prob = norm_topk_prob
146 + # for backward compatibility
147 + if num_key_value_heads is None:
148 + num_key_value_heads = num_attention_heads
149 +
150 + self.num_key_value_heads = num_key_value_heads
151 + self.initializer_range = initializer_range
152 + self.rms_norm_eps = rms_norm_eps
153 + self.pretraining_tp = pretraining_tp
154 + self.use_cache = use_cache
155 + self.rope_theta = rope_theta
156 + self.rope_scaling = rope_scaling
157 + self.attention_bias = attention_bias
158 + self.attention_dropout = attention_dropout
159 + self.mla_scale_q_lora = mla_scale_q_lora
160 + self.mla_scale_kv_lora = mla_scale_kv_lora
161 + self.zero_expert_num = zero_expert_num
162 + self.zero_expert_type = zero_expert_type
163 + self.routed_scaling_factor = routed_scaling_factor
164 + self.hidden_act = "silu"
165 + self.intermediate_size = self.ffn_hidden_size if hasattr(
166 + self, "ffn_hidden_size") else self.intermediate_size
167 + if hasattr(self, "moe_intermediate_size"):
168 + self.moe_intermediate_size = self.moe_intermediate_size
169 + elif hasattr(self, "expert_ffn_hidden_size"):
170 + self.moe_intermediate_size = self.expert_ffn_hidden_size
171 + else:
172 + self.moe_intermediate_size = self.intermediate_size
173 +
174 +
175 +class FlashMLP(nn.Module):
176 + """Flash MLP layer."""
177 +
178 + def __init__(
179 + self,
180 + hidden_size: int,
181 + intermediate_size: int,
182 + hidden_act: str,
183 + quant_config: Optional[QuantizationConfig] = None,
184 + reduce_results: bool = True,
185 + prefix: str = "",
186 + ) -> None:
187 + super().__init__()
188 + self.gate_up_proj = MergedColumnParallelLinear(
189 + hidden_size,
190 + [intermediate_size] * 2,
191 + bias=False,
192 + quant_config=quant_config,
193 + prefix=f"{prefix}.gate_up_proj",
194 + )
195 + self.down_proj = RowParallelLinear(
196 + intermediate_size,
197 + hidden_size,
198 + bias=False,
199 + quant_config=quant_config,
200 + reduce_results=reduce_results,
201 + prefix=f"{prefix}.down_proj",
202 + )
203 + if hidden_act != "silu":
204 + raise ValueError(f"Unsupported activation: {hidden_act}. "
205 + "Only silu is supported for now.")
206 + self.act_fn = SiluAndMul()
207 +
208 + def forward(self, x: torch.Tensor) -> torch.Tensor:
209 + if x.numel() == 0:
210 + return x
211 +
212 + gate_up, _ = self.gate_up_proj(x)
213 + x = self.act_fn(gate_up)
214 + x, _ = self.down_proj(x)
215 + return x
216 +
217 +
218 +class LongcatRouter(nn.Module):
219 +
220 + def __init__(self,
221 + config,
222 + zero_expert_num=0,
223 + rounter_params_dtype=torch.bfloat16,
224 + prefix: str = ""):
225 + super().__init__()
226 + self.n_routed_experts = config.n_routed_experts if hasattr(
227 + config, "n_routed_experts") else config.num_experts[0]
228 + self.n_routed_experts = self.n_routed_experts + zero_expert_num
229 + self.classifier = ReplicatedLinear(
230 + config.hidden_size,
231 + self.n_routed_experts,
232 + bias=config.router_bias,
233 + params_dtype=rounter_params_dtype,
234 + quant_config=None,
235 + prefix=f"{prefix}.classifier",
236 + )
237 + self.e_score_correction_bias = nn.Parameter(
238 + torch.zeros((self.n_routed_experts), dtype=rounter_params_dtype))
239 +
240 + def forward(self, hidden_states):
241 + logits, _ = self.classifier(hidden_states)
242 + return logits
243 +
244 +
245 +class LongcatMoe(nn.Module):
246 +
247 + def __init__(
248 + self,
249 + config: FlashConfig,
250 + num_experts: int,
251 + top_k: int,
252 + hidden_size: int,
253 + intermediate_size: int,
254 + params_dtype: Optional[torch.dtype] = None,
255 + quant_config: Optional[QuantizationConfig] = None,
256 + prefix: str = "",
257 + enable_eplb: bool = False,
258 + ):
259 + super().__init__()
260 + self.hidden_size = hidden_size
261 + self.zero_expert_num = config.zero_expert_num
262 + self.zero_expert_type = config.zero_expert_type
263 + self.routed_scaling_factor = config.routed_scaling_factor
264 + self.enable_eplb = enable_eplb
265 + # Gate always runs at half / full precision for now.
266 + self.rounter_params_dtype = params_dtype
267 + if config.router_dtype == "float32":
268 + self.rounter_params_dtype = torch.float32
269 +
270 + self.router = LongcatRouter(
271 + config=config,
272 + zero_expert_num=self.zero_expert_num,
273 + rounter_params_dtype=self.rounter_params_dtype,
274 + prefix=f"{prefix}.gate")
275 +
276 + self.experts = FusedMoE(
277 + num_experts=num_experts,
278 + top_k=top_k,
279 + hidden_size=hidden_size,
280 + intermediate_size=intermediate_size,
281 + reduce_results=True,
282 + params_dtype=params_dtype,
283 + e_score_correction_bias=self.router.e_score_correction_bias,
284 + renormalize=False,
285 + quant_config=quant_config,
286 + prefix=f"{prefix}.experts",
287 + zero_expert_num=self.zero_expert_num,
288 + zero_expert_type=self.zero_expert_type,
289 + enable_eplb=self.enable_eplb,
290 + routed_scaling_factor=config.routed_scaling_factor,
291 + )
292 +
293 + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
294 +
295 + num_tokens, hidden_dim = hidden_states.shape
296 + hidden_states = hidden_states.view(-1, hidden_dim)
297 +
298 + router_logits = self.router(hidden_states.to(
299 + self.rounter_params_dtype))
300 + final_hidden_states = self.experts(hidden_states=hidden_states,
301 + router_logits=router_logits)
302 +
303 + return final_hidden_states.view(num_tokens, hidden_dim)
304 +
305 +
306 +class FlashDecoderLayer(nn.Module):
307 + """Flash decoder layer with dual attention and MLP structure."""
308 +
309 + def __init__(
310 + self,
311 + config: FlashConfig,
312 + cache_config: Optional[CacheConfig] = None,
313 + quant_config: Optional[QuantizationConfig] = None,
314 + prefix: str = "",
315 + enable_eplb: bool = False,
316 + ) -> None:
317 + super().__init__()
318 + self.layer_idx = int(prefix.split(sep='.')[-1])
319 + self.hidden_size = config.hidden_size
320 + rope_theta = getattr(config, "rope_theta", 10000)
321 + rope_scaling = getattr(config, "rope_scaling", None)
322 + max_position_embeddings = getattr(config, "max_position_embeddings",
323 + 8192)
324 + if rope_scaling is not None and getattr(
325 + config, "original_max_position_embeddings", None):
326 + rope_scaling["original_max_position_embeddings"] = (
327 + config.original_max_position_embeddings)
328 +
329 + # Dual attention structure
330 + self.self_attn = nn.ModuleList([
331 + DeepseekV2MLAAttention(
332 + config=config,
333 + hidden_size=self.hidden_size,
334 + num_heads=config.num_attention_heads,
335 + qk_nope_head_dim=config.qk_nope_head_dim,
336 + qk_rope_head_dim=config.qk_rope_head_dim,
337 + v_head_dim=config.v_head_dim,
338 + q_lora_rank=(config.q_lora_rank if hasattr(
339 + config, "q_lora_rank") else None),
340 + kv_lora_rank=config.kv_lora_rank,
341 + rope_theta=rope_theta,
342 + rope_scaling=rope_scaling,
343 + max_position_embeddings=max_position_embeddings,
344 + cache_config=cache_config,
345 + quant_config=None if "self_attn" in getattr(
346 + config, "disable_quant_module", []) else quant_config,
347 + prefix=f"{prefix}.self_attn.{i}",
348 + ) for i in range(2)
349 + ])
350 + self.input_layernorm = nn.ModuleList([
351 + RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
352 + for i in range(2)
353 + ])
354 + self.post_attention_layernorm = nn.ModuleList([
355 + RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
356 + for i in range(2)
357 + ])
358 +
359 + # Dual MLP structure
360 + self.mlps = nn.ModuleList([
361 + FlashMLP(
362 + hidden_size=self.hidden_size,
363 + intermediate_size=config.intermediate_size,
364 + hidden_act=config.hidden_act,
365 + quant_config=None if "mlps" in getattr(
366 + config, "disable_quant_module", []) else quant_config,
367 + prefix=f"{prefix}.mlps.{i}",
368 + ) for i in range(2)
369 + ])
370 +
371 + self.mlp = LongcatMoe(
372 + config=config,
373 + num_experts=config.n_routed_experts if hasattr(
374 + config, "n_routed_experts") else
375 + config.num_experts[self.layer_idx],
376 + top_k=config.moe_topk
377 + if hasattr(config, "moe_topk") else config.num_experts_per_tok,
378 + hidden_size=config.hidden_size,
379 + intermediate_size=config.moe_intermediate_size,
380 + quant_config=quant_config,
381 + prefix=(f"{prefix}.mlp"),
382 + )
383 +
384 + def forward(
385 + self,
386 + positions: torch.Tensor,
387 + hidden_states: torch.Tensor,
388 + residual: Optional[torch.Tensor],
389 + ) -> tuple[torch.Tensor, torch.Tensor]:
390 +
391 + if residual is None:
392 + residual = hidden_states
393 + hidden_states = self.input_layernorm[0](hidden_states)
394 + else:
395 + hidden_states, residual = self.input_layernorm[0](hidden_states,
396 + residual)
397 +
398 + hidden_states = self.self_attn[0](
399 + positions=positions,
400 + hidden_states=hidden_states,
401 + )
402 +
403 + hidden_states, residual = self.post_attention_layernorm[0](
404 + hidden_states, residual)
405 +
406 + # moe
407 + hidden_states_copy = hidden_states.clone()
408 + moe_hidden_states = self.mlp(hidden_states_copy)
409 +
410 + # first mlp
411 + hidden_states = self.mlps[0](hidden_states)
412 +
413 + hidden_states, residual = self.input_layernorm[1](hidden_states,
414 + residual)
415 +
416 + # second_attn
417 + hidden_states = self.self_attn[1](
418 + positions=positions,
419 + hidden_states=hidden_states,
420 + )
421 + hidden_states, residual = self.post_attention_layernorm[1](
422 + hidden_states, residual)
423 +
424 + # second_mlp
425 + hidden_states = self.mlps[1](hidden_states)
426 +
427 + hidden_states = hidden_states + moe_hidden_states
428 +
429 + return hidden_states, residual
430 +
431 +
432 +@support_torch_compile
433 +class FlashModel(nn.Module):
434 + """Flash model."""
435 +
436 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
437 + super().__init__()
438 + config = FlashConfig(**vllm_config.model_config.hf_config.__dict__)
439 + cache_config = vllm_config.cache_config
440 + quant_config = vllm_config.quant_config
441 + self.config = config
442 +
443 + self.padding_idx = getattr(config, "pad_token_id", None)
444 + self.vocab_size = config.vocab_size
445 +
446 + if get_pp_group().is_first_rank:
447 + self.embed_tokens = VocabParallelEmbedding(
448 + config.vocab_size,
449 + config.hidden_size,
450 + prefix=maybe_prefix(prefix, "embed_tokens"),
451 + )
452 + else:
453 + self.embed_tokens = PPMissingLayer()
454 + self.start_layer, self.end_layer, self.layers = make_layers(
455 + config.num_hidden_layers,
456 + lambda prefix: FlashDecoderLayer(
457 + config,
458 + cache_config=cache_config,
459 + quant_config=quant_config,
460 + prefix=prefix,
461 + ),
462 + prefix=f"{prefix}.layers")
463 + if get_pp_group().is_last_rank:
464 + self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
465 + else:
466 + self.norm = PPMissingLayer()
467 + self.make_empty_intermediate_tensors = (
468 + make_empty_intermediate_tensors_factory(
469 + ["hidden_states", "residual"], config.hidden_size))
470 +
471 + def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
472 + return self.embed_tokens(input_ids)
473 +
474 + def forward(
475 + self,
476 + input_ids: torch.Tensor,
477 + positions: torch.Tensor,
478 + intermediate_tensors: Optional[IntermediateTensors] = None,
479 + inputs_embeds: Optional[torch.Tensor] = None,
480 + ) -> Union[torch.Tensor, IntermediateTensors]:
481 + if get_pp_group().is_first_rank:
482 + if inputs_embeds is not None:
483 + hidden_states = inputs_embeds
484 + else:
485 + hidden_states = self.get_input_embeddings(input_ids)
486 + residual = None
487 + else:
488 + assert intermediate_tensors is not None
489 + hidden_states = intermediate_tensors["hidden_states"]
490 + residual = intermediate_tensors["residual"]
491 +
492 + for i in range(self.start_layer, self.end_layer):
493 + layer = self.layers[i]
494 + hidden_states, residual = layer(
495 + positions,
496 + hidden_states,
497 + residual,
498 + )
499 +
500 + if not get_pp_group().is_last_rank:
501 + return IntermediateTensors({
502 + "hidden_states": hidden_states,
503 + "residual": residual
504 + })
505 +
506 + hidden_states, _ = self.norm(hidden_states, residual)
507 + return hidden_states
508 +
509 +
510 +class LongcatFlashForCausalLM(nn.Module, SupportsLoRA, SupportsPP):
511 + """Flash model for causal language modeling."""
512 +
513 + packed_modules_mapping = {
514 + "qkv_proj": [
515 + "q_proj",
516 + "k_proj",
517 + "v_proj",
518 + ],
519 + "gate_up_proj": [
520 + "gate_proj",
521 + "up_proj",
522 + ],
523 + }
524 +
525 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
526 + super().__init__()
527 + config = FlashConfig(**vllm_config.model_config.hf_config.__dict__)
528 + quant_config = vllm_config.quant_config
529 + lora_config = vllm_config.lora_config
530 +
531 + self.config = config
532 + config.intermediate_size = config.ffn_hidden_size if hasattr(
533 + config, "ffn_hidden_size") else config.intermediate_size
534 + self.lora_config = lora_config
535 + self.quant_config = quant_config
536 +
537 + self.model = FlashModel(vllm_config=vllm_config,
538 + prefix=maybe_prefix(prefix, "model"))
539 +
540 + if get_pp_group().is_last_rank:
541 + self.lm_head = ParallelLMHead(config.vocab_size,
542 + config.hidden_size,
543 + quant_config=quant_config,
544 + prefix=maybe_prefix(
545 + prefix, "lm_head"))
546 + else:
547 + self.lm_head = PPMissingLayer()
548 +
549 + self.logits_processor = LogitsProcessor(config.vocab_size)
550 + self.make_empty_intermediate_tensors = (
551 + self.model.make_empty_intermediate_tensors)
552 +
553 + def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor:
554 + return self.model.get_input_embeddings(input_ids)
555 +
556 + def forward(
557 + self,
558 + input_ids: torch.Tensor,
559 + positions: torch.Tensor,
560 + intermediate_tensors: Optional[IntermediateTensors] = None,
561 + inputs_embeds: Optional[torch.Tensor] = None,
562 + ) -> Union[torch.Tensor, IntermediateTensors]:
563 + hidden_states = self.model(input_ids, positions, intermediate_tensors,
564 + inputs_embeds)
565 + return hidden_states
566 +
567 + def compute_logits(
568 + self,
569 + hidden_states: torch.Tensor,
570 + ) -> Optional[torch.Tensor]:
571 + logits = self.logits_processor(self.lm_head, hidden_states)
572 + return logits
573 +
574 + def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
575 + # Params for weights, fp8 weight scales, fp8 activation scales
576 + # (param_name, weight_name, expert_id, shard_id)
577 + return FusedMoE.make_expert_params_mapping(
578 + ckpt_gate_proj_name="gate_proj",
579 + ckpt_down_proj_name="down_proj",
580 + ckpt_up_proj_name="up_proj",
581 + num_experts=self.config.n_routed_experts if hasattr(
582 + self.config, "n_routed_experts") else
583 + self.config.num_experts[0],
584 + )
585 +
586 + def load_weights(self, weights: Iterable[tuple[str,
587 + torch.Tensor]]) -> set[str]:
588 +
589 + stacked_params_mapping = [
590 + ("fused_qkv_a_proj", "q_a_proj", 0),
591 + ("fused_qkv_a_proj", "kv_a_proj_with_mqa", 1),
592 + (".gate_up_proj", ".gate_proj", 0),
593 + (".gate_up_proj", ".up_proj", 1),
594 + ]
595 +
596 + expert_params_mapping = self.get_expert_mapping()
597 + loaded_params: set[str] = set()
598 +
599 + params_dict = dict(self.named_parameters())
600 + for name, loaded_weight in weights:
601 + if "rotary_emb.inv_freq" in name:
602 + continue
603 + for param_name, weight_name, shard_id in stacked_params_mapping:
604 + if weight_name not in name:
605 + continue
606 + if "mlp" in name and "mlps" not in name:
607 + continue
608 + name = name.replace(weight_name, param_name)
609 + # Skip loading extra bias for GPTQ models.
610 + if (name.endswith(".bias")
611 + or name.endswith("_bias")) and name not in params_dict:
612 + continue
613 + # Skip mtp
614 + if ".mtp." in name:
615 + continue
616 + if is_pp_missing_parameter(name, self):
617 + continue
618 + param = params_dict[name]
619 + weight_loader = param.weight_loader
620 + weight_loader(param, loaded_weight, shard_id)
621 + break
622 + else:
623 + is_expert_weight = False
624 + for mapping in expert_params_mapping:
625 + param_name, weight_name, expert_id, shard_id = mapping
626 + if weight_name not in name:
627 + continue
628 + is_expert_weight = True
629 + name_mapped = name.replace(weight_name, param_name)
630 + # Skip mtp
631 + if ".mtp." in name_mapped:
632 + continue
633 + if (name_mapped.endswith(".bias")
634 + or name_mapped.endswith("_bias")
635 + ) and name not in params_dict:
636 + continue
637 + if is_pp_missing_parameter(name, self):
638 + continue
639 + param = params_dict[name_mapped]
640 + weight_loader = param.weight_loader
641 + weight_loader = typing.cast(Callable[..., bool],
642 + param.weight_loader)
643 + success = weight_loader(param,
644 + loaded_weight,
645 + name_mapped,
646 + shard_id=shard_id,
647 + expert_id=expert_id,
648 + return_success=True)
649 + if success:
650 + name = name_mapped
651 + break
652 + else:
653 + if is_expert_weight:
654 + # We've checked that this is an expert weight
655 + # However it's not mapped locally to this rank
656 + # So we simply skip it
657 + continue
658 + # Skip loading extra bias for GPTQ models.
659 + if name.endswith(".bias") and name not in params_dict:
660 + continue
661 + # Skip loading kv_scale from ckpts towards new design.
662 + if name.endswith(".kv_scale") and name not in params_dict:
663 + continue
664 + # Skip mtp
665 + if ".mtp." in name:
666 + continue
667 + if name is None:
668 + continue
669 + if is_pp_missing_parameter(name, self):
670 + continue
671 + param = params_dict[name]
672 + weight_loader = getattr(param, "weight_loader",
673 + default_weight_loader)
674 + weight_loader(param, loaded_weight)
675 + loaded_params.add(name)
676 + for layer_id in range(self.config.num_hidden_layers):
677 + for i in range(2):
678 + if isinstance(self.model.layers[layer_id], PPMissingLayer):
679 + continue
680 + self_attn = self.model.layers[layer_id].self_attn[i]
681 + if hasattr(self.quant_config, "weight_block_size"
682 + ) and self_attn.kv_b_proj.weight.dtype in (
683 + torch.float8_e4m3fn,
684 + torch.float8_e4m3fnuz,
685 + ):
686 + weight_block_size = self.quant_config.weight_block_size
687 + if weight_block_size is not None:
688 + assert hasattr(self_attn.kv_b_proj, "weight_scale_inv")
689 + dtype = torch.get_default_dtype()
690 + w = block_dequant(self_attn.kv_b_proj.weight,
691 + self_attn.kv_b_proj.weight_scale_inv,
692 + weight_block_size).to(dtype)
693 + else:
694 + w = self_attn.kv_b_proj.weight
695 +
696 + w_kc, w_vc = w.unflatten(
697 + 0,
698 + (-1,
699 + self_attn.qk_nope_head_dim + self_attn.v_head_dim)).split(
700 + [self_attn.qk_nope_head_dim, self_attn.v_head_dim],
701 + dim=1)
702 + self_attn.w_kc = w_kc.transpose(1, 2).contiguous().transpose(
703 + 1, 2)
704 + self_attn.w_vc = w_vc.contiguous().transpose(1, 2)
705 + if self.config.mla_scale_q_lora:
706 + self_attn.q_a_layernorm.weight.data *= (
707 + self.config.hidden_size / self.config.q_lora_rank)**0.5
708 + if self.config.mla_scale_kv_lora:
709 + self_attn.kv_a_layernorm.weight.data *= (
710 + self.config.hidden_size /
711 + self.config.kv_lora_rank)**0.5
712 + return loaded_params
vllm/model_executor/models/longcat_flash_mtp.py new
+352
@@ -0,0 +1,352 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +# Adapted from
5 +# https://github.com/vllm-project/vllm/blob/v0.7.3/vllm/model_executor/models/deepseek_mtp.py
6 +from collections.abc import Iterable
7 +from typing import Optional
8 +
9 +import torch
10 +import torch.nn as nn
11 +from transformers import PretrainedConfig
12 +
13 +from vllm.config import VllmConfig
14 +from vllm.model_executor.layers.layernorm import RMSNorm
15 +from vllm.model_executor.layers.linear import ReplicatedLinear
16 +from vllm.model_executor.layers.logits_processor import LogitsProcessor
17 +from vllm.model_executor.layers.quantization import QuantizationConfig
18 +from vllm.model_executor.layers.quantization.utils.int8_utils import (
19 + block_dequant)
20 +from vllm.model_executor.layers.vocab_parallel_embedding import (
21 + ParallelLMHead, VocabParallelEmbedding)
22 +from vllm.model_executor.model_loader.weight_utils import default_weight_loader
23 +from vllm.model_executor.models.longcat_flash import FlashConfig
24 +from vllm.sequence import IntermediateTensors
25 +
26 +from .deepseek_v2 import DeepseekV2DecoderLayer
27 +from .interfaces import SupportsPP
28 +from .utils import maybe_prefix
29 +
30 +
31 +class LongCatMultiTokenPredictorLayer(nn.Module):
32 +
33 + def __init__(
34 + self,
35 + config: PretrainedConfig,
36 + prefix: str,
37 + vllm_config: VllmConfig,
38 + quant_config: Optional[QuantizationConfig] = None,
39 + ) -> None:
40 + super().__init__()
41 + self.enorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
42 + self.hnorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
43 + self.eh_proj = ReplicatedLinear(2 * config.hidden_size,
44 + config.hidden_size,
45 + bias=False,
46 + quant_config=quant_config,
47 + prefix="eh_proj")
48 + self.mtp_block = DeepseekV2DecoderLayer(vllm_config, prefix)
49 + self.final_layernorm = RMSNorm(config.hidden_size,
50 + eps=config.rms_norm_eps)
51 +
52 + def forward(
53 + self,
54 + input_ids: torch.Tensor,
55 + positions: torch.Tensor,
56 + previous_hidden_states: torch.Tensor,
57 + inputs_embeds: Optional[torch.Tensor] = None,
58 + spec_step_index: int = 0,
59 + ) -> torch.Tensor:
60 + assert inputs_embeds is not None
61 + inputs_embeds = self.enorm(inputs_embeds)
62 + previous_hidden_states = self.hnorm(previous_hidden_states)
63 +
64 + hidden_states, _ = self.eh_proj(
65 + torch.cat([inputs_embeds, previous_hidden_states], dim=-1))
66 +
67 + hidden_states, residual = self.mtp_block(positions=positions,
68 + hidden_states=hidden_states,
69 + residual=None)
70 + hidden_states, _ = self.final_layernorm(hidden_states, residual)
71 + return hidden_states
72 +
73 +
74 +class LongCatMultiTokenPredictor(nn.Module):
75 +
76 + def __init__(self,
77 + *,
78 + vllm_config: VllmConfig,
79 + quant_config: Optional[QuantizationConfig] = None,
80 + prefix: str = ""):
81 + super().__init__()
82 + config = FlashConfig(**vllm_config.model_config.hf_config.__dict__)
83 + vllm_config.model_config.hf_config.intermediate_size \
84 + = config.intermediate_size
85 + self.mtp_start_layer_idx = config.num_hidden_layers * 2
86 + self.num_mtp_layers = 1
87 + self.layers = torch.nn.ModuleDict({
88 + str(idx):
89 + LongCatMultiTokenPredictorLayer(
90 + config,
91 + prefix=f"{prefix}.layers.{idx}",
92 + vllm_config=vllm_config,
93 + quant_config=quant_config,
94 + )
95 + for idx in range(self.mtp_start_layer_idx,
96 + self.mtp_start_layer_idx + self.num_mtp_layers)
97 + })
98 + self.embed_tokens = VocabParallelEmbedding(
99 + config.vocab_size,
100 + config.hidden_size,
101 + )
102 +
103 + def forward(
104 + self,
105 + input_ids: torch.Tensor,
106 + positions: torch.Tensor,
107 + previous_hidden_states: torch.Tensor,
108 + inputs_embeds: Optional[torch.Tensor] = None,
109 + spec_step_idx: int = 0,
110 + ) -> torch.Tensor:
111 + if inputs_embeds is None:
112 + inputs_embeds = self.embed_tokens(input_ids)
113 + current_step_idx = (spec_step_idx % self.num_mtp_layers)
114 + return self.layers[str(self.mtp_start_layer_idx + current_step_idx)](
115 + input_ids,
116 + positions,
117 + previous_hidden_states,
118 + inputs_embeds,
119 + current_step_idx,
120 + )
121 +
122 +
123 +class LongCatFlashMTP(nn.Module, SupportsPP):
124 +
125 + def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
126 + super().__init__()
127 + # LongCat MTP without MoE layers
128 + vllm_config.model_config.hf_config.n_routed_experts = None
129 + self.config = FlashConfig(
130 + **vllm_config.model_config.hf_config.__dict__)
131 + self.quant_config = None if "mtp" in getattr(
132 + self.config, "disable_quant_module",
133 + []) else vllm_config.quant_config
134 +
135 + self.model = LongCatMultiTokenPredictor(vllm_config=vllm_config,
136 + quant_config=self.quant_config,
137 + prefix=maybe_prefix(
138 + prefix, "model"))
139 + self.lm_head = ParallelLMHead(
140 + self.config.vocab_size,
141 + self.config.hidden_size,
142 + quant_config=self.quant_config,
143 + )
144 + self.logits_processor = LogitsProcessor(self.config.vocab_size)
145 +
146 + def forward(
147 + self,
148 + input_ids: torch.Tensor,
149 + positions: torch.Tensor,
150 + hidden_states: torch.Tensor,
151 + intermediate_tensors: Optional[IntermediateTensors] = None,
152 + inputs_embeds: Optional[torch.Tensor] = None,
153 + spec_step_idx: int = 0,
154 + ) -> torch.Tensor:
155 + hidden_states = self.model(input_ids, positions, hidden_states,
156 + inputs_embeds, spec_step_idx)
157 + return hidden_states
158 +
159 + def compute_logits(
160 + self,
161 + hidden_states: torch.Tensor,
162 + spec_step_idx: int = 0,
163 + ) -> Optional[torch.Tensor]:
164 +
165 + logits = self.logits_processor(self.lm_head, hidden_states)
166 + return logits
167 +
168 + def load_weights(self, weights: Iterable[tuple[str,
169 + torch.Tensor]]) -> set[str]:
170 + stacked_params_mapping = [
171 + ("gate_up_proj", "gate_proj", 0),
172 + ("gate_up_proj", "up_proj", 1),
173 + ("fused_qkv_a_proj", "q_a_proj", 0),
174 + ("fused_qkv_a_proj", "kv_a_proj_with_mqa", 1),
175 + ]
176 +
177 + new_to_old_names_mapping = {
178 + "model.mtp.embed_tokens.weight":
179 + "model.layers.0.embed_tokens.weight",
180 + "model.mtp.layers.0.eh_proj.weight": "eh_proj.weight",
181 + "model.mtp.layers.0.eh_proj.weight_scale_inv":
182 + "eh_proj.weight_scale_inv",
183 + "model.mtp.layers.0.enorm.m.weight": "enorm.weight",
184 + "model.mtp.layers.0.hnorm.m.weight": "hnorm.weight",
185 + "model.mtp.layers.0.input_layernorm.weight":
186 + "model.layers.0.input_layernorm.weight",
187 + "model.mtp.layers.0.post_attention_layernorm.weight":
188 + "model.layers.0.post_attention_layernorm.weight",
189 + "model.mtp.layers.0.self_attn.kv_a_layernorm.weight":
190 + "model.layers.0.self_attn.kv_a_layernorm.weight",
191 + "model.mtp.layers.0.self_attn.kv_a_proj_with_mqa.weight":
192 + "model.layers.0.self_attn.kv_a_proj_with_mqa.weight",
193 + "model.mtp.layers.0.self_attn.kv_a_proj_with_mqa.weight_scale_inv":
194 + "model.layers.0.self_attn.kv_a_proj_with_mqa.weight_scale_inv",
195 + "model.mtp.layers.0.self_attn.kv_b_proj.weight":
196 + "model.layers.0.self_attn.kv_b_proj.weight",
197 + "model.mtp.layers.0.self_attn.kv_b_proj.weight_scale_inv":
198 + "model.layers.0.self_attn.kv_b_proj.weight_scale_inv",
199 + "model.mtp.layers.0.self_attn.o_proj.weight":
200 + "model.layers.0.self_attn.o_proj.weight",
201 + "model.mtp.layers.0.self_attn.o_proj.weight_scale_inv":
202 + "model.layers.0.self_attn.o_proj.weight_scale_inv",
203 + "model.mtp.layers.0.self_attn.q_a_layernorm.weight":
204 + "model.layers.0.self_attn.q_a_layernorm.weight",
205 + "model.mtp.layers.0.self_attn.q_a_proj.weight":
206 + "model.layers.0.self_attn.q_a_proj.weight",
207 + "model.mtp.layers.0.self_attn.q_a_proj.weight_scale_inv":
208 + "model.layers.0.self_attn.q_a_proj.weight_scale_inv",
209 + "model.mtp.layers.0.self_attn.q_b_proj.weight":
210 + "model.layers.0.self_attn.q_b_proj.weight",
211 + "model.mtp.layers.0.self_attn.q_b_proj.weight_scale_inv":
212 + "model.layers.0.self_attn.q_b_proj.weight_scale_inv",
213 + "model.mtp.layers.0.transformer_layer.mlp.down_proj.weight":
214 + "model.layers.0.mlp.down_proj.weight",
215 + "model.mtp.layers.0.transformer_layer.mlp.down_proj.weight_scale_inv":
216 + "model.layers.0.mlp.down_proj.weight_scale_inv",
217 + "model.mtp.layers.0.transformer_layer.mlp.gate_proj.weight":
218 + "model.layers.0.mlp.gate_proj.weight",
219 + "model.mtp.layers.0.transformer_layer.mlp.gate_proj.weight_scale_inv":
220 + "model.layers.0.mlp.gate_proj.weight_scale_inv",
221 + "model.mtp.layers.0.transformer_layer.mlp.up_proj.weight":
222 + "model.layers.0.mlp.up_proj.weight",
223 + "model.mtp.layers.0.transformer_layer.mlp.up_proj.weight_scale_inv":
224 + "model.layers.0.mlp.up_proj.weight_scale_inv",
225 + "model.mtp.norm.weight": "final_layernorm.weight",
226 + }
227 +
228 + params_dict = dict(self.named_parameters())
229 + loaded_params: set[str] = set()
230 + for name, loaded_weight in weights:
231 + if "rotary_emb.inv_freq" in name:
232 + continue
233 + spec_layer = self.get_spec_layer_idx_from_weight_name(
234 + self.config, name)
235 + if spec_layer is None:
236 + continue
237 + name = self._rewrite_spec_layer_name(spec_layer, name,
238 + new_to_old_names_mapping)
239 + for (param_name, weight_name, shard_id) in stacked_params_mapping:
240 + # Skip non-stacked layers and experts (experts handled below).
241 + if weight_name not in name:
242 + continue
243 + # We have mlp.experts[0].gate_proj in the checkpoint.
244 + # Since we handle the experts below in expert_params_mapping,
245 + # we need to skip here BEFORE we update the name, otherwise
246 + # name will be updated to mlp.experts[0].gate_up_proj, which
247 + # will then be updated below in expert_params_mapping
248 + # for mlp.experts[0].gate_gate_up_proj, which breaks load.
249 + if (("mlp.experts." in name) and name not in params_dict):
250 + continue
251 + name = name.replace(weight_name, param_name)
252 +
253 + # QKV fusion is optional, fall back to normal
254 + # weight loading if it's not enabled
255 + if ((param_name == "fused_qkv_a_proj")
256 + and name not in params_dict):
257 + continue
258 +
259 + # Skip loading extra bias for GPTQ models.
260 + if name.endswith(".bias") and name not in params_dict:
261 + continue
262 +
263 + param = params_dict[name]
264 + weight_loader = param.weight_loader
265 + weight_loader(param, loaded_weight, shard_id)
266 + break
267 + else:
268 + # Skip loading extra bias for GPTQ models.
269 + if name.endswith(".bias") and name not in params_dict:
270 + continue
271 +
272 + # According to DeepSeek-V3 Technical Report, MTP modules
273 + # shares embedding layer. We only load the first weights.
274 + if (spec_layer != self.model.mtp_start_layer_idx
275 + and ".layers" not in name):
276 + continue
277 +
278 + param = params_dict[name]
279 + weight_loader = getattr(param, "weight_loader",
280 + default_weight_loader)
281 + weight_loader(param, loaded_weight)
282 + loaded_params.add(name)
283 + spec_layer_id = self.config.num_hidden_layers * 2
284 + self_attn = self.model.layers[str(spec_layer_id)].mtp_block.self_attn
285 + if hasattr(
286 + self.quant_config,
287 + "weight_block_size") and self_attn.kv_b_proj.weight.dtype in (
288 + torch.float8_e4m3fn,
289 + torch.float8_e4m3fnuz,
290 + ):
291 + weight_block_size = self.quant_config.weight_block_size
292 + if weight_block_size is not None:
293 + dtype = torch.get_default_dtype()
294 + w = block_dequant(self_attn.kv_b_proj.weight,
295 + self_attn.kv_b_proj.weight_scale_inv,
296 + weight_block_size).to(dtype)
297 + else:
298 + w = self_attn.kv_b_proj.weight
299 + else:
300 + w = self_attn.kv_b_proj.weight
301 + w_kc, w_vc = w.unflatten(
302 + 0, (-1, self_attn.qk_nope_head_dim + self_attn.v_head_dim)).split(
303 + [self_attn.qk_nope_head_dim, self_attn.v_head_dim], dim=1)
304 + self_attn.w_kc = w_kc.transpose(1, 2).contiguous().transpose(1, 2)
305 + self_attn.w_vc = w_vc.contiguous().transpose(1, 2)
306 + if self.config.mla_scale_q_lora:
307 + self_attn.q_a_layernorm.weight.data *= (
308 + self.config.hidden_size / self.config.q_lora_rank)**0.5
309 + if self.config.mla_scale_kv_lora:
310 + self_attn.kv_a_layernorm.weight.data *= (
311 + self.config.hidden_size / self.config.kv_lora_rank)**0.5
312 + return loaded_params
313 +
314 + def _rewrite_spec_layer_name(self, spec_layer: int, name: str,
315 + new_to_old_names_mapping: dict) -> str:
316 + """
317 + Rewrite the weight name to match the format of the original model.
318 + Add .mtp_block for modules in transformer layer block for spec layer
319 + and rename shared layer weights to be top level.
320 + """
321 + if name in new_to_old_names_mapping:
322 + name = new_to_old_names_mapping[name]
323 + spec_layer_weight_names = [
324 + "embed_tokens", "enorm", "hnorm", "eh_proj", "shared_head"
325 + ]
326 + if name.startswith("enorm") or name.startswith(
327 + "hnorm") or name.startswith("eh_proj") or name.startswith(
328 + "final_layernorm"):
329 + name = "model.layers." + str(spec_layer) + "." + name
330 + shared_weight_names = ["embed_tokens"]
331 + spec_layer_weight = False
332 + shared_weight = False
333 + for weight_name in spec_layer_weight_names:
334 + if weight_name in name:
335 + spec_layer_weight = True
336 + if weight_name in shared_weight_names:
337 + shared_weight = True
338 + break
339 + if not spec_layer_weight:
340 + # treat rest weights as weights for transformer layer block
341 + name = name.replace("model.layers.0.",
342 + f"model.layers.{spec_layer}.mtp_block.")
343 + elif shared_weight:
344 + # treat shared weights as top level weights
345 + name = name.replace("model.layers.0.", "model.")
346 + return name
347 +
348 + def get_spec_layer_idx_from_weight_name(self, config: PretrainedConfig,
349 + weight_name: str) -> Optional[int]:
350 + if "model.mtp" in weight_name:
351 + return config.num_hidden_layers * 2
352 + return None
vllm/model_executor/models/registry.py
+2
@@ -109,6 +109,7 @@ _TEXT_GENERATION_MODELS = {
109 "Llama4ForCausalLM": ("llama4", "Llama4ForCausalLM"), # noqa: E501
110 # For decapoda-research/llama-*
111 "LLaMAForCausalLM": ("llama", "LlamaForCausalLM"),
112 + "LongcatFlashForCausalLM": ("longcat_flash", "LongcatFlashForCausalLM"),
113 "MambaForCausalLM": ("mamba", "MambaForCausalLM"),
114 "FalconMambaForCausalLM": ("mamba", "MambaForCausalLM"),
115 "FalconH1ForCausalLM":("falcon_h1", "FalconH1ForCausalLM"),
@@ -287,6 +288,7 @@ _SPECULATIVE_DECODING_MODELS = {
288 "EagleDeepSeekMTPModel": ("deepseek_eagle", "EagleDeepseekV3ForCausalLM"),
289 "DeepSeekMTPModel": ("deepseek_mtp", "DeepSeekMTP"),
290 "ErnieMTPModel": ("ernie_mtp", "ErnieMTP"),
291 + "LongCatFlashMTPModel": ("longcat_flash_mtp", "LongCatFlashMTP"),
292 "Glm4MoeMTPModel": ("glm4_moe_mtp", "Glm4MoeMTP"),
293 "MedusaModel": ("medusa", "Medusa"),
294 "Qwen3NextMTP": ("qwen3_next_mtp", "Qwen3NextMTP"),
vllm/model_executor/models/utils.py
+13 -5
@@ -691,14 +691,14 @@ def maybe_prefix(prefix: str, name: str) -> str:
691 return name if not prefix else f"{prefix}.{name}"
692
693
694 -def extract_layer_index(layer_name: str) -> int:
694 +def extract_layer_index(layer_name: str, num_attn_module: int = 1) -> int:
695 """
696 Extract the layer index from the module name.
697 Examples:
698 - "encoder.layers.0" -> 0
699 - "encoder.layers.1.self_attn" -> 1
700 - "2.self_attn" -> 2
701 - - "model.encoder.layers.0.sub.1" -> ValueError
701 + - "model.encoder.layers.0.sub.1" -> ValueError if num_attn_module == 1
702 """
703 subnames = layer_name.split(".")
704 int_vals: list[int] = []
@@ -707,9 +707,17 @@ def extract_layer_index(layer_name: str) -> int:
707 int_vals.append(int(subname))
708 except ValueError:
709 continue
710 - assert len(int_vals) == 1, (f"layer name {layer_name} should"
711 - " only contain one integer")
712 - return int_vals[0]
710 + if num_attn_module == 1 or "attn" not in layer_name:
711 + assert len(int_vals) == 1, (f"layer name {layer_name} should"
712 + " only contain one integer")
713 +
714 + return int_vals[0]
715 + else:
716 + assert len(int_vals) <= 2, (f"layer name {layer_name} should"
717 + " contain most two integers")
718 + layer_index = int_vals[0] * num_attn_module + int_vals[1] if len(
719 + int_vals) == 2 else int_vals[0]
720 + return layer_index
721
722
723 def cast_overflow_tensors(
vllm/v1/spec_decode/eagle.py
+7 -4
@@ -169,7 +169,6 @@ class EagleProposer:
169 target_hidden_states = self.model.combine_hidden_states(
170 target_hidden_states)
171 assert target_hidden_states.shape[-1] == self.hidden_size
172 -
172 # Shift the input ids by one token.
173 # E.g., [a1, b1, b2, c1, c2, c3] -> [b1, b2, c1, c2, c3, c3]
174 self.input_ids[:num_tokens - 1] = target_token_ids[1:]
@@ -223,7 +222,8 @@ class EagleProposer:
222 hidden_states=self.hidden_states[:num_input_tokens],
223 inputs_embeds=inputs_embeds,
224 )
226 - if self.method in ("deepseek_mtp", "ernie_mtp", "qwen3_next_mtp"):
225 + if self.method in ("deepseek_mtp", "ernie_mtp", "qwen3_next_mtp",
226 + "longcat_flash_mtp"):
227 last_hidden_states = ret_hidden_states
228 hidden_states = last_hidden_states
229 else:
@@ -237,7 +237,10 @@ class EagleProposer:
237 return draft_token_ids.view(-1, 1)
238
239 positions = target_positions[last_token_indices]
240 - hidden_states = hidden_states[last_token_indices]
240 + if self.method in ("deepseek_mtp", "ernie_mtp", "longcat_flash_mtp"):
241 + hidden_states = self.hidden_states[last_token_indices]
242 + else:
243 + hidden_states = hidden_states[last_token_indices]
244
245 if isinstance(attn_metadata, TreeAttentionMetadata):
246 # Draft using tree attention.
@@ -350,7 +353,7 @@ class EagleProposer:
353 inputs_embeds=inputs_embeds,
354 )
355 if self.method in ("deepseek_mtp", "ernie_mtp",
353 - "qwen3_next_mtp"):
356 + "qwen3_next_mtp", "longcat_flash_mtp"):
357 last_hidden_states = ret_hidden_states
358 hidden_states = ret_hidden_states
359 else:
vllm/v1/worker/gpu_model_runner.py
+3 -1
@@ -3840,9 +3840,11 @@ class GPUModelRunner(LoRAModelRunnerMixin, KVConnectorModelRunnerMixin):
3840 target_layer_name)
3841 kv_caches[layer_name] = kv_caches[target_layer_name]
3842
3843 + num_attn_module = 2 \
3844 + if self.model_config.hf_config.model_type == "longcat_flash" else 1
3845 bind_kv_cache(kv_caches,
3846 self.compilation_config.static_forward_context,
3845 - self.kv_caches)
3847 + self.kv_caches, num_attn_module)
3848 return kv_caches
3849
3850 def maybe_add_kv_sharing_layers_to_kv_cache_groups(
vllm/v1/worker/utils.py
+3 -1
@@ -266,6 +266,7 @@ def bind_kv_cache(
266 kv_caches: dict[str, torch.Tensor],
267 forward_context: dict[str, "Attention"],
268 runner_kv_caches: list[torch.Tensor],
269 + num_attn_module: Optional[int] = 1,
270 ) -> None:
271 """
272 Bind the allocated KV cache to both ModelRunner and forward context so
@@ -289,7 +290,8 @@ def bind_kv_cache(
290 # Convert kv_caches dict to a list of tensors in the order of layer_index.
291 index2name = defaultdict(list)
292 for layer_name in kv_caches:
292 - index2name[extract_layer_index(layer_name)].append(layer_name)
293 + index2name[extract_layer_index(layer_name,
294 + num_attn_module)].append(layer_name)
295
296 for layer_index in sorted(index2name.keys()):
297 layer_names = index2name[layer_index]