Transformers 中的 LFM2-MoE 模型:融合门控卷积骨干与稀疏混合专家的端侧推理架构全解析
LFM2-MoE 是由 Liquid AI 提出的 LFM2 系列混合专家(Mixture-of-Experts, MoE)变体,它在保留 LFM2「短程输入感知门控卷积 + 分组查询注意力(GQA)」快速骨干的基础上,通过稀疏 MoE 前馈网络换取更高表示容量。本篇文章以仓库中的官方文档 docs/source/en/model_doc/lfm2_moe.md 为主体,结合 configuration_lfm2_moe.py 与 modeling_lfm2_moe.py 源码,完整讲解该模型的架构设计、全部配置参数、底层实现细节,并给出可直接运行的推理示例。读完本文,你将能够理解「8B 总参数、1.5B 激活参数」这类 MoE 模型的容量/算力拆解思路,掌握用 AutoModelForCausalLM 加载与生成的方法,以及 Layer 混合布局、专家路由等关键实现原理。
Overview:LFM2-MoE 是什么
LFM2-MoE 是 LFM2 的混合专家变体,于 2025-10-07 被贡献进入本仓库的 Transformers 生态。根据官方模型文档:
- LFM2 系列专为端侧(on-device)推理优化,其骨干网络把**短程、输入感知的门控卷积(short-range, input-aware gated convolutions)与分组查询注意力(GQA)**组合在一种为「严格速度与显存约束下最大化质量」而调校的层级布局中;
- LFM2-MoE 保留了这个快速骨干,并引入稀疏 MoE 前馈网络来增加表示容量,而不显著拉长激活计算路径(active compute path);
- 首个 LFM2-MoE 发布为 LFM2-8B-A1B:总参数量 8.3B,激活参数量约 1.5B。模型卡声称其质量接近 3-4B 稠密模型,同时速度优于同类 1.5B 量级模型。
说明:上述质量与速度结论来自官方模型文档的表述,属于模型发布方的自我描述;若需在自有场景验证,请以本地实测为准。本文不将其作为经仓库代码验证的客观事实展开。
从 Transformer 实现的角度,LFM2-MoE 在代码中的注册名称为 model_type = "lfm2_moe"(见 configuration_lfm2_moe.py),并已挂入自动映射表:AutoModel 将 lfm2_moe 映射到 Lfm2MoeModel,AutoModelForCausalLM 映射到 Lfm2MoeForCausalLM(见 src/transformers/models/auto/modeling_auto.py 与 src/transformers/models/auto/modeling_auto.py)。因此用户无需关心架构类名,直接走 Auto* 接口即可。
架构总览:LFM2 骨干如何叠加 MoE
仓库源码把 LFM2-MoE 的构建方式清晰地表达为一种「模块化拼装」:主文件 modeling_lfm2_moe.py 顶部注释声明它是由 modular_lfm2_moe.py 自动生成的,任何手工修改都会被 CI 覆盖回写,改动必须落在 modular 文件。而 modular 文件本身则通过继承把多个既有模型族拼接起来(见 modular_lfm2_moe.py):
Lfm2MoeRMSNorm继承自 Llama 的LlamaRMSNorm;Lfm2MoeAttention/Lfm2MoeShortConv/Lfm2MoeRotaryEmbedding直接复用 LFM2(dense 版)的同名组件;Lfm2MoeExperts继承 Qwen2-MoE 的专家容器,Lfm2MoeTopKRouter继承 Qwen2-MoE 的 Top-K 路由器;Lfm2MoeSparseMoeBlock继承 Qwen3-MoE 的稀疏 MoE 块;Lfm2MoeModel继承 Mixtral 的模型外壳,Lfm2MoeForCausalLM/Lfm2MoePreTrainedModel继承 Llama 的因果语言模型壳。
这种「从成熟实现里做减法/加法」的方式意味着:LFM2-MoE 的注意力与卷积部分完全复用已充分验证的 LFM2 逻辑,专家与路由器则沿用了 Qwen2/3-MoE 中被广泛测试的实现。它给你一个很有价值的对照视角——MoE 前馈的「稀疏替换」发生在解码器内部,而模型外层结构(嵌入、RoPE、因果掩码、缓存)几乎与主流 LLM 框架一致。
逐层解剖:Decoder Layer 内部的三种模块
核心的层级逻辑位于 modeling_lfm2_moe.py 的 Lfm2MoeDecoderLayer。每一层都以 layer_types[layer_idx] 决定自己的「算子类型」,并对算子做 RMSNorm 后再走残差;FFN 部分依据层号决定用稠密 MLP 还是稀疏 MoE。
1. 算子:full_attention 或 short conv
Lfm2MoeDecoderLayer.__init__ 中有一个关键判断:
self.is_attention_layer = config.layer_types[layer_idx] == "full_attention"
if self.is_attention_layer:
self.self_attn = Lfm2MoeAttention(config, layer_idx)
else:
self.conv = Lfm2MoeShortConv(config, layer_idx)
也就是说:不是每一层都做全局注意力,一部分层用轻量的门控卷积替代注意力,这正是 LFM2 骨干省算力/省显存的来源。前向时对算子输入先过 operator_norm(RMSNorm),算子输出加回残差,再叠加 ffn_norm 归一化后的前馈输出:
hidden_states = hidden_states + residual
hidden_states = hidden_states + self.feed_forward(self.ffn_norm(hidden_states))
2. 门控短程卷积(Lfm2MoeShortConv)
Lfm2MoeShortConv(modeling_lfm2_moe.py)内部是标准的门控卷积块:
- 深度可分离因果一维卷积
nn.Conv1d,groups=hidden_size、kernel_size=config.conv_L_cache(默认 3),配合padding=kernel-1保证因果性; - 先
in_proj把输入线性映射到 3×hidden,切分为B、C、x三段,做输入感知门控B * x,卷积后再用C对卷积输出做C * y二次门控,最后out_proj还原维度; - 解码阶段若命中缓存(
seq_len == 1且有历史卷积状态),走逐 token 的causal_conv1d_update,否则走整体causal_conv1d_fn;这两个函数都标注了@use_kernel_func_from_hub_with_fallback——即优先从 Hub 拉取融合 kernel(如基于 mamba-ssm 生态的 causal_conv1d),拉不到时回退到 PyTorchF.conv1d原生实现(见 modeling_lfm2_moe.py); - 卷积层还需要配套的循环式掩码:模型前向会根据
layer_types[i]为每层挑选掩码类型——"full_attention"层用create_causal_mask,"conv"层用create_recurrent_attention_mask,并在进入每层前通过causal_mask_mapping[self.config.layer_types[i]]分发(见 modeling_lfm2_moe.py)。
3. 分组查询注意力(Lfm2MoeAttention)
Lfm2MoeAttention(modeling_lfm2_moe.py)是标准的 GQA 实现,并有若干值得注意的细节:
num_key_value_groups = num_attention_heads // num_key_value_heads,默认配置 32 头 / 8 KV 头,即 4 组查询共享一组 K/V;- Q、K 投影之后各过一个按 head_dim 归一化的 RMSNorm(
q_layernorm/k_layernorm),这是近年 LLM 中常见的 QK-Norm 技巧,有助于稳定注意力训练; - 缩放因子
head_dim ** -0.5;RoPE 位置编码通过Lfm2MoeRotaryEmbedding计算,其 base 取自rope_parameters["rope_theta"],配置类默认default_theta = 1000000.0(见 configuration_lfm2_moe.py); - 注意力内核支持可插拔:
_supports_flash_attn = True、_supports_sdpa = True、_supports_flex_attn = True(见 modeling_lfm2_moe.py),前向通过ALL_ATTENTION_FUNCTIONS.get_interface(...)在 eager / SDPA / Flash Attention 之间切换,默认回退到文件内的eager_attention_forward(softmax 在 float32 下计算)。
4. 前馈:稠密 MLP 与稀疏 MoE 的分工
Lfm2MoeDecoderLayer 中 FFN 的选取是整份实现里最直接的「稀疏化切点」:
self.feed_forward = (
Lfm2MoeMLP(config, intermediate_size=config.intermediate_size)
if layer_idx < config.num_dense_layers
else Lfm2MoeSparseMoeBlock(config)
)
按配置文档字符串的解释(configuration_lfm2_moe.py),层级排布为 embed -> dense -> dense -> ... -> dense -> moe -> moe ... -> lm_head:浅层开头保留 2 个稠密 SwiGLU MLP 层(num_dense_layers = 2),其后的层全部替换为稀疏 MoE 块。
- 稠密 MLP(
Lfm2MoeMLP):经典的w1 / w3 / w2三投影 SwiGLU 形式,hidden→intermediate→hidden,无 bias,中间激活为silu(w1(x)) * w3(x)(见 modeling_lfm2_moe.py)。 - 稀疏 MoE(
Lfm2MoeSparseMoeBlock):由路由器Lfm2MoeTopKRouter+ 专家容器Lfm2MoeExperts组成。专家权重被存储为三维张量(见 modeling_lfm2_moe.py):
self.gate_up_proj = nn.Parameter(torch.empty(self.num_experts, 2 * self.intermediate_dim, self.hidden_dim))
self.down_proj = nn.Parameter(torch.empty(self.num_experts, self.hidden_dim, self.intermediate_dim))
每个 token 按 top-k 命中少数专家,实际计算时先 topk 得到被命中的专家下标集合,再只对这些专家执行 gate/up 与 down 线性变换,并按路由权重加权后 index_add_ 汇回原位置——这是「稀疏激活、稠密存储」的典型形态。
5. 路由器与专家偏置(Lfm2MoeTopKRouter)
路由逻辑在 modeling_lfm2_moe.py:
router_logits = F.linear(hidden_states, self.weight)
routing_weights = router_logits.sigmoid()
if self.use_expert_bias:
scores_for_routing = routing_weights + expert_bias
_, selected_experts = torch.topk(scores_for_routing, k=self.top_k, dim=-1)
...
if self.norm_topk_prob:
routing_weights = routing_weights / (routing_weights.sum(dim=-1, keepdim=True) + 1e-6)
routing_weights = routing_weights * self.routed_scaling_factor
几个要点:
- 路由打分用 sigmoid(而非 softmax),因此每个专家可独立被选中,天然适配多选 top-k 路由;
use_expert_bias=True(默认)时,打分结果会加上一个可学习/可维护的专家偏置向量expert_bias(在Lfm2MoeSparseMoeBlock中注册为nn.Buffer,初值为 0,见 modeling_lfm2_moe.py),用于在路由决策中引入与当前输入无关的专家先验偏移;初始化时该 Buffer 被显式置零(见 modeling_lfm2_moe.py);- 选中后的权重默认做归一化(
norm_topk_prob=True),再乘以routed_scaling_factor(默认 1.0)作为最终融合权重。
Lfm2MoeConfig:全部配置参数详解
Lfm2MoeConfig(configuration_lfm2_moe.py)是一个直接给出全部默认值的配置类(基于 huggingface_hub 的 @strict dataclass 与 @auto_docstring 注解)。下表汇总了该模型的核心超参、默认值及含义:
| 参数 | 默认值 | 含义 |
|---|---|---|
vocab_size |
65536 | 词表大小 |
hidden_size |
2048 | 隐藏维度 |
intermediate_size |
7168 | 稠密层 FFN 中间维度 |
moe_intermediate_size |
1792 | 单个专家的中间维度 |
num_hidden_layers |
32 | 解码器总层数 |
num_dense_layers |
2 | 浅层保留的稠密 FFN 层数,其后全为 MoE |
num_attention_heads |
32 | 查询头数 |
num_key_value_heads |
8 | 键/值头数(GQA 比例为 4:1) |
num_experts |
32 | 专家总数 |
num_experts_per_tok |
4 | 每个 token 激活的专家数(top-k) |
conv_L_cache |
3 | 卷积核长度(因果 conv 的短期上下文宽度) |
conv_bias |
False | 卷积层是否使用 bias |
use_expert_bias |
True | 路由打分是否叠加专家偏置 |
routed_scaling_factor |
1.0 | 路由权重缩放系数 |
norm_topk_prob |
True | 是否对 top-k 路由权重归一化 |
layer_types |
None | 每层算子类型列表("full_attention" / "conv"),由 __post_init__ 推导填充 |
max_position_embeddings |
128000 | 最大上下文长度 |
norm_eps |
1e-5 | RMSNorm 的 epsilon |
initializer_range |
0.02 | 参数初始化标准差 |
rope_parameters |
None | RoPE 参数(含 rope_type 与 rope_theta),default_theta = 1e6 |
tie_word_embeddings |
True | 输入/输出词嵌入是否共享 |
use_cache |
True | 是否启用 KV/卷积状态缓存 |
pad/bos/eos_token_id |
0 / 1 / 2 | 特殊 token id |
keys_to_ignore_at_inference |
["past_key_values"] |
推理时忽略的键 |
文档字符串(configuration_lfm2_moe.py)对四个 MoE/卷积专属参数给出了官方解释:conv_bias 控制卷积层 bias;conv_L_cache 是卷积层的缓存长度维度(即卷积核大小);num_dense_layers 描述浅层稠密层到 MoE 层的排布;use_expert_bias 决定路由权重是否叠加专家偏置。
层级排布 layer_types 的推导
Lfm2MoeConfig 本身不直接推导 layer_types(默认为 None),但它的孪生稠密配置 Lfm2Config 展示了同族模型的标准做法(src/transformers/models/lfm2/configuration_lfm2.py):当 layer_types is None 时,根据 full_attn_idxs 生成——落在索引集合内的层标为 "full_attention",其余为 "conv"。可以推断,真实发布的 LFM2-MoE 权重中,layer_types 在 checkpoint 的 config.json 里被显式给出,前向代码则直接按索引消费该列表。这种「卷积层与注意力层穿插」的布局,正是 LFM2 系列在速度/显存与质量之间做权衡的核心旋钮。
从默认值反推 8B-A1B 的容量拆解
以默认配置为 8.3B 总参数做一次粗略推算:32 层中约 30 层为 MoE,每层 32 个专家、每个专家(1792×2048 的 gate/up 与 2048×1792 的 down)参数约数百 MB 量级地堆叠,构成「总参数 8B」的主体;而每个 token 只经过前 2 层稠密 FFN + 各 MoE 层中 top-4 专家,因此激活参数量被压缩到约 1.5B,这也是模型名 LFM2-8B-A1B 中「A1B(Active ~1B 级)」的由来。需要说明:文档给出的是 8.3B 总参 / 1.5B 激活参这两个已发布口径,上面的推导仅用于帮助你理解稀疏激活如何产生数量级差异。
三个公开类:Lfm2MoePreTrainedModel / Lfm2MoeModel / Lfm2MoeForCausalLM
根据 docs/source/en/model_doc/lfm2_moe.md 的 autodoc 索引,本模型在公开 API 层面暴露三个类:
Lfm2MoeModel(基础 transformer 主干)
Lfm2MoeModel(modeling_lfm2_moe.py)是去掉语言模型头的纯主干:
- 组件包含
embed_tokens(词嵌入)、Lfm2MoeDecoderLayer × num_hidden_layers、pos_emb(旋转位置编码模块)与embedding_norm(主干末尾的 RMSNorm); forward只接受input_ids与inputs_embeds二选一;默认维护DynamicCache(注意:该缓存同时承担 KV 缓存与卷积状态缓存两类职责);- 输出类型为
MoeModelOutputWithPast,包含last_hidden_state与past_key_values。
Lfm2MoeForCausalLM(因果语言模型入口)
Lfm2MoeForCausalLM(modeling_lfm2_moe.py)在主干之上叠加 lm_head 线性层,并声明 _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"} 以支持权重共享(加载时自动对齐嵌入)。它的 forward 提供 labels 计算交叉熵损失与 logits_to_keep 只算尾部 logits 的优化路径,同时挂载了 GenerationMixin,可直接使用 generate()。
针对大模型并行加载,Lfm2MoeForCausalLM 还声明了三套并行规划:
_tp_plan:lm_head按列并行并收集输出(张量并行);_pp_plan:lm_head的流水线切分(管道并行);_fsdp_plan:lm_head保留完整权重(FSDP)。
此外 Lfm2MoePreTrainedModel 定义了专家并行 EP 计划(见 configuration_lfm2_moe.py),将路由 gate 映射为 ep_router、专家 gate/up 与 down 投影映射为 grouped_gemm、整个专家集合映射为 moe_tp_experts——从源码结构看,这套映射用于在 expert parallelism 下把不同专家分片到不同设备。这意味着 LFM2-MoE 从设计之初就为多卡分布式推理/训练保留了完整 hook。
Lfm2MoePreTrainedModel 的通用能力
基类声明了若干重要的能力开关(modeling_lfm2_moe.py):
supports_gradient_checkpointing = True(可梯度检查点);_supports_flash_attn / _supports_sdpa / _supports_flex_attn = True(三种注意力后端均可切换);_no_split_modules = ["Lfm2MoeDecoderLayer"](设备切分时保持解码器层完整);_can_compile_fullgraph = True(支持 torch.compile 全图编译)。
快速上手:加载与生成(可直接运行)
官方文档在 docs/source/en/model_doc/lfm2_moe.md 中给出了使用 AutoModelForCausalLM 生成答案的完整示例。由于 model_type = "lfm2_moe" 已挂入自动映射表,以下代码即可直接运行(需要先 pip install transformers accelerate torch,accelerate 提供 device_map="auto" 支持):
from transformers import AutoModelForCausalLM, AutoTokenizer
# 加载模型与 tokenizer
model_id = "LiquidAI/LFM2-8B-A1B"
model = AutoModelForCausalLM.from_pretrained(
model_id,
device_map="auto",
dtype="bfloat16",
# attn_implementation="flash_attention_2" # 在兼容 GPU 上取消注释以启用
)
tokenizer = AutoTokenizer.from_pretrained(model_id)
# 使用 chat template 组装提示词并生成
prompt = "What is C. elegans?"
input_ids = tokenizer.apply_chat_template(
[{"role": "user", "content": prompt}],
add_generation_prompt=True,
return_tensors="pt",
tokenize=True,
).to(model.device)
output = model.generate(
input_ids,
do_sample=True,
temperature=0.3,
min_p=0.15,
repetition_penalty=1.05,
max_new_tokens=512,
)
print(tokenizer.decode(output[0], skip_special_tokens=False))
几点补充说明,帮助你在真实环境中把这段代码用得更稳:
dtype="bfloat16":from_pretrained直接传 dtype 字符串即可,8B 级模型在 bf16 下约占用 16-17GB 权重显存,请按 GPU 显存规划是否叠加device_map="auto"(可自动卸载到 CPU)。- Flash Attention 可选:
attn_implementation="flash_attention_2"需要flash-attn库与 Ampere 及以上架构 GPU;由于基类声明_supports_flash_attn,在没有该参数时框架会自动回退到 SDPA/eager 路径,功能不受影响,仅在吞吐上有差异。 apply_chat_template:以LiquidAI/LFM2-8B-A1B的 tokenizer 为例,官方推荐把对话以[{"role": "user", "content": ...}]结构传入并打开add_generation_prompt,框架会拼接出该模型约定的对话格式,替代手工拼接提示词。- 采样参数:示例使用了
do_sample=True配合temperature=0.3与min_p=0.15——min_p是另一种核采样(仅保留概率不低于「最高概率 × min_p」的 token),相比top_p对温度更鲁棒;repetition_penalty=1.05抑制重复,max_new_tokens=512控制生成长度。 skip_special_tokens=False:保留输出中的特殊 token,便于观察模板效果;实际展示时通常改为True并配合clean_up_tokenization_spaces=False让文本更干净。
如果你不想拉取真实权重,也可以直接用配置类构造一个随机初始化模型,用于验证网络结构、跑通前后向:
from transformers import Lfm2MoeModel, Lfm2MoeConfig
# 初始化一个 LFM2-MoE 结构
configuration = Lfm2MoeConfig()
model = Lfm2MoeModel(configuration)
# 读取模型配置
configuration = model.config
注意:这是配置文档中展示的「随机初始化验证用法」,随机权重没有推理能力,仅适合做形状检查或结构调试;真正使用请走上面的预训练权重加载路径。
实现要点与阅读索引
- 模型文档:docs/source/en/model_doc/lfm2_moe.md(本篇主文档),稠密版对照可看 docs/source/en/model_doc/lfm2.md;
- 配置文件:src/transformers/models/lfm2_moe/configuration_lfm2_moe.py(全部默认超参与 EP 并行规划);
- 模型实现:src/transformers/models/lfm2_moe/modeling_lfm2_moe.py,重点读 4 段:
Lfm2MoeExperts/Lfm2MoeTopKRouter/Lfm2MoeSparseMoeBlock(MoE 核心,L139-L221)、Lfm2MoeAttention(GQA + QK-Norm,L294-L350)、Lfm2MoeShortConv(门控卷积与状态缓存,L408-L474)、Lfm2MoeDecoderLayer(层内排布与稠密/稀疏切换,L477-L523); - 模块化来源:src/transformers/models/lfm2_moe/modular_lfm2_moe.py(通过继承复用 LFM2 / Llama / Mixtral / Qwen2-MoE / Qwen3-MoE 组件的拼装清单);
- 自动映射:src/transformers/models/auto/modeling_auto.py(
AutoModel)与 src/transformers/models/auto/modeling_auto.py(AutoModelForCausalLM)。
小结
LFM2-MoE 是一份「混合架构教科书」级别的实现:它在 LFM2 的轻量混合骨干(门控短程卷积 + GQA 注意力穿插排布)之上,用稀疏 MoE 替换了绝大多数层的前馈网络,在保持约 1.5B 激活参数的前提下把总参数扩充到 8.3B。仓库中该模型代码以 modular 文件为唯一事实来源,通过继承 LFM2、Llama、Mixtral 与 Qwen2/3-MoE 的成熟组件拼装而成,既保证了卷积层、GQA、专家容器、路由器等每个环节都具备久经验证的底层实现,也让「新增一个模型族」的成本大幅下降。对开发者而言,理解这份实现最有价值的三件事是:layer_types 如何按层切换注意力与卷积算子、num_dense_layers 如何控制稠密到稀疏 FFN 的切换点,以及专家偏置(use_expert_bias)如何在 sigmoid 路由打分上做全局偏移——这三处共同定义了 LFM2-MoE 在速度、显存与生成质量之间的平衡曲线。
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 StartedRust0626
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