STAGE 3 高效大模型

DeepSeek MLA + MoE

用更少的计算资源达到大模型的效果 — 低秩压缩注意力 + 稀疏专家混合

📊 Excel 手推 ⏱️ 30 分钟 🎯 MLA + MoE
1
📋 本章要点
  • MLA(多头潜在注意力)用低秩压缩 KV 缓存,大幅降低推理成本
  • MoE(专家混合)让模型只激活部分参数,提升计算效率
  • DeepSeek 证明了"高效架构 + 精细工程"可以匹敌更大规模模型
  • 开源策略推动了整个 AI 社区的技术进步

DeepSeek 的创新

DeepSeek 的核心思想:用更少的计算资源达到大模型的效果。传统大模型(如 LLaMA)在推理时需要激活所有参数,计算成本极高。DeepSeek 通过两大创新大幅降低了计算开销。

生活类比:想象一家公司遇到一个复杂问题。传统做法是请一个全能专家(工资很高),而 DeepSeek 的做法是请一组各有所长的专家团队,每次只激活最相关的 2 位专家来解决问题。这样既保证了专业性,又大幅降低了成本。

两大核心创新

🗜️

Multi-head Latent Attention (MLA)

低秩压缩 KV Cache

将 K、V 压缩到低维空间,推理时只需缓存压缩后的向量,大幅减少显存占用

🧩

Mixture of Experts (MoE)

稀疏激活专家网络

路由器根据输入动态选择 Top-K 专家,每次只激活少量专家,大幅减少计算量

DeepSeek 效率对比

输入
token 序列
MLA
压缩 KV
MoE
选 Top-K 专家
输出
高效推理
2

Multi-head Latent Attention (MLA)

标准 Multi-head Attention 的问题:推理时需要缓存每个 token 的 K 和 V 向量(KV Cache),序列越长占用显存越大。MLA 通过低秩压缩解决这个问题。

标准 MHA vs MLA 的 KV Cache 对比

假设:模型维度 d=128,注意力头数 h=8,每头维度 dk=16,序列长度 n=1024,压缩维度 dc=32

KV Cache 显存占用对比
方案 每 token 缓存 计算公式 总显存 (n=1024)
标准 MHA K + V 2 × h × dk = 2 × 8 × 16 256 × 1024 = 262,144
MLA (压缩) cKV dc = 32 32 × 1024 = 32,768
节省比例 MLA 只需缓存压缩向量 cKV 8 倍压缩!

MLA 工作流程

h (输入)
d = 128 维
WDKV (下投影)
128 → 32 维
cKV
压缩向量 32 维
WUK, WUV
恢复 K, V

cKV = WDKV · h   (下投影:压缩)

K = WUK · cKV   (上投影:恢复 K)

V = WUV · cKV   (上投影:恢复 V)

3

MLA 低秩分解 — Excel 手推

用一个简化的例子手推 MLA 的压缩和恢复过程。点击蓝色单元格可以编辑输入值!

输入隐藏状态 h

假设输入向量 h 维度 d=4(简化演示),压缩维度 dc=2:

输入向量 h(点击编辑)
h1 h2 h3 h4
1.0 0.5 -0.3 0.8

Step 3a:下投影 — 压缩 cKV = WDKV · h

WDKV 将 d=4 维压缩到 dc=2 维:

下投影矩阵 WDKV (2×4)
h1 h2 h3 h4
W1 0.5 0.3 -0.2 0.1
W2 -0.1 0.4 0.6 0.2
压缩结果 cKV = WDKV · h
分量 计算过程 公式 结果
cKV,1 0.5×h1 + 0.3×h2 + (-0.2)×h3 + 0.1×h4 0.5×1.0 + 0.3×0.5 + (-0.2)×(-0.3) + 0.1×0.8 0.76
cKV,2 (-0.1)×h1 + 0.4×h2 + 0.6×h3 + 0.2×h4 (-0.1)×1.0 + 0.4×0.5 + 0.6×(-0.3) + 0.2×0.8 0.08

Step 3b:上投影 — 恢复 K 和 V

从压缩向量 cKV 恢复出完整的 K 和 V:

