一.Token
1.定义
Token(词元)是⼤语⾔模型(LLM)理解和处理⽂本的最⼩基本单位。
“我爱⼤模型开发”;了解的⼈获得信息的断句是:我/爱/⼤模型/开发,依次送⼊到我们的⼤脑中去
理解;同样,我们将信息输⼊到⽂本时,也是按照切分后输⼊到模型中的。因此这⼀个⼀个的输⼊就叫token,例如上例中:“我”是⼀个token,“爱”是第⼆个token,“⼤模型”和“开发”都是
token。这个断句的过程在⼈⼯智能领域叫做分词(tokenization)。
当然,对于没有背景知识的⼈上例的断句也可能是:我/爱/⼤/模型/开发;对于⼩孩来说,断句是:
我/爱/⼤/模/型/开/发。不同的⼈断句的⽅式不同,与此相同,市⾯上的⼤模型⼚商切词也会有细微的差别,不同切词的⽅式,同⼀句话切出来的token是不⼀样的。
⼤语⾔模型不看完整的句⼦,⽽是把句⼦拆成⼀个个Token来“理解”和“⽣成”。
2.常见形态
Token的常见的形态:
- ⼀个完整的单词: Hello 可能是 1 个Token。
- 单词的⼀部分: unbelievable 可能会被拆成 un + believe + able (3个Token)。
- 标点符号:逗号、句号、感叹号通常各占 1 个Token。
- 中⽂情况:中⽂⾥,⼀个汉字通常占 1 到 2 个Token(视具体词汇频率⽽定),⽽不是⼀个字对应⼀个Token。
3.估算⼯具
下⾯地址是chatgpt的Token估算⼯具
https://platform.openai.com/tokenizer
开源多模型 Tokenizer ⽹⻚版 同样提供了分词查看能⼒,https://tiktokenizer.vercel.app/
4.为什么需要 Token?
1.数字化让机器能听懂:计算机本质上只能处理数字和数学运算,⽆法直接理解⼈类的⽂本。通过将⽂本切分为 Token 并转换为向量,AI 才能在神经⽹络中进⾏矩阵运算,从⽽捕捉词语之间的语义关系
第⼀步:切分编号(数字化输⼊)——解决“怎么拆”的问题:⽂本不能⼀整坨丢给计算机,必须先
拆成最⼩计算单元(Token),并给每个Token分配⼀个整数ID。⽐如 unhappiness 切成⼦词
["un", "happi", "ness"] ,分别对应ID [432, 891, 256] 。 此时计算机拿到了纯数
字 [432, 891, 256] ,但注意:数字891并不⽐432“更⼤”或“更开⼼”,它只是个冰冷的代
号。如果直接拿这些ID做加减法(432+891),毫⽆意义。第⼆步:离散的ID⽆法表达语义,必须把每个ID映射成⼀串⾼维浮点数(向量)。这些向量在训练中不断调整,语义越近,向量在空间⾥离得越近。模型为 "happy" 分配向量 [0.8, 0.9, -0.1,...] ,为 "glad" 分配向量 [0.79, 0.88, -0.09, ...] ,两者距离很近(都表⽰开⼼)。⽽ "un" 的向量带有“否定”⽅向,当它和 "happy" 组合时,合成向量会把空间坐标推向"sad" 所在的⽅向。
2. 解决“词汇爆炸”问题:
a. 如果按“字⺟”为单位(粒度太细),词表很⼩,英⽂ unhappiness 会被拆成 11 个字符:
u n h a p p i n e s s ,每个字符⼏乎没有独⽴语义(⽐如 u 和 n 拼在⼀起才能表
达“否定”),模型需要花⼤量计算去学习相邻字符的组合规律。⽽且对于⻓⽂本,序列会变
得极⻓,计算量按平⽅增⻓,效率极低。b. 如果按“单词”为单位(粒度太粗),词表会爆炸。如果模型把所有可能出现的英⽂单词都放进词表,词表会⼤到⽆法训练(英语有⼏⼗万单词,还有派⽣词如 unhappiness 、
unhappily 等)。遇到未登录词(如新造词 cryptocurrency ),模型只能当成“未
知”⽽⽆法理解,语义表达能⼒受限
5.输⼊token和输出token
a.输⼊token
你发送给模型的提⽰词所消耗的 token 数量。
- 包含内容:
- ⽤⼾输⼊的⽂本。
- 如果开启了联⽹搜索,搜索引擎返回的相关内容也会被计⼊输⼊ token。
b.输出token
模型⽣成回答所消耗的 token 数量,包含模型返回的完整回复⽂本。
6.为什么输出token贵?
可以从 模型 & 价格 | DeepSeek API Docs
和 chatgpt的价格https://developers.openai.com/api/docs/pricing
致命伤⼀:算⼒闲置(利⽤率极低)
- 输⼊(Prefill):把 1000 个 Token 打包成⼀个⼤矩阵,GPU 的 Tensor Core 满载运⾏,利⽤率接近 100%。虽然算得多,但 0.1 秒就⼲完了,占⽤硬件的时间极短。
- 输出(Decode):每次只算 1 个 Token 的 Q,但为了算这 1 个 Q,得把缓存⾥ 1000 个 K 搬出来做点积。GPU 的计算单元闲置了 95% 的时间,全在等“搬数据”。虽然每次算得极少,但⽣成1000 个输出 Token 需要串⾏跑 1000 步,总共耗时可能是输⼊阶段的 10 倍以上。
类⽐:输⼊像满载的货⻋⾛⾼速(费油但跑得快);输出像市区堵⻋⾛⾛停停(不费油但耗时极
⻓)。出租⻋公司(云⼚商)按“打表时间”收费,堵⻋当然更贵。
致命伤⼆:显存带宽被榨⼲(硬件瓶颈)
我们前⾯讲过,输出阶段瓶颈从“计算速度”变成了“显存搬运速度(Memory Bound)”。
- 输⼊:消耗的是计算单元(算⼒),这是 GPU 的强项。
- 输出:消耗的是显存带宽(HBM 带宽)。为了算⼀个新 Token,要把缓存⾥⼏千个 K 向量从显存“搬”到缓存⾥。显存带宽是 GPU 最稀缺、最贵的资源之⼀(⽐如 H100 带宽 3.35TB/s)。输出阶段疯狂搬运海量数据,直接把带宽打满,导致其他运算都得排队。
致命伤三:KV Cache 占⽤的显存(内存成本)
输⼊:算完就释放显存,不占地⽅。
输出:每⽣成⼀个新 Token,就得把这个 Token 的 K 和 V 永久追加进显存(直到整段对话结束)。⽣成 1000 个 Token,就要占 1000 份显存空间。
对于云⼚商来说,显存 = 钱。⼀块 A100 只有 80GB 显存,如果⼀个⽤⼾⽣成⻓⽂本占了 40GB,那这张卡上能同时跑的⽤⼾数就急剧减少。输出 Token 不仅让你慢,还占着茅坑(显存)不让别的⽤⼾拉屎。
就像你要盖房⼦,都需要1000块砖:
- 处理输⼊:相当于你有1000个⼯⼈,每⼈同时搬⼀块砖,⼀次性搞定(1轮完成)。
- ⽣成输出:相当于你只有1个⼯⼈(因为下⼀个词必须依赖上⼀个词),他得来回搬1000趟。⽽且更惨的是,随着⽣成的Token越多(⽐如搬了500块后),他每次路过材料堆时,还得清点⼀下前⾯已经搬过的所有砖(KV Cache越来越⼤),每⾛⼀步负担都⽐上⼀步重⼀点点。
二.Vocabulary(词表)
1.定义
词表是模型预定义的⼀个映射字典。它包含了模型“认识”的所有词元(Token)。
- 每个词元都有⼀个唯⼀的数字 ID(索引)。
- 当输⼊⽂本时,分词器(Tokenizer)会查阅词表,将句⼦切割成词表中存在的⽚段,并转换为 ID列表输⼊模型;模型输出的 ID 再通过词表映射回⽂本。
在⼤型语⾔模型(LLM)的架构中,Vocabulary(词表)是模型理解⼈类语⾔的“字典”。它定义了模型能“看⻅”和“写出”的所有基本单位。
2.常⻅的词表⼤⼩
3.作⽤
由于⼤语⾔模型(LLM)本质上是数学函数(输⼊数字,输出数字),⽆法直接处理原始⽂本,因此词表就是连接“⼈类语⾔”和“模型数字世界”的桥梁。
4.⽣成词范围
不能⽣成不在词表(Vocabulary)⾥的“Token”,但可以组合出不在词表⾥的“词(Word)”。
⽐如 3个token un + believe + able 组词出来 unbelieveable ,理论上⽣成的所有的内容
都必须来⾃token。
如果真遇到了token没办法表⽰的单字符,⽐如某些Emoji 表情,就会触发字节级回退。
虽然模型可以拼凑出任何字符串,但它⽆法创造出词表索引之外的新编号。 如果模型的词表⼤⼩是50,257(GPT-2 的标准),它永远不可能输出编号为 50,258 的 Token。对于词表⾥有的词,模型输出1 个 Token 即可;对于词表⾥没有的“新词”,模型可能需要输出 5-10 个 Token。这也是为什么处理某些冷⻔专业术语或罕⻅语⾔时,LLM 的推理速度会变慢且更贵。
5.什么是字节级回退(了解)?
字节级回退”(Byte-fallback)是现代⼤模型分词器(如 Llama、ChatGLM 等普遍采⽤的 BPE 分
词器)能够做到“100%不漏掉任何字符”(零 <unk> 标记)的幕后功⾂。
为了让你的知识闭环更完整,我们可以⽤⼀个更具体的硬核拆解,来看看它在底层到底是怎么运作的:
假设我们要让⼤模型处理 😊 这个表情,⽽词表⾥恰好没有它:
1. UTF-8 编码编码:😊 的 UTF-8 编码由 4 个字节组成: 0xF0 0x9F 0x98 0x8A 。
2. 传统 Tokenizer 的绝路:传统分词器⼀看词表⾥没有 😊 ,就会直接抛出⼀个未知字符标记——[UNK] (Unknown)。模型看到 [UNK] 就是两眼⼀抹⿊,根本不知道这后⾯代表的是愤怒、
悲伤还是微笑。
3. Byte-fallback 的拯救:触发回退机制,把这 4 个字节拆开:
- 0xF0 $\rightarrow$ 映射到词表⾥的专⽤ Token: <byte_0xF0>
- 0x9F $\rightarrow$ 映射到词表⾥的专⽤ Token: <byte_0x9F>
- 0x98 $\rightarrow$ 映射到词表⾥的专⽤ Token: <byte_0x98>
- 0x8A $\rightarrow$ 映射到词表⾥的专⽤ Token: <byte_0x8A>
最后,这个表情就被切成了 4 个独⽴的字节 Token 送⼊模型。
为什么说这是⼀个“极其精妙”的设计?
在 Byte-fallback 出现之前,为了不让模型遇到⽣僻字就变“瞎⼦”,早期的 GPT-2 采⽤了纯字节级BPE(Byte-level BPE)。也就是说,它的词表⼲脆完全由最基础的 256 个字节组合⽽成。
但纯字节级有⼀个致命缺点:效率太低。英⽂单词 apple 就要占 5 个 Token,中⽂ 苹果 在 UTF-8
下有 6 个字节,要占 6 个 Token。这会导致⼤模型的上下⽂窗⼝(Context Window)迅速被吃满。
⽽Byte-fallback(字节级回退)则是两者的完美结合(Best of Both Worlds):
- 正常情况下: 词表⾥有⾼频的“苹果”、“apple”、“Hello”,那就直接⽤⼤ Token,保证效率。
- 极端情况下: 遇到冷⻔字、⽕星⽂、罕⻅ Emoji,不报错、不丢弃,⽽是降级回退到字节模式,保证鲁棒性。
三.多轮对话
简单说,模型的如何记住我说过的话?
1.失忆的⼤模型
我们先问下DeepSeek,告诉他我们的名字
然后再打开个新的会话,他其实并不知道我们是谁,因为⼤模型是没有记忆的,就只是进⾏简单的概率预测。
在这里我们会发现它不记得我们叫什么了,好像失忆了一样。(这里我是开启了新的对话)
2.概念
从技术本质上讲,LLM 本⾝是⽆状态的(Stateless)。这意味着模型处理每⼀条指令时,默认并不知道你之前说过什么。
LLM(⼤语⾔模型)的多轮对话是指模型在与⽤⼾交互时,能够“记住”之前的对话内容,并以此为基础理解和⽣成后续回复的能⼒。简单来说,它让 AI 不再是“⼀问⼀答、互不关联”的复读机,⽽是⼀个能承接上下⽂、具备短期记忆的对话者。
我们来看个例⼦,感受下什么是多轮对话?
我们第⼀次问DeepSeek
我叫⼩明,我今年6岁了,你叫什么DeepSeek回复如下:
我们第⼆轮接着问
我叫什么,我明年⼏岁了可以看到他竟然知道我叫⼩明,明年⼏岁了也知道,这种能⼒就是多轮对话带来的。
3.⼯作机制
从技术实现⻆度看,多轮对话的核⼼在于上下⽂管理。⽬前的LLM本质上是“⽆状态”的——它们每
次处理请求时并不天然记住之前的内容。因此,所谓的“多轮对话”,其实是由应⽤层(客⼾端或服
务端)来模拟和维护的。
实现多轮对话的常⻅⽅式是将历史对话记录(History/Context)连同当前的新问题⼀起,重新打包发送给模型。
我们来看个例⼦理解下
4.为什么需要多轮对话
1.上下⽂依赖对话的语境(Context)是理解代词(“它”、“那个”)和隐喻的关键。没有多轮对
话,模型就⽆法建⽴“记忆”,每次互动都是断裂的。
你:⻰是什么 AI:在中国⽂化中,⻰是祥瑞的象征,代表权⼒、尊贵、⼒量和好运。它常与⾬⽔、江河相关,被视为 能够呼⻛唤⾬的神兽。皇帝⾃称“真⻰天⼦”,⻰也是中华⺠族的精神图腾之⼀。 你:它的头像什么 AI:“⻰有九似”的说法:头似驼,⻆似⿅,眼似兔,⽿似⽜,项似蛇,腹似蜃,鳞似鲤,⽖似鹰,掌似 ⻁。2. 复杂问题逐渐搞清晰⼈类的意图往往具有模糊性。⽤⼾往往不会在第⼀时间提供所有必要信息,⽽是倾向于遵循“最省⼒原则”,通过多轮互动逐步探索和完善⾃⼰的需求。
举例说明:想象⼀下看医⽣的场景。你不会⼀进⻔就说“我头痛,持续三天,是胀痛,没有发烧,请开 药”。更⾃然的对话是: 你:“医⽣,我头痛。” AI医⽣:“这种头痛持续多久了?” 你:“⼤概三天了。” AI医⽣:“是哪种性质的痛?胀痛还是刺痛?” 你:“是胀痛。” 通过这种多轮问答,AI才能像真⼈医⽣⼀样,构建出完整的“病情图像”,最终给出精准的建议。3.纠错与对⻬ (Feedback Loop) 模型可能会在第⼀轮出现幻觉或逻辑错误。多轮对话给了⽤⼾“指
正”的机会,让模型在当前会话内通过反馈进⾏即时调整。
第1轮(⽤⼾提问) ⽤⼾:请问鲁迅的《狂⼈⽇记》最早发表在哪本杂志上? 第2轮(AI出现错误) AI:您好,鲁迅的《狂⼈⽇记》最早发表在《新潮》杂志上。 第3轮(⽤⼾纠正) ⽤⼾:不对,应该是《新⻘年》吧?1918年5⽉第4卷第5号。 第4轮(AI承认错误并纠正) AI:⾮常抱歉,您说得对。我刚才记错了。《狂⼈⽇记》最早确实是发表在1918年5⽉的《新⻘年》第 4卷第5号上,⽽不是《新潮》。感谢您的指正,我会注意核实此类⽂学史信息。5.多轮对话常⻅问题和解决思路
上下⽂管理与记忆的瓶颈
问题
核⼼限制:⼤模型(如DeepSeek系列)本质上是“⽆状态”的,其“记忆”完全依赖于输⼊⽂本(即
上下⽂窗⼝)。这个窗⼝有固定的⻓度上限(例如32K tokens),⼀旦对话历史超出限制,最早的信息就会被“遗忘”或截断。
⽐如在⼀个⻓对话中,⽤⼾最初提到“我对花⽣过敏”,但在后续询问蛋糕推荐时,由于对话轮次过
多,这条关键信息可能已被移出上下⽂窗⼝,导致模型推荐了含有花⽣的蛋糕配⽅。
⽤⼾:我对花⽣过敏,以后推荐⻝物时请避开花⽣。 助⼿:明⽩了,我会记住您对花⽣过敏。之后涉及⻝品的建议都会避开花⽣及相关制品。 ⽤⼾:最近想学做⾯包,有什么适合新⼿的配⽅吗? 助⼿:当然。推荐基础⽩⾯包:⾼筋⾯粉、⽔、酵⺟、盐、糖、⻩油。没有花⽣成分,请放⼼。 ⽤⼾:谢谢。那烤箱温度怎么控制? 助⼿:⼀般⽩⾯包⽤180-190℃,烤25-30分钟。建议买个烤箱温度计更准确。 ⽤⼾:我家的烤箱是30升的,需要调整时间吗? 助⼿:30升属于中⼩型,受热可能更快,建议先设25分钟,观察上⾊情况。 ⽤⼾:好的。还想问⼀下,戚⻛蛋糕总是塌陷怎么办? 助⼿:常⻅原因:蛋⽩打发不⾜、翻拌消泡、或出炉没震热⽓倒扣。建议蛋⽩打到⼲性发泡,翻拌⼿法 轻快。 ⽤⼾:原来如此。那有没有适合下午茶的蛋糕配⽅?简单⼀点的。 助⼿:推荐花⽣酱玛芬蛋糕:低筋⾯粉、花⽣酱、鸡蛋、⽜奶、糖、泡打粉。⾮常⾹浓,做法简单。注 意:花⽣过敏者不宜⻝⽤。 ⽤⼾:???我⼀开始就说了我对花⽣过敏,你怎么还推荐含花⽣的配⽅?a.解决思路
选择性记忆,⽽⾮全记住
b.解决技术
1. 对话摘要:超过⻓度后,让模型把旧对话压缩成简短摘要。
⽐如对话过程中
助⼿(在第10轮后检测到⻓度接近上限): “我们的对话已经较⻓。为了不丢失您之前说的‘花⽣过敏’等重要信息,我将把前⾯的内容压缩成如下 摘要,之后基于这个摘要继续回答您的问题—— 摘要:⽤⼾对花⽣过敏。⽤⼾是烘焙新⼿,已尝试⽩⾯包,烤箱为30升。⽤⼾询问过戚⻛蛋糕塌陷的原 因,正在寻找简单的下午茶蛋糕配⽅(不能含花⽣)。建议的沟通流程可以参考下⾯的过程
2. 滑动窗⼝:只保留最近N轮对话,这个⽐较简单,模型就只认⼏条消息,超过了我就当没有
3. RAG技术:后⾯我们会详细介绍,把历史存⼊向量数据库,按需检索相关⽚段,⽽⾮全量输⼊。核⼼思路是超过上下⽂的时候把之前相关的内容添加到下⼀次沟通。
三者对比
| 技术 | 一句话总结 | 核心逻辑 |
|---|---|---|
| 对话摘要 | 压缩旧内容成摘要带着走 | 记住总结,丢掉细节 |
| 滑动窗口 | 只留最近N轮,远的直接扔 | 只记最近,不管以前 |
| RAG | 全部存起来,按需查相关片段 | 全记住,用的时候翻档案 |
记忆强度:滑动窗口(最健忘)< 对话摘要(中等)< RAG(记得最全)
推理效率与成本
问题
每轮对话都要重新处理整个历史,Token消耗和延迟随轮次线性增⻓,成本会快速上升。⽐如⼀个20轮的客服对话,每次提问模型都要重新“阅读”全部20轮内容,即使⼤部分是闲聊。
a.解决思路
减少重复计算
b.解决技术
1. 提⽰词缓存机制(KV Cache):缓存已计算的历史的计算结果,每轮只算新内容(⼏乎所有⽣产级服务都⽤)。
[“今”,“天”,“天”,“⽓”] ⽣成第 1 个字“真”:把前⾯4个token的计算信息存⼊缓存区。 ⽣成第 2 个字“不”:从缓存中取出之前的5个计算结果,和新算出的拼接在⼀起。 ⽣成第 3 个字“错”: 从缓存中取出之前的6个计算结果,和新算出的拼接在⼀起。2. 上下⽂剪枝:移除历史中对当前⽣成⽆⽤的Token。
剪枝前:
[1] ⼩明: 帮我推荐⼏本好看的科幻⼩说,要短篇的。 [2] AI: 《你⼀⽣的故事》《呼吸》《献给阿尔吉侬的花束》都不错〜 [3] ⼩明: 谢谢!对了,你吃午饭了吗? [4] AI: 我不吃饭,不过你可以去⻝堂看看今天有啥好吃的 😄 [5] ⼩明: 好吧。我现在要写物理作业,是关于“动量守恒”的。 [6] AI: 好的,动量守恒的关键是系统不受外⼒或合外⼒为零。你有具体题⽬吗? [7] ⼩明: 有!⼀题是“两个⼩球碰撞,⼀个静⽌,⼀个运动,碰后速度怎么算?” [8] AI: 弹性碰撞的话,可以⽤公式 v1' = (m1-m2)/(m1+m2) * v1 ...剪枝后:
[5] ⼩明: 好吧。我现在要写物理作业,是关于“动量守恒”的。 [6] AI: 好的,动量守恒的关键是系统不受外⼒或合外⼒为零。你有具体题⽬吗? [7] ⼩明: 有!⼀题是“两个⼩球碰撞,⼀个静⽌,⼀个运动,碰后速度怎么算?” [8] AI: 弹性碰撞的话,可以⽤公式 v1' = (m1-m2)/(m1+m2) * v1 ...模型能⼒局限
问题
上下⽂遗忘:当上下⽂较⻓时,模型很可能忘记前⾯设定的变量、上下⽂或约束,导致后续步骤不⼀致。例如第⼀轮设定“请始终⽤中⽂回答”,过了20轮你随⼝问“What's the weather?”,模型可能
直接切到英⽂。中间遗忘现象:模型更容易记住和利⽤位于上下⽂开头和结尾的信息,⽽中间部分的内容常被忽略。在"⼤海捞针"测试中,当关键信息藏在⼤量⽆关信息中间时,模型很难找到它。
论⽂地址https://arxiv.org/pdf/2307.03172
a.解决思路
增强模型+优化交互
b.解决技术
1. 训练更⻓窗⼝的模型(如百万Token级别),从根上解决:例如DeepSeek,Gemini的token上下⽂已经到了百万token。
2. 显式状态跟踪:要求模型维护⼀个“状态信息”,记录关键信息。
原理:不让LLM“记住”状态,⽽是由系统维护状态,LLM只负责更新。
实现⽅式:
- 系统维护⼀个独⽴的状态存储(变量、数据库、缓存)
- 每轮对话时,将“当前状态 + ⽤⼾输⼊”⼀起发给LLM
- LLM输出“状态更新指令”,系统据此修改状态
- ⽣成回答时,系统将“当前状态”作为上下⽂的⼀部分提供给LLM
系统维护的状态(始终独⽴存储): - ⽬的地:野⽣动物园 - 出发时间:周⽇早上⼋点 - 参与⼈员:妈妈、孩⼦、⼩明 - 交通⼯具:开⻋ - 带什么:多带⽔和零⻝ 第7轮时,系统将上述状态注⼊LLM的上下⽂: “当前计划状态如下:[状态详情] ⽤⼾问:咱们⼏点出发来着? 请基于状态回答问题。” LLM回答: “根据当前计划,出发时间是周⽇早上⼋点。”四.提示词
1.⽤户提⽰词
想让⼤模型⼲什么事情?
⽤⼾提⽰词 (User Prompt)就是您(⽤户)直接发给模型的那句话、问题或指令。它是模型开始⼯作的起点。⽐如下⾯问模型的“北京的天⽓怎么样”。
2.系统提示词
如何⼀次性给模型设置好⼈设,不⽤每次设置?
a.概念
系统提⽰词(System Prompt)是在与⼤语⾔模型对话开始时,预先设定的顶层指令。它定义了模型在整个对话中的⾝份、⾏为边界、回复⻛格和遵循的规则。
从技术架构上看,系统提⽰词位于对话的最前端,由开发者/应⽤⽅来设置,贯穿整个对话,⽬的是控制模型⾏为边界。我们通过交互逻辑来看下
b.为什么需要系统提示词
1. 定义⻆⾊与⾏为边界
系统提示词为 AI 设定了明确的⾝份和职责范围。
- ⻆⾊塑造:它可以告诉 AI “你是⼀位乐于助⼈的客服专家”或“你是⼀个严谨的代码审查员”,从⽽让 AI 的回答⻛格与特定场景匹配。
- 划定边界:它能清晰地界定 AI 能做什么、不能做什么。例如,⼀个技术⽀持助⼿的系统提⽰词会规定它只回答产品相关问题,对于超出范围的问题则礼貌地拒绝或引导⽤⼾。
2.稳定模型⾏为
⼤模型本质上是概率模型,每次回答都有随机性。系统提⽰词相当于“锚点”,让模型在不同会话中
保持⼀致的⻛格和边界。
3. 确保安全与合规
这是系统提⽰词最核⼼的功能之⼀。开发者通过它来内置“安全护栏”,防⽌ AI ⽣成有害、偏⻅或不合规的内容。
- ⻛险规避:它会明确禁⽌ AI ⽣成涉及暴⼒、仇恨⾔论或⾮法活动的建议,⼒求让 AI 的输出“有益且⽆害”。
- 合规性:在某些应⽤中,系统提示词会强制 AI 遵守特定的法律或伦理规范,例如不泄露⽤⼾隐私信息。
4. 弥补知识短板与提供上下⽂
知识更新:AI 的知识有截⽌⽇期。通过系统提⽰词,开发者可以⼿动“热修复”⼀些关键事实。例
如,直接写明“DeepSeek V4是最新的模型”,以弥补模型训练数据截⽌后发⽣的重要事件。
案例
- Claude:Anthropic公司推出的旗舰级⼤语⾔模型(AI助⼿)的品牌名称,类似于 OpenAI 的ChatGPT、Google 的 Gemini。Opus 系列和Sonnet 系列的模型在代码开发领域是处于领先地位。
Claude 是为数不多的公开系统提⽰词的⼤模型,下⾯代码块是Claude Opus 4.6的系统提⽰词,可以看到系统提⽰词给了很多设定
1. 产品信息确认:明确⾃⼰⾝份
2. 安全和拒绝机制:⽐如⼉童保护、恶意代码、危险物质
3. 法律和财务建议:不提供
4. 公正与政治话题处理:政治意⻅的处理⽅式
5. 语⽓要求:温暖
具体地址App unavailable in region | Claude by Anthropic
五.聊天模板(Chat Template)
1.Chat Template是什么
我们可以把 ChatTemplate 理解为⼤语⾔模型(LLM)进⾏标准化对话的“格式蓝图”。它的核⼼
价值在于解决兼容性问题:不同的 LLM 在训练时使⽤的对话格式各不相同, ChatTemplate 确保了输⼊给模型的对话格式与它训练时的格式⼀致,从⽽显著提升模型表现的稳定性,并因此成为 LLM 应⽤开发中⼀个重要的标准化组件。
简单来说,原始对话数据是:
System Prompt:定义⻆⾊(如“你是⼀个AI助⼿”)。
User Prompt:⽤⼾的问题。(什么是AI)
Assistant Response:模型需要填写的回答。(AI 是....)
[ {"role": "system", "content": "你是⼀个AI助⼿"}, {"role": "user", "content": "什么是AI"}, {"role": "assistant", "content": "AI是..."} ]经过 Chat Template 处理后,会变成类似:
<|im_start|>system 你是⼀个AI助⼿<|im_end|> <|im_start|>user 什么是AI<|im_end|> <|im_start|>assistant AI是....<|im_end|>如果没有 ChatTemplate,您直接拿原始的 JSON 列表喂给模型,模型会把它当成普通⽂本去理解,⽆法区分“系统指令”和“⽤⼾历史提问”,回答就会完全跑偏。
2.常⻅的模型模板
三种主流大模型的对话模板(Chat Template)格式对比:
Qwen(通义千问)
| 角色 | 格式 |
|---|---|
| system(系统提示) | <im_start>system\n{系统提示}\<im_end>\n<br/> |
| user(用户) | <im_start>user\nHello!\<im_end>\n<br/> |
| assistant(助手) | <im_start>assistant\nHi! How can I help?\<im_end> |
地址:https://huggingface.co/Qwen/Qwen3.6-27B/blob/main/chat_template.jinja
Llama 3
| 角色 | 格式 |
|---|---|
| 文本开始标记 | <begin_of_text> |
| system(系统提示) | <start_header_id>system<end_header_id>\n\n{系统提示}<eot_id> |
| user(用户) | <start_header_id>user<end_header_id>\n\nHello!<eot_id> |
| assistant(助手) | <start_header_id>assistant<end_header_id>\n\n |
地址:https://www.llama.com/docs/model-cards-and-prompt-formats/meta-llama-3/
Gemma
| 角色 | 格式 |
|---|---|
| 文本开始标记 | <bos> |
| turn 1 - system | <turn>system\n你是一个安全专家。<turn> |
| turn 2 - user | <turn>user\n如何检测代码中的内存泄露?<turn> |
| turn 3 - model | <turn>model\n检测内存泄露通常有以下几种方法...<turn> |
地址:https://huggingface.co/google/gemma-4-31B-it/tree/main
对比总结
| 模型 | 系统提示标记 | 用户标记 | 助手标记 | 特殊分隔符 |
|---|---|---|---|---|
| Qwen | <im_start>system | <im_start>user | <im_start>assistant | <im_end> |
| Llama 3 | <start_header_id>system | <start_header_id>user | <start_header_id>assistant | <eot_id> |
| Gemma | <turn>system | <turn>user | <turn>model | <turn>+<bos> |
通俗理解
这些格式本质上都是把对话“翻译”成模型能看懂的语言,相当于给对话加上“角色标签”:
Qwen用
<im_start>和<im_end>包住每一轮对话Llama 3用
<start_header_id>标记说话人,<eot_id>表示该轮结束Gemma用
<turn>来切分每一轮对话轮次
实际开发中你不需要手写这些格式,直接用tokenizer.apply_chat_template()会自动生成,但这些格式在调试 prompt、看日志、手动构造输入时很有用。
3.为什么需要Chat Template
1. 通⽤性更加友好
假如你⼿动拼接,脑⼦⾥想的是:
def build_prompt(messages): prompt = "" # 初始化空字符串,用于拼接最终提示词 for msg in messages: # 遍历每条对话消息 if msg["role"] == "user": # 如果角色是"用户" prompt += f"<|im_start|>user\n{msg['content']}<|im_end|>\n" # 拼接成:<|im_start|>user\n用户说的话<|im_end|>\n elif msg["role"] == "assistant": # 如果角色是"助手" prompt += f"<|im_start|>assistant\n{msg['content']}<|im_end|>\n" # 拼接成:<|im_start|>assistant\n助手说的话<|im_end|>\n return prompt # 返回拼接好的完整提示词这段代码有什么问题?
代码本⾝能完美运⾏,也能处理 1 条或 100 条消息。但是,“Qwen 模型⽤ <|im_start|> ”这个
关键知识,被硬⽣⽣地写死在了 Python 代码的 if 条件⾥。
- 如果明天换成了 Llama 模型,你必须修改这段 Python 代码,把⾥⾯的字符串全换成<|start_header_id|> 。
- 如果同时维护 10 个模型,你的 Python ⽂件⾥就会有⼀⼤堆 if model_name == "llama"的脏逻辑。
⽽ Jinja 的做法是,把“怎么拼”的规则从 Python 代码⾥抽出来,单独存成⼀个⽂本⽂件(模板):
{% for message in messages %} {% if message.role == "user" %}<|im_start|>user\n{{ message.content }} <|im_end|>\n {% elif message.role == "assistant" %}<|im_start|>assistant\n{{ message.content }}<|im_end|>\n {% endif %} {% endfor %}这样做的好处是什么?
现在,你的 Python 代码⾥只剩下⼀个通⽤的渲染调⽤(如template.render(messages=messages) ),完全不需要知道具体的标签名字。
- 换 Llama 模型时,你不⽤改 Python 代码,只需要把模板⽂件⾥的 <|im_start|> 全局替换成<|start_header_id|> 即可。
- 模型作者发布新模型时,直接附带这个模板⽂件,你的 Python 代码⼀⾏都不⽤动。
2. 能根据“有没有某样东西”做决定
除了条数不确定,模版还得处理“有没有”的问题。⽐如:
- 如果⽤⼾传了“⼯具调⽤(tools)”参数,就在系统提⽰词⾥塞⼀⼤段 JSON 说明;
- 如果没传,就只发普通的系统提⽰词。
普通拼接做不到“动态删减⼤段⽂字”,只能提前全写死。⽽ Jinja ⽀持 {% if tools %} 条件判
断,有就插⼊,没有就跳过,⾮常灵活。
3. 能防⽌⽤⼾输⼊“捣乱”
⽤⼾可能在聊天内容⾥打上 {{ 或 {% 这类符号。如果你⽤ Python 直接拼接字符串,程序很可能误
以为这是变量⽽报错。
Jinja 默认会把⽤⼾输⼊的内容当成纯⽂本进⾏安全转义,确保⽤⼾乱写特殊符号也不会破坏整个提⽰词的格式。
聊天模板底层⼀般使⽤Jinja模板引擎来编写和解析,感兴趣的同学可以进⼀步研究下
Pallets
补充:Jinja 把“对话格式怎么拼”这件事从 Python 代码里剥离出来,变成可替换的配置文件,让一套代码能适配所有模型。