☰
大模型推理优化的分层实践与技术取舍
2026/11/6 2:00:42 网站建设 项目流程

很多团队在模型上线之后才发现,训练阶段那套以吞吐为唯一目标的优化经验,在推理场景里几乎全部失效。训练是离线批处理,可以等,可以把任务排队到深夜,可以用更大的集群把时间压下去。推理是在线的,用户敲下回车之后就开始计时,首字延迟超过两秒就会被投诉,输出速度低于每秒十五个 token 就会被判定为卡顿。更麻烦的是,推理的成本结构和训练完全不同,它不是一个可以用更多显卡线性摊薄的问题,而是一个被显存带宽、显存容量、调度粒度、内存碎片同时卡住的复合问题。

这篇文章不复述论文里的名词,而是按工程落地的顺序,把推理优化拆成若干层,逐层说明每一层在解决什么矛盾、代价落在哪里、参数该怎么定、上线之后会在什么地方翻车。凡是能在生产环境验证的判断,都给出具体的数量级;凡是依赖具体硬件和具体模型的结论,都标明成立的前提。读者如果只想要一句话总结,那就是:推理优化的本质是把每一份显存带宽和每一块显存容量都换成有效 token,任何不能提升这个兑换率的优化,都只是把瓶颈从一个地方挪到另一个地方。

推理成本的真实构成

先算一笔账,否则后面的讨论没有参照系。以一个七百亿参数的稠密模型为例,权重以半精度存储,占用约一百四十吉字节。单张八十吉字节显存的加速卡放不下,必须切分到两张或者四张卡上。假设四张卡各有每秒两太字节的有效显存带宽,模型被切成四份之后,每个解码步需要把一百四十吉字节的权重完整读一遍。读取时间是一百四十除以八,约等于十七点五毫秒。这意味着在批大小为一时,理论上每秒最多生成五十七个 token,而实际上因为内核启动开销、通信等待、采样开销,实测往往只有三十到四十个 token。

这个数字暴露了第一个事实:解码阶段的单请求速度由显存带宽除以模型大小决定,和算力几乎无关。加速卡的浮点运算能力在这个阶段大量闲置,利用率常常只有百分之五到百分之十。想要提升单请求速度,只有三条路,减少需要读取的字节数,也就是量化或者稀疏化;减少每个 token 需要参与计算的参数量,也就是 MoE 或者剪枝;增加每个字节能产出的 token 数,也就是投机解码这类用算力换带宽的手段。

第二个事实是成本的分母。同样一次权重读取,如果只服务一个请求,那么这次读取的成本全部由这一个请求承担;如果同时服务三十二个请求,成本就被摊薄三十二倍。这就是批处理存在的全部理由。线上推理的每 token 成本,约等于单步耗时乘以并发批大小再除以批大小,收敛到极限时等于权重读取时间除以批大小。批大小从一涨到三十二,单位成本降了将近一个数量级,而单请求的延迟几乎不变,因为读取权重的这十七毫秒是所有请求共享的。

第三个事实是长尾。真实流量里 prompt 长度分布极不均匀,短的几十个 token,长的几万 token。平均值毫无意义,P99 才有意义。一个平均长度八百 token 的负载,P99 可能是一万两千 token,这决定了显存容量的规划基线,也决定了调度器必须支持抢占和换出,否则少数长请求会把整批短请求堵死。

显存带宽与算力两道天花板

任何性能分析都要先判断当前落在哪个区域,这就是 roofline 模型的实际用法。横轴是算术强度,也就是每读取一个字节能完成多少次浮点运算;纵轴是可达性能。硬件的拐点由峰值算力除以峰值带宽决定。以半精度算力接近每秒一千太浮点运算、带宽每秒三点三五太字节的卡为例,拐点大约在三百次浮点运算每字节。

解码阶段每读取一个权重元素只做两次浮点运算,也就是一次乘加,算术强度是二,距离拐点有两个数量级的差距,稳稳落在带宽受限区。预填充阶段每个 token 要和全部权重做一次乘加,同时处理几百上千个 token,算术强度随批内 token 数线性上升,很快就越过拐点进入算力受限区。同一个模型、同一张卡,两个阶段的瓶颈完全不同,这就是为什么把预填充和解码混在一起调度会同时浪费两种资源。

