news 2026/8/29 4:17:03

Token消耗差万倍?大模型应用成本控制与工程优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Token消耗差万倍?大模型应用成本控制与工程优化

近期社区讨论里,“Opus 5 狂烧 6.9 亿 token 做游戏,GPT-5.6 用 5 美元复刻了”这类对比标题吸引了不少注意力。且不说标题中的模型版本具体是哪一代、价格是否准确,它背后反映的是一个所有大模型应用开发者都会遇到的实际问题:同样一个目标,不同实现路径下的 token 消耗为什么能差出好几个数量级。token 是大模型计费、上下文容量和推理成本的最小单位。无论是做代码生成、搭建 RAG 问答系统,还是做一个游戏 demo,token 消耗都直接决定你的调用成本、接口响应时间和线上可承载的请求量。这篇文章不讨论“谁更厉害”,而是把一条完整的工程链路讲清楚:token 是怎么被消耗掉的,为什么有些场景会烧到上亿级别,有些场景却能控制到极低水平,以及在自己的项目里如何做预算、记录、优化和排查。

1. 先看懂 token 消耗差距是怎么拉开的

1.1 token 是模型处理文本的最小单元

在大型语言模型里,文本并不是按“字符”或“单词”直接输入给模型的。模型会先把文本切分成一个个 token,再转成向量参与计算。不同模型的 tokenizer 不同,切分结果也不同。英文常见的切分是一个单词拆成一到两个 token,中文则可能是几个字合成一个 token,代码中的空格、换行、括号也会产生 token 消耗。

一个容易误解的点是:不能按字符数估算 token 数。下面这段代码可以直观展示切分差异:

import tiktoken enc = tiktoken.get_encoding("cl100k_base") text = "def hello(): return '你好,世界'" print(len(enc.encode(text)))

这里使用的cl100k_base是 OpenAI 文本模型常用的一种 BPE 词表。不同模型有各自的 tokenizer,同一个文本在不同模型的计费口径下,token 数并不一致。所以项目里做用量统计时,必须以实际调用的模型返回的usage字段为准,而不是本地估算后猜一个数。

1.2 做游戏这类长任务,token 消耗来自哪几层

标题里说的“做游戏”,本质上是一次多轮、长上下文的生成任务。这类任务的 token 消耗不是只来自“最终生成的那段代码”,而是来自四个叠加的层级:

第一层是初始需求描述。把“做一个俄罗斯方块”这种需求扩写成完整的产品说明、规则要求、技术栈约束,通常就是上千 token。第二层是每轮模型生成的输出。生成一个完整小游戏的源码,输出量可能在 5000 token 到 30000 token 之间,取决于代码长度和注释量。第三层是运行报错后的回填。工程实践中,开发者会把报错日志、堆栈、截图描述塞回模型,请求里的输入会迅速膨胀。第四层是长对话自动累积。很多项目不是一次生成成功,而是几十轮迭代。每一轮都要重新携带之前的系统提示词、历史消息和上下文,总消耗会随轮次增长。

用一个表格可以更直观表达这些消耗来源:

消耗来源典型内容token 量级(示例)
初始需求与规范中文需求、技术约束、目录结构说明1500 - 3000
单次代码输出完整源码与注释5000 - 30000
错误日志回填多行堆栈、日志片段2000 - 10000
历史对话累积30 轮以上迭代上下文数万至数十万
全量项目上下文代码库、规范文档反复注入十万至百万级

当迭代轮数达到几十轮、上下文里又始终携带整份代码和日志时,单次请求就可能消耗几万甚至十几万 token。再乘上几十轮迭代,总消耗达到千万级甚至亿级并不奇怪。

1.3 为什么有人狂烧 6.9 亿,有人 5 美元复刻

同样是“AI 做游戏”,两种不同思路会产生截然不同的成本曲线。

第一种思路是直接把所有内容堆进上下文,让模型一口气完成。开发过程中,开发者每遇到一次报错,就把完整日志和历史对话继续追加进去。这种方式实现最直接,但每一轮都会重复计算大量历史 token,而且输出内容往往包含大量解释性文字、示例代码和无用注释。时间一长,消耗会呈现指数级增长。

