LLM学习
提示工程
提示原则
原则一:编写清晰、具体的指令
- 使用分隔符实现不同文本部分的隔断,比如````,”””,< >,
`等,可以防止提示词注入 - 结构化输出,json形式
- 要求模型检查是否满足条件
- 提供少量示例
原则二:给模型时间去思考
- 指定完成任务所需的步骤
- 指导模型在下结论之前找出一个自己的解法
直接给模型输入一份解答,模型可能默认会判断输入是对的,需要让它自己思考一次再对比
提示词设计
语言类prompt检查是否满足四要素:
- 立角色
- 述问题
- 定目标
- 补要求
统一模板:
1 | 你是一名具有【领域】经验的【角色】 |
最重要的是:任务、约束、输出格式
提示词优化方法:
人工修改
自动提示词优化:利用大模型自动生成多个候选提示词,通过验证集评分,选出效果最好的提示词
APE(Automatic Prompt Engineer):Large Language Models Are Human-Level Prompt Engineers
图像类prompt一般不止包含正向提示词,还需要负向提示词来限制画面输出,正向提示词包含:
- 主体
- 细节
- 风格参考
多模态prompt需要明确模态分工,减少不同模态之间的对应关系歧义
- 图片:核心的语义负载,表达“是什么“
- 文字:权重调节器和语义补全器,表达“要什么”
设计原则:明确检索意图、锚定视觉焦点、属性调节校准
上下文学习
- Zero-shot(零样本提示):只告诉模型任务和要求,不给示例
- Few-shot(少样本提示):除任务要求外,再提供少量“输入—输出”示例,让模型模仿其中的规则、标签含义和输出格式
GPT-3 论文系统比较了 zero-shot、one-shot 和 few-shot 三种设置
一般来说先零样本测试,输出不稳定或错误较多时,再添加代表性示例,构成小样本提示,但是不能无限增加示例
Responses API
最新版不再推荐使用 system
1 | developer = 规则和业务逻辑 |
在 Responses API 中有几类数据
1 | developer / user / assistant -> 普通对话消息 Message |
标准流程是:
1 | 用户输入 |
函数定义:放在 tools
1 | tools = [ |
函数调用:放在 response.output,模型返回样式:
1 | { |
模型只是生成了调用请求,并没有执行 Python 函数,程序必须读取 name 和 arguments,然后自己调用对应函数
函数执行结果:放在 function_call_output
1 | { |
完整结构可以理解为:
1 | input_items = [ |
最后,模型才会生成普通的 assistant 消息
1 | { |
定义区分:
- Function Calling 是模型与外部工具交互的接口机制;
- RAG 是利用外部知识增强生成结果的信息处理架构;
- Agent 是由模型负责决策和控制任务执行过程的系统架构;
在 RAG 和 Agent 中都可以用到 Function Calling
区分:
- RAG 的典型流程:用户问题 → 检索文档 → 拼接上下文 → 生成回答,不一定利用Function Calling
- Agent 的典型流程:目标 → 决策 → 调工具 → 观察结果 → 再决策,根据目标和中间结果,动态选择步骤和工具
LLaMa
架构和GPT-3相同,但是用了一些不同的组件
旋转位置编码 RoPE
不是把位置向量加到词向量上,而是根据 token 的位置,对注意力中的 $Q$ 和 $K$ 向量进行旋转
- 用绝对位置控制旋转角度;
- 在注意力分数中自然形成相对位置信息;
相对传统Transformer使用的加法位置编码,RoPE不把位置向量加进 token,利用位置信息旋转 $Q/K$
对于一个二维向量,二维旋转矩阵为:
$$
R(\phi)=\begin{bmatrix}
\cos\phi & -\sin\phi\\
\sin\phi & \cos\phi
\end{bmatrix}
$$
重点,因为二维旋转矩阵是一个正交矩阵,RoPE 改变向量的方向和相位,但不改变其欧氏范数
旋转后的向量为:
$$
R(\phi)x = \begin{bmatrix}
x_1\cos\phi-x_2\sin\phi\\
x_1\sin\phi+x_2\cos\phi
\end{bmatrix}
$$
因为输入向量通常不是二维,而是高维,RoPE 会把向量的维度两两分组,每一对维度都视为一个二维平面,然后分别旋转
对于第 $i$ 对维度:
$$
\begin{bmatrix}x’{2i}\\x’{2i+1}
\end{bmatrix}=\begin{bmatrix}
\cos(m\theta_i) & -\sin(m\theta_i)\\
\sin(m\theta_i) & \cos(m\theta_i)
\end{bmatrix}
\begin{bmatrix}
x_{2i}\\
x_{2i+1}
\end{bmatrix}
$$
不同的二维子空间使用不同的旋转频率:
$$
\theta_i=b^{-\frac{2i}{d_h}}
$$
原始设计取 $b=10000$,不同频率的作用类似于传统正弦位置编码:
- 高频维度变化快,容易区分附近位置
- 低频维度变化慢,可以覆盖较长距离
位置 $m$ 的 query 为 $q_m$,位置 $n$ 的 key 为 $k_n$,经过 RoPE 后
$$
\tilde{q}_m = R_m q_m \qquad \tilde{k}_n = R_n k_n
$$
旋转后的注意力点积为:
$$
\tilde{q}_m^{\top}\tilde{k}n= (R_m q_m)^{\top}(R_n k_n) \rightarrow \tilde{q}m^{\top}\tilde{k}n= q_m^{\top}R_m^{\top}R_n k_n
$$
旋转矩阵满足:
$$
\color{red} R_m^{\top} = R{-m} \qquad R{-m}R_n = R{n-m}
$$
所以点积时可以反映信息
$$
\tilde{q}_m^{\top}\tilde{k}n= q_m^{\top}R{n-m}k_n
$$
Su J, Ahmed M, Lu Y, et al. Roformer: Enhanced transformer with rotary position embedding[J]. Neurocomputing, 2024, 568: 127063.
优点:
- 自然表达相对位置,无需额外维护相对位置 embedding 表
- 不改变向量长度
- 不需要学习位置参数
局限:
- 旋转具有周期性
- 上下文窗口拓展性受限
RMSNorm
$$
\operatorname{RMSNorm}(x)=\gamma\odot\frac{x}{\sqrt{\frac{1}{d}\sum_{i=1}^{d}x_i^2+\epsilon}}
$$
- LayerNorm 同时做“减均值”和“缩放方差”
- RMSNorm 省略减均值,只根据均方根缩放
RMSNorm 可以看作 LayerNorm 的简化版本,且现代LLM基本都采用了RMSNorm
减均值这个操作有三个坏处:
- 打扰注意力机制的结构
- 性能变慢
- 对梯度反而不稳定
分组查询注意力 GQA
标准多头注意力中,输入隐藏状态 $X$ 分别投影成 $Q, K, V$
GQA 不改变注意力本身公式,但Query head 的数量和 Key/Value head 的数量不再必须相同,且通常有 $H_{KV}<H_Q$
多个 Query heads 被分成若干组,每一组共享一个 Key head 和一个 Value head
现在大模型高效注意力大致形成了 GQA、MQA、MLA 以及局部注意力等多条路线
| MHA | MQA | GQA(4Q共享为例) |
|---|---|---|
| Q1 → K1, V1 Q2 → K2, V2 Q3 → K3, V3 Q4 → K4, V4 Q5 → K5, V5 Q6 → K6, V6 Q7 → K7, V7 Q8 → K8, V8 |
Q1 ─┐ Q2 ─┤ Q3 ─┤ Q4 ─┤ Q5 ─┤──→ 唯一的 K1, V1 Q6 ─┤ Q7 ─┤ Q8 ─┘ |
Q1 ─┐ Q2 ─┤ Q3 ─┤──→ K1, V1 Q4 ─┘ Q5 ─┐ Q6 ─┤ Q7 ─┤──→ K2, V2 Q8 ─┘ |
主要目的是为了减少 KV Cache
MQA 的 KV Cache 最小,但共享程度太高,可能限制不同注意力头表达不同类型的信息
GQA 的思想和Resnext很像,常见的就是4Q/8Q共享KV,多个 Query 头可以提出不同的问题,但必须使用同一套索引方式和信息表示
很多情况下:
- 多个 KV 头的投影空间相似;
- 不同头可能关注相近的位置;
- Query 头的差异比 KV 头的差异更重要;
所以共享KV不会带来太大的性能下降,反而因为KV cache的减少能带来更多收益
SwiGLU
传统 Transformer FFN 通常写成:
$$
\operatorname{FFN}(x)=W_2\phi(W_1x+b_1)+b_2
$$
普通 FFN 只有一条升维分支
SwiGLU加入门控,门控 FFN 则使用两条分支
1 | +--> ---------内容分支 ----------+ |
一条分支产生内容,一条分支决定这些内容应该保留、放大还是抑制
标准 GLU 可以写成:
$$
\operatorname{GLU}(x)=(W_v x+b_v)\odot\sigma(W_g x+b_g)
$$
SwiGLU 将门控分支中的激活函数换成 SiLU
$$
\operatorname{SwiGLU}(x)=\operatorname{SiLU}(W_g x)\odot(W_u x)
$$
第 $i$ 个专家可以写成
$$
E(x)=W_{\text{down}}
\left[\operatorname{SiLU}(W_{\text{gate}}x)\odot(W_{\text{up}}x)\right]
$$
混合专家结构 MoE
在LLaMa4中加入
在普通稠密 Transformer 中,每个 Token 都经过同一个 FFN
导致的问题:
- 所有 Token 使用相同参数
- 增大 FFN 参数量时,每个 Token 的计算量也同步增加
- 总参数量与单 Token 计算量紧密绑定
MoE 的目标是增加大量参数容量,但每个 Token 只激活其中一小部分,通过训练一个 Router,让输入选择少量专家
实现总参数量大幅增长,而单样本计算量不会按同样比例增长
1 | 输入 Token x |
一个 Token 会经过:
- 始终启用的 Shared Expert,共享专家;(非必须)
- 经过 Router 选出的 Routed Expert,路由专家;
Router 通常是一个很小的线性层
$$
z=W_r x\qquad W_r\in\mathbb{R}^{E\times d}
$$
${z}$ 中包含每个专家的路由分数,通常经过 Softmax 再选出 Top-k
$$
y(x)=\sum_{i\in \operatorname{TopK}(g(x))}p_i(x)E_i(x)
$$
Top-1 的情况下
| 优点 | 缺点 |
|---|---|
| 计算最少; 通信简单; |
单个专家错误会直接影响输出; 容易出现专家负载不均; 路由选择更离散; |
专家分工是训练中自动形成的,并不一定具有稳定、清晰的人类语义标签
主要训练问题:路由不均衡,热门专家过载,冷门专家得不到充分训练;
部分现代 MoE 不只使用路由专家,还增加一个或多个共享专家,减少路由专家间的知识冗余
1 | +--> Router --> Routed FFN Experts --+ |
$$
y(x)=\sum_{j=1}^{N_s}E^{\mathrm{shared}}j(x)+\sum{i\in\operatorname{TopK}(g(x))}p_i(x)E^{\mathrm{routed}}_i(x)
$$
DeepSeekMoE 将这个设计称为 Shared Expert Isolation,共享专家隔离
分布式训练框架
预训练一个 LLM 所需要的算力资源极其庞大,需要分布式训练
分布式训练的两条基本思路:
- 数据并行:模型复制,数据切分
- 模型并行:数据可以相同或分成 micro-batch,模型本身被切分
数据并行
主要就是Pytorch 的 DDP
每张 GPU:
- 保存一份完整模型;
- 读取不同的数据;
- 独立进行前向和反向传播;
- 通过
AllReduce聚合梯度; - 获得相同梯度后,各自更新模型。
1 | GPU 0: batch 0 → gradient G0 ─┐ |
$$
g=\frac{1}{P}\sum_{p=1}^{P}G_p
$$
前提是:完整模型及其训练状态能放入单卡
模型并行
当数据并行分片仍然不够,或者单个层本身太大,就需要模型并行
张量并行 TP:将一个矩阵运算拆到多张 GPU
比如:
- MLP层:将权重矩阵 W 按列切分为 [W1,W2] ,分别放置于不同GPU
- Attention层:将多头注意力切分到不同设备
流水线并行 PP:按照模型层切分
1 | 输入 |
PP 的主要问题是 pipeline bubble,流水线气泡
1 | 时间 0 1 2 3 4 5 6 7 |
1 | F = 整个 batch 的前向传播 |
大部分时间只有一张 GPU 在工作,设备利用率很低
把一个 batch 拆成多个 micro-batch,使不同流水线阶段同时处理不同 micro-batch
1 | 时间 0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
增加 micro-batch 数量可以摊薄填充和排空气泡,但也会导致激活显存、调度复杂度和优化器更新延迟增加
序列/上下文并行
对于长上下文模型,激活和注意力矩阵可能成为显存瓶颈,因此可以沿序列维度切分
1 | 完整序列: |
但注意力需要 token 之间相互交互,因此会引入 AllGather、AllToAll 或其他通信
AllGather:每张 GPU 把自己的数据分享给所有 GPU,最终所有 GPU 都拥有完整数据AllToAll:每张 GPU 把不同的数据块分别发送给不同 GPU,最终数据在 GPU 之间重新分布
专家并行 EP:
MoE的专家通常不在一张 GPU 上,Token 根据路由结果被发送到对应 GPU
1 | GPU 0: Experts 0, 1 |
token 需要从原 GPU 去往专家 GPU,专家计算完成后又要返回原 GPU,所以 MoE 的 FLOPs 可能较低,但端到端速度不一定按理论比例提升
零冗余优化器(ZeRO)
ZeRO 在数据并行基础上,通过“分片保存模型状态”降低单张 GPU 显存占用
普通 DDP 中,每张 GPU 都保存相同的三类东西
1 | P:模型参数 Parameters |
1 | GPU 0: |
ZeRO 将数据并行中的冗余状态逐步分片,分为三种不同的配置
ZeRO-1
1 | GPU 0:[完整参数] [完整梯度] [O0 O1] |
每张 GPU 只保存四分之一的优化器状态,参数和梯度依然是完整复制的
ZeRO-2
1 | GPU 0:[完整参数] [g0 g1] [O0 O1] |
这里的g已经是平均后的了
使用 ReduceScatter聚合完成后直接拆开
ZeRO-3
把参数也分片,每张 GPU 平时只保存一部分模型状态
1 | GPU 0:[p0 p1] [g0 g1] [O0 O1] |
但是计算前需要 AllGather
大模型微调
- 继续预训练:没有人工标签,仅输入额外文本,模型只是继续进行语言模型训练,适合学习领域术语、特殊语言
- 监督微调 SFT:用已经完成预训练的大语言模型,使用人工整理的“问题-答案”数据继续训练
- 强化学习微调 RL:让模型根据奖励改进,奖励可以来自多种来源,最主要是RLHF(基于人类反馈的强化学习)和PPO(近端策略优化)
SFT数据集
单条数据评估视角:规范性、完整性、及时性、安全性、有效性、唯一性
数据集评估视角:多样性(领域知识)、平衡性(领域数据集数量)、一致性、正交性(领域能力互不影响)、必要性(数据价值)
在数据筛选时需要用到
困惑度:形式上和交叉熵损失一致,困惑度与交叉熵损失是指数关系
$$
P_i(x)=\exp\left(-\frac{1}{T}\sum_{t=1}^{T}\log p(x_t\mid x_{<t})\right)
$$学习百分比:判断一条 SFT 数据的学习难度
$$
\operatorname{LP}(i)=\frac{P_{i-1}-P_i}{P_0-P_n}
$$
分子表示第 $i$ 个epoch内,困惑度的下降,一般选择 $LP(1)$数据难度可转移性:不同规模模型对数据难易程度的相对排序具有较强一致性,所以可以用小模型来筛选,降低成本
RL算法
| 算法 | 中文名 | 是否需要价值模型 | 核心思想 |
|---|---|---|---|
| PPO | 近端策略优化 | 通常需要 | 限制每次策略更新幅度 |
| GRPO | 组相对策略优化 | 不需要 | 同一问题的多个回答组内比较 |
| DAPO | 解耦裁剪与动态采样策略优化 | 不需要 | GRPO + 非对称裁剪 + 动态采样 + 长序列优化 |
PPO常见的目标函数是
$$
L_{\mathrm{PPO}}(\theta)=\mathbb{E}_t\left[\min\left(r_t(\theta)\hat A_t,;\operatorname{clip}\left(r_t(\theta),1-\epsilon,1+\epsilon\right)\hat A_t\right)\right]
$$
Tokenizer
主要有以下几类切分方法
- 词级切分(Word-level)
- 字符级切分(Character-level)
- 子词切分(Subword-level)
- 字节级切分(Byte-level)
目前大多数大语言模型使用的是子词或字节级方法
词级切分
将文本按空格和标点符号分割成单词
- 优点在于简单直接;
- 缺点很明显,无法处理未登录词(不在词表的词)和罕见词,对复合词或缩略词的处理也不够精细;
1 | I love machine learning |
现在的大语言模型很少使用纯词级 Tokenizer
字符级切分
将每个字符作为一个 Token,能非常精细地处理文本,适用于处理拼写错误、未登录词或新词
对于一些特定的应用场景,如生成式任务或需要处理大量未登录词的任务,特别有用
- 优点在于词表会非常小,几乎不会出现未知的token,对拼写错误、罕见单词比较鲁棒;
- 缺点是让 token 序列变得非常长,增加了模型的计算复杂度和训练时间;
1 | hello |
子词切分
子词方法介于词级和字符级之间
- 常见单词保留为完整 Token
- 罕见单词拆成多个有意义或高频的片段
常见的子词分词方法包括 BPE、WordPiece 和 Unigram
BPE
BPE 全称是 Byte Pair Encoding,核心思想是:从字符开始,不断合并语料中出现频率最高的相邻 Token 对
优点在于其简单和高效,能够有效地处理未知词和罕见词,同时保持较低的词典大小
1 | low |
在 GPT-2 上得到应用
WordPiece
与 BPE 很相似,但它选择合并项时,不只是看绝对频率,更关注合并后是否能提高语言模型对语料的解释能力
在简化理解下,可以把候选合并分数写成
$$
\operatorname{score}(a,b)=\frac{\operatorname{freq}(ab)}
{\operatorname{freq}(a)\operatorname{freq}(b)}
$$
1 | playing |
"##ing" 表示这个子词不能单独作为单词开头,而是接在其他 Token 后面
在 BERT 上得到应用
Unigram
Unigram 方法与 BPE 的方向相反
Unigram 是从一个较大的候选子词集合开始,不断删除不重要的子词


