引言
"你的 prompt 到底用了多少 token?"
这是"每日一个开源项目"系列的第121篇文章 。今天的主角是 tiktoken------OpenAI 开源的官方分词器。
在调用 OpenAI API 之前,几乎每个开发者都会遇到同一个问题:这段文本会消耗多少 token?超出上下文限制了吗?费用怎么估算?这些问题的答案,都藏在 tokenization 这一步里。
tiktoken 不只是一个"数 token 的工具",它是 GPT 系列模型在训练和推理时真实使用的分词器。理解它,就是理解模型真正"看到"的输入形态。
你将学到什么
- BPE(字节对编码)算法的核心原理和4个关键特性
- tiktoken 支持哪些编码,如何选择正确的编码
- 如何精确计算 Token 数量,避免 API 调用超限
- 如何添加自定义特殊 token
- tiktoken 的 Rust + Python 架构为何能比同类工具快 3-6 倍
前置知识
- 基础 Python 使用经验
- 了解 OpenAI API(知道 token 是计费单位即可)
- 对 NLP/分词有基本概念(可选)
项目背景
项目简介
tiktoken 是 OpenAI 发布的开源 BPE(Byte Pair Encoding,字节对编码)分词器库。它的核心职责是将文本字符串转化为 token 序列(整数列表),供语言模型消费;也可以将 token 序列还原为原始文本。
这不是一个"实验性"工具------它是 GPT-3.5、GPT-4、GPT-4o 等模型真实使用的分词器。当你通过 API 发送一段文字时,模型"看到"的其实是 tiktoken 生成的 token 序列。
作者/团队介绍
- 作者: OpenAI
- 项目创建: 2022年(伴随 GPT-3 API 开放逐渐公开)
- 核心贡献: 由 OpenAI 工程团队维护,Rust 核心部分提供高性能支撑
- 知名度: 被 184,000+ 个 GitHub 项目依赖,是 LLM 应用开发的基础设施
项目数据
- ⭐ GitHub Stars: 18,400+
- 🍴 Forks: 1,500+
- 📦 版本: 0.13.0(2026-05-15)
- 📄 License: MIT
- 🌐 语言组成: Python 64.9% + Rust 35.1%
主要功能
核心作用
tiktoken 做三件事:
- Encode:将文本转化为 token ID 列表(整数数组)
- Decode:将 token ID 列表还原为原始文本(可逆无损)
- Count:精确计算一段文本对应的 token 数量
这三件事看起来简单,但在 LLM 应用开发中至关重要:
- 上下文管理:确保 prompt + 历史消息不超过模型上下文限制
- 成本估算:在调用 API 前预估费用(OpenAI 按 token 计费)
- Prompt 工程:了解模型实际接收到的"文字单元",优化分词边界
使用场景
-
API 调用前的 Token 预算控制
- 在发送请求前检查是否超出
max_tokens限制,避免截断或报错
- 在发送请求前检查是否超出
-
长文档的智能分块(Chunking)
- 将文档切割成不超过指定 token 数的块,用于 RAG 检索增强生成
-
多轮对话的上下文窗口管理
- 动态裁剪历史消息,确保对话历史始终在模型窗口内
-
精确成本估算与监控
- 构建 token 用量统计面板,优化 prompt 的 token 效率
-
Fine-tuning 数据预处理
- 在准备训练数据时,按 token 数量控制样本长度
快速开始
bash
pip install tiktoken
python
import tiktoken
# 方式一:按编码名获取(推荐用于新项目)
enc = tiktoken.get_encoding("o200k_base")
# 方式二:按模型名获取(自动匹配模型对应的编码)
enc = tiktoken.encoding_for_model("gpt-4o")
# 编码:文本 → token ID 列表
tokens = enc.encode("Hello, tiktoken!")
print(tokens) # [13225, 11, 384, 4963, 0]
print(len(tokens)) # 5 ← 这就是 token 数
# 解码:token ID 列表 → 文本
text = enc.decode(tokens)
print(text) # "Hello, tiktoken!"
# 可逆性验证
assert enc.decode(enc.encode("任意文本都可以还原")) == "任意文本都可以还原"
核心特性
-
高性能 Rust 核心
- 核心分词逻辑用 Rust 实现,比同类 Python 分词器快 3-6 倍(1GB 文本基准测试对比
GPT2TokenizerFast)
- 核心分词逻辑用 Rust 实现,比同类 Python 分词器快 3-6 倍(1GB 文本基准测试对比
-
可逆无损编码
decode(encode(text)) == text始终成立,token 序列可完整还原原始文本
-
通用性覆盖任意文本
- 不依赖训练词表,任意 Unicode 文本(包括训练集外的内容)都能正确分词
-
高压缩率
- 平均每个 token 对应约 4 字节文本(英文约 4 字符,中文约 1-2 字),显著减少序列长度
-
子词感知
- 能识别英文常见子词(如
ing、tion、pre-),帮助模型理解词法规律
- 能识别英文常见子词(如
-
多编码支持
- 内置
o200k_base(GPT-4o)、cl100k_base(GPT-4/3.5-turbo)等主流编码
- 内置
-
特殊 Token 扩展
- 支持自定义添加
<|im_start|>等特殊控制 token,适配 Chat 格式
- 支持自定义添加
-
教育模块
- 内置
_educational模块,可视化 BPE 合并过程,适合学习算法原理
- 内置
项目优势对比
| 对比维度 | tiktoken | HuggingFace Tokenizers | SentencePiece |
|---|---|---|---|
| 速度 | ⚡ 最快(Rust核心) | 快(Rust核心) | 中等(C++) |
| 与 OpenAI 模型对齐 | ✅ 官方一致 | ❌ 近似 | ❌ 不支持 |
| Python 接口简洁度 | ✅ 极简 | 中等 | 中等 |
| 支持模型范围 | OpenAI 系列 | 通用 | 通用 |
| 自定义编码 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
| 依赖包大小 | 小 | 中 | 中 |
为什么选择 tiktoken?
- 调用 OpenAI API 时,只有 tiktoken 的计算结果与服务端完全一致
- 极简 API,两行代码即可完成 token 计数
- MIT 协议,商业项目无障碍使用
项目详细剖析
BPE 算法:4个关键特性
BPE(Byte Pair Encoding,字节对编码)是 tiktoken 的核心算法。理解 BPE 的 4 个特性,就能理解 tiktoken 的能力边界:
① 可逆无损(Lossless Reversibility)
token 序列可以 100% 还原为原始文本,没有任何信息丢失。这是 BPE 的基础承诺:
python
original = "GPT-4o 使用 o200k_base 编码"
assert enc.decode(enc.encode(original)) == original # 永远成立
② 通用性(Open Vocabulary)
tiktoken 基于字节级 BPE,词表从单个字节(256个)开始,通过统计频率逐步合并,最终覆盖高频子词。任何 Unicode 字符都能被分词------即使是模型从未见过的词:
python
# 训练集之外的新词、表情符号、代码,全部可以正常分词
enc.encode("😀🤖 tiktoken-v99 新词") # 不会报错
③ 高压缩率(High Compression)
平均每个 token 约对应 4 字节,这意味着序列长度大幅缩短,降低计算开销:
python
text = "The quick brown fox jumps over the lazy dog"
tokens = enc.encode(text)
print(f"字符数: {len(text)}, token数: {len(tokens)}")
# 字符数: 43, token数: 9 → 压缩率约 4.8:1
④ 子词感知(Subword Awareness)
BPE 能识别词根、词缀等语言规律,帮助模型泛化到未见过的组合:
python
# "encoding" → ["encod", "ing"] 模型可以理解 "encod-" 的含义
# "tokenization" → ["token", "ization"]
print(enc.decode([b]) for b in enc.encode("encoding"))
编码选择指南
选错编码会导致 token 计数与实际 API 调用不符。以下是完整对照表:
| 编码名称 | 适用模型 | 词表大小 |
|---|---|---|
o200k_base |
GPT-4o, GPT-4o-mini | 200,000 |
cl100k_base |
GPT-4, GPT-3.5-turbo, text-embedding-3-* | 100,000 |
p50k_base |
text-davinci-003 等旧版模型 | 50,000 |
r50k_base |
GPT-3 (davinci) 等 | 50,000 |
python
import tiktoken
def count_tokens(text: str, model: str = "gpt-4o") -> int:
"""精确计算指定模型的 token 消耗"""
enc = tiktoken.encoding_for_model(model)
return len(enc.encode(text))
# 测试
print(count_tokens("Hello, world!")) # 4
print(count_tokens("你好,世界!")) # 6
自定义特殊 Token
Chat 模型(如 gpt-3.5-turbo)使用特殊 token 来界定角色边界。你可以扩展现有编码来支持这些控制符:
python
import tiktoken
cl100k_base = tiktoken.get_encoding("cl100k_base")
# 创建带 Chat 格式特殊 token 的扩展编码
enc = tiktoken.Encoding(
name="cl100k_im", # 自定义名称
pat_str=cl100k_base._pat_str,
mergeable_ranks=cl100k_base._mergeable_ranks,
special_tokens={
**cl100k_base._special_tokens,
"<|im_start|>": 100264, # 角色开始
"<|im_end|>": 100265, # 角色结束
}
)
# 现在可以编码包含特殊 token 的 Chat 格式文本
text = "<|im_start|>user\nWhat is BPE?<|im_end|>"
tokens = enc.encode(text, allowed_special={"<|im_start|>", "<|im_end|>"})
print(f"token 数: {len(tokens)}")
实际应用:精确的 Token 预算控制
这是 tiktoken 最高频的使用场景------在发送 API 请求前,检查并裁剪消息列表:
python
import tiktoken
def trim_messages_to_budget(
messages: list[dict],
model: str = "gpt-4o",
max_tokens: int = 8000,
) -> list[dict]:
"""
裁剪消息历史,确保 token 总数不超过预算。
保留 system prompt,从最旧的 user/assistant 消息开始删除。
"""
enc = tiktoken.encoding_for_model(model)
def count(msgs):
# 每条消息有 4 token 的固定开销(角色、分隔符等)
total = sum(4 + len(enc.encode(m.get("content", ""))) for m in msgs)
return total + 2 # 回复前置的 2 token
system = [m for m in messages if m["role"] == "system"]
others = [m for m in messages if m["role"] != "system"]
while count(system + others) > max_tokens and others:
others.pop(0) # 删除最旧的消息
return system + others
# 使用示例
messages = [
{"role": "system", "content": "你是一个助手。"},
{"role": "user", "content": "第一个问题"},
{"role": "assistant", "content": "第一个回答"},
# ... 更多历史消息
]
trimmed = trim_messages_to_budget(messages, max_tokens=4096)
教育模块:可视化 BPE 过程
tiktoken 内置了一个教育用的简化版 BPE 实现,适合学习算法原理:
python
from tiktoken._educational import SimpleBytePairEncoding
# 使用真实的 cl100k_base 合并规则训练一个简化编码
enc = SimpleBytePairEncoding.from_tiktoken("cl100k_base")
# 可视化分词过程
result = enc.encode("hello world aaaaaaaaaaaa")
# 输出会显示每一步合并的过程
架构解析:为什么这么快?
tiktoken 之所以比同类工具快 3-6 倍,关键在于其 Python + Rust 混合架构:
scss
tiktoken/
├── tiktoken/
│ ├── __init__.py ← Python 接口层
│ ├── core.py ← 主 API(Encoding 类)
│ ├── model.py ← 模型名 → 编码名的映射表
│ ├── registry.py ← 编码注册与缓存
│ └── _educational.py ← 教育用纯 Python BPE 实现
│
└── src/ (Rust)
└── lib.rs ← 高性能 BPE 核心逻辑(通过 PyO3 暴露给 Python)
性能关键点:
- Rust 核心:BPE 的合并循环用 Rust 实现,避免了 Python 的 GIL 和解释器开销
- PyO3 绑定:Rust 函数通过 PyO3 直接暴露为 Python 对象,调用开销极低
- 词表缓存:编码词表在首次加载后缓存到内存,避免重复 I/O
- 正则预分割:在 BPE 之前用高效正则表达式预先切分文本(处理空格、标点边界)
项目地址与资源
官方资源
- 🌟 GitHub : openai/tiktoken
- 📚 使用教程 : OpenAI Cookbook - How to count tokens
- 🐛 Issue Tracker : github.com/openai/tikt...
相关资源
- OpenAI Tokenizer 可视化工具(在线查看文本的分词结果)
- OpenAI API Token 计费说明
- Hugging Face Tokenizers 文档(对比参考)
总结
tiktoken 的价值不只在于"数 token",它是连接开发者与 GPT 模型之间的翻译层。掌握 tiktoken,意味着你真正理解了模型的输入形态,能够精确控制上下文、估算成本、构建健壮的 LLM 应用。
Rust 核心 + Python 接口的设计选择,也是一个值得借鉴的工程范式:把性能关键路径交给系统语言,把易用性和灵活性留给动态语言。
欢迎来我的个人主页找到更多有用的知识和有趣的产品