第二种思路是模块化拆解。先把游戏拆成入口、游戏循环、碰撞检测、界面渲染等独立模块,每一轮只给模型当前模块的最小上下文。同时压缩系统提示词、限制输出格式、对历史对话做摘要,并对完全相同的请求启用结果缓存。这样每一轮消耗可能只有几千 token,十几轮迭代下来消耗依然可控。

两种路径可以这样对比:

对比维度无约束长上下文路线有预算控制路线
每轮上下文大小数万至数十万 token数千 token
输出控制自由生成,附带大量解释结构化输出,只返回代码
历史处理全量保留摘要 + 最近轮次
缓存使用基本不用重复请求直接命中缓存
总消耗量级容易达到百万级以上往往能控制在十万级以下

所以,所谓“狂烧”和“低成本复刻”,本质不是模型能力差异,而是工程策略差异。

2. 把 6.9 亿 token 和 5 美元换算成可计算的成本

2.1 输入、输出、缓存:计费单价完全不同

模型 API 的计费并不是“一个 token 一个价”。输入 token 和输出 token 的单价通常不同,输出单价往往明显高于输入单价。部分服务商对缓存命中的输入 token 提供更低的折扣价,对长上下文中的反复请求也可能有特殊计费策略。

因此,在比较“6.9 亿 token”和“5 美元”时,不能直接说“6.9 亿 token 一定值多少钱”。必须知道这些 token 是输入还是输出,是否命中缓存,以及使用的是什么模型。至少要把输入和输出拆分估算,才能得出一个相对可信的数量级。

2.2 用一张估算表理解数量级

下面用一个示例单价来说明数量级。这个价格不代表任何模型的真实报价,仅用于帮助理解。

估算项目数值
示例输入单价3 美元 / 百万 token
示例输出单价15 美元 / 百万 token
6.9 亿 token 按输入输出 4:1 估算输入 5.52 亿,输出 1.38 亿
对应估算成本约 3700 美元量级
5 美元按输入单价折算约 166 万输入 token
5 美元按输出单价折算约 33 万输出 token

从这张表可以看出,6.9 亿 token 和“5 美元”之间并不是同一个数量级的对比。能在很小预算内完成相近结果,说明实现路径必然在“每轮输入 token 数”“输出 token 数”“调用轮次”上都做了大幅压缩。

需要强调,真实项目的成本计算不能靠这种含糊估算。落地时要根据模型官方计费页、实际调用中的usage返回值和当前区域的定价进行逐项计算。

2.3 影响成本的核心变量

第一个变量是模型单价。不同模型之间的输入输出价格差距可能达到数倍甚至数十倍。使用同一个任务,选择不同模型,成本差异非常大。

第二个变量是每轮请求的上下文长度。系统提示词越长、历史消息越多、每次注入的代码库越大,单次请求的输入 token 就越高。把上下文从 8 万 token 压缩到 1 万 token,成本并不是简单减少到八分之一,因为在长文本场景中,服务商可能还会引入额外的上下文计费规则。

第三个变量是调用轮次。很多任务不是一次调用就能完成,如果反复把错误日志回传、反复让模型重写整个文件,轮次会迅速增加。减少无效调用轮次,是控制成本最直接的手段。

第四个变量是输出长度。输出 token 单价更高,所以模型生成的“废话”越多,成本越高。通过结构化输出、限制生成范围、控制max_tokens,能减少不必要的输出。

第五个变量是缓存命中率。如果大量请求的 prompt 前缀完全相同,缓存命中后成本会显著降低。这也是“低成本复刻”类方案里最常见的手段之一。

3. 统一模型调用入口,先给项目装上 token 计量表

3.1 为什么要统一入口

很多项目在接入大模型 API 时,会在各个业务模块里直接调用 SDK,导致用量散落各处。服务上线后想排查“哪条业务路径烧钱最多”,完全无从下手。正确做法是给所有模型请求加一个统一网关层,让每个请求都必须经过同一个入口。

