Yaphabates commited on
Commit
2d98e35
·
verified ·
1 Parent(s): 2ea7180

Add vLLM acceleration plugin (nas_child_vl_vllm package)

Browse files
nas_child_vl_vllm/config.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """NAS-Child-VL model config for vLLM."""
3
+
4
+ from transformers import AutoConfig, Qwen3VLConfig
5
+
6
+
7
+ class NasChildVLConfig(Qwen3VLConfig):
8
+ """Config for NAS-Child-VL model, extending Qwen3VLConfig
9
+ with per-layer NAS configuration."""
10
+
11
+ model_type = "nas-child-vl"
12
+
13
+ # FLA library defaults for hidden_size=2048
14
+ # These match the training configuration exactly.
15
+ DEFAULT_GDN_CONFIG = {
16
+ "num_heads": 6,
17
+ "head_k_dim": 256,
18
+ "head_v_dim": 512, # expand_v=2 -> head_k_dim * 2
19
+ "conv_size": 4,
20
+ }
21
+ DEFAULT_KDA_CONFIG = {
22
+ "num_heads": 16,
23
+ "head_dim": 128,
24
+ "conv_size": 4,
25
+ }
26
+ DEFAULT_MLA_CONFIG = {
27
+ "num_heads": 16,
28
+ "q_lora_rank": 64,
29
+ "qk_rope_head_dim": 64,
30
+ "kv_lora_rank": 512,
31
+ "v_head_dim": 128,
32
+ "qk_nope_head_dim": 128,
33
+ }
34
+
35
+ def __init__(self, nas_layer_config=None, **kwargs):
36
+ super().__init__(**kwargs)
37
+ self.nas_layer_config = nas_layer_config or []
38
+
39
+ def get_layer_attention_type(self, layer_idx: int) -> str:
40
+ if layer_idx < len(self.nas_layer_config):
41
+ return self.nas_layer_config[layer_idx].get(
42
+ "attention_type", "full_attention"
43
+ )
44
+ return "full_attention"
45
+
46
+ def get_layer_config(self, layer_idx: int) -> dict:
47
+ if layer_idx < len(self.nas_layer_config):
48
+ return self.nas_layer_config[layer_idx]
49
+ return {"attention_type": "full_attention", "ffn_type": "ffn"}
50
+
51
+ def get_gdn_config(self, layer_idx: int) -> dict:
52
+ """Get GDN config for a layer, falling back to FLA defaults."""
53
+ lc = self.get_layer_config(layer_idx)
54
+ explicit = lc.get("gdn_config", {})
55
+ result = dict(self.DEFAULT_GDN_CONFIG)
56
+ result.update(explicit)
57
+ return result
58
+
59
+ def get_kda_config(self, layer_idx: int) -> dict:
60
+ """Get KDA config for a layer, falling back to FLA defaults."""
61
+ lc = self.get_layer_config(layer_idx)
62
+ explicit = lc.get("kda_config", {})
63
+ result = dict(self.DEFAULT_KDA_CONFIG)
64
+ result.update(explicit)
65
+ return result
66
+
67
+ def get_mla_config(self, layer_idx: int) -> dict:
68
+ """Get MLA config for a layer, falling back to FLA defaults."""
69
+ lc = self.get_layer_config(layer_idx)
70
+ explicit = lc.get("mla_config", {})
71
+ result = dict(self.DEFAULT_MLA_CONFIG)
72
+ result.update(explicit)
73
+ return result
74
+
75
+ @property
76
+ def layers_block_type(self) -> list[str]:
77
+ """Return block type per layer for get_num_layers_of_type.
78
+
79
+ 'attention' for FULL/SWA/MLA layers, 'mamba' for GDN/KDA layers.
80
+ """
81
+ result = []
82
+ num_layers = getattr(self, "num_hidden_layers", 0)
83
+ if hasattr(self, "text_config"):
84
+ num_layers = getattr(self.text_config, "num_hidden_layers", num_layers)
85
+ for i in range(num_layers):
86
+ attn_type = self.get_layer_attention_type(i)
87
+ if attn_type in ("gdn", "kda"):
88
+ result.append("mamba")
89
+ else:
90
+ result.append("attention")
91
+ return result
92
+
93
+
94
+ AutoConfig.register("nas-child-vl", NasChildVLConfig)
nas_child_vl_vllm/model.py ADDED
@@ -0,0 +1,2022 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Inference-only NAS-Child-VL model for vLLM.
3
+
4
+ NAS-Child-VL is based on Qwen3-VL with per-layer mixed attention:
5
+ FULL / SWA : Standard GQA via vLLM PagedAttention
6
+ GDN : Native GatedDeltaNet (MambaBase, causal_conv1d + chunk ops)
7
+ KDA : Native KimiDeltaAttention (MambaBase, causal_conv1d + chunk ops)
8
+ MLA : fla wrapper with dummy MambaBase state
9
+
10
+ GDN/KDA use MambaBase for proper state management via the V1 engine.
11
+ MLA uses fla wrapper with use_cache=True and a dummy MambaBase state.
12
+ FULL/SWA use standard PagedAttention KV cache.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ from collections.abc import Iterable
19
+ from itertools import islice
20
+
21
+ import torch
22
+ import torch.nn.functional as F
23
+ from einops import rearrange
24
+ from torch import nn
25
+
26
+ from vllm.compilation.decorators import support_torch_compile
27
+ from vllm.config import CacheConfig, ModelConfig, VllmConfig, get_current_vllm_config
28
+ from vllm.distributed import (
29
+ divide,
30
+ get_pp_group,
31
+ get_tensor_model_parallel_rank,
32
+ get_tensor_model_parallel_world_size,
33
+ )
34
+ from vllm.forward_context import ForwardContext, get_forward_context
35
+ from vllm.logger import init_logger
36
+ from vllm.model_executor.layers.activation import SiluAndMul
37
+ from vllm.model_executor.layers.fla.ops import (
38
+ RMSNormGated,
39
+ chunk_gated_delta_rule,
40
+ fused_recurrent_gated_delta_rule,
41
+ )
42
+ from vllm.model_executor.layers.fla.ops.kda import (
43
+ FusedRMSNormGated,
44
+ chunk_kda,
45
+ fused_kda_gate,
46
+ fused_recurrent_kda,
47
+ )
48
+ from vllm.model_executor.layers.fla.ops.l2norm import l2norm_fwd
49
+ from vllm.model_executor.layers.layernorm import RMSNorm
50
+ from vllm.model_executor.layers.linear import (
51
+ ColumnParallelLinear,
52
+ MergedColumnParallelLinear,
53
+ QKVParallelLinear,
54
+ ReplicatedLinear,
55
+ RowParallelLinear,
56
+ )
57
+ from vllm.model_executor.layers.logits_processor import LogitsProcessor
58
+ from vllm.model_executor.layers.mamba.abstract import MambaBase
59
+ from vllm.model_executor.layers.mamba.mamba_utils import (
60
+ MambaStateCopyFunc,
61
+ MambaStateCopyFuncCalculator,
62
+ MambaStateDtypeCalculator,
63
+ MambaStateShapeCalculator,
64
+ )
65
+ from vllm.model_executor.layers.mamba.ops.causal_conv1d import (
66
+ causal_conv1d_fn,
67
+ causal_conv1d_update,
68
+ )
69
+ from vllm.model_executor.layers.quantization import QuantizationConfig
70
+ from vllm.model_executor.layers.rotary_embedding import get_rope
71
+ from vllm.model_executor.layers.vocab_parallel_embedding import (
72
+ ParallelLMHead,
73
+ VocabParallelEmbedding,
74
+ )
75
+ from vllm.model_executor.model_loader.weight_utils import (
76
+ default_weight_loader,
77
+ sharded_weight_loader,
78
+ )
79
+ from vllm.model_executor.utils import set_weight_attrs
80
+ from vllm.sequence import IntermediateTensors
81
+ from nas_child_vl_vllm.config import NasChildVLConfig
82
+ from vllm.utils.torch_utils import direct_register_custom_op
83
+ from vllm.v1.attention.backend import AttentionMetadata
84
+ from vllm.v1.attention.backends.gdn_attn import GDNAttentionMetadata
85
+
86
+ from vllm.model_executor.models.interfaces import HasInnerState, SupportsMultiModal, SupportsPP
87
+ from vllm.model_executor.models.qwen3_vl import (
88
+ Qwen3VLDummyInputsBuilder,
89
+ Qwen3VLForConditionalGeneration,
90
+ Qwen3VLMultiModalProcessor,
91
+ Qwen3VLProcessingInfo,
92
+ Qwen3_VisionTransformer,
93
+ )
94
+ from vllm.model_executor.models.utils import (
95
+ AutoWeightsLoader,
96
+ PPMissingLayer,
97
+ WeightsMapper,
98
+ extract_layer_index,
99
+ make_empty_intermediate_tensors_factory,
100
+ make_layers,
101
+ maybe_prefix,
102
+ )
103
+
104
+ logger = init_logger(__name__)
105
+
106
+ # Page size padded for ALL mamba-like layers (GDN, KDA).
107
+ # Must be >= each layer's actual state size AND a common multiple of every
108
+ # attention layer's real_page_size_bytes so that
109
+ # `unify_kv_cache_spec_page_size` can adjust block_size cleanly.
110
+ # FULL/SWA real_page_size = 16 * 8 * (128+128) * 2 = 65536
111
+ # MLA real_page_size = 16 * 16 * (192+192) * 2 = 196608
112
+ # Choose smallest k * 196608 that is >= 3211264 (original mamba sizing) and
113
+ # also divisible by 65536. 196608 = 3*65536 so any multiple of 196608 already
114
+ # divides 65536-multiples evenly.
115
+ # k = ceil(3211264 / 196608) = 17 -> 17 * 196608 = 3342336
116
+ # 3342336 % 65536 == 0 (51) ✓
117
+ # 3342336 % 196608 == 0 (17) ✓
118
+ _NAS_PAGE_SIZE_PADDED = 3342336
119
+
120
+
121
+ def _get_unified_state_shape(tp_size: int):
122
+ # 🌟 核心修改:将 (dim, 3) 改为 (3, dim),匹配 vLLM 底层 Cache 的约定
123
+ return (
124
+ (3, 6144 // tp_size), # State 0: GDN conv (6144) / KDA conv_q (2048)
125
+ (3, 2048 // tp_size), # State 1: KDA conv_k
126
+ (3, 2048 // tp_size), # State 2: KDA conv_v
127
+ (786432 // tp_size,), # State 3: GDN ssm (6*256*512=786432) / KDA ssm (16*128*128=262144)
128
+ )
129
+
130
+ def _get_unified_state_dtype(model_dtype: torch.dtype):
131
+ return (model_dtype, model_dtype, model_dtype, torch.float32)
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+ # ============================================================================
141
+ # Custom ops for GDN and KDA
142
+ # ============================================================================
143
+
144
+ def nas_gdn_attention_core(
145
+ mixed_qkv: torch.Tensor,
146
+ b: torch.Tensor,
147
+ a: torch.Tensor,
148
+ core_attn_out: torch.Tensor,
149
+ layer_name: str,
150
+ ) -> None:
151
+ forward_context: ForwardContext = get_forward_context()
152
+ self = forward_context.no_compile_layers[layer_name]
153
+ self._forward_core(
154
+ mixed_qkv=mixed_qkv,
155
+ b=b,
156
+ a=a,
157
+ core_attn_out=core_attn_out,
158
+ )
159
+
160
+
161
+ def nas_gdn_attention_core_fake(
162
+ mixed_qkv: torch.Tensor,
163
+ b: torch.Tensor,
164
+ a: torch.Tensor,
165
+ core_attn_out: torch.Tensor,
166
+ layer_name: str,
167
+ ) -> None:
168
+ return
169
+
170
+
171
+ direct_register_custom_op(
172
+ op_name="nas_gdn_attention_core",
173
+ op_func=nas_gdn_attention_core,
174
+ mutates_args=["core_attn_out"],
175
+ fake_impl=nas_gdn_attention_core_fake,
176
+ )
177
+
178
+
179
+ def nas_kda_attention_core(
180
+ q_proj_states: torch.Tensor,
181
+ k_proj_states: torch.Tensor,
182
+ v_proj_states: torch.Tensor,
183
+ g1: torch.Tensor,
184
+ beta: torch.Tensor,
185
+ core_attn_out: torch.Tensor,
186
+ layer_name: str,
187
+ ) -> None:
188
+ forward_context: ForwardContext = get_forward_context()
189
+ self = forward_context.no_compile_layers[layer_name]
190
+ self._forward_core(
191
+ q_proj_states=q_proj_states,
192
+ k_proj_states=k_proj_states,
193
+ v_proj_states=v_proj_states,
194
+ g1=g1,
195
+ beta=beta,
196
+ core_attn_out=core_attn_out,
197
+ )
198
+
199
+
200
+ def nas_kda_attention_core_fake(
201
+ q_proj_states: torch.Tensor,
202
+ k_proj_states: torch.Tensor,
203
+ v_proj_states: torch.Tensor,
204
+ g1: torch.Tensor,
205
+ beta: torch.Tensor,
206
+ core_attn_out: torch.Tensor,
207
+ layer_name: str,
208
+ ) -> None:
209
+ return
210
+
211
+
212
+ direct_register_custom_op(
213
+ op_name="nas_kda_attention_core",
214
+ op_func=nas_kda_attention_core,
215
+ mutates_args=["core_attn_out"],
216
+ fake_impl=nas_kda_attention_core_fake,
217
+ )
218
+
219
+
220
+ # ============================================================================
221
+ # FULL / SWA attention - vLLM PagedAttention
222
+ # ============================================================================
223
+
224
+ class NasAttention(nn.Module):
225
+ """Standard Qwen3-style GQA attention for FULL and SWA layers."""
226
+
227
+ def __init__(
228
+ self,
229
+ hidden_size: int,
230
+ num_heads: int,
231
+ num_kv_heads: int,
232
+ head_dim: int,
233
+ rope_parameters: dict,
234
+ max_position: int,
235
+ rms_norm_eps: float,
236
+ cache_config: CacheConfig | None = None,
237
+ quant_config: QuantizationConfig | None = None,
238
+ prefix: str = "",
239
+ sliding_window: int | None = None,
240
+ ):
241
+ super().__init__()
242
+ self.hidden_size = hidden_size
243
+ tp_size = get_tensor_model_parallel_world_size()
244
+ self.total_num_heads = num_heads
245
+ self.num_heads = self.total_num_heads // tp_size
246
+ self.total_num_kv_heads = num_kv_heads
247
+ self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
248
+ self.head_dim = head_dim
249
+ self.q_size = self.num_heads * self.head_dim
250
+ self.kv_size = self.num_kv_heads * self.head_dim
251
+ self.scaling = self.head_dim ** -0.5
252
+
253
+ self.qkv_proj = QKVParallelLinear(
254
+ hidden_size,
255
+ self.head_dim,
256
+ self.total_num_heads,
257
+ self.total_num_kv_heads,
258
+ bias=False,
259
+ quant_config=quant_config,
260
+ prefix=f"{prefix}.qkv_proj",
261
+ )
262
+ self.o_proj = RowParallelLinear(
263
+ self.total_num_heads * self.head_dim,
264
+ hidden_size,
265
+ bias=False,
266
+ quant_config=quant_config,
267
+ prefix=f"{prefix}.o_proj",
268
+ )
269
+ self.q_norm = RMSNorm(self.head_dim, eps=rms_norm_eps)
270
+ self.k_norm = RMSNorm(self.head_dim, eps=rms_norm_eps)
271
+ self.rotary_emb = get_rope(
272
+ self.head_dim,
273
+ max_position=max_position,
274
+ rope_parameters=rope_parameters,
275
+ )
276
+
277
+ from vllm.model_executor.layers.attention import Attention
278
+ from vllm.v1.attention.backend import AttentionType
279
+ attn_kwargs: dict = dict(
280
+ num_kv_heads=self.num_kv_heads,
281
+ cache_config=cache_config,
282
+ quant_config=quant_config,
283
+ prefix=f"{prefix}.attn",
284
+ attn_type=AttentionType.DECODER,
285
+ )
286
+ if sliding_window is not None:
287
+ attn_kwargs["per_layer_sliding_window"] = sliding_window
288
+ self.attn = Attention(
289
+ self.num_heads, self.head_dim, self.scaling, **attn_kwargs
290
+ )
291
+
292
+ def forward(
293
+ self,
294
+ positions: torch.Tensor,
295
+ hidden_states: torch.Tensor,
296
+ output: torch.Tensor,
297
+ ) -> None:
298
+ qkv, _ = self.qkv_proj(hidden_states)
299
+ q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
300
+ q = self.q_norm(q.reshape(-1, self.num_heads, self.head_dim))
301
+ k = self.k_norm(k.reshape(-1, self.num_kv_heads, self.head_dim))
302
+ q = q.reshape(-1, self.num_heads * self.head_dim)
303
+ k = k.reshape(-1, self.num_kv_heads * self.head_dim)
304
+ q, k = self.rotary_emb(positions, q, k)
305
+ attn_output = self.attn(q, k, v)
306
+ output[:], _ = self.o_proj(attn_output)
307
+
308
+
309
+ # ============================================================================
310
+ # GDN attention - Native implementation with MambaBase
311
+ # ============================================================================
312
+
313
+ class NasGDNAttention(nn.Module, MambaBase):
314
+ """GatedDeltaNet with native causal_conv1d + chunk ops and MambaBase state."""
315
+
316
+ @property
317
+ def mamba_type(self) -> str:
318
+ return "gdn_attention" # 🌟 统一命名
319
+
320
+ def get_state_dtype(self) -> tuple[torch.dtype, ...]:
321
+ return _get_unified_state_dtype(self.model_config.dtype)
322
+
323
+ def get_state_shape(self) -> tuple[tuple[int, ...], ...]:
324
+ return _get_unified_state_shape(self.tp_size)
325
+
326
+
327
+ def get_kv_cache_spec(self, vllm_config: VllmConfig):
328
+ spec = super().get_kv_cache_spec(vllm_config)
329
+ from dataclasses import replace
330
+
331
+ # 🌟 核心修改:如果 MambaBase 返回的 block_size 是 None,则赋予默认的 block_size
332
+ block_size = spec.block_size if spec.block_size is not None else vllm_config.cache_config.block_size
333
+
334
+ return replace(spec, page_size_padded=_NAS_PAGE_SIZE_PADDED, block_size=block_size)
335
+
336
+ def __init__(
337
+ self,
338
+ hidden_size: int,
339
+ layer_idx: int,
340
+ cache_config: CacheConfig | None = None,
341
+ model_config: ModelConfig | None = None,
342
+ quant_config: QuantizationConfig | None = None,
343
+ prefix: str = "",
344
+ **kwargs,
345
+ ):
346
+ super().__init__()
347
+ self.tp_size = get_tensor_model_parallel_world_size()
348
+ self.tp_rank = get_tensor_model_parallel_rank()
349
+ self.hidden_size = hidden_size
350
+ self.model_config = model_config
351
+ self.cache_config = cache_config
352
+ self.layer_idx = layer_idx
353
+ self.prefix = prefix
354
+
355
+ # GDN config: num_heads=6, head_k_dim=256, head_v_dim=512, conv_size=4
356
+ self.num_heads = 6
357
+ self.head_k_dim = 256
358
+ self.head_v_dim = 512 # expand_v=2
359
+ self.conv_kernel_size = 4
360
+
361
+ self.key_dim = self.head_k_dim * self.num_heads
362
+ self.value_dim = self.head_v_dim * self.num_heads
363
+ self.conv_dim = self.key_dim * 2 + self.value_dim
364
+
365
+ # Merged conv1d for Q+K+V
366
+ self.conv1d = ColumnParallelLinear(
367
+ input_size=self.conv_kernel_size,
368
+ output_size=self.conv_dim,
369
+ bias=False,
370
+ prefix=f"{prefix}.conv1d",
371
+ )
372
+ self.conv1d.weight.data = self.conv1d.weight.data.unsqueeze(1)
373
+
374
+ # Input projection: Q, K, V, Z (gate)
375
+ self.in_proj = MergedColumnParallelLinear(
376
+ input_size=hidden_size,
377
+ output_sizes=[self.key_dim, self.key_dim, self.value_dim, self.value_dim],
378
+ bias=False,
379
+ quant_config=quant_config,
380
+ prefix=f"{prefix}.in_proj",
381
+ )
382
+
383
+ # Beta and alpha projections
384
+ self.in_proj_ba = MergedColumnParallelLinear(
385
+ input_size=hidden_size,
386
+ output_sizes=[self.num_heads, self.num_heads],
387
+ bias=False,
388
+ quant_config=quant_config,
389
+ prefix=f"{prefix}.in_proj_ba",
390
+ )
391
+
392
+ # A_log and dt_bias
393
+ self.A_log = nn.Parameter(
394
+ torch.empty(divide(self.num_heads, self.tp_size), dtype=torch.float32)
395
+ )
396
+ set_weight_attrs(self.A_log, {"weight_loader": sharded_weight_loader(0)})
397
+
398
+ self.dt_bias = nn.Parameter(
399
+ torch.empty(divide(self.num_heads, self.tp_size), dtype=torch.float32)
400
+ )
401
+ set_weight_attrs(self.dt_bias, {"weight_loader": sharded_weight_loader(0)})
402
+
403
+ # Output norm and projection
404
+ # Match fla GatedDeltaNet: FusedRMSNormGated(head_v_dim, activation="swish")
405
+ # which computes rms_norm(x) * weight * silu(z) (norm_before_gate=True)
406
+ self.norm = RMSNormGated(
407
+ self.head_v_dim,
408
+ eps=1e-6,
409
+ norm_before_gate=True,
410
+ activation="swish",
411
+ )
412
+ self.out_proj = RowParallelLinear(
413
+ self.value_dim,
414
+ hidden_size,
415
+ bias=False,
416
+ quant_config=quant_config,
417
+ prefix=f"{prefix}.out_proj",
418
+ )
419
+
420
+ compilation_config = get_current_vllm_config().compilation_config
421
+ if prefix in compilation_config.static_forward_context:
422
+ raise ValueError(f"Duplicate layer name: {prefix}")
423
+ compilation_config.static_forward_context[prefix] = self
424
+
425
+ def forward(self, positions, hidden_states, output):
426
+ num_tokens = hidden_states.size(0)
427
+
428
+ # Input projection
429
+ projected, _ = self.in_proj(hidden_states)
430
+ q_proj, k_proj, v_proj, z = projected.split(
431
+ [self.key_dim // self.tp_size,
432
+ self.key_dim // self.tp_size,
433
+ self.value_dim // self.tp_size,
434
+ self.value_dim // self.tp_size],
435
+ dim=-1,
436
+ )
437
+ mixed_qkv = torch.cat([q_proj, k_proj, v_proj], dim=-1)
438
+
439
+ ba, _ = self.in_proj_ba(hidden_states)
440
+ b, a = ba.split([self.num_heads // self.tp_size,
441
+ self.num_heads // self.tp_size], dim=-1)
442
+
443
+ # Core attention via custom op
444
+ core_attn_out = torch.zeros(
445
+ (num_tokens, self.num_heads // self.tp_size, self.head_v_dim),
446
+ dtype=hidden_states.dtype,
447
+ device=hidden_states.device,
448
+ )
449
+
450
+ torch.ops.vllm.nas_gdn_attention_core(
451
+ mixed_qkv,
452
+ b,
453
+ a,
454
+ core_attn_out,
455
+ self.prefix,
456
+ )
457
+
458
+ # Output: norm(out, gate) -> o_proj
459
+ z_reshaped = z.reshape(-1, self.head_v_dim)
460
+ core_attn_out = core_attn_out.reshape(-1, self.head_v_dim)
461
+ core_attn_out = self.norm(core_attn_out, z_reshaped)
462
+ core_attn_out = core_attn_out.reshape(num_tokens, -1)
463
+ output[:], _ = self.out_proj(core_attn_out)
464
+
465
+ def _forward_core(
466
+ self,
467
+ mixed_qkv: torch.Tensor,
468
+ b: torch.Tensor,
469
+ a: torch.Tensor,
470
+ core_attn_out: torch.Tensor,
471
+ ):
472
+ forward_context = get_forward_context()
473
+ attn_metadata: AttentionMetadata = forward_context.attn_metadata
474
+
475
+ if attn_metadata is None:
476
+ # V1 profile run
477
+ return
478
+
479
+ assert isinstance(attn_metadata, dict)
480
+ attn_metadata = attn_metadata[self.prefix]
481
+ assert isinstance(attn_metadata, GDNAttentionMetadata)
482
+ has_initial_state = attn_metadata.has_initial_state
483
+ non_spec_query_start_loc = attn_metadata.non_spec_query_start_loc
484
+ non_spec_state_indices_tensor = attn_metadata.non_spec_state_indices_tensor
485
+ num_actual_tokens = attn_metadata.num_actual_tokens
486
+ self_kv_cache = self.kv_cache[forward_context.virtual_engine]
487
+ conv_state = self_kv_cache[0].transpose(-1, -2)
488
+ # ssm_state = self_kv_cache[1]
489
+ ssm_state = self_kv_cache[3].view(
490
+ -1,
491
+ self.num_heads // self.tp_size,
492
+ self.head_v_dim, # 先 V (512)
493
+ self.head_k_dim # 后 K (256)
494
+ )
495
+
496
+
497
+
498
+ mixed_qkv = mixed_qkv[:num_actual_tokens]
499
+ b = b[:num_actual_tokens]
500
+ a = a[:num_actual_tokens]
501
+
502
+ # 1. Convolution
503
+ conv_weights = self.conv1d.weight.view(
504
+ self.conv1d.weight.size(0), self.conv1d.weight.size(2)
505
+ )
506
+
507
+ if attn_metadata.num_prefills > 0:
508
+ mixed_qkv_T = mixed_qkv.transpose(0, 1)
509
+ mixed_qkv = causal_conv1d_fn(
510
+ mixed_qkv_T,
511
+ conv_weights,
512
+ self.conv1d.bias,
513
+ activation="silu",
514
+ conv_states=conv_state,
515
+ has_initial_state=has_initial_state,
516
+ cache_indices=non_spec_state_indices_tensor,
517
+ query_start_loc=non_spec_query_start_loc,
518
+ metadata=attn_metadata,
519
+ ).transpose(0, 1)
520
+ else:
521
+ decode_conv_indices = non_spec_state_indices_tensor[:num_actual_tokens]
522
+ mixed_qkv = causal_conv1d_update(
523
+ mixed_qkv,
524
+ conv_state,
525
+ conv_weights,
526
+ self.conv1d.bias,
527
+ activation="silu",
528
+ conv_state_indices=decode_conv_indices,
529
+ validate_data=True,
530
+ )
531
+
532
+ # Split Q, K, V
533
+ query, key, value = torch.split(
534
+ mixed_qkv,
535
+ [self.key_dim // self.tp_size,
536
+ self.key_dim // self.tp_size,
537
+ self.value_dim // self.tp_size],
538
+ dim=-1,
539
+ )
540
+ query = rearrange(query, "l (h d) -> 1 l h d", d=self.head_k_dim)
541
+ key = rearrange(key, "l (h d) -> 1 l h d", d=self.head_k_dim)
542
+ value = rearrange(value, "l (h d) -> 1 l h d", d=self.head_v_dim)
543
+
544
+ # 2. Compute gating: g = -exp(A_log) * softplus(a + dt_bias), beta = sigmoid(b)
545
+ num_heads_local = self.num_heads // self.tp_size
546
+ # g: (1, L, H) -> expand to head_k_dim
547
+ a_float = a.float()
548
+ dt = a_float + self.dt_bias.unsqueeze(0)
549
+ softplus_dt = torch.where(
550
+ dt > 20.0, dt, torch.log1p(torch.exp(dt))
551
+ )
552
+ g = -torch.exp(self.A_log.float()).unsqueeze(0) * softplus_dt
553
+ g = g.unsqueeze(0) # (1, L, H)
554
+
555
+ beta = b.float().sigmoid()
556
+ beta = beta.unsqueeze(0) # (1, L, H)
557
+
558
+ # 3. Recurrent attention
559
+ if attn_metadata.num_prefills > 0:
560
+ zero_idx = non_spec_state_indices_tensor[~has_initial_state]
561
+ ssm_state[zero_idx] = 0
562
+ initial_state = ssm_state[non_spec_state_indices_tensor].contiguous()
563
+ (
564
+ core_attn_out_non_spec,
565
+ last_recurrent_state,
566
+ ) = chunk_gated_delta_rule(
567
+ q=query,
568
+ k=key,
569
+ v=value,
570
+ g=g,
571
+ beta=beta,
572
+ initial_state=initial_state,
573
+ output_final_state=True,
574
+ cu_seqlens=non_spec_query_start_loc,
575
+ use_qk_l2norm_in_kernel=True,
576
+ )
577
+ ssm_state[non_spec_state_indices_tensor] = last_recurrent_state.to(
578
+ ssm_state.dtype
579
+ )
580
+ else:
581
+ (
582
+ core_attn_out_non_spec,
583
+ last_recurrent_state,
584
+ ) = fused_recurrent_gated_delta_rule(
585
+ q=query,
586
+ k=key,
587
+ v=value,
588
+ g=g,
589
+ beta=beta,
590
+ initial_state=ssm_state,
591
+ inplace_final_state=True,
592
+ cu_seqlens=non_spec_query_start_loc[:attn_metadata.num_decodes + 1],
593
+ ssm_state_indices=non_spec_state_indices_tensor,
594
+ use_qk_l2norm_in_kernel=True,
595
+ )
596
+
597
+ core_attn_out[:num_actual_tokens] = core_attn_out_non_spec.squeeze(0)[:num_actual_tokens]
598
+
599
+
600
+ # ============================================================================
601
+ # KDA attention - Native implementation with MambaBase
602
+ # ============================================================================
603
+
604
+ class NasKDAAttention(nn.Module, MambaBase):
605
+ """KimiDeltaAttention with native causal_conv1d + chunk ops and MambaBase state."""
606
+
607
+ @property
608
+ def mamba_type(self) -> str:
609
+ return "gdn_attention" # 🌟 统一命名
610
+
611
+ def get_state_dtype(self) -> tuple[torch.dtype, ...]:
612
+ return _get_unified_state_dtype(self.model_config.dtype)
613
+
614
+ def get_state_shape(self) -> tuple[tuple[int, ...], ...]:
615
+ return _get_unified_state_shape(self.tp_size)
616
+
617
+ def get_kv_cache_spec(self, vllm_config: VllmConfig):
618
+ spec = super().get_kv_cache_spec(vllm_config)
619
+ from dataclasses import replace
620
+
621
+ # 🌟 核心修改:如果 MambaBase 返回的 block_size 是 None,则赋予默认的 block_size
622
+ block_size = spec.block_size if spec.block_size is not None else vllm_config.cache_config.block_size
623
+
624
+ return replace(spec, page_size_padded=_NAS_PAGE_SIZE_PADDED, block_size=block_size)
625
+
626
+ def __init__(
627
+ self,
628
+ hidden_size: int,
629
+ layer_idx: int,
630
+ cache_config: CacheConfig | None = None,
631
+ model_config: ModelConfig | None = None,
632
+ quant_config: QuantizationConfig | None = None,
633
+ prefix: str = "",
634
+ **kwargs,
635
+ ):
636
+ super().__init__()
637
+ self.tp_size = get_tensor_model_parallel_world_size()
638
+ self.tp_rank = get_tensor_model_parallel_rank()
639
+ self.hidden_size = hidden_size
640
+ self.model_config = model_config
641
+ self.cache_config = cache_config
642
+ self.layer_idx = layer_idx
643
+ self.prefix = prefix
644
+
645
+ # KDA config: num_heads=16, head_dim=128, conv_size=4
646
+ self.num_heads = 16
647
+ self.head_dim = 128
648
+ self.conv_size = 4
649
+ assert self.num_heads % self.tp_size == 0
650
+ self.local_num_heads = divide(self.num_heads, self.tp_size)
651
+
652
+ projection_size = self.head_dim * self.num_heads
653
+
654
+ # Q, K, V projections
655
+ self.q_proj = ColumnParallelLinear(
656
+ self.hidden_size,
657
+ projection_size,
658
+ bias=False,
659
+ quant_config=quant_config,
660
+ prefix=f"{prefix}.q_proj",
661
+ )
662
+ self.k_proj = ColumnParallelLinear(
663
+ self.hidden_size,
664
+ projection_size,
665
+ bias=False,
666
+ quant_config=quant_config,
667
+ prefix=f"{prefix}.k_proj",
668
+ )
669
+ self.v_proj = ColumnParallelLinear(
670
+ self.hidden_size,
671
+ projection_size,
672
+ bias=False,
673
+ quant_config=quant_config,
674
+ prefix=f"{prefix}.v_proj",
675
+ )
676
+
677
+ # Convolutions for Q, K, V
678
+ self.q_conv1d = ColumnParallelLinear(
679
+ input_size=self.conv_size,
680
+ output_size=projection_size,
681
+ bias=False,
682
+ params_dtype=torch.float32,
683
+ prefix=f"{prefix}.q_conv1d",
684
+ )
685
+ self.k_conv1d = ColumnParallelLinear(
686
+ input_size=self.conv_size,
687
+ output_size=projection_size,
688
+ bias=False,
689
+ params_dtype=torch.float32,
690
+ prefix=f"{prefix}.k_conv1d",
691
+ )
692
+ self.v_conv1d = ColumnParallelLinear(
693
+ input_size=self.conv_size,
694
+ output_size=projection_size,
695
+ bias=False,
696
+ params_dtype=torch.float32,
697
+ prefix=f"{prefix}.v_conv1d",
698
+ )
699
+ # unsqueeze to fit conv1d weights shape
700
+ self.q_conv1d.weight.data = self.q_conv1d.weight.data.unsqueeze(1)
701
+ self.k_conv1d.weight.data = self.k_conv1d.weight.data.unsqueeze(1)
702
+ self.v_conv1d.weight.data = self.v_conv1d.weight.data.unsqueeze(1)
703
+
704
+ # f_gate: f_proj.0 (ReplicatedLinear) + f_proj.1 (ColumnParallelLinear)
705
+ self.f_proj_0 = ReplicatedLinear(
706
+ self.hidden_size,
707
+ self.head_dim,
708
+ bias=False,
709
+ quant_config=quant_config,
710
+ prefix=f"{prefix}.f_proj.0",
711
+ )
712
+ self.f_proj_1 = ColumnParallelLinear(
713
+ self.head_dim,
714
+ projection_size,
715
+ bias=False,
716
+ quant_config=quant_config,
717
+ prefix=f"{prefix}.f_proj.1",
718
+ )
719
+
720
+ # dt_bias
721
+ self.dt_bias = nn.Parameter(
722
+ torch.empty(divide(projection_size, self.tp_size), dtype=torch.float32)
723
+ )
724
+ set_weight_attrs(self.dt_bias, {"weight_loader": sharded_weight_loader(0)})
725
+
726
+ # A_log
727
+ self.A_log = nn.Parameter(
728
+ torch.empty(1, 1, self.local_num_heads, 1, dtype=torch.float32)
729
+ )
730
+ set_weight_attrs(self.A_log, {"weight_loader": sharded_weight_loader(2)})
731
+
732
+ # beta projection
733
+ self.b_proj = ColumnParallelLinear(
734
+ self.hidden_size,
735
+ self.num_heads,
736
+ bias=False,
737
+ quant_config=quant_config,
738
+ prefix=f"{prefix}.b_proj",
739
+ )
740
+
741
+ # output_gate: g_proj.0 (ReplicatedLinear) + g_proj.1 (ColumnParallelLinear, bias=True)
742
+ self.g_proj_0 = ReplicatedLinear(
743
+ self.hidden_size,
744
+ self.head_dim,
745
+ bias=False,
746
+ quant_config=quant_config,
747
+ prefix=f"{prefix}.g_proj.0",
748
+ )
749
+ self.g_proj_1 = ColumnParallelLinear(
750
+ self.head_dim,
751
+ projection_size,
752
+ bias=True,
753
+ quant_config=quant_config,
754
+ prefix=f"{prefix}.g_proj.1",
755
+ )
756
+
757
+ # Output norm and projection
758
+ self.o_norm = FusedRMSNormGated(
759
+ self.head_dim, eps=1e-6, activation="sigmoid"
760
+ )
761
+ self.o_proj = RowParallelLinear(
762
+ projection_size,
763
+ self.hidden_size,
764
+ bias=False,
765
+ quant_config=quant_config,
766
+ prefix=f"{prefix}.o_proj",
767
+ )
768
+
769
+ compilation_config = get_current_vllm_config().compilation_config
770
+ if prefix in compilation_config.static_forward_context:
771
+ raise ValueError(f"Duplicate layer name: {prefix}")
772
+ compilation_config.static_forward_context[prefix] = self
773
+
774
+ def forward(self, positions, hidden_states, output):
775
+ num_tokens = hidden_states.size(0)
776
+
777
+ # Project Q, K, V
778
+ q = self.q_proj(hidden_states)[0]
779
+ k = self.k_proj(hidden_states)[0]
780
+ v = self.v_proj(hidden_states)[0]
781
+
782
+ # Beta
783
+ beta = self.b_proj(hidden_states)[0].float().sigmoid()
784
+
785
+ # f_gate
786
+ g1 = self.f_proj_1(self.f_proj_0(hidden_states)[0])[0]
787
+ g1 = fused_kda_gate(g1, self.A_log, self.head_dim, g_bias=self.dt_bias)
788
+ beta = beta.unsqueeze(0)
789
+ g1 = g1.unsqueeze(0)
790
+
791
+ # Output gate
792
+ g_proj_states = self.g_proj_1(self.g_proj_0(hidden_states)[0])[0]
793
+ g2 = rearrange(g_proj_states, "... (h d) -> ... h d", d=self.head_dim)
794
+
795
+ # Core attention via custom op
796
+ core_attn_out = torch.zeros(
797
+ (1, num_tokens, self.local_num_heads, self.head_dim),
798
+ dtype=hidden_states.dtype,
799
+ device=hidden_states.device,
800
+ )
801
+ torch.ops.vllm.nas_kda_attention_core(
802
+ q,
803
+ k,
804
+ v,
805
+ g1,
806
+ beta,
807
+ core_attn_out,
808
+ self.prefix,
809
+ )
810
+
811
+ # Output: o_norm(core_out, output_gate) -> o_proj
812
+ core_attn_out = self.o_norm(core_attn_out, g2)
813
+ core_attn_out = rearrange(core_attn_out, "1 n h d -> n (h d)")
814
+ output[:] = self.o_proj(core_attn_out)[0]
815
+
816
+ def _forward_core(
817
+ self,
818
+ q_proj_states: torch.Tensor,
819
+ k_proj_states: torch.Tensor,
820
+ v_proj_states: torch.Tensor,
821
+ g1: torch.Tensor,
822
+ beta: torch.Tensor,
823
+ core_attn_out: torch.Tensor,
824
+ ) -> None:
825
+ forward_context = get_forward_context()
826
+ attn_metadata: AttentionMetadata = forward_context.attn_metadata
827
+
828
+ if attn_metadata is None:
829
+ # V1 profile run
830
+ return
831
+
832
+ assert isinstance(attn_metadata, dict)
833
+ attn_metadata = attn_metadata[self.prefix]
834
+ assert isinstance(attn_metadata, GDNAttentionMetadata)
835
+ has_initial_state = attn_metadata.has_initial_state
836
+ non_spec_query_start_loc = attn_metadata.non_spec_query_start_loc
837
+ non_spec_state_indices_tensor = attn_metadata.non_spec_state_indices_tensor
838
+ num_actual_tokens = attn_metadata.num_actual_tokens
839
+
840
+ constant_caches = self.kv_cache[forward_context.virtual_engine]
841
+
842
+ # 🌟 核心修改:因为 6144 现在在最后一维,所以切片要放在最后一维 [..., :, :2048]
843
+ conv_state_q = constant_caches[0][..., :, :2048 // self.tp_size].transpose(-1, -2)
844
+ conv_state_k = constant_caches[1].transpose(-1, -2)
845
+ conv_state_v = constant_caches[2].transpose(-1, -2)
846
+
847
+ # recurrent_state 只需要 262144,切片后 reshape
848
+ recurrent_state = constant_caches[3][..., :262144 // self.tp_size].view(
849
+ -1, self.local_num_heads, self.head_dim, self.head_dim
850
+ )
851
+
852
+ q_proj_states = q_proj_states[:num_actual_tokens]
853
+ k_proj_states = k_proj_states[:num_actual_tokens]
854
+ v_proj_states = v_proj_states[:num_actual_tokens]
855
+ g1 = g1[:, :num_actual_tokens]
856
+ beta = beta[:, :num_actual_tokens]
857
+
858
+ # (conv_state_q, conv_state_k, conv_state_v, recurrent_state) = constant_caches
859
+
860
+
861
+ q_conv_weights = self.q_conv1d.weight.view(
862
+ self.q_conv1d.weight.size(0), self.q_conv1d.weight.size(2)
863
+ )
864
+ k_conv_weights = self.k_conv1d.weight.view(
865
+ self.k_conv1d.weight.size(0), self.k_conv1d.weight.size(2)
866
+ )
867
+ v_conv_weights = self.v_conv1d.weight.view(
868
+ self.v_conv1d.weight.size(0), self.v_conv1d.weight.size(2)
869
+ )
870
+
871
+ if attn_metadata.num_prefills > 0:
872
+ q_proj_states = q_proj_states.transpose(0, 1)
873
+ k_proj_states = k_proj_states.transpose(0, 1)
874
+ v_proj_states = v_proj_states.transpose(0, 1)
875
+ q = causal_conv1d_fn(
876
+ q_proj_states,
877
+ q_conv_weights,
878
+ self.q_conv1d.bias,
879
+ activation="silu",
880
+ conv_states=conv_state_q,
881
+ has_initial_state=has_initial_state,
882
+ cache_indices=non_spec_state_indices_tensor,
883
+ query_start_loc=non_spec_query_start_loc,
884
+ metadata=attn_metadata,
885
+ ).transpose(0, 1)
886
+ k = causal_conv1d_fn(
887
+ k_proj_states,
888
+ k_conv_weights,
889
+ self.k_conv1d.bias,
890
+ activation="silu",
891
+ conv_states=conv_state_k,
892
+ has_initial_state=has_initial_state,
893
+ cache_indices=non_spec_state_indices_tensor,
894
+ query_start_loc=non_spec_query_start_loc,
895
+ metadata=attn_metadata,
896
+ ).transpose(0, 1)
897
+ v = causal_conv1d_fn(
898
+ v_proj_states,
899
+ v_conv_weights,
900
+ self.v_conv1d.bias,
901
+ activation="silu",
902
+ conv_states=conv_state_v,
903
+ has_initial_state=has_initial_state,
904
+ cache_indices=non_spec_state_indices_tensor,
905
+ query_start_loc=non_spec_query_start_loc,
906
+ metadata=attn_metadata,
907
+ ).transpose(0, 1)
908
+ else:
909
+ decode_conv_indices = non_spec_state_indices_tensor[:num_actual_tokens]
910
+ q = causal_conv1d_update(
911
+ q_proj_states,
912
+ conv_state_q,
913
+ q_conv_weights,
914
+ self.q_conv1d.bias,
915
+ activation="silu",
916
+ conv_state_indices=decode_conv_indices,
917
+ validate_data=True,
918
+ )
919
+ k = causal_conv1d_update(
920
+ k_proj_states,
921
+ conv_state_k,
922
+ k_conv_weights,
923
+ self.k_conv1d.bias,
924
+ activation="silu",
925
+ conv_state_indices=decode_conv_indices,
926
+ validate_data=True,
927
+ )
928
+ v = causal_conv1d_update(
929
+ v_proj_states,
930
+ conv_state_v,
931
+ v_conv_weights,
932
+ self.v_conv1d.bias,
933
+ activation="silu",
934
+ conv_state_indices=decode_conv_indices,
935
+ validate_data=True,
936
+ )
937
+
938
+ q, k, v = map(
939
+ lambda x: rearrange(x, "n (h d) -> 1 n h d", d=self.head_dim), (q, k, v)
940
+ )
941
+
942
+ if attn_metadata.num_prefills > 0:
943
+ zero_idx = non_spec_state_indices_tensor[~has_initial_state]
944
+ recurrent_state[zero_idx] = 0
945
+ initial_state = recurrent_state[non_spec_state_indices_tensor].contiguous()
946
+ (
947
+ core_attn_out_non_spec,
948
+ last_recurrent_state,
949
+ ) = chunk_kda(
950
+ q=q,
951
+ k=k,
952
+ v=v,
953
+ g=g1,
954
+ beta=beta,
955
+ initial_state=initial_state,
956
+ output_final_state=True,
957
+ use_qk_l2norm_in_kernel=True,
958
+ cu_seqlens=non_spec_query_start_loc,
959
+ )
960
+ recurrent_state[non_spec_state_indices_tensor] = last_recurrent_state
961
+ else:
962
+ (
963
+ core_attn_out_non_spec,
964
+ last_recurrent_state,
965
+ ) = fused_recurrent_kda(
966
+ q=q,
967
+ k=k,
968
+ v=v,
969
+ g=g1,
970
+ beta=beta,
971
+ initial_state=recurrent_state,
972
+ use_qk_l2norm_in_kernel=True,
973
+ cu_seqlens=non_spec_query_start_loc[:attn_metadata.num_decodes + 1],
974
+ ssm_state_indices=non_spec_state_indices_tensor,
975
+ )
976
+ core_attn_out[0, :num_actual_tokens] = core_attn_out_non_spec[0, :num_actual_tokens]
977
+
978
+
979
+ # ============================================================================
980
+ # MLA attention - Native vLLM PagedAttention (DeepSeek V2 pattern)
981
+ # ============================================================================
982
+
983
+ # class NasMLAAttention(nn.Module):
984
+ # """MultiheadLatentAttention with LoRA-compressed Q/KV projections + PagedAttention.
985
+
986
+ # Architecture (matching fla MLA exactly):
987
+ # - Q: hidden → q_lora_rank → RMSNorm → num_heads * qk_head_dim
988
+ # - K_rope: hidden → qk_rope_head_dim (shared across heads)
989
+ # - KV: hidden → kv_lora_rank → RMSNorm → num_heads * (qk_nope_head_dim + v_head_dim)
990
+ # - Attention: standard causal attention via vLLM PagedAttention
991
+ # - Output: num_heads * v_head_dim → hidden
992
+ # """
993
+
994
+ # def __init__(
995
+ # self,
996
+ # hidden_size: int,
997
+ # layer_idx: int,
998
+ # cache_config: CacheConfig | None = None,
999
+ # model_config: ModelConfig | None = None,
1000
+ # quant_config: QuantizationConfig | None = None,
1001
+ # prefix: str = "",
1002
+ # **kwargs,
1003
+ # ):
1004
+ # super().__init__()
1005
+ # self.hidden_size = hidden_size
1006
+ # tp_size = get_tensor_model_parallel_world_size()
1007
+
1008
+ # # MLA dimensions
1009
+ # self.num_heads = 16
1010
+ # self.q_lora_rank = 64
1011
+ # self.qk_rope_head_dim = 64
1012
+ # self.kv_lora_rank = 512
1013
+ # self.v_head_dim = 128
1014
+ # self.qk_nope_head_dim = 128
1015
+ # self.qk_head_dim = self.qk_nope_head_dim + self.qk_rope_head_dim # 192
1016
+
1017
+ # assert self.num_heads % tp_size == 0
1018
+ # self.num_local_heads = self.num_heads // tp_size
1019
+ # self.scaling = self.qk_head_dim ** -0.5
1020
+
1021
+ # # Q projection: hidden → q_lora_rank → RMSNorm → num_heads * qk_head_dim
1022
+ # self.q_a_proj = ReplicatedLinear(
1023
+ # self.hidden_size,
1024
+ # self.q_lora_rank,
1025
+ # bias=False,
1026
+ # quant_config=quant_config,
1027
+ # prefix=f"{prefix}.q_a_proj",
1028
+ # )
1029
+ # self.q_a_layernorm = RMSNorm(self.q_lora_rank, eps=1e-6)
1030
+ # self.q_b_proj = ColumnParallelLinear(
1031
+ # self.q_lora_rank,
1032
+ # self.num_heads * self.qk_head_dim,
1033
+ # bias=False,
1034
+ # quant_config=quant_config,
1035
+ # prefix=f"{prefix}.q_b_proj",
1036
+ # )
1037
+
1038
+ # # KV projection: hidden → kv_lora_rank → RMSNorm → num_heads * (nope + v)
1039
+ # self.kv_a_proj = ReplicatedLinear(
1040
+ # self.hidden_size,
1041
+ # self.kv_lora_rank,
1042
+ # bias=False,
1043
+ # quant_config=quant_config,
1044
+ # prefix=f"{prefix}.kv_a_proj",
1045
+ # )
1046
+ # self.kv_a_layernorm = RMSNorm(self.kv_lora_rank, eps=1e-6)
1047
+ # self.kv_b_proj = ColumnParallelLinear(
1048
+ # self.kv_lora_rank,
1049
+ # self.num_heads * (self.qk_nope_head_dim + self.v_head_dim),
1050
+ # bias=False,
1051
+ # quant_config=quant_config,
1052
+ # prefix=f"{prefix}.kv_b_proj",
1053
+ # )
1054
+
1055
+ # # K rope projection (shared across heads, single "head")
1056
+ # self.k_rope_proj = ReplicatedLinear(
1057
+ # self.hidden_size,
1058
+ # self.qk_rope_head_dim,
1059
+ # bias=False,
1060
+ # quant_config=quant_config,
1061
+ # prefix=f"{prefix}.k_rope_proj",
1062
+ # )
1063
+
1064
+ # # Output projection
1065
+ # self.o_proj = RowParallelLinear(
1066
+ # self.num_heads * self.v_head_dim,
1067
+ # self.hidden_size,
1068
+ # bias=False,
1069
+ # quant_config=quant_config,
1070
+ # prefix=f"{prefix}.o_proj",
1071
+ # )
1072
+
1073
+ # # Standard 1D RoPE (not mrope) for the rope portion
1074
+ # self.rotary_emb = get_rope(
1075
+ # self.qk_rope_head_dim,
1076
+ # max_position=262144,
1077
+ # rope_parameters={"rope_type": "default"},
1078
+ # is_neox_style=False,
1079
+ # )
1080
+
1081
+ # # PagedAttention
1082
+ # from vllm.model_executor.layers.attention import Attention
1083
+ # from vllm.v1.attention.backend import AttentionType
1084
+ # self.attn = Attention(
1085
+ # self.num_local_heads,
1086
+ # self.qk_head_dim,
1087
+ # self.scaling,
1088
+ # num_kv_heads=self.num_local_heads,
1089
+ # cache_config=cache_config,
1090
+ # quant_config=quant_config,
1091
+ # prefix=f"{prefix}.attn",
1092
+ # attn_type=AttentionType.DECODER,
1093
+ # )
1094
+
1095
+ # def forward(self, positions, hidden_states, output):
1096
+ # # Q: hidden → compress → norm → expand
1097
+ # q = self.q_a_proj(hidden_states)[0]
1098
+ # q = self.q_a_layernorm(q)
1099
+ # q = self.q_b_proj(q)[0].view(-1, self.num_local_heads, self.qk_head_dim)
1100
+ # q_nope, q_pe = q.split(
1101
+ # [self.qk_nope_head_dim, self.qk_rope_head_dim], dim=-1
1102
+ # )
1103
+
1104
+ # # KV: hidden → compress → norm → expand → split(k_nope, v)
1105
+ # kv = self.kv_a_proj(hidden_states)[0]
1106
+ # kv = self.kv_a_layernorm(kv)
1107
+ # kv = self.kv_b_proj(kv)[0].view(
1108
+ # -1, self.num_local_heads, self.qk_nope_head_dim + self.v_head_dim
1109
+ # )
1110
+ # k_nope, v = kv.split([self.qk_nope_head_dim, self.v_head_dim], dim=-1)
1111
+
1112
+ # # K rope (shared across heads)
1113
+ # k_pe = self.k_rope_proj(hidden_states)[0] # [N, 64]
1114
+ # k_pe = k_pe.unsqueeze(1) # [N, 1, 64]
1115
+
1116
+ # # Apply RoPE (need 1D positions; Qwen3-VL passes [3, N] for mrope)
1117
+ # pos_1d = positions[0] if positions.ndim == 2 else positions
1118
+ # q_pe, k_pe = self.rotary_emb(pos_1d, q_pe, k_pe)
1119
+
1120
+ # # Assemble full Q and K
1121
+ # q[..., self.qk_nope_head_dim:] = q_pe
1122
+ # k = torch.empty_like(q)
1123
+ # k[..., :self.qk_nope_head_dim] = k_nope
1124
+ # k[..., self.qk_nope_head_dim:] = k_pe # broadcasts [N,1,64] → [N,H,64]
1125
+
1126
+ # # Pad V to qk_head_dim for PagedAttention KV cache alignment
1127
+ # v = F.pad(v, [0, self.qk_head_dim - self.v_head_dim], value=0)
1128
+ # v = v.view(-1, self.num_local_heads * self.qk_head_dim)
1129
+
1130
+ # # PagedAttention (q is [N, H, D], k is [N, H, D], v is [N, H*D])
1131
+ # attn_output = self.attn(q, k, v)
1132
+
1133
+ # # Strip V padding and project output
1134
+ # attn_output = attn_output.view(
1135
+ # -1, self.num_local_heads, self.qk_head_dim
1136
+ # )[..., :self.v_head_dim].reshape(-1, self.num_local_heads * self.v_head_dim)
1137
+ # output[:], _ = self.o_proj(attn_output)
1138
+
1139
+ class NasMLAAttention(nn.Module):
1140
+ def __init__(
1141
+ self,
1142
+ hidden_size: int,
1143
+ num_heads: int,
1144
+ layer_idx: int,
1145
+ cache_config: CacheConfig | None = None,
1146
+ model_config: ModelConfig | None = None,
1147
+ quant_config: QuantizationConfig | None = None,
1148
+ prefix: str = "",
1149
+ rope_parameters: dict | None = None,
1150
+ **kwargs,
1151
+ ):
1152
+ super().__init__()
1153
+ self.hidden_size = hidden_size
1154
+ tp_size = get_tensor_model_parallel_world_size()
1155
+
1156
+ # 🌟 从 model_config.hf_config 动态获取参数,提供默认值以防 config 中没有
1157
+ hf_config = model_config.hf_config
1158
+ self.num_heads = num_heads
1159
+ self.q_lora_rank = getattr(hf_config, "q_lora_rank", 64)
1160
+ self.qk_rope_head_dim = getattr(hf_config, "qk_rope_head_dim", 64)
1161
+ self.kv_lora_rank = getattr(hf_config, "kv_lora_rank", 512)
1162
+ self.v_head_dim = getattr(hf_config, "v_head_dim", 128)
1163
+ self.qk_nope_head_dim = getattr(hf_config, "qk_nope_head_dim", 128)
1164
+
1165
+ self.qk_head_dim = self.qk_nope_head_dim + self.qk_rope_head_dim
1166
+
1167
+ assert self.num_heads % tp_size == 0
1168
+ self.num_local_heads = self.num_heads // tp_size
1169
+ self.scaling = self.qk_head_dim ** -0.5
1170
+
1171
+ # 1. 严格对齐 q_proj (Sequential: Linear -> RMSNorm -> Linear)
1172
+ if self.q_lora_rank is not None:
1173
+ self.q_proj = nn.ModuleList([
1174
+ ReplicatedLinear(self.hidden_size, self.q_lora_rank, bias=False, quant_config=quant_config, prefix=f"{prefix}.q_proj.0"),
1175
+ RMSNorm(self.q_lora_rank, eps=1e-6),
1176
+ ColumnParallelLinear(self.q_lora_rank, self.num_heads * self.qk_head_dim, bias=False, quant_config=quant_config, prefix=f"{prefix}.q_proj.2")
1177
+ ])
1178
+ else:
1179
+ self.q_proj = ColumnParallelLinear(self.hidden_size, self.num_heads * self.qk_head_dim, bias=False, quant_config=quant_config, prefix=f"{prefix}.q_proj")
1180
+
1181
+ # 2. 🌟 严格对齐 kv_proj (Sequential: Linear -> RMSNorm -> Linear)
1182
+ # 这将完美匹配 Checkpoint 中的 kv_proj.0, kv_proj.1, kv_proj.2
1183
+ self.kv_proj = nn.ModuleList([
1184
+ ReplicatedLinear(self.hidden_size, self.kv_lora_rank, bias=False, quant_config=quant_config, prefix=f"{prefix}.kv_proj.0"),
1185
+ RMSNorm(self.kv_lora_rank, eps=1e-6),
1186
+ ColumnParallelLinear(self.kv_lora_rank, self.num_heads * (self.qk_nope_head_dim + self.v_head_dim), bias=False, quant_config=quant_config, prefix=f"{prefix}.kv_proj.2")
1187
+ ])
1188
+
1189
+ # 3. 严格对齐 k_rope
1190
+ self.k_rope = ReplicatedLinear(
1191
+ self.hidden_size,
1192
+ self.qk_rope_head_dim,
1193
+ bias=False,
1194
+ quant_config=quant_config,
1195
+ prefix=f"{prefix}.k_rope"
1196
+ )
1197
+
1198
+ # 4. 严格对齐 o_proj
1199
+ self.o_proj = RowParallelLinear(
1200
+ self.num_heads * self.v_head_dim,
1201
+ self.hidden_size,
1202
+ bias=False,
1203
+ quant_config=quant_config,
1204
+ prefix=f"{prefix}.o_proj"
1205
+ )
1206
+ import copy
1207
+ mla_rope_params = copy.deepcopy(rope_parameters) if rope_parameters else {}
1208
+
1209
+ # 🌟 核心修改:只做纯粹的数学缩放,绝不修改 type 或 rope_type 字段
1210
+ if "mrope_section" in mla_rope_params:
1211
+ orig_sections = mla_rope_params["mrope_section"]
1212
+ target_sum = self.qk_rope_head_dim // 2 # 对于 64 维,这里是 32
1213
+ orig_sum = sum(orig_sections)
1214
+
1215
+ if orig_sum > 0 and orig_sum != target_sum:
1216
+ # 按比例缩放 (例如 64 -> 32,比例为 0.5)
1217
+ scale = target_sum / orig_sum
1218
+ new_sections = [int(s * scale) for s in orig_sections]
1219
+ # 修正由于向下取整可能导致的微小误差,把差值补到最后一个维度
1220
+ if sum(new_sections) != target_sum:
1221
+ new_sections[-1] += target_sum - sum(new_sections)
1222
+
1223
+ mla_rope_params["mrope_section"] = new_sections
1224
+
1225
+ self.rotary_emb = get_rope(
1226
+ self.qk_rope_head_dim,
1227
+ max_position=262144,
1228
+ rope_parameters=mla_rope_params, # 🌟 使用缩放后的参数
1229
+ is_neox_style=False,
1230
+ )
1231
+
1232
+ from vllm.model_executor.layers.attention import Attention
1233
+ from vllm.v1.attention.backend import AttentionType
1234
+ self.attn = Attention(
1235
+ self.num_local_heads,
1236
+ self.qk_head_dim,
1237
+ self.scaling,
1238
+ num_kv_heads=self.num_local_heads,
1239
+ cache_config=cache_config,
1240
+ quant_config=quant_config,
1241
+ prefix=f"{prefix}.attn",
1242
+ attn_type=AttentionType.DECODER,
1243
+ )
1244
+
1245
+ def forward(self, positions, hidden_states, output):
1246
+ # Q
1247
+ if isinstance(self.q_proj, nn.ModuleList):
1248
+ q = self.q_proj[0](hidden_states)[0]
1249
+ q = self.q_proj[1](q)
1250
+ q = self.q_proj[2](q)[0]
1251
+ else:
1252
+ q = self.q_proj(hidden_states)[0]
1253
+ q = q.view(-1, self.num_local_heads, self.qk_head_dim)
1254
+ q_nope, q_pe = q.split([self.qk_nope_head_dim, self.qk_rope_head_dim], dim=-1)
1255
+
1256
+ # KV (依次通过 kv_proj 的 3 层)
1257
+ kv = self.kv_proj[0](hidden_states)[0]
1258
+ kv = self.kv_proj[1](kv)
1259
+ kv = self.kv_proj[2](kv)[0]
1260
+ kv = kv.view(-1, self.num_local_heads, self.qk_nope_head_dim + self.v_head_dim)
1261
+ k_nope, v = kv.split([self.qk_nope_head_dim, self.v_head_dim], dim=-1)
1262
+
1263
+ # K rope
1264
+ k_pe = self.k_rope(hidden_states)[0]
1265
+ k_pe = k_pe.unsqueeze(1)
1266
+
1267
+ # Apply RoPE
1268
+ pos_1d = positions[0] if positions.ndim == 2 else positions
1269
+ # q_pe, k_pe = self.rotary_emb(pos_1d, q_pe, k_pe)
1270
+ q_pe, k_pe = self.rotary_emb(positions, q_pe, k_pe)
1271
+
1272
+ # Assemble full Q and K
1273
+ q[..., self.qk_nope_head_dim:] = q_pe
1274
+ k = torch.empty_like(q)
1275
+ k[..., :self.qk_nope_head_dim] = k_nope
1276
+ k[..., self.qk_nope_head_dim:] = k_pe
1277
+
1278
+ # Pad V to qk_head_dim for PagedAttention KV cache alignment
1279
+ v = F.pad(v, [0, self.qk_head_dim - self.v_head_dim], value=0)
1280
+ v = v.view(-1, self.num_local_heads * self.qk_head_dim)
1281
+
1282
+ # PagedAttention
1283
+ attn_output = self.attn(q, k, v)
1284
+
1285
+ # Strip V padding and project output
1286
+ attn_output = attn_output.view(-1, self.num_local_heads, self.qk_head_dim)[..., :self.v_head_dim].reshape(-1, self.num_local_heads * self.v_head_dim)
1287
+ output[:], _ = self.o_proj(attn_output)
1288
+
1289
+
1290
+ # ============================================================================
1291
+ # MLP
1292
+ # ============================================================================
1293
+
1294
+ class NasMLP(nn.Module):
1295
+ def __init__(self, hidden_size, intermediate_size, quant_config=None, prefix=""):
1296
+ super().__init__()
1297
+ self.gate_up_proj = MergedColumnParallelLinear(
1298
+ hidden_size, [intermediate_size] * 2, bias=False,
1299
+ quant_config=quant_config, prefix=f"{prefix}.gate_up_proj",
1300
+ )
1301
+ self.down_proj = RowParallelLinear(
1302
+ intermediate_size, hidden_size, bias=False,
1303
+ quant_config=quant_config, prefix=f"{prefix}.down_proj",
1304
+ )
1305
+ self.act_fn = SiluAndMul()
1306
+
1307
+ def forward(self, x):
1308
+ gate_up, _ = self.gate_up_proj(x)
1309
+ x = self.act_fn(gate_up)
1310
+ x, _ = self.down_proj(x)
1311
+ return x
1312
+
1313
+ # ============================================================================
1314
+ # Decoder layer
1315
+ # ============================================================================
1316
+
1317
+ class NasDecoderLayer(nn.Module):
1318
+ """NAS decoder layer with per-layer attention type dispatch."""
1319
+
1320
+ def __init__(
1321
+ self,
1322
+ config: NasChildVLConfig,
1323
+ text_config,
1324
+ layer_idx: int,
1325
+ cache_config: CacheConfig | None = None,
1326
+ model_config: ModelConfig | None = None,
1327
+ quant_config: QuantizationConfig | None = None,
1328
+ prefix: str = "",
1329
+ ):
1330
+ super().__init__()
1331
+ self.layer_idx = layer_idx
1332
+ self.hidden_size = text_config.hidden_size
1333
+
1334
+ layer_config = config.get_layer_config(layer_idx)
1335
+ self.attention_type = layer_config.get("attention_type", "full_attention")
1336
+
1337
+ # 🌟 核心修改:把 rope_parameters 的解析提前到所有 Attention 初始化之前
1338
+ rope_parameters = getattr(text_config, "rope_parameters", None)
1339
+ if rope_parameters is None:
1340
+ rope_scaling = getattr(text_config, "rope_scaling", None)
1341
+ rope_parameters = {"rope_type": "default", "factor": 1.0}
1342
+ if rope_scaling:
1343
+ rope_parameters.update(rope_scaling)
1344
+
1345
+ if self.attention_type in ("full_attention", "swa"):
1346
+ sliding_window = None
1347
+ if self.attention_type == "swa":
1348
+ sliding_window = layer_config.get(
1349
+ "sliding_window",
1350
+ getattr(text_config, "sliding_window", 1024),
1351
+ )
1352
+ num_kv_heads = layer_config.get("gqa_num_kv_heads", 0) or text_config.num_key_value_heads
1353
+
1354
+ self.self_attn = NasAttention(
1355
+ hidden_size=text_config.hidden_size,
1356
+ num_heads=text_config.num_attention_heads,
1357
+ num_kv_heads=num_kv_heads,
1358
+ head_dim=getattr(text_config, "head_dim",
1359
+ text_config.hidden_size // text_config.num_attention_heads),
1360
+ rope_parameters=rope_parameters, # 已经提前解析
1361
+ max_position=text_config.max_position_embeddings,
1362
+ rms_norm_eps=text_config.rms_norm_eps,
1363
+ cache_config=cache_config,
1364
+ quant_config=quant_config,
1365
+ prefix=f"{prefix}.self_attn",
1366
+ sliding_window=sliding_window,
1367
+ )
1368
+
1369
+ elif self.attention_type == "gdn":
1370
+ self.self_attn = NasGDNAttention(
1371
+ hidden_size=text_config.hidden_size,
1372
+ layer_idx=layer_idx,
1373
+ cache_config=cache_config,
1374
+ model_config=model_config,
1375
+ quant_config=quant_config,
1376
+ prefix=f"{prefix}.self_attn",
1377
+ )
1378
+
1379
+ elif self.attention_type == "kda":
1380
+ self.self_attn = NasKDAAttention(
1381
+ hidden_size=text_config.hidden_size,
1382
+ layer_idx=layer_idx,
1383
+ cache_config=cache_config,
1384
+ model_config=model_config,
1385
+ quant_config=quant_config,
1386
+ prefix=f"{prefix}.self_attn",
1387
+ )
1388
+
1389
+ elif self.attention_type == "mla":
1390
+ self.self_attn = NasMLAAttention(
1391
+ hidden_size=text_config.hidden_size,
1392
+ layer_idx=layer_idx,
1393
+ cache_config=cache_config,
1394
+ model_config=model_config,
1395
+ quant_config=quant_config,
1396
+ prefix=f"{prefix}.self_attn",
1397
+ num_heads=16,
1398
+ rope_parameters=rope_parameters, # 🌟 新增:把 rope_parameters 传给 MLA
1399
+ )
1400
+
1401
+ else:
1402
+ raise ValueError(f"Unsupported attention type: {self.attention_type}")
1403
+
1404
+ # Determine whether this layer has MLP based on ffn_type from config.
1405
+ # Training code: ffn_type="ffn" -> has MLP + post_attention_layernorm;
1406
+ # ffn_type="no-op" -> no MLP. This applies to ALL attention types
1407
+ # including GDN and KDA (only layer 14 is no-op in current config).
1408
+ ffn_type = layer_config.get("ffn_type", "ffn")
1409
+ self.has_mlp = ffn_type != "no-op"
1410
+
1411
+ self.input_layernorm = RMSNorm(text_config.hidden_size, eps=text_config.rms_norm_eps)
1412
+
1413
+ if self.has_mlp:
1414
+ intermediate_size = layer_config.get("child_intermediate_size", text_config.intermediate_size)
1415
+ self.mlp = NasMLP(
1416
+ hidden_size=text_config.hidden_size,
1417
+ intermediate_size=intermediate_size,
1418
+ quant_config=quant_config,
1419
+ prefix=f"{prefix}.mlp",
1420
+ )
1421
+ self.post_attention_layernorm = RMSNorm(text_config.hidden_size, eps=text_config.rms_norm_eps)
1422
+ else:
1423
+ self.mlp = None
1424
+ self.post_attention_layernorm = None
1425
+
1426
+ logger.info("NAS layer %d: %s (%s), has_mlp: %s", layer_idx, self.attention_type, type(self.self_attn).__name__, self.has_mlp)
1427
+
1428
+ def forward(self, positions, hidden_states, residual):
1429
+ if residual is None:
1430
+ residual = hidden_states
1431
+ hidden_states = self.input_layernorm(hidden_states)
1432
+ else:
1433
+ hidden_states, residual = self.input_layernorm(hidden_states, residual)
1434
+
1435
+ attn_output = torch.empty_like(hidden_states)
1436
+
1437
+ # All attention types use same interface
1438
+ self.self_attn(positions=positions, hidden_states=hidden_states, output=attn_output)
1439
+
1440
+ hidden_states = attn_output
1441
+
1442
+ # Only execute post_norm and mlp for layers that have them
1443
+ # (based on ffn_type from config, not attention_type)
1444
+ if self.has_mlp:
1445
+ hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
1446
+ hidden_states = self.mlp(hidden_states)
1447
+
1448
+ return hidden_states, residual
1449
+
1450
+
1451
+ # ============================================================================
1452
+ # Text model
1453
+ # ============================================================================
1454
+
1455
+ @support_torch_compile(
1456
+ dynamic_arg_dims={
1457
+ "input_ids": 0,
1458
+ # positions is (3, seq_len) for mrope, or (seq_len,) otherwise
1459
+ "positions": -1,
1460
+ "intermediate_tensors": 0,
1461
+ "inputs_embeds": 0,
1462
+ # IntermediateTensors-like dict, same leading dim as inputs_embeds
1463
+ "deepstack_input_embeds": 0,
1464
+ }
1465
+ )
1466
+ class NasChildVLTextModel(nn.Module):
1467
+
1468
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1469
+ super().__init__()
1470
+ config: NasChildVLConfig = vllm_config.model_config.hf_config
1471
+ text_config = config.text_config
1472
+ cache_config = vllm_config.cache_config
1473
+ model_config = vllm_config.model_config
1474
+ quant_config = vllm_config.quant_config
1475
+
1476
+ self.config = text_config
1477
+ self.nas_config = config
1478
+ self.vocab_size = text_config.vocab_size
1479
+
1480
+ if get_pp_group().is_first_rank:
1481
+ self.embed_tokens = VocabParallelEmbedding(
1482
+ text_config.vocab_size, text_config.hidden_size,
1483
+ prefix=f"{prefix}.embed_tokens",
1484
+ )
1485
+ else:
1486
+ self.embed_tokens = PPMissingLayer()
1487
+
1488
+ def get_layer(prefix: str):
1489
+ layer_idx = extract_layer_index(prefix)
1490
+ return NasDecoderLayer(
1491
+ config=config,
1492
+ text_config=text_config,
1493
+ layer_idx=layer_idx,
1494
+ cache_config=cache_config,
1495
+ model_config=model_config,
1496
+ quant_config=quant_config,
1497
+ prefix=prefix,
1498
+ )
1499
+
1500
+ self.start_layer, self.end_layer, self.layers = make_layers(
1501
+ text_config.num_hidden_layers, get_layer, prefix=f"{prefix}.layers",
1502
+ )
1503
+
1504
+ if get_pp_group().is_last_rank:
1505
+ self.norm = RMSNorm(text_config.hidden_size, eps=text_config.rms_norm_eps)
1506
+ else:
1507
+ self.norm = PPMissingLayer()
1508
+
1509
+ self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
1510
+ ["hidden_states", "residual"], text_config.hidden_size
1511
+ )
1512
+
1513
+ def embed_input_ids(self, input_ids):
1514
+ return self.embed_tokens(input_ids)
1515
+
1516
+ def forward(self, input_ids, positions, intermediate_tensors=None,
1517
+ inputs_embeds=None, deepstack_input_embeds=None):
1518
+ if get_pp_group().is_first_rank:
1519
+ hidden_states = inputs_embeds if inputs_embeds is not None else self.embed_input_ids(input_ids)
1520
+ residual = None
1521
+ else:
1522
+ assert intermediate_tensors is not None
1523
+ hidden_states = intermediate_tensors["hidden_states"]
1524
+ residual = intermediate_tensors["residual"]
1525
+
1526
+ for layer_idx, layer in islice(enumerate(self.layers), self.start_layer, self.end_layer):
1527
+ hidden_states, residual = layer(positions=positions, hidden_states=hidden_states, residual=residual)
1528
+ if deepstack_input_embeds is not None and layer_idx in range(len(deepstack_input_embeds)):
1529
+ hidden_states = hidden_states + deepstack_input_embeds[f"deepstack_input_embeds_{layer_idx}"]
1530
+
1531
+ if not get_pp_group().is_last_rank:
1532
+ return IntermediateTensors({"hidden_states": hidden_states, "residual": residual})
1533
+ hidden_states, _ = self.norm(hidden_states, residual)
1534
+ return hidden_states
1535
+
1536
+
1537
+ # ============================================================================
1538
+ # CausalLM wrapper - HasInnerState
1539
+ # (NOT IsHybrid: would conflict with the manual `_NAS_PAGE_SIZE_PADDED` override
1540
+ # in get_kv_cache_spec; HybridAttentionMambaModelConfig auto-pads to a different
1541
+ # value and clashes with MLA's larger page size.)
1542
+ # ============================================================================
1543
+
1544
+ class NasChildVLForCausalLM(nn.Module, HasInnerState, SupportsPP):
1545
+ """NAS-Child-VL causal language model with HasInnerState for mamba state management."""
1546
+
1547
+ has_inner_state = True
1548
+
1549
+ packed_modules_mapping = {
1550
+ "qkv_proj": ["q_proj", "k_proj", "v_proj"],
1551
+ "gate_up_proj": ["gate_proj", "up_proj"],
1552
+ }
1553
+
1554
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1555
+ super().__init__()
1556
+ config: NasChildVLConfig = vllm_config.model_config.hf_config
1557
+ text_config = config.text_config
1558
+ quant_config = vllm_config.quant_config
1559
+
1560
+ self.config = text_config
1561
+ self.nas_config = config
1562
+ self.model = NasChildVLTextModel(
1563
+ vllm_config=vllm_config.with_hf_config(config),
1564
+ prefix=maybe_prefix(prefix, "model"),
1565
+ )
1566
+
1567
+ if get_pp_group().is_last_rank:
1568
+ if getattr(config, "tie_word_embeddings", True):
1569
+ self.lm_head = self.model.embed_tokens
1570
+ else:
1571
+ self.lm_head = ParallelLMHead(
1572
+ text_config.vocab_size, text_config.hidden_size,
1573
+ quant_config=quant_config,
1574
+ prefix=maybe_prefix(prefix, "lm_head"),
1575
+ )
1576
+ else:
1577
+ self.lm_head = PPMissingLayer()
1578
+
1579
+ self.logits_processor = LogitsProcessor(text_config.vocab_size)
1580
+ self.make_empty_intermediate_tensors = self.model.make_empty_intermediate_tensors
1581
+
1582
+ @property
1583
+ def start_layer(self):
1584
+ return self.model.start_layer
1585
+
1586
+ def embed_input_ids(self, input_ids):
1587
+ return self.model.embed_input_ids(input_ids)
1588
+
1589
+ def forward(self, input_ids, positions, intermediate_tensors=None,
1590
+ inputs_embeds=None, deepstack_input_embeds=None, **kwargs):
1591
+ return self.model(input_ids, positions, intermediate_tensors,
1592
+ inputs_embeds, deepstack_input_embeds)
1593
+
1594
+ def compute_logits(self, hidden_states):
1595
+ return self.logits_processor(self.lm_head, hidden_states)
1596
+
1597
+ @classmethod
1598
+ def get_mamba_state_dtype_from_config(
1599
+ cls,
1600
+ vllm_config: VllmConfig,
1601
+ ) -> tuple[torch.dtype, ...]:
1602
+ """Return state dtypes for the largest mamba layer type (GDN 2-state)."""
1603
+ return MambaStateDtypeCalculator.gated_delta_net_state_dtype(
1604
+ vllm_config.model_config.dtype,
1605
+ vllm_config.cache_config.mamba_cache_dtype,
1606
+ vllm_config.cache_config.mamba_ssm_cache_dtype,
1607
+ )
1608
+
1609
+ @classmethod
1610
+ def get_mamba_state_shape_from_config(
1611
+ cls, vllm_config: VllmConfig,
1612
+ ) -> tuple[tuple[int, ...], ...]:
1613
+ """Return state shapes for GDN layers (the reference mamba type)."""
1614
+ tp_size = vllm_config.parallel_config.tensor_parallel_size
1615
+ # GDN: num_heads=6, head_k_dim=256, head_v_dim=512, conv_kernel_size=4
1616
+ return MambaStateShapeCalculator.gated_delta_net_state_shape(
1617
+ tp_size,
1618
+ 6, # num_k_heads
1619
+ 6, # num_v_heads
1620
+ 256, # head_k_dim
1621
+ 512, # head_v_dim
1622
+ 4, # conv_kernel_size
1623
+ )
1624
+
1625
+ @classmethod
1626
+ def get_mamba_state_copy_func(cls) -> tuple[MambaStateCopyFunc, ...]:
1627
+ return MambaStateCopyFuncCalculator.gated_delta_net_state_copy_func()
1628
+
1629
+ def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1630
+ """Load weights with name remapping for all layer types.
1631
+
1632
+ Checkpoint format:
1633
+ - FULL/SWA: q_proj, k_proj, v_proj (stacked to qkv_proj), q_norm, k_norm, o_proj
1634
+ - GDN: native weights loaded into self_attn.XXX directly
1635
+ - KDA: native weights loaded into self_attn.XXX with name remapping
1636
+ - MLA: kv_down_proj.0->kv_proj.0, kv_down_proj.1->kv_proj.1, kv_up_proj->kv_proj.2
1637
+ loaded into self_attn.mla.XXX
1638
+ """
1639
+ stacked_params_mapping = [
1640
+ ("qkv_proj", "q_proj", "q"),
1641
+ ("qkv_proj", "k_proj", "k"),
1642
+ ("qkv_proj", "v_proj", "v"),
1643
+ ("gate_up_proj", "gate_proj", 0),
1644
+ ("gate_up_proj", "up_proj", 1),
1645
+ ]
1646
+
1647
+ # Build attention type map
1648
+ layer_attn_types: dict[int, str] = {}
1649
+ for i, lc in enumerate(self.nas_config.nas_layer_config):
1650
+ layer_attn_types[i] = lc.get("attention_type", "full_attention")
1651
+
1652
+ params_dict = dict(self.named_parameters())
1653
+ loaded_params: set[str] = set()
1654
+
1655
+ def _load_param(param_name: str, loaded_weight: torch.Tensor) -> bool:
1656
+ if param_name not in params_dict:
1657
+ logger.debug("Param not found: %s", param_name)
1658
+ return False
1659
+ param = params_dict[param_name]
1660
+ weight_loader = getattr(param, "weight_loader", default_weight_loader)
1661
+ try:
1662
+ weight_loader(param, loaded_weight)
1663
+ except (RuntimeError, ValueError, AssertionError) as e:
1664
+ logger.warning("Skip loading %s: %s", param_name, e)
1665
+ return False
1666
+ loaded_params.add(param_name)
1667
+ return True
1668
+
1669
+ sa_pattern = re.compile(r"(model\.layers\.(\d+)\.self_attn)\.(.*)")
1670
+
1671
+ for name, loaded_weight in weights:
1672
+ m = sa_pattern.match(name)
1673
+ if m:
1674
+ sa_prefix = m.group(1)
1675
+ layer_idx = int(m.group(2))
1676
+ sa_suffix = m.group(3)
1677
+ attn_type = layer_attn_types.get(layer_idx, "full_attention")
1678
+
1679
+ # ---- GDN ----
1680
+ if attn_type == "gdn":
1681
+ # GDN native uses merged projections:
1682
+ # in_proj = [q_proj, k_proj, v_proj, g_proj] merged
1683
+ # in_proj_ba = [b_proj, a_proj] merged
1684
+ # Map checkpoint separate weights to merged params
1685
+ gdn_stacked = {
1686
+ "q_proj": ("in_proj", 0),
1687
+ "k_proj": ("in_proj", 1),
1688
+ "v_proj": ("in_proj", 2),
1689
+ "g_proj": ("in_proj", 3),
1690
+ "b_proj": ("in_proj_ba", 0),
1691
+ "a_proj": ("in_proj_ba", 1),
1692
+ }
1693
+ # Extract base name (without .weight/.bias)
1694
+ base = sa_suffix
1695
+ leaf = ""
1696
+ for lf in (".weight", ".bias"):
1697
+ if sa_suffix.endswith(lf):
1698
+ base = sa_suffix[:-len(lf)]
1699
+ leaf = lf
1700
+ break
1701
+
1702
+ if base in gdn_stacked:
1703
+ merged_name, shard_id = gdn_stacked[base]
1704
+ target = f"{sa_prefix}.{merged_name}{leaf}"
1705
+ if target in params_dict:
1706
+ param = params_dict[target]
1707
+ weight_loader = getattr(param, "weight_loader", default_weight_loader)
1708
+ try:
1709
+ weight_loader(param, loaded_weight, shard_id)
1710
+ except (RuntimeError, ValueError, AssertionError) as e:
1711
+ logger.warning("Skip loading %s: %s", target, e)
1712
+ continue
1713
+ loaded_params.add(target)
1714
+ continue
1715
+
1716
+ # o_norm.weight -> norm.weight
1717
+ if base == "o_norm":
1718
+ target = f"{sa_prefix}.norm{leaf}"
1719
+ _load_param(target, loaded_weight)
1720
+ continue
1721
+
1722
+ # o_proj -> out_proj
1723
+ if base == "o_proj":
1724
+ target = f"{sa_prefix}.out_proj{leaf}"
1725
+ _load_param(target, loaded_weight)
1726
+ continue
1727
+
1728
+ # q_conv1d/k_conv1d/v_conv1d -> merged conv1d
1729
+ # checkpoint: q_conv1d(1536,1,4), k_conv1d(1536,1,4), v_conv1d(3072,1,4)
1730
+ # model: conv1d(6144,1,4) = [q(1536) + k(1536) + v(3072)]
1731
+ if base in ("q_conv1d", "k_conv1d", "v_conv1d"):
1732
+ target = f"{sa_prefix}.conv1d{leaf}"
1733
+ if target in params_dict:
1734
+ param = params_dict[target]
1735
+ # Manual slice loading
1736
+ key_dim_local = 1536 // self.tp_size if hasattr(self, 'tp_size') else 1536
1737
+ # For TP=1: q=[0:1536], k=[1536:3072], v=[3072:6144]
1738
+ tp_size = get_tensor_model_parallel_world_size()
1739
+ kd = 1536 // tp_size
1740
+ vd = 3072 // tp_size
1741
+ offsets = {"q_conv1d": 0, "k_conv1d": kd, "v_conv1d": kd * 2}
1742
+ sizes = {"q_conv1d": kd, "k_conv1d": kd, "v_conv1d": vd}
1743
+ offset = offsets[base]
1744
+ size = sizes[base]
1745
+ # Shard the loaded weight for TP
1746
+ tp_rank = get_tensor_model_parallel_rank()
1747
+ if loaded_weight.size(0) > size:
1748
+ loaded_weight = loaded_weight[tp_rank * size:(tp_rank + 1) * size]
1749
+ param.data[offset:offset + size] = loaded_weight
1750
+ loaded_params.add(target)
1751
+ continue
1752
+
1753
+ # Direct: A_log, dt_bias
1754
+ target = f"{sa_prefix}.{sa_suffix}"
1755
+ _load_param(target, loaded_weight)
1756
+ continue
1757
+
1758
+ # ---- KDA ----
1759
+ if attn_type == "kda":
1760
+ # KDA native weight mapping:
1761
+ # checkpoint f_proj.0.* -> self_attn.f_proj_0.*
1762
+ # checkpoint f_proj.1.* -> self_attn.f_proj_1.*
1763
+ # checkpoint g_proj.0.* -> self_attn.g_proj_0.*
1764
+ # checkpoint g_proj.1.* -> self_attn.g_proj_1.*
1765
+ # checkpoint *_conv1d.weight (D, 1, K) -> load directly
1766
+ # checkpoint A_log (16,) -> reshape to (1, 1, local_num_heads, 1)
1767
+ kda_suffix = sa_suffix
1768
+ kda_suffix = kda_suffix.replace("f_proj.0.", "f_proj_0.")
1769
+ kda_suffix = kda_suffix.replace("f_proj.1.", "f_proj_1.")
1770
+ kda_suffix = kda_suffix.replace("g_proj.0.", "g_proj_0.")
1771
+ kda_suffix = kda_suffix.replace("g_proj.1.", "g_proj_1.")
1772
+ target = f"{sa_prefix}.{kda_suffix}"
1773
+
1774
+ if "A_log" in kda_suffix and loaded_weight.dim() == 1:
1775
+ # Reshape from (num_heads,) to (1, 1, num_heads, 1)
1776
+ loaded_weight = loaded_weight.reshape(1, 1, -1, 1)
1777
+
1778
+ if "conv1d.weight" in kda_suffix and loaded_weight.dim() == 3:
1779
+ # Already 3D (D, 1, K) - load directly
1780
+ _load_param(target, loaded_weight)
1781
+ else:
1782
+ _load_param(target, loaded_weight)
1783
+ continue
1784
+
1785
+ # ---- MLA ----
1786
+ if attn_type == "mla":
1787
+ # Remap checkpoint names to native MLA submodules
1788
+ # Checkpoint: q_proj.{0,1,2} -> q_a_proj, q_a_layernorm, q_b_proj
1789
+ # Checkpoint: kv_down_proj.{0,1} -> kv_a_proj, kv_a_layernorm
1790
+ # Checkpoint: kv_up_proj -> kv_b_proj
1791
+ # Checkpoint: k_rope -> k_rope_proj
1792
+ # Checkpoint: o_proj -> o_proj
1793
+ # 数学等价映射:将 Checkpoint 的 Sequential 映射到 vLLM 的拆分模块
1794
+ target = f"{sa_prefix}.{sa_suffix}"
1795
+ _load_param(target, loaded_weight)
1796
+ continue
1797
+
1798
+ # mla_remap = {
1799
+ # "q_proj.0": "q_a_proj",
1800
+ # "q_proj.1": "q_a_layernorm",
1801
+ # "q_proj.2": "q_b_proj",
1802
+ # "k_rope": "k_rope_proj",
1803
+ # "kv_down_proj.0": "kv_a_proj",
1804
+ # "kv_down_proj.1": "kv_a_layernorm",
1805
+ # "kv_up_proj": "kv_b_proj",
1806
+ # "o_proj": "o_proj",
1807
+ # }
1808
+ # # Extract base and leaf (.weight/.bias)
1809
+ # mla_base = sa_suffix
1810
+ # mla_leaf = ""
1811
+ # for lf in (".weight", ".bias"):
1812
+ # if sa_suffix.endswith(lf):
1813
+ # mla_base = sa_suffix[:-len(lf)]
1814
+ # mla_leaf = lf
1815
+ # break
1816
+ # mapped = mla_remap.get(mla_base)
1817
+ # if mapped:
1818
+ # target = f"{sa_prefix}.{mapped}{mla_leaf}"
1819
+ # else:
1820
+ # target = f"{sa_prefix}.{sa_suffix}"
1821
+ # _load_param(target, loaded_weight)
1822
+ # continue
1823
+
1824
+ # ---- FULL / SWA: handle stacked params ----
1825
+ shard_matched = False
1826
+ for param_name, weight_name, shard_id in stacked_params_mapping:
1827
+ if sa_suffix == f"{weight_name}.weight" or sa_suffix == f"{weight_name}.bias":
1828
+ leaf = ".weight" if sa_suffix.endswith(".weight") else ".bias"
1829
+ candidate = f"{sa_prefix}.{param_name}{leaf}"
1830
+ if candidate in params_dict:
1831
+ param = params_dict[candidate]
1832
+ weight_loader = getattr(param, "weight_loader", default_weight_loader)
1833
+ try:
1834
+ weight_loader(param, loaded_weight, shard_id)
1835
+ except (RuntimeError, ValueError) as e:
1836
+ logger.warning("Skip loading %s: %s", candidate, e)
1837
+ break
1838
+ loaded_params.add(candidate)
1839
+ shard_matched = True
1840
+ break
1841
+
1842
+ if not shard_matched:
1843
+ # Direct load (o_proj, q_norm, k_norm)
1844
+ _load_param(f"{sa_prefix}.{sa_suffix}", loaded_weight)
1845
+ continue
1846
+
1847
+ # ---- Non self_attn weights (MLP, layernorm, embed, norm, lm_head) ----
1848
+ # Handle gate_proj/up_proj stacking for MLP
1849
+ shard_matched = False
1850
+ for param_name, weight_name, shard_id in stacked_params_mapping:
1851
+ if weight_name not in name:
1852
+ continue
1853
+ candidate = name.replace(weight_name, param_name)
1854
+ if candidate in params_dict:
1855
+ param = params_dict[candidate]
1856
+ weight_loader = getattr(param, "weight_loader", default_weight_loader)
1857
+ try:
1858
+ weight_loader(param, loaded_weight, shard_id)
1859
+ except (RuntimeError, ValueError, AssertionError) as e:
1860
+ logger.warning("Skip loading %s: %s", candidate, e)
1861
+ break
1862
+ loaded_params.add(candidate)
1863
+ shard_matched = True
1864
+ break
1865
+
1866
+ if not shard_matched:
1867
+ _load_param(name, loaded_weight)
1868
+
1869
+ return loaded_params
1870
+
1871
+
1872
+ # ============================================================================
1873
+ # Top-level multimodal model
1874
+ # ============================================================================
1875
+
1876
+ from vllm.tokenizers.registry import cached_tokenizer_from_config
1877
+ from vllm.multimodal import MULTIMODAL_REGISTRY
1878
+
1879
+
1880
+ @MULTIMODAL_REGISTRY.register_processor(
1881
+ Qwen3VLMultiModalProcessor,
1882
+ info=Qwen3VLProcessingInfo,
1883
+ dummy_inputs=Qwen3VLDummyInputsBuilder,
1884
+ )
1885
+ class NasChildVLForConditionalGeneration(Qwen3VLForConditionalGeneration):
1886
+ """NAS-Child-VL multimodal model."""
1887
+
1888
+ has_inner_state = True
1889
+
1890
+ hf_to_vllm_mapper = WeightsMapper(
1891
+ orig_to_new_prefix={
1892
+ "visual.": "visual.",
1893
+ "lm_head.": "language_model.lm_head.",
1894
+ "embed_tokens.": "language_model.model.embed_tokens.",
1895
+ "norm.": "language_model.model.norm.",
1896
+ "layers.": "language_model.model.layers.",
1897
+ }
1898
+ )
1899
+
1900
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = "model"):
1901
+ nn.Module.__init__(self)
1902
+ config: NasChildVLConfig = vllm_config.model_config.hf_config
1903
+ quant_config = vllm_config.quant_config
1904
+ multimodal_config = vllm_config.model_config.multimodal_config
1905
+
1906
+ # Set triton allocator for chunk_gated_delta_rule etc. (runs in subprocess)
1907
+ import triton
1908
+ triton.set_allocator(
1909
+ lambda size, align, stream: torch.cuda.caching_allocator_alloc(
1910
+ size, stream=stream))
1911
+
1912
+ self.config = config
1913
+ self._tokenizer = cached_tokenizer_from_config(vllm_config.model_config)
1914
+ self.multimodal_config = multimodal_config
1915
+ self.use_data_parallel = multimodal_config.mm_encoder_tp_mode == "data"
1916
+ self.video_pruning_rate = multimodal_config.video_pruning_rate
1917
+ self.is_multimodal_pruning_enabled = multimodal_config.is_multimodal_pruning_enabled()
1918
+
1919
+ self.use_deepstack = hasattr(config.vision_config, "deepstack_visual_indexes")
1920
+ self.deepstack_num_level = (
1921
+ len(config.vision_config.deepstack_visual_indexes) if self.use_deepstack else 0
1922
+ )
1923
+ self.visual_dim = config.vision_config.out_hidden_size
1924
+ self.multiscale_dim = self.visual_dim * self.deepstack_num_level
1925
+
1926
+ with self._mark_tower_model(vllm_config, {"image", "video"}):
1927
+ self.visual = Qwen3_VisionTransformer(
1928
+ config.vision_config,
1929
+ norm_eps=getattr(config, "rms_norm_eps", 1e-6),
1930
+ quant_config=quant_config,
1931
+ prefix=maybe_prefix(prefix, "visual"),
1932
+ )
1933
+ if self.use_deepstack:
1934
+ self.deepstack_input_embeds = [
1935
+ torch.zeros(
1936
+ vllm_config.scheduler_config.max_num_batched_tokens,
1937
+ config.text_config.hidden_size,
1938
+ )
1939
+ for _ in range(self.deepstack_num_level)
1940
+ ]
1941
+
1942
+ with self._mark_language_model(vllm_config):
1943
+ self.language_model = NasChildVLForCausalLM(
1944
+ vllm_config=vllm_config.with_hf_config(config),
1945
+ prefix=maybe_prefix(prefix, "language_model"),
1946
+ )
1947
+
1948
+ self.make_empty_intermediate_tensors = self.language_model.make_empty_intermediate_tensors
1949
+
1950
+ # def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1951
+ # loader = AutoWeightsLoader(self)
1952
+ # return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
1953
+ def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1954
+ vision_weights = []
1955
+ lm_weights = []
1956
+
1957
+ for name, data in weights:
1958
+ if name.startswith("visual."):
1959
+ # 视觉侧保留原始 name
1960
+ vision_weights.append((name, data))
1961
+ else:
1962
+ # 手动将 HuggingFace 的权重名映射为 NasChildVLForCausalLM 期望的内部名
1963
+ if name.startswith("layers."):
1964
+ lm_name = "model." + name
1965
+ elif name.startswith("embed_tokens."):
1966
+ lm_name = "model." + name
1967
+ elif name.startswith("norm."):
1968
+ lm_name = "model." + name
1969
+ elif name.startswith("lm_head."):
1970
+ lm_name = name # lm_head 在 NasChildVLForCausalLM 中没有 model. 前缀
1971
+ else:
1972
+ lm_name = name # fallback
1973
+
1974
+ lm_weights.append((lm_name, data))
1975
+
1976
+ loaded_params = set()
1977
+
1978
+ # 1. 加载视觉侧权重 (AutoWeightsLoader 知道如何使用 mapper)
1979
+ if vision_weights:
1980
+ loader = AutoWeightsLoader(self)
1981
+ vision_loaded = loader.load_weights(vision_weights, mapper=self.hf_to_vllm_mapper)
1982
+ loaded_params.update(vision_loaded)
1983
+
1984
+ # 2. 加载语言模型侧权重
1985
+ if lm_weights:
1986
+ # 此时传入的 lm_name 已经是 "model.layers.0..." 格式
1987
+ # 完美匹配 NasChildVLForCausalLM 内部的正则和参数名
1988
+ lm_loaded = self.language_model.load_weights(lm_weights)
1989
+
1990
+ for name in lm_loaded:
1991
+ # 补齐前缀以匹配顶层多模态模型 (NasChildVLForConditionalGeneration) 的 named_parameters
1992
+ loaded_params.add(f"language_model.{name}")
1993
+
1994
+ return loaded_params
1995
+
1996
+ @classmethod
1997
+ def get_mamba_state_dtype_from_config(
1998
+ cls,
1999
+ vllm_config: VllmConfig,
2000
+ ) -> tuple[torch.dtype, ...]:
2001
+ return _get_unified_state_dtype(vllm_config.model_config.dtype)
2002
+
2003
+ @classmethod
2004
+ def get_mamba_state_shape_from_config(
2005
+ cls, vllm_config: VllmConfig,
2006
+ ) -> tuple[tuple[int, ...], ...]:
2007
+ return _get_unified_state_shape(vllm_config.parallel_config.tensor_parallel_size)
2008
+
2009
+ @classmethod
2010
+ def get_mamba_state_copy_func(cls) -> tuple[MambaStateCopyFunc, ...]:
2011
+ # 🌟 核心修改:从 mamba_utils 导入正确的拷贝函数
2012
+ from vllm.model_executor.layers.mamba.mamba_utils import (
2013
+ get_conv_copy_spec,
2014
+ get_temporal_copy_spec,
2015
+ )
2016
+ # 统一状态有 4 个:前 3 个是 conv,第 4 个是 temporal (ssm)
2017
+ return (
2018
+ get_conv_copy_spec,
2019
+ get_conv_copy_spec,
2020
+ get_conv_copy_spec,
2021
+ get_temporal_copy_spec,
2022
+ )
nas_child_vl_vllm/register.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Register NAS-Child-VL model with vLLM via monkey patch.
3
+
4
+ Call `register()` once before using vLLM with NAS-Child-VL models.
5
+ This is automatically done when `import nas_child_vl_vllm`.
6
+ """
7
+
8
+ _registered = False
9
+
10
+
11
+ def register():
12
+ global _registered
13
+ if _registered:
14
+ return
15
+ _registered = True
16
+
17
+ # 1. Register Config class in vLLM config discovery system
18
+ from vllm.transformers_utils.configs import _CLASS_TO_MODULE, __all__
19
+ from nas_child_vl_vllm.config import NasChildVLConfig
20
+
21
+ _CLASS_TO_MODULE["NasChildVLConfig"] = "nas_child_vl_vllm.config"
22
+ __all__.append("NasChildVLConfig")
23
+
24
+ # 2. Register model_type -> Config mapping
25
+ from vllm.transformers_utils.config import _CONFIG_REGISTRY
26
+ _CONFIG_REGISTRY["nas-child-vl"] = NasChildVLConfig
27
+
28
+ # 3. Register HuggingFace architecture -> model class mapping (lazy)
29
+ from vllm.model_executor.models.registry import ModelRegistry
30
+ ModelRegistry.register_model(
31
+ "NasChildVLModelForCausalLM",
32
+ "nas_child_vl_vllm.model:NasChildVLForConditionalGeneration",
33
+ )
34
+
35
+ # 4. Register Models Config (MambaModelConfig for hybrid state management)
36
+ from vllm.model_executor.models.config import MODELS_CONFIG_MAP, MambaModelConfig
37
+ MODELS_CONFIG_MAP["NasChildVLModelForCausalLM"] = MambaModelConfig
38
+
39
+ # 5. Monkey patch rmsnorm_fn (add sigmoid activation support)
40
+ import vllm.model_executor.layers.fla.ops.layernorm_guard as ln_mod
41
+ from nas_child_vl_vllm.rmsnorm_patch import rmsnorm_fn
42
+ ln_mod.rmsnorm_fn = rmsnorm_fn
nas_child_vl_vllm/rmsnorm_patch.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Pure PyTorch rmsnorm_fn with sigmoid activation support.
3
+
4
+ Replaces vLLM's Triton-based rmsnorm_fn which does not support
5
+ sigmoid activation and can cause issues with fla GDN/KDA layers.
6
+ """
7
+
8
+ import torch
9
+ import torch.nn.functional as F
10
+
11
+
12
+ def rmsnorm_fn(
13
+ x,
14
+ weight,
15
+ bias,
16
+ eps,
17
+ z=None,
18
+ group_size=None,
19
+ norm_before_gate=True,
20
+ activation="swish",
21
+ **kwargs,
22
+ ):
23
+ # 1. Native RMSNorm computation
24
+ variance = x.pow(2).mean(-1, keepdim=True)
25
+ out = x * torch.rsqrt(variance + eps)
26
+
27
+ if weight is not None:
28
+ out = out * weight
29
+ if bias is not None:
30
+ out = out + bias
31
+
32
+ # 2. If z is provided, this is a Fused RMSNorm + Gating
33
+ # Match fla FusedRMSNormGated behavior:
34
+ # norm_before_gate=True -> rms_norm(x) * weight * activation(z)
35
+ # norm_before_gate=False -> rms_norm(x * activation(z)) * weight
36
+ # fla always does norm_before_gate=True (norm first, then multiply by gate).
37
+ if z is not None:
38
+ if activation == "swish" or activation == "silu":
39
+ gate = F.silu(z)
40
+ elif activation == "sigmoid":
41
+ gate = torch.sigmoid(z)
42
+ else:
43
+ gate = z
44
+ out = out * gate
45
+
46
+ return out