Note
学习路径推荐
basic_tokenizer.py
→ regext_tokenizer.py
→ gpt4_tokenizer.py
→ sentencepiece_tokenizer.py
→ jieba_demo.py
这是一个从零开始学习大语言模型(LLM)Tokenizer 的教程项目,特别适合初学者。内容基于 Andrej Karpathy 的优秀课程。
# 克隆项目
git clone https://github.com/ShallowU/MyTokenizer_Learning.git
cd MyTokenizer_Learning
# 安装依赖
pip install tiktoken sentencepiece jieba tokenizers
核心概念: 字节对编码(BPE)的基本实现
关键方法:
-
train()
: 训练分词器- 迭代
num_merges = vocab_size - 256
次 - 不断合并最频繁的字节对
- 构建词汇表:
vocab[idx] = vocab[pair[0]] + vocab[pair[1]]
- 迭代
-
decode()
: 解码 token 序列# 将 token ID 列表转换为字符串 ids → vocab 查找 → 字节流 → UTF-8 解码
-
encode()
: 编码文本# 文本 → UTF-8 字节 → 应用合并规则 → token ID 列表
改进点: 添加智能文本分块,提高分词质量
核心特性:
# GPT-4 使用的分割模式
GPT4_SPLIT_PATTERN = r"""'(?i:[sdmt]|ll|ve|re)|[^\r\n\p{L}\p{N}]?+\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]++[\r\n]*|\s*[\r\n]|\s+(?!\S)|\s+"""
分割规则:
- 📝 缩写词('s, 'll 等)
- 🔤 连续字母序列(单词)
- 🔢 短数字序列
- ⭐ 标点符号
- ⬜ 空白字符
工作流程:
原始文本 → 正则分块 → 每块独立编码 → 合并结果
挑战与解决方案:
问题: GPT-4 使用 _mergeable_ranks
格式存储词汇表
解决: 实现 recover_merges()
函数转换格式
def recover_merges(mergeable_ranks):
"""从 GPT-4 格式恢复合并规则"""
merges = {}
for token, rank in mergeable_ranks.items():
if len(token) == 1:
continue # 跳过基础字节
# 恢复合并对
pair = tuple(bpe(mergeable_ranks, token, max_rank=rank))
ix0 = mergeable_ranks[pair[0]]
ix1 = mergeable_ranks[pair[1]]
merges[(ix0, ix1)] = rank
return merges
问题: GPT-4 对基础字节进行了重新排序
解决: 使用逆映射恢复原始字节顺序
功能扩展: 支持特殊控制 token(如 <|endoftext|>
)
实现要点:
- 分别处理普通 token 和特殊 token
- 支持不同的特殊 token 处理模式
- 避免特殊 token 被意外分割
特点对比:
特性 | TikToken | SentencePiece |
---|---|---|
语言支持 | 主要英语 | 多语言友好 |
处理级别 | 字节级 | Unicode 码点级 |
空格处理 | 正则分割 | ▁ 符号表示 |
未知字符 | 字节回退 | UNK 或字节回退 |
优势:
- 🌍 更好的多语言支持
- 🎯 端到端训练
- 🔄 字节回退机制
- ⚡ 高效的训练和推理
推荐方案: Jieba + WordPiece
原因分析:
- 中文没有天然的词语分隔符
- 直接字节编码会增加序列长度
- 需要先进行词语切分再应用子词算法
实现思路:
中文文本 → Jieba 分词 → WordPiece 子词化 → Token 序列
现象: 模型无法准确计算字符数量
原因: 字符被分散在不同 token 中
现象: 反转字符串等操作出错
解决: 字符间加空格强制单字符分词
原因: 训练数据英语占主导,其他语言 token 效率低
原因: 数字分词不一致,如 "1234" → ["12", "34"]
现象: <|endoftext|>
等特殊 token 触发意外行为
现象: 尾随空格影响生成质量
原因: 破坏了 "(空格)(内容)" 的常见模式
现象: 单词内大写字母导致预测异常
原因: 训练中此类模式较少
推荐: YAML > JSON
原因: YAML 特殊字符更少,分词更友好
💡 提示: 建议按照学习路径顺序逐步实践,每个阶段都有对应的代码实现可供参考。