这篇重点学什么
本篇重点:逐次记录调用费用,缺少用量时标为未知;拆开从请求开始到结束各阶段的耗时,再设置应用预算。
开始读正文 ↓学过相近内容?查看哪些可以跳过
两条路线有些基础相同。已经学会的部分可以跳过,另一条路线只用来补你当前需要的内容。
成本与推理性能
共同基础先说清统计哪些调用、哪些阶段,再比较费用、耗时和单位时间处理量(吞吐)。
另一条路线补充什么?
对比使用与不使用 KV Cache 的结果,分清先处理提示内容(prefill)和逐步生成(decode),再检查查询与键数量不同时的掩码、分页共享及写时复制。
读完这篇,你能做什么
能用固定任务做重复测量,区分成功与超时、缓存命中与未命中,并验证单项优化是否有收益。
学习理由、准备要求与知识点
为什么学它
一次任务的成本包括所有回合和重试。提速省钱时,也要同时检查回答质量、权限和失败率有没有变差。
先读这些内容
知识点清单
- 全任务调用量统计
- 输入输出及缓存计量
- 未知用量处理
- 首输出与总耗时
- 尾延迟和失败样本
- 并行步骤关键路径
- 缓存身份与版本键
- 质量成本延迟联合比较
运行前先确认完整算法使用 Java 17 标准库;JDK 源码基线为 OpenJDK 17 GA。示例是内存中的机制模型,外部服务接入需要另外配置和验证。
01先弄懂它是什么
比较哪个版本更快、更省之前,要先保证任务要求和质量标准一致。例如少检索几段资料也许能提速,但若正确率下降,就要一起报告这个代价。可以固定案例、资料和预算,再分别记录成功、拒答、超时和失败,避免只统计顺利完成的请求,把最慢或失败的情况漏掉。
算费用时,要把用量与单价分开。用量来自供应商报告,价格还要注明生效时间和币种;输入、命中计费缓存的输入、输出也可能使用不同费率。不同接口对输入总量是否已包含缓存部分,口径可能不同。先确认计数规则再汇总,缺字段就标为未知,不能把没有报告当成零消耗。
首个输出时间描述用户开始看到内容的等待,总延迟描述任务真正完成的等待。流式输出改善前者不必然降低后者。延迟还受排队、网络、模型、工具和持久化影响;观察分位数时固定样本范围和统计方法,并保留高延迟失败样本。
文中这几个词,先认一认
- 缓存输入量
- 供应商明确报告为命中计费缓存的那部分输入,不能仅凭应用自己有缓存就推算出来。
- 已知小计
- 只用信息完整的用量记录算出的费用合计。它不一定包含所有请求,因此不能直接当作完整账单。
- 尾延迟
- 等待时间分布中较慢的那一部分,例如高分位延迟,能反映部分用户遇到的长等待。
02看看它怎么工作
本题采用明确教学口径:input包含cached,非缓存输入为二者之差,三种费率都按每千单位给出。单次金额是非缓存输入乘普通费率、缓存输入乘缓存费率、输出乘输出费率的和再除一千。费率是测试输入,不对应任何实际商业模型报价。
价格配置用字符串构造 BigDecimal,可以保留所写的十进制值。累计费用时先别逐次四舍五入到分:很多很小的调用若各自先舍入,可能全部变成零。应按计费约定先累加精确值,再在展示或规定的结算步骤统一舍入;真正账单怎样计算,仍以供应商规则为准。
任意必要用量为空时,该调用记为未知并从已知小计排除,不能把空值当零。已知字段仍做非负和缓存不超过输入的检查。输出同时包含已知调用数与未知调用数,使仪表盘能显示覆盖率并触发数据采集修复。
正在加载流程图…
查看流程图源码(Mermaid)
flowchart TD
Q["固定质量门槛"] --> U["统一用量口径"]
U --> C["精确金额与未知计数"]
Q --> L["分解延迟与等待"]
C --> O["优化并执行回归"]
L --> O按顺序看,每一步做了什么
- 冻结质量门槛和可比较工作负载。
- 统一用量字段含义,保存来源与价格版本。
- 分解模型、工具、排队和存储时间。
- 先修重复请求及无效上下文,再评估缓存或并行。
- 报告质量、延迟分布、已知成本和缺失率。
03对照实现代码
OpenJDK 17 GA,jdk-17+35 · BigDecimal.multiply / add / setScale
下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。
BigDecimal rate = new BigDecimal("0.125"); // 测试费率,字符串保留十进制含义。
BigDecimal exact = rate.multiply(BigDecimal.valueOf(units)).movePointLeft(3);
BigDecimal sum = previous.add(exact); // 先累计精确值,避免逐请求舍入损失。
BigDecimal display = sum.setScale(2, RoundingMode.HALF_UP); // 展示规则与原始精度分离。BigDecimal以整数未缩放值和scale表示十进制数,适合表达来自配置的金额。直接从double构造可能带入二进制近似,不应假设显示的小数就是内部精确值。
add与multiply默认无MathContext时进行精确十进制运算,但数值规模仍受资源限制。教学题只处理有限记录和有限精度费率,不承诺任意输入都低成本。
movePointLeft(3)表达除以一千,避免除以任意数时出现无限循环小数。实际计价单位应来自版本化配置,不能把一千硬编码推广到所有服务。
setScale指定舍入边界。展示金额与结算金额不一定相同,因此原始用量、费率版本和精确小计都需要保留以便重算。
代码先调用谁,再调用谁
- 解析版本化十进制费率
- 校验用量完整性与口径
- 精确乘加计算已知小计
- 保留未知计数
- 按展示规则格式化金额
04动手写一个例子
含未知记录的精确用量账本
输入用量记录与每千单位费率,返回完整记录小计、已知数和未知数。input包含cached。只做内存算账,不代表真实供应商账单。Java17自测。
- 输入 1
input1000,cached200,output100;费率1,0.5,2- 输出 1
已知金额1.1
- 输入 2
input未知- 输出 2
unknown增加1,已知小计不变
逐条先检查所有非空字段合法性,再判断是否齐全,防止一条同时包含空值和负数的错误记录被未知状态掩盖。
完整记录按三个互斥计费部分计算,缓存量从总输入中扣除一次,避免对命中部分同时收取缓存和普通输入金额。
返回精确金额并单独计算展示值。测试用compareTo比较数值,避免BigDecimal.equals对scale敏感造成1.10与1.1不相等的误判。
为什么这样写是对的已知数加未知数等于已处理记录数;sum仅包含每条完整记录的一次精确金额,缓存部分没有重复计入。
D是累计金额及费率的最大十进制位数,运算成本随D增长;固定费率精度与有界计数时通常近似线性扫描。返回结果不复制输入列表。
保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。
展开收起代码 · Main.java · 完整可运行程序
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
import java.math.*;
import java.util.*;
public class Main {
record Usage(Long input,Long cached,Long output){}
record Rates(BigDecimal input,BigDecimal cached,BigDecimal output){}
record Bill(BigDecimal known,int complete,int unknown){}
static void nonnegative(Long x){if(x!=null&&x<0)throw new IllegalArgumentException("negative");}
static Bill bill(List<Usage> usages,Rates rates){
if(rates.input()==null||rates.cached()==null||rates.output()==null||rates.input().signum()<0||rates.cached().signum()<0||rates.output().signum()<0)throw new IllegalArgumentException("rates");
BigDecimal sum=BigDecimal.ZERO;int complete=0,unknown=0;
for(Usage u:usages){
nonnegative(u.input());nonnegative(u.cached());nonnegative(u.output());
if(u.input()!=null&&u.cached()!=null&&u.cached()>u.input())throw new IllegalArgumentException("cached");
if(u.input()==null||u.cached()==null||u.output()==null){unknown++;continue;} // 缺失不是零消费。
BigDecimal cost=BigDecimal.valueOf(u.input()-u.cached()).multiply(rates.input())
.add(BigDecimal.valueOf(u.cached()).multiply(rates.cached()))
.add(BigDecimal.valueOf(u.output()).multiply(rates.output())).movePointLeft(3); // 缓存输入只计一次。
sum=sum.add(cost);complete++; // 累加不提前舍入。
}
return new Bill(sum,complete,unknown);
}
static void eq(String amount,int c,int u,Bill b){if(b.known().compareTo(new BigDecimal(amount))!=0||b.complete()!=c||b.unknown()!=u)throw new AssertionError(b);}
public static void main(String[] args){
Rates r=new Rates(new BigDecimal("1"),new BigDecimal("0.5"),new BigDecimal("2"));
eq("1.1",1,0,bill(List.of(new Usage(1000L,200L,100L)),r));
eq("0.5",1,0,bill(List.of(new Usage(1000L,1000L,0L)),r));
eq("0",0,1,bill(List.of(new Usage(null,0L,3L)),r));
eq("0",0,0,bill(List.of(),r));
Bill tiny=bill(Collections.nCopies(10,new Usage(1L,0L,0L)),r);eq("0.010",10,0,tiny);
if(!tiny.known().setScale(2,RoundingMode.HALF_UP).equals(new BigDecimal("0.01")))throw new AssertionError();
try{bill(List.of(new Usage(1L,2L,0L)),r);throw new AssertionError();}catch(IllegalArgumentException expected){}
try{bill(List.of(new Usage(null,-1L,0L)),r);throw new AssertionError();}catch(IllegalArgumentException expected){}
System.out.println("PASS 7 tests");
}
}这些特殊情况也要试一试
- 未知用量不按零处理。
- 缓存量是否属于总输入必须先规范口径。
- 真实账单可能有最低计费单位和不同舍入规则。
进阶选读 · 后续再读:质量约束下的路由与缓存
场景不同任务有不同难度,缓存和多级模型调用存在不同代价。
做法在离线集测量各路由质量与延迟,用可解释门槛决定缓存、检索和升级;缓存键纳入身份范围、资料版本和提示版本。
复杂度全局资源和质量约束下的离散选择可变为组合优化;在线启发式需通过保留集与流量实验验证。
好处与代价便宜路径可能失败后触发昂贵重试,应比较整个任务期望成本而非首个调用报价。
05项目里怎么用
优化前,先查有没有重复调用工具、反复发送用不上的历史,以及异常后不停重试。处理这些浪费后,再考虑模型路由,也就是按任务需要选择不同模型。每改一次,都用同一组任务检查回答质量有没有退步,这就是质量回归检查。同时保留输入长度、输出长度和任务类型:如果新版刚好只测了短问题,不能把省下的时间都归功于代码优化。连接复用和超时设置也可能比更换模型更直接。
缓存需要同时满足内容正确与权限正确。不同用户即使问题相同,也可能允许读取不同资料,不能直接共享含私有证据的答案;资料、索引和提示版本变化应触发失效策略。缓存命中率高但答案陈旧,仍然是错误。
并行工具减少依赖允许范围内的等待,却可能增加并发配额压力和峰值成本。设全局并发与预算,记录取消后是否继续产生用量。性能报告说明测试规模、并发、网络范围和未采集部分,不用单次最快记录代表稳定体验。
容易踩的坑
- 未知用量填零后宣称成本下降。
- 缓存输入同时计入普通输入导致重复计费。
- 只看首字时间忽视完整任务等待。
- 只展示成功样本的平均延迟。
几种做法,怎么选
表格有四列,窄屏可左右滑动查看代价和适用条件。
| 方案 | 优势 | 代价与限制 | 适用条件 |
|---|---|---|---|
| 缩短有效上下文 | 常能减少用量和等待 | 可能丢失关键证据 | 有回归集保护的冗余清理 |
| 结果缓存 | 可大幅减少重复工作 | 需要权限隔离和失效 | 重复且资料稳定的请求 |
| 受限并行 | 压缩独立步骤等待 | 增加峰值压力与协调 | 依赖清楚的工具任务 |
06检验是否读懂了
先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。
先用自己的话解释
01首字快代表全部完成快吗?
展开参考答案与推导
不代表,流式能提前展示,但工具、生成和持久化仍可能耗时。
02BigDecimal为何从字符串构造?
展开参考答案与推导
保留配置的十进制值,避免double二进制近似进入金额。
03成本优化为什么要质量门槛?
展开参考答案与推导
减少必要检索或输出也能变便宜,但可能使任务无法正确完成。
换个情况,还会处理吗
01费率不变但成本突然降零,先查什么?
展开参考答案与推导
先检查用量字段采集和解析,未知不能默认零,再核对调用量与缓存口径。
02同问题缓存给不同用户安全吗?
展开参考答案与推导
取决于资料和权限范围,缓存键或读取校验必须隔离身份与授权内容。
03并发加倍后p95更慢,可能原因?
展开参考答案与推导
配额限流、连接池排队、CPU和下游拥塞;分解等待时间并调整整体并发。
写代码试一试
01测试例成本是多少?
- 输入 / 条件
输入1000含缓存200,输出100,费率1/0.5/2- 输出 / 预期
1.1
提示 非缓存为800。
展开参考答案与推导
普通0.8加缓存0.1加输出0.2,得到1.1。
02十次0.001如何展示?
- 输入 / 条件
先合计再保留两位- 输出 / 预期
0.01
提示 不要每次舍入为0.00。
展开参考答案与推导
保留原始精度累加0.010,最后按展示规则舍入。
03未知记录怎么返回?
- 输入 / 条件
一条完整,一条缺输出- 输出 / 预期
已知数1,未知数1
提示 结果包含覆盖率。
展开参考答案与推导
只计算完整记录小计,同时显示总成本尚不完整,不能给出虚假总额。
读完之后,独立完成
课后作业:自己做一遍
本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。
能用固定任务做重复测量,区分成功与超时、缓存命中与未命中,并验证单项优化是否有收益。
本篇重点加练
未知计量与并行关键路径影响性能结论,重点验证不完整观测时还能做哪些比较。
- 未知用量处理
- 并行步骤关键路径
重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。
先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。
基础必做
- 作业 01原理问答
给一项含三次模型调用和两次工具调用的任务设计用量账本,标出输入、输出、缓存与不可观测部分的计量单位。
- 提交内容
- 一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
- 怎样算完成
- 逐项回应题目要求,区分事实、假设与个人判断。
- 能用自己的例子解释适用条件,记录尚未弄懂的点。
练到的知识点全任务调用量统计 · 输入输出及缓存计量
- 作业 02代码推演
某次流式调用首段100毫秒到达,最后一段800毫秒到达,最终结束事件缺失。定义耗时字段并记录未知用量应如何表示。
- 提交内容
- 手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
- 怎样算完成
- 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
- 主动检查至少一个边界或异常条件,能解释手算与运行差异。
练到的知识点未知用量处理 · 首输出与总耗时
- 作业 03原理问答
为并行调用记录定义缺失计量政策,区分总成本未知、局部耗时未知与关键路径未闭合,说明各自允许输出什么形式的结论。
- 提交内容
- 概念或合同说明、关系图与自造案例,明确事实、假设和适用条件。
- 怎样算完成
- 关键概念有明确边界,能用案例检验声明。
- 未获证据支持的假设单独标注。
练到的知识点未知用量处理 · 并行步骤关键路径
综合应用
- 作业 04面试场景
构造10个运行样本,其中2个超时且3个含并行工具步骤,设计尾延迟与关键路径统计协议,说明失败样本如何进入报告。
- 提交内容
- 场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
- 怎样算完成
- 方案回应给定约束,列出至少一种替代方案及代价。
- 每项关键结论说明所需证据,不能核实的部分明确标注。
练到的知识点尾延迟和失败样本 · 并行步骤关键路径
- 作业 05编程实践
实现带用户身份、资料版本和提示版本的缓存键,对照两种缓存策略的质量、成本和延迟,检查跨用户复用与旧版本命中。
- 提交内容
- 可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
- 怎样算完成
- 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
- 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。
练到的知识点缓存身份与版本键 · 质量成本延迟联合比较
- 作业 06排错实验
在账本中混入重复事件、负用量和缺失结束时间,设计校验与对账过程,提交异常记录而非把未知值自动记为0。
- 提交内容
- 最小故障输入、复现步骤、观测记录、定位依据及回归用例。
- 怎样算完成
- 其他人可按记录复现故障,并区分现象与已验证原因。
- 定位过程有观测依据,回归用例可以再次暴露原问题。
练到的知识点全任务调用量统计 · 未知用量处理 · 尾延迟和失败样本
- 作业 07编程实践
编写离线轨迹账本,包含重叠span、缺失结束时间和未知用量;按你的合同生成可确认量与未知项,避免自动填补成确定测量。
- 提交内容
- 可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
- 怎样算完成
- 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
- 验证目标由合同确定,模拟结果不冒充真实服务证据。
练到的知识点未知用量处理 · 并行步骤关键路径
- 作业 08排错实验
模拟一次关键路径上的span丢失,但总请求耗时仍存在。设计对账步骤,判断现有记录能否定位慢点,并提交需要补采的最少字段。
- 提交内容
- 最小故障序列、观测记录、原因核对与可再次触发原问题的测试。
- 怎样算完成
- 故障可以稳定重现,现象与已验证原因分开。
- 观测记录足以判断声明的合同是否被违反。
练到的知识点未知用量处理 · 并行步骤关键路径
进阶挑战 · 掌握前置后选做
- 作业 09设计评审
对两种并发策略进行固定负载的小实验,记录质量与资源范围;写出升级到真实服务前仍需测量的成本及尾延迟条件。
- 提交内容
- 设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
- 怎样算完成
- 比较使用一致条件与事先声明的标准,记录失败与未知结果。
- 说明方案的适用前提、未覆盖风险及后续验证方法。
练到的知识点输入输出及缓存计量 · 并行步骤关键路径 · 质量成本延迟联合比较
思维拓展
先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。
- 作业 10思维拓展 · 条件变化
如果采集预算只允许保留一半的步骤记录,重新设计采样与性能报告,说明如何表达成本和尾延迟结论的不确定性。
- 提交内容
- 变更前后条件表、重新评审的方案、检验用例与新增代价。
- 怎样算完成
- 只改变明确列出的条件,比较口径保持一致。
- 修改方案说明收益、代价与仍未验证的范围。
练到的知识点未知用量处理 · 并行步骤关键路径
完成后,再检验一次
给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。
继续阅读
先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。
- 必读参考文档英文
OpenTelemetry:Metrics (新标签页)↗
阅读指标类型、聚合及直方图的用途,选择合适的成功率、请求量与延迟分布统计。
- 实践参考文档英文
Spring AI:Observability (新标签页)↗
查看模型、工具和向量检索的观测字段及默认内容采集策略,按运行标识定位耗时与失败。
- 深入实践指南英文
Google Cloud:Retry strategy (新标签页)↗
重点阅读指数退避、幂等条件与重试反模式;示例针对 Cloud Storage,应用到其他服务时须重新核对错误语义。
- 深入参考文档英文
OpenJDK 17 BigDecimal 源码 (新标签页)↗
按指定版本定位
BigDecimal.multiply/ add /setScale;对照源码分析理解实际调用边界。