统一入口能带来几个直接收益:所有调用统一获得usage数据;可以直接按项目名、模型名、日期记录用量;可以统一插入限流、缓存、重试和熔断逻辑;在模型切换或计价调整时,只改一个地方。

3.2 最小版 LLM 网关

下面是一个最小结构的 Python 网关示例。它不绑定任何具体 SDK,只展示核心逻辑。

class LLMGateway: def __init__(self, storage): self.storage = storage def complete(self, *, model, messages, max_tokens=1024, project="default"): response = self._call_model_api( model=model, messages=messages, max_tokens=max_tokens, ) usage = response["usage"] self.storage.record( project=project, model=model, input_tokens=usage["input_tokens"], output_tokens=usage["output_tokens"], ) return response["content"] def _call_model_api(self, *, model, messages, max_tokens): # 实际项目在这里接入你自己的模型 SDK, # 并保证返回结构里包含 usage 字段。 raise NotImplementedError

这个网关的核心约束是:所有模型调用都必须返回usage,并且所有调用的用量都必须落库。缺少任何一项,后续成本统计都会变成盲人摸象。实际项目中,usage的字段名可能叫prompt_tokenscompletion_tokensinput_tokensoutput_tokens,也可能有单独的reasoning_tokens字段,解析时要以具体 SDK 的返回结构为准。

3.3 用量落到 SQLite,方便后续聚合

对于中小项目,用量记录可以先落到 SQLite。表结构不需要过于复杂,能支撑按日期、按项目、按模型的聚合统计即可。

CREATE TABLE IF NOT EXISTS token_usage ( id INTEGER PRIMARY KEY AUTOINCREMENT, project TEXT NOT NULL, model TEXT NOT NULL, input_tokens INTEGER NOT NULL, output_tokens INTEGER NOT NULL, created_at TEXT NOT NULL );

对应 Python 写入逻辑:

import sqlite3 from datetime import datetime class SqliteTokenStorage: def __init__(self, db_path): self.conn = sqlite3.connect(db_path) self.conn.execute(""" CREATE TABLE IF NOT EXISTS token_usage ( id INTEGER PRIMARY KEY AUTOINCREMENT, project TEXT NOT NULL, model TEXT NOT NULL, input_tokens INTEGER NOT NULL, output_tokens INTEGER NOT NULL, created_at TEXT NOT NULL ) """) self.conn.commit() def record(self, *, project, model, input_tokens, output_tokens): self.conn.execute( "INSERT INTO token_usage (project, model, input_tokens, output_tokens, created_at) VALUES (?, ?, ?, ?, ?)", (project, model, input_tokens, output_tokens, datetime.now().isoformat()), ) self.conn.commit()

生产环境不建议直接在每台应用服务器上各建一个 SQLite 文件。多实例部署时,需要把用量记录写入集中式数据库或日志采集平台,避免出现重复统计或统计遗漏。

4. 降低单次调用 token 消耗的四种手段

4.1 精简 system prompt,去掉样板话

系统提示词是每轮请求都会携带的固定输入。如果系统提示词很长,它会成为整个项目最稳定的 token 消耗来源。

下面是一个对比示例。原始版本:

你是一个资深游戏开发专家。你擅长使用 Python 和 Pygame 开发各种小游戏。在编写代码之前,你要先仔细分析需求,然后列出游戏规则,再编写完整代码。代码中需要包含尽量详细的中文注释。你还需要解释每段代码的作用,并提供运行说明。

压缩后版本:

你是一位 Python 游戏开发助手。规则: 1. 只输出代码,不解释。 2. 代码必须有中文注释。 3. 使用 Pygame 实现。

压缩后的提示词保留了任务目标、输出格式和行为约束,去掉了大量人设描述和冗余铺垫。对于固定规则,写成编号列表比写成长段文字更节省 token,也更容易被模型遵循。

4.2 用结构化输出约束模型不要“话痨”

很多 token 的浪费来自模型输出的解释性文字。如果业务只关心最终代码或结果,可以让模型直接输出结构化内容,并约定字段含义。

