【模型推理篇】vLLM核心思想 - ② 动态批处理 continuous batching

上篇文章介绍了vLLM核心之一的 paged attention(点积查看),今天继续复习 continuous batching

因为我目前就是在做调度系统,而这种动态batching也算一种调度,并且本周也还被人问到了, 所以即便周末北平天气这么好,非常想去爬山,但也还是先坚持沉淀一下;

以下内容均来自 对vLLM 官方文档 和 源码(截至2025-11月最新tag)的个人理解,如果错误请指正;

在这里插入图片描述

batching 的重要性

首先回顾下,为什么说batching是提升推理性能的关键,以及传统方案和问题:

  1. 目标:最大化GPU利用率,跑满GPU,在单位时间内尽可能多的处理请求,提高吞吐
  2. 挑战:经典的 ①显存资源受限 和 ②计算资源受限

传统方案:把多个请求收集起来(一般可以限定在 max_delay_ms 时间内),拼凑成一个固定的batch,一次性的喂给LLM

缺陷:

  1. 长尾延迟:整个批次必须等待所有请求完成后才能释放,所以其中的长尾请求,会严重拖慢整体响应
  2. 资源浪费:一个batch内原始输入 prompt 长短不一,必须 padding 到该批次的最长序列的长度,或直接干脆全都padding到max_prompt_len,但这样大量资源就会被浪费到了无效的padding token上
  3. 灵活性差:因为大多提供的都是实时在线服务,不是离线任务,无法动态响应不断到达的新请求

token-level 动态 batching 调度

目前主流框架的解法是 token-level 的动态batching调度,像 vLLMcontinuous batchingTensorRT-LLMIn-Flight batching 都属于这类;

核心思想就是:打破传统 request 级别的调度,采用 token 级别的调度(更细粒度的调度策略)

request 级别的调度策略中,一个batch内的所有request,类比大家中午去食堂吃饭,一起坐下,但要等最后那个人吃完再撤(一般是我… 此时旁边已经站了一圈压力怪盯着你…)

在这里插入图片描述

上图中,batch内4个request,黄色是prefill阶段,蓝色是decode阶段,每次生成1个token,直到全部完成输出

token 级别的调度策略中,则大家以 token 作为调度粒度,还拿中午去食堂吃饭类比,一桌人每吃完一口,就看看他是不是吃完了,如果是的话,吃完不等同行的人直接撤,旁边等座的就可以坐下吃饭了(提高了食堂吞吐、又降低了食堂座位资源的浪费,座位满负荷运转)

在这里插入图片描述

上图中,当一个requst完成后,立即有新的request被压进来,执行prefill-then-decode(这图让我想起了PP流水线并行中,解bubble气泡的1F1B,所以很多东西底层都是类似、相通的,就那些原理,只不过很多东西没时间了解罢了)

想法很简单,细节很魔鬼

话虽如此,听起来很简单,但实现起来还是有很多细节的

首先第一个问题就是,不对啊,新加入的reqeust和已经正在处理的request所处的阶段stage都不同,GPU也不能在同一个时间步里执行啊;

就像食堂一张桌子(1个Warp)4个椅子(32个线程),上一个人吃完离席后,小A刚坐下,需要先掏出手机刷两下、发个微信、拍张照报备啥的(prefill - attn kv cache - 计算密集型),而其他三个人小BCD都在一口一口吃(decode - NTP中 - io密集型),没法在同一个时间步里同步啊,这不就出现线程束分化了么;

设计细节

这里可以参考 【这篇文章】中引用的 【ORCA的论文】

在这里插入图片描述

虽然整个NTP流程分成PD两个阶段,但除了attention外,其余层都是per-token处理的;

也就是说不管这个token是prefill的token还是decode的token,在像FFN这些层中,单独处理,还是放到一块儿处理的逻辑是一样的,所以可以merge到一起跑;

但是attn层不行,prefill和decode处理的逻辑不同,需要进行区分;

源码细节

都在 【scheduler.py】

在这里插入图片描述

首先,vLLM 底层设计了 3个queue 来实现这种调度

在这里插入图片描述

其实准确来说有其他queue,通过搜create_request_queue‎,还能看到一个 skip_waiting_queue(这玩意不重要先不管它,其实是我不想看了,狗头…)

在这里插入图片描述

所以:

  • 1个是waiting_queue:用来存储等待执行的request的
  • 1个是running_queue:用来存储当下正在执行的request
  • 1个是finished_req_ids:存储已经处理完的request,然后可以释放掉它的资源了

其中,waiting_queue 可以看到有个参数 policy,指的是等待策略,默认是 fcfs,也就是 first - come - first -serve,但也允许根据优先级 priority

配置代码在 SchedulerConfig 中:

在这里插入图片描述

在处理reqeust的时候,针对request本身,vLLM并不会显式的区分它是在prefill还是decode,而都当它们在追token;

在这里插入图片描述
但是在attention计算的时候,确实会进行拆分,batch中哪些是prefill的request,哪些是decode的request

在这里插入图片描述
同时,vLLM内部还使用了 paged attention,查看其源码也可以看到,区分了prefill和decode分别执行:

chunked_prefill_paged_decode.py 中,根据参数 query_len 来区分 prefill 还是 decode:

  • query_len == 1:则是 decode 阶段,只需要生成1个token
  • query_len > 1:则代表了 prefill 阶段,同时配置参数 skip_decode = true

在这里插入图片描述

所以:

  1. 先处理 prefill,执行 context_attention_fwd
  2. 然后再处理 decode,执行 kernel_paged_attention_2d

在这里插入图片描述


以上简单介绍了vLLM如何优化了batching策略,从而提高serving性能,下一篇继续盘一盘vLLM中还有哪些推理优化手段。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值