覆盖 LLaMA1 → LLaMA2 → CodeLlama → LLaMA3 → LLaMA4 包含模型结构、训练方式、工程实践、前沿探索
论文:LLaMA: Open and Efficient Foundation Language Models 定位:开源高效基础语言模型,目前主流开源大模型(Dense)基本都是LLaMA架构
相比 GPT,LLaMA1 做出了以下四个核心改动:
传统 Post-LayerNorm 的问题:
输入 → 注意力/FFN → 输出 → 再做归一化
类比:先跑完100米,再量血压——数值因剧烈运动而波动。
LLaMA1 的 Pre-RMSNorm:
输入 → 先做归一化 → 再做注意力/FFN → 输出
类比:先量好血压,再让你跑步——从一开始就稳定。
RMSNorm vs LayerNorm:
| 对比项 | LayerNorm | RMSNorm |
|---|---|---|
| 计算内容 | 均值 + 方差 | 只有方差(RMS) |
| 计算量 | 较大 | 更小 |
| 隐含假设 | 无 | 激活值天然趋近零均值 |
RMSNorm 去掉均值计算的合理性:深度网络中,权重初始化(Xavier/He)+ 对称激活函数,使中间层输出天然围绕 0 波动,减去均值几乎没有额外效果,却多花了计算。
公式:
标准 FFN(ReLU):
输出 = ReLU(xW₁) · W₂
类比:只有开关的水龙头,要么全开,要么关死。
SwiGLU:
输出 = (xW₁ · SiLU(xW_gate)) · W₂
类比:水龙头 + 调节阀,门控值决定每个维度放多少信息通过。
参数量补偿: SwiGLU 多了一个 W_gate 矩阵,LLaMA 通过缩小 FFN 中间维度来补偿:
标准FFN: 2个矩阵,中间维度 4d
SwiGLU FFN:3个矩阵,中间维度 8d/3 ≈ 2.67d
→ 参数量基本持平
绝对位置编码的问题: 给每个位置贴固定标签,训练时见过 1~2K,推理时遇到 2001 就懵了——这个位置标签从未见过。
RoPE 的思路: 不给位置贴标签,而是把位置信息编码进向量的旋转角度里。
位置m的词向量:旋转 m·θ 度
位置n的词向量:旋转 n·θ 度
注意力点积:
Q · K = (旋转了mθ的向量) · (旋转了nθ的向量)
= 只取决于 (m-n)θ ← 相对距离!
多频率设计(类比时钟):
低维度 → 小θ → 转得快 → 像秒针 → 感知近距离细粒度差异
高维度 → 大θ → 转得慢 → 像时针 → 感知远距离粗粒度差异
多个"指针"叠加,才能唯一表示任意位置。若所有维度用同一个θ,旋转到 2π 就归零重置,位置 1 和位置 1+周期 完全一样,模型无法区分。
核心思想:合并高频字符对
第一步:统计相邻字符对出现频率
第二步:合并最高频的字符对为一个新token
第三步:重复,直到词表达到目标大小(LLaMA1是32K)
LLaMA1 的特殊处理:
-
数字强制拆分: 所有数字分解为单独数字 token
普通BPE:"123456" → ["123", "456"] LLaMA1: "123456" → ["1","2","3","4","5","6"]原因:训练时见过 "42",推理时遇到 "43",如果整体处理则不认识;拆开后 "4" 和 "3" 都见过。 代价:模型需要隐式维护进位状态,数学计算更难。
-
UTF-8 字节回退: 对未知字符回退到字节分解
"🔥" → ["<0xF0>","<0x9F>","<0x94>","<0xA5>"]
意义:没有回退机制时,遇到生僻字只能输出
<UNK>,信息丢失;有回退则信息完整保留。
基础的自监督学习模型,没有经过任何形式的特定任务微调。
普通 Adam 的问题: 权重衰减被加入梯度,随后被 Adam 的自适应学习率"稀释",大权重没有被有效惩罚。
AdamW 的修复:解耦权重衰减
# 第一步:正常的Adam梯度更新
weight = weight - lr * adam_update(gradient)
# 第二步:独立的权重衰减(不经过Adam缩放)
weight = weight - lr * λ * weightLLaMA1 具体配置:
optimizer = AdamW(
β1 = 0.9, # 梯度均值历史权重(90%历史+10%当前)
β2 = 0.95, # 梯度方差历史权重(更稳定的步长估计)
weight_decay = 0.1, # 防止权重过大
grad_clip = 1.0 # 梯度裁剪,防止梯度爆炸
)梯度裁剪的必要性:
if gradient.norm() > 1.0:
gradient = gradient / gradient.norm()触发场景:训练数据里出现极端样本(格式异常的代码、生僻字文章),loss 暴增,梯度随之暴增。不裁剪则参数直接飞出合理区间,后续 loss 变成 NaN,训练彻底崩溃。
为什么选余弦而不是线性:
线性衰减:匀速下降,前期下降太快,模型还没探索清楚
余弦衰减:前期缓慢→中期快速→后期缓慢,给模型充分探索空间
完整学习率生命周期:
def get_lr(step, warmup_steps, total_steps, lr_max, lr_min):
# 第一阶段:warmup(线性上升)
if step < warmup_steps:
return lr_max * (step / warmup_steps)
# 第二阶段:余弦衰减
progress = (step - warmup_steps) / (total_steps - warmup_steps)
return lr_min + 0.5 * (lr_max - lr_min) * (1 + cos(π * progress))
# 曲线形状:
# lr ↑ /﹨
# | / ﹨_
# | / ﹨___
# |——warmup——|———余弦衰减———→ stepWarmup 的作用: 训练最开始参数随机初始化,梯度方向不可信,用极小学习率让各层输出分布趋于正常,再逐步放开学习率大步走。
不同模型大小用不同学习率:
7B/13B 模型:lr_max = 3×10⁻⁴
33B/65B 模型:lr_max = 1×10⁻⁴
原因:大模型层数更深,梯度传播时误差累积更多(多层连乘放大误差),且参数耦合更复杂,改动一个引发更长连锁反应,大 lr 容易导致整个网络震荡。
总量 1.4T token,全部公开数据,自监督学习。
| 数据集 | 采样比例 | Epoch | 磁盘大小 |
|---|---|---|---|
| CommonCrawl | 67.0% | 1.10 | 3.3 TB |
| C4 | 15.0% | 1.06 | 783 GB |
| Github | 4.5% | 0.64 | 328 GB |
| Wikipedia | 4.5% | 2.45 | 83 GB |
| Books | 4.5% | 2.23 | 85 GB |
| ArXiv | 2.5% | 1.06 | 92 GB |
| StackExchange | 2.0% | 1.03 | 78 GB |
为什么 CommonCrawl 占 67%(而非更多高质量数据):
- 数据量天花板:Wikipedia 只有 83GB,重复训练会严重过拟合
- 多样性比纯净度更重要:CommonCrawl 覆盖人类语言真实场景,让模型"说人话"
- Epoch 数是真正的质量加权:Wikipedia 训练 2.45 轮,Github 只有 0.64 轮
Github 数据连一轮都没训练完的启示: 代码数据利用率不足,缺乏合成数据构造能力,LLaMA3 通过扩充4倍代码数据+合成代码来解决。
代码数据为何能提升逻辑推理: 代码天然携带严格因果链(if→then)、显式步骤分解(step1→2→3)、零噪声逻辑(对就是对,错就是错),训练后模型学到的是精确推理而非模糊推理。
论文:Llama 2: Open Foundation and Fine-Tuned Chat Models 核心变化:更多训练数据、更长上下文、GQA、完整 RLHF 流程
| 变化点 | LLaMA1 | LLaMA2 |
|---|---|---|
| 上下文长度 | 2K | 4K |
| 大参数模型注意力 | MHA | GQA |
| FFN 矩阵维度 | 标准 | 扩充(增强泛化) |
| 训练数据 | 1.4T | 2T |
KV Cache 的危机:
每生成1个新token → 需要读取所有历史K和V
→ 序列越长,KV Cache占用显存越大
→ 8个头 × 序列长度 × 维度 × 精度 = 显存爆炸
三种注意力机制对比:
MHA:8个Query头 → 对应8个K头、8个V头(最贵)
GQA:8个Query头 → 共享2个K头、2个V头(分组共享)
MQA:8个Query头 → 共享1个K头、1个V头(最省但效果差)
类比:餐厅每个服务员都有完整菜单(MHA)→ 几个服务员共用一份菜单(GQA)。
GQA 的能力损失: 损失了不同头对 K、V 的差异化关注能力。
- 短上下文、语义简单任务:损失可忽略
- 长上下文、需要同时关注多处细节(如100页法律合同):损失明显
KV Cache 显存计算:
显存 = 层数 × 组数 × 序列长度 × 维度 × 精度字节数 × 2(K和V)
LLaMA4 Scout(1000万token):
= 64 × 8 × 10,000,000 × 128 × 2 × 2
= 2,621,440,000,000 字节 ≈ 2.4 TB
需要约30张H100
预训练使用来自公开可用源的 2T 个数据 token,相当于 LLaMA1 的 1.4 倍。
核心理念:"quality is all you need"
- 开始时使用公开数据做 SFT,后来改用自有标注数据
- 不同数据源和标注供应商会显著影响下游微调结果
- 数据检查极其重要
Llama-2-chat 的完整流程:
预训练 → SFT → 奖励模型训练 → RLHF(BoN + PPO)→ Llama-2-chat
核心洞察:一万个样本就够了
原因:
预训练阶段:模型已经"见过"几乎所有人类知识
SFT 的真正作用:不是教知识,而是教"对话格式和态度"
SFT 数据堆太多的危害:
→ 模型被过度约束在标注员水平
→ 标注员能力 < 模型潜力上限
→ 潜力被压制
SFT vs RLHF:
SFT = 模仿人类 → 上限是人类
RLHF = 被人类评价 → 上限可超越人类
数据必须来自自己的模型:
❌ 错误:用 GPT4 的输出
→ 奖励模型只学会判断GPT4风格
→ 遇到LLaMA输出 = 分布外数据 → 打分失效
✅ 正确:用自己模型的输出
→ 奖励模型精准感知LLaMA的好坏边界
采样策略: 对不同大小模型做不同 temperature 的采样
7B 模型:temperature=0.7 → 输出偏保守
13B 模型:temperature=0.9 → 输出适中
34B 模型:temperature=1.1 → 输出偏多样
→ 覆盖不同难度样本,奖励模型见识更广
LLaMA2 奖励模型数据统计:
| 数据源 | 对比数量 |
|---|---|
| Meta (Safety & Helpfulness) | 1,418,091 |
| StackExchange | 1,038,480 |
| Anthropic Helpful | 122,387 |
| OpenAI WebGPT | 13,333 |
Meta 自收集数据最多的原因:不是资源问题,而是分布匹配。OpenAI数据训练的奖励模型只懂GPT风格的好坏,对LLaMA输出是分布外数据。
for prompt in prompts:
outputs = model.generate(prompt, n=K) # 同一prompt生成K个回答
best = reward_model.pick_best(outputs) # 奖励模型评分
sft_data.append(best) # 最优答案存入训练集特点:模型权重没有被直接更新,奖励模型只是"筛选器",无法被针对性作弊。
RLHF 的哲学:宏观层面的梯度下降
| 微观梯度下降 | 宏观 RLHF |
|---|---|
| loss 函数 | 人类偏好标注 |
| 梯度方向 | 奖励模型信号 |
| 学习率 | PPO 更新幅度 |
| 一个 batch | 一轮迭代数据 |
| 时间粒度:毫秒 | 时间粒度:天/周 |
迭代策略:
第1-4轮:BoN(Best of N)
→ 模型还弱,奖励模型也不够准
→ BoN只是筛选器,无法被作弊
→ 让模型和奖励模型共同成熟
第5轮:PPO
→ 模型和奖励模型都已足够稳定
→ 梯度信号可信
→ 做最后一步精准对齐
PPO 不能过早使用的原因:奖励作弊(Reward Hacking)
PPO 优化目标:最大化 reward_model(输出) 的分数
模型发现捷径:输出超长废话、重复讨好性语句
→ 奖励分数高,但人类觉得很烂
RLHF 迭代收益递减的根本瓶颈:人类标注员辨别能力上限
初级阶段:差距明显 → 人人能判断 → 信号强
高级阶段:差距微小 → 需领域专家 → 普通标注员随机选
→ 奖励模型学到噪声而非信号
破局方向:
- AI Feedback(RLAIF):用更强的模型做裁判
- 任务分解:把"哪个更好"拆成更简单的判断
- 宪法AI:给模型一套规则让其自我评判
重要结论: LLaMA2 前四次迭代都是简单的 BoN,只有最后一次才是 PPO,说明 RLHF 的核心是 HF(人类反馈),而不是 RL(强化学习算法)。
论文:Code Llama: Open Foundation Models for Code 定位:基于 LLaMA2 训练的代码领域模型
从头训练:随机初始化 → 从零学习所有知识(20年)
继续预训练:LLaMA2基础 → 在代码数据上继续训练(几个月)
类比:培养从小学代码的程序员 vs 让博士转行学编程
混入自然语言数据的必要性: 防止灾难性遗忘(只喂代码数据会让模型忘记怎么说人话)。
起点:LLaMA2(所有尺寸)
↓ 500B代码数据训练(所有尺寸)
↓
├── 7B/13B → Infilling训练
│
└── 所有尺寸 → 长上下文微调(20B token,4K→100K)
↓
┌──────────────────┐
│ │
CodeLlama-Python CodeLlama-Instruct
(+100B Python数据) (+5B指令微调数据)
为什么只做 Python 专项:
| 语言 | 在AI场景使用频率 | 专项训练价值 |
|---|---|---|
| Python | 极高(训练/推理/数据全流程) | 最大 |
| C++ | 中等(CUDA底层/推理引擎) | 数据量少,有限 |
| Java | 低(与AI场景关系弱) | 接近零 |
专项训练的价值公式:
收益 = 该语言在目标场景的使用频率 × 现有模型在该语言上的能力缺口
为什么需要 Infilling:
普通代码生成(自回归):只能从左到右,给开头续写结尾
真实编程场景:中间空了需要填入 ← 普通模型完全不会 💀
数据构造(SPM 格式):
# 从完整代码中随机选一段进行掩码
original = "完整代码..."
prefix = 掩码前的部分
middle = 被掩码的部分(训练目标)
suffix = 掩码后的部分
# 输入格式:
input = f"<PRE> {prefix} <SUF> {suffix} <MID>" # 模型预测 <MID> 部分为什么用 SPM 而不是 BERT 的 [MASK]:
BERT [MASK]:双向注意力,只能预测单个token,不适合生成整段代码
SPM: 保持自回归生成,把后缀放到前缀前面输入
→ 模型看到完整上下文后,从左到右生成中间缺失部分
→ "把填空题转化成看着答案范围来续写"
随机掩码策略:
def create_infilling_sample(code):
start = random.randint(1, len(lines)-2) # 随机起始行
length = random.randint(1, len(lines)//2) # 随机掩码长度
# 位置和长度完全随机 → 模型必须真正理解代码逻辑为什么只有 7B/13B 做 Infilling:
7B/13B:部署在IDE插件,实时代码补全,Infilling是刚需
34B/65B:用于复杂代码生成和架构设计,不需要实时补全
Infilling 的局限性: 防御性编程(如除零判断)生成不稳定。训练数据从正确代码构造,掩码位置随机,有时防御逻辑在前缀里,模型只需填核心逻辑,倾向于生成最简单的填充。
工业界解法:
- 数据工程:提高含防御代码的掩码样本权重
- 指令微调:显式教会"注意边界情况"
- 后处理:接静态分析工具兜底(最实用)
| 产品 | 特点 | 适用场景 |
|---|---|---|
| CodeLlama(基础版) | 纯代码补全,无对话能力 | 嵌入IDE |
| CodeLlama-Python | Python 专项强化 | 数据科学/AI开发 |
| CodeLlama-Instruct | 自然语言描述需求 | 对话式编程助手 |
长上下文的工程价值(4K→100K): 跨文件理解整个项目结构(main.py + utils.py + models/ + tests/),需要同时看多个文件的完整内容,4K 完全不够。
论文:The Llama 3 Herd of Models 包含 LLaMA3 和 LLaMA3.1 两个系列(差别不大,多语言/长文本/tool use)
| 变化点 | LLaMA2 | LLaMA3 |
|---|---|---|
| Tokenizer | sentencepiece | tiktoken |
| 词表大小 | 32K | 128K |
| 上下文长度 | 4K | 8K(后扩展到128K) |
| GQA | 部分模型 | 全系列 |
| 代码数据 | 少量 | 扩充4倍 |
LLaMA3 主要模型规格:
| 8B | 70B | 405B | |
|---|---|---|---|
| 层数 | 32 | 80 | 126 |
| 模型维度 | 4096 | 8192 | 16384 |
| FFN 维度 | 14336 | 28672 | 53248 |
| 注意力头数 | 32 | 64 | 128 |
| KV 头数 | 8 | 8 | 8 |
| 词表大小 | 128000 | 128000 | 128000 |
| 位置编码 | RoPE (θ=500,000) | RoPE (θ=500,000) | RoPE (θ=500,000) |
词表 32K → 128K 的四重影响:
-
多语言覆盖: 32K 主要覆盖英语,其他语言靠字节回退;128K 可以给中/日/阿拉伯文等分配专属 token
-
序列压缩效应(最重要):
同样文字: 32K词表 → 9个token 128K词表 → 5个token(压缩44%) → 不改变上下文窗口大小 → 但实际能处理的文本量增加80% → 相当于免费获得了更长的上下文 -
推理成本变化:
词表扩大的代价:输出层对128K个词计算softmax(比32K多4倍) 序列压缩的收益:序列变短,省约40%计算 结论:序列压缩收益 > 词表扩大代价 → 整体速度更快 -
代码处理更好: tiktoken 保留完整编程关键词("for"/"in"/"range"各自是独立token),而 sentencepiece 会把代码切成很多碎片
阶段一:初始预训练
├── 数据:15T token 混合数据
├── 上下文:8K
└── 目标:建立扎实的通用能力
阶段二:长上下文预训练
├── 数据:长文本专项数据
├── 上下文:8K → 128K(逐步扩展)
└── 目标:让模型适应超长序列
阶段三:退火(Annealing)
├── 数据:换成高质量精选数据
├── 学习率:从当前值降到接近0
└── 目标:最终精细收敛
为什么要逐步扩展上下文(不能直接从8K跳到128K):
注意力计算量:O(序列长度²)
8K: 8000² = 6400万
128K:128000² = 163亿 (贵2500倍)
直接跳的风险:
RoPE位置编码进入从未训练过的角度区间
→ 注意力分数分布崩塌
→ 模型在8K-128K之间的位置输出乱码
退火的冶金类比:
铸铁急速冷却 → 原子来不及调整 → 结构有内应力 → 刀刃易崩裂
模型直接停训 → 参数停在将就位置 → 没充分利用最后优化空间
退火 = 缓慢降温:
→ 参数有足够时间在局部精细调整
→ 换成高质量数据做最后雕琢(铸剑最后用细磨刀石收尾)
核心发现(OpenAI 2020年):
loss = A / N^α + B / D^β + 不可消除误差
(N=参数量,D=数据量)
→ 参数量/数据量翻倍,loss 下降固定比例
LLaMA3 的工程用法:
第一步:在小模型(1B/7B)上训练,记录不同配置的benchmark性能
第二步:拟合每种能力的扩展曲线
第三步:把 405B 参数代入公式,预测最优数据配比
→ 不需要真的训练405B才知道结果
Scaling Laws 的边界:无法预测涌现能力
涌现的本质是多个子能力同时达标,类似木桶装水:
CoT 推理需要同时具备:
子能力A:理解问题语义 → 80% ✅
子能力B:分解成步骤 → 60% ✅
子能力C:每步推理不出错 → 30% ❌ ← 最短板
子能力D:整合多步结果 → 70% ✅
木桶容量 = 最短板 = 30% → CoT 完全失效
当模型增大到子能力C突破60%:
→ 四个子能力同时达标
→ CoT 突然涌现
→ Scaling Laws 看到的是平均loss线性下降
→ 无法预测这种非线性跳变
四道关卡:
第一关:启发式过滤(快速)
# 长度过滤、特殊字符比例、数字比例、重复行比例
if len(doc) < 100 or len(doc) > 100000: return False
if special_char_ratio > 0.2: return False
if digit_ratio > 0.5: return False
if unique_line_ratio < 0.5: return False第二关:NSFW 过滤 用多维分类模型(色情/暴力/仇恨/违法/自伤)打分,针对不同领域(医学/法律/历史)设置不同阈值。
第三关:语义去重
# 不能用精确匹配,用向量相似度
vectors = [encoder(doc) for doc in documents]
similar_pairs = find_similar_pairs(vectors, threshold=0.85)
# 保留质量更高的那个,而不是随机删一个
# 用 MinHash+LSH 把复杂度从 O(n²) 降到 O(n log n)语义重复数据的三重危害:
- 知识分布失衡,模型对重复内容过度自信
- 死记硬背而非泛化学习,换个问法就不会
- 污染验证集评估(训练/验证集都有相似内容,验证 loss 虚低)
第四关:质量分类器(用上一代 LLaMA 做裁判)
quality_model = load_model("llama2") # 上一代模型判断数据质量飞轮效应: LLaMA2 筛选数据 → 训练更好的 LLaMA3 → LLaMA3 筛选数据 → ...
循环偏见风险与防御:
| 风险 | 表现 | 防御方案 |
|---|---|---|
| 偏见放大 | 每代偏见指数级放大(b → b² → b³) | 多元模型打分(Ensemble) |
| 错误继承 | 上代认为低质量的类别被系统过滤 | 分桶多样性采样(每领域固定配额) |
| 能力天花板 | 无法超越上代模型的判断能力 | 人工抽查返工 + 外部基准锚定 |
最优数据过滤实践:
# 不是固定比例,而是质量加权采样
def quality_score(sample):
return check_logic(sample) + check_consistency(sample) + check_format(sample)
sampler = WeightedSampler(samples, weights=[quality_score(s) for s in samples])
# 分桶保证多样性(每个领域独立筛选,不跨领域竞争)
for domain, docs in domain_buckets.items():
result.extend(sort_by_quality(docs)[:quota[domain]])完整迭代循环:
for round in range(num_rounds):
outputs = current_model.generate(prompts, n=K)
best_outputs = reward_model.select_best(outputs) # 拒绝采样
preference_pairs = construct_pairs(best, worst)
sft_model = finetune(base_model, data=best_outputs) # SFT
dpo_model = dpo_train(sft_model, preference_pairs,
mask_shared_prefix=True, # LLaMA3改进
nll_regularization=0.2) # LLaMA3改进
current_model = average_models([dpo_v1, dpo_v2, dpo_v3]) # 模型平均化问题根源:
chosen: "好的,我来帮你...[答案]...<EOS>"
rejected: "好的,我来帮你...[废话]...<EOS>"
共同前缀:"好的,我来帮你..."
DPO loss 同时要求:
→ 增加 chosen 中这段话的概率
→ 减少 rejected 中这段话的概率
→ 同一段话,又增又减 → 梯度互相打架 💥
两个修复方案:
# 修复1:mask 掉共享前缀的 loss
loss = dpo_loss(chosen, rejected, mask_shared_tokens=True)
# 修复2:加入 NLL 正则项(系数0.2)
# 确保 chosen 的概率不会因对抗训练而整体下滑
total_loss = dpo_loss + 0.2 × nll_loss(chosen)直接平均 vs Task Arithmetic:
# 直接平均(LLaMA3 用的)
averaged_params = mean([model_A, model_B, model_C])
# 适用条件:同基础模型、超参数略有不同、参数空间距离近
# Task Arithmetic(更先进)
task_vector_A = model_A.params - base_model.params # 计算相对变化方向
task_vector_B = model_B.params - base_model.params
merged = base_model.params + mean([task_vector_A, task_vector_B])
# 在"方向空间"合并而非"绝对位置空间"每阶段都平均 vs 只在最后平均:
只在最后:误差层层累积放大(RM误差→SFT误差→DPO误差)
每阶段: 误差在每层被截断(高质量数据→高质量下游)
学术界更先进的合并方法演进:
- 2022:直接平均
- 2023:Task Arithmetic(方向空间合并)
- 2023:TIES-Merging(解决符号冲突,保留多数同意的方向)
- 2024:DARE(随机丢弃噪声分量)
- 2024:SLERP(球面线性插值,保留特征方向)
| 对比项 | PPO | DPO |
|---|---|---|
| 需要模型数量 | 4个(Actor/Critic/Reference/Reward) | 2个 |
| 显存占用 | 单模型 × 4 | 单模型 × 2 |
| 训练稳定性 | 极难调参 | 接近 SFT |
| 工程复杂度 | 极高 | 低 |
官网:https://ai.meta.com/blog/llama-4-multimodal-intelligence/ 核心变化:MoE 架构、多模态、超长上下文
| 模型 | 激活参数 | 总参数 | 专家数 | 上下文长度 | 训练Token |
|---|---|---|---|---|---|
| Llama 4 Scout | 17B | 109B | 16 | 10M | ~40T |
| Llama 4 Maverick | 17B | 400B | 128 | 1M | ~22T |
| Llama 4 Behemoth | 288B | 2T | 16 | - | 仍在训练 |
Scout vs Maverick(推理成本相同,能力不同):
两者激活参数都是 17B → 推理FLOPs相同 → 推理速度/成本完全一致
能力差异:
Scout(16专家):候选池小,遇到罕见问题可能无精准专家
Maverick(128专家):候选池大,自发形成更细粒度专业分工
数论专家 / 复分析专家 / 代码专家 / 多语言专家...
MoE 的核心价值:把"存储成本"和"推理成本"解耦
继承 LLaMA2 的特性:
- 前置 RMSNorm
- SwiGLU 激活函数
- GQA(分组查询注意力)
新增特性:
- QK Norm(Attention 内部 L2 归一化)
- iRoPE(交替 RoPE 编码)
- MoE 架构(多个 SwiGLU 专家)
为什么 MoE 特别需要 QK Norm:
MoE 训练不稳定性:
不同专家更新频率不同(热门专家频繁,冷门专家稀少)
→ Q/K 向量的数值范围差异巨大
→ [email protected] 出现极大值(如2000)和极小值(如0.03)
→ softmax 退化成"只看一个位置"
→ 多头注意力能力丧失 💀
L2 Norm 的修复:
class Llama4TextL2Norm(torch.nn.Module):
def _norm(self, x):
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
# 归一化后:Q和K向量模长均为1,注意力分数范围[-1,1](余弦相似度)公式:$y = \frac{x}{\sqrt{\frac{1}{d}\sum_{i=1}^{d}x_i^2 + \epsilon}}$
Scout 用 QK Norm,Maverick 不用的原因:
Scout(16专家):专家粒度粗,Q/K分布不均匀,需要归一化兜底
Maverick(128专家):专家高度专精,输入分布天然均匀,无需归一化
且保留向量模长信息(模长大=该token很确定要找什么)
→ 额外的隐性表达维度
普通 RoPE 的超长上下文危机:
训练:最长 256K token
推理:遇到 1000万 token
→ 旋转角度进入从未见过的区域
→ 注意力分数分布崩塌,输出乱码 💀
iRoPE 的解法:
# 每4层才用一次RoPE
self.use_rope = int((layer_idx + 1) % 4 != 0)
# 不用RoPE的层:用推理时温度缩放代替
if not self.use_rope:
attn_scales = torch.log(
torch.floor((cache_position.float() + 1.0) / self.floor_scale) + 1.0
) * self.attn_scale + 1.0
query_states = query_states * attn_scales无 RoPE 层靠什么感知顺序:
第1层 [有RoPE] → hidden_state 携带位置信息
第2层 [无RoPE] → 继承上层 hidden_state(位置"惯性")
第3层 [无RoPE] → 继续继承,做语义加工
第4层 [有RoPE] → 重新注入/校准位置信息
类比:导航不需要每秒重新定位,定期校准一次,中间靠惯性推算。
iRoPE 的额外好处:
频繁 RoPE 的问题:超长序列时旋转角度累积过大,高频维度转几百圈,数值精度损失
iRoPE:只有1/4的层做RoPE → 角度累积压力分散 → 长上下文稳定性大幅提升
每个 Expert 仍是 SwiGLU 结构(与 LLaMA3 一致):
class Llama4TextMLP(nn.Module):
def forward(self, x):
down_proj = self.activation_fn(self.gate_proj(x)) * self.up_proj(x)
return self.down_proj(down_proj)Shared Expert 设计:
普通MoE:所有expert都参与竞争路由
LLaMA4: 1个Shared Expert永远激活(处理通用知识)
+ Router选出的Expert(处理专业知识)
Expert Collapse(专家坍塌)问题与防御:
问题:训练初期某几个Expert碰巧被多选
→ 这几个Expert更新更多,变得更强
→ Router更倾向选它们
→ 其他Expert永远学不到东西 → 退化成2-3个Expert在工作
防御方案一:辅助负载均衡 Loss
balance_loss = ((expert_usage - 1/num_experts)²).sum()
total_loss = main_loss + 0.01 * balance_loss
防御方案二:Bias 动态调控(DeepSeek-V3 方案,更优雅)
if expert_i 使用率过高: expert_bias[i] -= δ
if expert_i 使用率过低: expert_bias[i] += δ
# 完全解耦,不干扰主loss的梯度方向
三模块标准架构:
Vision Encoder(MetaCLIP)
↓
Projector(单层MLP)← 实现简单但有效,本质是LLaVA架构
↓
LLM(LLaMA4文本模型)
class Llama4ForConditionalGeneration(Llama4PreTrainedModel):
def __init__(self, config):
self.vision_model = Llama4VisionModel(config.vision_config) # MetaCLIP
self.multi_modal_projector = Llama4MultiModalProjector(config) # 单层MLP
self.language_model = Llama4ForCausalLM(config.text_config) # LLaMA4文本模型
class Llama4MultiModalProjector(nn.Module):
def __init__(self, config):
self.linear_1 = nn.Linear(vision_output_dim, text_hidden_size, bias=False)MetaCLIP vs CLIP: 没有模型架构调整,只改进训练数据的质量和分布来提升性能。
为什么需要低精度:
Behemoth(2T参数)用FP32:
参数:8TB + 梯度:8TB + 优化器状态:16TB = 32TB
需要约400张H100 → 工程上不可行
浮点精度对比:
| 精度 | 位数 | 数值范围 | 显存占用 |
|---|---|---|---|
| FP32 | 32 | ±3.4×10³⁸ | 基准×1 |
| BF16 | 16 | ±3.4×10³⁸ | 基准×0.5 |
| FP8(E4M3) | 8 | ±448 | 基准×0.25 |
| FP8(E5M2) | 8 | ±57344 | 基准×0.25 |
FP8 使用策略:
✅ 可以用FP8:矩阵乘法输入输出、前向激活值、大部分梯度
❌ 必须高精度:优化器状态(Adam动量)、权重更新、Loss计算
核心挑战:动态缩放(防止数值溢出)
# 问题:FP8最大值只有448,某层激活值可能出现500 → 溢出 → NaN
# 解决:每个张量有自己的缩放因子
scale = MAX_FP8_VALUE / tensor.abs().max()
fp8_tensor = convert_to_fp8(tensor * scale)
# 使用时还原
original = fp8_tensor / scale分布式训练中的梯度聚合问题:
# 问题:不同GPU的梯度在不同scale下压缩,直接相加就像人民币和美元直接加
# 解决:先还原,再聚合
def aggregate_gradients(local_grad, local_scale):
true_grad = local_grad * local_scale # 还原到真实值(BF16)
aggregated = all_reduce(true_grad) # 跨GPU聚合
new_scale = compute_scale(aggregated) # 重新压缩FP8
return aggregated / new_scale, new_scaleFP8 的实际收益(相比BF16):
- 显存节省:约60%
- 算力提升:H100的FP8算力是BF16的2倍
- 实际加速:约1.5-1.8倍
传统超参数迁移的问题:
7B模型最优lr:3×10⁻⁴
直接用到70B → 可能完全不对
原因:参数量增大,梯度统计特性变化
MetaP 的思路:找规律而非找最优值
发现:lr ∝ 1/sqrt(参数量)
7B: lr = C/sqrt(7B) = 3×10⁻⁴ → 求出C
70B: lr = C/sqrt(70B) = 1×10⁻⁴ (自动推导)
405B:lr = C/sqrt(405B) = ? (公式算出)
→ 不需要在大模型上重新调参
MoE 特有超参数不能从 Dense 迁移:
Dense 假设:所有参数均匀更新(均匀梯度流)
MoE 实际: 热门Expert频繁更新,冷门Expert稀疏更新
→ 违反了Dense的均匀假设
→ Dense规律完全失效
MoE 必须重新建立的超参数规律:
├── top-k:激活专家数(k=1和k=2是质变,不是量变)
├── 负载均衡Loss系数(太大破坏专家分工,太小Expert Collapse)
├── Shared Expert的独立学习率(更新频率远高于Routed Expert)
└── 路由温度
总体流程:
轻量SFT → 在线RL → 轻量DPO
SFT 阶段:
用 Llama 模型筛选训练数据
→ 去掉50%被标记为"简单"的问题
→ 只保留较难的问题做 SFT
在线 RL 阶段:
精心选择 medium-to-hard 难度的问题
持续在线RL策略:
→ 交替进行模型训练和提示词筛选
→ 动态保留 medium-to-hard 难度的提示词
DPO 阶段: 针对模型响应质量相关的 corner case 进行轻量 DPO。
预训练模型对比:
| 任务 | LLaMA 3.1 70B | LLaMA 3.1 405B | LLaMA 4 Scout | LLaMA 4 Maverick |
|---|---|---|---|---|
| MMLU | 79.3 | 85.2 | 79.6 | 85.5 |
| MMLU-Pro | 53.8 | 61.6 | 58.2 | 62.9 |
| MATH | 41.6 | 53.5 | 50.3 | 61.2 |
| MBPP | 66.4 | 74.4 | 67.8 | 77.6 |
| ChartQA | - | - | 83.4 | 85.3 |
| DocVQA | - | - | 89.4 | 91.6 |
指令微调模型对比:
| 任务 | LLaMA 3.3 70B | LLaMA 3.1 405B | LLaMA 4 Scout | LLaMA 4 Maverick |
|---|---|---|---|---|
| MMMU | - | - | 69.4 | 73.4 |
| GPQA Diamond | 50.5 | 49.0 | 57.2 | 69.8 |
| MMLU Pro | 68.9 | 73.4 | 74.3 | 80.5 |
| LiveCodeBench | 33.3 | 27.7 | 32.8 | 43.4 |
| MGSM | 91.1 | 91.6 | 90.6 | 92.3 |
| 特性 | LLaMA1 | LLaMA2 | LLaMA3 | LLaMA4 |
|---|---|---|---|---|
| 归一化 | Pre-RMSNorm | Pre-RMSNorm | Pre-RMSNorm | Pre-RMSNorm + QK Norm |
| 激活函数 | SwiGLU | SwiGLU | SwiGLU | SwiGLU (MoE) |
| 位置编码 | RoPE | RoPE | RoPE(θ=500K) | iRoPE |
| 注意力 | MHA | GQA(大模型) | GQA(全系列) | GQA + QK Norm |
| FFN | Dense | Dense | Dense | MoE |
| 上下文 | 2K | 4K | 8K→128K | 1M/10M |
| Tokenizer | sentencepiece(32K) | sentencepiece(32K) | tiktoken(128K) | tiktoken(128K) |
| 训练数据 | 1.4T | 2T | 15T | 22T~40T |
| 多模态 | ❌ | ❌ | ❌ | ✅ |
稳定性:
Pre-RMSNorm(LLaMA1)→ QK Norm(LLaMA4)→ FP8动态缩放(LLaMA4)
效率:
MHA → GQA(LLaMA2)→ MoE(LLaMA4)
[显存:头数×序列长度] → [组数×序列长度] → [激活参数/总参数解耦]
长上下文:
2K → 4K → 8K/128K → 1M/10M
RoPE → iRoPE(每4层用一次)
对齐:
无(LLaMA1)→ BoN+PPO(LLaMA2)→ DPO改进(LLaMA3)→ 轻量SFT+在线RL+轻量DPO(LLaMA4)
数据工程:
公开数据(LLaMA1)→ 质量优先(LLaMA2)→ 四道过滤Pipeline(LLaMA3)→ AI辅助筛选50%(LLaMA4)
| 版本 | 最重要创新 | 理由 |
|---|---|---|
| LLaMA1 | RoPE | 奠定了后续所有版本长上下文的数学基础 |
| LLaMA2 | 迭代RLHF | 证明了"宏观梯度下降"的对齐哲学 |
| CodeLlama | SPM Infilling | 把代码填空变成自回归生成,工程上极实用 |
| LLaMA3 | 数据过滤Pipeline | 15T token质量保证,数据工程决定模型上限 |
| LLaMA4 | iRoPE + MoE | 实现存储/推理解耦,撑起1000万token上下文 |
笔记整理完成。覆盖文档全部内容,并补充了课程中深度讨论的工程原理、数学推导和工业界实践。