分词 (Tokenization)
📌 核心定义 (What)
Section titled “📌 核心定义 (What)”一句话定义:Tokenization(分词/词元化)是将连续的文本切分成离散的 Token(词元) 的过程。Token 是 LLM 处理文本的最小单位。
"Hello, world!" → ["Hello", ",", " world", "!"]"你好世界" → ["你", "好", "世", "界"] 或 ["你好", "世界"]Token ≠ 词。Token 可以是一个字符、一个词、甚至半个词(子词)。
🏠 生活类比 (Analogy)
Section titled “🏠 生活类比 (Analogy)”🧩 “乐高积木工厂”
Section titled “🧩 “乐高积木工厂””想象你要用乐高搭建任何物体:
- 字符级:只有 26 种基础小颗粒(字母),可以搭任何东西,但太慢
- 词级:预制了”门”、“窗”、“轮子”等大块,搭建快但无法搭新东西
- 子词级(BPE):预制了最常用的组合,既灵活又高效
"unhappiness" 的切分方式:
字符级: ["u", "n", "h", "a", "p", "p", "i", "n", "e", "s", "s"] → 11 个词级: ["unhappiness"] → 1 个(但如果没见过这个词就无法处理)子词级: ["un", "happiness"] → 2 个 ✅ 最佳平衡💻 JS 开发者类比
Section titled “💻 JS 开发者类比”// 你已经在做 "分词" 了!
// 1. 最简单的分词:按空格切分const text = "Hello world";const tokens = text.split(" "); // ["Hello", "world"]
// 2. URL 路由就是一种"分词"const path = "/api/users/123";const segments = path.split("/").filter(Boolean); // ["api", "users", "123"]
// 3. JSON 解析也需要"分词"(Lexer/Tokenizer)// `{"name": "AI"}` → ["{", "name", ":", "AI", "}"]
// LLM 的 Tokenizer 做的是同样的事,只是更复杂🎬 视频详解 (Video)
Section titled “🎬 视频详解 (Video)”🔤 交互演示:分词器 (Interactive)
Section titled “🔤 交互演示:分词器 (Interactive)”输入任意文本,观察它被分解成哪些 Token。
✂️ 分词器可视化
Hello[15496]
world[995]
![0]
Token ID 序列 (模型输入):
[15496, 995, 0]
字符数
12
Token 数
3
字符/Token
4.00
💡 关键发现:
- 空格也是 token: " world" 和 "world" 是不同的 token
- ChatGPT 被拆分: ["Chat", "G", "PT"] = 3 个 token
- 中文效率低: 每个汉字约 1-2 个 token
- 数字被切分: 每 3 位一组,所以 LLM 数学不好
💡 这是简化演示,实际 GPT-4 词汇表有 100,000+ token
🎯 为什么需要它 (Why)
Section titled “🎯 为什么需要它 (Why)”问题:LLM 无法直接处理字符串
Section titled “问题:LLM 无法直接处理字符串”# ❌ 不能直接输入文本model("Hello, world!") # 报错!模型只接受数字
# ✅ 必须先分词 + 转索引tokens = tokenizer.encode("Hello, world!") # [15496, 11, 995, 0]output = model(tokens) # 现在可以了分词的三个阶段
Section titled “分词的三个阶段”文本 → [Tokenizer] → Token 序列 → [Embedding] → 向量序列 → [Model] → 输出
"猫很可爱" ↓ 分词["猫", "很", "可爱"] ↓ 转索引[1234, 567, 8901] ↓ 嵌入[[0.2, 0.8, ...], [0.1, 0.5, ...], [0.3, 0.9, ...]] ↓ 模型处理输出📊 分词算法对比
Section titled “📊 分词算法对比”1. 字符级 (Character-level)
Section titled “1. 字符级 (Character-level)”text = "Hello"tokens = list(text) # ['H', 'e', 'l', 'l', 'o']| 优点 | 缺点 |
|---|---|
| 词汇表小(仅 256 个字符) | 序列太长(每个字符一个 token) |
| 不会有”未知词” | 难以学习长距离语义 |
2. 词级 (Word-level)
Section titled “2. 词级 (Word-level)”text = "Hello world"tokens = text.split() # ['Hello', 'world']| 优点 | 缺点 |
|---|---|
| 语义完整 | 词汇表巨大(需 100k+) |
| 序列短 | 无法处理新词 (OOV) |
3. 子词级 (Subword) ⭐ 现代标准
Section titled “3. 子词级 (Subword) ⭐ 现代标准”text = "unhappiness"tokens = ["un", "happi", "ness"] # BPE 切分| 优点 | 缺点 |
|---|---|
| 词汇表适中(32k~100k) | 算法较复杂 |
| 能处理任何新词 | 同一词可能有多种切分 |
| 平衡语义和长度 |
🔧 主流分词算法
Section titled “🔧 主流分词算法”BPE (Byte Pair Encoding)
Section titled “BPE (Byte Pair Encoding)”GPT 系列使用。核心思想:反复合并最常见的字符对。
初始词汇表: ['a', 'b', 'c', ..., 'z', ' ']
训练语料: "aaabdaaabac"
第 1 轮: 'aa' 出现最多 → 合并为 'Z' → "ZabdZabac"第 2 轮: 'Za' 出现最多 → 合并为 'Y' → "YbdYbac"第 3 轮: 'Yb' 出现最多 → 合并为 'X' → "XdXac"...
最终词汇表: ['a', 'b', ..., 'z', 'aa', 'ab', 'aab', ...]WordPiece
Section titled “WordPiece”BERT 使用。与 BPE 类似,但选择合并对时使用似然最大化而非频率。
# WordPiece 的特点:子词以 ## 开头"unbelievable" → ["un", "##believ", "##able"]SentencePiece
Section titled “SentencePiece”支持无空格语言(中文、日文)。直接在原始文本上训练,不依赖预分词。
"我爱北京天安门" → ["▁我", "爱", "北京", "天安门"]# ▁ 表示词的开始(原本是空格的位置)💻 代码实现 (Code)
Section titled “💻 代码实现 (Code)”from transformers import AutoTokenizer
# 加载 GPT-2 的分词器tokenizer = AutoTokenizer.from_pretrained("gpt2")
text = "Hello, world! This is AI."
# 1. 编码:文本 → Token IDtoken_ids = tokenizer.encode(text)print(token_ids) # [15496, 11, 995, 0, 1212, 318, 9552, 13]
# 2. 查看 Token 文本tokens = tokenizer.tokenize(text)print(tokens) # ['Hello', ',', 'Ġworld', '!', 'ĠThis', 'Ġis', 'ĠAI', '.']# Ġ 表示前面有空格
# 3. 解码:Token ID → 文本decoded = tokenizer.decode(token_ids)print(decoded) # "Hello, world! This is AI."
# 4. 查看词汇表大小print(f"词汇表大小: {tokenizer.vocab_size}") # 50257import tiktoken
# GPT-4 使用的分词器enc = tiktoken.encoding_for_model("gpt-4")
text = "Hello, world! 你好世界"
# 编码token_ids = enc.encode(text)print(token_ids) # [9906, 11, 1917, 0, ...]
# 查看每个 tokenfor tid in token_ids: print(f"{tid} → '{enc.decode([tid])}'")
# 统计 token 数量(计费用)print(f"Token 数量: {len(token_ids)}")// 简化的 BPE 概念实现
// 词汇表(实际有 50000+ 条)const vocab = { "Hello": 15496, ",": 11, " world": 995, // 注意:空格也是 token 的一部分 "!": 0,};
// 编码(简化版,实际更复杂)function encode(text) { const tokens = []; let remaining = text;
while (remaining.length > 0) { // 贪婪匹配:找词汇表中最长的匹配 let matched = false; for (let len = remaining.length; len > 0; len--) { const substr = remaining.slice(0, len); if (vocab[substr] !== undefined) { tokens.push(vocab[substr]); remaining = remaining.slice(len); matched = true; break; } } if (!matched) { // 未知字符,跳过(实际会用 <unk>) remaining = remaining.slice(1); } }
return tokens;}
console.log(encode("Hello, world!")); // [15496, 11, 995, 0]🔢 Token 数量与成本
Section titled “🔢 Token 数量与成本”| 模型 | 词汇表大小 | Tokenizer | 1 Token ≈ |
|---|---|---|---|
| GPT-2 | 50,257 | r50k_base | 4 个英文字符 |
| GPT-3.5/4 | 100,000 | cl100k_base | 4 个英文字符 / 1-2 个中文字 |
| GPT-4o | 200,000 | o200k_base | 更高效,中文提升 2-3x |
| GPT-5 (2025.8) | 200,000+ | 新版本 | 多语言优化 |
| Claude 3.5 | 100,000 | 自定义 | 类似 GPT-4 |
| Claude 4 (2025.5) | 100,000+ | 优化版 | 代码效率提升 |
| LLaMA 2/3 | 32,000 | SentencePiece | 英文更优 |
| Gemini 2.0 (2025) | - | 自定义 | 多模态优化 |
# 计费估算text = "这是一段测试文本"tokens = tokenizer.encode(text)
price_per_1k = 0.03 # 假设 $0.03/1k tokenscost = len(tokens) / 1000 * price_per_1kprint(f"预估成本: ${cost:.4f}")⚠️ 常见误区 (Pitfalls)
Section titled “⚠️ 常见误区 (Pitfalls)”🔗 相关概念
Section titled “🔗 相关概念”- 词嵌入 - 分词后的下一步:转为向量
- Transformer - 使用 Token 的模型架构
- Prompt Engineering - 理解 Token 计费
📚 延伸资源
Section titled “📚 延伸资源”- OpenAI Tokenizer - 在线体验分词
- HuggingFace Tokenizers - 官方文档
- Andrej Karpathy - Let’s build the GPT Tokenizer - 视频教程