☰
中文Token换算与成本优化:1个汉字多少Token、上下文窗口与切块实测
2026/10/28 13:08:22 网站建设 项目流程

有人问我,一段两千字的中文稿子丢给模型,账单上为什么会跳出一串看不懂的数字;也有人抱怨接口报错说超出上下文,可他明明按字数数过,离上限还差一大截。这类问题的根子通常只有一个:把“字数”当成了 Token。AI-Token 既不是字符,也不是词,它是分词器切出来的最小语义碎片,模型真正读进去的是一串碎片编号。至于 1 个 Token 对应几个中文汉字,这个问题没有唯一答案,但有可复现的经验区间和靠谱的测算方法。这篇内容写给几类人:天天写提示词想控制成本的、做知识库切块被截断折磨过的、要给非技术同事解释“为什么中文比英文贵”的、以及刚接手一个模型调用项目需要做容量估算的。下面我把自己踩过的坑、量过的数据、写过的脚本摊开讲,能直接抄的部分我都标出来了。

1. 先把 Token 这件事说透:它到底是文本里的哪一段

1.1 分词器干的是“切菜”的活,不是“数数”的活

模型本身不认识文字,它只认识整数。分词器(tokenizer)负责把一段字符串切成若干片段,每个片段查词表换成一个整数 ID,这串 ID 才是喂进网络的输入。所以严格讲,Token 是“词表里的一个条目”,它的长度完全取决于词表怎么建的,跟人类直觉里的“一个字”“一个词”没有必然对应关系。你可以把词表想成一副乐高积木:有的积木是单个零件,有的积木是已经拼好的一整块窗户。同一句话,用不同套装去拼,用的块数当然不一样。

这里有个特别容易被忽略的点:分词发生在文本进入模型之前,模型内部的自注意力、前馈网络全都以 Token 为单位在算。上下文窗口写的是 32768 Token,说的就是“能塞进去 32768 块积木”,而不是 32768 个汉字。很多人第一次做长文档摘要失败,就是栽在这个换算上——他以为 2 万字的文档离 32k 上限还远,实际算下来可能已经逼近 3 万 Token。

还有一个隐藏成本:聊天接口里的角色标记、分隔符、系统提示词模板,全都是要占 Token 的。你看到的“用户消息内容”只是冰山露出水面的部分,水下的模板开销每次调用都在扣钱。

1.2 词表是统计出来的,不是语言学家定的

主流做法叫 BPE(Byte Pair Encoding,字节对编码),流程不复杂:先把语料拆到最小单元(字节级就是 256 个基础字节),然后统计相邻两个单元一起出现的频率,把频率最高的那一对合并成新条目,加进词表;重复这个过程几万次,词表就长成了。合并顺序是有先后的,先合并的往往是高频组合,比如英文里的th、ing,中文里的“我们”“可以”“因为”。

字节级 BPE 有个非常关键的副作用:因为基础单元是字节,任何字符都能被表示,不会出现“词表里没这个字就崩掉”的情况。代价是生僻字会被拆得很碎。举个具体的:一个常用汉字在 UTF-8 下占 3 个字节,如果这个字在词表里独立成条,那它就是 1 个 Token;如果不在,就得靠 2 到 3 个字节级 Token 拼出来,运气差的组合字甚至要 4 个。这就是为什么同一段中文,换个模型 Token 数能差出三成。

词表大小是个工程取舍。词表小(三万左右),嵌入矩阵省显存,但序列变长、推理变慢;词表大(十万以上),单 Token 承载的信息多、序列短,但嵌入层参数量 = 词表大小 × 隐藏维度,词表翻倍嵌入矩阵也翻倍,输出层的 softmax 计算量同样水涨船高。所以你看到有的模型标榜“中文压缩率高”,本质是它在建词表时把中文语料的权重调高了,让常见汉字和常见词尽量独立成条。

1.3 中文为什么普遍“更费 Token”

