项目入口与前置契约
本课围绕一个可复跑的 CPU 推理实验台展开。先修要求是会读取矩阵形状、理解因果注意力和下一词元预测;若这些概念尚不熟悉,应先完成 Transformer 实战前四章。本课不再训练语言能力,而是冻结一台小解码器,比较同一输入在不同执行方式下的输出、空间与耗时。项目随机权重只提供可审计的数值函数,随机词元只提供固定形状的工作负载;它们不能回答中文问答质量好坏。最终交付是带源码、负对照与测量边界的报告。
一次自回归请求先接收长度为 的提示,再输出最多 个词元。预填充将提示一起送入模型,最后位置的 logits 用来选择第一个输出。随后每得到一个新词元,再把它送回模型得到下一个分布。这意味着生成 个输出通常只需 次后续解码前向;本项目为了比较固定工作量,另定义“给定后续输入轨迹”,预填充后继续处理指定的 个输入词元,并比较 个预测分布。两种计数不可混用,性能表会明确使用后者。
从时间轴定义指标
令到达、开始计算、首个输出、最终输出时刻依次为 ,单位统一为秒。排队时间是 ,服务侧首词元时间是 ,用户侧首词元等待是 。总响应时间为 。若确有 个输出,平均输出间隔可定义为 ;分母不是提示长度,也不是包括首词元的输出数量。网络传输、分词和格式化是否计入,需要在记录开始点和结束点处说明,否则同名指标仍可能不是同一个量。
吞吐量也有不同分母。某观测区间内完成的输出词元数除以区间秒数,是聚合输出吞吐;把每个请求的输出速率求平均,是请求速率均值。这两个统计量通常不同。只测已经开始执行的批次,会漏掉拥塞时不断增长的队列;只看总吞吐,也会掩盖短请求等长请求的体验。项目第七章只测前向计算,排队和用户首词元延迟均标为未测量,第八章再把可证明的结论与需要服务系统才能判断的结论分开。
乘加账本必须先声明约定
用 表示批量, 表示当前序列长度, 表示隐藏宽度, 表示前馈中间宽度, 表示块数, 表示词表大小。一项标量乘法加一次加法计作两次浮点运算。矩阵 乘 的主项因此为 ;它忽略每个输出少一次加法的精确差值。下文统计的是密集实现的主项,不包括归一化、偏置、指数、数据移动和 Python 调用开销。这里的近似来自记账口径,不是模型输出近似。
普通多头注意力的 Q、K、V、O 四个投影都是宽度 的线性变换,每块合计 次运算。两层前馈分别从 到 、从 到 ,合计 。多头的打分与加权求和各需 ,共 。项目显式计算完整方阵后施加因果 mask,因此此项按方阵记;若专门的三角内核跳过不可见位置,常数会改变。不能因为上三角被设为负无穷,就认为普通矩阵乘法已经免算了这些位置。
缓存解码只对新位置产生查询和新的 K、V。若加入当前词元后的可见总长度为 ,单步块内投影与前馈变成 ,注意力为 。词表头仅对新位置输出时为 。无缓存的参考实现重新计算长度 的全部前缀,因而要把预填充公式中的 换成 。缓存没有让新查询免看旧内容,它消除的是旧位置的重复投影、前馈和注意力计算。
完整算例一:同一个小模型的两种执行
取 。每块投影与前馈的单位置主项为 。长度十二的预填充每块为 ,两块再加头部 ,共 次运算。在可见长度十三处缓存单步为 ;整前缀重算则为 。这是算术计数差,不是十三倍速度的保证。
为何运算减少很多而实际速度未必同比改善?小矩阵的调用固定成本、张量分配、缓存拼接、解释器调度都可能占显著比例;大模型还受权重与 KV 读写限制。令算术工作量为 、最低数据流量为 字节,硬件理想运算率为 运算每秒、带宽为 字节每秒,则执行时间至少受 约束。这个下界允许计算与传输重叠,不能把两项直接相加当精确时间,也不能把峰值规格当实际达成率。
完整算例二:首词元和吞吐给出相反排序
假设方案甲排队零毫秒、预填充四十毫秒,首输出后九个间隔均为十毫秒;方案乙排队八十毫秒、预填充二十毫秒,九个间隔均为五毫秒。两者都输出十词元,甲的用户首词元等待四十毫秒、完成一百三十毫秒,乙分别一百毫秒、一百四十五毫秒。乙的后续输出更快,用户却更晚拿到首词元和完整答案。如果只报告每秒二百对一百词元,就会丢掉影响体验的队列成本。此处数字是构造反例,不是本机实测。
单位检查是第一类独立验证:时间不得与词元直接相加,乘加量除以运算率才是秒;字节除以字节每秒也才是秒。第二类验证是结构极限:令 时预填充和单步公式一致,令 时不应出现后续解码间隔,令 翻倍时块内主项翻倍而头部不翻倍。这些检验不依赖某个运行库。下面用标准库把算例固化为断言,再在后续章节与真实张量及计时交叉对照。
def prefill(T, N=2, B=1, d=32, f=128, V=31):
return N*B*(8*T*d*d+4*T*d*f+4*T*T*d)+2*B*T*V*d
def decode(t, N=2, B=1, d=32, f=128, V=31):
return N*B*(8*d*d+4*d*f+4*t*d)+2*B*V*d
assert prefill(12)==650496
assert prefill(13)==708032 and decode(13)==54464
assert prefill(1)==decode(1)
print(prefill(13)/decode(13))从工作量进一步检查带宽假设
算术强度定义为 ,单位是运算每字节。设一个线性层权重有一百万个元素,每个元素两字节,每个请求的单位置矩阵向量乘约需两百万次运算。若权重每步都从主存读取且没有复用,单请求仅就权重而言强度约为一运算每字节。四个请求在同一批次共享一次读取而执行四份计算时,强度约变成四。这个推导说明批处理可能摊薄权重流量,却没有计算 KV、激活、输出和缓存层级,也没有证明四请求延迟与单请求一样。真实流量应由对应硬件工具另测。
再取一个明确标为假设的设备:有效运算率每秒一百亿次、有效带宽每秒十亿字节。若任务含一亿次运算与两千万字节流量,两个下界分别为十毫秒与二十毫秒,理想重叠的下界是二十毫秒。将运算减半但流量不变,下界仍是二十毫秒;将流量减半则两项同为十毫秒。由此可见,同一个乘加优化在不同瓶颈下可能没有可见收益。项目只测端到端 CPU 前向,未采集实际带宽计数,因此不能声称已经证实该次运行属于某个瓶颈。
检查聚合吞吐还需要闭合观测区间。若请求甲在一秒完成十词元、请求乙在两秒完成十词元,二者同时从零开始,则到第二秒总共完成二十词元,聚合吞吐为每秒十词元;各请求速率的均值却为每秒七点五词元。若漏掉最后一个慢请求,只观察第一秒已完成者,还会制造选择偏差。后文测量表保留全部重复和全部固定后续步,没有以删掉慢样本来提高数字。
练习与项目增量
TRY IT YOURSELF
基础:指标计算
某请求到达0秒,0.03秒开始计算,0.08秒首输出,0.20秒末输出,共7个输出。求排队、用户首词元等待和后续平均间隔。
提示 1
提示 2
对照参考解答
TRY IT YOURSELF
进阶:前馈宽度变化
在算例一中只把f从128改为256,长度12的预填充增加多少主项运算?
提示 1
提示 2
对照参考解答
TRY IT YOURSELF
挑战:识别不成立的速度结论
有人从708032/54464约等于13断言缓存必快13倍。给出一个定量反例,并说明应补测什么。
提示 1
提示 2
对照参考解答
本章增量是工作负载契约:项目 README 说明固定后续输入而非采样生成,记录提示十二、后续十六、批量一及窗口六十四。后续第二章必须先证明缓存与参考函数相同,第三章再检查空间增长;只有两者通过,才允许把计时表解释为同一任务的执行差异。若改变窗口、词表或输出位置数量,应建立新的工作负载记录,不能直接把两个不一致的表格相除。