1.1 一次推理的算力账单


文档摘要

1.1 一次推理的算力账单 本节摘要:导读铺开了四层缓存版图,本节先回答一个更基本的问题——账单上的钱到底花在哪。一次 LLM 调用的付费 token 由系统提示词、历史上下文、用户输入、输出四块构成;多轮对话里,前三块中的绝大部分每轮都在重复计费。本节给出构成拆解、估算口径、可运行的测算脚本,并用一张膨胀表格引出全章主线:重复 Prefill。 一张客服账单暴露的重复计算 某电商平台的智能客服团队专门处理"重复计费"类投诉。这类问题天然是多轮的:用户先描述情况,机器人核对订单、解释规则,用户再追问细节。运营负责人核对月度账单时发现一个反直觉的数字——用户平均每天在对话框里只打两百字上下,系统每天为每个会话付出的输入 token 却是打字量的几十倍。钱花在哪了?

1.1 一次推理的算力账单

本节摘要:导读铺开了四层缓存版图,本节先回答一个更基本的问题——账单上的钱到底花在哪。一次 LLM 调用的付费 token 由系统提示词、历史上下文、用户输入、输出四块构成;多轮对话里,前三块中的绝大部分每轮都在重复计费。本节给出构成拆解、估算口径、可运行的测算脚本,并用一张膨胀表格引出全章主线:重复 Prefill。

一张客服账单暴露的重复计算

某电商平台的智能客服团队专门处理"重复计费"类投诉。这类问题天然是多轮的:用户先描述情况,机器人核对订单、解释规则,用户再追问细节。运营负责人核对月度账单时发现一个反直觉的数字——用户平均每天在对话框里只打两百字上下,系统每天为每个会话付出的输入 token 却是打字量的几十倍。钱花在哪了?答案藏在每一次 API 调用的 token 构成里,而拆开它,就摸到了"缓存为什么存在"的第一块证据。

付费 token 的四块构成

模型在一次调用里看到的输入,远不止用户刚打的那行字。四块构成各自的性质决定了它们在账单里的角色:

构成 内容示例 特点 每轮是否重付
系统提示词 角色设定、业务规则、输出格式约束 全会话固定,几百到几千 token 每轮原样重发、重付
历史上下文 之前所有轮次的输入与输出 随轮次滚雪球式增长 每轮原样重发、重付
本轮用户输入 用户刚打的字 每轮新增,占比极小 只付一次
模型输出 生成的回答 每轮新增 只付一次

估算公式用文字写出来是:设系统提示词为 S 个 token,第 k 轮用户输入 U_k、模型输出 O_k,则第 k 轮的输入 token 数等于 S,加上前 k−1 轮全部输入与输出之和,再加上 U_k;单轮付费等于输入 token 乘输入单价,加输出 token 乘输出单价。把 k 从 1 累加到 n,就是整段会话的总账单。公式里最扎眼的是中间那一大坨:它每轮都在变大,且每轮都被原样重付一遍。

估算口径:token 从哪里数出来

公式的三个变量要靠分词器来数,不能拿字数乘系数。经验口径是:中文在主流分词器下大约 1 字折 0.6 到 1.1 个 token,生僻字与特殊符号会显著放大这个比值;英文平均 1 个 token 约 0.75 个单词;数字、标点与 JSON 结构符各自单独计。这里有两个常踩的坑:按字数做容量规划,误差能到三成以上;提示词里塞大段 JSON 示例,结构符消耗的 token 往往比正文还多。靠谱的做法是上线前用分词器离线量一遍系统提示词与典型样本,把 S、U、O 三个参数标定成实测值,再代入公式推算账单。另一个约束同样常被忽略:上下文窗口有上限,膨胀不只是钱的问题——后文的膨胀表里第 8 轮输入已近 10 万 token,中等窗口的模型根本装不下,多轮对话要么截断历史、要么做摘要压缩,而这些手段又会在 1.4 节的失效规则下反过来影响缓存命中。

用脚本把账算清楚

下面这段脚本不依赖任何外部库,直接运行即可。输入是三个假设参数(系统提示词长度、每轮用户输入、每轮输出),输出是每轮付费明细与累计统计:

# 多轮对话付费 token 测算脚本 # 输入:无外部依赖,直接运行;可修改 S / U / O 三个参数 # 输出:每轮付费明细表与累计付费 S = 600 # 系统提示词固定 600 token U = 80 # 用户每轮输入 80 token O = 200 # 模型每轮输出 200 token rounds = 8 history = 0 # 历史上下文 = 之前所有轮的输入+输出 cum = 0 print(f"{'轮次':>4} {'系统':>6} {'历史':>8} {'输入':>8} {'输出':>6} {'本轮付费':>9} {'累计':>9} {'重复占比':>8}") for k in range(1, rounds + 1): inp = S + history + U # 本轮实际送进模型的输入 paid = inp + O # 输入与输出都计费 repeated = history + (S if k > 1 else 0) # 之前轮次已付过的部分 cum += paid print(f"{k:>4} {S:>6} {history:>8} {inp:>8} {O:>6} {paid:>9} {cum:>9} {repeated / inp:>7.1%}") history += inp + O # 本轮内容进入下一轮的历史

运行输出示例:

轮次 系统 历史 输入 输出 本轮付费 累计 重复占比 1 600 0 680 200 880 880 0.0% 2 600 880 1560 200 1760 2640 94.9% 3 600 2440 3120 200 3320 5960 97.4% 4 600 5560 6240 200 6440 12400 98.7% 5 600 11800 12480 200 12680 25080 99.4% 6 600 24280 24960 200 25160 50240 99.7% 7 600 49240 49920 200 50120 100360 99.8% 8 600 99160 99840 200 100040 200400 99.9%

