实时拆词与效率
同一段文本会被 GPT-2、cl100k、o200k 和字符级基线切成不同 token;悬浮 token 可查看 ID 与字节。
💰 Token 效率对比
同一段文本,token 数越少,长上下文和 API 成本压力越低;但谁更省取决于具体 tokenizer。
🌊 DeepSeek 官方口径估算
按字符比例做课堂估算,不等同于真实 tokenizer 编码;适合提醒“不同模型不能混用同一条经验”。
🧩 什么是 BPE 分词?
Byte Pair Encoding (BPE) 是大语言模型最常用的分词算法。它不按"词"切分文本,而是学习一套子词(subword)词表。 核心思想:从最小单元(字节)开始,反复合并出现最频繁的相邻对,直到词表达到目标大小。
l · o · w · e · r合并 1:
l · o · w · er ← "er" 是高频对合并 2:
low · er ← "lo"→"low" 合并结果:
low er → 2 个 token
🔍 预分词(Pre-tokenization)
BPE 不是直接对整个文本做合并 — 那样计算量太大。实际流程是先用正则表达式将文本切成若干"块"(chunk), 然后在每个块内部独立做 BPE。这个正则模式决定了哪些字符会被"粘在一起"。
's|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+
- 数字限制为 1~3 位一组(
\p{N}{1,3}),防止长数字被当成一个 token - 增加了大小写不敏感匹配英文缩写(
(?i:'s|'t|...)),同时匹配 I'M 和 I'm - 对换行符(
\r\n)有更精细的处理,避免跨行合并 - o200k 进一步区分了 Unicode 字母类别(Lu/Lt/Lm/Lo),对中日韩文字更友好
为什么不直接按"词"分? 因为英文还好(空格分词),但中文没有天然分隔符, 阿拉伯文从右往左写,泰文没有空格…… BPE 的字节级方案天然支持所有语言和特殊字符(包括 emoji), 这就是"字节对编码"名字的由来。
💾 UTF-8 编码与字节
BPE 的最小单元是字节(byte),而不是字符。不同字符在 UTF-8 中占用不同数量的字节:
| 字符类型 | 示例 | UTF-8 字节数 | 十六进制 |
|---|---|---|---|
| ASCII 英文/数字 | A | 1 | 41 |
| 西欧重音字符 | é | 2 | c3 a9 |
| 中日韩文字 | 你 | 3 | e4 bd a0 |
| Emoji | 🤖 | 4 | f0 9f a4 96 |
这解释了为什么早期 GPT-2 对中文不友好:一个中文字在 UTF-8 中占 3 字节,如果词表没有常用中文合并,就会拆得很碎。 但现代 tokenizer 和不同厂商模型已经大幅改变了这个结论。悬浮/点击实时拆词里的 token 标签可以查看其 ID 和字节表示。
🔧 BPE 编码算法详解
给定一段文本和训练好的 BPE 词表,编码过程如下(这正是本页 JS 代码中 _bpe() 函数的实现):
function bpe_encode(text, vocab): // 第一步:将文本转为 UTF-8 字节序列 parts = [b for b in utf8_bytes(text)] // 第二步:反复查找并合并优先级最高的相邻对 while len(parts) > 1: best_rank = ∞ best_i = -1 for i in range(len(parts) - 1): merged = parts[i] + parts[i+1] rank = vocab.get(merged, ∞) if rank < best_rank: best_rank = rank best_i = i if best_i == -1: break // 没有可合并的对了 // 合并 parts[best_i] = parts[best_i] + parts[best_i+1] parts.remove(best_i + 1) return parts // 每个元素就是一个 token
关键概念:词表中每个 token 有一个 rank(排名),rank 越小表示该 token 在训练时越早被合并, 也就是越"常见"。编码时总是优先合并 rank 最小的对。
时间复杂度:朴素实现为 $O(n^2)$,其中 $n$ 是字节数。对于短文本(< 几百字节)这完全够用。 OpenAI 的 tiktoken 库使用 Rust 实现了优化版本,速度是 Python 实现的 3~6 倍。
本页实现:纯 JavaScript,加载的词表 JSON 中 token 的排列顺序就是 rank 顺序
(数组下标 = rank),所以只需要一个 Map 就能 O(1) 查询 rank。
📈 不同 tokenizer:不是一条规则管所有模型
OpenAI 随着模型迭代,分词器不断进化;DeepSeek 也给出了自己的 token 使用口径。 课堂上更好的说法不是“中文贵/英文省”,而是:具体模型怎样切 token,决定了具体成本。
| GPT-2 | cl100k_base | o200k_base | DeepSeek 口径 | |
|---|---|---|---|---|
| 使用模型 | GPT-2, GPT-3 | GPT-3.5, GPT-4 | GPT-4o, o1, o3 | DeepSeek API |
| 词表大小 | 50,257 | 100,256 | 199,998 | 以实际 usage / tokenizer 为准 |
| 训练语料 | 英文为主 | 多语言 | 多语言 + 代码 | 官方给出字符比例估算 |
| "你好" token 数 | 6 | 2 | 1 | 约 1.2 |
| "Hello" token 数 | 1 | 1 | 1 | 约 1.5 |
| 发布年份 | 2019 | 2023 | 2024 | 以当前文档为准 |
💰 分词与 API 成本:按模型看,不按传闻看
大多数 LLM API 按 token 数量计费。以一段 100 个中文字符的文本为例,不同口径下估算会很不一样:
💡 试试看:输入同一个意思的中文句子和英文句子。你会发现“单字符比例”和“整句话成本”不是一回事; 中文表达更短时,在某些模型口径下反而可能更省。