词元与分词器
模型从未见过“词”或“字母”。它看到的是词元 ID——索引一个固定子词片段词表的整数,用字节对编码(BPE)或其近亲变体构建。分词是一切的关卡:你的提示词、回复、上下文窗口、每秒词元计数、API 账单。一旦你开始用词元思考,关于 LLM 行为的一半民间传说就消散了。
词表是如何构建的
BPE 从原始字节出发,反复合并最频繁的相邻对,直到词表达到预算。结果是:常见词成为单个词元,罕见词拆成 2–5 片,而且没有无法表示的字符串——字节回退覆盖一切字符串,包括错别字和 emoji。词表规模从 ~30k 条目(老模型)到 ~250k(现代多语种):
| 分词器 | 词表大小 | 备注 |
|---|---|---|
| GPT-2 | ~50k | 以英语/网络文本为主 |
| Llama 2 | ~32k | 韩语拆分得很痛苦 |
| Llama 3 | ~128k | 大幅多语种升级 |
| Qwen 2 / 2.5 / 3 | ~151k | 强大的 CJK 合并 |
| Gemma | ~256k | 常见词表中最大 |
主要用英语训练的分词器会激进地合并英语片段;韩语音节对得到的有用合并少得多。早在模型本身韩语好坏之前,词表就决定了你的“韩语税”。
实例:英语
"Tokenization turns text into pieces."
→ ["Token", "ization", " turns", " text", " into", " pieces", "."]
→ 37 characters → 7 tokens ≈ 5.3 chars/token
注意细节:常见词 "Token" 是一片;后缀自成一派;前导空格属于后面的词元(" turns" 而不是 "turns")。这就是为什么 "hello"、" hello" 和 "Hello" 是不同的词元——差一个空格或大小写的提示词,分词结果就不同。
实例:韩语
同一句话,更差的经济性。谚文以音节块书写,而韩语合并在英语为主的词表里更稀少(下面的拆分是合理推测,非精确值):
"안녕하세요, 만나서 반가워요." # 17 个字符
强多语种词表(~150k 条目):
["안녕", "하세요", ",", " 만나", "서", " 반", "가", "워", "요", "."]
→ ~10 tokens ≈ 1.7 chars/token
弱词表(~32k 条目,字母级拆分):
["안", "녕", "하", "세", "요", ",", " 만", "나", "서", ...]
→ 15+ tokens ≈ 1.1 chars/token
英语平均 ~4 chars/token。韩语常见落在 1–1.5。同样的含义,3–4 倍的词元。这解释了:
- CJK 提示词以惊人的速度消耗上下文窗口——一个 8k 词元窗口装下的英语内容约为韩语的 3–4 倍。
- 相同 tok/s 下韩语生成“感觉”更慢:每句更多词元,每词元更少文本。
- 韩语在按词元计费的 API 上成本更高,用韩语写的推理轨迹同样烧预算。
为什么数字母的问题会失败
问 "strawberry 里有几个 r",连大模型都会翻车。原因:模型从未见过字母。"strawberry" 到达时大致是 ["straw", "berry"]——两个整数。整数里没有字母计数,就像页码不会告诉你那一页的拼写。模型可以背诵记忆的拼写或一步步推理,两者都不可靠。如果必须做:强制模型先把单词逐字母一行一行拼写出来,然后数行数。
词表随模型一起发布
- 分词器文件(词表、合并表)是发布的一部分;GGUF 内嵌它们,safetensors 仓库把它们与权重放在一起。
- 引擎(llama.cpp、LM Studio、vLLM……)自动加载正确的分词器。你永远不用自己挑。
- 词元计数相对于模型:同一段文本在某一族的词表下可能是 1,000 词元,在另一族下是 1,150。绝不要跨模型硬编码词元预算。
- 词表质量和模型质量是两个独立维度:32k 词表的模型能处理韩语(它理解自己的词元);只是每句话花更多词元。
一切都以词元计价
| 你关心的 | 计量单位 |
|---|---|
| 上下文窗口(4k → 128k+) | 输入 + 输出词元合计 |
| 生成速度 | 每秒词元 |
| KV 缓存内存 | 随上下文词元增长(KV 缓存) |
| API 定价 | 每百万词元 |
| 输出长度上限 | 最大输出词元 |
| 推理/思考预算 | 模型回答前可花费的词元 |
| 为 RAG 嵌入文档 | 按百万词元分块计数 |
特殊词元与对话模板
除文本片段外,词表还保留了控制 ID:序列开始、序列结束和角色标记。你的聊天 UI 发消息时,发的不是裸文本——它填充一个对话模板,把 <|im_start|>user ... <|im_end|> 之类的特殊词元穿插在你的文字周围。这层包装就是引擎报告的词元数总比你消息纯文本略高的原因,也是混用不同家族的模板会让模型糊涂的原因。
估算规则:英语字符数 ÷ 4。韩语字符数 ÷ 1.0–1.5——按最坏情况假设。模拟器 使用 ~4 chars/token 近似并针对英语校准;韩语请自行在脑内放大。
自己数词元
pip install tiktoken
python -c "import tiktoken as t; e = t.get_encoding('o200k_base'); print(len(e.encode('hello world')))"
- tiktoken(https://github.com/openai/tiktoken)覆盖 OpenAI 家族的编码;其他家族不同,但英语大致落在 ±15% 以内。
- Hugging Face 模型页带分词器查看器;分词器 playground 可以粘贴文本实时看拆分。
- 本地引擎会在日志里打印提示词词元数——用真实提示词核对一次,然后按比例估算。
相关: 深入 Transformer 内部 · 预填充与解码 · KV 缓存