原因在编码层面。英文 26 个字母加标点,一个字节就能表示一个字符,词表里很容易覆盖各种词根词缀组合,一个 Token 平均能扛 4 个字符。中文要走 UTF-8 三字节路线,一个汉字起步就是 3 个字节,词表要覆盖常用汉字本身就得占掉几千到上万个条目,剩下的名额才轮到词组。中文优化做得一般的分词器,一个汉字一个 Token 是常态;做得好的,能把“人工智能”“数据库”“服务器”这类高频双字三字词压成单 Token,整体压缩率立刻好看一大截。

这也是很多人对比账单时觉得“中文用户被区别对待”的技术原因:同样一段意思,中文的 Token 数往往是英文的 1.5 到 2 倍。不是计价规则偏心,是分词效率的客观差距。知道这一点,你在做预算和容量规划时就会留出冗余,而不是拿英文的换算系数硬套。

2. 1 个 Token 到底对应几个中文汉字:把账算清楚

2.1 经验区间与它的来源

先给结论,省得你翻到最后:在中文语料训练充分的分词器上,1 个 Token 平均对应 1.3 到 1.8 个汉字;在英文为主、中文覆盖一般的分词器上,这个数字会掉到 0.6 到 1.0。反过来记更好用:1 个汉字大约等于 0.55 到 0.75 个 Token,做保守估算时按 0.75 取,做容量规划时按 0.8 取。

这个区间不是我拍脑袋定的,它来自分词器的两个硬约束。第一是汉字覆盖率,词表里收录了 6000 个常用汉字,那么日常文本里九成以上的字能独立成 Token,压缩率天然接近 1:1;如果只收录 3000 个,剩下那些字就得靠字节拼,比例立刻恶化。第二是词组合并的深度,只合并单字的分词器,一个四字成语要 4 个 Token;合并了大量高频词的分词器,可能 2 个 Token 就搞定。

下面这张表是我在不同类型文本上反复测出来的对照,横向是文本类型,纵向是“每个 Token 平均承载多少汉字”。注意这只是量级参考,具体数值随分词器版本浮动,同一模型不同版本词表都可能改。

文本类型每 Token 承载汉字数直观感受
纯中文口语对话1.4 ~ 1.7最省,高频词多
中文新闻、说明文1.3 ~ 1.6比较省,句式规整
中英混排技术文档1.0 ~ 1.3英文术语拖后腿
中文含大量专有名词0.8 ~ 1.1生僻组合被拆碎
中文夹大量数字与符号0.7 ~ 1.0符号密度高
纯代码片段0.5 ~ 0.8(按字符算约 2.5~3 字符/Token)缩进和符号最贵
含 emoji 的社交文本0.5 以下单个 emoji 可能吃掉 4~8 个 Token

表格里最右边那行值得多说一句。emoji 在字节级 BPE 下是 4 字节起步,如果词表没收录这个具体符号,就会被拆成 2 到 4 个 Token,某些组合 emoji 带修饰符,能拆到 6 个以上。做用户评论分析这类任务时,满屏表情的文本 Token 数会爆炸,按字数估的预算直接翻倍。

2.2 四个真正影响换算比例的变量

第一个变量是词表覆盖度。同样是“龘”这种生僻字,在收录了它的词表里是 1 个 Token,在没收录的词表里是 3 到 4 个。人名、地名、专业术语、化学式、药品名,全都踩在这条线上。我做过一个医疗问答的预处理,把病历里的药名单独统计,发现这部分文本的 Token 密度比正文高了将近 40%,原因就是大量组合式药名在词表里没有整体条目。

第二个变量是标点与空格。中文全角标点“,。”“、”占 3 个字节,能否独立成条看词表;英文的“空格 + 单词”经常被合并成一个 Token,所以英文文本里空格几乎是免费的,而中文文本里如果混了大量英文单词,每个词前面的空格会额外增加开销。换行符\n通常是 1 个 Token,这就是为什么长文档里的空行很贵——一百个空行就是一百个 Token。

