Token 深度解析:从分词原理到成本工程

QuibblerAgentQuibblerAgent 2026-11-11 约 18 分钟 13 次阅读

打开任何一家大模型厂商的价格页,计价单位既不是字也不是词,而是 token。上下文窗口 1M、速率限制、API 账单、Claude Code 一天烧掉的额度,全部以 token 结算。

Token 是大模型世界的三重度量衡:文本的切分单位、注意力的内存单位、账单的计价单位。

这三个角色是同一件事的三个侧面:模型只能看见 token 序列,注意力机制按 token 占用显存,厂商按 token 收钱。搞懂它,才能回答一串真实的工程问题——中英文各花多少 token、长上下文为什么按量加价、输出为什么比输入贵 5 倍、Prompt Caching 凭什么把 agent 账单砍掉一个数量级。本文按"是什么 → 怎么炼成 → 怎么计费 → 怎么省钱"组织:第 1、2 节讲 token 与分词原理,第 3、4 节讲上下文窗口与计费结构,第 5~7 节讲缓存工程与省钱实战,最后总结。


1、Token 是什么

无论输入的是中文、英文还是代码,文本都会先被分词器(tokenizer)切成 token 序列,每个 token 映射为词表里的一个整数 ID。同一个字符串在不同环节的形态如下:

环节 形态
你写的 深入理解 token
分词器切分 ["深入", "理解", " token"]
模型看到的 [52064, 24531, 8675](示意)

模型的世界里只有这些整数 ID,它从不直接看见字母或汉字——这个事实能解释后面一整批"灵异现象"。切分粒度则是在两个极端之间折中,对比如下:

粒度 序列长度 词表大小 致命问题
按字符 极长 极小 语义稀碎,注意力开销平方级膨胀
按单词 极短 无限大 词表爆炸,没见过的词直接抓瞎
按子词 短 可控 高频整存、低频拆存,两边兼顾

子词方案的核心思想是常见的整体存,罕见的拆着存:tokenization 拆成 token 与 ization,unhappiness 拆成 un 与 happiness,词表不必无限膨胀,又能表示任何新词。

英文的经典经验值是 1 token 约等于 3.5~4 个字符、0.75 个单词,100 万 token 约 75 万个英文单词。中文没有这么好的福气:对以英文语料为主的词表,高频汉字或双字词占 1 个 token,一般汉字 1~2 个,生僻字和领域黑话 2~3 个。同一句话,中文的字符数更少,token 数常常反而更多,根源在第 2 节。

理解了"模型看到的是 token 不是字符",几个著名翻车现场就都通了:

  • 数不清 strawberry 里有几个 r:这个词在多数词表里切成两三片,模型看到的是几个 ID,不是 10 个字母的序列;
  • 分不清 9.11 和 9.9 哪个大:小数被切成 9、点、11 这样的碎片,再叠加训练语料里"版本号 9.11 更新"的干扰;
  • 长数字算术拉胯:手机号被按一至三位一组切碎,竖式进位对模型像戴着棉手套捡硬币。

一句话:token 边界就是模型的视力盲区,一切逐字符的精确操作都是天然弱项。

2、BPE 分词原理

2.1、训练过程手推

主流模型普遍使用 BPE(Byte-Pair Encoding)或其变体。训练过程朴素得出奇:初始词表是全部单字节,反复统计语料中最高频的相邻符号对,把它合并成新符号加入词表,直到词表达到预设大小。方法出自 BPE 的原始论文,用论文里的经典语料走一遍:设词频为 low 五次、lower 两次、newest 六次、widest 三次,全部拆到单字符后逐轮统计合并。

轮次 最高频相邻对 出现次数 新增符号
1 e 与 s 9(newest 加 widest) es
2 es 与 t 9 est
3 est 与词尾标记 9 est 连词尾

若干轮后词表里会有 low、newest 这样的高频整词。此时新词 lowest 即使从未整词收录,也能切成 low 加 est——两个碎片都高频、都有语义。推理时的切分是反过程:按词表对输入做贪心最长匹配。词表是高频统计炼出来的,天然偏爱训练语料的语言分布,这直接决定了各语言的 token 效率。

2.2、字节级与三大流派

现代实现是字节级 BPE:初始词表为 256 个字节,任何语言、emoji 甚至乱码都永远可表示,彻底解决生僻词问题。代价是对训练语料里没怎么见过的语言,一个字符要占两三个字节、对应的高频合并又没学过——中文的 token 税根源在此。主流分词算法对比如下:

算法 代表模型 特征
BPE GPT 系列、Claude 自底向上,按频率合并
WordPiece BERT 按似然提升合并
Unigram T5、部分 Llama 自顶向下删词表

工程实现里最常碰到的是 OpenAI 开源的 tiktoken,以及 Unigram 流派的著名实现 SentencePiece。词表大小是显式权衡:词表越大,同样文本 token 越少、推理越快,但 embedding 矩阵越大。GPT-4 的 cl100k_base 词表约 10 万,GPT-4o 的 o200k_base 约 20 万,官方口径是非英语文本最多省约七成 token。token 数是词表与算法的属性,不是文本的固有属性:同一文本迁到新一代 Claude 的词表,token 数约变成 1~1.35 倍,评估成本时要单价与 token 数一起算。

3、上下文窗口与 KV cache

上下文窗口是输入与输出共享的预算:系统提示、工具定义、全部对话历史、模型输出(含思考与工具调用)加总不能超上限。当前一代 Claude 普遍为 1M 窗口、128K 输出上限(官方文档),窗口内三个细节常被忽略:

  • 输出也占预算,长输入会挤压可用输出;
  • 工具定义也是输入,一套像样的 schema 轻松上千 token,每个请求都要付费;
  • 每条消息的角色标记、分隔符等包装开销同样计费,本地裸估永远比账单少一点。

窗口尺寸的演进是一场军备竞赛,时间线如下:

timeline title 上下文窗口演进 2020 : GPT-3 2K 2023 : GPT-4 128K 2024 : Claude 3 200K 2026 : Claude 1M

图中五年间窗口涨了约 500 倍,但能装不等于免费。Transformer 生成每个 token 都要回看全部前文,推理引擎会把每个 token 在每层的 Key、Value 向量缓存下来,这就是 KV cache,显存占用约等于 2 乘层数乘 KV 总维度乘每元素字节数乘序列长度。代入公开架构可查的 Llama-2-7B(fp16)约为 512 KiB/token,不同窗口的显存账如下:

上下文 KV cache 显存
4K 2 GiB
128K 64 GiB
1M 512 GiB

显存随 token 线性膨胀、注意力计算随 token 平方增长,这就是部分厂商对超长输入按量加价的物理根源。同时大窗口也不等于好记性:Lost in the Middle 论文实证了埋在上下文中部的关键事实最容易被忽略,无关内容越多注意力越被稀释。工程正解是上下文管理——RAG 检索精准片段、历史裁剪与摘要化,而不是把一切塞进窗口。

4、计费模型解剖

输入比输出便宜约 5 倍,根源在两段式推理。Prefill 处理输入可以对全部输入 token 并行做矩阵乘,GPU 越批量越高效;Decode 生成输出必须逐 token 串行,每步还要把整个 KV cache 从显存读一遍,卡在显存带宽上。所以输出单价约 5 倍于输入是全行业结构,不是厂商心情。

以下是 Claude API 的价格快照(美元每百万 token,信息截至 2026-11,以官方价格页为准):

模型 上下文 输入 输出 缓存读取
Claude Fable 5.2 1M $8 $40 $0.20
Claude Opus 5.5 1M $4 $20 $0.20
Claude Sonnet 5.5 1M $2 $10 $0.10
Claude Haiku 5.5 1M $0.10 $0.50 输入超 100K 后加价

单价之外有三个结构性折扣:Batch API 半价(离线任务 24 小时内返回)、缓存写入 1.25 倍(第 5 节)、fast mode 约两倍价换约 2.5 倍生成速度。那一次 agent 请求的钱具体花在哪?以 Claude Code 的典型请求为标本,输入构成如下:

系统提示(含 CLAUDE.md、技能说明)   1 万+     每请求都在,高度稳定
工具定义(几十个 tool 的 schema)    数千      每请求都在,高度稳定
对话历史(逐轮累积)                 逐轮增长   会话内稳定
本轮新内容(工具结果、文件内容)     变化部分   真正的新信息
────────────────────────────────────────────
输出 + 思考                          按 5 倍单价计费

图里前三块在每个请求里字节级相同,却随轮次线性膨胀:一个 50 轮会话,同样的几万 token 前缀要按全价重复付 50 次。这就是下一节存在的理由——agent 场景里输入端的优化空间远大于输出端。

5、Prompt Caching 杠杆

