DeepseekV3论文阅读笔记
1. kv cache 内存换速度
- 只用于推理阶段
- 会有一定的内存占用
- 只存在解码器
- 目的是为了加速注意力的计算
- 一般使用中会限制kv cache的大小,避免超出电脑内存,导致模型崩溃,当序列增长时,达到内存上限,会有一定的遗忘
- 计算注意力时,先算第1个词的注意力,再算第1,2个词的注意力,依次类推算整个句子的注意力,后面的计算与前面的计算有重复的,
可以把前一次的计算结果保存下来,再计算后面的,提升推理速度,也就是用内存换速度
2. 注意力机制的改进: MLA(Multi-Head Latent Attention)
- 低秩投影
- MLA明明计算更复杂了,为什么mla更高效了?
- 推理更高效
- RoPE是为了添加位置编码,把输入映射到一个低秩的矩阵,减少kv cache缓存的内存
3. DeepseekMOE
- 针对ffn层的修改,实际训练中大部分神经元都是无用的
- 为了防止专家负载不均衡(部分专家被过分使用),使用了一个偏置项, 这个偏置项不参与梯度更新,只参与选择路由数量
- 为什么没有使用辅助损失函数?避免对模型训练造成干扰
- 减少通信压力,每个token最多被发送到m个节点,m一般取2或者3,每个节点上一般4个专家
- 偏置是不是梯度更新的,人为指定的训练规则
4. MTP
- 一次性生成多个token
- t_n时刻,训练时,不仅利用h_n预测t_n+1,还利用h_n和t_n+1的embedding预测t_n+2, 训练时t_n+1是真实值,预测时t_n+1是预测值
5. tokenizer 分词
- Byte-level BPE
- 词表12.8w tokens
- 改动点:针对不同语言调整分割规则 标点和分割符合并处理,减少序列长度,即token数量
- 可能会出现Token boundary bias(训练/推理分布不一致),解决方案:随机拆分标点和换行符
6. 工程优化: 流水线并行 专家并行 数据并行
7. 超参数
- 61层transformer 隐藏层维度7168
- MLA层
- 注意力头数128 每个头的维度128
- kv压缩维度512
- Query压缩维度 1536 (压缩后的query)
- 解耦 Q/K 维度 64维 (额外的RoPE维度)
- MOE参数 前3层用普通FFN 第4~61层开始用moe 1个共享专家,256个路由专家,每个专家隐藏层维度2048,每个token最多分布在4个节点
- MTP 每个时刻同时预测下一个token
- 共6710亿参数(训练) 每个token约370亿参数(推理)
- 训练过程多阶段精细调整,核心思想是前期快速调整,后期精细调优
- 训练时最多用了4000token, 但是实际预测可以到12.8w token
8. 训练流程
阶段 0: 预训练 (14.8T tokens, 序列长度 4K)
↓ 学习率衰减到 7.3e-6
↓
阶段 1: 长文本扩展 - 第一部分
- 序列长度: 32K
- 步数: 1000
- 批次: 1920 (每步 61.44M tokens)
- 学习率: 7.3e-6
↓
阶段 2: 长文本扩展 - 第二部分
- 序列长度: 128K
- 步数: 1000
- 批次: 480 (每步 61.44M tokens)
- 学习率: 7.3e-6
↓
阶段 3: 监督微调 (SFT)
↓
最终模型: 支持 128K 上下文,NIAH 测试表现优异
9. 微调
原始响应带有思考过程,教会模型思考;精炼响应,教模型学会输出
┌─────────────────────────────────────────────────────────────┐
│ 1. 初始数据 │
│ R1 生成原始推理数据(高准确性,但冗长、格式差) │
├─────────────────────────────────────────────────────────────┤
│ 2. SFT 训练专家模型 │
│ 格式 A: <problem, original_response> │
│ 格式 B: <system_prompt, problem, R1_response> │
├─────────────────────────────────────────────────────────────┤
│ 3. RL 训练专家模型 │
│ 高温采样 → 探索多样性 → 整合 R1 和原始模式 │
├─────────────────────────────────────────────────────────────┤
│ 4. 拒绝采样 │
│ 生成多个候选 → 评估 → 只保留最高质量样本 │
├─────────────────────────────────────────────────────────────┤
│ 5. 最终 SFT 数据 │
│ 高准确性 + 简洁有效 + 格式规范 │
└─────────────────────────────────────────────────────────────┘
10. 强化学习
- 基于规则的评估
- 基于模型的评估
11. 模型结构及参数量(共6710亿参数)
输入层
- token变为embedding 词表大小128k 隐藏层维度7168 参数约9亿
- 输入是样本序列: [batch_size, seq_len] 中间层[batch_size, seq_len, vocab_size] 输出是 [batch_size, seq_len, 7168]
61层transformer层:输入输出维度[batch_size, seq_len, 7168]
RMSNorm
- 输入输出维度[batch_size, seq_len, 7168]
- 只有7168个参数,最后一维进行归一化(每个token独立归一化)
- 输出保持形状不变,归一化不减去均值,没有平移项,计算比较简单
- 具体公式:
- step1 mean_sq = (1/7168) × Σ(x_i²) # 对最后一维计算
- step2 RMS(x) = √(mean_sq + ε)
- y = (x / RMS(x)) * γ γ 是要训练的参数
MLA 多头潜在注意力层
- 输入输出维度[batch_size, seq_len, 7168]
- 维度变化: 7168 —— 1536(压缩) —— 16384(多头128 * 128)—— 投影(7168)共92亿参数
- Query分为两部分
- 一部分是内容query,维度由7168到1536再到128*128;
- 另一部分是位置query,维度由7168到1536再到64,最后拼接成维度[128, 128+64]
- query压缩维度是1536比512大,是因为query只针对当前token生效,不需要缓存,可以大一点
- Key分为两部分
- 一部分是内容key 7168到512,512再到128*128;
- 另一部分是位置key 7168到512,512再到64,然后拼接起来[128, 128+64]
- value只考虑内容部分 ,7168到512,512再到128128,Query和key维度一致,切分为128个头,每个头128+64维,得到一个128128的注意力矩阵,乘以value得到最终结果
- 为什么value不需要位置编码?
- 注意力权重关注位置信息,但是value只关注具体的内容是什么,也就是只要语义就行
- 为什么value不需要位置编码?
- 推理时参数矩阵全部保存,只需要缓存512维的压缩kv和64维的位置信息就可以计算,query是当前token,用完就丢弃了,不会被重复使用,下一轮预测时query是一个全新的向量
RMSNorm 同上
FFN
- 前3层是FFN,与transformer的ffn不同,采用的是SwiGLU结构
- 输入维度: [batch_size, seq_len, 7168]
- 隐藏层: 维度[batch_size, seq_len, 18432]
- 公式: SwiGLU(x) = Swish(gate_proj(x)) ⊙ up_proj(x)
- gate_proj(x):第一分支的输出(门控信号)线性变换
- up_proj(x):第二分支的输出(实际内容)线性变换
- Swish 激活函数:Swish(x) = x ⊙ σ(x)(σ 是 Sigmoid 函数)
- ⊙:逐元素乘法
- 公式: SwiGLU(x) = Swish(gate_proj(x)) ⊙ up_proj(x)
- 线性变化层 [batch_size, seq_len, 7168]
- 残差连接 [batch_size, seq_len, 7168]
- 输出维度 [batch_size, seq_len, 7168]
- 为什么要用SwiGLU替代原来的relu激活函数?
- 原来的relu函数与输入无关,属于一刀切,SwiGLU的两个分支,可以动态的根据输入判断每个元素的重要性,表征能力更强
- 前3层是FFN,与transformer的ffn不同,采用的是SwiGLU结构
DeepMOE层(后58层)
- 输入输出维度[batch_size, seq_len, 7168]
- 简单总结: 7168 —— 1个共享专家 256个路由专家权重 —— 每个token选择8个专家权重(各7168维)—— 加权聚合 共6530亿参数
- 动态路由,计算亲和度,选择topk个路由专家
- 门控网络 维度为[256, 7168] 256是路由专家数量,7168是输入的向量维度
- 门控网络 * 输入向量是每个token与每个专家的亲和度 维度为[batch_size, sql_len, 256]
- 每个专家有一个偏置项,根据人为指定的规则更新:初始值为0,根据每个专家处理的token数和理想处理的token数之间的大小,来动态更新具体的值
- 理想处理token数是每个共享专家处理的token数量一样,公式为:每个专家理想处理的token数 = 总token数 * 每个token激活的共享专家数 / 总的专家数
- 对加了偏置项的亲和度得分进行sigmoid归一化,得到每个专家的权重(偏置项不参与权重的计算,只是用来选择路由专家)
- 选择topk专家,每个专家内部也是3层的FFN结构,区别就是中间维度是2048(同上)
- 加权融合共享专家和路由专家
- 残差连接,输入和输出相加
RMSNorm 同上 归一化
输出层: 预测下一个token出现的概率 输出维度7168, 词表大小128k, 参数约9亿 [batch_size, sql_len, 7168] 到 [batch_size, seq_len, 词表大小]
- 输出层的参数矩阵通常会和输入层的共享[128k, 7168]
12. 损失函数
- 总损失函数 = 主损失函数 + 权重 * MTP损失函数 + 序列内负载均衡损失函数
- 主损失函数是预测下一个词出现的概率,交叉熵损失函数 t时刻的token,预测t+1的token
- MTP损失函数是预测未来未知的损失,可以有多个,比如t+2,t+3…,论文中应该只有t+2,损失函数也是交叉熵损失函数
- 序列内的负载均衡损失函数
- 没有用全局的辅助损失函数控制负载均衡,但是用了很小的一个辅助损失(主要是为了解决局部(序列内部)的分配不均)
- 公式:L_Bal = α * Σ_{i=1}^{N_r} (f_i * P_i) N_r是路由专家的数量 f_i表示专家i在当前序列的实际负载,p_i表示专家i在当前序列被选中的平均概率
- p_i越大说明专家i越重要,专家i实际负载越高,同时重要性也越高,就表明分配的越不均衡,需要进行调整
- 没有用全局的辅助损失函数控制负载均衡,但是用了很小的一个辅助损失(主要是为了解决局部(序列内部)的分配不均)
13. 论文总结
┌─────────────────────────────────────────────────────────────┐
│ DeepSeek-V3 论文总结 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【成就】 │
│ ├── 671B 参数,37B 激活,最强开源模型 │
│ ├── 与 GPT-4o、Claude-3.5-Sonnet 相当 │
│ └── 训练成本仅 2.788M H800 GPU 小时 │
│ │
│ 【创新】 │
│ ├── MLA (高效注意力) │
│ ├── 无辅助损失负载均衡 │
│ ├── MTP (多 token 预测) │
│ └── FP8 训练 + 工程优化 │
│ │
│ 【局限】 │
│ ├── 部署单元大,小团队负担重 │
│ └── 推理速度仍有提升空间 │
│ │
│ 【未来】 │
│ ├── 坚持开源、长期主义路线 │
│ ├── 持续投资架构、效率、能力研究 │
│ └── 最终目标:AGI │
│ │
└─────────────────────────────────────────────────────────────┘
参考链接:https://www.bilibili.com/video/BV18iNBehEwq/?spm_id_from=333.337.search-card.all.click&vd_source=288339af6f2a8be7370585518f33382e