工程上的直接推论有三条。解码阶段不要去优化矩阵乘的算力效率,那是在优化一个不是瓶颈的东西,应该去优化访存模式、提高缓存命中、减少内核启动次数。预填充阶段才值得投入算子融合和更激进的张量核配置。混合阶段应该做阶段分离,把预填充和解码拆到不同的实例甚至不同的卡上,各自用最适合的并行策略,这就是业界常说的预填充解码分离架构的由来,它的收益主要来自消除了相互干扰,而不是来自某个单点加速。

还有一个容易忽略的第二道天花板,显存容量。带宽决定速度,容量决定并发。一张八十吉字节的卡,扣掉权重四十七吉字节、框架开销和临时激活,可用于 KV Cache 的余量往往只剩二十吉字节出头。这个数字直接决定能同时容纳多少个序列,也就直接决定了吞吐上限。很多线上事故的根因不是算力不够,而是 KV Cache 被撑爆之后调度器被迫降低批大小,吞吐断崖式下跌。

预填充与解码的算力画像

预填充阶段一次处理整个 prompt,所有 token 并行进入注意力计算,矩阵乘的规模是序列长度乘以隐藏维度。这个阶段的特点是算力密集、显存访问集中、延迟与序列长度近似线性。一千 token 的 prompt 在主流卡上通常几十毫秒完成,一万 token 就要几百毫秒,而且注意力部分的二次复杂度开始显现。

解码阶段每步只处理一个新 token,矩阵乘退化成矩阵向量乘,张量核的利用率极低。这个阶段的延迟主要是权重读取时间和内核启动时间之和。当模型层数达到八十层,每层至少五到八个内核调用,一步就是几百次内核启动。如果每次启动有五微秒开销,光启动就是一两毫秒,占单步时间的百分之五到十。这就是为什么 CUDA Graph 这类把整段计算图捕获成一次提交的技术,在解码阶段能带来实实在在的收益,收益大小和内核数量成正比,模型越深收益越明显。

两个阶段的另一处差异在批处理的收益曲线上。预填充阶段的批处理几乎线性,把四个 prompt 拼成一批和分四次做,总时间接近,因为算力被充分利用。解码阶段的批处理也接近线性,但原因不同,是因为权重只读一次,多出来的请求只增加了很少的额外访存。这两种线性背后是完全不同的机制,混淆它们会导致错误的容量估算。比如有人按预填充的线性关系去推算解码并发,得出的数字会高估三到五倍。

实测中还有一个坑,两个阶段混批时,一个长 prompt 的预填充会把同批所有解码请求的步进卡住。用户感知到的不是自己的首字慢,而是别人的输出突然停顿几百毫秒。这种抖动在监控上表现为 TPOT 的尖刺,在日志里表现为某些请求的 token 间隔异常。解决办法只有两个,要么在调度层把预填充请求攒到一定量再统一执行,也就是分块预填充,要么物理隔离。前者实现简单但有额外延迟,后者成本高但抖动可控。

KV Cache 的内存账本

KV Cache 是推理系统里最昂贵的资源,也是最容易被低估的部分。它的容量公式很简单,每个 token 每一层需要存两份向量,键和值,每份长度为注意力头数乘以每头维度。以八十层、六十四个头、每头一百二十八维的模型为例,每个 token 每层占用六十四乘一百二十八乘二再乘二字节,等于三十二千字节,八十层合计二点六兆字节。一个四千 token 的对话,单条序列就要占十吉字节以上。

这个数字解释了两件事。第一,为什么长上下文模型对显存的要求不是线性增长而是爆炸式增长。上下文从四千扩到三十二万,单条序列的 KV Cache 从十吉字节涨到八百吉字节,任何单机都放不下,必须引入分页、压缩、驱逐、跨卡切分中的至少两项。第二,为什么并发数和上下文长度是强耦合的,二者共同消耗同一块显存池,产品经理要求同时支持十万上下文和一百并发,在物理上就是自相矛盾的。

