中文 token 怎么算?别用字数估成本,用真实样本去数
中文 token 怎么算,是我被问得最多的问题之一,而且提问的人几乎都带着同一个期待:给我一个换算比,我拿字数一乘就知道要花多少钱。这个期待本身就是坑。token 不是字,而且每个模型的分词器都不一样——同样一段中文,换个模型去数,得到的 token 数就变了。用一个通用比例去做预算,量小的时候看不出来,量一大就崩。
先说一个我见过好几次的场景。产品那边给了个需求:客服机器人,日均 8000 次对话,问技术这个月要花多少钱。技术翻了翻文档,看到一句”中文一个字大概是一点几个 token”,拿平均对话字数一乘,报了个数。上线一个月后账单出来,差了好几倍。复盘的时候才发现,问题根本不在那个换算比准不准——真正吃掉大头的东西,压根就不在他数的那段文字里。
token 到底是什么
模型不直接读字符,它读的是分词器(tokenizer)切出来的片段。分词器是模型自带的一部分,训练的时候就定好了,它把输入文本切成一串编号,模型处理这串编号,计费也按这串编号的个数算。
关键在于「切法」这件事没有统一标准。英文里一个常见单词可能是一个 token,生僻词会被拆成几块;中文的切法则完全取决于这个分词器在训练时见过什么样的语料——有的模型会把常见的双字词、三字词整体切成一个 token,有的则倾向于按字甚至按字节切。所以同一句中文:
- 在 A 模型上可能被切成 12 个 token
- 在 B 模型上可能被切成 20 个 token
- 同一家厂商的新老两代模型,切出来的结果也可能不同
这不是谁更”高效”的问题,而是它们本来就是不同的东西。你不能拿在 A 模型上量出来的比例去估 B 模型的账单。
还有几件事经常被漏掉:标点、空格、换行、emoji 都占 token。全角逗号、句号是要计费的;你为了让提示词好读而加的空行、缩进,也是要计费的。这些东西单看很少,乘上几十万次调用就不是小数。
为什么「一个汉字等于多少 token」没有标准答案
我在这篇文章里明确拒绝给你一个固定比例,理由不是谨慎,是因为给了反而害人。
想想一个错误比例会怎么传播:你在方案文档里写下”按 1 个汉字 1.5 个 token 估”,评审通过,预算按这个数批下来。半年后业务量涨了十倍,如果实际模型的切分比这个数高出四成,你的账单就直接超出预算四成——而这时候没人会回头怀疑那个当初随手写下的系数,大家只会觉得”AI 就是烧钱”。更麻烦的是中途换模型:新模型的分词器不一样,你那个系数一夜之间失效,但代码里、报表里、预算表里全是按它算的。
而且这个比例本身就不稳定,它至少受这几样影响:
| 影响因素 | 说明 |
|---|---|
| 分词器不同 | 不同厂商、不同架构的模型自带不同分词器,切法差异可能很大 |
| 版本迭代 | 同一家的不同代模型可能换过分词器 |
| 文本类型 | 日常口语、专业术语、混排英文、代码,密度完全不同 |
| 标点与排版 | 全角标点、空行、缩进、markdown 符号都会计入 |
| 语种混排 | 中英夹杂的技术文档,和纯中文散文不是一个量级 |
所以正确的问法不是”中文一个字几个 token”,而是”我这段真实文本,在我实际要用的那个模型上,是多少 token”。这个问题有确定答案,而且你现在就能量出来——把文本粘进 token 计数器 直接数,比任何经验系数都靠谱。想把 token 这个概念本身搞明白,可以看 token 是什么?和字数怎么换算;想知道分词器为什么会这么切,可以看 Tokenizer 原理。
需要提醒一句:任何离线计数工具给的都是近似值,用来做量级判断和相互比较很够用,但要卡到精确数字,最终以你实际调用后 API 返回的用量字段为准。跑通几十次真实请求,把返回的用量记下来求平均,这个数才是可以写进预算表的。
真正吃 token 的东西,往往不是正文
这一节是整篇里最实用的。大部分人估算时只数了”用户问的那句话 + 模型答的那段话”,然后就得出了一个偏低到离谱的数字。一次请求里实际发出去的内容,远不止这两块。
系统提示词。 每一次请求都会带上它。你写了两千字的角色设定、语气要求、禁止事项,这两千字对应的 token 每一次调用都要付一遍。它是纯固定开销,调用量越大越显眼。很多团队的系统提示词是一路”补丁”式加上去的——出一次问题就加一段约束,半年下来臃肿得没人敢删。
工具/函数定义。 只要你用了 function calling,工具的 JSON schema 就会随请求一起发出去,而且是每次都发。参数描述写得越详细越占地方,工具数量越多越占地方。我见过工具定义比系统提示词还长的项目,而这部分开销在监控面板上是完全隐形的——你只看到输入 token 涨了,不知道是被 schema 吃掉的。
多轮对话历史。 这是长会话成本失控的头号原因。模型本身没有记忆,你要它记得上文,就得把上文原样再发一遍。第 10 轮请求携带的历史,是前 9 轮所有内容的总和。也就是说,一次 20 轮的会话,累计发出去的 token 大致按轮次的平方在长,而不是线性——单轮看着不贵,整场会话下来就很吓人。想细算这块,可以配合 上线前怎么估算月成本 里的场景拆分法。
结构化输出的格式说明与示例。 为了让模型稳定吐 JSON,很多人会在提示词里塞上完整的字段说明加两三个示例。示例写得越全,输出越稳,但每次请求都在为这份稳定性付费。
结构化文本本身的密度。 JSON、代码、表格、HTML 这类内容,符号(括号、引号、冒号、逗号、缩进)占比极高,而这些符号大多会被切成独立的 token。同样”看起来”一屏的内容,一段散文和一段 JSON 的 token 数可能差出一大截。检索增强(RAG)注入的片段如果没做清洗,把原文里的 markdown 符号、脚注、空行都带进来,这部分虚耗相当可观。
把这些拼起来,一次请求的 token 构成大致是这样一张清单——估算时要数的是整张表,不是其中一行:
| 组成部分 | 每次请求都发? | 会随使用增长? |
|---|---|---|
| 系统提示词 | 是 | 否(固定开销) |
| 工具/函数定义 | 是(启用工具时) | 随工具数量增长 |
| 输出格式说明与示例 | 是 | 否 |
| 对话历史 | 是(多轮场景) | 是,随轮次快速增长 |
| 检索注入的上下文 | 视场景 | 随召回条数增长 |
| 本轮用户输入 | 是 | 否 |
| 模型输出 | —— | 随回答长度增长 |
顺带一提,输入和输出通常是分开计价的,两者单价不一样,所以估算时必须拆开数,不能合成一个总量再乘一个平均单价。这块的规则可以再看 上线前怎么估算月成本。
正确的估算流程
我的做法固定为五步,每一步都不跳。
第一步:取真实样本,不要造例句。 从你已有的日志、工单、客服记录里捞 30 到 50 条真实请求,尽量覆盖长中短各种情况。自己编的”你好,请问怎么退货”这种例句,长度和真实用户输入根本不是一回事。如果产品还没上线,就找同类业务的历史文本,或者让几个同事按真实场景手打一批。
第二步:用目标模型的计数器逐条数。 注意是「目标模型」——你打算上线用哪个模型,就用哪个模型的分词器去数。同一批样本换个模型要重新数一遍。数的时候记得把系统提示词、工具定义、格式说明这些固定块单独数一次,它们不随样本变——这几块直接粘进 token 计数器 数一遍,两分钟就有结果。
第三步:算出「每次业务操作的平均 token」。 这一步是整个流程里最有价值的产出。所谓”一次业务操作”是业务视角的单位——一次完整问答、一次文档摘要、一次工单分类,而不是一次 HTTP 请求(有些业务操作背后是多次模型调用,那就把它们加起来)。
这个中间量为什么重要:它只跟你的业务形态和提示词设计有关,跟单价无关。换平台、换模型、厂商调价,它基本不变;而单价是公开信息,随时能查。有了它,任何”换到 X 平台要花多少钱”的问题,都变成一次乘法,不用重新做一遍调研。它也是优化效果的度量衡——你压缩了提示词,就看这个数降了多少,比看月账单直观得多。
第四步:乘调用量得到月 token 量。 输入和输出分别乘。
第五步:再乘单价。 单价去官方定价页查,以官方为准。
下面走一遍算术。以下所有参数都是我为了演示编的假设值,不对应任何厂商的真实价格,也不代表任何真实业务的实际用量,你必须用自己量出来的数替换:
假设场景是一个带工具调用和知识库检索的客服机器人,一次业务操作 = 一轮完整问答。假设量出来的构成是:
| 组成部分 | 假设 token 数 |
|---|---|
| 系统提示词 | 900 |
| 工具定义(3 个) | 1200 |
| 对话历史(保留 6 轮,每轮约 260) | 1560 |
| 本轮用户输入 | 120 |
| 检索注入片段 | 1400 |
| 输入合计 | 5180 |
| 模型输出 | 350 |
单次输入合计:900 + 1200 + 1560 + 120 + 1400 = 5180 token。加上输出 350,一次业务操作总共 5530 token。
假设日均 8000 次业务操作,一个月按 30 天算,月调用量 = 8000 × 30 = 240,000 次。
- 月输入 token = 5180 × 240,000 = 1,243,200,000,即 1243.2 M
- 月输出 token = 350 × 240,000 = 84,000,000,即 84 M
再假设单价为输入 1 元 / 百万 token、输出 3 元 / 百万 token(纯占位数字,随手编的):
- 输入费用 = 1243.2 × 1 = 1243.2 元
- 输出费用 = 84 × 3 = 252 元
- 合计 = 1495.2 元
换个角度用「每次业务操作」核对一遍:单次成本 = 5180 ÷ 1,000,000 × 1 + 350 ÷ 1,000,000 × 3 = 0.005180 + 0.001050 = 0.006230 元,约合 0.62 分。乘以 240,000 次 = 1495.2 元,和上面对得上。
这个交叉验算的习惯建议保留:两条路径算出同一个数,说明你没把哪一块漏掉或重复计入。
注意看这张表里的比例——用户真正输入的那 120 个 token,只占输入的百分之二点几。如果按开头那种”拿用户问题的字数乘个系数”的估法,你会得到一个比真实值小几十倍的数字。差的不是系数准不准,是你根本没数该数的东西。
省 token 的实操,以及每条的代价
每一条都有代价,没有白拿的优化。
压缩系统提示词。 把重复表述、过度铺陈的礼貌用语、已经被模型默认遵守的常识性约束删掉,是纯赚。代价是删过头会让输出质量掉下来,所以每删一版都要跑一遍评测集比对,别凭感觉删。
裁剪对话历史。 滑动窗口(只带最近 N 轮)实现简单、效果立竿见影,代价是模型会”忘记”更早的信息,用户问”我刚才说的那个订单”时就答不上来。摘要式压缩(把早期轮次压成一段摘要再带上)能保留更多信息,代价是每次摘要本身也是一次模型调用,有额外成本和延迟,而且摘要会丢细节。实践中常见做法是两者结合:最近几轮原样保留,更早的压成摘要。
控制输出长度。 输出单价通常比输入高,而且模型不会自己收着写。在提示词里明确要求篇幅、设置最大输出长度上限,都能直接砍成本。代价是限太死会出现回答被截断,或者模型为了塞进限制而省掉必要的解释。
精简工具定义。 参数描述写到够用即可,不要把整份文档塞进 description;同时按场景动态挂载工具,一次请求只带这个场景可能用到的那几个,而不是把所有工具都发过去。代价是动态挂载需要你自己写一层路由判断,判断错了模型就调不到该调的工具。
别让模型复述输入。 常见的浪费是让模型”先把原文重述一遍再分析”,或者输出 JSON 时把输入内容整段回填。这些 token 按输出价计费,是最贵的那一档。代价是某些任务(比如需要精确引用原文片段)确实要模型带上原文,那就限定只回引用必需的部分。
优先复用与批处理。 如果你的固定前缀(系统提示词 + 工具定义)很长而且每次都一样,缓存类机制往往能省下可观的重复输入费用;对时效不敏感的离线任务,批量接口通常也有价格优势。具体的可用性、规则和折扣以各平台官方文档为准,别照抄别处的经验数值。
把前面那个算例套用一下:假设把对话历史从 6 轮压到 3 轮(减少 780 token),工具定义从 1200 精简到 500(减少 700 token),输入就从 5180 降到 3700。月输入 token = 3700 × 240,000 = 888,000,000,即 888 M,费用 888 元;加上输出 252 元,合计 1140 元。相比原来的 1495.2 元省下 355.2 元,降幅约 23.8%。两处改动,接近四分之一的账单——这就是为什么要先搞清楚 token 花在哪儿,再去谈优化。
自查清单
- 你手上的估算,是基于真实样本量出来的,还是套了一个网上看来的换算比例?后者请作废重来。
- 你数的是整次请求,还是只数了用户输入和模型输出?系统提示词、工具定义、对话历史、检索片段有没有算进去?
- 输入和输出是不是分开统计、分开乘单价的?
- 你数 token 用的分词器,是不是你实际要上线的那个模型的?换模型后有没有重新数?
- 有没有把「每次业务操作的平均 token」单独记下来?换平台、比价、评估优化效果都要靠它。
- 算例里的调用量是按业务操作数还是 HTTP 请求数?一次业务操作背后有几次模型调用,心里有数吗?
- 多轮会话场景,有没有按最长会话(而不是平均会话)估过一遍上限?
- 上线后有没有把 API 返回的真实用量记进监控,跟估算值定期对账?
想知道你那段文本在你那个模型上到底是多少 token,别猜,用 token 计数器 数一遍。