示例:

{ "code": "完整的 pygame 代码字符串", "assets": ["每个资源文件的生成方式"], "steps": ["运行步骤"] }

在提示词里告诉模型“只输出 JSON,不要输出其他内容”。这样模型会收敛到指定字段,就不会反复输出“好的,下面我来实现”这类无效内容。需要注意的是,JSON 里的字段名本身也会占用 token,所以字段名不宜过长,字段数量也应该控制在业务必需范围内。

4.3 历史上下文裁剪与摘要压缩

长对话里最消耗 token 的是历史。随着轮次增加,历史消息会反复携带旧代码、旧日志、旧讨论。常见优化方式是“保留最近 N 轮 + 对早期内容做摘要”。

def compress_history(history, max_round=10, summarize_fn=None): if len(history) <= max_round: return history early = history[:-max_round] recent = history[-max_round:] if summarize_fn is None: summary = "早期上下文已省略" else: summary = summarize_fn(early) return [{"role": "system", "content": f"早期对话摘要:{summary}"}] + recent

摘要本身可能也是一次模型调用,也会产生 token。只有当摘要占用的 token 远低于直接携带早期历史时,这种替换才划算。更简单的做法是直接丢弃早期原始文本,只保留最近几轮的上下文。对于游戏开发这类任务,早期需求已经转换为代码,后续轮次并不需要反复携带完整需求原文。

4.4 相同请求结果直接走缓存

最省 token 的方式,是避免产生第二次相同调用。对于输入完全相同的请求,可以按模型和消息内容生成哈希键,直接在缓存中返回结果。

import hashlib class PromptCache: def __init__(self): self.data = {} def get(self, model, messages): key = self._key(model, messages) return self.data.get(key) def set(self, model, messages, result): key = self._key(model, messages) self.data[key] = result @staticmethod def _key(model, messages): raw = model + "|" + repr(messages) return hashlib.sha256(raw.encode("utf-8")).hexdigest()

这个方案只适合“完全相同”或“高度可复用”的 prompt,不适合开放式生成。生产环境可以使用 Redis 这类外部缓存,并设置过期时间。缓存命中后,模型调用没有发生,对应的 token 消耗也为零,这是压缩成本效果最显著的手段之一。

5. 用量统计、预算告警与验证

5.1 按天、按项目、按模型聚合

有了统一网关和用量存储,就可以用 SQL 做聚合分析。下面这条查询按天、项目、模型汇总输入输出 token:

SELECT date(created_at) AS day, project, model, sum(input_tokens) AS input_tokens, sum(output_tokens) AS output_tokens, sum(input_tokens) + sum(output_tokens) AS total_tokens FROM token_usage GROUP BY day, project, model ORDER BY day DESC;

这一步能快速回答“今天哪个项目消耗最多”“是输入还是输出占大头”这类问题。建议把这条 SQL 固化成定时任务,每日生成一份用量报告。

5.2 用脚本设置软线和硬线

成本控制不能只靠事后分析,还需要在达到阈值时触发告警。可以在统计脚本里设置两个阈值:软阈值用于提前提醒,硬阈值用于彻底拦截。

daily_limit = 500_000 # 示例:每日硬预算 50 万 token warn_ratio = 0.8 # 假设 total 是当天统计出的实际消耗 if total >= daily_limit: print("ALERT: daily budget exceeded, block new requests") elif total >= daily_limit * warn_ratio: print("WARN: daily budget reaches 80%, prepare to throttle")

真实系统中,软线附近可以增加限流概率,硬线触发后可以临时拒绝非核心请求。需要注意的是,模型请求通常是异步的,统计结果可能滞后,所以硬线阈值要留有余量,不能卡在精确的预算上限上执行拦截。

5.3 调优前后做对比验证

做任何 token 优化,都要先记录基线,再对比优化前后的差异。下表是一个演示示例,实际数据以自己项目的日志为准:

指标优化前优化后
单局游戏开发平均调用轮次35 轮10 轮
每轮平均输入 token8000012000
每轮平均输出 token40001800
单任务日均总消耗约 294 万约 13.8 万