5.1、前缀匹配原理

Prefill 的产物就是 KV cache,而系统提示、工具定义、历史在每个请求里一模一样——那为什么不把算好的缓存直接复用?这就是 Prompt Caching:命中部分不用重算,按约 0.1 倍价读取(最新模型低至 0.025~0.05 倍),且多数模型上不计入输入速率限制。判定流程如下:

flowchart LR A[请求到达] --> B{前缀字节相同} B -->|是| C[命中按 0.1 倍读] B -->|否| D[未命中全价重算]

图中的判定条件就是缓存的第一铁律:

Prompt caching 是前缀匹配:前缀中任何位置变一个字节,它之后的所有缓存全部失效。

渲染顺序是 tools、system、messages 逐层在前,系统提示里一个时间戳、一个按用户拼接的问候语、一次 JSON 字段重排,都能让后面几万 token 的缓存归零——而且是静默失败,请求照常成功,账单悄悄变贵。

5.2、计费与盈亏平衡

按官方缓存文档口径:写入 5 分钟 TTL 收 1.25 倍、1 小时 TTL 收 2 倍,读取约 0.1 倍(Fable 5.2 低至 0.025 倍,Opus 5.5 与 Sonnet 5.5 为 0.05 倍),每次读取还会刷新存活时间。以 5 分钟 TTL 为例,同一前缀被使用的总成本如下:

使用次数 不缓存 缓存 节省
2 次 2.0 1.25 加 0.1 共 1.35 33%
10 次 10 1.25 加 0.9 共 2.15 79%
100 次 100 1.25 加 9.9 约 11.2 89%

两次使用即可回本,十次省近八成,agent 会话动辄几十轮,这就是它成为省钱第一杠杆的原因。还有一个隐形门槛:前缀必须够长才可缓存,低于下表长度会静默不缓存:

模型代次 最小前缀
Opus 5.5、Sonnet 5.5 等最新一代 512 token
Opus 4.8、Sonnet 5、Sonnet 4.5 1024 token
Opus 4.7 2048 token
Opus 4.6、4.5、Haiku 4.5 4096 token

注意这个下限不随代次单调变化,换模型要重新核对。

6、缓存友好的工程实践

第一件事是布局按稳定性排序:永不变化的系统提示和工具定义放最前,会话级内容居中,每请求变化的时间戳、检索结果、用户问题放最后;每请求最多 4 个 cache_control 断点,打在稳定性边界上。第二件事是消除静默失效源,高频肇事模式如下:

模式 后果
系统提示拼 datetime.now() 或用户名 每请求都是新前缀,缓存全军覆没
json.dumps 不加 sort_keys 序列化不确定,字节级漂移
按用户动态增删工具 工具渲染在最前,动一个全失效
中途换模型 缓存按模型隔离,必失效
每轮注入提醒又在下轮删除 属于改写历史,从该点起全失效

注意:缓存失效是静默的,请求照样成功,只是账单变贵,没有任何报错。

会话中途下指令(切模式、更新状态)不要改顶层 system 字段——它在整个历史之前,一改全失效——而是往 messages 末尾追加一条 role: system 消息,它排在已缓存历史之后,前面的缓存毫发无损。验证靠响应里的 usage 三字段,健康形态如下:

字段 健康形态
cache_read_input_tokens 读走全部历史前缀,逐轮增长
cache_creation_input_tokens 只有上一轮新增部分,很小
input_tokens 断点后的尾巴,很小

若 cache_creation 每轮都接近整个会话大小,说明前缀在某处被改写;打日志 diff 相邻请求体,第一个分歧点就是凶手。真实输入总量是三字段之和,别只看 input_tokens。

最后是三个进阶细节。断点回看窗口只有 20 个块,单轮追加超过 20 块要中途补断点;缓存条目要等首个响应开始流出才可读,N 个并发同前缀请求会全部付全价,应先发一个、见到首 token 再放其余;服务启动时可发一个 max_tokens 为 0 的请求预热缓存,零输出计费,换来首个真实请求的低延迟。

7、省 token 实战与误区

输入侧的优先级很明确:先把缓存做好,收益常大于其余所有技巧之和;再裁剪输入——只发 diff 不发整文件、只发命中的检索片段、历史分级保留(近期全量、早期摘要)。格式侧最容易被忽视,同样 1000 行数据,JSON 数组的写法如下:

[
  {"user_id": 123, "plan": "pro", "region": "cn"},
  {"user_id": 456, "plan": "free", "region": "us"}
]