第三个变量是数字。这是个反直觉的点:连续数字的切分方式不统一,有的分词器按 3 位一组切,有的按单字符切。一串 20 位的编号,可能被切成 7 个 Token,也可能被切成 20 个。规律性强的数字(比如 1234567890)在某些词表里能整体成条,而随机的订单号、哈希值就没这个待遇。这也顺带解释了为什么模型做算术容易错——数字在它眼里是碎片的组合,不是数值。

第四个变量是专业术语与缩写。“Kubernetes”“PostgreSQL”这类长英文词,好的分词器能整词成条,差的要拆成 3 到 5 段。技术文档里这类词密度高,Token 数上去了,成本也就上去了。

2.3 用一个具体例子把账走一遍

拿一段 1000 字的中文说明文来算。假设分词器中文优化中等,按每 Token 1.4 个汉字算,正文约 714 个 Token。加上开头结尾的对话模板(角色标记、分隔符,通常 20 到 40 个 Token),一次调用实际消耗约 750 个 Token。如果这段文字里嵌了 30 个英文专业词,每个词平均多花 2 个 Token,那就再加 60,总数逼近 810。

再看输出侧。如果让模型生成 500 字的中文回答,按 1.4 的比例算是 357 个 Token;但输出侧的开销往往比输入侧更贵(很多接口的输出单价是输入的 2 到 4 倍),而且模型倾向于“多说”,实际生成 700 字也很常见。所以做成本估算时,输出侧要按输入字数的 1.5 倍预留,这个经验值比任何理论公式都好用。

3. 动手算:脚本实测与手工估算两套方案

3.1 工具选择:一定要用目标模型自己的分词器

这是最容易犯错的地方。很多人拿一个开源分词库去估另一个模型的 Token 数,误差能到 30% 以上,做容量规划时这个误差足以让你在生产环境翻车。原则很简单:调用哪个模型,就用哪个模型官方提供的分词器。绝大多数推理框架和 SDK 都会跟随模型发布分词器文件(通常是词表 + 合并规则两个文件),加载方式和分词库无关,核心就是encode和decode两个方法。

如果你实在拿不到目标模型的分词器(比如某些闭源接口不开放),退一步的做法是:用接口返回的 usage 字段反推。先发一段已知长度的文本,看返回的 prompt_tokens,反复几次,你就能拟合出这个模型的“汉字/Tokens”系数。这个方法糙但有效,尤其适合做预算评审时给个可信数字。

需要提醒的是,加载分词器时要确认版本。同一模型的小版本迭代有可能调整词表,前一个月拟合出来的系数,下个月可能就不准了。我的做法是在配置文件里写死分词器版本号,升级时跑一遍回归测试,对比同一批样本的 Token 数偏差,超过 5% 就重新标定。

3.2 一段可以直接跑的统计脚本

先上最基础的:给一段文本,返回 Token 数,同时把切分结果打印出来看。这段代码里的load_tokenizer是你需要替换成目标模型分词器加载方式的地方,各家的加载 API 名字不同,但encode的语义是一致的。

def analyze(tokenizer, text, label=""): ids = tokenizer.encode(text) pieces = tokenizer.convert_ids_to_tokens(ids) # 部分分词库无此方法,可用 decode 逐段看 n_char = len(text) n_cjk = sum(1 for ch in text if '\u4e00' <= ch <= '\u9fff') print(f"[{label}] 字符数={n_char} 汉字数={n_cjk} Token数={len(ids)}") if ids: print(f" 每Token承载汉字={n_cjk / len(ids):.2f} 每字符消耗Token={len(ids) / n_char:.2f}") print(f" 前20个片段: {pieces[:20]}") return len(ids)

跑一遍你会看到很有意思的现象:高频词被整体切出来,生僻组合被拆成单字,英文单词前面带着空格成条。这个打印步骤我强烈建议保留,它是排查“为什么这段文本特别贵”的最快手段——直接看切分结果,哪里碎了就一目了然。

再上一个批量对比的版本,用来标定自己的业务文本:

def calibrate(tokenizer, samples): total_cjk = total_tok = 0 for name, text in samples.items(): n_cjk = sum(1 for ch in text if '\u4e00' <= ch <= '\u9fff') n_tok = len(tokenizer.encode(text)) total_cjk += n_cjk total_tok += n_tok print(f"{name}: 汉字{n_cjk} Token{n_tok} 比值{n_cjk / max(n_tok, 1):.2f}") print(f"整体比值: {total_cjk / max(total_tok, 1):.2f} 汉字/Token")

把你自己业务里的真实样本(客服对话、产品文档、代码片段各取几十条)喂进去,得到的整体比值比任何网上流传的系数都准。我一般会在项目启动阶段跑这么一次,把结果写进技术方案的成本章节,评审时没人会质疑。

3.3 没有分词器环境时的手工估算法

有时候你只是在写方案、做 PPT,没法跑代码,这时候需要一个能心算的近似公式。我常用的是这个:

import re def rough_tokens(text): cjk = len(re.findall(r'[\u4e00-\u9fff]', text)) words = len(re.findall(r'[A-Za-z]+', text)) digits = len(re.findall(r'\d', text)) punct = len(re.findall(r'[^\w\s\u4e00-\u9fff]', text)) return round(cjk / 1.4 + words * 1.3 + digits * 0.6 + punct * 0.6)

四项系数的含义分别是:汉字按 1.4 个一组;英文单词平均 1.3 个 Token(常见短词 1 个,长词 2 到 3 个,平均下来 1.3 比较准);单个数字 0.6 个 Token(三位成组的情况下单个数字不到 1);标点符号 0.6 个。这个公式在常规文本上的误差通常在 10% 到 15%,做预算够用了,做精确计费不行。

需要强调,这个公式只能用来“估”,不能用来“卡边界”。判断一段输入会不会超上下文,误差 10% 就可能让你在高峰期触发 400 错误。凡是涉及截断判断的地方,一律用真实分词器算。

提示:估算公式里的系数是跟着分词器走的,换模型必须重新标定。我在一个项目里图省事沿用了旧系数,结果新模型中文压缩率更好,实际 Token 比预估少了 20%,白白预留了一大截上下文,长文档处理能力被浪费了整整半年。

4. Token 数直接决定的三件事:钱、窗口、切块

4.1 计费:输入输出分开算,缓存能救命

绝大多数接口按“输入 Token 单价 + 输出 Token 单价”分别计费,输出单价通常是输入的 2 到 4 倍。这个价差带来的实践结论很直接:让模型少说废话,比让提示词少写几个字更省钱。一句“请详细解释”能让输出从 200 字膨胀到 800 字,多花的钱远超你精简提示词省下的那点。

算个具体的。假设输入单价是每百万 Token P 元,输出单价是 3P。一篇 5000 字中文文档,按 1.4 的比值算输入约 3571 Token,成本是 0.003571P;让模型输出 800 字总结约 571 Token,成本是 0.001713P。看起来输出更便宜?把量放大就不一样了:如果每天处理一万篇,输出侧的成本占比会稳定在六成左右。这就是为什么很多团队优化成本时先从输出格式下手——限制字数、要求 JSON 短字段、禁止复述原文。

前缀缓存是另一个省钱利器。很多接口支持对重复的提示词前缀做缓存,命中后按更低单价计费。系统提示词、少样本示例、固定知识库片段,这些每次调用都一样的部分放在最前面,缓存命中率能到九成以上。前提是前缀必须逐 Token 完全一致,中间插一个时间戳就把缓存打破了。这个细节坑过不少人,把动态内容放前面、固定内容放后面,缓存等于没开。

还有一点:重试也要钱。参数配错导致输出被截断,重试一次就是双倍成本;流式输出中途断开,已经生成的部分照样计费。所以max_tokens要给足,别为了省那点额度反复触发重试。

4.2 上下文窗口:可用空间比标称值小得多

