大模型推理批内优先级调度与长请求抢占回收落地

日期:2026-08-25

一、项目背景

我们这套大模型推理服务跑在私有化底座上,用的是连续批处理。刚开始大家只看 GPU 利用率,觉得越高越好,汇报也好看。实际用起来客服侧抱怨很多:短问答明明该秒回,却要排很久;关键业务的工单摘要被长文档摘要挤在后面,等得业务方直接来群里催。我们查监控才发现,利用率高是因为长请求一直占着批,短请求被拖在队尾动不了。显存也迟迟不释放,新请求进不来,体验比数字难看得多,客服的满意度调查分数一路往下掉,倒逼我们非改不可。那阵子我们和客服团队每周对一次抱怨清单,排在前三的永远是"转圈圈""等半天""摘要出不来",全是调度的问题,不是模型的问题,这让我们意识到得从批处理内部动手。

二、落地场景

我们在推理框架的调度层加了批内优先级。请求进来先按 SLA 分级,关键业务标为高优,普通闲聊标为低优,分级标签由网关在入口处打,不用业务改代码。短请求在批内优先排,长请求允许跑到一定步数后被新来的高优请求抢占回收。KV 缓存做分页管理,被抢占的请求状态先落盘,等空闲再续跑,用户侧只觉得慢了一点没报错。关键业务保底带宽,保证不被长任务完全淹没,工单摘要这种也不再排到队尾。整套调度对上层调用方透明,调用方只感知延迟变低了,接入方式完全没变,迁移成本几乎为零,业务侧无感升级,上线那天客服群里没人抱怨,反而有人问是不是换了更快的模型。业务方后来把高优标签接到了自己的工单系统,关键客户进线自动带高优,不用人工在网关侧配,分级跟着业务走而不是写死。长文档摘要这种重请求我们单独划了一个批处理池,避免和短问答挤在同一个批里互相拖累,两个池子各算各的账,资源隔离后谁也不抢谁。运营看板新增了被抢占回收次数这个指标,能直观看到长任务让了多少步,调度策略调起来心里有数,不再是凭感觉加并发。

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

批内优先级建模是第一个难点。分级不能只看业务标签,还要结合请求预估长度,否则短的高优和长的低优混排还是会互相拖累,分级就失去意义。长请求抢占回收最棘手:抢得太狠,长任务反复重算浪费算力;抢得太松,短请求还是等。我们的折中是给每个长请求设一个可抢占步数,到了才允许让位,配合 KV 分页让状态可恢复,被抢的能从落盘点续上。KV 分页和显存管理要一起改,不然抢占后显存算不清,反而容易 OOM。关键业务保底则是预留一小部分并发,宁可整体吞吐略降也要兜住,这部分我们和业务的 SLA 对齐过。这里走过弯路,第一版硬抢占把长任务直接掐断重算,算力浪费得厉害,延迟也没真正降,改成借而不抢、跑完即还才平滑,长任务跑完释放比中途掐断划算,抢的是时间窗口不是整段算力。

案例片段(已脱敏): 批内优先级与抢占回收配置(示意): schedule.batch_priority: true priority.by_sla_and_length: true long_preempt_step: 512 kv_cache.paged: true critical.reserved_concurrency: 2 某业务线首字延迟 P99 从 2.1 秒降到 0.3 秒,短请求排队下降约七成,GPU 利用率仍保持在 0.8 左右。

四、效果数据

我们盯的是 P99 首字延迟、短请求排队时长、抢占回收率和 GPU 利用率。P99 首字延迟从两秒多压到三百毫秒内,客服那边的体感最明显;短请求排队时长下降约七成,闲聊类不再被长任务拖死;抢占回收率维持在可接受水平,没有因为抢占导致大量重算,借而不抢的策略见效;GPU 利用率仍保持在八成上下,没有为降延迟牺牲太多吞吐,老板看成本也舒服。我们还把抢占回收的数据拿去做容量规划,长任务占比高的时段提前预留显存,避免临时 OOM,资源利用从拍脑袋加卡变成了看数加卡。业务方看到首字延迟下来之后,把更多高优场景切到了这套底座,调用量涨了三成但延迟没反弹,说明调度红利还在,没到天花板。

文中数据为项目复盘口径,实际值随流量结构波动,仅作趋势参考。最开心的是客服侧的投诉明显少了,他们原话是"终于不像卡住了",满意度调查分数也回去了,不用再写检讨,我们和客服的关系也缓和不少。

五、可复用经验总结

连续批处理最容易被利用率骗,高利用率背后可能是长请求卡住短请求,看指标一定要拆开看,别被一个平均数糊弄。批内加优先级、给长任务设可抢占步数,比无脑堆卡有用,卡加下去利用率看着更高但体验照样差。KV 缓存分页是抢占能平滑的前提,没它抢占就是反复重算,纯烧钱还不见效。我们现在的看法是,关键业务一定要留保底并发,宁可吞吐略降也别让它被淹没,SLA 才是给业务的交代。做推理调度的人,先想清楚"谁该被优先",再去调参数,别一上来就加机器,方向错了加机器也白搭,钱花在刀刃上才是真省钱。