🦙 第3章 微调 Llama 2 大模型
📄 一句话概括

微调的效果上限,在写第一行训练代码之前就定了:数据长什么样,模型就学成什么样。 这一节讲三件事——数据从哪来、标注口径怎么定、转成模板之后 token 涨了多少。 最后那件事最容易被忽略:一段两百字的系统提示配五千条数据,就要在 token 预算里出现五千次。

📝 本章和前面几章的关系

上一章把模型跑起来了,这一章开始改它。改之前先得有材料。 17 课 编程原理 讲的是程序怎么处理数据, 这一节讲的是给人看的数据怎么变成给模型看的数据——中间隔着一层模板,也隔着一层口径。

一、数据从哪来:三条路,各有各的脏

第一条是自有业务数据:工单、客服记录、审批意见、成稿的方案。 这类最贴业务,但量最少、最脏,还牵扯脱敏——客户名字、电话、报价都得先替换掉。 第二条是公开数据集改写。 量大,但文风不对:拿别人的问答训出来的模型,说的是别人的话,业务里的行话一句也不会。 第三条是让强模型造。 最快,一天能造几千条,风险是「自己教自己」——基座不会的东西,造出来的数据也不会, 训完只是把错误说得更流利。

比较稳的做法是这么配:先用几百条真数据把口径定死,再用强模型按这个口径扩量,最后人工抽检百分之十。 几百条真数据的作用不是训练,是定标准。

二、标注:口径比标注员重要

先写手册,再动手。 手册要能回答「遇到某种情况怎么办」,不能只写「回答要简洁」——什么叫简洁,得给例子,得给边界。 判断手册够不够用,有个便宜的办法:让两个标注员各标一百条,比对一致率。 低于八成,说明手册有问题,不是标注员不认真。这时候该改手册,不是换人。 边界案例单独收一册。这些例子以后调提示、调评测、判断模型退化都要用,是整份数据里最值钱的部分。

还有一条:允许标「不确定」。硬猜出来的样本在训练里跟正确样本拿一样的权重, 它会主动教模型一些错的东西;标成不确定的样本可以后面再议。 答案的写法比答案的内容更影响结果。写成「结论 + 依据」两段,模型学得快; 写成一段散文,模型学会的是「说一段话」,而不是「先给结论」。

三、三种格式,最后都要拼成一条字符串

Alpaca 格式最简:instruction、input、output 三个字段,适合单轮任务。 ShareGPT 格式能存整段对话:conversations 是个列表,每项带 from 和 value, from 取值是 human 与 gpt。多轮数据一般用它。 Llama 2 原生格式是一条纯文本:<s>[INST] 指令 [/INST] 回答 </s>, 带系统提示时把 <<SYS>> 那段塞在第一条 [INST] 里。只有这种能直接喂给模型。

前两种是「存」的格式,第三种是「喂」的格式,中间那一步转换最容易出错:多一个空格、少一个 </s>, 模型学到的边界就错位,表现是「回答完不收尾,继续说」。 模板会把 token 撑大。演示里五条数据正文 236 字,拼完 587 token,平均 2.49 倍。 这个倍数直接乘在你的显存和训练时长上。

四、训练之前先体检

五项必查:长度分布、空答案、答案复述提问、重复条目、测试集污染。 长度要看两头的极端值。太长的会被截断,而截断常常把答案切掉一半——模型于是学到「话说一半就停」。 复述是最隐蔽的一种坏数据:答案跟提问高度重合,比如问「图纸改了吗」,答「图纸改了」。 这类样本训下去,模型学会的是复读。

重复和污染要分开看。同一条出现十次,等于给这条加了十倍权重; 测试题混进训练集,评测分数会好看得不像真的,而且你很难发现。

五、动手 Demo:同一批数据三种格式,模板吃掉多少 token

这一节造五条业务问答(亮化工程现场那类),先列出来看,再写成三种格式,然后真的拼成 Llama 2 模板, 数 token。 分词器用的是本课自带的字符级替身,不是真的 sentencepiece——所以绝对数会比真机大, 但膨胀的比例是可信的。

SYS_PROMPT = u'你是一个工程助理,回答先给结论,再给依据,不超过六十个字。'

# 拼成 Llama 2 原生模板
s = u'<s>[INST] <<SYS>>\n%s\n<</SYS>>\n\n%s [/INST] %s </s>' % (SYS_PROMPT, q, a)
n = tokens(s)                       # 字符级分词替身
print(len(q) + len(a), n, n / float(len(q) + len(a)))

# 多轮:每轮一对 [INST] … [/INST] … </s>
parts = []
for q, a in conv:
    parts.append(u'[INST] %s [/INST] %s </s>' % (q, a))
s = u'<s>' + u''.join(parts)

实跑输出:

--------------------------------------------------------------------
同一批数据三种格式,模板吃掉多少 token
--------------------------------------------------------------------


  ① 先看数据长什么样
  --------------------------------------------------------------------
| 序 | 指令 | 回答 |
|:---:|---|---|
| 1 | 把「图纸已更新,请查收」改得更正… | 图纸已完成更新,请查收。 |
| 2 | 这三条待办按紧急程度排一下:报价… | 结论:验收最急,其次报价单,最后图纸… |
| 3 | 用一句话说明为什么这个月进度落后 | 结论:材料到场晚了两周。理由:供应商… |
| 4 | 把这段会议记录提炼成三条结论 | 一、三月报价按新成本重算。二、验收推… |
| 5 | 客户问为什么价格涨了,怎么回 | 结论:因为主要材料涨了 12%。建议… |

  这一节用五条样例演示,真机上通常是几千到几万条。字段就两个:问和答。
  ! 注意每条的答案都带「结论」两个字——这不是文风,是格式约束。
  指令微调最直接的作用就是把输出样式固定下来。

  ② 三种常见格式
  --------------------------------------------------------------------