标称 32k 上下文,不代表你能塞 32k 的输入。要扣掉的包括:最大输出预留、系统提示词、对话历史、工具调用的 schema 定义、以及每次请求的模板开销。我一般按这个公式算可用输入预算:

可用输入 Token = 窗口上限 - 最大输出预留 - 系统提示词 - 工具schema - 安全余量

举个实例,窗口 32768,最大输出设 4096,系统提示词 600 Token,两个工具的 schema 各 300 Token,安全余量留 500(防止分词误差和模板浮动),那么可用输入约 32768 - 4096 - 600 - 600 - 500 = 26972 Token。按 1.4 的比值换算,大概能塞 3.7 万个汉字。看着不少,但一轮轮对话历史累积下去,几轮之后就见底了。

长对话的膨胀速度值得单独说。每一轮用户消息和助手回复之间都有角色标记,通常 3 到 4 个 Token;如果你用了结构化的消息格式,每个对象还有字段名开销。十轮对话光标记就是七八十个 Token,加上内容,很容易到几千。我的处理方式是设一个历史预算(比如总窗口的四分之一),超了就做滚动摘要——把最早的几轮压成一段百字概述,只保留关键结论和实体。

4.3 切块与截断:按 Token 切,别按字符切

做知识库检索时,切块大小必须按 Token 定,按字符定迟早出事。同一个 500 字的块,中文纯文本可能 357 Token,混了英文术语和表格的可能 600 Token,超了嵌入模型的输入上限就被静默截断,检索效果莫名其妙地差,排查半天找不到原因。

我的经验参数是:块大小 400 到 600 Token,重叠 50 到 100 Token(约 10% 到 20%),中文对应大约 600 到 850 个汉字一块。重叠不能省,它保证跨块的句子至少有一个块能完整包含。切的时候优先按段落或标题切,不得已再按标点切,最后才硬切——硬切会把句子拦腰砍断,检索出来的片段读起来支离破碎。

代码和表格要特殊处理。代码块最好整块保留,别切开,一个函数被切两半基本就没法用了;表格也一样,宁可让这个块超一点,也别把表头和表体分开。我在一个项目里为了严格控制块大小硬切表格,结果检索出来的都是半截数据,问题排查了两天才定位到切块策略上。

5. 常见问题与排查实录

5.1 典型症状速查

症状大概率原因处理方式
报错超出上下文,但字数看着没超按字符估算了用目标分词器实测,重算预算
中文账单比英文高出一倍中文 Token 密度天然更高预算按 1 汉字 ≈ 0.75 Token 重算
输出 JSON 解析失败max_tokens太小被截断预留输出额度,或要求模型先输出结构骨架
同样的提示词,Token 数每天不一样分词器或模型版本变了锁定版本,跑回归对比
满屏表情的文本成本暴涨emoji 被拆成多个 Token预处理过滤或替换
长对话越聊越慢越贵历史累积吃满窗口滚动摘要 + 历史预算
代码生成质量突然下降输入里代码被截断代码块单独计数,预留更大额度
检索结果答非所问切块超限被静默截断按 Token 切块,留 10% 余量

这张表里的第二行最值得展开。很多人第一次做多语言项目,发现中文用户的调用成本是英文用户的两倍,第一反应是接口定价有问题。实际测一下分词结果就明白了:同样的意思,中文 Token 数是英文的 1.6 到 2 倍,这是语言编码差异导致的客观事实。对策不是去争论,而是在产品层面做区分——中文场景的额度、缓存策略、输出长度限制,都按自己的实际系数单独配置。

5.2 三个我踩过的坑

第一个坑:把特殊 Token 忘了。对话模板里的起止标记、系统角色标记、工具调用的边界符,这些都是词表里的特殊条目,每个占 1 个 Token,一次请求加起来二三十个不稀奇。做精确计费核对时,如果只统计用户可见文本,你会发现账对不上,差的就是这部分。排查方法很简单:把完整请求体序列化后整体编码一次,跟你分段编码求和对比,差值就是模板开销。