优化 KV Cache 有四个方向,每个方向的代价不同。减少每 token 的字节数,用低比特量化键和值,常见做法是键用较高精度、值用较低精度,因为键对异常值更敏感,这样能把占用压到原来的四成到六成,代价是长上下文下的检索精度轻微下降。减少层数维度的冗余,利用相邻层键值高度相似的现象做跨层共享,能省三成左右,但需要重新校准,通用模型直接套用会掉点。减少保留的 token 数,做滑动窗口或者重要性驱逐,配合注意力下沉,能处理超长流式输入,但会丢失远距离细节。改变存储位置,把冷序列换出到主机内存或者本地固态盘,用 PCIe 带宽换显存容量,代价是重新载入时的延迟尖峰。

工程上最容易被忽视的是碎片。早期实现按最大长度为每条序列预分配连续显存,实际长度参差不齐,浪费率普遍在六成以上。改成按固定大小块分配、用块表映射逻辑位置之后,浪费率能压到百分之四以内,这就是分页注意力方案的核心贡献。它的价值不在于算法多巧妙,而在于把内存管理从静态预留变成了动态分配,让批大小可以随负载弹性伸缩。

静态批处理为何浪费

早期的推理服务用固定批大小,攒够 N 个请求或者等满 T 毫秒就发一批,整批一起跑完再收下一批。这个模型的问题在于,一批之内所有请求必须等最长的那个结束。一个生成长度为二十的请求,和一个生成长度为两千的请求放在一起,前者完成后它的槽位就空转,直到后者结束才能释放。

空转的代价可以用一个简单模型估算。假设生成长度服从长尾分布,均值为两百,P95 为八百。固定批大小为十六时,整批的完成时间由最慢的那个决定,接近八百步,而有效 token 只有均值的十六倍,槽位利用率不到三成。也就是说,七成的显存带宽和算力被浪费在等待上。

更糟的是排队。请求必须等一批凑满才能进入,短请求的平均排队时间等于半批的等待时间加上前一批的执行时间。在低峰期批凑不满,等待时间被拉长到几百毫秒,用户体验比高峰期还差。这是静态批处理最反直觉的缺陷,负载越低延迟越高。

还有一个隐性成本,显存按最大长度预留。一批十六个请求,每个都按两千 token 预留 KV Cache,实际平均只用两百,浪费九成。这直接限制了批大小,形成恶性循环,批越小等待越久,等待越久越要攒批,攒批又要求更大的预留空间。

判断是否还在用静态批处理,看两个指标就够。槽位空闲率,也就是批执行期间已完成请求占用的时间比例,超过两成说明浪费明显。排队时间占端到端延迟的比例,超过三成说明调度粒度太粗。

连续批处理与调度器设计

连续批处理把调度粒度从请求级降到迭代级。每一步解码结束后,完成的序列立刻退出并释放资源,排队的请求立刻补位。批的组成在每一步都可能变化,硬件始终保持在满负荷状态。

这个改动带来的吞吐提升在公开数据里通常是两到二十倍,具体取决于生成长度的离散程度。长度差异越大收益越高,因为静态批处理的浪费主要来自长短混跑。如果所有请求长度几乎一致,收益就只有两倍左右。这个规律在容量评估时很有用,不要盲目套用别人博客里的加速比。

调度器要处理的核心问题是优先级和抢占。当显存不足以容纳新请求时,有三个选择。拒绝新请求,简单但会推高错误率。让新请求排队,会造成队头阻塞。抢占已有序列,把它的 KV Cache 换出到主机内存,腾出空间给新请求,等空间够了再换回来重算或者续算。

换出的策略有两种。整序列换出,实现简单,但重新载入时要一次性搬回全部缓存,延迟尖峰明显。按块换出,只换出部分块,粒度细,但块表管理复杂。重算比换出更简单,丢弃缓存后把已生成的 token 拼回 prompt 重新预填充,代价是浪费已完成的算力,对长序列不划算,对短序列反而比换出更快,因为省掉了主机内存往返。

