大模型推理服务优先级抢占与长任务隔离落地

日期:2026-09-25

一、项目背景

我们推理服务开着连续批处理,吞吐是上去了,但尾延迟也跟着飘。短请求比如客服问答,经常排在长请求后面干等,用户点一下要等好几秒才有首字。GPU 利用率看着挺高,体验却差,因为长任务占着批内的槽位不释放,短请求被拖在队列里。我们进场时,客服场景的 P99 首字延迟偶尔翻倍,用户明显觉得到点慢,运维盯着利用率还以为一切正常。

当时所有请求一视同仁进同一个批,没有任何优先级概念。结论很清楚,连续批处理不能只追求吞吐,得给不同请求分三六九等,让关键业务的短请求能插队,同时把长任务圈起来别拖累别人。这个项目让我改了看法,利用率高不等于服务好,尾延迟才是用户感知的真相。

二、落地场景

新方案在网关侧给请求打优先级标签,客服和实时问答标为高优先,批量摘要和离线嵌入标为低优先。推理服务按优先级分层排队,高优先请求到来时,如果批内有长任务跑过一定步数,就把它暂时挂起,腾出槽位给高优先请求先出首字。长任务被抢占后记录断点,轮到它时从断点续跑,不重算。

显存水位做了隔离,给长任务单独划一块显存池,避免它把共享池占满导致短请求排队等显存。关键业务的保障率单独监控,运维看板能看到高优先请求的实时首字延迟。上线后我们跟踪过一周,高优先请求的排队等待时间从平均一点几秒降到几十毫秒,客服侧的会话中断投诉几乎归零。

三、关键技术挑战与解决思路

优先级抢占最怕打断代价高,长任务一旦挂起要能无损续跑,否则重算比排队还慢。我们用 KV 缓存分页把长任务的上下文落到一个可寻址的块,挂起时只保留块引用,续跑时直接挂载,成本很低。抢占阈值要调,步数设太短长任务永远跑不完,设太长短请求又等不及,我们按业务容忍度反复压测才定下来。

显存隔离难在动静分配,长任务池设太大浪费,设太小又挡不住突发。我们按历史长任务并发峰值留了余量,再叠一层按需借用,平时小池、峰值借共享池,平衡了利用率和隔离性。关键业务保障率要可观测,不然优先级写了没人知道有没有生效,监控埋点是这套方案能落地的关键。

抢占相关的配置大致如下,阈值靠压测反推:

preemption:
  priority_levels: [high, low]
  preempt_threshold_steps: 64      # 长任务跑过该步数可被抢占
  kv_cache_paging: enabled         # 挂起只保留块引用
  long_task_vram_pool: isolated    # 长任务独立显存池
  borrow_from_shared: on_peak      # 峰值向共享池借

四、效果数据

首字延迟 P99 从翻倍状态压回正常区间,高优先客服请求的首字从秒级降到百毫秒级,用户体验的投诉明显少。长任务隔离率做到满分,长任务跑飞不再拖垮短请求,两者互不影响。GPU 利用率基本没掉,因为抢占回收的成本被 KV 分页摊薄了,吞吐和体验同时保住。

关键请求保障率稳定在九成九以上,运维第一次能直接看到优先级真的在起作用,而不是嘴上说说。我们复盘算过,光客服场景少流失的会话,折算下来每月多保住的稳定咨询量相当可观。压测时我们还特意造过极端长任务洪峰,短请求首字延迟依旧稳,隔离确实兜住了。

我们还在压测里验证了极端场景,故意制造长任务堆积,高优先请求的首字延迟依旧稳,说明隔离是真的兜住了而不是偶然跑赢。运维后来把这套优先级看板当成了日常巡检项,不再只看 GPU 利用率一个指标,因为那个数字曾经骗了我们很久。我们还发现一个副作用,长任务被圈起来后,显存碎片明显少了,之前那种跑着跑着就 OOM 的玄学故障基本消失。我们事后承认,这套方案最大的收益不是提速,而是让服务的表现变得可解释、可预期,运维终于不用靠玄学保平安。

五、可复用经验总结

连续批处理最怕长请求卡短请求,给批内加优先级、允许抢占回收,比盲目拉大批次聪明得多,我们早期把批次窗口拉到很大,吞吐高了体验却崩了。KV 分页是抢占能低成本落地的命门,没有它挂起就是重算,得不偿失。显存隔离别定死,留一层按需借用的弹性,利用率和隔离性才能兼得。监控埋点不是可有可无,优先级不透明就等于没做。我们当时迷信大批次提吞吐,是被尾延迟打脸后才转去做抢占的。下一步想把优先级和成本路由联动,高优先也尽量走便宜的实例。

案例片段(已脱敏): 抢占配置:网关打优先级标签,批内长任务跑过阈值步数可被高优先请求抢占,KV 缓存分页落盘、断点续跑。显存隔离池 + 按需借用共享池。某月统计:高优先首字延迟由秒级降至约 180ms;长任务隔离率 100%;GPU 利用率基本持平;关键请求保障率 99.2%(原无保障,P99 偶发翻倍);极端长任务洪峰下短请求首字延迟仍稳。