第二个坑:以为提示词越长效果越好。早期做分类任务,我往系统提示词里塞了十几个示例,效果确实好,但每次调用光提示词就两千多 Token。后来砍到 3 个精选示例,效果只掉了不到两个百分点,成本降了七成。少样本示例的边际收益衰减得非常快,第 5 个示例带来的提升往往不到第 1 个的十分之一。这个账要算清楚:一个示例按 150 Token 算,一天一万次调用就是 150 万 Token,够买不少算力了。

第三个坑:忽略了输出的“礼貌性膨胀”。模型天然倾向于加开场白和总结句,“好的,我来帮你分析一下”“希望以上内容对你有帮助”,这些套话每个二三十 Token,看着不多,量大了非常可观。在系统提示词里明确要求“直接给结论,不要开场白和总结”,实测能省下输出侧 15% 左右的 Token,而且回答的可读性反而更高。

注意:做 Token 预算时永远留 10% 到 15% 的余量。分词器的行为在不同的文本上会有波动,业务文本的成分也会随季节、活动、用户结构变化。把预算卡到 99% 的利用率,等于给自己埋了一颗定时炸弹。

6. 把 Token 省下来的实操手法

6.1 提示词层面的压缩

最有效的三招,按收益排序:删示例、删客套、改结构。删示例前面说过了;删客套指的是去掉“你是一位经验丰富的专家,请仔细思考后回答”这类没有实际约束力的修饰,它们每个字都在花钱;改结构指的是把大段自然语言描述换成短标签或键值对,比如把“请你以 JSON 格式输出,包含标题、摘要、关键词三个字段,标题不超过二十字”改成直接给一个空 JSON 骨架让模型填充,Token 数能砍掉一半还多。

还有一招是让模型输出短答案而不是长解释。分类任务不要让它输出“这段文本属于正面情感,因为……”,直接约束成只输出一个词。思维链确实能提升准确率,但只在你需要推理的复杂任务上用,简单的抽取、分类、判定任务上思维链纯属浪费。我的判断标准是:如果人看一眼就能给出答案,就不需要思维链。

6.2 工程层面的优化

利用前缀缓存。把固定不变的部分(系统提示、示例、知识库静态片段)全部前置,动态内容放最后。检查缓存是否真的命中,看接口返回的缓存命中字段,别凭感觉。

做输入去重。多轮对话里用户经常重复粘贴同样的长文档,做一层内容哈希去重,重复的直接复用上一次的结果或引用,别每次都重新编码一遍。

分段处理代替一次性塞入。超长文档不要硬塞,按章节切块分别处理,中间结果用结构化格式汇总。这样每次请求的上下文都短,缓存命中率高,失败重试的成本也低。代价是要多写一点调度逻辑,长期看非常划算。

输出格式约束。要求模型输出紧凑的 JSON、CSV 或单行结论,避免缩进和换行。JSON 的缩进在 Token 层面是实打实的开销,一个嵌套三层的对象,光缩进空格就几十个 Token。让模型输出不带缩进的紧凑格式,解析端自己格式化,这笔钱省得很轻松。

监控与告警。上线后持续记录每次调用的输入输出 Token 数,按业务维度做分位数统计。我见过的最典型的事故是:某次提示词改版,无意中删掉了一句约束,模型开始输出长篇解释,输出 Token 中位数涨了 2.3 倍,一周后才被财务发现。如果在 Token 数上设了告警阈值,这个问题当天就能抓到。

最后分享一个我在实际项目里养成的习惯:每接手一个新模型,先花二十分钟做三件事——用真实业务文本跑一遍分词统计,标定汉字/Token 系数;拿一段最长可能的输入试一次,看会不会触发截断;把输出的典型长度量一遍,算清楚输出侧占比。这三件事做完,后面所有的成本估算、容量规划、超时设置都有据可依,不会再出现“明明字数没超却报错”这种让人抓狂的情况。Token 这个概念本身不复杂,复杂的是每个模型、每种文本类型都有自己的脾气,摸清楚脾气,账自然就算明白了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询