分块预填充是另一个必备特性。把长 prompt 切成固定大小的块,每步只处理一块,块与块之间插入解码步。这样长 prompt 不会独占硬件,解码请求的步进间隔被控制在可接受范围内。代价是长 prompt 的首字延迟增加,因为要等所有块处理完才能出第一个 token。块大小的取值是个权衡,取大了抖动明显,取小了调度开销占比高,常见取值是五百一十二到两千零四十八个 token。

优先级设计要考虑业务语义。交互式对话应该高于批量摘要,付费用户应该高于免费用户,带工具调用的请求因为后续还有多轮,等待成本更高,应该给较高优先级。但要防止优先级反转,低优先级的长请求占满显存后,高优先级请求进来仍然拿不到资源,必须有强制抢占作为兜底。

分页注意力与显存碎片治理

分页注意力的思路借自操作系统虚拟内存。把 KV Cache 切成固定大小的块,每块存固定数量的 token,比如十六个。每条序列维护一张块表,记录逻辑块号到物理块号的映射。注意力内核按块表做间接寻址,不再要求物理连续。

这个设计解决了三个问题。内部碎片,每条序列最后一块可能只用了一部分,浪费上限是块大小减一,相对总长度可以忽略。外部碎片,块可以从任意空闲位置分配,不存在找不到连续大块的情况。共享,多条序列如果前缀相同,可以指向同一批物理块,配合写时复制实现前缀共享,在系统提示词很长的场景下能省下大量显存。

代价在访存模式上。连续存储时,注意力内核可以按顺序读取,缓存友好。分页之后,每个块的物理地址不连续,需要额外的间接层,非连续访问会让有效带宽下降一到两成。块越小,间接层开销占比越高;块越大,内部碎片越多,共享粒度越粗。十六到三十二是实践中比较稳的区间,具体要看注意力头维度和硬件的缓存行大小。

另一个代价是内核复杂度。要支持变长序列、块表映射、共享块,内核必须写成 gather 形式,寄存器压力和分支预测都比连续版本差。这也是为什么早期分页实现的单步耗时比优化过的连续实现高百分之十左右,后来通过把块表预取到共享内存、按块对齐线程束才把差距抹平。

治理碎片还要配合容量水位线。不要等到显存耗尽才触发换出,那样会进入频繁换出换入的抖动状态。常见做法是设两条线,高水位百分之九十触发换出,低水位百分之七十停止换出,中间留出缓冲吸收突发。水位线的具体数值要看请求长度的方差,方差大的负载要把缓冲留宽一些。

量化的精度代价与选型

量化是降低显存占用和带宽压力最直接的手段,但它的收益和风险都很不对称。权重从十六位降到八位,模型体积减半,解码带宽需求减半,单请求速度接近翻倍,精度损失通常在一个点以内,属于性价比最高的档位。降到四位,体积再减半,速度再有提升,但精度损失变得不可预测,某些任务会突然崩掉。

八位权重量化的关键难点是激活值里的离群通道。少数通道的数值幅度比其他通道大几十倍,直接按张量统一缩放会把正常通道压成零。解决办法是按通道缩放,或者把离群值单独用高精度保留。工程上更稳的做法是用训练后量化加少量校准数据,校准集要覆盖真实业务分布,不能只用通用语料,否则在领域任务上掉点会很明显。

四位量化的两条主流路线取向不同。一条是逐层重建,用二阶信息补偿量化误差,精度好但量化过程慢,适合一次性离线处理。另一条是激活感知的权重量化,先在少量数据上观察激活分布,再决定每层的缩放系数,速度快且对指令微调模型更友好。选哪条要看迭代节奏,模型频繁更新的团队应该选快的,模型稳定的团队可以选精度更好的。

