Transformers 中的 AFMoE 模型:共享专家 + Token 选择的稀疏 MoE 架构详解与实战
本文以仓库内模型文档 docs/source/en/model_doc/afmoe.md 为主体,结合
src/transformers/models/afmoe/下的配置与实现源码、tests/models/afmoe/下的测试用例,深入讲解 AFMoE(Arcee Foundational Mixture of Experts)的架构创新、配置项与调用方式。读完本文你将掌握:AFMoE 相对标准 Transformer / Llama 的六大架构改动(共享专家、Token-Choice 路由、Q/K 归一化与注意力门控、滑窗 + 全局混合注意力、双重归一化、前置稠密层)、每个AfmoeConfig参数的含义与默认值,并能用Pipeline或AutoModel快速完成文本生成推理。
AFMoE(Arcee Foundational Mixture of Experts)是 2025-11-29 由 Arcee AI 与 Hugging Face Transformers 团队共同合入的纯解码器(decoder-only)Transformer 模型。它在 Llama 架构基础上引入稀疏专家混合(MoE):每个 token 通过 token-choice 路由激活 top-k 个专家,同时保留每层始终激活的共享专家以提供稳定的基础计算路径,并配合混合注意力模式与双重归一化等技巧,在推理效率与模型能力之间取得平衡。在库内它被接入 AutoModel / AutoModelForCausalLM 体系(见 src/transformers/models/auto/modeling_auto.py 中对 "afmoe" 的映射)。
核心架构特性一览
AFMoE 在标准 Transformer 基础上引入了若干关键修改(下文均能在 src/transformers/models/afmoe/modeling_afmoe.py 中找到对应实现):
- 共享专家 + 路由专家的专家混合:路由专家由可学习的门控按 token 激活;共享专家(shared experts)对每个 token 恒定激活,提供稳定的基础计算,降低模型输出方差,并提升对分布外输入的鲁棒性。
- Token-Choice 路由:基于 sigmoid(或 softmax 类)评分的路由 + 归一化与缩放,决定每个 token 选哪些专家,详见下文"专家路由"一节。
- Q/K 归一化与门控(gating):对 query、key 投影应用 RMSNorm,并对注意力输出施加 sigmoid 门控,以提升训练稳定性。
- 混合注意力模式:层间交替使用滑窗注意力和全注意力,兼顾长上下文效率与全局建模(在 docs/source/en/model_doc/afmoe.md 原文中称 full attention 每 N 层施加一次,由
global_attn_every_n_layers控制)。 - 双重归一化(Dual Normalization):围绕注意力和 MLP 块各做一次前归一化 + 后归一化,利于稳定训练。
- 可配置的稠密层前置(Dense Layers):允许靠前的若干层先使用稠密 MLP,再过渡到稀疏 MoE 层,对应配置项
num_dense_layers。
在工程层面,模型基于 RoPE 旋转位置编码支持扩展上下文长度(默认 max_position_embeddings=16384),并完整支持 Transformers 的 Flash Attention 2、SDPA、FLEX ATTENTION、梯度检查点(gradient checkpointing)以及量化。得益于稀疏化带来的高效扩展(capacity scaling)与共享专家的稳定基线,AFMoE 特别适合"希望在可控算力预算内扩展模型容量、同时保持稳定输出质量"的场景。
快速上手:一行 API 生成文本
原文档提供了两条等价的使用路径,完整示例(如下)可直接复制运行:
方式一:使用 Pipeline(text-generation)
from transformers import pipeline
pipeline = pipeline(
task="text-generation",
model="arcee-ai/Trinity-Mini",
device=0
)
output = pipeline("The key innovation in mixture of experts is")
print(output[0]["generated_text"])
方式二:使用 AutoTokenizer + 显式模型类(AutoModel 体系)
import torch
from transformers import AfmoeForCausalLM, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("arcee-ai/Trinity-Mini")
model = AfmoeForCausalLM.from_pretrained(
"arcee-ai/Trinity-Mini",
device_map="auto"
)
inputs = tokenizer("The key innovation in mixture of experts is", return_tensors="pt").to(model.device)
with torch.no_grad():
outputs = model.generate(**inputs, max_new_tokens=50)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
注意:两种用法默认都会联网下载预训练权重,需要在有网络的环境中执行;device_map="auto" 需要已安装 accelerate。除官方文档主推的 arcee-ai/Trinity-Mini 检查点外,集成测试中还使用了 arcee-ai/trinity-nano-preview(见 tests/models/afmoe/test_modeling_afmoe.py 中的 AfmoeIntegrationTest),可作为小模型验证路线的参考。
深入源码:六大机制如何落地
专家路由(Expert Routing):Token-Choice Top-K
AFMoE 使用 token-choice 路由:每个 token 独立地基于路由 logits 选择 top-k 个专家。实现对应 modeling_afmoe.py 中的 AfmoeTokenChoiceRouter,其前向过程可以拆解为:
- 门控层
gate(Linear(hidden_size, num_experts, bias=False))将每个 token 映射为路由 logits,并转成float32计算; - 用
sigmoid(router_logits)得到各专家的原始得分(文档强调其与已发布检查点一致); - 在得分上叠加可学习的专家偏置
expert_bias后取topk(k = num_experts_per_tok)选出专家索引——这是文档中"Bias correction for expert selection"的落地; - 对选中的 top-k 分数做归一化(除以得分和,
denominator = sum + 1e-20防除零),得到最终权重; - 乘上路由缩放系数
route_scale控制专家贡献强度。
需要说明:原文档提及的可配置打分函数(sigmoid 或 softmax)以及 route normalization 开关,在当前仓库实现中固定采用 sigmoid + 上述归一化求和路线(AfmoeTokenChoiceRouter.forward 未再暴露 softmax / route_norm 的独立开关参数),配置类中与之对应的是 route_scale;expert_bias 是挂在 AfmoeSparseMoeBlock 上、requires_grad=False 的参数(见下)。
共享专家与稀疏 MoE 块
标准 MoE 中每个 token 只过被路由选中的专家;AFMoE 则额外保留始终激活的共享专家。在 AfmoeSparseMoeBlock 中:
self.shared_experts = AfmoeMLP(config, config.moe_intermediate_size * config.num_shared_experts)
self.experts = AfmoeExperts(config)
self.expert_bias = nn.Parameter(torch.zeros(config.num_experts), requires_grad=False)
其 forward 将输入分别送入共享专家与路由专家,最后做加和:
shared_output = self.shared_experts(hidden_states_flat).view(batch_size, seq_len, hidden_dim)
routed_output = self.experts(hidden_states_flat, selected_experts, top_scores).view(batch_size, seq_len, hidden_dim)
return shared_output + routed_output
共享专家的中间宽度被放大为 moe_intermediate_size * num_shared_experts,即共享专家实际是一个"加宽版稠密 MLP"。路由专家侧,AfmoeExperts 把所有专家权重以 3D 张量存放(gate_up_proj 形状 (num_experts, 2*intermediate, hidden)、down_proj 形状 (num_experts, hidden, intermediate)),并在前向中通过 index_add_ 将各专家结果加权写回——这是文档所说"routed experts enable model capacity scaling"的直接体现:64 个专家共享同一份推理路径,参数量显著高于等效稠密模型,而每次推理只走其中 6 个。
源码层面,
AfmoeSparseMoeBlock与AfmoeTokenChoiceRouter均标注@use_experts_implementation/ 具备可替换实现钩子;modular_afmoe.py 显示其 MLP、Experts 分别复用Qwen2MoeMLP、Qwen2MoeExperts,RMSNorm 复用GptOssRMSNorm,注意力与旋转编码则继承LlamaAttention、LlamaRotaryEmbedding,说明 AFMoE 在库内属于"基于成熟模块组装"的架构,维护成本可控。
注意力机制:Q/K 归一化 + 输出门控 + 滑窗/全注意力交替
AfmoeAttention 在 Llama 注意力基础上增加了三类 AFMoE 特有改动(对应文档"所有注意力层都包含 Q/K 归一化与输出门控"):
- Q/K 归一化:新增
q_norm、k_norm(作用于单头维度head_dim的AfmoeRMSNorm),前向中先q_proj/k_proj投影、view 成多头形状,再对每个头做归一化; - 输出门控:新增
gate_proj(无 bias),将注意力输出与torch.sigmoid(gate_states)逐元素相乘后再过o_proj; - 滑窗支持:层类型为
sliding_attention时设置sliding_window并把滑窗 mask 传给注意力接口。
混合注意力的层间编排在 AfmoeModel.forward 中完成:它一次性构造 causal_mask_mapping,把 full_attention 层映射到 create_causal_mask 生成的因果掩码,把 sliding_attention 层映射到 create_sliding_window_causal_mask 生成的滑窗掩码,随后逐层按下标从 self.config.layer_types[i] 取对应掩码喂给解码层。从代码结构可以推断:滑动窗口层更多负责局部、高效的特征提取,而每隔 global_attn_every_n_layers 层出现的全注意力层负责注入全局上下文。
双重归一化与前置稠密层
AfmoeDecoderLayer(继承 GradientCheckpointingLayer)内部布局为:
# 注意力块:前归一化 → 注意力 → 后归一化 → 残差
hidden_states = self.input_layernorm(hidden_states)
hidden_states, _ = self.self_attn(...)
hidden_states = self.post_attention_layernorm(hidden_states)
hidden_states = residual + hidden_states
# FFN 块:前归一化 → MLP/MoE → 后归一化 → 残差
residual = hidden_states
hidden_states = self.pre_mlp_layernorm(hidden_states)
hidden_states = self.mlp(hidden_states)
hidden_states = self.post_mlp_layernorm(hidden_states)
hidden_states = residual + hidden_states
即注意力与 MLP 前后各有一层 AfmoeRMSNorm(input_layernorm/post_attention_layernorm、pre_mlp_layernorm/post_mlp_layernorm),构成文档所称的 dual normalization。层内"稠密 or MoE"的选择依据 moe_enabled = layer_idx >= config.num_dense_layers:当 layer_idx < num_dense_layers 时该层使用普通稠密 AfmoeMLP(与 Qwen2MoE 的 MLP 同构),否则替换为 AfmoeSparseMoeBlock。因此 num_dense_layers=1 意味着第 0 层是普通稠密层、从第 1 层起才是稀疏 MoE 层——这是"让初始层用稠密 MLP 起步、再过渡到稀疏 MoE"的可配置实现。
RoPE、muP 与长上下文支持
模型自带 AfmoeRotaryEmbedding(复用 Llama 的 RoPE 实现,可从 rope_parameters 配置 rope_type、rope_theta 等;其中 rope_theta 默认来自原版公式 1.0 / (base ** (torch.arange(0, dim, 2) / dim))),max_position_embeddings 默认 16384,支撑长序列输入。此外配置类还暴露一个 muP(Maximal Update Parametrization)开关:当 mup_enabled=True 时,AfmoeModel.forward 会对输入嵌入乘以 sqrt(hidden_size) 作为输入缩放。
AfmoeConfig 配置参数详解
AfmoeConfig(见 src/transformers/models/afmoe/configuration_afmoe.py)直接继承 PreTrainedConfig,model_type = "afmoe"。除标准 AutoConfig 注册外(auto_mappings 中 ("afmoe", "AfmoeConfig")),还在 __post_init__ 中自动派生两个字段:
layer_types:若未显式传入,按"sliding_attention" if (i+1) % global_attn_every_n_layers else "full_attention"为每一层自动生成,即默认每隔global_attn_every_n_layers层出现一层全注意力;num_key_value_heads:若为None,自动取num_attention_heads的值(即不启用 GQA 分组)。
下表汇总了当前仓库中配置类声明的字段、默认值与语义(可直接在代码中核对):
| 参数 | 默认值 | 语义 |
|---|---|---|
vocab_size |
200192 | 词表大小 |
hidden_size |
2048 | 隐藏层维度 |
intermediate_size |
6144 | 稠密 MLP(含共享专家放大后)的中间维度基准 |
moe_intermediate_size |
1408 | 单个路由专家的中间维度 |
num_hidden_layers |
32 | 解码层总数 |
num_dense_layers |
1 | 前置稠密(非 MoE)层数 |
num_attention_heads |
16 | 注意力头数 |
num_key_value_heads |
None |
KV 头数;为 None 时默认取 num_attention_heads |
head_dim |
128 | 每个注意力头的维度 |
hidden_act |
"silu" |
激活函数(SiLU/Swish) |
max_position_embeddings |
16384 | 最大位置编码长度 |
initializer_range |
0.02 | 初始化标准差 |
rms_norm_eps |
1e-5 | RMSNorm 的 epsilon |
use_cache |
True |
生成时是否使用 KV cache |
tie_word_embeddings |
False |
是否绑定 embedding 与 lm_head |
rope_parameters |
None |
RoPE 相关参数(rope_type、rope_theta 等),由训练配置填充 |
num_experts |
64 | 路由专家总数 |
num_experts_per_tok |
6 | 每个 token 激活的专家数(top-k) |
num_shared_experts |
2 | 共享专家个数(共享专家以放大 MLP 实现) |
route_scale |
1.0 | 路由得分缩放系数,控制专家贡献强度 |
output_router_logits |
False |
是否在输出中返回各层路由 logits |
global_attn_every_n_layers |
4 | 全注意力层的出现频率(每隔 N 层) |
sliding_window |
1024 | 滑窗注意力窗口大小 |
layer_types |
None |
每层注意力类型;None 时按上述规则自动生成 |
attention_dropout |
0.0 | 注意力 dropout 概率 |
mup_enabled |
False |
是否启用 muP 输入缩放(sqrt(hidden_size)) |
eos_token_id / pad_token_id / bos_token_id |
None |
特殊 token id |
attention_bias |
False |
注意力线性投影是否带 bias |
初始化示例(来自配置类的 docstring,可直接运行):
>>> from transformers import AfmoeModel, AfmoeConfig
>>> # Initializing an AFMoE configuration
>>> configuration = AfmoeConfig()
>>> # Initializing a model from the afmoe-small-sft-v1 style configuration
>>> model = AfmoeModel(configuration)
>>> # Accessing the model configuration
>>> configuration = model.config
前向接口与输出结构
AfmoeModel 的前向签名包含 input_ids / inputs_embeds(二选一,同时传则报错)、attention_mask、position_ids、past_key_values、use_cache 等,内部逻辑为:构造 DynamicCache(若 use_cache 且未提供)→ 生成或复用双掩码 → 应用 RoPE 位置编码 →(可选)muP 输入缩放 → 逐层前向 → 末尾 AfmoeRMSNorm → 返回 MoeModelOutputWithPast(含 last_hidden_state、past_key_values)。
AfmoeForCausalLM(继承 GenerationMixin)在前向中支持 labels(自动计算语言建模损失)与 logits_to_keep,并额外在输出 MoeCausalLMOutputWithPast 中携带 router_logits(由 output_router_logits 控制)。从测试 test_modeling_afmoe.py 的 test_router_logits_without_aux_loss 可以看到其行为特征:把 num_dense_layers 设为 0、output_router_logits=True 后前向,result.router_logits 非空且其最后一维等于 num_experts,同时 aux_loss 为 None——即本仓库实现不施加 MoE 辅助负载均衡损失,路由仅靠主任务梯度与可学习专家偏置调节。同样在测试中被 skip 的项也提示了一个使用限制:AFMoE 因施加 K/Q 归一化,"packing(无 padding 拼接)"与带 padding 的 padding-free 前向在数值上不完全等价(见 skip 项 test_eager_padding_matches_padding_free_with_position_ids 等),训练/评测 packing 数据时需自行评估数值一致性影响。
模型级工程能力与集成测试验证
AfmoePreTrainedModel 上的类属性表明其具备完整的库级能力矩阵:
- 注意力后端:
_supports_sdpa = True、_supports_flash_attn = True、_supports_flex_attn = True、_supports_attention_backend = True,即 SDPA、FlashAttention 2、Flex Attention 均声明支持(文档徽标亦标注 FlashAttention 与 SDPA); - 编译与检查点:
_can_compile_fullgraph = True,支持torch.compile全图编译;supports_gradient_checkpointing = True,支持梯度检查点; - 精度保持:
_keep_in_fp32_modules中列出各归一化层、q_norm/k_norm与expert_bias,说明这些模块在混合精度训练/推理中会被保持在 FP32,以避免 RMSNorm、路由偏置等数值敏感算子失稳; - 并行支持:
base_model_ep_plan表明专家权重可做专家并行(router 与 grouped GEMM 分别切分)、base_model_pp_plan提供流水线并行默认切分,_tp_plan/_pp_plan/_fsdp_plan则定义了lm_head的张量/流水线/FSDP 策略; - 权重转换:
test_moe_legacy_conversion_mapping_registered验证了从旧的逐专家gate_proj.weight到新的融合权重mlp.experts.gate_up_proj的 legacy 转换映射已注册(fused_expertconverter),可平滑加载历史格式的权重。
端到端验证方面,集成测试 AfmoeIntegrationTest(标注 @slow、需要 GPU/加速器)在 arcee-ai/trinity-nano-preview 上做了三个对照实验:默认动态 KV cache 生成 vs cache_implementation="static" 静态缓存生成,结果必须逐 token 一致;随后对 model.forward 施加 torch.compile(..., mode="reduce-overhead", fullgraph=True),静态编译生成的输出仍需与动态缓存结果一致。这说明仓库对 AFMoE 的静态缓存 + 全图编译路径做了严格的数值一致性保障,可以放心在生产推理链路中组合使用。
模型源码的生成与演进
AFMoE 的实现遵循 Transformers 的"modular"开发流程:主源码文件 modeling_afmoe.py 顶部注释明确说明它由 modular_afmoe.py 自动生成,任何修改应落在 modular 源文件上(CI 会校验二者一致)。modular 文件中通过导入复用 LlamaAttention、LlamaRotaryEmbedding、eager_attention_forward(来自 Llama),Qwen2MoeMLP、Qwen2MoeExperts(来自 Qwen2-MoE),以及 GptOssRMSNorm(来自 GPT-OSS)等成熟组件,再以最小改动叠加 AFMoE 特有的路由、共享专家、门控与双重归一化逻辑。阅读时若想快速把握"哪些是真正的 AFMoE 创新点",直接对比 modular_afmoe.py 中未标注 pass 的类即可:AfmoeTokenChoiceRouter、AfmoeSparseMoeBlock、AfmoeAttention、AfmoeDecoderLayer 承载了几乎所有差异化逻辑。
小结
AFMoE 在库内是一个"基于成熟模块(Llama 注意力/RoPE、Qwen2MoE 的 MLP/专家、GPT-OSS 的 RMSNorm)重组并叠加稀疏化创新"的 decoder-only 模型。实践要点可归纳为:直接 from_pretrained("arcee-ai/Trinity-Mini") 走 AfmoeForCausalLM 或 text-generation pipeline;如需自定义结构,重点观察 num_experts/num_experts_per_tok/num_shared_experts(容量与稀疏度)、num_dense_layers(前置稠密层数)、global_attn_every_n_layers/sliding_window(混合注意力节奏)与 layer_types(自动生成的全/滑窗层序列);推理侧可放心组合 SDPA / FlashAttention 2、静态 KV cache 与 torch.compile。值得留意的是:本实现未内置 MoE 辅助负载均衡损失(aux_loss=None),且 K/Q 归一化使 packing 场景与 padding 前向存在数值差异,这两点在做大规模训练调优时需要自行评估。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00