| 格式 | 形状 | 适合 |
|---|---|---|
| Alpaca | {"instruction": ..., "input": "", "output": ...} | 单轮,字段最简 |
| ShareGPT | {"conversations": [{"from": "human", "value": ...}, ...]} | 多轮,能存整段对话 |
| Llama 2 原生 | <s>[INST] 指令 [/INST] 回答 </s> | 拼成一条纯文本,训练时直接用 |

  三种格式里只有第三种能直接喂给模型——前两种都要先转成模板字符串。

  ③ 转成 Llama 2 的模板
  --------------------------------------------------------------------
| 序 | 正文字数 | 模板后 token | 膨胀比 |
|:---:|---:|---:|---:|
| 1 | 29 | 91 | 3.14 |
| 2 | 66 | 142 | 2.15 |
| 3 | 45 | 117 | 2.60 |
| 4 | 49 | 122 | 2.49 |
| 5 | 47 | 115 | 2.45 |

  五条合计 587 token,正文 236 字,平均膨胀 2.49 倍。
  膨胀来自三处:系统提示每条都要带一遍、[INST] 这类标记、以及中文本身一字一 token。
  ! 系统提示重复是最亏的——如果有五千条数据,那段话就要进 token 预算五千次。
  所以长系统提示在大数据集上是要花钱的。

  ④ 多轮怎么拼
  --------------------------------------------------------------------
  三轮拼成一条:155 个字符,141 token。
  每一轮都以 </s> 收尾,下一轮重新开 [INST]——这个结构告诉模型「该我说完了」。
  ! 多轮最容易错的地方是:只把最后一轮算损失。
  真机上要把前面几轮也放进上下文,但只有助手说的话进损失——这就是第 1.4 节讲的屏蔽。

  ⑤ 数据要先体检
  --------------------------------------------------------------------
| 检查项 | 值 |
|---|---:|
| 条数 | 5 |
| 最短 / 最长 | 91 / 142 token |
| 平均 | 117 token |
| 超过 512 的 | 0 |
| 答案为空 | 0 |

  这几项体检要在训练前跑:太长的截断、太短的手工看、空的直接删。
  ! 最贵的一类错误是「答案其实是提问的复述」——这种数据训下去,模型会学会复读。

先看①那张表:五条数据字段只有两个,问和答。 但每条的答案都以「结论: 」开头——这不是文风,是格式约束。指令微调最直接的作用就是把输出样式固定下来。 ②那张表是三种格式的对照。要点在最后一行:只有 Llama 2 原生格式能直接喂给模型,前两种都得先转。 所以「用什么格式存」和「用什么格式训」是两件事。 ③是这一节的主表。五条数据正文 236 字,拼成模板 587 token,平均膨胀 2.49 倍。 膨胀来自三处:系统提示每条重复一遍、[INST] 这类标记、字符级分词本身不省。 系统提示重复是最亏的那一项。 五千条数据,那段话就进预算五千次——长系统提示在大数据集上是要花钱的,这笔钱按 token 计, 不是按字数计。 ④的多轮拼法值得记住:每一轮都以 </s> 收尾,下一轮重新开 [INST]。 这个结构告诉模型「该我说完了」。少一个 </s>,模型就不知道边界在哪。 ⑤是训练前该跑的体检。 这五项里最贵的一类错是「答案其实是提问的复述」——它不会报错,也不会让损失异常, 只会让模型慢慢学会复读。

30 秒实验

  1. 把 SYS_PROMPT 整段删掉,重跑第③段。膨胀比会掉到多少? 再算一笔:五千条数据下,这个差别是多少 token?
  2. 把第④段的三轮对话改成五轮,看 token 怎么涨。想一想:多轮数据的长度是线性涨,还是更快?
  3. 把第⑤段的截断阈值从 512 改成 128,看有几条被截。被截的那几条,答案还在不在? 这就是「长度体检」要提前做的原因。

常见误区

  1. 以为数据越多越好。 五百条口径一致的数据,胜过五万条口径混乱的。模型学的是模式, 口径乱的样本教给它的是「随便写」。
  2. 让标注员自由发挥。 没有手册就没有一致性,没有一致性就没有可学的模式,训出来的模型会时而给结论、时而写作文, 而且你查不出是哪条数据带坏的。
  3. 拿测试集调参。分数一路涨,上线一路掉,这是最常见也最难自查的一种自欺。 训练集、验证集、测试集要在动手前就切好,切完不许动。
自测三问(点开对答案) 1. 为什么说系统提示重复是「最亏的」一项? —— 因为它对每条数据都要占一遍 token,而且内容完全一样——信息量是零,成本是满的。 五千条数据就是五千遍。把系统提示压短,等于免费省掉一部分训练成本。 2. 多轮对话为什么只把助手说的话算进损失? —— 因为模型要做的是「接着往下说」,用户说的话是输入条件,不是它该学会生成的内容。 如果连用户那几段也算损失,模型会学会自己跟自己对话。 3. 模板膨胀 2.49 倍,这个数对训练有什么实际影响? —— 显存和时长都按 token 数算,所以大约按这个倍数放大。 同一批数据,去掉冗余的系统提示、缩短标记,训练成本能明显下来,而模型学到的内容没变。

数据备齐了,下一节把它喂进去——先解决「怎么把十几 GB 的权重装进显存」这件事。

相关笔记