WUK (4×2) — 恢复 K
[[0.8, -0.2], [0.1, 0.5], [-0.3, 0.7], [0.4, 0.1]]
WUV (4×2) — 恢复 V
[[0.6, 0.3], [-0.2, 0.9], [0.5, -0.1], [0.1, 0.4]]
恢复的 K = WUK · cKV
分量 计算 结果
K1 0.8×0.76 + (-0.2)×0.08 0.59
K2 0.1×0.76 + 0.5×0.08 0.12
K3 (-0.3)×0.76 + 0.7×0.08 -0.17
K4 0.4×0.76 + 0.1×0.08 0.31
恢复的 V = WUV · cKV
分量 计算 结果
V1 0.6×0.76 + 0.3×0.08 0.48
V2 (-0.2)×0.76 + 0.9×0.08 -0.08
V3 0.5×0.76 + (-0.1)×0.08 0.37
V4 0.1×0.76 + 0.4×0.08 0.11

参数量对比

组件 标准 MHA MLA
KV 投影矩阵 WK + WV:2 × d × d = 2 × 128 × 128 = 32,768 WDKV + WUK + WUV:128×32 + 32×128 + 32×128 = 12,288
压缩比 参数量减少 62.5%,KV Cache 减少 8 倍
4

Mixture of Experts (MoE) — Excel 手推

MoE 的核心:路由器 (Router) 根据输入决定激活哪些专家,然后加权合并专家输出。点击蓝色单元格可以编辑输入值!

Step 4a:路由器计算

路由器是一个简单的线性层 + Softmax,输出每个专家的"被选中概率"。假设 4 个专家,输入 x 维度=2:

输入向量 x(点击编辑)
x1 x2
0.8 0.3
路由器权重矩阵 Wr (4×2)
专家 Wr1 Wr2
专家 1 1.0 0.5
专家 2 0.3 1.2
专家 3 -0.5 0.8
专家 4 0.7 -0.2
路由器输出(logits → Softmax → 权重)
专家 logit 计算 logit 值 Softmax 权重 Top-2?
专家 1 1.0×0.8 + 0.5×0.3 0.95 0.32
专家 2 0.3×0.8 + 1.2×0.3 0.60 0.23
专家 3 (-0.5)×0.8 + 0.8×0.3 -0.16 0.11
专家 4 0.7×0.8 + (-0.2)×0.3 0.50 0.20

Step 4b:专家计算与加权输出

只有被选中的 Top-2 专家参与计算,其他专家不激活(稀疏激活):

专家输出与加权合并
专家 专家输出(简化) 路由器权重 加权输出 是否激活
专家 1 [0.9, 0.2] 0.32 [0.29, 0.06] ✓ 激活
专家 2 未激活 0.00 [0, 0] ✗ 跳过
专家 3 未激活 0.00 [0, 0] ✗ 跳过
专家 4 [0.5, 0.7] 0.20 [0.10, 0.14] ✓ 激活
最终输出 = 专家1加权 + 专家4加权 [0.39, 0.20] 2/4 专家

MoE 只用 2/4 = 50% 的专家就完成了计算,但效果接近使用全部专家。

实际 DeepSeek 使用 64 个专家,每次只激活 2 个,计算量仅为 2/64 = 3.125%!

5

互动实验 — MoE 专家路由

模拟 8 个专家的 MoE 路由。拖动滑块调整输入特征,观察路由器如何动态选择专家。

输入特征

专家激活权重

柱状图显示每个专家的路由器权重,绿色为被选中的 Top-2 专家:

稀疏激活效果

2
激活专家数
25%
计算量占比
75%
稀疏度
6

代码实现 (PyTorch)

mla.py
import torch
import torch.nn as nn
import torch.nn.functional as F

