前面所有的结论,最后都要落成一条字符串。这一条字符串拼错了,前面全白做。 这一节把四种拼法摆在一起看:token 差不太多,但「模型看到几个轮次边界」差很多。
上一节验完了模型,这一节把「怎么问」定下来。 字符串怎么切分、怎么转义,17 课 编程原理 里讲过; 这一节讲的是一串字符里的「结构」怎么被模型读出来——靠的不是标签,是位置和习惯。
一、Llama 2 的模板长什么样
三层结构:最外层 <s> 与 </s>,中间用 [INST] 与 [/INST] 包住用户的话,
系统提示包在 <<SYS>> 与 <</SYS>> 里。
系统提示只在第一个 [INST] 里出现一次。多轮时后面每轮只有 [INST] 用户话 [/INST] 助手话 </s>。
</s> 的位置是关键:它在助手话后面,告诉模型「该我说完了」。
少一个,模型会一直往下编——因为它在等一个结束信号。
[INST] 与 [/INST] 是成对的。少一个收尾,模型分不清哪句是用户说的、哪句该它接。
二、四种拼法,差在哪
朴素拼接(Q:/A:):谁都看得懂,token 最省。
但模型在预训练里没见过这个约定,学的是「模仿这个格式」,不是「接着对话」。
只有 [INST] 不带系统提示:省 token,但丢了角色设定。适合「不需要人设」的场合。
SYS 在首轮:官方写法,124 token。角色、格式约束都在这段里。
再加一句「已知:部位=外立面;预算=二十万;」:144 token,多 20 个。 这 20 个 token 买的是「硬字段不会被截断」。
三、系统提示里该放什么
放三样:角色(你是谁)、格式约束(先结论后依据、不超过多少字)、硬字段(已知的槽位)。 不放三样:具体业务知识(该进检索)、示例对话(占 token 且容易过拟合)、 长篇否定式清单(模型记不住「不要做」的十条)。 硬字段放这里的理由是位置:历史会被截断,系统提示不会被截断。每次都一定读到的位置, 才配放不能丢的东西。
这也解释了一个常见现象:把预算写进用户第一句话,十轮之后它就没了;写进系统提示,三十轮之后它还在。
四、拼错的样子
系统提示写在 [INST] 外面:模型当普通文本读,角色设定基本失效。
每轮都写一遍系统提示:模型以为指令在变,输出风格来回跳,一会儿正式一会儿口语。
少一个 </s>:回答完不收尾,一直往下编,最后被长度上限截断。
共同点:都不报错。所以拼完必须把字符串原样打印出来看一遍,比调参快十倍。
五、本章小结
这一章六节,绕着一个问题转:多轮对话里,模型为什么忘事。 4.1 七条原则要能落成代码;4.2 保多轮与拆单轮要混用;4.3 槽位存字段、摘要存过程、最近几轮存原话。 4.4 追问的价值在于不问已知的;4.5 评测输出必须是分类不是分数; 4.6 系统提示只写一次、槽位句每次都在。
如果只记一句:多轮的问题八成不在模型,在「你给了它什么」。历史拼错、槽位没存、 系统提示重复——这三样修好,7B 的表现能上一个台阶。 下一章换个方向:不再改对话形式,而是往里灌一个行业的全部知识。
六、动手 Demo:同一段对话四种拼法,token 与可解析性各差多少
同一段两轮对话,用四种方式拼:朴素拼接、只有 [INST]、SYS 在首轮、SYS 加槽位句。
量三件事:token 多少、能解析出几个轮次、系统提示在不在。最后把推荐写法原样打出来。
# 官方写法:SYS 只在第一个 [INST] 里
s = (u'<s>[INST] <<SYS>>\n%s\n<</SYS>>\n\n%s [/INST] %s </s>'
u'<s>[INST] %s [/INST] %s </s>' % (SYS,) + tuple(t for _r, t in CONV2))
# 加槽位句:多一行「已知:…」
st = u'已知:部位=外立面;预算=二十万;'
s = (u'<s>[INST] <<SYS>>\n%s\n%s\n<</SYS>>\n\n%s [/INST] %s </s>'
u'<s>[INST] %s [/INST] %s </s>' % (SYS, st) + tuple(t for _r, t in CONV2))
# 数一数:拼完之后有几个 [INST],就是几轮
n_turn = s.count(u'[INST]')
实跑输出:
--------------------------------------------------------------------
同一段对话四种拼法,token 与可解析性各差多少
--------------------------------------------------------------------
① 要拼的这段对话
--------------------------------------------------------------------
| 角色 | 内容 |
|---|---|
| 用户 | 外立面改亮,预算二十万 |
| 助手 | 结论:轮廓加重点。理由:石材适合洗墙。 |
| 用户 | 色温定多少 |
| 助手 | 结论:三千。理由:暖白跟石材搭。 |
两轮四句。同一份内容,拼法不同,模型看到的「边界」就不同。
② 四种拼法
--------------------------------------------------------------------
| 拼法 | token | 轮数 | 系统提示 |
|---|---:|:---:|:---:|
| 朴素拼接 | 72 | 2 | 无 |
| 只有 [INST] | 81 | 2 | 无 |
| SYS 在首轮 | 124 | 2 | 有 |
| SYS + 槽位句 | 144 | 2 | 有 |
token 差得不多,但「能不能解析出轮数」差很多——朴素拼接靠「Q:」这个约定,
模型在预训练里没见过这个约定。
! 这是多轮微调最费钱的一处:格式不对,模型学的是「模仿这个格式」,不是「接着对话」。
③ 两种写法的实际字符串
--------------------------------------------------------------------
┌─ 朴素拼接
│ Q:外立面改亮,预算二十万
│ A:结论:轮廓加重点。理由:石材适合洗墙。
│ Q:色温定多少
│ A:结论:三千。理由:暖白跟石材搭。
└─
┌─ SYS 在首轮
│ <s>[INST] <<SYS>>
│ 你是工程助理,回答先给结论再给依据,不超过六十个字。
│ <</SYS>>
│
│ 外立面改亮,预算二十万 [/INST] 结论:轮廓加重点。理由:石材适合洗墙。 </s><s>[INST] 色…
└─
看第二段:<<SYS>> 只出现一次,位置在第一个 [INST] 里面。
! 常见的错写法是把系统提示塞在 [INST] 外面,或者每轮都写一遍。
前者模型当普通文本读,后者会以为指令每轮都变。
④ 推荐模板:SYS 在首轮 + 槽位句
--------------------------------------------------------------------
┌─ 推荐写法(144 token)
│ <s>[INST] <<SYS>>
│ 你是工程助理,回答先给结论再给依据,不超过六十个字。
│ 已知:部位=外立面;预算=二十万;
│ <</SYS>>
│
│ 外立面改亮,预算二十万 [/INST] 结论:轮廓加重点。理由:石材适合洗墙。 </s><s>[INST] 色…
└─
比官方写法多一句「已知:部位=外立面;预算=二十万;」,20 token。
! 这一句的作用是把硬字段从历史里提出来,放在模型每次都一定读到的位置。
历史可以截断,系统提示不会被截断——这就是它值这几个 token 的原因。
⑤ 真机上的拼法
--------------------------------------------------------------------
[真机代码 · 本沙箱不执行]
| from transformers import AutoTokenizer
|
| tok = AutoTokenizer.from_pretrained('meta-llama/Llama-2-7b-chat-hf')
| ids = tok.apply_chat_template(msgs, tokenize=True, add_generation_prompt=True)
| out = model.generate(ids, max_new_tokens=256)
要 transformers 与 7B 权重,本沙箱都没有,不执行。
apply_chat_template 做的就是上面那几种拼法,它按模型的模板文件来拼。
自己手拼的意义在于:出问题时你知道每一段是哪来的。
! 最后一条经验:模板拼错的表现不是报错,是「答得有点像但总差一点」。
遇到这种情况,先把拼好的字符串原样打印出来看一遍,比调参快十倍。
⑥ 本章小结
--------------------------------------------------------------------
这一章六节,绕着一个问题转:多轮对话里,模型为什么会「忘事」。
| 节 | 一句话 |
|:---:|---|
| 4.1 | 七条原则要能落成七行代码 |
| 4.2 | 保多轮省 token,拆单轮样本多,两种都要 |
| 4.3 | 槽位存字段、摘要存过程、最近几轮存原话 |
| 4.4 | 追问的价值在于不问已知的 |
| 4.5 | 评测输出必须是分类,不是分数 |
| 4.6 | 系统提示只写一次,槽位句每次都在 |
! 如果只记一句:多轮的问题八成不在模型,在「你给了它什么」。
历史拼错、槽位没存、系统提示重复——这三样修好,7B 的表现能上一个台阶。
①两轮四句。内容是同一份,拼法不同,模型看到的「边界」就不同。
②token 从 72 到 144,翻了一倍。
但要注意:翻倍的不是内容的量,是结构的量。系统提示和标记占了七成。
「能解析出几个轮次」这一列全都一样是 2,因为每种拼法都保留了两次用户发言。
但它靠的东西不同:朴素拼接靠 Q: 这个约定,官方写法靠 [INST]。
③把两种字符串原样打出来看,差别一眼就出来:官方写法里 <<SYS>> 只出现一次,
位置在第一个 [INST] 里面。
常见的错写法有两种:把系统提示塞在 [INST] 外面,或者每轮都写一遍。
前者模型当普通文本读,后者会以为指令每轮都在变。
④推荐的写法只比官方多一行:「已知:部位=外立面;预算=二十万;」,多 20 token。
这 20 个 token 的作用是把硬字段从历史里提出来,放在每次都一定读到的位置。
历史会被截断,系统提示不会被截断——这就是它值这 20 个 token 的原因。
⑤apply_chat_template 做的就是上面那几种拼法,它按模型的模板文件来拼。
自己手拼的意义在于:出问题时你知道每一段是哪来的。
最后一句经验:模板拼错的表现不是报错,是「答得有点像但总差一点」。遇到这种情况,
先把拼好的字符串原样打印出来看一遍。
⑥本章小结那张表是六节的压缩。如果只带走一句:多轮的问题八成不在模型,在「你给了它什么」。
30 秒实验
- 把系统提示从一百多字扩到四百字,重跑第②步。token 涨到多少?按一百轮对话算,多花多少?
- 故意把第一个
</s>删掉,看拼出来的字符串变成什么样。再想想:模型会怎么表现? - 把「已知:…」那句话从系统提示移到第一句用户话里,重跑一遍。两者的 token 一样,差别在哪? (提示:想想哪一处会被截断)
常见误区
- 以为模板只是格式。模板决定模型看到几个轮次边界,边界错了,学到的就是「模仿格式」。
- 每轮都带系统提示。多花 token 是小事,输出风格来回跳是大事,而且很难归因到模板上。
- 手拼完不打印。 拼错不报错,只表现成「答得有点像但总差一点」,于是你开始调 temperature、换模型、 加数据——全是白费。
自测三问(点开对答案)
1. 为什么系统提示只写在第一个 `[INST]` 里? —— 因为这是 Llama 2 模板的定义:`<第 4 章到此结束。下一章换个方向:不再改对话形式,而是把一个行业的全部知识灌进去。
- ☐ (本篇完,下一篇 5.1 业务数据分析指导行业大模型定制)
相关笔记
- 造数据集:系统提示只写一次
- 存历史:槽位怎么存
- 17 课 编程原理:字符串与模板
- 🦙 Llama 大模型实践 · 索引