
上周发完 token 预算告警文章后,有读者问:"用 tiktoken 统计了,但 API 还是爆了预算,为什么?"我复盘发现——不同模型用的编码器不同,同一段文字,token 数可以差 1 倍。中文场景尤其明显,这是之前文章没有覆盖到的盲区。
这篇文章说清楚一件事:为什么你的 token 计数总是不准,以及怎么用正确的编码器拿到准确数字。
问题现场:同一个字符串,三种编码器算出三个结果
先看一个具体例子。用 Python 写一个带 docstring 的函数:
code = 'def summarize():\n """对 话历史超过阈值时压缩"""\n pass'
用 tiktoken 分别用 cl100k_base(GPT-4)和 p50k_base(GPT-3.5)编码:
| 编码器 | 适用模型 | token 数 |
|---|---|---|
cl100k_base |
GPT-4 / GPT-3.5-turbo | 24 |
p50k_base |
GPT-3.5(旧)/ Codex | 36 |
r50k_base |
GPT-2 / 早期模型 | 40 |
同一个字符串,三种编码器算出三个结果,最大差值 +67%。
中文场景更明显:
| 文本 | cl100k_base | p50k_base | 差值 |
|---|---|---|---|
你好世界(4字) |
5 | 8 | +60% |
上下文中途压缩后缓存失效(12字) |
16 | 24 | +50% |
RAG系统向量相似度下降问题(14字) |
13 | 27 | +108% |
如果你的系统用 cl100k_base 统计完预算,丢给一个用 p50k_base 计费的 API——中文段落的实际消耗会比预算多出 1 倍。
根因:四种编码器,词汇表完全不同
tiktoken 底层有四套编码词汇表,每个词表包含的 token 不一样:
| 编码器 | 词汇表大小 | 典型用途 |
|---|---|---|
cl100k_base |
~100k tokens | GPT-4、GPT-3.5-turbo、Claude 3 |
p50k_base |
~50k tokens | GPT-3.5(旧版本)、Codex |
p50k_edit |
~50k tokens | GPT-3.5 Edit API |
r50k_base |
~50k tokens | GPT-2 及早期模型 |
中文不是这些词汇表的长项。编码器对中文采用 byte-level BPE 切分——一个中文字符被切成 2-4 个 byte-level token。不同编码器对同一个汉字的切分粒度不同,导致最终 token 数差异巨大。
用 tiktoken 实测验证:
import tiktoken
def count_tokens(text, encoding_name):
enc = tiktoken.get_encoding(encoding_name)
return len(enc.encode(text))
text = "RAG系统向量相似度下降问题"
print(f"cl100k_base: {count_tokens(text, 'cl100k_base')} tokens")
# 输出: cl100k_base: 13 tokens
print(f"p50k_base: {count_tokens(text, 'p50k_base')} tokens")
# 输出: p50k_base: 27 tokens
差值 14 个 token——如果上下文里有 10 段这样的中文,每段多算 14 tokens,累加起来就超过阈值告警线了。
实战案例:切换模型后,凌晨 2 点告警触发
我之前踩过一次这个坑。
客服机器人上线第一周,接入了 GPT-3.5-turbo(cl100k_base),用 tiktoken 统计 token,预算告警阈值设为 80000 tokens。运行正常。
第二周切到 GPT-3.5-0613(老模型,p50k_base 编码),没换统计脚本。
第三天凌晨 2 点,告警触发:单会话 token 计数 78000,接近 80000 上限。
但 API 返回 context length exceeded——实际已经超过 12 万 tokens。
根因定位:
- 统计脚本一直用
cl100k_base(GPT-4/turbo 默认) - 实际 API 用的是
p50k_base - 中文对话内容占 40%,每个中文句子的 token 统计偏差在 50-100%
- 差距累积到后期,直接撑爆了上下文窗口
解决代码:
import tiktoken
# 模型到编码器的精确映射
MODEL_TO_ENCODING = {
"gpt-4": "cl100k_base",
"gpt-4-0314": "cl100k_base",
"gpt-3.5-turbo": "cl100k_base",
"gpt-3.5-turbo-0613": "cl100k_base",
"gpt-3.5-turbo-16k": "cl100k_base",
"gpt-3.5-turbo-0301": "cl100k_base",
"code-davinci-002": "p50k_base",
"gpt-3.5-turbo-0613-old": "p50k_base",
}
def get_encoding_for_model(model_name: str):
encoding_name = MODEL_TO_ENCODING.get(model_name.lower())
if encoding_name:
return tiktoken.get_encoding(encoding_name)
return tiktoken.get_encoding("cl100k_base") # 兜底
def count_tokens(text: str, model_name: str = "gpt-3.5-turbo") -> int:
enc = get_encoding_for_model(model_name)
return len(enc.encode(text))
自动检测:你的系统用的是哪个编码器?
不确定自己用的 API 对应哪个编码器?用这个函数做实测验证:
import tiktoken
def detect_encoding(model: str) -> str:
test_cases = {
"cl100k_base": {"你好世界": 5, "def summarize():": 4},
"p50k_base": {"你好世界": 8, "def summarize():": 4},
}
for enc_name, samples in test_cases.items():
enc = tiktoken.get_encoding(enc_name)
if all(len(enc.encode(s)) == expected for s, expected in samples.items()):
return enc_name
return "cl100k_base"
现在你可以做什么
- 确认模型对应的编码器: GPT-4 系列和当前 GPT-3.5-turbo 用
cl100k_base(一致)。如果你用的是旧版 GPT-3.5 或 Codex,查文档确认编码器名称。 - 替换统计函数: 把项目中所有硬编码
tiktoken.get_encoding("cl100k_base")的地方,替换成根据 model name 动态获取的版本。上面的MODEL_TO_ENCODING映射表可以直接拷贝使用。 - 上线前用真实数据验证: 取生产环境最近 10 条对话,分别用两种编码器统计,对比差距。如果差距超过 20%,重新跑一遍基准数据。
- 告警加双阈值: 不要只设置一个 80% 告警。参考实测数据,普通用户 Round 25(~46000 tokens)触发轻度告警,重度用户 Round 15(~70000 tokens)触发严重告警。
更多交流点击入群