从表中可以看出,优化后的总消耗可能只有优化前的几十分之一。如果任务中还有大量重复请求命中缓存,消耗还能进一步下降。验证时要注意,不要只看“总 token 数下降”,还要确认任务完成度和输出质量没有明显下降。

6. token 相关常见坑和排查路径

6.1 token 费用突增怎么排查

当某个项目的 token 消耗突然飙升时,按这个顺序排查:

  1. 先按项目、模型、日期聚合,定位是哪个入口消耗增长。
  2. 检查单次请求平均输入和输出 token 是否变大,如果变大,重点看系统提示词和历史消息是否被改长。
  3. 检查调用轮次是否增多,重点看是否出现了大量失败重试或错误日志回填。
  4. 检查有没有代码在循环里重复调用模型,比如把模型调用写进了高频循环。
  5. 检查日志记录的usage是否出现异常大的单次消耗,例如有人把整份代码库塞进了 prompt。

大多数费用突增都不是模型价格变了,而是请求形态变了。

6.2 鉴权里的 token 报错不是计费 token

“token”这个词在计算机领域有多个含义。大模型计费中的 token 是文本切分单元,而登录鉴权里常见的access tokenrefresh tokenid token是完全不同的概念。

很多开发者在集成模型 API 时遇到过类似报错:

sign-in failed: login server error: token exchange failed: token endpoint returned status 403 forbidden

这类报错与模型费用无关,而是 OAuth 登录流程中的 token 交换失败。排查路径如下:检查 access token 或 refresh token 是否已过期;检查客户端 id、secret、scope 是否配置正确;检查服务器系统时间是否偏差过大,时间偏差会导致 JWT 校验失败;确认账号在目标服务商的可用范围内。如果公司出口网络对 OAuth 域名存在访问限制,也可能导致握手失败,需要从网络连通性角度排查。注意不要混淆“token 用量统计”和“登录 token 交换”两件事。

6.3 JWT 续签场景的注意点

如果项目里使用 JWT 作为访问凭证,常见坑是只在请求失败时才被动刷新。更好的做法是根据 JWT 的exp声明判断剩余有效期,并在过期前提前刷新。

import time def need_refresh(exp_epoch, advance_seconds=300): return time.time() > (exp_epoch - advance_seconds)

提前刷新可以避免并发请求同时发现 token 过期,然后同时触发刷新导致请求雪崩。生产环境还需要对刷新操作加锁,保证同一时刻只有一个线程去刷新。

6.4 不要用“字符串长度”估算 token

用中文、英文、代码混合文本时,按字符数估算 token 误差很大。正确做法是:开发环境中使用模型对应的 tokenizer 本地估算;线上环境中直接读取模型返回的usage字段,以官方计费口径为准。任何“我一个字符大概等于多少 token”的换算都只适用于粗略理解,不能用于成本核算。

7. 生产环境最佳实践与可复用清单

7.1 学习环境与生产环境的差异

学习环境里可以把所有请求都打到模型 API,方便快速调试。生产环境必须多考虑几层:

关注点学习环境生产环境
用量记录可以不记录必须统一入库,支持按项目和模型聚合
缓存可加可不加高频重复请求应该做结果缓存
预算手动看几次即可必须有软告警和硬拦截
鉴权使用临时 token 即可使用独立密钥,定期轮换
失败重试手动重试增加退避重试、熔断、幂等
日志打印到控制台输出结构化日志,关联请求 id
模型版本随意固定版本,升级前先灰度

7.2 可复用清单:上线前逐一打勾

以下清单适用于以大模型 API 为核心成本的项目:

  1. 所有模型调用是否通过统一网关入口?
  2. 每次调用是否记录了项目、模型、输入 token、输出 token、时间戳?
  3. 系统提示词是否做过精简,去掉了不必要的长文本?
  4. 输出是否有限制条件或结构化约束?
  5. 历史消息是否有裁剪或摘要策略?
  6. 相同或相似请求是否有缓存?
  7. 是否设置了每日软告警阈值和硬拦截阈值?
  8. 是否按天生成用量报告,并检查异常突增?
  9. 调用密钥和登录 token 是否分开管理,是否有过期刷新机制?
  10. 是否保留了每次请求的日志,方便在费用异常时回溯?

