MoE 混合专家模型推理参数与前向传播详解
以 DeepSeek-V2-Lite 为例,详细拆解 MoE 的每个参数含义、Router 路由决策、Token Permutation、Grouped GEMM、共享专家计算、MLA 注意力压缩与多 GPU 并行策略。
以 DeepSeek-V2-Lite 为参考模型,详细拆解 MoE 推理过程中每个参数的含义和前向传播流程。
1. DeepSeek-V2-Lite 架构总览
1.1 模型基本参数
hidden_size: 2048 # 隐藏层维度
num_hidden_layers: 27 # Decoder 层数
num_attention_heads: 16 # 注意力头数
vocab_size: 102400 # 词表大小
dtype: bfloat16
1.2 层结构:Dense 层 vs MoE 层
DeepSeek-V2-Lite 的 27 层不全是 MoE:
first_k_dense_replace = 1 # 前 1 层使用 Dense FFN
moe_layer_freq = 1 # 之后每层都是 MoE
Layer 0: Attention + Dense FFN (intermediate_size = 10944)
Layer 1: Attention + MoE (64 routed experts + 2 shared experts)
Layer 2: Attention + MoE
...
Layer 26: Attention + MoE
共计: 1 层 Dense + 26 层 MoE
为什么第一层用 Dense? 第一层直接接 Embedding 输出,表征还不够丰富,路由决策质量低。用 Dense 层先做一次充分变换,后续层再做稀疏路由效果更好。
1.3 MoE 配置
n_routed_experts = 64 # 路由专家总数
num_experts_per_tok = 6 # 每个 token 激活的专家数 (top-k)
n_shared_experts = 2 # 共享专家数(始终激活)
moe_intermediate_size = 1408 # 每个专家的中间维度
scoring_func = softmax # 路由评分函数
topk_method = greedy # top-k 选择方法
n_group = 1 # 专家分组数(Lite 不分组)
topk_group = 1 # 每组选择数
norm_topk_prob = false # 不重归一化路由权重
routed_scaling_factor = 1.0 # 路由输出缩放因子
1.4 MLA(Multi-head Latent Attention)配置
kv_lora_rank = 512 # KV 压缩的 LoRA 秩
q_lora_rank = null # Lite 不压缩 Q(完整版 V2 = 1536)
qk_nope_head_dim = 128 # Q/K 非旋转部分维度
qk_rope_head_dim = 64 # Q/K 旋转位置编码部分维度
v_head_dim = 128 # V 头维度
1.5 DeepSeek-V2-Lite 完整 Decoder Layer 结构图
输入 hidden_states: (num_tokens, 2048)
│
├── input_layernorm (RMSNorm)
│
├── MLA Attention ─────────────────────────────────────────────┐
│ ├── kv_a_proj: (2048 → 512+64) KV 压缩 + rope 部分 │
│ ├── kv_a_layernorm (RMSNorm) │
│ ├── kv_b_proj: (512 → 16*(128+128)) 解压为 K_nope + V │
│ ├── q_proj: (2048 → 16*(128+64)) Q 直接投影 │
│ ├── RoPE (对 rope 部分) │
│ ├── Attention 计算 │
│ └── o_proj: (16*128 → 2048) │
│ │
├── + residual ←───────────────────────────────────────────────┘
│
├── post_attention_layernorm (RMSNorm)
│
├── MoE / Dense FFN ──────────────────────────────────────────┐
│ ├── [Layer 0] Dense FFN: │
│ │ gate_up_proj: (2048 → 2*10944) │
│ │ SiLU + mul │
│ │ down_proj: (10944 → 2048) │
│ │ │
│ ├── [Layer 1-26] MoE: │
│ │ ├── gate: (2048 → 64) Router │
│ │ ├── 64 Experts: 每个 (2048↔1408) SwiGLU FFN │
│ │ └── 2 Shared Experts: (2048↔2816) SwiGLU FFN │
│ └─────────────────────────────────────────────────────────┘
│
└── + residual
输出 hidden_states: (num_tokens, 2048)
2. MoE 层的全部参数(以 DeepSeek-V2-Lite 为例)
2.1 Router(门控网络)
gate.weight: (64, 2048)
| 属性 | 值 |
|---|---|
| 参数量 | 64 × 2048 = 131K (0.13M) |
| 有无 bias | 无 |
| 作用 | 将 token 映射为 64 个专家的分数 |
计算过程:
router_logits = hidden_states @ gate.weight.T # (num_tokens, 64)
Router 参数量极小(仅 0.13M),但决定了所有 token 的路由去向,是 MoE 中最关键的组件。
2.2 路由专家(Routed Experts)
每个路由专家是一个 SwiGLU FFN,包含 3 个权重矩阵:
单个 Expert 的参数:
├── w1 (gate_proj): (2048, 1408) # 门控投影
├── w3 (up_proj): (2048, 1408) # 上投影
└── w2 (down_proj): (1408, 2048) # 下投影
单个 Expert 参数量: 3 × 2048 × 1408 = 8.65M
vLLM 中所有专家堆叠存储以支持 Grouped GEMM:
实际存储 (FusedMoE):
├── w13: (64, 2*1408, 2048) # w1 和 w3 合并,64 个专家堆叠
└── w2: (64, 2048, 1408) # 64 个专家堆叠
64 个路由专家总参数量: 64 × 8.65M = 553.6M
2.3 共享专家(Shared Experts)
DeepSeek-V2-Lite 有 2 个共享专家,它们对所有 token 始终激活,不经过 Router。
2 个共享专家被合并为一个更大的 FFN:
shared_experts 参数:
├── gate_up_proj: (2048, 2 × 2 × 1408) = (2048, 5632)
└── down_proj: (2816, 2048)
shared_intermediate_size = n_shared_experts × moe_intermediate_size
= 2 × 1408 = 2816
共享专家参数量: 2 × 2048 × 2816 + 2816 × 2048 = 17.3M
为什么合并? 2 个小专家可以合并为 1 个 intermediate_size=2816 的 FFN,减少 kernel launch 次数。
2.4 单层 MoE 参数汇总
组件 Shape 参数量
──────────────────────────────────────────────────────────────
gate (Router) (64, 2048) 0.13M
64 路由专家 w13 (64, 2816, 2048) 368.9M
64 路由专家 w2 (64, 2048, 1408) 184.5M
共享专家 gate_up_proj (2048, 5632) 11.5M
共享专家 down_proj (2816, 2048) 5.8M
──────────────────────────────────────────────────────────────
单层 MoE 总参数: ~570.8M
但每个 token 实际计算量:
6 个路由专家: 6 × 8.65M = 51.9M
2 个共享专家: = 17.3M
Router: = 0.13M
─────────────────────────────────────────
每 token 计算量: ~69.3M (仅总量的 12.1%)
2.5 全模型参数构成
Embedding: 102400 × 2048 = 209.7M
Layer 0 (Dense):
Attention: ~MLA 参数 ≈ 25M
Dense FFN: 2048×10944×3 = 67.2M
Layer 1-26 (MoE × 26):
Attention × 26: ~MLA 参数 ≈ 650M
MoE × 26: 570.8M × 26 = 14.84B
LM Head: 2048 × 102400 = 209.7M
──────────────────────────────────────────────────────────────
总参数量: ~15.7B
每 token 激活参数: ~2.4B(共享专家 + 6/64 路由专家 + Attention)
3. 完整前向传播流程
以处理 32 个 token 为例,详细跟踪每一步的张量变化。
3.1 Phase 1: Routing(路由决策)
# Step 1: 计算 router logits
router_logits = hidden_states @ gate.weight.T
# (32, 2048) × (2048, 64) → (32, 64)
# Step 2: Softmax 评分
# scoring_func = "softmax"
routing_weights = softmax(router_logits, dim=-1) # (32, 64) 概率分布
# Step 3: Greedy Top-6 选择
# topk_method = "greedy", num_experts_per_tok = 6
topk_weights, topk_ids = topk(routing_weights, k=6)
# topk_weights: (32, 6) — 6 个被选中专家的权重
# topk_ids: (32, 6) — 6 个被选中专家的编号 (0~63)
# Step 4: 不重归一化
# norm_topk_prob = false → 保持原始 softmax 概率,不除以 sum
具体示例(Token "深"):
router_logits: [0.3, -0.1, 0.8, ..., 1.2, 0.5, ...] (64 个值)
softmax 后: [0.02, 0.01, 0.03, ..., 0.05, 0.02, ...] (和为 1.0)
Greedy top-6 选择:
Expert 47 (权重 0.053)
Expert 12 (权重 0.048)
Expert 3 (权重 0.041)
Expert 58 (权重 0.039)
Expert 21 (权重 0.036)
Expert 7 (权重 0.033)
注意: 6 个权重之和 = 0.25 (不是 1.0,因为 norm_topk_prob=false)
DeepSeek-V2-Lite 不使用分组 Top-k
n_group = 1, topk_group = 1 → 直接 greedy 选 top-6
(完整版 V2 使用 n_group=8, topk_group=3 的分组路由)
3.2 Phase 2: Token Permutation(Token 重排)
每个 token 被复制 6 份(top_k=6),按专家 ID 重新排列:
路由结果(简化为 4 token, 3 expert 示例):
Token 0 → Expert [2, 5, 11]
Token 1 → Expert [5, 2, 8]
Token 2 → Expert [11, 5, 2]
Token 3 → Expert [8, 2, 11]
Permute 后(按专家分组,连续排列):
Expert 2: [Token 0, Token 1, Token 2, Token 3] offset: 0→4
Expert 5: [Token 0, Token 1, Token 2] offset: 4→7
Expert 8: [Token 1, Token 3] offset: 7→9
Expert 11: [Token 0, Token 2, Token 3] offset: 9→12
expert_first_token_offset = [0, 4, 7, 9, 12] ← Grouped GEMM 的边界
inv_permuted_idx = [...] ← 恢复原始顺序的映射
张量变化(实际 32 token 场景):
输入: hidden_states (32, 2048)
复制 ×6: expanded (192, 2048) = 32 × 6
重排: permuted_hidden (192, 2048) 同大小,但按专家排序
expert_offsets (65,) 64 个专家 + 1 个终止位
3.3 Phase 3: Expert Computation(专家计算)
3.3.1 路由专家的 SwiGLU FFN
每个专家对分配给它的 token 执行:
# Expert i 处理 M 个 token
x = permuted_hidden[start:end] # (M, 2048)
# Step 1: gate_proj + up_proj(合并为一次矩阵乘法)
gate_up = x @ w13[i].T # (M, 2048) × (2048, 2816) → (M, 2816)
gate_out, up_out = gate_up.chunk(2) # 各 (M, 1408)
# Step 2: SwiGLU 激活
x = SiLU(gate_out) * up_out # (M, 1408)
# Step 3: down_proj
expert_out = x @ w2[i].T # (M, 1408) × (1408, 2048) → (M, 2048)
SwiGLU 激活函数详解:
SwiGLU(gate, up) = SiLU(gate) × up
= (gate × σ(gate)) × up
= gate × sigmoid(gate) × up
其中 σ(x) = 1/(1+e^(-x)) 是 sigmoid 函数
SiLU(x) = x × σ(x),也称 Swish 激活
Grouped GEMM:实际不会逐专家循环,而是一次 kernel 处理所有专家:
输入: permuted_hidden (192, 2048) + expert_offsets (65,)
w13 (64, 2816, 2048)
w2 (64, 2048, 1408)
kernel 内部按 expert_offsets 分段:
Expert 0: offset[0]:offset[1] 的 token → w13[0], w2[0]
Expert 1: offset[1]:offset[2] 的 token → w13[1], w2[1]
...
Expert 63: offset[63]:offset[64] 的 token → w13[63], w2[63]
输出: permuted_output (192, 2048)
3.3.2 共享专家的计算
共享专家独立于路由,对所有 32 个 token 都计算:
# 共享专家 = 合并后的单个 SwiGLU FFN
# shared_intermediate_size = 2 × 1408 = 2816
x = hidden_states # (32, 2048)
gate_up = x @ shared_gate_up_proj.T # (32, 2048) × (2048, 5632) → (32, 5632)
gate_out, up_out = gate_up.chunk(2) # 各 (32, 2816)
x = SiLU(gate_out) * up_out # (32, 2816)
shared_out = x @ shared_down_proj.T # (32, 2816) × (2816, 2048) → (32, 2048)
3.4 Phase 4: Unpermute & Combine(加权合并)
# Step 1: 恢复原始 token 顺序 + 按路由权重加权
# 伪代码:
for token_id in range(32):
routed_output[token_id] = 0
for j in range(6): # top_k = 6
expert_id = topk_ids[token_id, j]
weight = topk_weights[token_id, j]
routed_output[token_id] += weight * expert_output_of(token_id, expert_id)
# routed_output: (32, 2048)
# Step 2: 合并路由输出 + 共享专家输出
# routed_scaling_factor = 1.0 → 无额外缩放
final_output = routed_output + shared_out # (32, 2048)
3.5 完整张量形状变化一览
阶段 张量 Shape
─────────────────────────────────────────────────────────────────────────
输入 hidden_states (32, 2048)
Phase 1: Routing
gate 线性层 router_logits (32, 64)
softmax + greedy top-6 topk_weights (32, 6)
topk_ids (32, 6)
Phase 2: Permute
token 复制 × top_k=6 permuted_hidden (192, 2048)
expert_offsets (65,)
inv_permuted_idx (192,)
Phase 3a: Routed Expert Compute (Grouped GEMM)
w13 (gate+up_proj) intermediate (192, 2816)
chunk → SiLU × up activated (192, 1408)
w2 (down_proj) expert_output (192, 2048)
Phase 3b: Shared Expert Compute (并行)
shared gate_up_proj intermediate (32, 5632)
chunk → SiLU × up activated (32, 2816)
shared down_proj shared_out (32, 2048)
Phase 4: Unpermute + Combine
加权合并路由结果 routed_output (32, 2048)
+ 共享专家 final_output (32, 2048)
4. MLA:DeepSeek 的注意力压缩
DeepSeek-V2-Lite 不仅在 FFN 用了 MoE,Attention 也有独特设计——MLA(Multi-head Latent Attention),大幅压缩 KV Cache。
4.1 标准 MHA vs MLA
标准 MHA (如 LLaMA):
KV Cache 大小 = num_heads × head_dim × 2 (K+V)
= 16 × 128 × 2 = 4096 per token
MLA (DeepSeek-V2-Lite):
KV Cache 大小 = kv_lora_rank + qk_rope_head_dim
= 512 + 64 = 576 per token
压缩比: 576 / 4096 = 14.1% → KV Cache 缩小到 1/7!
4.2 MLA 前向过程
# --- KV 路径: 压缩 → 解压 ---
# 1. 压缩: hidden_states → 低秩表示 + rope 部分
kv_a = kv_a_proj(hidden_states) # (N, 2048) → (N, 512+64)
kv_compressed, k_rope = split(kv_a, [512, 64])
# 2. LayerNorm
kv_compressed = kv_a_layernorm(kv_compressed) # (N, 512)
# 3. 解压: 低秩 → 全尺寸 K_nope 和 V
kv = kv_b_proj(kv_compressed) # (N, 512) → (N, 16*(128+128))
k_nope, v = split(kv, [16*128, 16*128])
# KV Cache 只存 kv_compressed (512) + k_rope (64) = 576 维!
# --- Q 路径: 直接投影 ---
q = q_proj(hidden_states) # (N, 2048) → (N, 16*(128+64))
q_nope, q_rope = split(q, [16*128, 16*64])
# --- RoPE ---
q_rope = apply_rope(q_rope)
k_rope = apply_rope(k_rope)
# --- 拼接 Q 和 K ---
q = cat([q_nope, q_rope]) # (N, 16, 192)
k = cat([k_nope, k_rope]) # (N, 16, 192)
# --- Attention ---
attn_output = scaled_dot_product_attention(q, k, v) # (N, 16, 128)
output = o_proj(attn_output) # (N, 2048)
5. 多 GPU 下的参数分布
5.1 Tensor Parallelism(TP)
切分每个专家的 intermediate 维度:
TP=2 时 (DeepSeek-V2-Lite):
GPU 0: w13[:, :1408, :] w2[:, :, :704] # 前半 intermediate
GPU 1: w13[:, 1408:, :] w2[:, :, 704:] # 后半 intermediate
每个 GPU 都有全部 64 个专家,但每个专家只有一半参数
最后 all-reduce 合并结果
5.2 Expert Parallelism(EP)
将不同专家分配到不同 GPU:
EP=2 时 (64 experts):
GPU 0: Expert 0~31 (完整参数)
GPU 1: Expert 32~63 (完整参数)
Token 路由到不在本地的专家 → All-to-All 通信
5.3 EP + TP 混合
EP=2, TP=2 时 (4 GPUs):
GPU 0: Expert 0-31 的前半 intermediate
GPU 1: Expert 0-31 的后半 intermediate
GPU 2: Expert 32-63 的前半 intermediate
GPU 3: Expert 32-63 的后半 intermediate
5.4 All-to-All 通信(EP 核心)
┌─ GPU 0 (Expert 0-31) ─┐
Token 分发 ──→ │ │ ──→ 结果收集
(All-to-All) └─ GPU 1 (Expert 32-63) ─┘ (All-to-All)
Step 1 (Dispatch): 每个 GPU 把路由到远程专家的 token 发出去
Step 2 (Compute): 每个 GPU 用本地专家处理收到的 token
Step 3 (Combine): 每个 GPU 把结果发回给原始 token 所在的 GPU
共享专家与 All-to-All 重叠:
时间线:
├─ All-to-All dispatch ─┤─ Expert compute ─┤─ All-to-All combine ─┤
├─ Shared expert compute ───────────────────┤
(共享专家计算与通信并行,隐藏延迟)
6. Router 的负载均衡
6.1 问题:专家负载不均
64 个专家,如果所有 token 都涌向少数几个 → 热门专家过载,其余空闲。
6.2 辅助损失(训练时)
DeepSeek-V2-Lite 使用 sequence-level 辅助损失(seq_aux=true, aux_loss_alpha=0.001):
# 计算每个专家收到的 token 比例(在序列级别)
expert_load = (topk_ids == expert_id).float().mean(dim=0)
# 计算每个专家的平均路由权重
expert_importance = routing_weights[:, expert_id].mean()
# 辅助损失: 鼓励均匀分配
aux_loss = n_routed_experts * (expert_load * expert_importance).sum()
total_loss = main_loss + 0.001 * aux_loss
6.3 EPLB(推理时,vLLM 特有)
Expert Parallel Load Balancing:动态复制热门专家:
原始: 64 experts + N redundant slots
如果 Expert 12 过载:
Physical 0-63: 原始 64 个 expert
Physical 64: Expert 12 的副本 ← 冗余副本分担负载
Physical 65: Expert 47 的副本
...
7. DeepSeek-V2-Lite vs 完整版 V2 vs V3
| 特性 | V2-Lite | V2 (236B) | V3 (671B) |
|---|---|---|---|
| hidden_size | 2048 | 5120 | 7168 |
| 层数 | 27 | 60 | 61 |
| n_routed_experts | 64 | 160 | 256 |
| top_k | 6 | 6 | 8 |
| n_shared_experts | 2 | 2 | 1 |
| moe_intermediate_size | 1408 | 1536 | 2048 |
| n_group (分组路由) | 1 (不分组) | 8 | 8 |
| topk_group | 1 | 3 | 4 |
| q_lora_rank | null (不压缩) | 1536 | 1536 |
| kv_lora_rank | 512 | 512 | 512 |
| routed_scaling_factor | 1.0 | 1.0 | 16.0 |
| 总参数 | ~15.7B | 236B | 671B |
| 激活参数 | ~2.4B | ~21B | ~37B |
V2-Lite 的简化:
- 不分组路由(
n_group=1)→ 直接 greedy top-6 - 不压缩 Q(
q_lora_rank=null)→ 直接投影 - 无路由缩放(
routed_scaling_factor=1.0)
8. MoE 的量化
MoE 参数量大部分在 Expert FFN,量化收益显著:
| 量化方式 | Expert 权重精度 | V2-Lite 估计大小 |
|---|---|---|
| BF16 | 16-bit | ~31.4 GB |
| FP8 (W8A8) | 8-bit | ~15.7 GB |
| INT4 (W4A16) | 4-bit | ~7.8 GB |
Router (gate) 通常不量化——仅 0.13M 参数,但决定路由质量。
9. 源码索引
| 组件 | vLLM 文件位置 |
|---|---|
| DeepSeek-V2 模型定义 | vllm/.../models/deepseek_v2.py |
| DeepseekV2MoE 类 | deepseek_v2.py:235-398 |
| DeepseekV2MLP (Dense FFN) | deepseek_v2.py:188-232 |
| MLA Attention | deepseek_v2.py:537-581 |
| FusedMoE 主模块 | vllm/.../layers/fused_moe/layer.py |
| SharedFusedMoE (共享专家) | vllm/.../fused_moe/shared_fused_moe.py |
| Router / GateLinear | vllm/.../fused_moe/router/gate_linear.py |
| Token Permute/Unpermute | vllm/.../fused_moe/moe_permute_unpermute.py |
| Expert 并行分配 | layer.py → determine_expert_map() |
| CUDA MoE kernels | vllm/csrc/moe/ |
| HuggingFace 配置 | transformers/.../deepseek_v2/configuration_deepseek_v2.py |
10. 一句话总结
DeepSeek-V2-Lite 的 MoE 推理 = Router 从 64 个专家中 greedy 选 6 个 → Token 按专家分组 Grouped GEMM → 加权合并 + 2 个共享专家输出。15.7B 总参数中每个 token 只激活 ~2.4B,配合 MLA 的 KV Cache 压缩(7 倍),实现了高效推理。