KV Cache 量化是另一个独立的收益点,而且经常被忽略。在长上下文场景里,缓存占用可能超过权重本身,把缓存从十六位压到八位,能直接让并发翻倍。风险在于键的分布比值的分布更尖锐,键量化到八位通常安全,值量化到八位在长上下文检索任务上会有可测的下降。保守做法是键八位、值八位但按头分组缩放,激进做法是缓存也上四位,只建议在摘要类任务上尝试。

有一个常见误区是把量化当成免费的午餐,量化后不重新做评测就上线。量化误差是系统性的,不是随机噪声,它会在特定输入上放大。必须准备一套覆盖推理、代码、多语言、长文检索的评测集,量化前后逐项对比,任何一项掉超过两个点都要回退到更高精度。

投机解码的收益边界

投机解码用一个小模型快速起草若干 token,再用大模型一次性验证。如果起草的 token 被接受,就相当于用一次大模型前向换了多个 token 的输出。这个方法在解码阶段是把闲置的算力换成带宽节省,方向上完全正确。

收益的数学边界很清楚。设起草模型单步耗时是大模型的 c 倍,一次起草 k 个 token,接受率为 a。期望每次验证产出 1 加上 a 加 a 平方一直加到 a 的 k 次方个 token,耗时为 1 加 c 乘 k。加速比就是这个期望产出除以耗时。当 a 接近一时,加速比接近 k 除以 1 加 c k,理论上限是 1 除以 c。当 a 很低时,加速比小于一,纯亏。

这个公式给出三条实践准则。起草模型不能太慢,c 要小于零点一,否则再高的接受率也救不回来。k 不能太大,因为接受率随位置快速衰减,第四个之后基本无效,常见取值是三到五。接受率必须实测,不同任务差异极大,代码补全这类强模式任务能到零点八,开放创作只有零点四。

接受率低的时候还有一个变种,用同一模型的多个头并行预测,或者用检索到的相似片段作为草稿。前者不需要额外模型,但需要训练额外的预测头,且只能预测固定长度。后者在 RAG 场景下很有效,从检索文档里摘一段连续文本作为草稿,接受率往往比小模型更高,因为风格和事实都对齐。

必须提醒的是,投机解码不改变输出分布只在验证策略正确时成立。贪心解码下验证规则简单,取第一个不匹配位置之前的全部 token。采样解码下需要按概率比做接受拒绝,实现错了会引入分布偏移,表现为输出重复度异常或者温度失效。上线前要用固定种子做分布一致性测试,不能只看几个样例。

前缀缓存与命中率工程

真实业务里大量请求共享同一段前缀。系统提示词、工具定义、少样本示例、多轮对话的历史部分,这些内容在每个请求里都重复出现。前缀缓存把这些部分的 KV Cache 保留下来,后续请求直接复用,跳过大段预填充。

收益取决于命中长度占比。一个两千 token 的系统提示词加两百 token 的用户问题,命中率九成,预填充成本降一个数量级,首字延迟从几百毫秒降到几十毫秒。反过来,如果每个请求都是独立的短文档问答,命中率接近零,缓存只是白占显存。

工程实现的关键是哈希与淘汰。按块粒度对 token 序列做哈希,块哈希要包含前序块的内容,保证前缀完全一致才命中,否则会出现语义错位的错误复用。淘汰策略用最近最少使用,但要给高频前缀设保护,避免被一次性长请求冲掉。缓存容量要给一个独立配额,不能和活动序列抢同一块显存池,否则高峰期缓存全部被挤掉,命中率归零。

多轮对话要特别处理。会话历史在增长,每轮的前缀都不同,天真的做法是每轮重建缓存,等于没有缓存。正确做法是保留会话的块表,新轮次只增量计算新增部分。这要求调度器理解会话边界,并且能处理历史被截断的情况,截断后前缀哈希失效,必须重新计算。

命中率是可以观测的,而且必须观测。指标包括请求级命中率、token 级命中率、缓存块复用次数分布。token 级命中率低于三成说明前缀设计有问题,可能是提示词里混入了时间戳或者请求 ID 这类每次都变的内容。把可变内容挪到提示词末尾,是一个几乎零成本就能提升命中率的改动,很多团队却一直没做。

多卡并行的通信账

