94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
521 lines
20 KiB
Python
521 lines
20 KiB
Python
"""EAGLE3 / EAGLE3.1 draft model with MLA attention for Kimi-K2.x.
|
|
|
|
The ``kimi-k2.5-eagle3-mla`` checkpoint pairs an EAGLE3 layout
|
|
(concatenated [embed_norm, hidden_norm] pre-attention input, fc projection
|
|
over the concatenated multi-layer aux hidden states, single decoder layer,
|
|
dense MLP) with DeepSeek-V2 multi-latent attention. Sharing the MLA layout
|
|
with the Kimi-K2.x target keeps the draft KV cache small.
|
|
|
|
The eagle3.1 variant (e.g. ``kimi-k2.6-eagle3.1-mla``) adds two optional
|
|
config flags on top of the same layout:
|
|
|
|
* ``fc_norm``: per-chunk RMSNorm applied to each auxiliary hidden state
|
|
before the fc projection.
|
|
* ``norm_output``: emit post-norm (rather than pre-norm) hidden states as
|
|
the auxiliary output consumed by the next draft step.
|
|
"""
|
|
|
|
import copy
|
|
import logging
|
|
import re
|
|
from typing import Iterable, List, Optional, Tuple
|
|
|
|
import torch
|
|
from torch import nn
|
|
from transformers import PretrainedConfig
|
|
|
|
from sglang.srt.distributed import get_pp_group
|
|
from sglang.srt.distributed.device_communicators import triton_symm_mem_ag
|
|
from sglang.srt.layers.communicator import AttentionInputs, get_attn_tp_context
|
|
from sglang.srt.layers.layernorm import RMSNorm
|
|
from sglang.srt.layers.linear import ColumnParallelLinear, ReplicatedLinear
|
|
from sglang.srt.layers.logits_processor import LogitsProcessor
|
|
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
|
from sglang.srt.layers.vocab_parallel_embedding import (
|
|
ParallelLMHead,
|
|
VocabParallelEmbedding,
|
|
get_embedding_tp_kwargs,
|
|
)
|
|
from sglang.srt.model_executor.forward_batch_info import ForwardBatch, PPProxyTensors
|
|
from sglang.srt.model_loader.weight_utils import default_weight_loader
|
|
from sglang.srt.models.deepseek_v2 import DeepseekV2AttentionMLA, DeepseekV2MLP
|
|
from sglang.srt.utils import BumpAllocator, add_prefix
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _get_eagle_aux_layer_count(config: PretrainedConfig) -> int:
|
|
"""Number of target layers whose hidden states get concatenated into fc."""
|
|
eagle_config = getattr(config, "eagle_config", None)
|
|
if isinstance(eagle_config, dict):
|
|
layer_ids = eagle_config.get("eagle_aux_hidden_state_layer_ids")
|
|
else:
|
|
layer_ids = getattr(eagle_config, "eagle_aux_hidden_state_layer_ids", None)
|
|
if layer_ids is None:
|
|
layer_ids = getattr(config, "eagle_aux_hidden_state_layer_ids", None)
|
|
if layer_ids is None:
|
|
return 3
|
|
return len(layer_ids)
|
|
|
|
|
|
class Eagle3MLADecoderLayer(nn.Module):
|
|
"""One EAGLE3 draft layer that uses DeepSeek-V2 multi-latent attention.
|
|
|
|
Pre-attention concatenates the input embedding and the target hidden
|
|
state along the channel dim, doubling the input width to MLA's fused
|
|
QKV-down projection.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
config: PretrainedConfig,
|
|
layer_id: int = 0,
|
|
quant_config: Optional[QuantizationConfig] = None,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
self.hidden_size = config.hidden_size
|
|
|
|
if hasattr(config, "rope_parameters") and config.rope_parameters is not None:
|
|
rope_params = config.rope_parameters
|
|
rope_theta = rope_params.get("rope_theta", 10000)
|
|
rope_scaling = (
|
|
rope_params if rope_params.get("rope_type") != "default" else None
|
|
)
|
|
else:
|
|
rope_theta = config.rope_theta
|
|
rope_scaling = config.rope_scaling
|
|
max_position_embeddings = config.max_position_embeddings
|
|
|
|
self.self_attn = DeepseekV2AttentionMLA(
|
|
config=config,
|
|
hidden_size=config.hidden_size,
|
|
num_heads=config.num_attention_heads,
|
|
qk_nope_head_dim=config.qk_nope_head_dim,
|
|
qk_rope_head_dim=config.qk_rope_head_dim,
|
|
v_head_dim=config.v_head_dim,
|
|
q_lora_rank=config.q_lora_rank,
|
|
kv_lora_rank=config.kv_lora_rank,
|
|
rope_theta=rope_theta,
|
|
rope_scaling=rope_scaling,
|
|
max_position_embeddings=max_position_embeddings,
|
|
quant_config=quant_config,
|
|
layer_id=layer_id,
|
|
reduce_results=True,
|
|
prefix=add_prefix("self_attn", prefix),
|
|
)
|
|
|
|
# EAGLE3 doubles MLA's QKV-down input by concatenating
|
|
# input_layernorm(embed) and hidden_norm(target_hidden) along the
|
|
# feature dim. Replace the projection that DeepseekV2AttentionMLA
|
|
# built for a single-hidden input.
|
|
attn = self.self_attn
|
|
if attn.q_lora_rank is None:
|
|
raise ValueError(
|
|
"Eagle3 MLA layer requires q_lora_rank in the draft config"
|
|
)
|
|
attn.fused_qkv_a_proj_with_mqa = ReplicatedLinear(
|
|
2 * config.hidden_size,
|
|
attn.q_lora_rank + attn.kv_lora_rank + attn.qk_rope_head_dim,
|
|
bias=False,
|
|
quant_config=quant_config,
|
|
prefix=add_prefix("self_attn.fused_qkv_a_proj_with_mqa", prefix),
|
|
)
|
|
# Recompute fused-proj-dependent flags so they reflect the new input dim.
|
|
attn.has_fused_proj = True
|
|
attn.use_min_latency_fused_a_gemm = False
|
|
quant_method = getattr(attn.fused_qkv_a_proj_with_mqa, "quant_method", None)
|
|
attn.is_packed_weight = (
|
|
quant_method is not None
|
|
and hasattr(quant_method, "quant_config")
|
|
and quant_method.quant_config is not None
|
|
and quant_method.quant_config.get_name()
|
|
in {"awq", "awq_marlin", "moe_wna16"}
|
|
)
|
|
|
|
self.mlp = DeepseekV2MLP(
|
|
hidden_size=config.hidden_size,
|
|
intermediate_size=config.intermediate_size,
|
|
hidden_act=config.hidden_act,
|
|
quant_config=quant_config,
|
|
prefix=add_prefix("mlp", prefix),
|
|
)
|
|
|
|
self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
|
self.hidden_norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
|
self.post_attention_layernorm = RMSNorm(
|
|
config.hidden_size, eps=config.rms_norm_eps
|
|
)
|
|
|
|
def forward(
|
|
self,
|
|
positions: torch.Tensor,
|
|
embeds: torch.Tensor,
|
|
hidden_states: torch.Tensor,
|
|
forward_batch: ForwardBatch,
|
|
zero_allocator: BumpAllocator,
|
|
) -> Tuple[torch.Tensor, torch.Tensor]:
|
|
residual = hidden_states
|
|
embeds = self.input_layernorm(embeds)
|
|
hidden_states = self.hidden_norm(hidden_states)
|
|
attn_input = torch.cat([embeds, hidden_states], dim=-1)
|
|
|
|
# MLA's forward_absorb_prepare reads the qkv-down projection result
|
|
# from attn_tp_context. We bypass LayerCommunicator here (the eagle3
|
|
# draft layer is one isolated layer with custom pre-attention norms),
|
|
# so publish the attention input ourselves.
|
|
get_attn_tp_context().set_attn_inputs(
|
|
AttentionInputs(
|
|
attn_input,
|
|
forward_batch,
|
|
self.self_attn.prepare_qkv_latent,
|
|
)
|
|
)
|
|
|
|
attn_out = self.self_attn(
|
|
positions=positions,
|
|
hidden_states=attn_input,
|
|
forward_batch=forward_batch,
|
|
zero_allocator=zero_allocator,
|
|
)
|
|
if isinstance(attn_out, tuple):
|
|
attn_out = attn_out[0]
|
|
|
|
hidden_states, residual = self.post_attention_layernorm(attn_out, residual)
|
|
hidden_states = self.mlp(hidden_states)
|
|
return hidden_states, residual
|
|
|
|
|
|
class Eagle3MLAModel(nn.Module):
|
|
def __init__(
|
|
self,
|
|
config: PretrainedConfig,
|
|
quant_config: Optional[QuantizationConfig] = None,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
self.config = config
|
|
self.vocab_size = config.vocab_size
|
|
|
|
self.embed_tokens = VocabParallelEmbedding(
|
|
config.vocab_size,
|
|
config.hidden_size,
|
|
prefix=add_prefix("embed_tokens", prefix),
|
|
**get_embedding_tp_kwargs(),
|
|
)
|
|
|
|
target_hidden_size = (
|
|
getattr(config, "target_hidden_size", None) or config.hidden_size
|
|
)
|
|
self.num_aux_hidden_states = _get_eagle_aux_layer_count(config)
|
|
self.fc = ColumnParallelLinear(
|
|
target_hidden_size * self.num_aux_hidden_states,
|
|
config.hidden_size,
|
|
bias=getattr(config, "bias", False),
|
|
gather_output=False,
|
|
quant_config=quant_config,
|
|
prefix=add_prefix("fc", prefix),
|
|
)
|
|
# Guarded multimem all-gather for the fc output; buffer covers prefill.
|
|
self._fc_gatherer = triton_symm_mem_ag.MultimemAllGatherer(
|
|
max_tokens=triton_symm_mem_ag.recommended_max_tokens(
|
|
include_prefill=True, floor=512
|
|
),
|
|
skip_entry_sync=False,
|
|
)
|
|
|
|
# Per-aux RMSNorm before fc; enabled via `fc_norm` or legacy
|
|
# `use_aux_norm` flag. Matches the eagle3.1 layout.
|
|
use_fc_norm = getattr(config, "fc_norm", None) or getattr(
|
|
config, "use_aux_norm", False
|
|
)
|
|
if use_fc_norm:
|
|
self.fc_norm = nn.ModuleList(
|
|
[
|
|
RMSNorm(target_hidden_size, eps=config.rms_norm_eps)
|
|
for _ in range(self.num_aux_hidden_states)
|
|
]
|
|
)
|
|
else:
|
|
self.fc_norm = None
|
|
|
|
if config.num_hidden_layers != 1:
|
|
raise ValueError("EAGLE3 currently only supports 1 layer")
|
|
self.midlayer = Eagle3MLADecoderLayer(
|
|
config,
|
|
layer_id=0,
|
|
quant_config=quant_config,
|
|
prefix=prefix,
|
|
)
|
|
|
|
self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
|
# Draft decode captures pre-norm hidden by default; eagle3.1 opts for
|
|
# post-norm via `norm_output: true`.
|
|
self.norm_output = getattr(config, "norm_output", False)
|
|
|
|
def forward(
|
|
self,
|
|
input_ids: torch.Tensor,
|
|
positions: torch.Tensor,
|
|
forward_batch: ForwardBatch,
|
|
input_embeds: torch.Tensor = None,
|
|
pp_proxy_tensors: Optional[PPProxyTensors] = None,
|
|
) -> Tuple[torch.Tensor, List[torch.Tensor]]:
|
|
if input_embeds is None:
|
|
# MM positions in input_ids hold MM_PAD_SHIFT_VALUE+hash sentinels (far above
|
|
# vocab_size). Use target-produced mm_input_embeds for these positions and
|
|
# only call embed_tokens on the appended next-token to avoid embed OOB.
|
|
embeds = forward_batch.mm_input_embeds
|
|
if (
|
|
forward_batch.forward_mode.is_extend()
|
|
and forward_batch.contains_mm_inputs()
|
|
and not forward_batch.forward_mode.is_draft_extend_v2()
|
|
):
|
|
assert embeds is not None
|
|
last_indices = (
|
|
forward_batch.extend_start_loc + forward_batch.extend_seq_lens - 1
|
|
).long()
|
|
embeds[last_indices] = self.embed_tokens(input_ids[last_indices])
|
|
if embeds is None:
|
|
embeds = self.embed_tokens(input_ids)
|
|
else:
|
|
embeds = input_embeds
|
|
|
|
hidden_states = forward_batch.spec_info.hidden_states
|
|
if hidden_states.shape[-1] != embeds.shape[-1]:
|
|
if self.fc_norm is not None:
|
|
chunks = hidden_states.chunk(self.num_aux_hidden_states, dim=-1)
|
|
hidden_states = torch.cat(
|
|
[norm(chunk) for norm, chunk in zip(self.fc_norm, chunks)],
|
|
dim=-1,
|
|
)
|
|
hidden_states, _ = self.fc(hidden_states)
|
|
hidden_states = self._fc_gatherer(hidden_states)
|
|
|
|
if hidden_states.shape[0] == 0:
|
|
return hidden_states, [hidden_states]
|
|
|
|
zero_allocator = BumpAllocator(
|
|
buffer_size=2,
|
|
dtype=torch.float32,
|
|
device=embeds.device,
|
|
)
|
|
hidden_states, residual = self.midlayer(
|
|
positions, embeds, hidden_states, forward_batch, zero_allocator
|
|
)
|
|
|
|
hidden_states_to_logits, hidden_states_to_aux = self.norm(
|
|
hidden_states, residual
|
|
)
|
|
aux = hidden_states_to_logits if self.norm_output else hidden_states_to_aux
|
|
return hidden_states_to_logits, [aux]
|
|
|
|
|
|
class Eagle3DeepseekV2ForCausalLM(nn.Module):
|
|
"""EAGLE3 draft model architecture with DeepSeek-V2 MLA attention.
|
|
|
|
Used by checkpoints like ``kimi-k2.5-eagle3-mla`` that pair
|
|
an EAGLE3 layout with multi-latent attention so the draft KV cache shape
|
|
matches the target's MLA cache.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
config: PretrainedConfig,
|
|
quant_config: Optional[QuantizationConfig] = None,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
self.config = config
|
|
# Match deepseek_nextn behavior: modelopt_fp4 is target-only and the
|
|
# bf16 draft must not inherit the FP4 quant method.
|
|
if quant_config is not None and quant_config.get_name() == "modelopt_fp4":
|
|
logger.warning(
|
|
"Overriding Eagle3DeepseekV2ForCausalLM quant config for "
|
|
"modelopt_fp4 target; draft weights are bf16."
|
|
)
|
|
quant_config = None
|
|
self.quant_config = quant_config
|
|
self.pp_group = get_pp_group()
|
|
|
|
self.model = Eagle3MLAModel(
|
|
config, quant_config=quant_config, prefix=add_prefix("model", prefix)
|
|
)
|
|
|
|
# llama_eagle3 sets a load-from-target flag when draft_vocab_size is
|
|
# missing. This checkpoint declares its own draft head, so keep ours.
|
|
self.load_lm_head_from_target = False
|
|
draft_vocab_size = getattr(config, "draft_vocab_size", None)
|
|
if config.tie_word_embeddings:
|
|
self.lm_head = self.model.embed_tokens
|
|
else:
|
|
if draft_vocab_size is None:
|
|
self.load_lm_head_from_target = True
|
|
draft_vocab_size = config.vocab_size
|
|
config.draft_vocab_size = draft_vocab_size
|
|
self.lm_head = ParallelLMHead(
|
|
draft_vocab_size,
|
|
config.hidden_size,
|
|
quant_config=quant_config,
|
|
prefix=add_prefix("lm_head", prefix),
|
|
)
|
|
|
|
# Logits processor sees the draft vocab.
|
|
config_for_logits = copy.deepcopy(config)
|
|
config_for_logits.vocab_size = draft_vocab_size or config.vocab_size
|
|
self.logits_processor = LogitsProcessor(config_for_logits)
|
|
|
|
self.capture_aux_hidden_states = True
|
|
self.hot_token_id = None
|
|
|
|
@torch.no_grad()
|
|
def forward(
|
|
self,
|
|
input_ids: torch.Tensor,
|
|
positions: torch.Tensor,
|
|
forward_batch: ForwardBatch,
|
|
input_embeds: torch.Tensor = None,
|
|
pp_proxy_tensors: Optional[PPProxyTensors] = None,
|
|
) -> torch.Tensor:
|
|
with get_attn_tp_context().maybe_input_scattered(forward_batch):
|
|
hidden_states = self.model(
|
|
input_ids, positions, forward_batch, input_embeds, pp_proxy_tensors
|
|
)
|
|
aux_hidden_states = None
|
|
if isinstance(hidden_states, tuple):
|
|
hidden_states, aux_hidden_states = hidden_states
|
|
return self.logits_processor(
|
|
input_ids,
|
|
hidden_states,
|
|
self.lm_head,
|
|
forward_batch,
|
|
aux_hidden_states,
|
|
)
|
|
|
|
def get_input_embeddings(self) -> nn.Embedding:
|
|
return self.model.embed_tokens
|
|
|
|
def get_embed_and_head(self):
|
|
return self.model.embed_tokens.weight, self.lm_head.weight
|
|
|
|
def set_embed(self, embed: torch.Tensor) -> None:
|
|
# If draft hidden size != target hidden size, embeddings can't be shared.
|
|
if (
|
|
hasattr(self.config, "target_hidden_size")
|
|
and self.config.target_hidden_size is not None
|
|
and self.config.target_hidden_size != self.config.hidden_size
|
|
):
|
|
return
|
|
del self.model.embed_tokens.weight
|
|
self.model.embed_tokens.weight = embed
|
|
torch.cuda.empty_cache()
|
|
torch.cuda.synchronize()
|
|
|
|
def set_embed_and_head(self, embed: torch.Tensor, head: torch.Tensor) -> None:
|
|
del self.model.embed_tokens.weight
|
|
del self.lm_head.weight
|
|
self.model.embed_tokens.weight = embed
|
|
self.lm_head.weight = head
|
|
torch.cuda.empty_cache()
|
|
torch.cuda.synchronize()
|
|
|
|
def get_hot_token_id(self):
|
|
return self.hot_token_id
|
|
|
|
def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]) -> None:
|
|
params_dict = dict(self.named_parameters())
|
|
stacked_params_mapping = [
|
|
(".gate_up_proj", ".gate_proj", 0),
|
|
(".gate_up_proj", ".up_proj", 1),
|
|
]
|
|
cached_a_proj: dict[str, torch.Tensor] = {}
|
|
|
|
for name, loaded_weight in weights:
|
|
if name == "d2t" or name.endswith(".d2t"):
|
|
# d2t stores diffs between draft id and target id; absent in
|
|
# checkpoints whose draft_vocab_size equals vocab_size.
|
|
self.hot_token_id = loaded_weight + torch.arange(loaded_weight.shape[0])
|
|
continue
|
|
if name == "t2d" or name.endswith(".t2d"):
|
|
continue
|
|
|
|
# Map checkpoint layout (layers.0.X, embed_tokens.X, fc, norm) to the
|
|
# internal layout (model.midlayer.X, model.embed_tokens.X, model.fc,
|
|
# model.norm). lm_head stays at the top level.
|
|
mapped_name = re.sub(r"^layers\.0\.", "midlayer.", name)
|
|
if not mapped_name.startswith("lm_head."):
|
|
mapped_name = f"model.{mapped_name}"
|
|
|
|
handled = False
|
|
for param_name, weight_name, shard_id in stacked_params_mapping:
|
|
if weight_name not in mapped_name:
|
|
continue
|
|
target_name = mapped_name.replace(weight_name, param_name)
|
|
if target_name not in params_dict:
|
|
continue
|
|
param = params_dict[target_name]
|
|
weight_loader = getattr(param, "weight_loader", default_weight_loader)
|
|
weight_loader(param, loaded_weight, shard_id)
|
|
handled = True
|
|
break
|
|
if handled:
|
|
continue
|
|
|
|
if "q_a_proj" in mapped_name or "kv_a_proj_with_mqa" in mapped_name:
|
|
cached_a_proj[mapped_name] = loaded_weight
|
|
q_name = (
|
|
mapped_name
|
|
if "q_a_proj" in mapped_name
|
|
else mapped_name.replace("kv_a_proj_with_mqa", "q_a_proj")
|
|
)
|
|
kv_name = (
|
|
mapped_name
|
|
if "kv_a_proj_with_mqa" in mapped_name
|
|
else mapped_name.replace("q_a_proj", "kv_a_proj_with_mqa")
|
|
)
|
|
if q_name in cached_a_proj and kv_name in cached_a_proj:
|
|
fused_weight = torch.cat(
|
|
[cached_a_proj[q_name], cached_a_proj[kv_name]], dim=0
|
|
)
|
|
fused_name = q_name.replace("q_a_proj", "fused_qkv_a_proj_with_mqa")
|
|
if fused_name in params_dict:
|
|
param = params_dict[fused_name]
|
|
weight_loader = getattr(
|
|
param, "weight_loader", default_weight_loader
|
|
)
|
|
weight_loader(param, fused_weight)
|
|
cached_a_proj.pop(q_name)
|
|
cached_a_proj.pop(kv_name)
|
|
continue
|
|
|
|
if mapped_name not in params_dict:
|
|
logger.warning("Eagle3 MLA: skipping unexpected weight %s", name)
|
|
continue
|
|
param = params_dict[mapped_name]
|
|
weight_loader = getattr(param, "weight_loader", default_weight_loader)
|
|
weight_loader(param, loaded_weight)
|
|
|
|
self.post_load_weights()
|
|
|
|
def post_load_weights(self) -> None:
|
|
"""Split kv_b_proj into w_kc / w_vc tensors used by MLA absorb_core.
|
|
|
|
DeepseekV2 normally does this in DeepseekV2WeightLoaderMixin.post_load_weights;
|
|
we re-implement the bf16 fast-path directly here to keep the eagle3 draft
|
|
path independent of the full DeepseekV2 weight loader.
|
|
"""
|
|
self_attn = self.model.midlayer.self_attn
|
|
w = self_attn.kv_b_proj.weight
|
|
if w.dtype not in (torch.bfloat16, torch.float16, torch.float32):
|
|
raise NotImplementedError(
|
|
f"Eagle3 MLA draft post_load_weights only supports float dtypes, got {w.dtype}"
|
|
)
|
|
w_kc, w_vc = w.unflatten(
|
|
0, (-1, self_attn.qk_nope_head_dim + self_attn.v_head_dim)
|
|
).split([self_attn.qk_nope_head_dim, self_attn.v_head_dim], dim=1)
|
|
self_attn.w_kc = w_kc.transpose(1, 2).contiguous().transpose(1, 2)
|
|
self_attn.w_vc = w_vc.contiguous().transpose(1, 2)
|
|
|
|
|
|
EntryClass = [Eagle3DeepseekV2ForCausalLM]
|