多轮对话的付费膨胀表

把脚本输出整理成表格,趋势一目了然:

轮次 系统提示词 历史上下文 本轮输入 本轮输出 本轮付费 累计付费 输入中重复占比
1 600 0 680 200 880 880 0.0%
2 600 880 1560 200 1760 2640 94.9%
3 600 2440 3120 200 3320 5960 97.4%
4 600 5560 6240 200 6440 12400 98.7%
5 600 11800 12480 200 12680 25080 99.4%
6 600 24280 24960 200 25160 50240 99.7%
7 600 49240 49920 200 50120 100360 99.8%
8 600 99160 99840 200 100040 200400 99.9%

读这张表有两个落点。其一,恶化速度:第 2 轮就把重复占比推到 94.9%——从第二轮起,每一轮付费的主体都是"重听一遍前面的话";到第 8 轮,累计 20 万 token 里只有约 2200 个与新增信息有关,其余全是复读。其二,结构不变性:无论把系统提示词调长调短、把输出调大调小,历史上下文按"前一轮全部内容"累加的规律不变,因此膨胀曲线的形状不变——改变的只是纵轴的刻度。这意味着任何多轮对话产品都在跟同一条曲线搏斗,也意味着缓存的收益模型可以泛化:重复占比逼近 100% 的地方,就是回收空间最大的地方。

图 1-1:多轮对话付费 token 堆叠增长图

图 1-1:多轮对话付费 token 堆叠增长图

无状态 API:重复付费的制度根源

一个自然的疑问:平台为什么不记住会话、只收新增 token 的钱?根源在于 API 的无状态设计。每次调用必须携带模型给出正确回答所需的全部上下文,服务端不为会话保留任何中间状态。这样设计有充分的工程理由:负载均衡之下,任意节点都能服务任意请求;请求之间相互隔离,容错与水平扩容都简单;保存会话状态本身就是一笔存储与一致性的开销,平台不愿替所有用户背着。于是"重复"被制度化了——会话越长,重复传输与重复计算越多,这是结构性现象,而非计费缺陷。

重复的根源与缓存的分布,其实是同一枚硬币的两面:既然谁有能力记住状态、谁就能提供复用,那么缓存的落点就取决于"谁记住了什么"。推理引擎记住了请求内的 KV,于是有显存层;框架记住了跨请求的前缀分页,于是有框架层;平台记住了付费用户的热前缀,于是有平台层;调用方记住了历史答案,于是有应用层。四层版图的形状,正是"状态可以被谁记住"这个问题的四种答案。

输出侧为什么省不掉

公式的输出项值得单独说一句:无论缓存技术怎么演进,输出 token 都省不掉。原因有二。自回归生成本质上是一步一步采样,同样的前缀可以合法地产生不同的输出,不存在"提前知道要算什么"的捷径;输出在生成完成之前也不可知,无法作为键去查找任何东西。所以复用经济学在 LLM 里的适用边界是清晰的:缓存的杠杆全部作用在输入侧,输出侧的降本只能靠更小的模型、更短的回答或更快的硬件——这也解释了 1.2 节之后,全书讨论缓存时为什么盯着 Prefill 与 KV。

还有一个常见误区要拆掉:平台并没有"按新增 token 计费"的选项,因为计费反映的是推理的真实成本——Prefill 的计算量与输入长度成正比,平台收的正是这段算力的钱。只有当平台能把重复前缀的计算真正省下来(也就是 context caching),才有空间打折。换句话说,折扣是省出来的,不是让出来的。

案例复盘:账单追因的五步

背景:客服机器人上线三个月,"重复计费"类会话平均 8 轮,日均 1 万通;按输入 2 元每百万 token、输出 8 元每百万 token 计价,月账单持续走高,团队却找不到可优化的抓手。
操作:团队用上面的脚本代入真实参数——系统提示词 600 token,用户每轮 80 token,输出每轮 200 token——还原单通会话的 token 流水,并把 1 万通会话的日均值折算出来。
结果:单通会话累计付费约 20 万 token,其中输入侧约 19.9 万、输出侧 1600,按单价折算约 0.41 元;日均 1 万通即约 4100 元,一个月近 12.3 万元。而 8 轮里用户真正贡献的新信息只有 640 token,付费 token 中与"新增用户输入"相关的只占约 1.1%,其余全是反复重付的系统提示词与历史上下文。
解读:膨胀的根源在计费方式与推理机制的重叠区。API 按"每次调用看到的全部输入"计费,推理引擎每次都要对全部输入做一遍 Prefill——正是下一节要解剖的两段式流水线的第一段。账单膨胀并非厂商乱收费,而是"重复计算"被如实计了费;既然重复是结构性的,回收它的缓存就有明确的经济动机。
变式:三个方向能改变数字。把系统提示词从 600 加到 3000(塞入更多业务规则),单通成本涨幅不到两成,因为历史上下文才是大头;把每轮输出从 200 提到 800,输出侧成本翻四倍,但输出 token 无法靠缓存省掉——缓存只作用于输入侧;若为会话启用平台前缀缓存,重复部分按约十分之一计价,单通成本从约 0.41 元降到约 0.06 元,降幅接近九成。三个变式放在一起,划出了"哪些钱能省、哪些钱省不掉"的边界,也把追问引向了流水线内部:省掉的到底是什么计算。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U