如果这十项都做到了,基本不会出现“上个月花了多少钱完全不知道”的失控局面。

7.3 下一步扩展方向

在预算控制跑通之后,可以做更精细的治理。第一个方向是模型路由:简单任务走便宜模型,复杂任务走高能力模型,降低平均成本。第二个方向是 Prompt 压缩:对长代码库、长日志做自动摘要,把必须注入的内容压缩到最低。第三个方向是缓存深化:使用服务商提供的上下文缓存,减少长 prompt 重复计算。第四个方向是把用量统计接入监控平台,结合成本面板做每日、每周、每项目的多维分析。

对于刚接触大模型开发的读者,建议先不要追求复杂的成本治理系统,而是先把统一的调用入口和用量记录搭起来。有了数据,优化才有方向;没有数据,任何“省 token”的讨论都只是猜测。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/29 4:15:50

微分方程建模实战:从核心思想到MATLAB/Python实现

1. 从“变化”到“方程”&#xff1a;微分方程建模的核心思想在数学建模的实战中&#xff0c;我们常常遇到一个核心问题&#xff1a;如何描述一个系统随着时间&#xff08;或空间&#xff09;的推移而产生的动态变化&#xff1f;无论是预测未来几天的疫情感染人数&#xff0c;分…

作者头像 李华
网站建设 2026/8/29 4:14:45

AI应用上线前,必须搞定可控、可审、可回滚三个问题

最近关于 AI 研发节奏的讨论又多了起来&#xff1a;模型越来越大&#xff0c;能力越来越强&#xff0c;但也有不少声音认为应该先停下来&#xff0c;把安全边界和工程基础补上。对普通开发者和技术团队来说&#xff0c;这种讨论其实有一个更落地的版本&#xff1a;你的 AI 功能…

作者头像 李华
网站建设 2026/8/29 4:14:38

Sentinel微服务流量控制与熔断降级实战指南

1. 项目概述&#xff1a;为什么我们需要Sentinel&#xff1f;在微服务架构里&#xff0c;服务之间的调用关系变得像一张复杂的蜘蛛网。一个订单服务可能要调用用户服务、库存服务、支付服务&#xff0c;而支付服务又可能依赖外部的银行网关。当“双十一”零点流量洪峰涌来&…

作者头像 李华
网站建设 2026/8/29 4:14:10

数据库运维校招笔试核心考点与备考策略解析

1. 从这份笔试卷看网易在找什么样的数据库运维2018年网易校招数据库运维工程师&#xff08;BJ&#xff09;的笔试卷&#xff0c;到现在还有人在找、在刷&#xff0c;本身就说明了一个问题&#xff1a;这份卷子出的有水平。网易不是第一次做校招&#xff0c;数据库运维这个岗位也…

作者头像 李华
网站建设 2026/8/29 4:11:17

图论与网络优化实战指南:从最短路径到车辆调度

1. 项目概述&#xff1a;从“图”到“优化”的实战思维如果你参加过数学建模竞赛&#xff0c;或者处理过物流配送、社交网络分析、通信网络规划这类问题&#xff0c;那你大概率已经和“图论与网络优化”打过交道了。这听起来像是个纯理论的高深数学分支&#xff0c;但实际上&am…

作者头像 李华
网站建设 2026/8/29 4:10:46

多智能体开放世界中的自主数学发现:从博弈到验证的工程实践

如果说大模型已经在代码生成、数学竞赛题解上表现得像一个“解题高手”&#xff0c;那“自主数学发现”就是一个完全不同的游戏&#xff1a;它不给你题目&#xff0c;不告诉你哪里有定理&#xff0c;甚至不保证你正在探索的方向一定有意义。 过去几年&#xff0c;AI 在数学上最…

作者头像 李华