main
py 685 lines 25.4 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """NVIDIA implementation of the Dots3Note language model."""
4
5 import copy
6 from collections.abc import Iterable, Iterator
7 from dataclasses import replace
8
9 import torch
10 from torch import nn
11
12 import vllm.envs as envs
13 from vllm.config import VllmConfig
14 from vllm.distributed import (
15 get_pp_group,
16 get_tensor_model_parallel_rank,
17 get_tensor_model_parallel_world_size,
18 tensor_model_parallel_all_gather,
19 tensor_model_parallel_all_reduce,
20 )
21 from vllm.model_executor.layers.attention import MLAAttention
22 from vllm.model_executor.layers.fused_embed_norm import has_full_vocab_on_rank
23 from vllm.model_executor.layers.layernorm import RMSNorm
24 from vllm.model_executor.layers.linear import (
25 ColumnParallelLinear,
26 DCPGroupColumnParallelLinear,
27 ReplicatedLinear,
28 RowParallelLinear,
29 )
30 from vllm.model_executor.layers.quantization import QuantizationConfig
31 from vllm.model_executor.layers.quantization.utils.quant_utils import (
32 GroupShape,
33 scaled_dequantize,
34 )
35 from vllm.model_executor.layers.rotary_embedding import get_rope
36 from vllm.model_executor.layers.vocab_parallel_embedding import VocabParallelEmbedding
37 from vllm.model_executor.models.deepseek_v2 import (
38 DeepSeekV2FusedQkvAProjLinear,
39 DeepseekV2MLAAttention,
40 DeepseekV2MLP,
41 DeepseekV2MoE,
42 )
43 from vllm.model_executor.models.utils import (
44 PPMissingLayer,
45 make_empty_intermediate_tensors_factory,
46 make_layers,
47 sequence_parallel_chunk,
48 )
49 from vllm.models.deepseek_v32.nvidia.model import (
50 DeepseekV32DecoderLayer,
51 DeepseekV32ForCausalLM,
52 DeepseekV32Model,
53 )
54 from vllm.platforms import current_platform
55 from vllm.v1.kv_cache_interface import MLAAttentionSpec
56
57 from .attention import (
58 Dots3NoteFlashAttnPrefillBackend,
59 Dots3NotePaddedSparseBackend,
60 Dots3NoteTritonMLABackend,
61 )
62
63
64 def _padded_mlp_size(
65 intermediate_size: int,
66 quant_config: QuantizationConfig | None,
67 is_sequence_parallel: bool = False,
68 ) -> int:
69 block_size = getattr(quant_config, "weight_block_size", None)
70 if block_size is None or is_sequence_parallel:
71 return intermediate_size
72 tp_size = get_tensor_model_parallel_world_size()
73 blocks = (intermediate_size + block_size[0] - 1) // block_size[0]
74 return ((blocks + tp_size - 1) // tp_size) * block_size[0] * tp_size
75
76
77 def _pad_dense_mlp_weight(
78 name: str,
79 loaded_weight: torch.Tensor,
80 weight_block_size: list[int] | None,
81 ) -> torch.Tensor:
82 if weight_block_size is None or ".mlp.experts." in name:
83 return loaded_weight
84 if not any(
85 proj_name in name for proj_name in (".gate_proj.", ".up_proj.", ".down_proj.")
86 ):
87 return loaded_weight
88 dim = 1 if ".down_proj." in name else 0
89 block_step = 1 if name.endswith("weight_scale_inv") else weight_block_size[0]
90 multiple = get_tensor_model_parallel_world_size() * block_step
91 pad = (-loaded_weight.shape[dim]) % multiple
92 if pad == 0:
93 return loaded_weight
94 pad_shape = list(loaded_weight.shape)
95 pad_shape[dim] = pad
96 return torch.cat([loaded_weight, loaded_weight.new_zeros(pad_shape)], dim=dim)
97
98
99 class Dots3NoteMoE(DeepseekV2MoE):
100 """DeepSeek MoE with NOTE's block-padded shared expert."""
101
102 def __init__(
103 self,
104 config,
105 parallel_config,
106 quant_config: QuantizationConfig | None = None,
107 reduce_results: bool = True,
108 prefix: str = "",
109 apply_routed_scale_to_output: bool = False,
110 ) -> None:
111 num_shared_experts = config.n_shared_experts
112 routed_config = copy.copy(config)
113 object.__setattr__(routed_config, "n_shared_experts", None)
114 super().__init__(
115 config=routed_config,
116 parallel_config=parallel_config,
117 quant_config=quant_config,
118 reduce_results=False,
119 prefix=prefix,
120 apply_routed_scale_to_output=apply_routed_scale_to_output,
121 )
122 self.n_shared_experts = num_shared_experts
123 self.reduce_results = reduce_results
124 self.shared_experts = DeepseekV2MLP(
125 hidden_size=config.hidden_size,
126 intermediate_size=_padded_mlp_size(
127 config.moe_intermediate_size * num_shared_experts,
128 quant_config,
129 self.is_sequence_parallel,
130 ),
131 hidden_act=config.hidden_act,
132 quant_config=quant_config,
133 is_sequence_parallel=self.is_sequence_parallel,
134 reduce_results=False,
135 prefix=f"{prefix}.shared_experts",
136 )
137
138 def forward(
139 self,
140 hidden_states: torch.Tensor,
141 already_sequence_parallel: bool = False,
142 ) -> torch.Tensor:
143 num_tokens = hidden_states.shape[0]
144 gather_output = self.is_sequence_parallel and not already_sequence_parallel
145 if gather_output:
146 hidden_states = sequence_parallel_chunk(hidden_states)
147 assert self.shared_experts is not None
148 output = super().forward(
149 hidden_states, already_sequence_parallel=True
150 ) + self.shared_experts(hidden_states)
151 if gather_output:
152 return tensor_model_parallel_all_gather(output, 0)[:num_tokens]
153 if self.reduce_results:
154 return tensor_model_parallel_all_reduce(output)
155 return output
156
157
158 def _forward_note_mla(
159 attention,
160 positions: torch.Tensor,
161 hidden_states: torch.Tensor,
162 *,
163 g_proj: nn.Module,
164 k_rope_only_layernorm: nn.Module,
165 attention_gate_type: str,
166 q_lora_scale: float,
167 kv_lora_scale: float,
168 llama_4_scaling: torch.Tensor | None = None,
169 ) -> torch.Tensor:
170 qkv_lora = attention.fused_qkv_a_proj(hidden_states)[0]
171 q_c, kv_lora = qkv_lora.split(
172 [
173 attention.q_lora_rank,
174 attention.kv_lora_rank + attention.qk_rope_head_dim,
175 ],
176 dim=-1,
177 )
178 q_c = attention.q_a_layernorm(q_c) * q_lora_scale
179 kv_c, k_pe = kv_lora.split(
180 [attention.kv_lora_rank, attention.qk_rope_head_dim], dim=-1
181 )
182 kv_c_normed = attention.kv_a_layernorm(kv_c) * kv_lora_scale
183 k_pe = k_rope_only_layernorm(k_pe).unsqueeze(1)
184
185 q = attention.q_b_proj(q_c)[0]
186 heads = attention.num_heads
187 if attention.dcp_q_replicate:
188 heads *= attention.q_b_proj.group_size
189 q = q.view(-1, heads, attention.qk_head_dim)
190 q[..., attention.qk_nope_head_dim :], k_pe = attention.rotary_emb(
191 positions, q[..., attention.qk_nope_head_dim :], k_pe
192 )
193
194 if attention.indexer and attention.is_sparse and not attention.skip_topk:
195 attention.indexer(hidden_states, q_c, positions, attention.indexer_rope_emb)
196 if llama_4_scaling is not None:
197 q *= llama_4_scaling
198
199 q_dcp_replicated = None
200 if attention.dcp_q_replicate:
201 q_dcp_replicated, q = q, attention.q_b_proj._local_view(q)
202 attn_out = attention.mla_attn(
203 q,
204 kv_c_normed,
205 k_pe,
206 output_shape=(
207 hidden_states.shape[0],
208 attention.num_heads * attention.v_head_dim,
209 ),
210 q_dcp_replicated=q_dcp_replicated,
211 )
212
213 gate = g_proj(hidden_states)[0]
214 if attention_gate_type == "headwise":
215 if gate.shape[-1] != attention.num_heads:
216 rank = get_tensor_model_parallel_rank()
217 gate = gate.narrow(-1, rank * attention.num_heads, attention.num_heads)
218 attn_out = attn_out.view(-1, attention.num_heads, attention.v_head_dim)
219 gate = torch.sigmoid(gate.float()).to(attn_out.dtype)
220 attn_out = (attn_out * gate.unsqueeze(-1)).flatten(-2)
221 else:
222 gate = torch.sigmoid(gate.float()).to(attn_out.dtype)
223 attn_out = attn_out * gate
224 return attention.o_proj(attn_out)[0]
225
226
227 class Dots3NotePaddedMLAAttention(MLAAttention):
228 """MLA layer whose physical cache rows match NOTE's SWA rows."""
229
230 def __init__(self, *args, physical_head_size: int, **kwargs) -> None:
231 kwargs["attn_backend"] = Dots3NotePaddedSparseBackend
232 super().__init__(*args, **kwargs)
233 assert physical_head_size >= self.head_size
234 self.physical_head_size = physical_head_size
235
236 def get_kv_cache_spec(self, vllm_config: VllmConfig) -> MLAAttentionSpec:
237 spec = super().get_kv_cache_spec(vllm_config)
238 assert isinstance(spec, MLAAttentionSpec)
239 return replace(spec, head_size=self.physical_head_size)
240
241
242 class Dots3NoteFullAttention(DeepseekV2MLAAttention):
243 """NOTE DSA on the existing DeepSeek sparse MLA attention."""
244
245 def __init__(
246 self,
247 vllm_config: VllmConfig,
248 config,
249 prefix: str,
250 topk_indices_buffer: torch.Tensor | None = None,
251 ) -> None:
252 quant_config = vllm_config.quant_config
253 local_config = copy.copy(config)
254 object.__setattr__(
255 local_config,
256 "rope_parameters",
257 {
258 "rope_type": "default",
259 "rope_theta": config.rope_parameters["rope_theta"],
260 },
261 )
262 super().__init__(
263 vllm_config=vllm_config,
264 config=local_config,
265 hidden_size=config.hidden_size,
266 num_heads=config.num_attention_heads,
267 qk_nope_head_dim=config.qk_nope_head_dim,
268 qk_rope_head_dim=config.qk_rope_head_dim,
269 v_head_dim=config.v_head_dim,
270 q_lora_rank=config.q_lora_rank,
271 kv_lora_rank=config.kv_lora_rank,
272 max_position_embeddings=config.max_position_embeddings,
273 cache_config=vllm_config.cache_config,
274 quant_config=quant_config,
275 prefix=prefix,
276 topk_indices_buffer=topk_indices_buffer,
277 reduce_results=False,
278 )
279 wrapper = self._modules.pop("mla_attn")
280 inner_attention = wrapper.mla_attn
281 vllm_config.compilation_config.static_forward_context.pop(
282 inner_attention.layer_name
283 )
284 prefill_backend_cls = (
285 type(inner_attention.prefill_backend)
286 if inner_attention.prefill_backend is not None
287 else None
288 )
289 wrapper.mla_attn = Dots3NotePaddedMLAAttention(
290 num_heads=inner_attention.num_heads,
291 scale=inner_attention.scale,
292 q_lora_rank=inner_attention.q_lora_rank,
293 kv_lora_rank=inner_attention.kv_lora_rank,
294 qk_nope_head_dim=inner_attention.qk_nope_head_dim,
295 qk_rope_head_dim=inner_attention.qk_rope_head_dim,
296 v_head_dim=inner_attention.v_head_dim,
297 kv_b_proj=inner_attention.kv_b_proj,
298 dcp_q_replicate=inner_attention.dcp_q_replicate,
299 cache_config=vllm_config.cache_config,
300 quant_config=quant_config,
301 prefix=inner_attention.layer_name,
302 use_sparse=True,
303 indexer=wrapper.indexer,
304 topk_indices_buffer=topk_indices_buffer,
305 prefill_backend_cls=prefill_backend_cls,
306 physical_head_size=(config.swa_kv_lora_rank + config.swa_qk_rope_head_dim),
307 )
308 gate_type = config.attention_gate_type
309 gate_cls = ReplicatedLinear if gate_type == "headwise" else ColumnParallelLinear
310 gate_size = (
311 config.num_attention_heads
312 if gate_type == "headwise"
313 else config.num_attention_heads * config.v_head_dim
314 )
315 self.g_proj = gate_cls(
316 config.hidden_size,
317 gate_size,
318 bias=False,
319 quant_config=quant_config,
320 prefix=f"{prefix}.g_proj",
321 )
322 self.k_rope_only_layernorm = RMSNorm(
323 config.qk_rope_head_dim, eps=config.rms_norm_eps
324 )
325 self.attention_gate_type = gate_type
326 self.q_lora_scale = 1.0
327 self.kv_lora_scale = 1.0
328 if config.apply_mla_qkv_lora_rescale:
329 self.q_lora_scale = (config.hidden_size / config.q_lora_rank) ** 0.5
330 self.kv_lora_scale = (config.hidden_size / config.kv_lora_rank) ** 0.5
331 self.mla_attn = wrapper
332
333 def forward(
334 self,
335 positions: torch.Tensor,
336 hidden_states: torch.Tensor,
337 llama_4_scaling: torch.Tensor | None = None,
338 ) -> torch.Tensor:
339 return _forward_note_mla(
340 self.mla_attn,
341 positions,
342 hidden_states,
343 g_proj=self.g_proj,
344 k_rope_only_layernorm=self.k_rope_only_layernorm,
345 attention_gate_type=self.attention_gate_type,
346 q_lora_scale=self.q_lora_scale,
347 kv_lora_scale=self.kv_lora_scale,
348 llama_4_scaling=llama_4_scaling,
349 )
350
351
352 class Dots3NoteSlidingAttention(nn.Module):
353 """NOTE SWA constructed directly as dense sliding-window MLA."""
354
355 def __init__(
356 self,
357 vllm_config: VllmConfig,
358 config,
359 prefix: str,
360 topk_indices_buffer: torch.Tensor | None = None,
361 ) -> None:
362 super().__init__()
363 del topk_indices_buffer
364 num_heads = config.swa_num_attention_heads
365 qk_nope_head_dim = config.swa_qk_nope_head_dim
366 qk_rope_head_dim = config.swa_qk_rope_head_dim
367 v_head_dim = config.swa_v_head_dim
368 q_lora_rank = config.swa_q_lora_rank
369 kv_lora_rank = config.swa_kv_lora_rank
370 qk_head_dim = qk_nope_head_dim + qk_rope_head_dim
371 tp_size = get_tensor_model_parallel_world_size()
372 assert num_heads % tp_size == 0
373 quant_config = vllm_config.quant_config
374
375 fused_qkv_a_proj = DeepSeekV2FusedQkvAProjLinear(
376 config.hidden_size,
377 [q_lora_rank, kv_lora_rank + qk_rope_head_dim],
378 quant_config=quant_config,
379 prefix=f"{prefix}.fused_qkv_a_proj",
380 )
381 qrep_enabled = (
382 envs.VLLM_DCP_Q_REPLICATE
383 and vllm_config.parallel_config.decode_context_parallel_size > 1
384 and vllm_config.parallel_config.prefill_context_parallel_size <= 1
385 )
386 q_proj_cls = (
387 DCPGroupColumnParallelLinear if qrep_enabled else ColumnParallelLinear
388 )
389 q_a_layernorm = RMSNorm(q_lora_rank, eps=config.rms_norm_eps)
390 q_b_proj = q_proj_cls(
391 q_lora_rank,
392 num_heads * qk_head_dim,
393 bias=False,
394 quant_config=quant_config,
395 prefix=f"{prefix}.q_b_proj",
396 )
397 kv_a_layernorm = RMSNorm(kv_lora_rank, eps=config.rms_norm_eps)
398 kv_b_proj = ColumnParallelLinear(
399 kv_lora_rank,
400 num_heads * (qk_nope_head_dim + v_head_dim),
401 bias=False,
402 quant_config=quant_config,
403 prefix=f"{prefix}.kv_b_proj",
404 )
405 o_proj = RowParallelLinear(
406 num_heads * v_head_dim,
407 config.hidden_size,
408 bias=False,
409 reduce_results=False,
410 quant_config=quant_config,
411 prefix=f"{prefix}.o_proj",
412 )
413 gate_type = config.swa_attention_gate_type
414 gate_cls = ReplicatedLinear if gate_type == "headwise" else ColumnParallelLinear
415 gate_size = num_heads if gate_type == "headwise" else num_heads * v_head_dim
416 g_proj = gate_cls(
417 config.hidden_size,
418 gate_size,
419 bias=False,
420 quant_config=quant_config,
421 prefix=f"{prefix}.g_proj",
422 )
423 k_rope_only_layernorm = RMSNorm(qk_rope_head_dim, eps=config.rms_norm_eps)
424 rotary_emb = get_rope(
425 qk_rope_head_dim,
426 max_position=config.max_position_embeddings,
427 rope_parameters={
428 "rope_type": "default",
429 "rope_theta": config.swa_rope_theta,
430 },
431 is_neox_style=False,
432 )
433 cache_config = vllm_config.cache_config
434 if cache_config.cache_dtype == "fp8_ds_mla":
435 cache_config = copy.copy(cache_config)
436 cache_config.cache_dtype = "fp8"
437 apply_rescale = config.apply_mla_qkv_lora_rescale
438 self.hidden_size = config.hidden_size
439 self.num_heads = num_heads // tp_size
440 self.qk_nope_head_dim = qk_nope_head_dim
441 self.qk_rope_head_dim = qk_rope_head_dim
442 self.qk_head_dim = qk_head_dim
443 self.v_head_dim = v_head_dim
444 self.q_lora_rank = q_lora_rank
445 self.kv_lora_rank = kv_lora_rank
446 self.fused_qkv_a_proj = fused_qkv_a_proj
447 self.q_a_layernorm = q_a_layernorm
448 self.q_b_proj = q_b_proj
449 self.kv_a_layernorm = kv_a_layernorm
450 self.kv_b_proj = kv_b_proj
451 self.rotary_emb = rotary_emb
452 self.o_proj = o_proj
453 self.g_proj = g_proj
454 self.k_rope_only_layernorm = k_rope_only_layernorm
455 self.indexer = None
456 self.indexer_rope_emb = None
457 self.is_sparse = False
458 self.skip_topk = False
459 self.dcp_q_replicate = getattr(q_b_proj, "qrep_active", False)
460 self.attention_gate_type = gate_type
461 self.q_lora_scale = (
462 (config.hidden_size / q_lora_rank) ** 0.5 if apply_rescale else 1.0
463 )
464 self.kv_lora_scale = (
465 (config.hidden_size / kv_lora_rank) ** 0.5 if apply_rescale else 1.0
466 )
467 self.mla_attn = MLAAttention(
468 num_heads=self.num_heads,
469 scale=qk_head_dim**-0.5,
470 qk_nope_head_dim=qk_nope_head_dim,
471 qk_rope_head_dim=qk_rope_head_dim,
472 v_head_dim=v_head_dim,
473 q_lora_rank=q_lora_rank,
474 kv_lora_rank=kv_lora_rank,
475 kv_b_proj=kv_b_proj,
476 dcp_q_replicate=self.dcp_q_replicate,
477 cache_config=cache_config,
478 quant_config=quant_config,
479 prefix=f"{prefix}.attn",
480 sliding_window=config.sliding_window_size,
481 attn_backend=Dots3NoteTritonMLABackend,
482 prefill_backend_cls=Dots3NoteFlashAttnPrefillBackend,
483 )
484
485 def forward(
486 self,
487 positions: torch.Tensor,
488 hidden_states: torch.Tensor,
489 llama_4_scaling: torch.Tensor | None = None,
490 ) -> torch.Tensor:
491 return _forward_note_mla(
492 self,
493 positions,
494 hidden_states,
495 g_proj=self.g_proj,
496 k_rope_only_layernorm=self.k_rope_only_layernorm,
497 attention_gate_type=self.attention_gate_type,
498 q_lora_scale=self.q_lora_scale,
499 kv_lora_scale=self.kv_lora_scale,
500 llama_4_scaling=llama_4_scaling,
501 )
502
503
504 class Dots3NoteDecoderLayer(DeepseekV32DecoderLayer):
505 """DeepSeek-V3.2 decoder orchestration with NOTE-local modules."""
506
507 def __init__(
508 self,
509 vllm_config: VllmConfig,
510 prefix: str,
511 config=None,
512 topk_indices_buffer: torch.Tensor | None = None,
513 ) -> None:
514 nn.Module.__init__(self)
515 if config is None:
516 config = vllm_config.model_config.hf_config
517 quant_config = vllm_config.quant_config
518 parallel_config = vllm_config.parallel_config
519 self.hidden_size = config.hidden_size
520 layer_idx = int(prefix.split(sep=".")[-1])
521 self.layer_idx = layer_idx
522 self.use_mha = False
523 self.use_sequence_parallel = False
524 attention_cls = (
525 Dots3NoteSlidingAttention
526 if config.layer_types[layer_idx] == "sliding_attention"
527 else Dots3NoteFullAttention
528 )
529 self.self_attn = attention_cls(
530 vllm_config=vllm_config,
531 config=config,
532 prefix=f"{prefix}.self_attn",
533 topk_indices_buffer=topk_indices_buffer,
534 )
535
536 moe_layer_freq = getattr(config, "moe_layer_freq", 1)
537 is_moe = (
538 layer_idx < config.num_hidden_layers
539 and config.n_routed_experts is not None
540 and layer_idx >= config.first_k_dense_replace
541 and layer_idx % moe_layer_freq == 0
542 )
543 if is_moe:
544 self.mlp = Dots3NoteMoE(
545 config=config,
546 parallel_config=parallel_config,
547 quant_config=quant_config,
548 reduce_results=False,
549 prefix=f"{prefix}.mlp",
550 apply_routed_scale_to_output=False,
551 )
552 else:
553 self.mlp = DeepseekV2MLP(
554 hidden_size=config.hidden_size,
555 intermediate_size=_padded_mlp_size(
556 config.intermediate_size, quant_config
557 ),
558 hidden_act=config.hidden_act,
559 quant_config=quant_config,
560 prefix=f"{prefix}.mlp",
561 reduce_results=False,
562 )
563 self.use_sequence_parallel_moe = False
564 self.tp_size = parallel_config.tensor_parallel_size
565 self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
566 self.post_attention_layernorm = RMSNorm(
567 config.hidden_size, eps=config.rms_norm_eps
568 )
569 self.routed_scaling_factor = getattr(config, "routed_scaling_factor", 1.0)
570
571
572 class Dots3NoteModel(DeepseekV32Model):
573 """DeepSeek-V3.2 runtime shell with NOTE-local decoder layers."""
574
575 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
576 nn.Module.__init__(self)
577 config = vllm_config.model_config.hf_config
578 quant_config = vllm_config.quant_config
579 self.config = config
580 self.device = current_platform.device_type
581 self.vocab_size = config.vocab_size
582 self.use_sequence_parallel = False
583 self.is_v32 = True
584 self._weight_block_size = getattr(quant_config, "weight_block_size", None)
585 topk_indices_buffer = torch.empty(
586 vllm_config.scheduler_config.max_num_batched_tokens,
587 config.index_topk,
588 dtype=torch.int32,
589 device=self.device,
590 )
591 if get_pp_group().is_first_rank:
592 self.embed_tokens = VocabParallelEmbedding(
593 config.vocab_size,
594 config.hidden_size,
595 quant_config=quant_config,
596 prefix=f"{prefix}.embed_tokens",
597 )
598 else:
599 self.embed_tokens = PPMissingLayer()
600 self.replicated_embed = has_full_vocab_on_rank(self.embed_tokens)
601 self.start_layer, self.end_layer, self.layers = make_layers(
602 config.num_hidden_layers,
603 lambda prefix: Dots3NoteDecoderLayer(
604 vllm_config=vllm_config,
605 prefix=prefix,
606 topk_indices_buffer=topk_indices_buffer,
607 ),
608 prefix=f"{prefix}.layers",
609 )
610 if get_pp_group().is_last_rank:
611 self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
612 else:
613 self.norm = PPMissingLayer()
614 self.make_empty_intermediate_tensors = make_empty_intermediate_tensors_factory(
615 ["hidden_states", "residual"], config.hidden_size
616 )
617 self.aux_hidden_state_layers = tuple[int, ...]()
618 self.num_redundant_experts = (
619 vllm_config.parallel_config.eplb_config.num_redundant_experts
620 )
621
622 def _adapt_weights(
623 self, weights: Iterable[tuple[str, torch.Tensor]]
624 ) -> Iterator[tuple[str, torch.Tensor]]:
625 pending_indexer_fp8: dict[tuple[str, str], dict[str, torch.Tensor]] = {}
626 for name, weight in weights:
627 if name.startswith("mtp."):
628 continue
629 if ".indexer." in name:
630 parts = name.split(".")
631 if "layers" in parts:
632 layer_idx = int(parts[parts.index("layers") + 1])
633 if (
634 layer_idx < self.config.num_hidden_layers
635 and self.config.layer_types[layer_idx] == "sliding_attention"
636 ):
637 continue
638
639 projection = next(
640 (
641 candidate
642 for candidate in ("wk", "weights_proj")
643 if f".indexer.{candidate}." in name
644 ),
645 None,
646 )
647 is_fp8_weight = (
648 name.endswith(".weight") and weight.dtype == torch.float8_e4m3fn
649 )
650 is_scale = "weight_scale" in name
651 if projection is not None and (is_fp8_weight or is_scale):
652 layer_prefix = name.rsplit(f".{projection}.", 1)[0]
653 key = (layer_prefix, projection)
654 entry = pending_indexer_fp8.setdefault(key, {})
655 entry["weight" if is_fp8_weight else "scale"] = weight
656 if "weight" not in entry or "scale" not in entry:
657 continue
658 weight_fp8 = entry["weight"]
659 scale_inv = entry["scale"]
660 del pending_indexer_fp8[key]
661 group_shape = GroupShape(
662 weight_fp8.shape[0] // scale_inv.shape[0],
663 weight_fp8.shape[1] // scale_inv.shape[1],
664 )
665 dequantized = scaled_dequantize(
666 weight_fp8,
667 scale_inv,
668 group_shape=group_shape,
669 out_dtype=torch.bfloat16,
670 )
671 yield f"{layer_prefix}.{projection}.weight", dequantized
672 continue
673 yield name, _pad_dense_mlp_weight(name, weight, self._weight_block_size)
674 if pending_indexer_fp8:
675 missing = ", ".join(
676 f"{prefix}.{proj}" for prefix, proj in pending_indexer_fp8
677 )
678 raise ValueError(f"Incomplete FP8 indexer weights: {missing}")
679
680 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
681 return super().load_weights(self._adapt_weights(weights))
682
683
684 class Dots3NoteLanguageModelForCausalLM(DeepseekV32ForCausalLM):
685 model_cls = Dots3NoteModel