class MultiHeadLatentAttention(nn.Module):
    """Multi-head Latent Attention (MLA) — DeepSeek-V2 核心创新"""

    def __init__(self, d_model=512, n_heads=8, d_compress=64):
        super().__init__()
        self.d_model = d_model
        self.n_heads = n_heads
        self.d_k = d_model // n_heads
        self.d_compress = d_compress

        # Q 投影(与标准 MHA 相同)
        self.W_q = nn.Linear(d_model, d_model)

        # MLA 核心:下投影 + 上投影(替代标准的 K/V 投影)
        self.W_DKV = nn.Linear(d_model, d_compress)    # 下投影:压缩
        self.W_UK = nn.Linear(d_compress, d_model)      # 上投影:恢复 K
        self.W_UV = nn.Linear(d_compress, d_model)      # 上投影:恢复 V

        self.W_o = nn.Linear(d_model, d_model)

    def forward(self, h, kv_cache=None):
        batch, seq_len, _ = h.shape

        # Q:标准投影
        Q = self.W_q(h)

        # MLA:压缩 → 缓存 → 恢复
        c_KV = self.W_DKV(h)             # [batch, seq, d_compress]
        if kv_cache is not None:
            c_KV = torch.cat([kv_cache, c_KV], dim=1)

        K = self.W_UK(c_KV)              # 恢复完整 K
        V = self.W_UV(c_KV)              # 恢复完整 V

        # 多头拆分 + 注意力计算
        Q = Q.view(batch, seq_len, self.n_heads, self.d_k).transpose(1, 2)
        K = K.view(batch, -1, self.n_heads, self.d_k).transpose(1, 2)
        V = V.view(batch, -1, self.n_heads, self.d_k).transpose(1, 2)

        scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5)
        attn = F.softmax(scores, dim=-1)
        out = torch.matmul(attn, V)

        out = out.transpose(1, 2).contiguous().view(batch, seq_len, self.d_model)
        return self.W_o(out), c_KV  # 返回压缩缓存供下次使用
moe.py
class MixtureOfExperts(nn.Module):
    """Mixture of Experts (MoE) — 稀疏专家混合"""

    def __init__(self, d_model=512, n_experts=8, top_k=2, d_ff=1024):
        super().__init__()
        self.n_experts = n_experts
        self.top_k = top_k

        # 路由器:决定激活哪些专家
        self.router = nn.Linear(d_model, n_experts)

        # 专家列表:每个专家是一个简单的 FFN
        self.experts = nn.ModuleList([
            nn.Sequential(
                nn.Linear(d_model, d_ff),
                nn.GELU(),
                nn.Linear(d_ff, d_model),
            )
            for _ in range(n_experts)
        ])

    def forward(self, x):
        batch, seq_len, d = x.shape
        x_flat = x.view(-1, d)           # [batch*seq, d]

        # 路由器计算
        router_logits = self.router(x_flat)  # [batch*seq, n_experts]
        router_weights = F.softmax(router_logits, dim=-1)

        # Top-K 选择
        topk_weights, topk_indices = torch.topk(router_weights, self.top_k, dim=-1)
        topk_weights = topk_weights / topk_weights.sum(dim=-1, keepdim=True)  # 归一化

        # 只计算被选中的专家
        output = torch.zeros_like(x_flat)
        for k in range(self.top_k):
            expert_idx = topk_indices[:, k]    # 第 k 个被选中的专家
            weight = topk_weights[:, k:k+1]  # 对应权重

            for e in range(self.n_experts):
                mask = (expert_idx == e)
                if mask.any():
                    expert_out = self.experts[e](x_flat[mask])
                    output[mask] += weight[mask] * expert_out

        return output.view(batch, seq_len, d)
deepseek_block.py
class DeepSeekBlock(nn.Module):
    """DeepSeek Transformer Block = MLA + MoE"""

    def __init__(self, d_model=512, n_heads=8, d_compress=64,
                 n_experts=8, top_k=2, d_ff=1024):
        super().__init__()
        self.norm1 = nn.RMSNorm(d_model)
        self.attn = MultiHeadLatentAttention(d_model, n_heads, d_compress)
        self.norm2 = nn.RMSNorm(d_model)
        self.moe = MixtureOfExperts(d_model, n_experts, top_k, d_ff)

    def forward(self, x, kv_cache=None):
        # MLA 注意力 + 残差连接
        attn_out, new_cache = self.attn(self.norm1(x), kv_cache)
        x = x + attn_out

        # MoE 前馈 + 残差连接
        x = x + self.moe(self.norm2(x))
        return x, new_cache

# 使用示例
block = DeepSeekBlock(d_model=512, n_heads=8, n_experts=64, top_k=2)
x = torch.randn(2, 128, 512)  # [batch=2, seq=128, d=512]
out, cache = block(x)
print(out.shape)  # torch.Size([2, 128, 512])
7

小测验

🔗 相关章节推荐
  • TransformerMLA 是 Transformer 注意力的优化
  • RNN / MambaMamba 的 SSM 与 RNN 的关系
  • MLPMoE 的专家网络基于 MLP

1. MLA(Multi-head Latent Attention)的主要目的是什么?

2. MoE(Mixture of Experts)中路由器的作用是什么?

3. DeepSeek 相比同规模密集模型(如 LLaMA)的效率优势来自哪里?

Easy Deep Learning · 用 Excel 手推理解每一个公式