单卡放不下模型就必须切分,切分方式决定了通信开销,通信开销决定了扩展效率。张量并行把每一层的矩阵按列或者按行切开,每层都要做两次全归约,通信量与批大小乘以隐藏维度成正比。流水并行按层切分,只在 stage 边界传激活,通信量小,但有流水气泡。

张量并行的效率取决于互联带宽。同一台机器内用高速互联,带宽是 PCIe 的十倍以上,四卡张量并行的扩展效率能到七成到八成。跨机走普通网络,全归约的延迟会把收益吃光,这时候必须用流水并行或者把张量并行度压到机内。

张量并行度还受注意力头数约束。头数不能被并行度整除时,需要补齐或者复制,补齐会浪费算力,复制会增加显存。六十四个头配三卡就很尴尬,实际部署里并行度通常取二的幂,或者取头数的因数。

流水并行的核心参数是微批数量。微批越多,气泡占比越小,气泡占比近似等于 stage 数减一除以微批数。八级流水配八个微批,气泡还有一成多;配三十二个微批,气泡降到百分之二。但微批多了会增加调度开销和显存占用,因为每个微批都要独立保存激活。推理场景下还有一个约束,解码阶段的批本来就小,切微批会让每批的矩阵乘退化成向量乘,反而降低效率。所以解码阶段通常不用流水并行,或者只用很少的级数。

混合并行的实际配置要看模型和集群。单机八卡跑七百亿模型,常见是张量并行八路,简单直接,通信全在机内。跨机跑更大模型,常见是机内张量并行四路或者八路,机间流水并行两到四级,再叠加专家并行。专家并行的通信模式是全对全,对网络要求比全归约更高,需要专门的负载均衡策略,否则热门专家会成为瓶颈。

还有一个容易忽略的成本,权重加载。七百亿模型四卡切分,每张卡加载三十五吉字节,从网络存储拉取需要几分钟。扩容时新实例的冷启动时间经常被低估,自动扩缩容策略如果没有预热机制,流量高峰到来时新实例还没加载完,扩容等于没扩。

算子融合与内核细节

到了这一层,优化空间变窄但收益仍然实在。解码阶段的内核启动开销占比高,融合能直接减少启动次数。常见的融合包括残差相加与归一化融合、旋转位置编码与查询键变换融合、激活函数与门控融合。每融合一组,单步就少几次启动,八十层的模型累计省下的时间能到百分之五以上。

注意力内核是重点。标准实现要多次读写中间矩阵,显存带宽被大量浪费在中间结果上。分块计算加在线归一化的做法把中间结果留在片上,只把最终输出写回,长序列下能省数倍带宽。这个优化对预填充阶段效果最明显,对解码阶段因为查询长度为一,收益主要体现在避免读写巨大的注意力分数矩阵。

批内变长是另一个难点。同一批里序列长度不同,如果按最长补齐,短序列的算力和带宽都白费。变长内核按实际长度调度,但会引入负载不均,部分线程束提前空闲。工程上的折中是按长度分桶,把长度接近的请求放在一批,桶内补齐浪费小,桶间不互相拖累。分桶会降低调度灵活性,桶的数量要控制,太多会导致每桶批太小。

精度相关的细节也值得注意。半精度累加在某些长序列归约上会溢出或者丢精度,关键归约要用单精度累加。混合精度策略要逐算子确认,不能全局一刀切。层归一化的统计量计算必须用单精度,否则深层模型的数值稳定性会出问题,表现为长输出后期出现重复或者乱码。

最后一个常被忽视的点是内存对齐。张量在显存里的起始地址没有对齐到缓存行,会让每次访问多一次事务,有效带宽下降一到两成。框架通常会自动处理,但自定义算子如果手动分配缓冲区,很容易踩这个坑。排查方法是看性能计数里的扇区读取数是否明显高于理论值。

长上下文的特殊矛盾

上下文一长,三个矛盾同时爆发。注意力计算量随长度平方增长,预填充时间变得不可接受;KV Cache 线性增长,显存迅速耗尽;远距离信息在注意力里被稀释,模型开始丢失中间部分的内容,这就是常说的中段失忆。

