对应 Maps to:Stanford CS329Z Engineering AI Agents(Fall 2026),Lecture 9 · Wed Oct 21 · Optimization。阅读材料:Snell et al., Scaling LLM Test-Time Compute Optimally can be More Effective than Scaling Model Parameters(ICLR 2025);Agrawal et al., GEPA: Reflective Prompt Evolution Can Outperform Reinforcement Learning(ICLR 2026)/ Soylu et al., Fine-Tuning and Prompt Optimization: Two Great Steps that Work Better Together(EMNLP 2024);Opsahl-Ong et al., Optimizing Instructions and Demonstrations for Multi-Stage Language Model Programs(MIPROv2,2024)。 依据说明:写作时(2026-10-16)这一讲的 slides 还没有公布。本讲依据课表上的主题描述(从提示词到微调的全景;提示词优化:GEPA、MIPROv2、OPRO、TextGrad;测试时算力扩展;LoRA / QLoRA;蒸馏;RLHF / DPO 概览;什么时候优化提示词、什么时候优化权重、什么时候加推理算力)和四篇阅读材料来写;引用的论文数字都核对过原文摘要。等 slides 公布后,侧重点可能和这里不同。全部实验是本讲义自己设计的,不代表课堂上讲了什么。 配套代码 Code:
code/lecture09/(只用 Python 标准库;6 个脚本 + 5 个模块 + 1 个可选脚本;不需要 API key;最慢的脚本约 50 秒) 预计阅读时间:3–4 小时(含运行代码)
0. 本讲导航 · How to read this lecture
0.1 学习目标 Learning goals
学完这一讲你应该能够:
- 说出一个 LM 程序里有哪几类”可以调的参数”(指令、示范、权重、推理算力),以及它们各自的一次性成本、每次调用的成本和需要的数据。
- 解释三种提示词优化器的思路和差别:OPRO(LLM 看着”提示词—分数”写新提示词)、MIPROv2(自举示范 + 有根据的提议 + 贝叶斯优化)、GEPA(读轨迹和文字反馈、反思出修改、Pareto 前沿选候选);知道为什么反馈函数比优化器的选择更重要。
- 复述 Snell et al. 的核心结论:测试时算力怎样花最好取决于题目难度;按难度分配(计算最优)比 best-of-N 省得多;推理算力什么时候能替代更大的模型、什么时候不能。
- 算出微调一个 8B / 70B 模型要多少显存,解释 LoRA / QLoRA 省在哪里;说清蒸馏、RLHF / DPO 各自用在什么场合。
- 面对一个具体系统,判断该改提示词、改权重还是加推理算力——取决于你缺的是知识还是可靠性、错误有多贵、调用量多大、任务多久变一次。
0.2 先修与本讲范围 Prerequisites & scope
| 默认你已经会 Assumed | 本讲只给直觉,后面再展开 Previewed only |
|---|---|
| 第 5 讲 §2:DSPy 的签名、模块、BootstrapFewShot、赢家诅咒 | 训练数据从哪来、怎样整理(第 10–11 讲) |
| 第 2 讲:解码与采样、pass@k、训练流程概览 | 评估的统计(置信区间、配对比较,第 12 讲) |
| 第 6 讲 §2.3、§2.5:投票、evaluator–optimizer;第 8 讲 §5:辩论 vs 投票 | LLM 评审作为指标(第 13 讲) |
0.3 本讲地图 Map
§0.6 开场:提示词也是参数
§1 优化什么:指令、示范、权重、推理算力;一段提示词值多少 ← 代码 01
§2 提示词优化:问题的形式;OPRO、TextGrad、MIPROv2、GEPA;同样预算下比一比 ← 代码 02
§3 GEPA 的内部:一次迭代、反馈的质量、模块之间的归因、Pareto 还是贪心、预算 ← 代码 03
§4 测试时算力:Snell et al.——两类机制、难度、计算最优、和更大的模型比 ← 代码 04
§5 改权重:全参数 / LoRA / QLoRA、蒸馏、RLHF 与 DPO、提示词 + 权重 ← 代码 05
§6 什么时候改什么:知识还是可靠性、错误的代价、调用量 ← 代码 06
§7 用真实模型试一试 ← 代码 07(可选)
§8 小结 §9 常见误区 §10 练习 §11 延伸阅读 §12 下一讲预告
0.4 术语 Terms
| 术语 | 本讲里的意思 |
|---|---|
| rollout | 程序在一个例子上完整跑一遍(这里一次 rollout = 两次 LLM 调用);提示词优化的预算单位 |
| 指令 / 示范(instruction / demonstration) | 提示词里的两种”参数”:任务说明和规则 / 少样本例子 |
| 反馈函数(feedback function) | 除了打分,还用文字说明”为什么错”的指标(GEPA 的 μ_f) |
| Pareto 前沿(Pareto frontier) | 在至少一个例子上”做得最好”、而且没有被别的候选全面压过的候选集合 |
| TPE | 树结构 Parzen 估计,一种贝叶斯优化方法;MIPROv2 用它在”指令 × 示范”的组合里搜 |
| PRM / ORM | 过程奖励模型(给每一步打分)/ 结果奖励模型(只给最终答案打分) |
| best-of-N / 束搜索 / 修改(revision) | 采样 N 个挑最好的 / 按步展开、按 PRM 剪枝 / 看着上一个答案改 |
| 计算最优(compute-optimal) | 按题目难度为每道题挑最合适的测试时策略和超参数 |
| LoRA / QLoRA | 冻住原权重、只训练低秩增量 / 再把冻住的权重量化成 4 bit |
| 蒸馏(distillation) | 用强模型(老师)的输出训练弱模型(学生) |
| DPO | 直接偏好优化:用”A 比 B 好”的偏好对直接调策略,β 控制离参考模型多远 |
0.5 本讲的代码
主要的实验在一个报销审核任务上(expense_world.py):每张报销单写着类别、商家、日期、金额(5 种货币,单子上写着汇率)、人数、有没有发票、备注;程序要回答 APPROVE / REJECT / ESCALATE。标准答案按一份模型看不到的公司政策算出来:8 条规则(餐费每人 75 美元以上升级审批、25 美元以上没有发票驳回、酒水不报、软件必须走 IT、周末的非差旅支出升级审批……),外加”外币要先换算""餐费要按人头平摊”两条换算规则。程序分两步(policy_lm.ExpenseAudit,用第 5 讲的 mini_dspy 写成):normalize 写出美元金额和每人金额,decide 下结论。
模拟的任务模型(PolicyLM)会真的读指令:用正则从指令里读出写了哪些规则、阈值是多少,然后照着办——写错的阈值会被如实执行,写了不是政策的规则也会被执行。没写的规则只能靠”常识”(凭常识驳回带酒水的单子,概率 0.25)。
提醒:遵守一条规则的概率、凭常识做对的概率、反思模型读懂反馈的概率、只有分数时猜中原因的概率都是旋钮(
policy_lm.KNOBS);测试时算力部分的 PRM 噪声、修改的效果也是旋钮(ttc_lab.KNOBS)。机制是真的:规则的解析和执行、优化器的搜索与记账、Pareto 前沿、TPE、束搜索的剪枝、投票;学生模型和 DPO 是真的在训练。
0.6 开场:提示词也是参数
第 5 讲说 DSPy 把”写提示词”变成”写程序”:签名说要什么,模块说怎样做,优化器负责把程序里的提示词调好。那一讲的优化器只挑示范;这一讲的优化器还会改写指令。
为什么值得这样做?想一想报销审核:任何一个聪明的模型都知道”私人消费不报”,但没有模型知道”这家公司的餐费每人 75 美元以上要升级审批”。这条知识要么写进提示词,要么训练进权重,要么——如果它根本不在任何地方——模型只能猜。所谓”优化”,就是用数据和指标把这些知识找出来、放进系统里。三类杠杆各有各的价钱:
- 提示词:改一句话就生效,但每次调用都要为更长的提示词付钱;
- 权重:一次性训练,之后可以换成更小、更便宜的模型,但要数据、要 GPU、改起来慢;
- 推理算力:不用训练,每次调用多花 N 倍——它修得好随机的错误,修不好”不知道”。
1. 优化什么 · What can be optimized
1.1 一个 LM 程序里的参数
把一个 LM 程序写成 Φ(第 5 讲的模块组合)。在模型固定时,能调的东西有:
| 参数 | 例子 | 怎样调 |
|---|---|---|
| 指令 | “Escalate meals above 75 USD per person.” | 人写;OPRO、MIPROv2、GEPA、TextGrad 自动改写 |
| 示范 | 几个”输入 → 输出”的例子 | LabeledFewShot、BootstrapFewShot(第 5 讲)、MIPROv2 |
| 权重 | 模型参数(全部或 LoRA 增量) | SFT、蒸馏、RLHF / DPO、BetterTogether |
| 推理算力 | 采样几次、怎样挑、要不要搜索 / 修改 | best-of-N、束搜索、修改链、按难度分配 |
还有一类是结构(几个模块、怎样连接),第 6 讲的 Archon 式搜索就在调它;本讲不展开。
1.2 一段提示词值多少
代码 01 先看起点程序(指令只说”决定 APPROVE / REJECT / ESCALATE”)错在哪:测试集 600 张单子上 53.5%;把整份政策原样写进指令就到 96.2%。错误几乎全来自模型不知道的规定——涉及最多的是餐费(108 个错误)和周末(107 个),而”本该批准却被常识规则拦下”的错误只有 1 个。
第一条经验:如果你手里有这份政策,最好的”提示词优化”就是把它写进去。自动优化器要解决的是你没有、或者写不清楚的那部分知识。
再看每条规则单独值多少(在起点程序上只加一条):
| 只加这一条 | 准确率 | 变化 |
|---|---|---|
| Escalate non-travel expenses dated on a weekend. | 67.7% | +14.2% |
| Escalate meals above 75 USD per person. | 62.7% | +9.2% |
| Reject any expense above 25 USD that has no receipt. | 61.7% | +8.2% |
| Reject any expense that includes alcohol. | 60.8% | +7.3% |
| Reject software purchases; software must be bought through IT. | 59.0% | +5.5% |
| Escalate client gifts above 100 USD. | 57.2% | +3.7% |
| Reject claims marked as a possible duplicate of an earlier claim. | 56.7% | +3.2% |
| Escalate business-class airfare. | 53.7% | +0.2% |
| Convert foreign-currency amounts to USD …(normalize) | 53.5% | +0.0% |
| For meals, divide the USD total by the number of attendees …(normalize) | 53.5% | +0.0% |
和一些”写错了”的版本:
| 指令 | 准确率 |
|---|---|
| 整份政策(换算 + 平摊 + 8 条规则) | 96.2% |
| 整份政策,但没写换算 | 94.0% |
| 整份政策,但没写按人头平摊 | 93.2% |
| 整份政策,但没写餐费规则 | 87.3% |
| 整份政策,但餐费阈值写成 100 | 92.3% |
| 整份政策,但发票阈值写成 0 | 94.5% |
| 整份政策 + 一条不是政策的规则(驳回出租车) | 86.8% |
三点直觉:
- 价值取决于覆盖面和”常识能做对多少”:周末规则覆盖约 19% 的单子、模型凭常识一张都做不对,所以最值钱;商务舱规则只覆盖 1.3%。
- 价值取决于其余部分写了什么:两条换算规则单独加上去是 +0.0%——没有任何金额阈值的规则时,金额换没换算根本不影响结论;可是在整份政策里去掉它们,就分别掉 2.2 和 3.0 个点。一段提示词的价值不是独立的,这会在 §3 变成”多模块归因”的难题。
- 自信的错误规则最贵:阈值写错(100 而不是 75)比完全不写好,但多写一条不是政策的规则(“驳回出租车”)掉了近 10 个点。优化器从少量数据里”归纳”规则时,最怕的就是这种东西。
1.3 提示词越长越好吗
写全政策之后再往 decide 里加示范:
| decide 的示范个数 | 每条规则被遵守的概率 | 每次 rollout 的提示词 token | 准确率 |
|---|---|---|---|
| 0 | 0.925 | 510 | 96.2% |
| 4 | 0.901 | 1,043 | 92.8% |
| 8 | 0.877 | 1,571 | 92.2% |
| 16 | 0.829 | 2,633 | 86.7% |
| 32 | 0.733 | 4,747 | 81.7% |
“指令越长、每条规则被遵守的概率越低”是我们设的旋钮(真实模型确实会在长提示词里漏看指令,但下降多快因模型而异);token 的增长是实打实的:32 个示范让每次调用的输入涨了 9 倍。优化提示词时要同时看准确率和长度。
1.4 三类杠杆的成本结构
| 杠杆 | 改的是什么 | 一次性成本 | 每次调用的成本 | 需要的数据 |
|---|---|---|---|---|
| 提示词(指令 + 示范) | 模型读到的文字 | 优化时的 rollout(几百到几千次) | 提示词变长 | 几十到几百个带指标的例子 |
| 权重(微调 / 蒸馏) | 模型本身 | 训练 + 准备训练数据 | 可以换成更小、更便宜的模型 | 成百上千条(好的)轨迹或标签 |
| 推理算力 | 每次回答花多少计算 | 几乎没有(可能要训练一个验证器) | ×N | 一个验证器 / 打分方法 |
2. 提示词优化 · Prompt optimization
2.1 问题的形式
一个 LM 程序有若干模块,每个模块有一条指令和一组示范。给定:
- 一个小训练集(这里 150 个例子)和验证集(100 个),只标注了最终输出——中间步骤(例如 normalize 该写多少美元)通常没有标注;
- 一个指标 μ(这里:结论对不对);
- 一个预算(rollout 次数)。
求:让验证集上平均 μ 最大的指令和示范。难点有三:搜索空间是自然语言,没有梯度;每次评估都要调用 LLM,很贵;多个模块时,最终的对错要”归因”到某个模块的某句指令上。
2.2 OPRO:LLM 当优化器
Yang et al.(2023,Large Language Models as Optimizers)的想法最直接:把”以前试过的提示词和它们的分数”按分数从低到高排好,连同几个任务例子一起放进一个元提示,让 LLM 写一个分数更高的新提示词;评估、加进历史、重复。论文报告,OPRO 找到的提示词比人写的在 GSM8K 上最多高 8%、在 Big-Bench Hard 的任务上最多高 50%(著名的例子是 PaLM 2 上找到的 “Take a deep breath and work on this problem step-by-step.”)。
OPRO 的信息来源只有”提示词 ↔ 分数”的对应关系:它不知道哪个例子错了、为什么错。
2.3 TextGrad:文字形式的”梯度”
TextGrad(Yuksekgonul et al., 2024)把 PyTorch 的自动微分搬到文字上:程序是一张计算图,LLM 对输出给出文字批评(“梯度”),批评沿着图反向传给每个变量(提示词、中间结果),再由 LLM 按批评改写变量(“梯度下降”)。它和 GEPA 的共同点是用自然语言的反馈代替标量奖励;本讲不单独实现它。
2.4 MIPROv2:有根据的提议 + 贝叶斯优化
MIPROv2 针对多步的 LM 程序,摘要里点出三个关键技术:
- 程序感知、数据感知的指令提议:提议模型先读训练数据写一段数据摘要、读程序的代码了解每个模块的角色,再结合自举出来的示范和随机的”提示”(tip,例如”简洁一点""覆盖边界情况”)写出多条候选指令;
- 随机小批量评估 + 代理模型:每次只在验证集的一个小批量上评估一个”指令 × 示范”的组合,用这些有噪声的分数训练一个代理模型(TPE),决定下一个试哪个组合;
- 元优化:随着搜索调整提议的方式。
论文在 7 个多步程序中的 5 个上胜过基线优化器,用 Llama-3-8B 时最多高 13%。
代码里的 miprov2() 实现了前两步:自举 6 组示范(第 0 组为空);提议模型从 30 个训练例子里归纳规律(“有相关特征且结论一致的例子 ≥ 2 个、多于反例”→ 以一定概率写进摘要,阈值取被批准的最大值和被拦下的最小值的中点);每个模块 6 条候选指令;然后用 TPE 在 6 × 6 × 6 × 6 的组合里搜,每次 25 个例子的小批量,每 8 次把小批量平均分最高的组合完整评估一次。
TPE 的直觉:把试过的组合按分数分成”好的”(前 25%)和”其余”两组,对每个变量估计 l(x)(好组里取 x 的频率)和 g(x)(其余组里的频率),下一个试 l(x)/g(x) 最大的组合——“像好配置、不像坏配置”。它只看排名,所以能容忍小批量评估的噪声。
2.5 GEPA:反思式的提示词进化
GEPA(Agrawal et al., ICLR 2026 口头报告)的出发点:强化学习(例如 GRPO)把一次几十步的执行轨迹压成一个标量奖励,丢掉了大量信息;而 LLM 能读懂轨迹——推理过程、工具调用、编译器报错、评分细则里没满足的那一条。GEPA 的三个部件:
- 反思式变异:在训练集的一个小批量(例如 3 个例子)上跑当前候选,收集某一个模块的输入、输出和文字反馈,让反思模型读这些、写出这个模块的新指令;
- 严格检验:新候选在同一个小批量上严格更好,才在整个 Pareto 验证集上评估、加进候选池;
- Pareto 选择:下一个要改的父候选,从”在某个验证例子上做得最好”的候选里抽(权重 = 它是多少个例子上的最好者),而不是总改当前平均分最高的那个——保留不同的发现,避免卡在局部最优;另有可选的合并:把两条进化路线各自改好的模块拼在一起。
论文摘要:在 6 个任务上平均比 GRPO 高 6%、最多高 20%,同时少用最多 35 倍的 rollout;比 MIPROv2 高 10% 以上(例如 AIME-2025 上 +12%)。
GEPA 的关键依赖是反馈函数:指标除了给分,还要说出为什么。代码里的 expense_world.feedback() 有三种:
| 模式 | decide 模块看到的反馈(一张本该升级审批的餐费单) |
|---|---|
| score | Expected decision: ESCALATE. Got: APPROVE. |
| rich | 同上 + Applicable policy: Escalate meals above 75 USD per person.;上游金额错了时,说明”错在 normalize”,并且告诉它”输入对的时候该用哪条政策” |
| local | 和 rich 一样,只是上游出错时只说”错在上游”,不再提政策 |
2.6 同样的预算下比一比
代码 02:每种方法 2,500 次 rollout,5 个随机种子:
| 方法 | 验证集(挑中的) | 测试集(5 次平均) | 测试集最差 – 最好 | 优化模型调用 | 提示词 token |
|---|---|---|---|---|---|
| 起点(只说任务) | 55.0% | 53.5% | — | 0 | 407 |
| BootstrapFewShot + 随机搜索(只挑示范,第 5 讲) | 59.2% | 57.2% | 56.0% – 58.3% | 0 | 1,118 |
| MIPROv2(只改指令) | 74.0% | 72.6% | 67.3% – 79.7% | 11 | 460 |
| MIPROv2(指令 + 示范) | 80.8% | 74.8% | 65.0% – 83.8% | 11 | 1,605 |
| OPRO | 74.8% | 68.2% | 54.8% – 78.3% | 32 | 442 |
| GEPA | 92.6% | 90.5% | 89.2% – 91.8% | 46 | 478 |
| (参照)把整份政策写进指令 | — | 96.2% | — | — | 510 |
种子 0 时各方法最后写出的规则:
- 只挑示范:没有规则,3 + 3 个示范;
- MIPROv2(指令 + 示范):decide 里
receipt(315)、alcohol、weekend+ 5 + 5 个示范——发票阈值 315 是从 30 个例子里”估”出来的(那几张没发票被驳回的单子金额都很大),真实是 25; - OPRO:
alcohol、weekend、meal_limit(10820)、duplicate——餐费阈值 10820 是从一张没换算的日元单子上猜出来的; - GEPA:normalize 里
per_person、currency,decide 里alcohol、meal_limit(75)、receipt(25)、software、weekend——阈值全对。
读法:
- 只挑示范帮不上忙:自举的示范来自起点程序做对的单子,那些大多是本来就该批准的——示范里没有公司的规定。
- GEPA 的优势是信息:它读的是失败的轨迹 + 引用政策原文的反馈,一次反思就能写对一条规则(含阈值)。OPRO 只看到”提示词 ↔ 分数”,MIPROv2 的提议只来自一次性的数据摘要——它们只能猜,猜出来的阈值和规则可能很离谱。
- MIPROv2 的上限由提议决定:候选指令一开始就定了,贝叶斯优化只在组合里挑。
- 验证集高估了测试集:OPRO 6.6 个点、MIPROv2 6.0、GEPA 2.1——在同一个验证集上比较几十个候选、挑最高的,第 5 讲的赢家诅咒。
- 种子之间差很多:OPRO 最差 55%、最好 78%。报告一个优化器的效果,要跑多个种子。
- 示范很贵:带示范的 MIPROv2 每次 rollout 1,605 token,是 GEPA 的 3 倍多。
3. GEPA 的内部 · Inside GEPA
3.1 一次迭代
代码 03 演示 1 打印了一次运行(种子 0)的前几个修改:
| 迭代 | 父候选 | 模块 | 修改 | 小批量(3 个)得分 | 结果 |
|---|---|---|---|---|---|
| 1 | 0 | decide | +alcohol | 2 → 3 | 接受(候选 1) |
| 5 | 0 | decide | +weekend +meal_limit(75) | 1 → 3 | 接受(候选 2) |
| 7 | 0 | decide | +gift(100) +weekend | 1 → 3 | 接受(候选 3) |
| 11 | 0 | decide | +weekend +receipt(25) +software | 0 → 3 | 接受(候选 5) |
| 12 | 5 | normalize | +currency | 0 → 1 | 接受(候选 6) |
| 14 | 0 | normalize | +currency | 1 → 1 | 拒绝 |
| 24 | 7 | normalize | +per_person +currency | 1 → 2 | 接受(候选 9) |
一次迭代 = 挑父候选 → 在 3 个训练例子上跑、收集一个模块的轨迹和反馈 → 反思写出新指令 → 在同样的 3 个例子上严格更好,才花 100 次 rollout 在验证集上完整评估。这次运行提出了 30 个修改,接受了 21 个;结束时候选池 22 个,其中 16 个在 Pareto 前沿上。
注意第 14 次:在候选 0(decide 里一条规则都没有)上加换算规则,小批量得分没变,被拒——下游没有金额阈值的规则时,把金额换算对了不会让任何结论变对(§1.2 的”交互作用”)。normalize 的 12 个修改里有 8 个被拒,大多是这个原因。
3.2 反馈的质量决定一切
代码 03 演示 2(预算 2,500,5 个种子):
| 配置 | 验证集 | 测试集(平均) | 测试集最差 – 最好 |
|---|---|---|---|
| 默认:rich 反馈 + Pareto 选择 | 92.6% | 90.5% | 89.2% – 91.8% |
| 反馈:local(上游出错时只说”错在上游”) | 91.6% | 84.3% | 82.5% – 86.3% |
| 反馈:score(只给标准结论) | 87.4% | 81.6% | 77.0% – 86.7% |
| 选择:总是改当前最好的 | 97.6% | 95.3% | 92.5% – 97.0% |
| 加上合并 | 90.4% | 85.9% | 83.3% – 88.2% |
| 小批量 8 个(默认 3 个) | 92.6% | 90.9% | 87.2% – 95.8% |
rich > local > score。score 模式下,反思模型只知道”应该是 ESCALATE”,只能从单子的特征猜原因(猜中真正原因的概率是旋钮 0.55),猜出来的规则和阈值常常是错的。
local 模式最有意思:它的反馈看起来更”准确”(错确实在上游),结果却比 rich 差 6 个点。原因是一个死锁:
- 上游(normalize)还没学会换算时,decide 的反馈里只有”错在上游”,没有政策——decide 学不到餐费规则;
- 上游学会换算的修改,又要等 decide 有了餐费规则才能让某个结论变对——小批量检验看不出它的价值,被拒。
两个模块互相等对方。rich 反馈打破了死锁:即使错在上游,也告诉 decide”输入对的时候该用哪条政策”。
第二条经验:在 GEPA 这类方法里,写反馈函数就是在写优化器的一半。好的反馈要告诉每个模块”你该怎样做”,而不只是”错不在你”。编译器报错、没通过的测试、评分细则里没满足的条目、审核员退回单子时写的理由——这些现成的文字往往就是最好的反馈。
3.3 模块之间的归因
演示 4:local 反馈下,decide 的反思一共 110 次被告知”错在上游”,其中 21 次没意识到(旋钮:意识到的概率 0.8),按眼前错误的金额给自己加了规则——例如把没换算的日元金额当成了餐费阈值。rich 反馈下是 81 次中的 9 次。多模块程序的优化,难点之一就是把功劳和过错记到对的模块上:模块越多、中间步骤越没有标注,这件事越难(第 5 讲的 BootstrapFewShot 用”整条程序做对了就把每一步都当示范”回避了它)。
3.4 Pareto 还是贪心:和论文相反的结果
在我们的任务上,“总是改当前最好的”(95.3%)比 Pareto 选择(90.5%)好——和 GEPA 论文的消融结论相反。为什么?
- 这里的”景观”很简单:规则之间几乎互不干扰,在最好的候选上再加一条对的规则总是更好,没有局部最优;
- Pareto 选择把一部分预算花在”只在少数例子上最好”的候选上(探索)。验证集的逐例分数有噪声,22 个候选里 16 个都在前沿上——很多只是在某个例子上”碰巧最好”。
论文的任务(多跳问答、指令遵循、隐私改写、代码优化)里,贪心会卡在局部最优:当前最好的候选可能已经被一条过于具体的规则”锁死”,在它上面的局部修改都不再有效,这时保留不同的路线才值钱。消融的结论依赖任务——拿到一个新优化器,先在自己的任务上量。
合并在这里也降低了准确率(85.9%):每次尝试合并都要在 5 个例子上跑三个候选,被接受的还要完整评估一次,挤占了反思的预算。合并的价值取决于不同的进化路线是否各自改好了不同的模块。小批量 8 个(90.9%)和 3 个差不多:检验更可靠,但每次迭代更贵、迭代次数更少。
3.5 预算
演示 3(3 个种子的平均测试准确率;括号里是验证集高估了多少):
| 方法 | 500 次 | 1,000 次 | 2,500 次 | 5,000 次 |
|---|---|---|---|---|
| OPRO | 61.2% (−0.2%) | 65.8% (+2.9%) | 69.3% (+5.4%) | 75.9% (+5.7%) |
| MIPROv2 | 75.8% (+3.5%) | 77.1% (+7.9%) | 76.9% (+8.4%) | 76.9% (+8.4%) |
| GEPA | 73.3% (+3.4%) | 77.6% (+3.4%) | 89.9% (+2.1%) | 89.5% (+6.2%) |
| GEPA(总是改最好的) | 77.6% (+1.1%) | 91.9% (+0.7%) | 95.7% (+2.0%) | 95.7% (+2.0%) |
- 预算很小时(500 次),MIPROv2 一次性归纳出的几条常见规律领先:比一条条反思来得快。
- MIPROv2 从 1,000 次之后几乎不涨:候选指令一开始就定死了。
- OPRO 稳定地慢慢涨;它评估的候选最多、候选之间差别又小,挑出来的最高分里运气成分最大(5,000 次时验证集高估 5.7 个点)。
- GEPA(Pareto 选择)在 2,500 次之后不再涨,贪心版则到了 95.7%,接近写全政策的 96.2%。再往上,剩下的是任务模型”偶尔没照规则办”的随机错误,改提示词修不好——这要靠 §4 的推理算力(§6 会看到投票正好能修它)或 §5 的权重。
4. 测试时算力 · Test-time compute
4.1 问题
Snell et al. 问:如果允许一个 LLM 在回答一道难题时多花推理算力,它能提高多少?怎样花最好?他们研究两类机制:
- 对着验证器搜索:用一个按步打分的过程奖励模型(PRM),对采样出的解或部分解打分——best-of-N(采样 N 个完整解、挑最好的)、束搜索(每一步保留 PRM 打分最高的前缀,再往下展开)、前瞻搜索;
- 修改提议分布:微调一个会”看着自己上一个答案修改”的模型,一条链接一条链地改(顺序),或者同时开几条链(并行),或者两者按比例混合。
核心结论(摘要):哪种方法最有效取决于题目的难度;按难度挑选策略(“计算最优”)比 best-of-N 基线省 4 倍以上的算力;在 FLOPs 相同的比较里,在小模型本来就有一定成功率的题上,测试时算力可以胜过一个大 14 倍的模型。论文里的”难度”是相对于基础模型的:按基础模型在这道题上的通过率分成 5 组(“神谕难度”),或者用 PRM 对几个样本的打分来估计(“预测难度”,不需要标准答案)。
4.2 模拟的题库
ttc_lab.py:每道题有一个难度 z;小模型单次做对的概率 p = sigmoid(0.2 − 1.5z),一个解有 4 步。错的解给出错答案,35% 的时候是同一个”诱人的错答案”;错答案在第一次出错时就定下来,同一个错前缀往下写出的解给出同一个错答案。PRM 对前缀打分:全对 ~ N(1, σ),有错 ~ N(0, σ),题越难 σ 越大;还有很小的概率(每步 1.2%)写出一个”钻 PRM 空子”的步骤——PRM 给它的分数比真正正确的步骤还高(均值 1.3),之后的答案却是错的。修改:对的答案以 10% 的概率被改错,错的以 0.35·p 的概率被改对。
4.3 同样的预算,不同的策略
代码 04 演示 1(600 道测试题,预算 = N 个完整解的算力):
| 策略 | N = 4 | N = 16 | N = 64 |
|---|---|---|---|
| 多数投票 | 65.2% | 75.2% | 76.0% |
| best-of-N(只取 PRM 最高的一个) | 64.8% | 73.5% | 66.0% |
| best-of-N(按答案加权投票) | 72.2% | 85.0% | 87.2% |
| 束搜索(M=4,按步打分) | 78.2% | 79.3% | 89.3% |
| 修改链(1 条链,全顺序) | 64.0% | 84.5% | 93.5% |
| 修改链(√N 条链 × √N 次) | 68.8% | 84.7% | 92.3% |
(单次采样 52.6%。)
- 多数投票最差:难题上错答案常常集中在同一个诱人的错答案上(第 6 讲 §2.3、第 8 讲 §5.2 的”错误相关”)。
- 只取 PRM 最高分的一个,N 越大越糟(N=64 只有 66%):样本越多,越容易碰到一个骗过 PRM 的解——对着一个不完美的验证器”用力优化”,就会优化出它的漏洞。
- 按答案加权投票(把同一答案的 PRM 分数加起来)最稳;这是 Snell 用的 best-of-N 基线。
4.4 按难度看
演示 2(N = 16 和 64,第 0 组最简单):
| 策略 | 第 0 组 | 第 1 组 | 第 2 组 | 第 3 组 | 第 4 组 |
|---|---|---|---|---|---|
| N=16 best-of-N(加权) | 100% | 100% | 100% | 93% | 39% |
| N=16 束搜索 | 88% | 88% | 85% | 85% | 52% |
| N=16 修改链(全顺序) | 99% | 99% | 96% | 80% | 43% |
| N=64 best-of-N(加权) | 100% | 100% | 100% | 97% | 41% |
| N=64 束搜索 | 94% | 96% | 94% | 89% | 68% |
| N=64 修改链(全顺序) | 100% | 100% | 100% | 98% | 72% |
| 单次采样的 pass@1 | 90% | 73% | 53% | 33% | 14% |
没有哪种策略处处最好:
- 束搜索在难题上最强、在简单题上反而更差。它每一步只留 PRM 打分最高的前缀;一个”钻空子”的前缀一旦出现,打分最高、一定被留下,它的后代也继承高分,几步之后就能占满整个束。简单题本来随便采样就能做对,搜索只带来了被 PRM 骗的风险——这就是 Snell 观察到的束搜索在简单题、高预算下的”过度优化”。难题上正确的前缀很稀少,按步筛选的好处大过被骗的风险。
- best-of-N 在中低难度上已经饱和,难题上停滞在 40% 左右。
4.5 计算最优
既然不同难度适合不同策略,那就按难度挑:在训练题上,为每个难度组挑准确率最高的策略(多数投票、best-of-N、束搜索、各种”并行条数 × 顺序次数”的修改链);测试时先判断题目属于哪组,再用那组的策略。演示 3:
| N | best-of-N | 计算最优(神谕难度) | 计算最优(PRM 估计难度) | best-of-N 要多少个才追上 | 每组挑中的策略(第 0 → 4 组) |
|---|---|---|---|---|---|
| 4 | 70.7% | 80.2% | 77.0% | 9(2.2 倍) | majority / revise(4x1) / beam / beam / beam |
| 16 | 85.2% | 91.3% | 86.3% | > 256 | best-of-N / best-of-N / best-of-N / revise(16x1) / beam |
| 64 | 88.5% | 93.8% | 93.5% | > 256 | best-of-N / best-of-N / best-of-N / revise(8x8) / revise(1x64) |
- N=16 时,计算最优比 best-of-N 高 6 个点;best-of-N 到 256 个样本也只有 88.5%(它在最难的一组上停滞),再多的样本也追不上。论文报告的是”省 4 倍以上”。
- 用 PRM 估计难度(不看标准答案)在 N=16 时差了不少(86.3%):估计本身有噪声,而且估计难度也要花样本。
- 挑中的策略和 Snell 的发现不完全一样:我们这里简单组用 best-of-N、难组用束搜索或修改链;Snell 发现顺序修改最适合简单题,难题要在顺序和并行之间找一个比例。差别来自旋钮——我们的修改模型很少把对的改错,一条链一旦改对就会反复投出正确答案。哪种策略在哪种难度上最好,取决于你的模型和验证器,这正是要”在自己的数据上按难度挑”的理由。
4.6 推理算力 vs 更大的模型
Snell 的另一个问题:同样多的 FLOPs,是给小模型更多推理算力好,还是预训练一个更大的模型好?粗略地,预训练 FLOPs ≈ 6·P·D_pre、推理 ≈ 2·P·D_inf(P 是参数量)。设 R = D_inf / D_pre(部署时要生成的 token 和预训练 token 之比)。大 14 倍的模型多花的总 FLOPs,换算成小模型每题可以多用的推理倍数:
14·(6·D_pre + 2·D_inf) = 6·D_pre + 2·D_inf·X ⟹ X = 39 / R + 14
R 越小(推理量相对预训练越少),小模型每题能多花的推理越多。演示 4(大模型每题只答一次;大模型强多少是未知的,用 logit 上高出 1.6 和 3.0 两种情况):
| 小模型 + X 倍推理 vs 大模型 | 第 0 组 | 第 1 组 | 第 2 组 | 第 3 组 | 第 4 组 |
|---|---|---|---|---|---|
| R = 22(X = 15),能力差 1.6 | 100% vs 98% | 100% vs 93% | 95% vs 86% | 82% vs 70% | 52% vs 43% |
| R = 22(X = 15),能力差 3.0 | 100% vs 99% | 100% vs 98% | 95% vs 96% | 82% vs 90% | 52% vs 73% |
| R = 0.16(X = 257),能力差 1.6 | 100% vs 98% | 100% vs 93% | 100% vs 86% | 100% vs 70% | 77% vs 43% |
| R = 0.16(X = 257),能力差 3.0 | 100% vs 99% | 100% vs 98% | 100% vs 96% | 100% vs 90% | 77% vs 73% |
- 大模型只强一点(1.6)时,即使推理量很大(R = 22),小模型 + 推理算力在每一组都不输。
- 大模型强很多(3.0)、推理量又大时,从第 2 组起大模型赢——难题上,大模型多学到的东西是采样换不来的。
- 同样的大模型,推理量小(R = 0.16,每题 257 倍)时,小模型又在每一组都追上了。
和 Snell 的结论是同一个形状:在小模型”有一定成功率”的题上,测试时算力可以替代预训练;难题上要看大模型多学到了多少;推理量越大,分给每题的额外算力越少,越该投资在更大的模型上。论文还指出,测试时算力和预训练算力并不能一比一互换——它们是互补的。
5. 改权重 · Optimizing weights
这一讲只讲到”概览”的深度:什么时候值得改权重,改的时候在付什么代价。
5.1 微调要多少显存:全参数、LoRA、QLoRA
全参数微调(混合精度 + Adam)每个参数要约 16 字节:bf16 权重 2 + bf16 梯度 2 + fp32 主权重 4 + Adam 的两个动量 8(还不算激活值)。
LoRA(Hu et al., 2021):冻住原来的权重矩阵 W(a×b),旁边加一个低秩增量 ΔW = A·B(A 是 a×r,B 是 r×b,r 很小),只训练 A、B。B 初始化为 0,所以训练开始时模型和原来一模一样。可训练参数从 a·b 降到 r·(a+b)。论文报告,在 GPT-3 175B 上可训练参数减少一万倍、显存需求降到三分之一。
QLoRA(Dettmers et al., 2023):再把冻住的权重量化成 4 bit(NF4,一种按正态分布设计分位点的 4-bit 格式),量化常数本身再量化一次(“双重量化”),优化器状态放进可以换页到 CPU 内存的”分页优化器”。论文报告,一张 48GB 的 GPU 就能微调 65B 的模型,效果和 16-bit 微调相当。
代码 05 演示 1 按 Llama 3 的公开结构算(所有线性层都加 LoRA):
| 模型 | 参数 | 全参数微调(GB) | LoRA r=16 可训练参数 | LoRA(GB) | QLoRA(GB) |
|---|---|---|---|---|---|
| Llama-3-8B | 8.03B | 128 | 41.9M(0.52%) | 16.7 | 4.8 |
| Llama-3-70B | 70.55B | 1,129 | 207.1M(0.29%) | 144.4 | 39.7 |
(只算参数、梯度、优化器状态;激活值取决于批大小和序列长度,另算。)全参数微调 70B 要一个机柜的 GPU;QLoRA 让它进了一张卡。代价:LoRA 的表达能力有限(低秩),QLoRA 的前向计算要先反量化、更慢。
5.2 蒸馏:老师打标签,学生学
如果任务固定、调用量很大,可以让”老师”(贵的大模型 + 优化好的提示词)给大量无标注数据打标签,训练一个便宜得多的”学生”。代码 05 演示 2 的学生是一个真的在训练的 softmax 回归(纯 Python,特征哈希:类别、币种、有没有发票、“币种 × 金额桶”、“类别 × 人数 × 金额桶”等组合特征);老师是写全政策的程序(测试集 96.2%):
| 标签个数 | 学生(老师的标签) | 学生(人工标签) |
|---|---|---|
| 100 | 77.5% | 80.3% |
| 300 | 85.5% | 87.7% |
| 1,000 | 92.5% | 94.5% |
| 3,000 | 94.3% | 98.5% |
- 数据少时,瓶颈是数据量:300 个标签时用老师标签和人工标签只差 2 个点。
- 数据多了,瓶颈变成标签质量:3,000 个人工标签的学生到了 98.5%,比老师还高;用老师标签的只有 94.3%——老师 4.1% 的错标签也被学了进去。
- 学生学不会什么:餐费规则 87%(老师 96%)、礼品规则 83%(老师 92%)——要除以人数、要换算汇率的规则,线性模型只能靠”币种 × 金额桶 × 人数”硬记。
- 学生也可能比老师好:软件规则 94%(老师 88%)。老师的错误是随机的(偶尔没照规则办),学生学到的是多数标签的规律,把这些噪声”平均”掉了。
- 成本:老师每张单子约 650 个输入 token、两次大模型调用;学生是一次点积。代价是学生只会这一件事,政策一变就要重新打标签、重新训练。
真实的蒸馏多是”序列级”的:用老师生成的完整回答(含推理过程)微调一个小 LLM。道理相同:学生的上限受老师和自身容量的双重限制。
5.3 RLHF 与 DPO
有些任务说不出标准答案,只能比较好坏:回答的语气、有用性、是否安全。RLHF(InstructGPT 的做法)先用人的偏好对训练一个奖励模型,再用强化学习(PPO)最大化奖励,同时加一个”别离原模型太远”的 KL 惩罚。DPO(Rafailov et al., 2023)证明这两步可以合成一步:直接在偏好对上最小化
L = −log σ( β·[ (log π(y_w|x) − log π_ref(y_w|x)) − (log π(y_l|x) − log π_ref(y_l|x)) ] )
y_w 是更好的回答,y_l 是更差的;π_ref 是参考模型(通常是 SFT 之后的模型);β 越小,允许离参考模型越远。
代码 05 演示 3 在学生模型上做 DPO:600 张单子,SFT 模型各采样两个回答,两个不同、其中一个对时,人选对的那个(116 对):
| 模型 | 训练轮数 | 测试准确率 | 和 SFT 的平均 KL |
|---|---|---|---|
| SFT(300 个老师标签) | — | 85.5% | 0.000 |
| DPO,β = 0.1 | 2 / 10 | 86.7% / 85.2% | 0.007 / 0.195 |
| DPO,β = 0.5 | 2 / 10 | 86.0% / 81.2% | 0.089 / 0.352 |
| DPO,β = 2.0 | 2 / 10 | 87.0% / 88.3% | 0.031 / 0.069 |
| 对照:把偏好对里”更好的那个”当标签,接着做 SFT | — | 89.5% | 0.055 |
- 训 2 轮时三种 β 都小涨;训到 10 轮,β 小的离 SFT 越来越远、准确率下降(β = 0.5:81.2%),β = 2.0 一直贴着参考模型。β 就是缰绳:只有 116 个偏好对时,放得太松就会过拟合。
- 同样的信息直接当标签做 SFT 是 89.5%,比任何 DPO 都好:在有标准答案的任务上,偏好数据不比标签更有用。DPO / RLHF 的用武之地是只能比较好坏的任务。
5.4 提示词 + 权重:BetterTogether
Soylu et al.(EMNLP 2024)第一次把权重优化和提示词优化结合起来优化一个多步的 LM 程序:先用提示词优化让程序变好,用程序自己生成、经指标过滤的轨迹微调权重,再优化一次提示词。论文报告,在多跳问答、数学推理、特征分类上,用 Mistral-7B、Llama-2-7B、Llama-3-8B,平均比只改权重最多高 60%、比只改提示词最多高 6%;这个优化器已经放进了 DSPy。
代码 05 演示 4 用模拟的微调(一条规则在正确的训练轨迹里出现 ≥ 3 次就被”学进权重”,不用写在指令里也会用上)走一遍这个流程:
| 做法 | 测试准确率 | 提示词 token | 学进权重的规则 | 训练用的正确轨迹 |
|---|---|---|---|---|
| 起点(只说任务) | 53.5% | 407 | 0 | — |
| 只改权重:起点程序的正确轨迹 → 微调 | 69.8% | 407 | 3 | 79 |
| 只改提示词:GEPA(2,500 次) | 91.2% | 485 | 0 | — |
| 提示词 → 权重(配 GEPA 的提示词) | 91.3% | 485 | 7 | 134 |
| 提示词 → 权重,配回起点的短提示词 | 91.0% | 407 | 7 | 134 |
| 提示词 → 权重 → 提示词(再跑 GEPA 1,000 次) | 96.5% | 504 | 7 | 134 |
| 对照:不微调,同样再跑 GEPA 1,000 次 | 96.2% | 504 | 0 | — |
- 先改提示词,能拿来训练的轨迹更好:起点程序做对的 79 条轨迹大多是本来就该批准的单子,只学会了 3 条规则;GEPA 程序做对的 134 条轨迹学会了 7 条。这是”先提示词、后权重”的机制。
- 但这一张表里,第三步的增益不是微调带来的:对照组(不微调、同样多跑 1,000 次 GEPA)也有 96.2%。如果没有这一行,我们会错误地把 5 个点的提升记在微调头上。做对照实验,才看得出增益从哪来。
- 在这个模拟里,微调真正带来的是省 token:规则进了权重,配回起点的短提示词仍有 91.0%。论文里的增益发生在真实的 7–8B 小模型上——微调改进的不只是”记住几条规则”,还有格式、推理习惯,这些我们的模拟没有建模。
6. 什么时候改什么 · When to optimize what
6.1 推理算力补不了知识
代码 06 演示 1:每个程序采样 5 次(temperature 0.7)投票:
| 程序 | 1 次 | 5 次投票 | 变化 |
|---|---|---|---|
| 起点(只说任务) | 53.5% | 50.7% | −2.8% |
| GEPA 优化过的提示词 | 91.2% | 94.0% | +2.8% |
| 把整份政策写进指令 | 96.2% | 100.0% | +3.8% |
起点程序投票之后更差了。它不知道公司的规定,大多数单子上每次都犯同一个错;少数单子它凭”常识”有两三成的概率做对(例如凭常识驳回带酒水的),投票把这些少数派的正确答案投掉了——每次做对的概率低于一半时,投票会让它更稳定地错(第 8 讲 §5.2 的错误相关是同一件事)。而写全政策的程序剩下的错误几乎都是”偶尔没照规则办”,每次做对的概率远高于一半,投票把它们全抵消了。
第三条经验:先分清你的错误是系统性的(模型不知道)还是随机的(模型知道、但偶尔做错)。前者要补知识(提示词或权重),后者才是推理算力的用武之地。
6.2 一张账
演示 2–3(价格按第 5 讲:输入每百万 token 5 美元、输出 25 美元):
| 做法 | 测试准确率 | 一次性成本(美元) | 每 1,000 张单子(美元) |
|---|---|---|---|
| 起点提示词 | 53.5% | 0 | 3.34 |
| GEPA 提示词 | 91.2% | 9.84 | 3.73 |
| GEPA 提示词 × 5 次投票 | 94.0% | 9.84 | 18.63 |
| 整份政策写进指令(人写的) | 96.2% | 0(人的时间) | 3.85 |
| 蒸馏的学生(3,000 个老师标签) | 94.3% | 11.55 | ≈ 0 |
一次性成本都是小钱:一次 GEPA 约 10 美元,给 3,000 张单子打标签约 12 美元。优化的成本通常不是瓶颈,数据和指标才是。
再把错误的代价算进去(每 100 万张单子):一次判错只要 0.01 美元时(错了也无所谓),总成本最低的是蒸馏的学生(578 美元)——计算成本占大头;一次判错要 2 美元的人工返工时,最低的是写全政策的提示词(80,517 美元),学生(113,345)和 5 次投票(138,635)都更贵——这时多一两个点的准确率比全部计算成本都值钱。“优化什么”的答案取决于你的错误有多贵。
6.3 一张决策表
| 情形 | 先试什么 | 为什么 |
|---|---|---|
| 你知道规则,只是没写下来 | 写进提示词 | 最便宜、最快、最好改;自动优化器替代不了领域知识 |
| 有指标、有几十到几百个例子,规则说不清 | 提示词优化(GEPA / MIPROv2) | 几千次 rollout 就够;把反馈写好比选哪个优化器更重要 |
| 错误是随机的(偶尔没照做、偶尔读错) | 推理算力:采样投票 / 验证器 | 独立的错误能被投票抵消 |
| 错误是系统性的(模型不知道) | 不是推理算力 | 投票只会把同一个错误投出来;要补知识 |
| 量很大、任务固定、延迟敏感 | 蒸馏 / 微调一个小模型 | 一次性成本摊薄后每次调用几乎免费;代价是灵活性 |
| 说不出标准答案,只能比较好坏 | 偏好优化(RLHF / DPO) | 偏好对是这类任务唯一现成的信号 |
| 任务和规则经常变 | 提示词,少碰权重 | 改一句话比重新打标签、重新训练快得多 |
7. 用真实模型试一试 · Try it on a real model
07_real_llm.py(可选,需要 pip install anthropic 和 ANTHROPIC_API_KEY,约 140 次调用)用真实的 Claude 当任务模型和反思模型,跑一个迷你 GEPA:单步程序(report → decision),起点指令只说任务;循环 3 次——在 3 张训练单子上跑当前最好的指令,把失败的例子和反馈交给 Claude 反思,得到的新指令在同样 3 张上严格更好才在 15 张验证单子上评估;最后在 30 张测试单子上和起点比。
python3 07_real_llm.py
只给标准结论、不给政策原文:
python3 07_real_llm.py --feedback score
不联网检查脚本本身:python3 07_real_llm.py --toy(用模拟模型)。
读结果时想一想:
- 真实模型很聪明,可能凭常识就会换算汇率、按人头平摊——它缺的只是公司的规定。读了带政策原文的反馈,它能不能一次写对一条规则(包括阈值)?只给标准结论时,它会不会”发明”出反馈里没有的规则?
- 验证集只有 15 张:挑出来的指令在测试集上掉了多少(赢家诅咒)?
- 编写时没有用真实 API 跑过这个脚本,这里不报告数字;请以你自己的运行结果为准。
8. 小结 · Summary
- 可以优化的东西:指令、示范、权重、推理算力(还有结构)。一段提示词的价值取决于它覆盖多少情形、模型凭常识能做对多少、以及其余部分写了什么;自信的错误规则代价最大;提示词越长越贵,也未必越好。
- 提示词优化:OPRO 只看”提示词 ↔ 分数”;MIPROv2 自举示范、读数据提出候选、用 TPE 在组合里搜(上限由提议决定);GEPA 读失败的轨迹和文字反馈、反思出针对性的修改、严格检验、用 Pareto 前沿保留多样性。同样预算下 GEPA 最好,因为它的信息最多;所有方法的验证集分数都高估了测试集。
- GEPA 的内部:反馈的质量决定一切(rich > local > score);多模块程序有归因难题——上下游互相等对方会造成死锁,好的反馈告诉每个模块”你该怎样做”;Pareto 选择 vs 贪心、要不要合并,结论依赖任务。
- 测试时算力:按答案加权投票是稳健的 best-of-N;对着不完美的验证器用力搜索会优化出它的漏洞(束搜索在简单题上的过度优化);按难度挑策略比统一用 best-of-N 好得多;在小模型有一定成功率的题上,推理算力可以替代更大的模型,难题上不行,推理量越大越该投资在模型上。
- 权重:LoRA / QLoRA 把微调的显存降到一两张卡;蒸馏在量大、任务固定时最省,学生受老师和自身容量的双重限制;DPO 用于只能比较好坏的任务,β 控制离参考模型多远;先改提示词能产生更好的训练轨迹——但要用对照实验确认增益从哪来。
- 什么时候改什么:先分清错误是系统性的还是随机的;把错误的代价算进总成本;优化本身的成本通常不是瓶颈,数据和指标才是。
9. 常见误区 · Common pitfalls
- “提示词优化能替代领域知识。” 你手里有政策就写进去(96.2%);优化器是用来找你没有的那部分知识的,而且它只能从数据和反馈里找。
- “优化器选对了就行。” 同一个 GEPA,反馈从 rich 换成 score 就从 90.5% 掉到 81.6%。写反馈函数就是在写优化器的一半。
- “错在上游,告诉下游’不是你的错’就够了。” local 反馈让两个模块互相等对方,比 rich 差 6 个点。
- “验证集上 92.6%,上线就是 92.6%。” 在同一个验证集上挑了几十个候选,分数被高估(OPRO 高估 6.6 个点);要在没碰过的测试集上报告。
- “论文的消融结论在我的任务上也成立。” Pareto 选择在论文里胜过贪心,在我们的任务上输了 5 个点。
- “示范越多越好。” 32 个示范让 token 涨 9 倍、准确率从 96.2% 掉到 81.7%(在我们的旋钮下)。
- “多采样几次、投个票总没坏处。” 每次做对的概率低于一半时,投票让它更稳定地错(起点程序 53.5% → 50.7%)。
- “best-of-N 的 N 越大越好。” 只取 PRM 最高分的一个,N=64 比 N=16 还差:样本越多,越容易碰到骗过验证器的解。
- “推理算力可以替代更大的模型。” 只在小模型有一定成功率的题上;难题上大模型多学到的东西采样换不来。
- “微调之后又涨了,是微调的功劳。” 不做”同样额外预算、不微调”的对照,就分不清。
10. 练习 · Exercises
练习 1(一段提示词的价值):代码 01 里,两条换算规则单独加到起点程序上都是 +0.0%,但从整份政策里去掉它们会掉 2–3 个点。(a) 解释这个现象。(b) 它对”一次只评估一处修改”的优化器(例如 GEPA 的小批量检验)意味着什么?(c) 举一个你自己工作里”单独看没用、组合起来才有用”的提示词片段。
参考答案
(a) 换算规则只影响中间值(美元金额、每人金额),而中间值只有在 decide 用到金额阈值时才影响结论;起点程序的 decide 没有任何阈值规则,所以换没换算结论都一样。(b) 在一个没有阈值规则的候选上提出"加换算",小批量检验看不到任何提升,会被拒——价值依赖上下文的修改,要等它依赖的部分先到位;搜索的顺序(先 decide 后 normalize)和反馈的写法(rich 反馈让 decide 先学到阈值规则)都会影响能不能找到它。(c) 例如"输出用 JSON"单独看不影响正确率,但配上"下游用程序解析"就决定了能不能用;或者"先列出所有约束"配上"逐条检查约束"。练习 2(反馈函数):为下面的任务各写一个 rich 反馈函数会写什么:(a) 生成能通过单元测试的代码;(b) 按评分细则(5 条)给作文打分的程序;(c) 一个两步的 RAG 程序(先写检索词、再回答),最终答案错了。
参考答案
(a) 编译 / 运行的报错信息、没通过的测试名称、期望值和实际值(就是 GEPA 论文里代码任务用的反馈)。(b) 哪一条细则没满足、细则原文、作文里对应的句子。(c) 对检索模块:标准答案所在的文档有没有出现在检索结果里(没有 → "检索词应该包含 X 实体");对回答模块:如果证据在上下文里却答错了,指出证据句;如果证据不在,说明"错在检索,不是你"并告诉它"证据在时应该怎样回答"——不要只说"错不在你"(§3.2 的死锁)。练习 3(TPE):MIPROv2 已经试了 8 个配置(两个变量:指令 i ∈ {0,1,2}、示范 d ∈ {0,1}),小批量分数为:(0,0) 0.60、(1,0) 0.72、(2,0) 0.64、(1,1) 0.80、(0,1) 0.56、(2,1) 0.68、(1,0) 0.76、(0,0) 0.52。取”好的”为前 25%(2 个),用加 1 平滑算 l 和 g,下一个最可能试哪个配置?
参考答案
好的两个是 (1,1) 0.80 和 (1,0) 0.76。l(i):i=1 出现 2 次 → (2+1)/(2+3) = 0.6,i=0、2 → 1/5 = 0.2;l(d):d=0 和 d=1 各 1 次 → (1+1)/(2+2) = 0.5。其余 6 个:i=0 3 次、i=1 1 次、i=2 2 次 → g(i) = 4/9、2/9、3/9;d=0 4 次、d=1 2 次 → g(d) = 5/8、3/8。l/g:i=1 → 0.6/(2/9) = 2.7,最大;d=1 → 0.5/(3/8) ≈ 1.33 > d=0 的 0.5/(5/8) = 0.8。所以最可能试 (1,1)(再试一次,因为小批量有噪声)或者从 l 里抽到的相近配置。练习 4(Pareto 前沿):3 个候选在 4 个验证例子上的分数:A = [1,1,0,0],B = [1,0,1,0],C = [1,0,0,0]。(a) 哪些候选在前沿上,各自的选择权重是多少?(b) 贪心选择会选谁?(c) 什么样的任务里,B 值得被继续改?
参考答案
(a) 例子 1:A、B、C 都是 1;例子 2:只有 A;例子 3:只有 B;例子 4:没人拿分,不算。C 被 A 全面压过(A 每个例子都不比它差、例子 2 更好),去掉。权重:A = 1(例子 1)+ 1(例子 2)= 2,B = 1 + 1 = 2。(b) A 和 B 平均分一样(0.5),贪心会按平局规则选一个——另一个的"发现"(例子 3 或例子 2)可能永远不会被继续发展。(c) 当 A 已经被一条过于具体的规则锁死、在它上面的局部修改都无效,而 B 解决的那类例子(例子 3)需要一条和 A 的规则不冲突的新规则时;或者合并 A 和 B 能同时拿下例子 2 和 3 时。练习 5(只取最高分的 best-of-N):PRM 给正确解的分数 ~ N(1, 0.5),给错误解 ~ N(0, 0.5);另有 2% 的解”钻了空子”,分数 ~ N(1.3, 0.5)、答案是错的。一道题的单次正确率是 0.5。(a) 定性地说,N 从 4 增加到 64,“只取 PRM 最高分的一个”的准确率怎样变?(b) “按答案加权投票”为什么更稳?
参考答案
(a) N 小时,样本里很少有钻空子的解,最高分大多来自正确的解,N 增加会先提高准确率;N 大时,样本里几乎一定有钻空子的解(64 个里期望 1.3 个),它们的分数均值比正确解高,最高分越来越可能是它们——准确率先升后降(代码 04 里 N=16 73.5%、N=64 66.0%)。(b) 钻空子的解给出的是**各不相同**的错答案,每个错答案只有一两个解支持;正确答案有大约一半的样本支持。把同一答案的分数加起来,正确答案的总分远高于任何一个钻空子的答案。加权投票只怕"许多解犯同一个错"(错误相关)。练习 6(FLOPs 换算):推导 X = 39/R + 14,并算出 R = 1 时的 X。如果大模型是小模型的 4 倍(而不是 14 倍),公式变成什么?
参考答案
大模型总 FLOPs:6·(14P)·D_pre + 2·(14P)·D_inf;小模型:6·P·D_pre + 2·P·D_inf·X。相等:84·D_pre + 28·D_inf = 6·D_pre + 2·D_inf·X ⟹ X = (78·D_pre + 28·D_inf)/(2·D_inf) = 39/R + 14。R = 1:X = 53。大模型 4 倍:24·D_pre + 8·D_inf = 6·D_pre + 2·D_inf·X ⟹ X = 9/R + 4。练习 7(LoRA 的账):Llama-3-8B 的一层里有 q(4096×4096)、k、v(4096×1024)、o(4096×4096)、gate、up(4096×14336)、down(14336×4096)。(a) 只在 q 和 v 上加 r = 8 的 LoRA,32 层一共多少可训练参数?(b) 全参数微调和 LoRA(所有线性层,r = 16)的参数相关显存各约多少?
参考答案
(a) q:8 × (4096 + 4096) = 65,536;v:8 × (4096 + 1024) = 40,960;每层 106,496,32 层约 3.4M。(b) 全参数:8.03B × 16 字节 ≈ 128 GB;LoRA:冻住的 bf16 权重 8.03B × 2 ≈ 16.1 GB,加 41.9M × 16 ≈ 0.67 GB,共约 16.7 GB(代码 05 演示 1)。练习 8(蒸馏的上限):代码 05 里,3,000 个人工标签的学生 98.5%,3,000 个老师标签的学生 94.3%,老师 96.2%。(a) 为什么学生能超过老师?(b) 为什么用老师标签的学生不如老师?(c) 如果老师的错误是系统性的(例如它总把日元餐费判错),用老师标签的学生会怎样?
参考答案
(a) 人工标签没有错;学生的特征足以表达大部分规则,3,000 个例子覆盖了大部分"币种 × 金额 × 人数"的组合,所以它能学到比老师(偶尔不照规则办)更稳定的决策。(b) 老师 4.1% 的标签是错的,学生把一部分错标签当成了规律学进去;再加上学生在需要除法和换算的规则上容量不足。(c) 系统性的错误在标签里是**一致的**,学生会把它当成规律完整地学会——随机错误能被"平均"掉,系统性错误不能。蒸馏前要先检查老师在哪类输入上系统性地错。练习 9(DPO 的 β):解释为什么代码 05 里 β = 0.5 训 10 轮比 β = 2.0 差得多。提示:看 DPO 梯度里的系数 β·σ(−β·Δ)。
参考答案
Δ 是"策略相对参考模型,对 y_w 比 y_l 多偏好了多少"。β 大时,Δ 稍微变大,σ(−β·Δ) 就趋近于 0,梯度消失——策略只要比参考模型略偏向 y_w 就停下,一直贴着参考模型(KL 0.069)。β 小时,要 Δ 很大 σ(−β·Δ) 才变小,于是策略被一直推着离开参考模型,在只有 116 个偏好对时就过拟合了(KL 0.352、准确率 81.2%)。β 越小,"缰绳"越松。练习 10(开放题:为你的智能体选优化路线):你维护一个客服智能体,每天 5 万次对话,错误需要人工补救(每次约 3 美元),规则每季度变一次,现在的错误里 60% 是”不知道某条规定”、40% 是”知道但偶尔漏了步骤”。设计你的优化路线:先做什么、用什么数据、怎样验证、怎样控制成本。
参考答案(之一)
1) 先补知识:把已知的规定写进提示词(或技能文件,第 6 讲的渐进披露);对说不清的部分,收集带标注的失败对话,写一个 rich 反馈函数(引用规定原文、指出哪一步漏了),用 GEPA 类方法优化指令,在没碰过的测试集上报告并跑多个种子。2) 再修可靠性:对"偶尔漏步骤"的 40%,加一个检查清单式的验证步骤(第 8 讲 MAST 的任务目标验证)或对关键决定采样投票;错误每次 3 美元、每天 5 万次,2 个点的准确率每天值 3,000 美元,远超额外的推理成本。3) 暂不微调:规则每季度变,提示词改一句话就生效;如果调用量继续涨、规则趋于稳定,再考虑用优化后程序的正确轨迹蒸馏一个小模型,并保留"同样预算、不微调"的对照。4) 持续评估:每次规则变化后重跑测试集,监控错误的类型是否从"不知道"变成"偶尔漏"。11. 延伸阅读 · Further reading
本讲的阅读材料
- C. Snell et al., Scaling LLM Test-Time Compute Optimally can be More Effective than Scaling Model Parameters, ICLR 2025.
- L. A. Agrawal et al., GEPA: Reflective Prompt Evolution Can Outperform Reinforcement Learning, ICLR 2026.
- D. Soylu et al., Fine-Tuning and Prompt Optimization: Two Great Steps that Work Better Together, EMNLP 2024.
- K. Opsahl-Ong et al., Optimizing Instructions and Demonstrations for Multi-Stage Language Model Programs(MIPROv2),2024.
提示词优化
- C. Yang et al., Large Language Models as Optimizers(OPRO),2023.
- M. Yuksekgonul et al., TextGrad: Automatic “Differentiation” via Text, 2024.
- O. Khattab et al., DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines, ICLR 2024(第 5 讲)。
改权重
- E. Hu et al., LoRA: Low-Rank Adaptation of Large Language Models, ICLR 2022.
- T. Dettmers et al., QLoRA: Efficient Finetuning of Quantized LLMs, NeurIPS 2023.
- G. Hinton et al., Distilling the Knowledge in a Neural Network, 2015.
- L. Ouyang et al., Training Language Models to Follow Instructions with Human Feedback(InstructGPT),NeurIPS 2022.
- R. Rafailov et al., Direct Preference Optimization: Your Language Model is Secretly a Reward Model, NeurIPS 2023.
12. 下一讲预告 · Next lecture
第 10 讲 智能体需要什么数据(10/28,原课 10/26 的客座讲座本讲义不覆盖)。这一讲的每个优化方法都离不开数据:GEPA 要带反馈的例子,蒸馏要老师的标签,BetterTogether 要正确的轨迹,DPO 要偏好对,评估要一个没碰过的测试集。下一讲退一步问:智能体到底需要哪些数据——执行轨迹、示范、反馈;用于优化的数据和用于评估的数据为什么要分开;怎样从上线后的真实交互里搭一个”数据飞轮”(Shankar 的 Data Flywheels for LLM Applications);以及怎样用 LLM 生成和标注数据(Tan et al. 的综述)。配套代码会模拟一个上线的智能体:记录轨迹、从中抽取不同类型的训练数据、比较显式和隐式的反馈信号、跑一个按周转动的数据飞轮,并演示合成数据的去重与过滤。