LLM 学习系列(1 / 5)
1. 概念总览:文字怎样进入模型
LLM(Large Language Model,大语言模型)的神经网络不直接处理字符串。输入文本先由 Tokenizer 转换为整数编号,称为 Token ID;模型生成新的 ID,再由 Tokenizer 转回文字。
本系列主要讨论生成文本的 LLM。先按「使用一个已经训练好的模型」理解前 3 篇,再看第 4 篇如何训练、第 5 篇如何发布。
在文本编码层,三个核心概念的边界是:
| 概念 | 定义 |
|---|---|
| Token | Tokenizer 切分文本后得到的离散单位 |
| Vocabulary | Token 与整数 ID 之间的有限映射表 |
| Tokenizer | 负责文本规范化、切分、ID 映射以及解码的完整系统 |
五篇共用的例子
输入
小猫跑了一天,它很,让模型接着生成累。本篇追踪的是:这段文字如何变成 ID,生成的 ID 又如何变回文字。为了方便手算,假设一个教学用 Tokenizer 将输入切成
小猫 · 跑 · 了 · 一天 · , · 它 · 很,对应 ID 为[10, 11, 12, 13, 14, 15, 16];另外约定累 = 17、。 = 18、<BOS> = 1、<EOS> = 2。这套切分和编号是虚构的,后续各篇保持一致,不代表任何真实模型。 主线先演示纯文本续写;对话模板可能额外加入角色和边界标记。
累是选定的演示续写,并非现实中唯一合理的答案。
2. Token(词元):模型处理的文本单位
Token 是 LLM 处理文本时使用的基本离散单位。「离散」表示它来自有限的类别集合,而不是一个可以任意连续变化的数值。
Token 不一定是一个完整的词,也不一定对应一个字符。它可能是:
- 一个完整单词,如
apple - 单词的一部分,如
un、believ、able - 一个汉字或连续的多个汉字
- 一个字符的一部分字节,此时单个 Token 未必能独立显示成文字
- 标点、空格或换行
- 特殊标记,如
<BOS>、<EOS>
Note
Token 的边界由 Tokenizer 的规范化、预切分、词表和切分规则共同决定。不同 Tokenizer 对同一段文本的切分结果可能不同,因此 Token 数不是文本自身的固定属性。
Tip
「1 Token 约等于 0.75 个英文单词」只是针对特定语料和 Tokenizer 的粗略统计,不能直接套用于中文、代码或特殊符号。准确的 Token 数必须使用对应模型的 Tokenizer 计算。
2.1 Token Sequence(Token 序列)
Token 序列是多个 Token 按照它们在文本中的顺序组成的有限有序序列。
沿用开头的例子:
在这个例子中:
| 对象 | 内容 |
|---|---|
| Token 序列 | ["小猫", "跑", "了", "一天", ",", "它", "很"] |
| Token ID 序列 | [10, 11, 12, 13, 14, 15, 16] |
| 序列长度 | 7,暂不计特殊 Token |
Token 序列具有以下性质:
- 有序性:交换 Token 的位置通常会改变文本含义。
- 有限长度:输入与输出序列受模型上下文窗口和生成长度限制。
- 可包含特殊 Token:例如
<BOS>、<EOS>、<PAD>以及对话角色标记。 - 依赖 Tokenizer:同一文本经过不同 Tokenizer,可能得到不同的 Token 序列及序列长度。
上下文窗口(Context Window) 是模型一次能利用的 Token 容量;在常见的 Decoder-only 文本模型中,输入和已生成输出共同占用它。可对照术语库中的 Context Window。
模型接口通常接收 Token ID 张量(Tensor,即多维数值数组)。单条序列可表示为长度为 的数组;多条一起处理时,常组成形状为 的数组。 是 Batch Size(批量大小,一起处理的序列数), 是对齐后的长度。例如,一条含 7 个 Token 的输入可以表示为形状 [1, 7] 的数组。
在自回归生成过程中,新生成的 Token ID 会不断追加到已有序列:
Note
工程代码经常用
tokens同时指 Token 序列和 Token ID 序列,但两者并不相同:前者是符号序列,后者是词表中对应整数编号的序列。模型的 Embedding 层读取的是 Token ID 序列。
2.2 Special Tokens(特殊 Token)
特殊 Token 是由 Tokenizer 和模型共同约定、用于表达序列结构或控制信息的 Token。 它们存在于词表中并拥有固定的 Token ID,但通常不直接对应普通文本片段。
BOS(Beginning of Sequence,序列开始标记) 表示一个 Token 序列的起点。常见形式包括 <BOS> 和 <s>:
BOS 为模型提供明确的起始上下文,但并非所有模型都要求显式添加 BOS。
EOS(End of Sequence,序列结束标记) 表示一个 Token 序列的终点。常见形式包括 <EOS>、</s> 和 <|endoftext|>:
在自回归训练中,模型学习在完整序列末尾预测 EOS;推理时,如果模型生成的 Token ID 等于配置的 eos_token_id,生成框架通常会停止解码。
BOS 和 EOS 的具体文本形式与 Token ID 均由模型配套的 Tokenizer 配置决定。例如,一个模型可能将 BOS 映射为 ID 1、EOS 映射为 ID 2,另一个模型则可能使用完全不同的表示。
PAD(Padding,填充标记) 常用于把一批不同长度的序列补到相同长度。框架通过掩码忽略填充位置,使它们不充当正常上下文或训练目标。
Chat Template(对话模板) 是把「用户说了什么、助手回答什么」排成模型所需输入格式的规则。例如,模板可能把用户消息包在角色标记之间,并在末尾加入助手开始作答的标记。这些格式也是模型学习协议的一部分。
例子推进 · 给序列标出边界
若教学模型要求 BOS,输入会是
[1, 10, 11, 12, 13, 14, 15, 16],长度从 7 变成 8。假设模型依次生成[17, 18, 2],它们表示累 · 。 · <EOS>。
。是普通标点,通常不会自动终止生成;<EOS>才是此处约定的结束标记。输出解码时一般隐藏它,所以读者看到的是累。。
Warning
EOS 与 Stop Sequence 不相同。EOS 是词表中的一个特殊 Token,由模型直接预测;Stop Sequence 是推理系统在 Token 或输出文本中检测的终止模式,可以由一个或多个普通 Token 组成。
Important
Chat Template 可能自动插入 BOS、EOS、消息边界和角色标记。手动添加后再调用自动插入逻辑,可能产生重复的特殊 Token,导致输入格式偏离模型训练时使用的格式。
3. Vocabulary(词表):给 Token 编号
词表是 Token 与整数 ID 之间的有限映射表。
教学词表的一部分如下:
| Token | Token ID |
|---|---|
<BOS> | 1 |
<EOS> | 2 |
小猫 | 10 |
很 | 16 |
累 | 17 |
ID 是地址,不是语义大小。 17 比 16 大,不表示 累 比 很 更强、更重要或更相似。下一篇的 Embedding 层会根据地址查出可计算的向量。
如果一个模型的词表大小为 100,000,表示它能够直接识别 100,000 个不同的 Token,并不表示它只能处理 100,000 个单词。词表中没有完整收录的单词,可以拆成多个已知 Token。
词表大小需要在模型参数量与序列长度之间权衡:
- 词表较大:文本通常会被编码为更短的 Token 序列,但嵌入层和输出层的参数量更大。
- 词表较小:嵌入层和输出层更小,但文本会被切得更碎,序列更长,训练和推理成本随之增加。
- 语言覆盖不均衡:某些语言可能需要更多 Token 表达相同信息,导致可用上下文减少、计算成本增加。
4. Tokenizer(词元化器):编码与解码
Tokenizer 是在原始文本与 Token ID 序列之间进行转换的完整系统。
它通常提供两个方向的转换:
4.1 Encode:从文字到 ID
Tokenizer 的编码过程通常包含以下步骤:
- 文本规范化:统一或保留大小写、Unicode 表示等文本形式。
- 预切分:按照空格、标点或语言规则对文本进行初步划分。
- 子词切分:把文本切成词或更小片段,例如
unbelievable可以示意为un · believ · able;常用 BPE 算法,见第 5 节。 - ID 映射:通过词表把每个 Token 映射为整数 ID。
- 特殊 Token 处理:按模型协议添加
<BOS>、<EOS>等标记。
并非每个 Tokenizer 都会执行完全相同的步骤。例如,有些 Tokenizer 会执行有损的文本规范化,有些则直接以原始字节为基础编码。因此,编码与解码不一定是严格可逆的。
Unicode 为字符定义码点(可理解为字符编号),UTF-8 是把这些码点存成字节的一种编码方式,字节(Byte) 由 8 个二进制位组成。一个汉字通常占多个 UTF-8 字节,所以「按字符切」和「按字节切」不是一回事。
例子推进 · Tokenizer 到这里就完成了输入工作
对
小猫跑了一天,它很,假设不修改原文,按约定切分并映射后得到[10, 11, 12, 13, 14, 15, 16],再按需添加 BOS。Tokenizer 负责表示转换;判断后面更可能是累还是饿,是语言模型的任务。
4.2 Detokenization(反词元化)
Detokenization 是 Tokenizer 将 Token ID 序列转换为可读文本的过程,也常被称为「反分词」。
Detokenization 通常包含:
- 根据词表将 Token ID 映射回 Token。
- 按算法规则拼接子词、字符或字节。
- 恢复空格和 Unicode 字符。
- 按配置保留或移除
<BOS>、<EOS>等特殊 Token。
4.2.1 为什么解码可能不可逆
如果 Tokenizer 在切分前通过规范化函数 修改了输入文本,那么编码阶段实际处理的是 ,而不是原始文本 。若其余环节可逆,且没有未知 Token 替换、特殊 Token 增删或额外文本清理,往返结果才可以写成:
不一定能保证:
原因不是 Detokenization 「解码错误」,而是规范化阶段已经丢弃了部分信息。多个不同的原始字符串可能得到相同的规范化结果,解码时无法判断输入最初是哪一种形式。
常见的不可逆情况包括:
| 可能导致不可逆的处理 | 处理前表示 | 处理后表示 | 丢失的信息 |
|---|---|---|---|
| 转换为小写 | Hello | hello | 原始大小写 |
| 合并连续空格 | hello world | hello world | 空格数量 |
| 删除首尾空格 | ␠hello␠ | hello | 首尾空格 |
| Unicode 兼容性规范化 | A | A | 全角与半角形式 |
| Unicode 组合规范化 | e + U+0301 | é | 原始码点序列 |
| 跳过特殊 Token | <BOS>hello<EOS> | hello | 特殊 Token |
表中的 ␠ 表示一个空格。Unicode 组合规范化的例子在视觉上可能完全相同,但底层码点序列不同。因此,「恢复出相同显示文本」不等于「恢复出相同字节串」。
这里讨论的是 Decode(Encode(x)) 的输入往返。模型生成的是新的 ID 序列;把它解码成 累。,当然不要求恢复为输入的 小猫跑了一天,它很。
Important
原始字节级往返要求输入信息完整保留,并按配套规则处理特殊 Token 和文本清理。例如,编码时自动附加的 BOS/EOS,解码时可能需要移除才能还原输入;但删除原输入本身包含的标记又可能丢失信息。Byte-level Tokenizer 可以保留 UTF-8 字节,但「Byte-level」本身并不保证完整处理流程可逆。
Note
流式生成时不能假设每个 Token 都能独立解码成合法文本。一个 Unicode 字符可能跨越多个 Byte-level Token,工程实现通常需要缓存不完整的字节序列,再增量输出完整字符。
5. 切分规则怎样得到:算法与 Tokenizer 训练
Tokenizer 的「训练」通常是从语料(文本集合)中统计出词表和切分规则。它与第 4 篇通过反向传播调整神经网络权重的「模型训练」是两件事:通常先确定 Tokenizer,再用它编码语料来训练模型。
常见的 Tokenizer 算法或基础粒度方案包括:
| 类型 | 核心方法 | 典型应用 |
|---|---|---|
| BPE | 从较小的基础符号集合开始,反复合并高频相邻单元 | LLaMA 1/2、Mistral |
| Byte-level BPE | 以 UTF-8 字节为基础单元,再执行 BPE 合并 | GPT-2、RoBERTa、OpenAI GPT 系列 |
| WordPiece | 构建子词词表,编码时通常采用最长匹配 | BERT、DistilBERT |
| Unigram Language Model | 从较大的候选词表开始,根据概率模型逐步裁剪子词 | T5、ALBERT、XLNet |
| Character-level | 直接以字符作为基本单位 | CANINE 等模型 |
| Byte-level | 直接以字节作为模型输入的基本单位 | ByT5 等模型 |
这些方法的构造方向不同:
Byte-level 若覆盖全部 256 种字节值,就不因新词而缺少基础表示;Character-level 能否覆盖新字符取决于字符集合,有限字符表仍可能遇到未知字符。无法表示的输入有时会被替换为 UNK(Unknown,未知标记),原始信息因此丢失。更细的基础粒度通常也意味着更长的序列。
5.1 BPE:训练时学合并,使用时按规则切分
以 BPE(Byte Pair Encoding,字节对编码) 为例:训练时,它从基础符号集合开始,每轮统计语料中的相邻单元对,合并频率最高的一对,直到词表达到目标大小或满足停止条件。
图中用字符作基础单元,只演示规则如何作用于贯穿例子。不能靠这一句话断言哪些相邻对在整个语料中最高频;真实规则还受预切分等约束。使用已训练好的 BPE 编码新输入时,按已有合并优先级执行,不会为每条请求重新训练词表。
经过大量合并后,常见词或词根可能成为一个 Token,罕见词和新词仍可以由多个子词组合表示:
- 相比完整单词词表,BPE 的词表规模更可控,也能处理新词。
- 相比单字符词表,BPE 通常可以生成更短的 Token 序列。
Note
BPE 最初是一种数据压缩算法。用于 NLP 时,初始单元可以是字符,也可以是字节;Byte-level BPE 明确以 UTF-8 字节为基础单元,因此原则上可以编码任意 Unicode 文本,通常不需要
<UNK>。
Warning
「BPE 就是 Tokenizer」并不严谨。BPE 是学习和应用子词合并规则的算法;完整的 Tokenizer 还包括文本规范化、预切分、特殊 Token 处理和 Token ID 映射。
6. 常用工程实现:算法、工具与产物
算法描述「如何切分」,工程实现负责训练或加载词表、保存规则以及执行编码和解码。
| 工具 | 类型 | 支持或用途 |
|---|---|---|
| SentencePiece | 训练与运行工具 | 支持 BPE 和 Unigram;可直接处理中文、日文等没有天然空格边界的文本 |
| Hugging Face Tokenizers | 高性能实现库 | 使用 Rust 实现,支持 BPE、Byte-level BPE、WordPiece、Unigram 等算法 |
| tiktoken | 高性能实现库 | 主要用于 OpenAI GPT 系列采用的 BPE 编码 |
| AutoTokenizer | 统一加载接口 | 根据模型配置加载匹配的 Tokenizer;本身不是分词算法或实现库 |
它们处于不同的概念层次:
7. 选择原则:与模型配套
在使用已有模型时,不应该独立选择 Tokenizer,而应该加载模型训练时配套的 Tokenizer:
模型的嵌入层和输出层参数与特定 Token ID 的语义一一对应。即使另一个 Tokenizer 能编码同一段文本,只要词表或 ID 映射不同,生成的 ID 对该模型就具有不同含义。
Note
「配套」不等于「独一无二」:多个模型可以共用一套 Tokenizer,也可以从同一基座继续微调。关键是模型参数已经适配这套 ID 和文本处理规则。扩展词表或迁移 Tokenizer 并非绝对不可能,但通常需要调整 Embedding、输出层并继续训练,不能当作普通配置替换。
Important
使用现成模型时,加载其配套的 Tokenizer、词表、特殊 Token 配置和文本处理规则。从头训练或专门进行 Tokenizer 迁移时,再讨论算法选型。
例子收束 · 交给下一篇的是什么
本篇得到
[10, 11, 12, 13, 14, 15, 16],按需加上 BOS。若换一套 Tokenizer,ID17可能代表另一段文字,模型输出与文本解释就会错位。接下来,架构篇 解释模型如何把这些 ID 变成向量,并算出
累的分数;模型选出[17, 18, 2]后,再由本篇的解码步骤还原成累。。