做LLM应用开发这些年,被问得最多的问题往往不是模型效果,而是账单:“我好像没怎么用,API费用怎么这么高?”说实话,我自己也曾经被月度账单刺痛过,之后才把Token这件事从头到尾研究了一遍。这篇文章就是我那段时间的排查心得:Token是怎么产生的、API账单是怎么计算的,以及最关键的——你的钱到底浪费在哪、怎么省下来。内容适合正在做LLM应用、对成本敏感的开发者,也适合想搞懂计费逻辑的产品和项目负责人。
1. 先把Token概念讲透:为什么大模型不认字,只认Token
1.1 模型其实是“数字处理器”,文字要先编码才能进去
先说一个底层事实:大模型本身不认识文字。你发过去的“你好”,在模型看来是一串编号。这中间隔着一个叫“分词”的环节,也就是Tokenization。
模型训练的时候,分词器会先把语料切成Token,再给每个Token分配一个数字ID。模型学到的是这些ID之间的排列规律,不是字的形状和发音。所以每次调用API,你的文本会先被切成Token,Token再变成ID矩阵进入模型,模型处理完之后再把输出ID换算回文本返回。计费恰好就发生在这两个方向上:你消耗了多少输入Token、多少输出Token,服务商就按这个数量收钱,而不是按字数、按条数。
很多人在这一步就开始误解了,以为“我发了一行字,就该按一行字收费”。但一行“12345678901234567890”和一行“今天天气不错”,Token消耗完全不是一回事。这就是为什么同样的“字数”,有的请求便宜,有的请求贵得离谱。
1.2 BPE分词:Token到底是怎么切出来的
目前主流模型用的多是BPE这类子词分词算法,全称是Byte Pair Encoding。它不按语言的“字”切,而是盯着字节和字符组合。
简单说,BPE会把文本先拆成单字节,然后反复统计相邻字节对的出现频率,把高频出现的组合合并成一个新Token,合并到预设的词表规模为止。效果就是:常见的高频片段会成为一个完整Token,而生僻词、乱码、长数字会被切得七零八落。
用一个生活化的类比:BPE像是把一篇文章里反复出现的固定搭配装订成一个个“常用词块”,比如英文里的“ing”“tion”“ the ”会很快被合并成整体;而一串没有任何规律的随机字符串,找不到任何高频片段,就只能被硬切成一小段一小段,Token数量自然飙升。中文也是同样的逻辑:常用字词一个Token,生僻字或者专有名词可能要拆成两三个Token。
这解释了一个最让人困惑的现象:为什么同样长度的一段英文和一段中文,Token消耗差别很大;为什么一段网上复制的乱码文本,能瞬间吃掉你几百个Token。
1.3 不同文本的Token消耗速查表
我先给一张常用估算表,后面章节会讲怎么精确计算。注意这张表是经验值,不同模型的分词器有差异,但用来理解量级完全够用。
| 文本类型 | 估算消耗 | 备注 |
|---|---|---|
| 英文日常文本 | 1000个单词约1300 Token | 一个英文单词平均约1.3个Token |
| 中文日常文本 | 1000个汉字约1500至2000 Token | 常见字词省Token,生僻词会翻倍 |
| 代码 | 1000个字符约250至350 Token | 空格、换行、缩进、符号全部计费 |
| 长数字串 | 20位数字可能被拆成10个以上Token | 随机ID、时间戳、哈希值尤其费 |
| JSON/XML | 1000个字符约300至400 Token | 键名、引号、缩进全部算 |
| 表情符号 | 单个emoji可能1至3个Token | 带修饰符的组合表情更费 |
这张表的核心结论是:Token不是“字数”的同义词,而是“信息片段”的度量。你写的内容越规整、越常见,Token就越少;越随机、越生僻、格式越重,Token就越多。
2. API费用计算逻辑:你的钱到底花在哪
2.1 输入和输出Token,单价差好几倍
所有按Token计费的模型服务,几乎都遵循一个基本规则:输出Token比输入Token贵得多。
背后的原因其实很合理。输入是模型“读一遍”就完事,编码过程可以批量并行处理,计算成本相对低;输出是模型“一个字一个字生成”的过程,每生成一个Token都要做一次完整的前向推理,计算量大得多,所以单价贵。主流平台的比例通常在3比1到5比1之间。
我拿一个示例价格来说事,大家对照自己的服务商报价就行:某主流闭源旗舰模型,输入约30元/百万Token,输出约120元/百万Token。也就是说,同样一个Token,输出方向的价格是输入方向的4倍。很多新手只盯着“百万Token多少钱”看,没注意到输入输出是分开计价的,结果看到输出账单时整个人都懵了。
2.2 一次真实请求的账单拆解:从0.17元看起
假设你正在做一个带知识库的智能问答应用,模型需要结合检索到的资料回答问题。一次普通请求的Token组成大概是这样的:
| Token来源 | 数量 |
|---|---|
| 系统提示词(人设、规则、输出格式要求) | 512 Token |
| 最近3轮对话历史 | 约1500 Token |
| 用户当前提问 | 80 Token |
| 检索召回的上下文片段 | 1200 Token |
| 输入合计 | 3292 Token |
| 模型生成的本次回复 | 600 Token |
按上面那个示例价格算一下:
- 输入费用:3292 ÷ 1000000 × 30 ≈ 0.099元
- 输出费用:600 ÷ 1000000 × 120 ≈ 0.072元
- 单次请求合计:约0.17元
0.17元一次,看起来是真的便宜。但注意,这只是一个很克制的请求:没有长历史、没有过量的检索内容、没有复杂的工具定义。真实生产环境里,这些“看起来没什么”的部分,单次请求的Token量轻轻松松能翻三四倍。
2.3 从单次请求到月账单:高成本是怎么滚出来的
单次0.17元不吓人,吓人的是调用量。我们继续算账。
假设你的应用日活在慢慢起来,每天有5000次API调用,每次平均就是上面这个0.17元:
- 单日费用:5000 × 0.17 = 850元
- 一个月(按30天):约25500元
也就是说,一个看起来“单次很便宜”的问答功能,在每天5000次调用的情况下,月账单会摸到两万五这个量级。如果每次请求再膨胀到8000 Token呢?那就是另一个故事了。
这里还没算失败重试、内部联调测试、后台批量跑数据这些“看不见”的调用。很多团队账单突然翻倍,根本原因是调用量上来了,而每次请求的Token失控没人管。成本问题从来不是“某一次贵”,而是“每次都多一点点,量一大就爆炸”。
3. 四个最容易烧钱的隐藏坑
3.1 系统提示词:每次调用都在重复付费
系统提示词是隐藏开销里最典型的一个。它不管你的用户问了什么,都会拼在每次请求的最前面,跟着完整输入一起计费。
我曾经看过一个客户项目,系统提示词写了1200字,换算下来约1800到2400 Token。按单日1万次调用算,光系统提示词一天就是2000万Token量级的固定支出,一个月就是6亿Token,折合输入费用超过1800元。这还只是“一个月白付的固定部分”,不包含任何业务内容。
省钱的逻辑很简单:系统提示词是每次调用都在付的钱,把它压到最短,就是纯粹的省钱。我看过太多提示词里写三遍“请用中文回答”,还有大段自我介绍式的废话。删掉这些,约束改成简短祈使句,同样的效果,Token能少一半。另外,如果你的服务商支持上下文缓存,固定不变的系统提示词可以命中缓存、享受折扣,这个后面会专门讲。
3.2 多轮对话历史:Token像滚雪球一样增长
对话类应用是隐藏开销的重灾区,也是最容易让账单爆炸的场景。
原因在于,每发起一次新请求,客户端要把之前的全部对话历史再发一遍。这是为了让模型“记得”前面聊了什么。假设每轮对话约300 Token,第10次请求时,历史已经累积了9轮,光历史就是2700 Token,加上系统提示词和当前提问,单次请求轻松超过4000 Token。
更可怕的是增长速度。对话进行到第50轮时,历史累计可能接近2万Token,每次新增一轮,这个数字还在往上加。从整个会话生命周期看,累计消耗的Token量大约随对话轮数的平方增长。换句话说,聊得越久,平均每次请求越贵,总成本越失控。
我遇到过真实案例:每天1万用户、平均每人5轮对话,一个月下来账单直接上了六位数,排查半天,大半原因是历史记录无脑全量回传。修复方式后面会讲,核心思路是“保留最近几轮+前面内容压缩成摘要”,而不是全部原样带回。
3.3 工具定义与Few-shot示例:还没用上就在花钱
如果你接了函数调用或者工具调用,那么工具的JSON Schema也会全部计入输入Token。一个带参数描述、枚举值、示例的完整工具定义,动辄几百Token;注册3个工具,就是上千Token的固定开销。这些工具可能很多请求根本不会触发,但只要在API参数里声明了,就要付钱。
Few-shot示例是同一个道理。为了让模型学会你的输出格式,很多人在系统提示词里放了五六个示例,每个示例300 Token,一次请求就多出1500到2000 Token。这些示例也是每次调用都跟着走的。
我的建议是:工具描述精简到“能把参数含义说清楚”即可,不需要写长篇说明;Few-shot示例只在真正需要的时候按需加载,不要常驻在系统提示词里。还有一个很常见的坑是RAG场景无脑塞检索结果。很多团队把5个、8个chunk全部放进上下文,其中一半和用户问题根本不相关。我现在的做法是,先让一个小模型或者轻量规则做一遍相关性筛选,只保留最相关的2到3个片段,Token能省下一大半,回答效果反而更稳定。
3.4 重试、长输出和临时膨胀:零碎开销也不少
重试机制看起来不起眼,但在超时场景非常烧钱。某些平台的调用流程是:请求已经到达服务端并开始计算,客户端这边因为等待超时,以为失败就重发了,而服务端的第一次计算并没有取消。结果一次业务请求,被算了两次甚至三次输入和输出。加上网络抖动造成的重复请求、流式输出中途断开后的恢复重试,一个月下来能多出5%到15%的费用。
输出部分也有一个容易被忽略的点。max_tokens参数设得太高本身不会扣钱,因为按实际输出量计费;但如果提示词没有把输出边界约束清楚,模型很容易生成一大段“正确的废话”,把输出Token白白拉高。用stop序列、强制“只输出JSON”“只给结论”这类约束,我实测能砍掉30%以上的输出量。输出Token单价本来就是输入的4倍,这一刀下去,效果会非常直接。
4. Token估算实操:手把手教你算预算
4.1 中英混杂与代码的估算规则
做预算的前提是能估算Token。我给一套自己在用的经验规则,先有量级概念,后面再精确验证:
- 英文文本:单词数乘以1.3,就是大概Token数。1000个单词约1300 Token。
- 中文文本:汉字数乘以1.5到2。保守一点直接按2算,1000个汉字按2000 Token做预算。
- 中英混杂:按中文和英文分别估算,再加总。比如“请用中文回答Today's weather”,中文部分10字约15到20 Token,英文部分两个词约3 Token,合计约20个Token。
- 代码:字符数除以3到4。一段50行、1500字符的Python函数,大概400到500 Token。缩进、空行、操作符全都要算,所以代码比自然语言“贵”。
- 结构化数据:JSON或者XML,字符数除以2.5到3。键名、引号、换行缩进都是Token。
拿一个实际场景算:用户问了一个100字的问题,你往上下文里塞了1000字的参考资料。大概就是100×2加上1000×1.5,约1700 Token,再加系统提示词和输出预留,一次请求轻松超过2500 Token。这个量级估算准了,预算就不会偏离太多。
4.2 官方分词工具和本地脚本怎么选
估算规则只能做预算,真正上线前必须用精确的工具验证。每家模型服务商一般都会提供官方的Token计算工具,网页版或者配套的官方分词库都有。你在自己项目里用哪个模型,就去找哪个模型的官方分词器,因为不同模型的分词表不一样,同一个句子在两个模型里的Token数可能是不同的。
这里有一个常见的误区:很多人用文本编辑器里的“字符数”来估算Token,或者拿A模型的计算器去套B模型的结果,误差能到30%以上。特别是中文本身,不同分词器的表现差异非常大,有的模型对中文一个字一个Token,有的模型两三个字合成一个Token。所以准确的做法是:以你实际使用的模型配套分词器为准。
另外一个实操细节:如果请求体是JSON格式的,JSON的结构字符本身也会计入Token。messages数组里每个角色标记、引号、冒号,都会增加几十个Token。不要只数“内容字数”,要把格式开销也算进去。
4.3 我常用的预算公式与扣费预警线
我自己的做法是给每个功能单独定一份“Token预算”,公式大概是:
单次请求Token预算 = 系统提示词Token + (平均历史轮数 × 每轮Token) + 平均检索内容Token + 当前输入Token + 预留输出Token把它写进项目文档,甚至做成一个简单的统计函数,比如这样:
def estimate_call_tokens(system_text, history, query, max_output=800): system_tokens = int(len(system_text) * 1.5) # 中文场景按1.5估算 history_tokens = 0 for msg in history: history_tokens += int(len(msg.get("content", "")) * 1.3) query_tokens = int(len(query) * 1.5) return system_tokens + history_tokens + query_tokens + max_output这个函数用的估算系数不精确,但是做预算和告警完全够用。跑一段时间之后,把API响应里usage字段的真实数字拉出来和预算对比,误差超过20%,就说明哪一块悄悄膨胀了。
我的习惯是再设一条“日Token消耗告警线”,比如单日消耗超过3000万Token就推送通知到工作群。预算公式的价值不是精确预报,而是让每一次交互都有成本感知。没有成本感知的系统,账单翻倍是迟早的事。
5. 降本优化方案:从Prompt到架构
5.1 Prompt精简与去重:不降质量先降体积
Prompt精简是所有优化里见效最快的。我看过很多系统提示词,同一句“请用中文回答”出现三遍,还有大段“你是一个乐于助人的助手”这类背景介绍。这些文字在每次调用中都会变成Token,而且是固定成本。
精简不是让你牺牲效果,而是去掉重复和冗余。我的常用压缩方法包括:把多句约束合并成一句;删掉所有客套话;输出格式要求用“正例”表达,少写三个“反例”;能用一个词表达清楚的绝不用一个句子。改完之后跑一遍回归测试,只要效果没掉,就说明这些Token本来就是浪费的。
这里还有一个战术性技巧:把固定内容放Prompt开头,可变内容放结尾。这不仅仅是为了可读性,更是为了命中服务商的前缀缓存,后面的章节会展开讲。
5.2 对话历史的压缩与滑动窗口策略
多轮对话历史的处理,分为三步走。
第一步,滑动窗口。只保留最近N轮对话全部回传,更早的内容丢弃。这一步能快速把单次请求的Token量压下来。缺点很明显:模型会遗忘太早的内容。所以需要第二步。
第二步,摘要化。每聊完K轮,让模型把前面的对话总结成一段简短的摘要,后续请求只带“摘要+最近N轮原文”。举个例子,50轮对话平均每轮300 Token,全量带回是15000 Token;如果改成“摘要500 Token+最近5轮1500 Token”,单次请求只有2000 Token,节省87%。代价是每K轮消耗一次摘要生成费用,但和长期全量回传相比,省下的钱不是一个量级。
第三步,会话分级。有些对话本身是短期的,比如查一次天气、算一道题,根本不需要跨会话记忆;有些是长期项目型对话,需要保留核心事实。按场景分策,而不是一刀切地全量保存或者全量删除,预算会健康得多。
5.3 缓存命中:让重复内容按折扣价计费
如果你的服务商支持上下文缓存,这可能是最省钱的优化手段,但很多人没用对。
缓存的基本逻辑是:相同的前缀内容在短时间内被重复请求时,服务商可以复用之前计算过的KV缓存,只按原价的很小比例收输入费,甚至某些情况接近免费。系统提示词、固定Few-shot示例、长文档开头这些不变的内容,都能命中这种折扣。
关键要求是“前缀完全一致,顺序不能乱”。只要中间插入了一段不同的内容,前面的缓存就会失效。所以Prompt结构的设计就有讲究了:固定不变的说明放最前面,动态变化的内容(用户问题、检索结果、日志)放在后部,绝不能让可变内容出现在固定内容之前。很多人没意识到这一点,历史里一插新内容,整个前缀缓存全部作废,折扣一分没拿到。
5.4 模型分级调用:别用大炮打蚊子
最后一条架构级优化:不要所有请求都用同一个旗舰模型。
一个典型的应用里,真正需要旗舰模型深度推理的请求可能只占10%到20%。意图识别、关键词抽取、简单问答、格式转换,这些任务用轻量模型甚至规则引擎就能完成。我的建议是做一个任务路由层:先判断请求复杂度,简单任务走便宜模型,复杂任务才升级到旗舰模型。
我参与过一个客服系统的改造,原先所有消息都调用旗舰模型,账单很高。后来在前面加了一层意图分类,90%的常见问题走轻量模型,只有那10%的疑难问题升级到旗舰模型。效果几乎没有变化,成本下降了差不多七成。再加上异步任务走批量接口的折扣,把每天定时处理的报表、日志分析这些非实时任务集中跑,又是一笔不小的节省。
6. 常见问题与排查技巧实录
6.1 账单和估算对不上?三个常见原因
“我算出来明明是5万Token,账单却扣了8万”,这是最常遇到的疑问。原因通常有三个。
第一,分词器不一致。你用A模型的在线计算器统计自己的Prompt,但实际线上跑的是B模型,两个模型词表不同,Token数自然不同。之前就有人统计时用某个开源模型的分词器,线上却是另一套闭源模型,误差接近40%。
第二,隐藏的附加内容。有些模型服务商会在你的请求之外追加一段系统层级的提示词或安全指令,这部分不会显示在你的日志里,但会出现在usage的prompt_token字段中。排查方法是用API返回的usage字段对比你自己本地统计的Token数,差值就是隐藏开销。
第三,格式开销没算进去。messages数组的JSON结构、角色标记、换行缩进都会产生Token。只统计content里的字数是远远不够的,整个请求体的Token都要算。
6.2 为什么数字、代码、表情这么烧Token
数字和代码烧Token的根本原因是BPE分词找不到高频片段。一串20位的随机ID在训练语料里几乎不可能完整出现过,分词器只能把它切成很多个小片段,Token数量比同样长度的普通英文高好几倍。
代码的问题在于空白字符全都要计费。一个缩进四个空格,就是四个Token;一行代码里的操作符、括号、分号,各自独立成Token。所以同样1000字符,自然语言可能只消耗500 Token,代码却能消耗300 Token以上。
表情符号也容易踩坑。常见emoji在UTF-8里占4个字节,通常会被分成一个或多个Token;带肤色修饰、带组合序列的复杂表情,Token数可能翻倍。如果你的应用鼓励用户发表情,这部分开销虽然单次不大,但量大了也值得留意。
6.3 一套可落地的排查流程
我自己从成本爆表到恢复正常,靠的是一套固定排查流程,分享给大家。
第一步,每次请求打印usage三件套。在调用模型的公共方法里,把prompt_token、completion_token、total_token全部记录到日志。
第二步,按功能维度聚合。把日志按接口路径或者业务功能分组,统计每个功能每天消耗多少Token、花费多少钱。这一步能直接看出是哪个功能在烧钱。
第三步,抽取高消耗样本。把Token消耗排名前1%的请求完整存下来,回放分析它们的系统提示词、历史长度、检索内容、输出长度,找出是哪一部分异常膨胀。
第四步,用官方分词器对样本做分段统计。把系统提示词、历史、检索内容、当前输入分别数一遍Token,占比一目了然。
第五步,针对占比最大的部分做优化,上线后观察一周。
这套流程帮我最快定位过两次成本翻倍,一次是多轮历史全量回传,一次是RAG无脑塞全部检索结果。每次都是先用数据定位,再改配置,而不是凭感觉猜。
最后说点体会。Token成本管理这件事,本质上不是让你牺牲效果,而是让你对每一笔调用有成本感知。我自己的实践是:所有项目上线前先算一次单请求的Token预算,上线后每周看一眼按功能分组的Token消耗。项目跑久了你会发现,省下来的往往不是某一次大改动,而是系统提示词短了一半、历史不再全量回传、检索结果只留最相关的几段——这些不起眼的小事加在一起,月账单的差距能到一个数量级。希望这篇记录能让你少走我走过的弯路。