Signed-off-by: timka <timka.byzov@yandex.ru> Co-authored-by: Codex <noreply@openai.com>
734 lines
28 KiB
Python
734 lines
28 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
"""Inference-only AliceAI model."""
|
|
|
|
from collections.abc import Iterable
|
|
from copy import copy
|
|
from itertools import islice
|
|
from typing import cast
|
|
|
|
import torch
|
|
from torch import nn
|
|
from transformers import PreTrainedConfig, Qwen3NextConfig
|
|
|
|
from vllm.compilation.decorators import support_torch_compile
|
|
from vllm.config import VllmConfig
|
|
from vllm.distributed import (
|
|
get_ep_group,
|
|
get_pp_group,
|
|
get_tensor_model_parallel_world_size,
|
|
)
|
|
from vllm.model_executor.layers.fused_moe import FusedMoEFactory, GateLinear
|
|
from vllm.model_executor.layers.fused_moe.utils import (
|
|
is_model_fused_shared_expert_compatible,
|
|
resolve_layer_fused_shared_expert,
|
|
)
|
|
from vllm.model_executor.layers.layernorm import GemmaRMSNorm
|
|
from vllm.model_executor.layers.linear import ReplicatedLinear
|
|
from vllm.model_executor.layers.logits_processor import LogitsProcessor
|
|
from vllm.model_executor.layers.mamba.gdn.kimi_gdn_linear_attn import (
|
|
KimiGatedDeltaNetAttention,
|
|
)
|
|
from vllm.model_executor.layers.mamba.mamba_utils import (
|
|
MambaStateCopyFunc,
|
|
MambaStateCopyFuncCalculator,
|
|
MambaStateDtypeCalculator,
|
|
MambaStateShapeCalculator,
|
|
)
|
|
from vllm.model_executor.layers.quantization.utils.config_utils import (
|
|
get_quark_ocp_mx_group_size,
|
|
)
|
|
from vllm.model_executor.layers.vocab_parallel_embedding import (
|
|
ParallelLMHead,
|
|
VocabParallelEmbedding,
|
|
)
|
|
from vllm.model_executor.models.interfaces import (
|
|
HasInnerState,
|
|
IsHybrid,
|
|
MixtureOfExperts,
|
|
SupportsLoRA,
|
|
)
|
|
from vllm.model_executor.models.qwen2_moe import Qwen2MoeMLP
|
|
from vllm.model_executor.models.qwen3_next import (
|
|
Qwen3NextAttention,
|
|
_should_replicate_misaligned_shared_expert,
|
|
)
|
|
from vllm.model_executor.models.utils import (
|
|
AutoWeightsLoader,
|
|
WeightsMapper,
|
|
extract_layer_index,
|
|
make_layers,
|
|
maybe_fuse_shared_experts,
|
|
maybe_prefix,
|
|
)
|
|
from vllm.model_executor.offloader import (
|
|
PrefetchOffloader,
|
|
UVAOffloader,
|
|
get_offloader,
|
|
)
|
|
from vllm.models.kimi_k3.nvidia.ops import attn_res
|
|
from vllm.sequence import IntermediateTensors
|
|
from vllm.transformers_utils.configs.kimi_linear import KimiLinearConfig
|
|
|
|
_SPLIT_KDA_CONV_SHARDS = {
|
|
".linear_attn.q_conv1d": (".linear_attn.conv1d", 0),
|
|
".linear_attn.k_conv1d": (".linear_attn.conv1d", 1),
|
|
".linear_attn.v_conv1d": (".linear_attn.conv1d", 2),
|
|
}
|
|
|
|
|
|
def _load_split_kda_conv_weights(
|
|
weights: Iterable[tuple[str, torch.Tensor]],
|
|
params: dict[str, nn.Parameter],
|
|
loaded: set[str],
|
|
) -> Iterable[tuple[str, torch.Tensor]]:
|
|
"""Load split AliceAI conv weights through the upstream fused-conv ABI."""
|
|
for name, weight in weights:
|
|
for source_name, (target_name, shard_id) in _SPLIT_KDA_CONV_SHARDS.items():
|
|
if source_name not in name:
|
|
continue
|
|
mapped_name = name.replace(source_name, target_name, 1)
|
|
param = params.get(mapped_name)
|
|
if param is None:
|
|
raise ValueError(f"No target parameter for {name!r}: {mapped_name!r}")
|
|
weight_loader = getattr(param, "weight_loader", None)
|
|
if weight_loader is None:
|
|
raise ValueError(
|
|
f"Target parameter {mapped_name!r} has no weight loader"
|
|
)
|
|
weight_loader(param, weight, shard_id)
|
|
loaded.add(mapped_name)
|
|
break
|
|
else:
|
|
yield name, weight
|
|
|
|
|
|
def get_attn_res_block_size(hf_config: object) -> int:
|
|
"""Return the architecture-rebase AttnRes grouping, not legacy metadata."""
|
|
block_size = getattr(hf_config, "block_attn_res_block_size", None)
|
|
if isinstance(block_size, bool) or not isinstance(block_size, int):
|
|
raise ValueError(
|
|
"AliceAI requires a positive integer block_attn_res_block_size"
|
|
)
|
|
if block_size <= 0:
|
|
raise ValueError("block_attn_res_block_size must be positive")
|
|
return block_size
|
|
|
|
|
|
def _make_kimi_kda_config(config: PreTrainedConfig) -> KimiLinearConfig:
|
|
"""Adapt the flat AliceAI KDA geometry to the existing upstream Kimi layer."""
|
|
num_heads = config.linear_num_key_heads
|
|
num_value_heads = config.linear_num_value_heads
|
|
head_dim = config.linear_key_head_dim
|
|
value_head_dim = config.linear_value_head_dim
|
|
if num_heads != num_value_heads or head_dim != value_head_dim:
|
|
raise ValueError(
|
|
"AliceAI KDA requires matching key/value head counts and dimensions"
|
|
)
|
|
|
|
adapted = copy(config)
|
|
adapted.linear_attn_config = {
|
|
"head_dim": head_dim,
|
|
"num_heads": num_heads,
|
|
"short_conv_kernel_size": config.linear_conv_kernel_dim,
|
|
"use_full_rank_gate": False,
|
|
"gate_lower_bound": None,
|
|
}
|
|
return cast(KimiLinearConfig, adapted)
|
|
|
|
|
|
def _make_alice_ai_full_attention_config(
|
|
config: PreTrainedConfig,
|
|
) -> Qwen3NextConfig:
|
|
adapted = copy(config)
|
|
adapted.attn_output_gate = True
|
|
adapted.qkv_bias = False
|
|
adapted.dual_chunk_attention_config = None
|
|
adapted.is_causal = True
|
|
return cast(Qwen3NextConfig, adapted)
|
|
|
|
|
|
class _AttentionResidualFinal(nn.Module):
|
|
def __init__(self, hidden_size: int, rms_norm_eps: float, prefix: str) -> None:
|
|
super().__init__()
|
|
self.rms_norm_eps = rms_norm_eps
|
|
self.res_proj = ReplicatedLinear(
|
|
input_size=hidden_size,
|
|
output_size=1,
|
|
bias=False,
|
|
prefix=f"{prefix}.res_proj",
|
|
)
|
|
self.res_norm_weight = nn.Parameter(torch.ones(hidden_size))
|
|
|
|
def forward(
|
|
self,
|
|
residual_bank: torch.Tensor,
|
|
num_completed_sources: int,
|
|
partial: torch.Tensor | None,
|
|
) -> torch.Tensor:
|
|
if partial is None:
|
|
if num_completed_sources < 1:
|
|
raise RuntimeError("AttnRes requires at least one residual source")
|
|
prefix = residual_bank[:, num_completed_sources - 1]
|
|
num_blocks = num_completed_sources - 1
|
|
else:
|
|
prefix = partial
|
|
num_blocks = num_completed_sources
|
|
return attn_res(
|
|
prefix=prefix,
|
|
delta=None,
|
|
blocks=residual_bank,
|
|
norm_weight=self.res_norm_weight,
|
|
qk_weight=self.res_proj.weight.squeeze(0),
|
|
output_norm_weight=None,
|
|
num_blocks=num_blocks,
|
|
block_write_idx=-1,
|
|
eps=self.rms_norm_eps,
|
|
output_norm_eps=0.0,
|
|
)
|
|
|
|
|
|
class AliceAISparseMoeBlock(nn.Module):
|
|
def __init__(self, vllm_config: VllmConfig, prefix: str = "") -> None:
|
|
super().__init__()
|
|
|
|
config = vllm_config.model_config.hf_text_config
|
|
parallel_config = vllm_config.parallel_config
|
|
quant_config = vllm_config.quant_config
|
|
|
|
self.tp_size = get_tensor_model_parallel_world_size()
|
|
self.ep_size = get_ep_group().device_group.size()
|
|
self.n_routed_experts = config.num_experts
|
|
if self.tp_size > self.n_routed_experts:
|
|
raise ValueError(
|
|
f"Tensor parallel size {self.tp_size} is greater than "
|
|
f"the number of experts {self.n_routed_experts}."
|
|
)
|
|
|
|
self.is_fused_shared_expert_enabled = False
|
|
if config.shared_expert_intermediate_size > 0:
|
|
self.is_fused_shared_expert_enabled = resolve_layer_fused_shared_expert(
|
|
quant_config=quant_config,
|
|
prefix=prefix,
|
|
shared_expert_name="shared_expert",
|
|
)
|
|
|
|
self.replicate_shared_expert = False
|
|
if not self.is_fused_shared_expert_enabled:
|
|
shared_expert_group_size = get_quark_ocp_mx_group_size(
|
|
quant_config=quant_config,
|
|
layer_name=f"{prefix}.shared_expert.down_proj",
|
|
)
|
|
self.replicate_shared_expert = _should_replicate_misaligned_shared_expert(
|
|
intermediate_size=config.shared_expert_intermediate_size,
|
|
tp_size=self.tp_size,
|
|
group_size=shared_expert_group_size,
|
|
enable_expert_parallel=parallel_config.enable_expert_parallel,
|
|
is_sequence_parallel=False,
|
|
)
|
|
|
|
self.enable_eplb = parallel_config.enable_eplb
|
|
self.n_logical_experts = self.n_routed_experts
|
|
self.n_redundant_experts = parallel_config.eplb_config.num_redundant_experts
|
|
self.n_physical_experts = self.n_logical_experts + self.n_redundant_experts
|
|
self.n_local_physical_experts = self.n_physical_experts // self.ep_size
|
|
|
|
self.gate = GateLinear(
|
|
input_size=config.hidden_size,
|
|
output_size=config.num_experts,
|
|
prefix=f"{prefix}.gate",
|
|
)
|
|
self.gate.e_score_correction_bias = nn.Parameter(
|
|
torch.empty(config.num_experts, dtype=torch.float32),
|
|
requires_grad=False,
|
|
)
|
|
self.shared_expert_gate = ReplicatedLinear(
|
|
input_size=config.hidden_size,
|
|
output_size=1,
|
|
bias=False,
|
|
prefix=f"{prefix}.shared_expert_gate",
|
|
)
|
|
|
|
if (
|
|
self.is_fused_shared_expert_enabled
|
|
or config.shared_expert_intermediate_size <= 0
|
|
):
|
|
self.shared_expert = None
|
|
else:
|
|
self.shared_expert = Qwen2MoeMLP(
|
|
hidden_size=config.hidden_size,
|
|
intermediate_size=config.shared_expert_intermediate_size,
|
|
hidden_act=config.hidden_act,
|
|
quant_config=quant_config,
|
|
reduce_results=False,
|
|
expert_gate=self.shared_expert_gate,
|
|
disable_tp=self.replicate_shared_expert,
|
|
prefix=f"{prefix}.shared_expert",
|
|
)
|
|
|
|
self.experts = FusedMoEFactory(
|
|
shared_experts=(
|
|
None if self.replicate_shared_expert else self.shared_expert
|
|
),
|
|
gate=self.gate,
|
|
num_experts=self.n_routed_experts,
|
|
top_k=config.num_experts_per_tok,
|
|
hidden_size=config.hidden_size,
|
|
intermediate_size=config.moe_intermediate_size,
|
|
renormalize=getattr(config, "norm_topk_prob", True),
|
|
scoring_func="sigmoid",
|
|
e_score_correction_bias=self.gate.e_score_correction_bias,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.experts",
|
|
enable_eplb=self.enable_eplb,
|
|
num_redundant_experts=self.n_redundant_experts,
|
|
n_shared_experts=1 if self.shared_expert is None else None,
|
|
fuse_shared_experts=self.is_fused_shared_expert_enabled,
|
|
shared_expert_prefix=f"{prefix}.shared_expert",
|
|
shared_expert_gate=self.shared_expert_gate
|
|
if self.shared_expert is None
|
|
else None,
|
|
)
|
|
|
|
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
|
orig_shape = hidden_states.shape
|
|
hidden_states = hidden_states.view(-1, hidden_states.shape[-1])
|
|
shared_output = (
|
|
self.shared_expert(hidden_states)
|
|
if self.replicate_shared_expert and self.shared_expert is not None
|
|
else None
|
|
)
|
|
output = self.experts(hidden_states=hidden_states, router_logits=hidden_states)
|
|
if shared_output is not None:
|
|
output += shared_output
|
|
return output.view(orig_shape)
|
|
|
|
|
|
class AliceAIDecoderBlock(nn.Module):
|
|
"""AliceAI topology with upstream Qwen, Kimi KDA, MoE and AttnRes operations."""
|
|
|
|
def __init__(
|
|
self,
|
|
vllm_config: VllmConfig,
|
|
layer_type: str,
|
|
prefix: str = "",
|
|
) -> None:
|
|
super().__init__()
|
|
|
|
config = vllm_config.model_config.hf_text_config
|
|
model_config = vllm_config.model_config
|
|
cache_config = vllm_config.cache_config
|
|
parallel_config = vllm_config.parallel_config
|
|
quant_config = vllm_config.quant_config
|
|
|
|
if config.num_experts <= 0:
|
|
raise ValueError("AliceAI requires sparse experts")
|
|
if parallel_config.use_sequence_parallel_moe:
|
|
raise NotImplementedError("AliceAI does not support sequence-parallel MoE")
|
|
|
|
self.layer_type = layer_type
|
|
self.layer_idx = extract_layer_index(prefix)
|
|
self.attn_res_block_size = get_attn_res_block_size(config)
|
|
self.attn_res_rms_norm_eps = float(config.rms_norm_eps)
|
|
|
|
if layer_type == "linear_attention":
|
|
self.linear_attn = KimiGatedDeltaNetAttention(
|
|
config=_make_kimi_kda_config(config),
|
|
vllm_config=vllm_config,
|
|
prefix=f"{prefix}.linear_attn",
|
|
)
|
|
self.linear_attn.o_norm.eps = float(config.rms_norm_eps)
|
|
elif layer_type == "full_attention":
|
|
self.self_attn = Qwen3NextAttention(
|
|
config=_make_alice_ai_full_attention_config(config),
|
|
model_config=model_config,
|
|
cache_config=cache_config,
|
|
quant_config=quant_config,
|
|
prefix=f"{prefix}.self_attn",
|
|
)
|
|
else:
|
|
raise ValueError(f"Invalid layer_type {layer_type}")
|
|
|
|
self.mlp = AliceAISparseMoeBlock(
|
|
vllm_config=vllm_config,
|
|
prefix=f"{prefix}.mlp",
|
|
)
|
|
self.input_layernorm = GemmaRMSNorm(
|
|
hidden_size=config.hidden_size, eps=config.rms_norm_eps
|
|
)
|
|
self.post_attention_layernorm = GemmaRMSNorm(
|
|
hidden_size=config.hidden_size, eps=config.rms_norm_eps
|
|
)
|
|
|
|
if self.layer_idx > 0:
|
|
self.attn_res_proj = ReplicatedLinear(
|
|
input_size=config.hidden_size,
|
|
output_size=1,
|
|
bias=False,
|
|
prefix=f"{prefix}.attn_res_proj",
|
|
)
|
|
self.attn_res_norm_weight = nn.Parameter(torch.ones(config.hidden_size))
|
|
self.mlp_res_proj = ReplicatedLinear(
|
|
input_size=config.hidden_size,
|
|
output_size=1,
|
|
bias=False,
|
|
prefix=f"{prefix}.mlp_res_proj",
|
|
)
|
|
self.mlp_res_norm_weight = nn.Parameter(torch.ones(config.hidden_size))
|
|
|
|
def _apply_attention_residual(
|
|
self,
|
|
residual_bank: torch.Tensor,
|
|
num_completed_sources: int,
|
|
partial: torch.Tensor | None,
|
|
*,
|
|
projection: ReplicatedLinear,
|
|
norm_weight_name: str,
|
|
) -> torch.Tensor:
|
|
if partial is None:
|
|
if num_completed_sources < 1:
|
|
raise RuntimeError("AttnRes requires at least one residual source")
|
|
prefix = residual_bank[:, num_completed_sources - 1]
|
|
num_blocks = num_completed_sources - 1
|
|
else:
|
|
prefix = partial
|
|
num_blocks = num_completed_sources
|
|
return attn_res(
|
|
prefix=prefix,
|
|
delta=None,
|
|
blocks=residual_bank,
|
|
norm_weight=getattr(self, norm_weight_name),
|
|
qk_weight=projection.weight.squeeze(0),
|
|
output_norm_weight=None,
|
|
num_blocks=num_blocks,
|
|
block_write_idx=-1,
|
|
eps=self.attn_res_rms_norm_eps,
|
|
output_norm_eps=0.0,
|
|
)
|
|
|
|
def _apply_attention(
|
|
self, hidden_states: torch.Tensor, positions: torch.Tensor
|
|
) -> torch.Tensor:
|
|
if self.layer_type == "linear_attention":
|
|
return self.linear_attn(hidden_states=hidden_states, positions=positions)
|
|
return self.self_attn(hidden_states=hidden_states, positions=positions)
|
|
|
|
def forward_attn_res(
|
|
self,
|
|
positions: torch.Tensor,
|
|
residual_bank: torch.Tensor,
|
|
num_completed_sources: int,
|
|
partial: torch.Tensor | None,
|
|
) -> tuple[int, torch.Tensor]:
|
|
if self.layer_idx > 0 and self.layer_idx % self.attn_res_block_size == 0:
|
|
if partial is None:
|
|
raise RuntimeError("cannot finalize an empty AttnRes block")
|
|
residual_bank[:, num_completed_sources].copy_(partial)
|
|
num_completed_sources += 1
|
|
partial = None
|
|
|
|
if self.layer_idx == 0:
|
|
hidden_states = residual_bank[:, 0]
|
|
else:
|
|
hidden_states = self._apply_attention_residual(
|
|
residual_bank=residual_bank,
|
|
num_completed_sources=num_completed_sources,
|
|
partial=partial,
|
|
projection=self.attn_res_proj,
|
|
norm_weight_name="attn_res_norm_weight",
|
|
)
|
|
hidden_states = self.input_layernorm(hidden_states)
|
|
attention_output = self._apply_attention(
|
|
hidden_states=hidden_states, positions=positions
|
|
)
|
|
partial = attention_output if partial is None else partial + attention_output
|
|
|
|
hidden_states = self._apply_attention_residual(
|
|
residual_bank=residual_bank,
|
|
num_completed_sources=num_completed_sources,
|
|
partial=partial,
|
|
projection=self.mlp_res_proj,
|
|
norm_weight_name="mlp_res_norm_weight",
|
|
)
|
|
hidden_states = self.post_attention_layernorm(hidden_states)
|
|
mlp_output = self.mlp(hidden_states)
|
|
return num_completed_sources, partial + mlp_output
|
|
|
|
|
|
@support_torch_compile
|
|
class AliceAIModel(nn.Module):
|
|
hf_to_vllm_mapper = WeightsMapper(
|
|
orig_to_new_substr={
|
|
".linear_attn.a_log_bias": ".linear_attn.A_log",
|
|
},
|
|
orig_to_new_stacked={
|
|
".linear_attn.q_proj": (".linear_attn.in_proj_qkvgfab", 0),
|
|
".linear_attn.k_proj": (".linear_attn.in_proj_qkvgfab", 1),
|
|
".linear_attn.v_proj": (".linear_attn.in_proj_qkvgfab", 2),
|
|
".linear_attn.b_proj": (".linear_attn.in_proj_qkvgfab", 3),
|
|
".linear_attn.f_a_proj": (".linear_attn.in_proj_qkvgfab", 4),
|
|
".self_attn.q_proj": (".self_attn.qkv_proj", "q"),
|
|
".self_attn.k_proj": (".self_attn.qkv_proj", "k"),
|
|
".self_attn.v_proj": (".self_attn.qkv_proj", "v"),
|
|
".shared_expert.gate_proj": (".shared_expert.gate_up_proj", 0),
|
|
".shared_expert.up_proj": (".shared_expert.gate_up_proj", 1),
|
|
},
|
|
)
|
|
|
|
def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
|
|
super().__init__()
|
|
config = vllm_config.model_config.hf_text_config
|
|
self.config = config
|
|
self.vocab_size = config.vocab_size
|
|
self.num_redundant_experts = (
|
|
vllm_config.parallel_config.eplb_config.num_redundant_experts
|
|
)
|
|
self.attn_res_block_size = get_attn_res_block_size(config)
|
|
self.embed_tokens = VocabParallelEmbedding(
|
|
num_embeddings=config.vocab_size, embedding_dim=config.hidden_size
|
|
)
|
|
|
|
def get_layer(prefix: str) -> AliceAIDecoderBlock:
|
|
return AliceAIDecoderBlock(
|
|
vllm_config=vllm_config,
|
|
layer_type=config.layer_types[extract_layer_index(prefix)],
|
|
prefix=prefix,
|
|
)
|
|
|
|
self.start_layer, self.end_layer, self.layers = make_layers(
|
|
num_hidden_layers=config.num_hidden_layers,
|
|
layer_fn=get_layer,
|
|
prefix=f"{prefix}.layers",
|
|
)
|
|
self.is_fused_shared_expert_enabled = is_model_fused_shared_expert_compatible(
|
|
layers=self.layers, moe_cls=AliceAISparseMoeBlock, moe_name="mlp"
|
|
)
|
|
self.norm = GemmaRMSNorm(
|
|
hidden_size=config.hidden_size, eps=config.rms_norm_eps
|
|
)
|
|
self.attnres_final = _AttentionResidualFinal(
|
|
hidden_size=config.hidden_size,
|
|
rms_norm_eps=float(config.rms_norm_eps),
|
|
prefix=f"{prefix}.attnres_final",
|
|
)
|
|
|
|
def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
|
|
return self.embed_tokens(input_ids)
|
|
|
|
def _init_residual_bank(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
|
residual_bank = hidden_states.new_empty(
|
|
(
|
|
hidden_states.shape[0],
|
|
(self.config.num_hidden_layers + self.attn_res_block_size - 1)
|
|
// self.attn_res_block_size,
|
|
hidden_states.shape[-1],
|
|
)
|
|
)
|
|
residual_bank[:, 0].copy_(hidden_states)
|
|
return residual_bank
|
|
|
|
def forward(
|
|
self,
|
|
input_ids: torch.Tensor | None,
|
|
positions: torch.Tensor,
|
|
intermediate_tensors: IntermediateTensors | None = None,
|
|
inputs_embeds: torch.Tensor | None = None,
|
|
) -> torch.Tensor | IntermediateTensors:
|
|
if intermediate_tensors is not None:
|
|
raise NotImplementedError("AliceAI does not support pipeline parallelism")
|
|
if not get_pp_group().is_first_rank or not get_pp_group().is_last_rank:
|
|
raise NotImplementedError("AliceAI requires pipeline_parallel_size=1")
|
|
|
|
if inputs_embeds is not None:
|
|
hidden_states = inputs_embeds
|
|
else:
|
|
assert input_ids is not None
|
|
hidden_states = self.embed_input_ids(input_ids)
|
|
residual_bank = self._init_residual_bank(hidden_states)
|
|
|
|
num_completed_sources = 1
|
|
partial = None
|
|
for layer in islice(self.layers, self.start_layer, self.end_layer):
|
|
num_completed_sources, partial = layer.forward_attn_res(
|
|
positions=positions,
|
|
residual_bank=residual_bank,
|
|
num_completed_sources=num_completed_sources,
|
|
partial=partial,
|
|
)
|
|
if partial is None:
|
|
raise RuntimeError("AttnRes produced an empty final block")
|
|
hidden_states = self.attnres_final(
|
|
residual_bank=residual_bank,
|
|
num_completed_sources=num_completed_sources,
|
|
partial=partial,
|
|
)
|
|
return self.norm(hidden_states)
|
|
|
|
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
|
weights = maybe_fuse_shared_experts(
|
|
weights=weights,
|
|
enabled=self.is_fused_shared_expert_enabled,
|
|
n_routed_experts=self.config.num_experts,
|
|
n_shared_experts=1,
|
|
ckpt_prefix="mlp.shared_expert",
|
|
)
|
|
loader = AutoWeightsLoader(self)
|
|
return loader.load_weights(weights=weights, mapper=self.hf_to_vllm_mapper)
|
|
|
|
|
|
class AliceAIForCausalLM(
|
|
nn.Module, HasInnerState, IsHybrid, SupportsLoRA, MixtureOfExperts
|
|
):
|
|
hf_to_vllm_mapper = WeightsMapper(orig_to_new_prefix={"mtp.": None})
|
|
|
|
packed_modules_mapping = {
|
|
"qkv_proj": ["q_proj", "k_proj", "v_proj"],
|
|
"gate_up_proj": ["gate_proj", "up_proj"],
|
|
"in_proj_qkvgfab": [
|
|
"q_proj",
|
|
"k_proj",
|
|
"v_proj",
|
|
"b_proj",
|
|
"f_a_proj",
|
|
],
|
|
"conv1d": ["q_conv1d", "k_conv1d", "v_conv1d"],
|
|
}
|
|
|
|
def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
|
|
# AttnRes calls bypass the per-layer forward hooks used by offloading.
|
|
offloader = get_offloader()
|
|
if isinstance(offloader, PrefetchOffloader):
|
|
raise NotImplementedError("AliceAI does not support prefetch offloading")
|
|
if (
|
|
isinstance(offloader, UVAOffloader)
|
|
and offloader.cpu_offload_max_bytes > 0
|
|
and not offloader.uva_offloading
|
|
):
|
|
raise NotImplementedError("AliceAI CPU offloading requires UVA")
|
|
|
|
super().__init__()
|
|
config = vllm_config.model_config.hf_text_config
|
|
self.vllm_config = vllm_config
|
|
self.model_config = vllm_config.model_config
|
|
self.scheduler_config = vllm_config.scheduler_config
|
|
self.quant_config = vllm_config.quant_config
|
|
self.config = config
|
|
self.model = AliceAIModel(
|
|
vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
|
|
)
|
|
self.lm_head = ParallelLMHead(
|
|
num_embeddings=config.vocab_size,
|
|
embedding_dim=config.hidden_size,
|
|
prefix=maybe_prefix(prefix, "lm_head"),
|
|
)
|
|
self.logits_processor = LogitsProcessor(config.vocab_size)
|
|
self.set_moe_parameters()
|
|
|
|
def set_moe_parameters(self) -> None:
|
|
self.moe_layers = [layer.mlp.experts for layer in self.model.layers]
|
|
moe = self.model.layers[self.model.start_layer].mlp
|
|
self.num_moe_layers = len(self.moe_layers)
|
|
self.num_expert_groups = 1
|
|
self.num_shared_experts = 0
|
|
self.num_logical_experts = moe.n_logical_experts
|
|
self.num_physical_experts = moe.n_physical_experts
|
|
self.num_local_physical_experts = moe.n_local_physical_experts
|
|
self.num_routed_experts = moe.n_routed_experts
|
|
self.num_redundant_experts = moe.n_redundant_experts
|
|
|
|
def update_physical_experts_metadata(
|
|
self,
|
|
num_physical_experts: int,
|
|
num_local_physical_experts: int,
|
|
) -> None:
|
|
assert self.num_local_physical_experts == num_local_physical_experts
|
|
self.num_physical_experts = num_physical_experts
|
|
self.num_local_physical_experts = num_local_physical_experts
|
|
self.num_redundant_experts = num_physical_experts - self.num_logical_experts
|
|
for layer in self.model.layers:
|
|
moe = layer.mlp
|
|
moe.n_local_physical_experts = num_local_physical_experts
|
|
moe.n_physical_experts = num_physical_experts
|
|
moe.n_redundant_experts = self.num_redundant_experts
|
|
moe.experts.update_expert_map()
|
|
|
|
def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
|
|
return self.model.embed_input_ids(input_ids)
|
|
|
|
def forward(
|
|
self,
|
|
input_ids: torch.Tensor | None,
|
|
positions: torch.Tensor,
|
|
intermediate_tensors: IntermediateTensors | None = None,
|
|
inputs_embeds: torch.Tensor | None = None,
|
|
**kwargs: object,
|
|
) -> torch.Tensor | IntermediateTensors:
|
|
return self.model(
|
|
input_ids=input_ids,
|
|
positions=positions,
|
|
intermediate_tensors=intermediate_tensors,
|
|
inputs_embeds=inputs_embeds,
|
|
)
|
|
|
|
def compute_logits(self, hidden_states: torch.Tensor) -> torch.Tensor | None:
|
|
return self.logits_processor(lm_head=self.lm_head, hidden_states=hidden_states)
|
|
|
|
@classmethod
|
|
def get_mamba_state_dtype_from_config(
|
|
cls, vllm_config: VllmConfig
|
|
) -> tuple[torch.dtype, torch.dtype]:
|
|
return MambaStateDtypeCalculator.kda_state_dtype(
|
|
model_dtype=vllm_config.model_config.dtype,
|
|
mamba_cache_dtype=vllm_config.cache_config.mamba_cache_dtype,
|
|
)
|
|
|
|
@classmethod
|
|
def get_mamba_state_shape_from_config(
|
|
cls, vllm_config: VllmConfig
|
|
) -> tuple[tuple[int, int], tuple[int, int, int]]:
|
|
config = vllm_config.model_config.hf_text_config
|
|
num_spec = (
|
|
vllm_config.speculative_config.num_speculative_tokens
|
|
if vllm_config.speculative_config
|
|
else 0
|
|
)
|
|
return MambaStateShapeCalculator.kda_state_shape(
|
|
tp_world_size=vllm_config.parallel_config.tensor_parallel_size,
|
|
num_heads=config.linear_num_key_heads,
|
|
head_dim=config.linear_key_head_dim,
|
|
conv_kernel_size=config.linear_conv_kernel_dim,
|
|
num_spec=num_spec,
|
|
)
|
|
|
|
@classmethod
|
|
def get_mamba_state_copy_func(
|
|
cls,
|
|
) -> tuple[MambaStateCopyFunc, MambaStateCopyFunc]:
|
|
return MambaStateCopyFuncCalculator.gated_delta_net_state_copy_func()
|
|
|
|
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
|
|
weights = self.hf_to_vllm_mapper.apply(weights)
|
|
manually_loaded: set[str] = set()
|
|
weights = _load_split_kda_conv_weights(
|
|
weights=weights,
|
|
params=dict(self.named_parameters()),
|
|
loaded=manually_loaded,
|
|
)
|
|
loader = AutoWeightsLoader(self)
|
|
loaded = loader.load_weights(weights=weights)
|
|
loaded.update(manually_loaded)
|
|
expected_target_weights = {
|
|
name
|
|
for name, _ in self.named_parameters()
|
|
if ".attnres_final." in name
|
|
or ".attn_res_" in name
|
|
or ".mlp_res_" in name
|
|
or name.endswith(".e_score_correction_bias")
|
|
}
|
|
if missing := expected_target_weights - loaded:
|
|
raise ValueError(
|
|
"AliceAI checkpoint is missing target-specific weights: "
|
|
+ ", ".join(sorted(missing))
|
|
)
|
|
return loaded
|