
近期社区讨论里“Opus 5 狂烧 6.9 亿 token 做游戏GPT-5.6 用 5 美元复刻了”这类对比标题吸引了不少注意力。且不说标题中的模型版本具体是哪一代、价格是否准确它背后反映的是一个所有大模型应用开发者都会遇到的实际问题同样一个目标不同实现路径下的 token 消耗为什么能差出好几个数量级。token 是大模型计费、上下文容量和推理成本的最小单位。无论是做代码生成、搭建 RAG 问答系统还是做一个游戏 demotoken 消耗都直接决定你的调用成本、接口响应时间和线上可承载的请求量。这篇文章不讨论“谁更厉害”而是把一条完整的工程链路讲清楚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 美元 / 百万 token6.9 亿 token 按输入输出 4:1 估算输入 5.52 亿输出 1.38 亿对应估算成本约 3700 美元量级5 美元按输入单价折算约 166 万输入 token5 美元按输出单价折算约 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_tokens1024, projectdefault): response self._call_model_api( modelmodel, messagesmessages, max_tokensmax_tokens, ) usage response[usage] self.storage.record( projectproject, modelmodel, input_tokensusage[input_tokens], output_tokensusage[output_tokens], ) return response[content] def _call_model_api(self, *, model, messages, max_tokens): # 实际项目在这里接入你自己的模型 SDK # 并保证返回结构里包含 usage 字段。 raise NotImplementedError这个网关的核心约束是所有模型调用都必须返回usage并且所有调用的用量都必须落库。缺少任何一项后续成本统计都会变成盲人摸象。实际项目中usage的字段名可能叫prompt_tokens、completion_tokens、input_tokens、output_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_round10, summarize_fnNone): 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 做聚合分析。下面这条查询按天、项目、模型汇总输入输出 tokenSELECT 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 消耗突然飙升时按这个顺序排查先按项目、模型、日期聚合定位是哪个入口消耗增长。检查单次请求平均输入和输出 token 是否变大如果变大重点看系统提示词和历史消息是否被改长。检查调用轮次是否增多重点看是否出现了大量失败重试或错误日志回填。检查有没有代码在循环里重复调用模型比如把模型调用写进了高频循环。检查日志记录的usage是否出现异常大的单次消耗例如有人把整份代码库塞进了 prompt。大多数费用突增都不是模型价格变了而是请求形态变了。6.2 鉴权里的 token 报错不是计费 token“token”这个词在计算机领域有多个含义。大模型计费中的 token 是文本切分单元而登录鉴权里常见的access token、refresh token、id 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_seconds300): return time.time() (exp_epoch - advance_seconds)提前刷新可以避免并发请求同时发现 token 过期然后同时触发刷新导致请求雪崩。生产环境还需要对刷新操作加锁保证同一时刻只有一个线程去刷新。6.4 不要用“字符串长度”估算 token用中文、英文、代码混合文本时按字符数估算 token 误差很大。正确做法是开发环境中使用模型对应的 tokenizer 本地估算线上环境中直接读取模型返回的usage字段以官方计费口径为准。任何“我一个字符大概等于多少 token”的换算都只适用于粗略理解不能用于成本核算。7. 生产环境最佳实践与可复用清单7.1 学习环境与生产环境的差异学习环境里可以把所有请求都打到模型 API方便快速调试。生产环境必须多考虑几层关注点学习环境生产环境用量记录可以不记录必须统一入库支持按项目和模型聚合缓存可加可不加高频重复请求应该做结果缓存预算手动看几次即可必须有软告警和硬拦截鉴权使用临时 token 即可使用独立密钥定期轮换失败重试手动重试增加退避重试、熔断、幂等日志打印到控制台输出结构化日志关联请求 id模型版本随意固定版本升级前先灰度7.2 可复用清单上线前逐一打勾以下清单适用于以大模型 API 为核心成本的项目所有模型调用是否通过统一网关入口每次调用是否记录了项目、模型、输入 token、输出 token、时间戳系统提示词是否做过精简去掉了不必要的长文本输出是否有限制条件或结构化约束历史消息是否有裁剪或摘要策略相同或相似请求是否有缓存是否设置了每日软告警阈值和硬拦截阈值是否按天生成用量报告并检查异常突增调用密钥和登录 token 是否分开管理是否有过期刷新机制是否保留了每次请求的日志方便在费用异常时回溯如果这十项都做到了基本不会出现“上个月花了多少钱完全不知道”的失控局面。7.3 下一步扩展方向在预算控制跑通之后可以做更精细的治理。第一个方向是模型路由简单任务走便宜模型复杂任务走高能力模型降低平均成本。第二个方向是 Prompt 压缩对长代码库、长日志做自动摘要把必须注入的内容压缩到最低。第三个方向是缓存深化使用服务商提供的上下文缓存减少长 prompt 重复计算。第四个方向是把用量统计接入监控平台结合成本面板做每日、每周、每项目的多维分析。对于刚接触大模型开发的读者建议先不要追求复杂的成本治理系统而是先把统一的调用入口和用量记录搭起来。有了数据优化才有方向没有数据任何“省 token”的讨论都只是猜测。