首页
/ Transformers 中的 LFM2-MoE 模型:融合门控卷积骨干与稀疏混合专家的端侧推理架构全解析

Transformers 中的 LFM2-MoE 模型:融合门控卷积骨干与稀疏混合专家的端侧推理架构全解析

2026-09-07 14:24:11作者:卓炯娓

LFM2-MoE 是由 Liquid AI 提出的 LFM2 系列混合专家(Mixture-of-Experts, MoE)变体,它在保留 LFM2「短程输入感知门控卷积 + 分组查询注意力(GQA)」快速骨干的基础上,通过稀疏 MoE 前馈网络换取更高表示容量。本篇文章以仓库中的官方文档 docs/source/en/model_doc/lfm2_moe.md 为主体,结合 configuration_lfm2_moe.pymodeling_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),并已挂入自动映射表:AutoModellfm2_moe 映射到 Lfm2MoeModelAutoModelForCausalLM 映射到 Lfm2MoeForCausalLM(见 src/transformers/models/auto/modeling_auto.pysrc/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.pyLfm2MoeDecoderLayer。每一层都以 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)

Lfm2MoeShortConvmodeling_lfm2_moe.py)内部是标准的门控卷积块:

  • 深度可分离因果一维卷积 nn.Conv1dgroups=hidden_sizekernel_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),拉不到时回退到 PyTorch F.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)

Lfm2MoeAttentionmodeling_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 归一化的 RMSNormq_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/updown 线性变换,并按路由权重加权后 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:全部配置参数详解

Lfm2MoeConfigconfiguration_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_typerope_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 主干)

Lfm2MoeModelmodeling_lfm2_moe.py)是去掉语言模型头的纯主干:

  • 组件包含 embed_tokens(词嵌入)、Lfm2MoeDecoderLayer × num_hidden_layerspos_emb(旋转位置编码模块)与 embedding_norm(主干末尾的 RMSNorm);
  • forward 只接受 input_idsinputs_embeds 二选一;默认维护 DynamicCache(注意:该缓存同时承担 KV 缓存与卷积状态缓存两类职责);
  • 输出类型为 MoeModelOutputWithPast,包含 last_hidden_statepast_key_values

Lfm2MoeForCausalLM(因果语言模型入口)

Lfm2MoeForCausalLMmodeling_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_planlm_head 按列并行并收集输出(张量并行);
  • _pp_planlm_head 的流水线切分(管道并行);
  • _fsdp_planlm_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 torchaccelerate 提供 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))

几点补充说明,帮助你在真实环境中把这段代码用得更稳:

  1. dtype="bfloat16"from_pretrained 直接传 dtype 字符串即可,8B 级模型在 bf16 下约占用 16-17GB 权重显存,请按 GPU 显存规划是否叠加 device_map="auto"(可自动卸载到 CPU)。
  2. Flash Attention 可选attn_implementation="flash_attention_2" 需要 flash-attn 库与 Ampere 及以上架构 GPU;由于基类声明 _supports_flash_attn,在没有该参数时框架会自动回退到 SDPA/eager 路径,功能不受影响,仅在吞吐上有差异。
  3. apply_chat_template:以 LiquidAI/LFM2-8B-A1B 的 tokenizer 为例,官方推荐把对话以 [{"role": "user", "content": ...}] 结构传入并打开 add_generation_prompt,框架会拼接出该模型约定的对话格式,替代手工拼接提示词。
  4. 采样参数:示例使用了 do_sample=True 配合 temperature=0.3min_p=0.15——min_p 是另一种核采样(仅保留概率不低于「最高概率 × min_p」的 token),相比 top_p 对温度更鲁棒;repetition_penalty=1.05 抑制重复,max_new_tokens=512 控制生成长度。
  5. 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

注意:这是配置文档中展示的「随机初始化验证用法」,随机权重没有推理能力,仅适合做形状检查或结构调试;真正使用请走上面的预训练权重加载路径。

实现要点与阅读索引

小结

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 在速度、显存与生成质量之间的平衡曲线。

登录后查看全文
热门项目推荐
相关项目推荐