第一个矛盾的缓解手段是分块预填充配合流式输出。用户不必等全部上下文处理完才看到第一个字,感知延迟大幅下降。但要保证语义正确,分块之间不能破坏依赖,位置编码必须按全局位置计算,不能每块重新从零开始。

第二个矛盾的缓解手段是缓存压缩。滑动窗口只保留最近若干 token 的完整缓存,远距离的只保留摘要或者稀疏采样。注意力下沉的做法是额外保留最前面若干个 token 的缓存,因为研究发现这些位置承载了 disproportionate 的注意力权重。两者结合能让显存占用变成常数级,代价是精确检索能力下降。

第三个矛盾没有通用解法,只能靠外挂。把长文档做检索增强,只把相关片段放进上下文,是工程上最可靠的做法。上下文长度不是越长越好,把十万 token 全塞进去,既有成本和延迟问题,也有精度问题,不如检索出最相关的一千 token。

长上下文还有一个隐蔽的性能陷阱,位置编码的插值。为了支持超出训练长度的上下文,需要对旋转位置编码做频率调整,调整系数选错会导致短文本性能下降。上线前必须用原始长度范围内的评测集回归,确认短文本没有退化,很多团队只测长文本,忽略了这部分回归。

容量规划与线上观测

容量规划不能从显卡数量倒推,要从服务等级目标正推。先定三个指标,首字延迟的上限、单 token 输出间隔的上限、错误率的上限。然后测量单实例在不同批大小下的这三个指标,画出曲线,找到满足目标的最大批大小,再乘以实例数得到总容量。

这个过程中要区分吞吐和有效吞吐。总吞吐是每秒生成的 token 总数,有效吞吐是满足延迟目标的那些请求贡献的 token 数。为了追求总吞吐把批开得很大,会导致部分请求超时,有效吞吐反而下降。线上真正该优化的是有效吞吐,这就是 goodput 的概念,很多容量评估报告只报总吞吐,掩盖了延迟超标的事实。

排队论在这里有直接用处。把推理实例看成一个服务台,用利特尔法则可以估算在给定到达率和平均服务时间下的排队长度。当利用率超过八成,排队时间会非线性上升,这是所有排队系统的共性。容量规划要把峰值利用率控制在七成以下,留出的余量不是浪费,是吸收突发和应对慢请求的缓冲。

观测体系要覆盖四个层面。请求层记录排队时间、预填充时间、解码时间、 token 数分布,这是定位问题的第一手数据。批次层记录批大小的时间序列、槽位利用率、抢占次数、换出次数,这能看出调度是否健康。显存层记录已用块数、碎片率、水位线触发次数,这能提前发现容量风险。内核层记录各算子耗时占比、启动次数,这能指导优化投入的方向。

告警设置要避免两个极端。只看平均值会漏掉长尾,P99 突然从两百毫秒涨到八百毫秒但均值不变的情况非常常见。只看单点阈值会产生大量噪音,某一次换出就告警会让值班同学麻木。合理做法是对 P99 做同比和环比的双重判断,同时对错误率和抢占率设硬阈值。

最后是灰度与回滚。任何推理优化改动,包括量化、内核替换、调度参数调整,都要能按流量比例灰度。优化往往会改变输出的数值细节,即使指标不掉,也可能在某些输入上产生不同结果。灰度期间要对比新旧版本的输出一致性,差异率超过阈值就停止放量。回滚路径要提前验证,不能等到出事才发现旧镜像已经被覆盖。

把这十四个层面串起来看,推理优化没有单点银弹。真正决定成本的是几个基础决策,模型选型是否匹配任务、并行策略是否匹配集群、调度粒度是否匹配流量形态、缓存策略是否匹配请求分布。这四件事定错了,后面所有的内核调优都是在错误的方向上努力。定对了,剩下的工作就是把每一层的参数调到与业务分布吻合,然后建立一套能持续观测和回归的体系,让优化不会随着版本迭代而悄悄退化。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询