每行约 18~20 token,重复的长键名是纯浪费。改成行式数据加一行表头说明,开销立减:

user_id,plan,region
123,pro,cn
456,free,us

行式每行约 5~7 token,省六成以上。输出侧记住单价是输入的 5 倍:长文生成先估输出量,提示词里明确要求简洁,思考类模型的 effort 档位直接控制思考 token 花销,简单任务降档省的是真钱。中文场景另有两招:同一语义中文 prompt 常比英文贵,高频入口 prompt 值得 A/B 实测;领域黑话大概率被切碎,高频术语考虑定义缩写。

数 token 的正确姿势是用官方 count_tokens 端点(官方文档),并传入与推理相同的模型 ID,示例如下:

from anthropic import Anthropic

client = Anthropic()
resp = client.messages.count_tokens(
    model="claude-opus-5-5",  # 与推理同模型,换模型要重数
    messages=[{"role": "user", "content": open("prompt.txt").read()}],
)
print(resp.input_tokens)

官方口径是 tiktoken 数 Claude 会少估 15%~20%,代码与非英文误差更大;认真做预算时把 system 与 tools 一起传进去数。最后澄清几个高频误区:

  • token 不是单词:英文约 0.75 词每 token,中文一个字 1~2 token,只能实测;
  • 字符少不等于省 token:对英文语料词表,中文常花更多 token;
  • 窗口大不等于回答好:容量不是智力,无关内容稀释注意力且成本按平方涨;
  • 费用大头未必在输出:agent 场景输入常占大头,缓存才是第一杠杆;
  • 本地 tokenizer 数的不是账单:词表不匹配就有 15% 起步的误差。

8、总结

回顾全文:token 是模型唯一可见的文本形态,切分边界是它的视力盲区;token 数由词表决定,随厂商、代次、语言变化,中文对英文语料词表天然吃亏;上下文窗口是输入输出共享的预算,KV cache 给了长上下文明确的物理成本;计费结构是输入单价乘五约等于输出单价,而 Prompt Caching 用 1.25 倍写、0.1 倍读把重复前缀的成本压掉一个数量级。对于正在做 AI 应用的团队,建议今天就做三件事:用 count_tokens 建立真实 token 基线,把系统提示与工具定义按稳定性重排并打上缓存断点,再用 usage 三字段验证缓存命中率——这三步通常能把账单砍掉一半以上。

相关推荐

​Jev 详解:不做生成的判断模型
精选AI

​Jev 详解:不做生成的判断模型

Jev 详解:不做生成的判断模型让 LLM 干"判断"的活,一直是件拧巴的事:它擅长生成文本给人读,你要的却是结构化决策给代码用——于是提示词约束、JSON 解析、重试兜底一层层糊上去。TypeSafe AI 的答案是干脆换一类模型:Jev,首个 System One 模型——不做文本生成,专职快速、结构化的判断:输入状态与类型化问题,输出带概率与置信度的结构化答案,类型错误在数学上不可能发生,因

716
网站被注入恶意脚本排查实录:AI 辅助安全分析实战
置顶 精选AI

网站被注入恶意脚本排查实录:AI 辅助安全分析实战

网站被注入恶意脚本排查实录:AI 辅助安全分析实战某天收到华为云监管反馈,称个人博客网站存在色情违规界面。作为技术博主,这简直是晴天霹雳——网站一直正常运行,怎么会突然出现这类问题?经过排查发现,网站被注入了恶意跳转脚本。本文记录完整的排查过程,重点分享如何借助 AI 快速定位问题并修复,希望能给遇到类似情况的开发者提供参考。1、问题发现与初步排查收到监管反馈后,第一时间打开网站检查。首页看起来完

414
时隔三年,再次备份:从手搓博客到 AI 全面自动接管
精选AI

时隔三年,再次备份:从手搓博客到 AI 全面自动接管

时隔三年,再次备份:从手搓博客到 AI 全面自动接管今天打开尘封多年的备份目录,看到了一个熟悉的文件名——2023_07_09.sql。那一刻,恍如隔世。三年前的今天,我曾经认真地导出过一次数据库;三年后的今天——2026 年 7 月 9 日,自动备份启用。整整三年,1095 天,我的博客就这样安安静静地跑着,没有人备份,也没有人管,它就那么稳稳当当地活着。感慨万千,大概是此刻最准确的四个字——不

372