|
|
同样是“快”,推理系统里的两种“快”其实说的是两件事:一是单次请求的延迟要低且稳定,二是在高并发下整体吞吐要高。实践里,这两者像跷跷板:你把一头按住,另一头常常翘起来。真正的工程挑战在于,如何在业务可接受的边界内做取舍,并把“抖”控制住。
先说延迟抖动。很多人盯平均延迟,但用户感知的是P95、P99,以及尾部请求在交互中的体验。影响抖动的因素不少:模型本身的解码复杂度(比如长上下文、复杂推断路径)、批处理大小动态变化、调度粒度、以及底层硬件的资源争用(显存碎片、PCIe/NVLink拥塞)。批太小,设备吃不满;批太大,排队时间和KV缓存搬运都在拉长尾部。真正难的是负载不平稳的时候,批次在几十毫秒级的时间窗内忽大忽小,调度器一旦“追高”,尾延迟立刻被放大。
吞吐则更好理解:单位时间内完成多少token或请求。它主要受三点制约:并行度(并发会话数、批大小)、硬件峰值(算力/带宽)以及模型算子实现的效率。为了追求吞吐,常见做法是激进批处理、分阶段解码(连续批)、多租户合批和流式回包。但每一步都可能把延迟曲线拉宽,特别是当不同会话长度差异大时,批内“短任务等长任务”的低效会转化为抖动。
两者对比的关键在于指标选择和SLA定义。我的经验是,不要用单一指标驱动优化,而是用一组“护栏”:例如SLA约束P95延迟≤X毫秒、P99≤Y毫秒,同时最大化每卡每秒token吞吐。实际落地可分层:交互式流式请求优先保证P95,把它们放在低拥塞队列,限制每批的最大跨度;离线或批量评分走“吞吐优先”通道,用更大的批、更激进的连续批,把卡吃满。轻量模型或量化版本可以承担前台尖峰,重模型吞离线,这比“一刀切”更稳。
缓解抖动的几个实操点值得一提。第一,令牌级调度而非请求级调度,缩短批内等待;第二,KV缓存与参数的内存布局要固定化,避免频繁搬移导致尾延迟暴涨;第三,设置合理的队列截断与分级降级,比如上下文过长的请求自动转到次级队列或分片到长上下文专用实例;第四,流控要前置到入口,估算并公布排队时间,避免系统层面的拥塞崩盘。还有一个常被忽略的点:温启动与模型切换导致的冷延迟,要通过预热、热点路由和连接池化来摊薄。
在模型侧,推理精度与速度的平衡也会反映到延迟/吞吐上。INT8/FP8量化、张量并行/流水并行都会带来不同的抖动画像:并行度越高,跨设备同步越频繁,尾延迟倍增的概率越大;而更激进的量化虽然提升吞吐,但某些任务上需要更长的解码步才能达到同等质量,等效延迟可能不降反升。所以别盲
抱着“越快越好”的执念,要先定义“好”的边界,再决定“快”的方式。具体落地,我更建议把优化拆成三个闭环:观测、调度、适配。
观测闭环:别只看平均和吞吐,要同时拉三条曲线——P50/P95/P99延迟、批大小分布随时间、以及设备利用率/内存带宽占用。把这些做成同一时间轴上的关联视图,你才能定位抖动是“排队放大”还是“执行放大”。另外,记录请求画像(上下文长度、目标token数、温度/采样配置),因为采样策略会改变解码步长与算子命中率,直接影响尾部。
调度闭环:将会话按长度与交互性分桶,采用短作业优先(SJF)倾向的令牌级调度,但设置“老化”机制避免长请求饥饿。连续批要控“批跨度”(如最大步差),让批内请求尽量同质化。为多租户提供隔离的速率上限和突发额度,避免单租户尖峰拖累全局。必要时做“尾部切割”:P99超阈值即把超长上下文或慢请求迁到影子队列,牺牲其体验换取整体稳定。
适配闭环:前台服务启用小批+高频合批,配合流式首令牌加速(TTFT优先),后台作业走大批+连续批。对高风险的长上下文/高温度请求,默认路由到更大显存、更高带宽的实例组,并启用片上KV压缩或分块KV。对模型侧进行分层配置:同一任务提供“低延迟轻量版”和“高质量重模型”,用策略路由按SLA与负载动态切换。别忘了在入口层做“可见的背压”,返回预计排队时间与建议稍后重试窗口,用户感知会更好。
评测方法上,推荐双场景基准:交互式基准(短上下文+流式返回)强调TTFT与P95;批评分基准(长上下文+非流式)强调tokens/s与成本。两者都要引入泊松/脉冲流量,以及“混合分布”的请求长度,才能真实还原抖动。优化目标不是把P99拉到跟P50一样,而是让P95稳定在阈值内、P99可解释且可控,且在既定成本下最大化吞吐。
最后谈取舍哲学:延迟与吞吐并非零和,但它们共享同一组瓶颈——显存、带宽、同步与排队。你能做的,是把不确定性前移并分层隔离,把确定性资源留给最敏感的路径。工程上,一条简单实用的准则是:面向人类交互,优先稳定的低抖动;面向机器管道,优先可预测的高吞吐。把两者放在同一张卡上硬拼,才是抖动与抱怨的来源。 |
|