qinghuan
← 用数据改进可靠性 / 全部笔记

单元 35 / 用数据改进可靠性

P1 · 重点提升L3 · 深入

质量、成本与尾延迟:关注等待较久的请求

统计一整次任务的模型、工具用量和等待时间,在质量相同的条件下优化上下文、并发与缓存。

本篇目录 · 按需要跳转

这篇重点学什么

本篇重点:逐次记录调用费用,缺少用量时标为未知;拆开从请求开始到结束各阶段的耗时,再设置应用预算。

开始读正文 ↓
学过相近内容?查看哪些可以跳过

两条路线有些基础相同。已经学会的部分可以跳过,另一条路线只用来补你当前需要的内容。

成本与推理性能

共同基础先说清统计哪些调用、哪些阶段,再比较费用、耗时和单位时间处理量(吞吐)。

另一条路线补充什么?

对比使用与不使用 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

按顺序看,每一步做了什么

  1. 冻结质量门槛和可比较工作负载。
  2. 统一用量字段含义,保存来源与价格版本。
  3. 分解模型、工具、排队和存储时间。
  4. 先修重复请求及无效上下文,再评估缓存或并行。
  5. 报告质量、延迟分布、已知成本和缺失率。

03对照实现代码

OpenJDK 17 BigDecimal 源码 (新标签页)↗

OpenJDK 17 GA,jdk-17+35 · BigDecimal.multiply / add / setScale

下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。

核心机制代码 · java
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指定舍入边界。展示金额与结算金额不一定相同,因此原始用量、费率版本和精确小计都需要保留以便重算。

代码先调用谁,再调用谁

  1. 解析版本化十进制费率
  2. 校验用量完整性与口径
  3. 精确乘加计算已知小计
  4. 保留未知计数
  5. 按展示规则格式化金额

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仅包含每条完整记录的一次精确金额,缓存部分没有重复计入。

时间 O(n)次十进制运算额外空间 O(D)辅助数字空间

D是累计金额及费率的最大十进制位数,运算成本随D增长;固定费率精度与有界计数时通常近似线性扫描。返回结果不复制输入列表。

保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。

展开收起代码 · Main.java · 完整可运行程序Java · 35 行

长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。

Main.java · 完整可运行程序 · Java
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 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。

这次练什么

能用固定任务做重复测量,区分成功与超时、缓存命中与未命中,并验证单项优化是否有收益。

本篇重点加练

未知计量与并行关键路径影响性能结论,重点验证不完整观测时还能做哪些比较。

  • 未知用量处理
  • 并行步骤关键路径

重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。

先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。

基础必做

  1. 作业 01原理问答

    给一项含三次模型调用和两次工具调用的任务设计用量账本,标出输入、输出、缓存与不可观测部分的计量单位。

    提交内容
    一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
    怎样算完成
    • 逐项回应题目要求,区分事实、假设与个人判断。
    • 能用自己的例子解释适用条件,记录尚未弄懂的点。

    练到的知识点全任务调用量统计 · 输入输出及缓存计量

  2. 作业 02代码推演

    某次流式调用首段100毫秒到达,最后一段800毫秒到达,最终结束事件缺失。定义耗时字段并记录未知用量应如何表示。

    提交内容
    手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
    怎样算完成
    • 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
    • 主动检查至少一个边界或异常条件,能解释手算与运行差异。

    练到的知识点未知用量处理 · 首输出与总耗时

  3. 作业 03原理问答

    为并行调用记录定义缺失计量政策,区分总成本未知、局部耗时未知与关键路径未闭合,说明各自允许输出什么形式的结论。

    提交内容
    概念或合同说明、关系图与自造案例,明确事实、假设和适用条件。
    怎样算完成
    • 关键概念有明确边界,能用案例检验声明。
    • 未获证据支持的假设单独标注。

    练到的知识点未知用量处理 · 并行步骤关键路径

综合应用

  1. 作业 04面试场景

    构造10个运行样本,其中2个超时且3个含并行工具步骤,设计尾延迟与关键路径统计协议,说明失败样本如何进入报告。

    提交内容
    场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
    怎样算完成
    • 方案回应给定约束,列出至少一种替代方案及代价。
    • 每项关键结论说明所需证据,不能核实的部分明确标注。

    练到的知识点尾延迟和失败样本 · 并行步骤关键路径

  2. 作业 05编程实践

    实现带用户身份、资料版本和提示版本的缓存键,对照两种缓存策略的质量、成本和延迟,检查跨用户复用与旧版本命中。

    提交内容
    可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
    怎样算完成
    • 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
    • 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。

    练到的知识点缓存身份与版本键 · 质量成本延迟联合比较

  3. 作业 06排错实验

    在账本中混入重复事件、负用量和缺失结束时间,设计校验与对账过程,提交异常记录而非把未知值自动记为0。

    提交内容
    最小故障输入、复现步骤、观测记录、定位依据及回归用例。
    怎样算完成
    • 其他人可按记录复现故障,并区分现象与已验证原因。
    • 定位过程有观测依据,回归用例可以再次暴露原问题。

    练到的知识点全任务调用量统计 · 未知用量处理 · 尾延迟和失败样本

  4. 作业 07编程实践

    编写离线轨迹账本,包含重叠span、缺失结束时间和未知用量;按你的合同生成可确认量与未知项,避免自动填补成确定测量。

    提交内容
    可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
    怎样算完成
    • 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
    • 验证目标由合同确定,模拟结果不冒充真实服务证据。

    练到的知识点未知用量处理 · 并行步骤关键路径

  5. 作业 08排错实验

    模拟一次关键路径上的span丢失,但总请求耗时仍存在。设计对账步骤,判断现有记录能否定位慢点,并提交需要补采的最少字段。

    提交内容
    最小故障序列、观测记录、原因核对与可再次触发原问题的测试。
    怎样算完成
    • 故障可以稳定重现,现象与已验证原因分开。
    • 观测记录足以判断声明的合同是否被违反。

    练到的知识点未知用量处理 · 并行步骤关键路径

进阶挑战 · 掌握前置后选做

  1. 作业 09设计评审

    对两种并发策略进行固定负载的小实验,记录质量与资源范围;写出升级到真实服务前仍需测量的成本及尾延迟条件。

    提交内容
    设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
    怎样算完成
    • 比较使用一致条件与事先声明的标准,记录失败与未知结果。
    • 说明方案的适用前提、未覆盖风险及后续验证方法。

    练到的知识点输入输出及缓存计量 · 并行步骤关键路径 · 质量成本延迟联合比较

思维拓展

先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。

  1. 作业 10思维拓展 · 条件变化

    如果采集预算只允许保留一半的步骤记录,重新设计采样与性能报告,说明如何表达成本和尾延迟结论的不确定性。

    提交内容
    变更前后条件表、重新评审的方案、检验用例与新增代价。
    怎样算完成
    • 只改变明确列出的条件,比较口径保持一致。
    • 修改方案说明收益、代价与仍未验证的范围。

    练到的知识点未知用量处理 · 并行步骤关键路径

完成后,再检验一次

给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。

继续阅读

先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。

打开资料库,找下一份学习材料 ↗