先读协议再读数字
本章把前面各机制接入同一冻结对象。项目为两层、宽度三十二、四头、前馈一百二十八、词表三十一、窗口六十四的随机解码器;种子十七,双精度,CPU 单线程,eval 模式且无 dropout。计时输入由单独的固定随机生成器产生,总长二十八,提示十二,给定后续输入十六,批量一。缓存和参考每次看到完全相同的词元轨迹,不做采样、不遇 EOS、不含网络请求。数字只回答这个具体小矩阵前向工作负载的问题。
计时用高分辨率单调的 perf_counter 取差值,单位秒。每段起点放在调用模型之前,终点放在取末位置 logits 并 clone 之后;输入生成、权重初始化、文件读取、JSON 写入、误差计算均在外部。预填充独立计时,后续每一步单独记录,再求十六步总和。缓存更新发生在前向内部,所以拼接成本在范围内。取末位置输出的 clone 也在范围内,避免候选路径未完成输出物化却被提前停止计时。
预热、顺序与共同输入
正式测量前运行两组参考和缓存轨迹,目的是触发运行库的懒初始化并稳定基本执行路径。预热不是把所有较慢结果删掉;其次数在实验前就固定,正式的九组成对重复全部保存。奇数组先参考后缓存,偶数组先缓存后参考,以减少固定顺序与温度、后台负载或频率漂移的混淆。交错不能消除所有系统噪声,所以还要保留每组原始时间和最小最大值,不把一个最快样本当保证。
同一对完成后在计时范围外比较全部对应 logits,最大误差不应超出第二章的协议。如果先计时一个错误但极快的实现,再拿另一版本的正确性证据背书,就没有在同一对象上联合满足条件。项目 cpu-timing.json 每个原始条目记录重复编号、是否缓存、预填充秒数、逐步秒数、总解码秒数与该对最大误差,并附模型哈希。由这些原始值能复算摘要,而不是只能相信一张无法追溯的截图。
完整算例一:本机实测如何准确转述
本次冻结的本机运行使用 Python 三点十二点十四、PyTorch 二点六点零、macOS arm64、一个 torch 线程。九组十六步解码总时长的中位数与最小最大值见下表,二者中位数之比约为 1.5506。预填充中位数分别为 0.6462 与 0.7125 毫秒。这是已保存一次运行的描述,重跑数值会受系统噪声变化。
| 路径 | 解码中位数 ms | 最小 ms | 最大 ms |
|---|---|---|---|
| false | 14.6855 | 9.3352 | 22.6540 |
| true | 9.4708 | 5.4948 | 23.2611 |
十六个固定后续输入步不能在报告中改名为用户收到的十六个新输出,因为本实验没有采样和输出传输。可报告模型侧固定步吞吐:用十六除以总解码秒数;但先对时间取中位数再取倒数,与先计算每组速率再取平均并不等同。也不能把参考与缓存各自最小值相除后称为典型加速。项目摘要明确采用总解码时间中位数之比,原始配对值留在文件中,便于读者另算成对比值分布并检查顺序效应。
import json,statistics,pathlib
root=pathlib.Path('evidence')
d=json.loads((root/'cpu-timing.json').read_text())
t={flag:[r['decode_total_seconds'] for r in d['raw'] if r['cached']==flag]
for flag in [False,True]}
ratio=statistics.median(t[False])/statistics.median(t[True])
assert abs(ratio-d['median_decode_ratio_uncached_over_cached'])<1e-12
assert len(t[False])==len(t[True])==9
print(ratio,{str(k):[min(v),max(v)] for k,v in t.items()})完整算例二:两种统计摘要可以给相反判断
构造三组参考时间八、八、八毫秒,候选时间四、四、二十毫秒。中位数从八降到四,看上去典型样本快两倍;均值却从八变成九点三三毫秒,变慢。如果服务目标要求所有请求低于十毫秒,候选有一次失败而参考没有。三组不足以估计可靠尾部概率,这个算例只证明中位数改善不蕴含尾部满足要求。报告应保留全部值与失败数,而不是因为二十是慢样本就无理由剔除。
import statistics
base=[8.,8.,8.];candidate=[4.,4.,20.]
assert statistics.median(base)/statistics.median(candidate)==2
assert statistics.mean(candidate)>statistics.mean(base)
assert sum(x>10 for x in candidate)==1
print(statistics.mean(candidate))小样本、尾部与因果解释
九组成对重复可以发现明显时间波动,却不足以稳定估计百分之九十九分位。若用最近秩方法,九个样本的高分位会直接落在最大值,只有一个观测支撑;它不是线上海量请求尾部的可靠估计。重复来自同一机器同一短时间段,也未必独立同分布,后台任务与热状态可产生相关性。因此本课不用九次运行给精密置信区间,不用最大小延迟作未来保证,也不声称已测得服务可用性目标。
若结果与第一章算术比例不同,先检查口径是否相同:参考是否对全部位置执行词表头、缓存是否只执行新位置、是否包含张量分配与拼接、窗口是否重建。再区分工作量解释与硬件瓶颈证据。没有实际带宽或内核剖析,就不能仅凭“加速不大”断言一定带宽受限。当前 CPU 小模型的固定调用开销可能显著,这只是合理候选解释,需专门改变形状并测剖析才能分辨。课程将解释性假设与已测数值分开标注。
为什么 GPU 和服务延迟需要另一套实验
CPU 同步前向调用结束通常意味着相应计算已完成,当前范围可直接用时钟差比较;GPU 操作常异步排队,若仅量主机提交时间,会漏掉设备执行。迁移时应使用设备事件或按协议同步,说明是否含主机设备传输和同步开销。PyTorch 官方基准工具处理预热、线程设置与必要的异步同步,可作为新实验设计参考。本项目没有运行 GPU,收据明确写 NOT RUN,不能把 CPU 的约 1.5506比值复制到 FlashAttention 或其他硬件宣传中。
线上服务还需要到达过程、真实输入长度分布、并发、分词、排队、取消、网络输出与失败记录。固定轨迹基准没有这些内容,所以 user_latency_slo 标为 NOT MEASURED。第四章的 tick 调度也不补上这个空缺,因为它假定每轮耗时固定。若要研究服务首词元和输出间隔,需要真实端到端时戳与请求身份,且负载必须跨过稳定与拥塞区域;这些属于后续独立实验,不是本课小项目已经完成的功能。
第一类检查是原始数据复算:中位数、单位换算、范围、重复计数及比值应由保存的原始数组得到。第二类是配对一致性:同模型哈希、同输入、同窗口和相近 logits;再检查计时范围之外没有漏掉候选特有的必要工作。质量通过与速度改善要在一个冻结版本上同时成立。若只在缓存开启时出现异常或某一组误差失败,应保留该条目并标无效比较,不能将其速度仍计入通过成绩。
成对数据还允许检验顺序影响。将先运行缓存的组与后运行缓存的组分别列出,观察差值是否有一致偏向;若偏向明显,应扩大间隔或采用更系统的随机顺序,而不是只保留占优顺序。当前九组采用确定性交替,方便复跑与审计,未声称已经完成显著性检验。也应保留预填充与每步曲线,因为总和相同可能掩盖单步突发;反过来某一步偶然尖峰也不能单独证明算法随长度退化。只有相同长度区间的多次重复与明确变更才能支持那类因果判断。
尤其不要将两个各自有噪声的最小值相除当作稳定上限。最小值偶尔用于估计干扰较少的执行,但其含义必须单独声明,不能同时称为用户典型体验。本项目保留最小值和最大值是为了展示范围,结论主体仍由事先选定的中位数与完整原始重复支撑。
练习与项目增量
TRY IT YOURSELF
基础:单位与比值
参考十六步中位数0.012秒,候选0.008秒。求中位数之比和候选模型侧固定步速率。
提示 1
提示 2
对照参考解答
TRY IT YOURSELF
进阶:识别不公平范围
参考计时包含模型加载,候选先加载再计时。即使输出相同,可以直接报告缓存加速吗?
提示 1
提示 2
对照参考解答
TRY IT YOURSELF
挑战:为什么九次不能承诺尾部
九次全部低于20毫秒,就写线上p99低于20毫秒。指出统计和工作负载两个缺口。
提示 1
提示 2
对照参考解答
本章交付是 cpu-timing.json 的原始重复与范围明确的摘要。运行 lab.py 会生成新的测量,因此原始冻结数据应保留或写到新目录,不能重跑后仍把旧数字说成刚产生的新结果。最后一章给出把源码、模型和证据绑定的办法,并说明一个合格报告可以同时包含通过、未实现、未运行和未测量。