大模型推理的交通指挥官:解码vLLM调度器的设计哲学

想象一下,你正站在一个繁忙的十字路口,眼前是川流不息的车辆。有的车刚从匝道驶入,需要快速并入主路;有的车已经在主路上行驶了一段时间,正平稳前进;还有的车因为前方拥堵,暂时停在了路边。你的任务是指挥这些车辆,确保整个交通系统高效、有序地运行,既要让新来的车尽快上路,又不能影响已经在路上的车辆顺利到达目的地。

这,就是vLLM调度器每天面对的场景。只不过,它指挥的不是车辆,而是大语言模型推理过程中的一个个请求。每个请求都像一辆车,有的刚进入系统(waiting状态),有的正在GPU上飞驰(running状态),有的因为资源不足被暂时“靠边停车”(swapped状态)。调度器的核心使命,就是在有限的GPU显存这条“高速公路”上,最大化通行效率,同时保证公平性。

对于技术决策者和架构师而言,理解vLLM调度器的设计思想,远比单纯阅读源码更重要。它揭示了大模型推理服务在应对高并发、动态负载时的核心挑战与解决方案。今天,我们就抛开繁琐的代码细节,从设计哲学的角度,深入探讨这个“交通指挥官”是如何思考的。

1. 调度器的核心挑战与设计目标

在大模型推理服务中,调度器面临的根本矛盾是:有限的GPU显存资源与近乎无限的并发请求之间的冲突。每个请求都需要占用KV Cache空间,而GPU的显存是固定的。如何在这块固定的“画布”上,安排不断涌入的“画作”,是调度器必须解决的难题。

vLLM调度器的设计目标可以概括为三个核心维度:

  1. 高吞吐量(Throughput):单位时间内完成尽可能多的请求。
  2. 低延迟(Latency):单个请求从提交到获得首个Token(TTFT)以及后续每个Token(TPOT)的响应时间尽可能短。
  3. 高资源利用率(Utilization):GPU的计算能力和显存空间被充分、高效地利用。

这三个目标往往相互制约。追求极致吞吐可能会批量处理大量请求,但会延长单个请求的等待时间;追求极低延迟可能会优先处理新请求,但会导致GPU利用率波动,整体吞吐下降。vLLM调度器的精妙之处,就在于它在这三者之间找到了一系列动态平衡点。

为了更直观地理解这些权衡,我们可以看下面这个对比表格:

调度策略倾向对吞吐量的影响对延迟(TTFT)的影响对资源利用率的影响典型应用场景
偏向新请求(Prefill优先)可能降低显著改善可能导致GPU计算资源空闲(等待Decode积累)交互式聊天应用,强调首字快
偏向进行中请求(Decode优先)可能提升可能恶化保持GPU持续解码,利用率高批量文本生成、摘要任务
严格FCFS(先到先服务)稳定但非最优对长请求不友好一般简单的队列处理
动态混合调度(vLLM策略)优化平衡高效通用高并发推理服务

vLLM采取的是最后一种——动态混合调度。它的核心思想不是静态地偏向某一方,而是根据系统实时状态(如swapped队列是否为空、waiting队列积累情况)动态决策。这就像一位经验丰富的交警,不仅看红绿灯,还会观察各方向车流密度,实时调整放行策略。

2. 状态机与队列模型:理解调度器的基本框架

在深入动态策略之前,我们必须先理解vLLM调度器管理请求的基本模型。每个用户请求在系统中被抽象为一个 SequenceGroup。你可以把它想象成一列火车,车头是用户的输入提示词(Prompt),后面的车厢则是模型可能生成的多种输出(例如,当用户要求生成多个候选结果时)。

这列火车在系统的“铁路网”中运行,会处于以下几种状态之一:

  • WAITING(等待进站):请求刚刚到达,还未进行首次计算(Prefill)。它停在waiting队列这个“编组站”里,等待调度器安排“机车”(GPU资源)进行牵引。
  • RUNNING(正在行驶):请求已经开始了生成过程,正在GPU上运行。它位于running队列,表示正在“主干线”上行驶。
  • SWAPPED(临时停靠):由于“主干线”拥堵(GPU显存不足),这列火车被整体调度到“备用线”(CPU内存)上临时停放,释放出当前占用的轨道(GPU显存)。它位于swapped队列。
  • FINISHED(抵达终点):请求已完成生成,离开调度系统。

调度器维护着三个核心队列,正好对应前三种运行状态:

# 概念性代码,展示调度器核心数据结构
class Scheduler:
    def __init__(self):
        self.waiting: Deque[SequenceGroup] = deque()  # 等待首次执行的请求
        self.running: Deque[SequenceGroup] = deque()  # 正在执行的请求
        self.swapped: Deque[SequenceGroup] = deque()  # 被换出到CPU的请求
        self.block_manager: BlockManager              # 物理块管理器,管理“轨道”资源

SequenceGroup 是关键抽象。它将一个用户请求(可能对应多个输出序列,如Beam Search或Parallel Sampling)捆绑在一起管理。这样做有两个巨大优势:

  1. 原子性调度:一个请求下的所有序列同进同退,简化了状态管理和资源分配的逻辑。
  2. 资源共享:同一个SequenceGroup内的序列共享Prompt部分的KV Cache,通过PagedAttention的Copy-on-Write机制避免重复存储,极大节省了显存。

提示:SequenceGroup的抽象是vLLM能高效处理Beam Search等复杂采样策略的基础。调度器以它为最小调度单位,使得策略逻辑清晰,且能与底层的块管理器(Block Manager)高效协作。

3. 调度策略的核心决策逻辑

调度器在每个推理步骤(Step)中都要做出一个关键决策:本次是处理新请求(Prefill),还是继续处理进行中的请求(Decode)? 这个决策直接影响了我们前面提到的吞吐、延迟和利用率三角。

vLLM的决策流程图虽然复杂,但其核心逻辑可以用一个优先级规则来概括:

SWAPPED > (RUNNING 且 无新抢占) > WAITING (需满足延迟阈值)

让我们拆解这个规则:

3.1 为何SWAPPED队列拥有最高优先级?

这体现了调度器的“公平性”与“效率”权衡。被换出(Swap-out)的请求是之前的“受害者”——它们原本在运行,却因资源不足被中断。如果长时间不恢复它们的执行,其延迟会变得不可接受。

# 概念性逻辑,解释SWAPPED优先的原因
if self.swapped:
    # 优先尝试将SWAPPED队列中的请求换回GPU
    for seq_group in list(self.swapped):
        if self._can_swap_in(seq_group):
            self._swap_in(seq_group)
            self.running.append(seq_group)
            self.swapped.remove(seq_group)
    # 只有在处理完SWAPPED队列后,才考虑其他队列

更重要的是,从系统整体效率角度看,恢复一个已部分执行的请求(只需从CPU内存读回KV Cache)的成本,通常低于重新开始一个全新请求(需要重新计算整个Prompt的Prefill)。因此,优先处理swapped队列是一种对整体吞吐量有利的决策。

3.2 RUNNING队列的持续服务与抢占机制

对于running队列中的请求,调度器的默认策略是尽力让它们继续执行(Decode阶段)。因为每次Decode只生成一个Token,所需的计算和显存增量相对较小,容易满足。

但这里有一个关键机制:抢占(Preemption)。当调度器试图将一个新请求从waiting队列加入running队列,却发现GPU显存不足时,它不会直接拒绝新请求,而是可能从running队列中踢出(抢占)一个或多个现有请求,为新人腾出空间。

vLLM的抢占策略非常智能,它根据SequenceGroup的特征决定是“交换”(Swap)还是“重计算”(Recompute):

  • Swap(换出):如果被抢占的SequenceGroup包含多个序列(例如,Beam Search宽度>1),则将其整个KV Cache从GPU交换到CPU内存。代价是I/O开销,但保留了计算成果。
  • Recompute(重计算):如果被抢占的SequenceGroup只有一个序列,则直接释放其GPU显存,并将其状态重置为WAITING,放回waiting队列前端。下次调度时,它将从头开始Prefill。因为单个序列重计算成本较低,避免了I/O开销。
# 抢占策略的核心逻辑
def _preempt(self, seq_group: SequenceGroup):
    if seq_group.get_max_num_running_seqs() == 1:
        # 单序列,采用重计算
        self._preempt_by_recompute(seq_group)  # 释放GPU块,状态回退至WAITING
    else:
        # 多序列,采用交换
        self._preempt_by_swap(seq_group)  # GPU块换出至CPU,状态改为SWAPPED

这种差异化的处理方式,体现了设计者对不同负载场景下成本收益的精细考量。

3.3 WAITING队列的调度与延迟阈值

waiting队列的调度是整个系统响应速度(TTFT)的关键。如果一味优先处理running和swapped队列,新请求可能会饿死。但如果过于频繁地处理新请求,又会打断正在进行的Decode流,损害吞吐量。

vLLM引入了一个巧妙的 “延迟调度”(Delayed Scheduling) 机制来控制这个频率。其核心是一个自适应阈值:

是否调度WAITING队列 = (当前时间 - WAITING队列中最老请求到达时间) > (延迟因子 * 上一次Prefill调度间隔)

或者,更直白地说:只有当最早到达的等待请求已经等了足够久,或者当前根本没有正在运行的请求时,才调度新的Prefill。

这个机制的精妙之处在于:

  • 自适应:阈值基于上一次Prefill的调度间隔动态调整。如果系统繁忙,Prefill间隔自然长,阈值变大,系统更倾向于继续Decode以保吞吐。
  • 防饥饿:(当前时间 - 最老请求到达时间) 保证了即使系统持续繁忙,等待过久的请求最终也能被处理。
  • 保吞吐:当有请求正在运行时(self.running非空),会提高新请求的调度门槛,避免频繁的Prefill打断连续的Decode,后者对维持高吞吐至关重要。

4. 资源评估与分配:调度器的“交通管制”手段

调度器做出决策前,必须精确评估“道路容量”——即GPU的KV Cache剩余空间。这涉及两个核心判断:

4.1 对新请求(Prefill)的容量评估:can_allocate

当一个SequenceGroup从waiting队列被选中,准备进行Prefill时,调度器需要评估其Prompt长度所需的KV Cache块数。

# 简化的评估逻辑
def can_allocate_for_prefill(seq_group):
    prompt_seq = seq_group.get_waiting_seq()
    num_blocks_needed = len(prompt_seq.logical_token_blocks)  # 根据Prompt长度计算所需块数
    
    num_free_blocks = self.gpu_allocator.get_num_free_blocks()
    
    # 关键:预留水位线(watermark)空间,防止频繁缓存驱逐
    if num_free_blocks - num_blocks_needed >= self.watermark_blocks:
        return AllocStatus.OK  # 可以分配
    elif self.total_blocks - num_blocks_needed < self.watermark_blocks:
        return AllocStatus.NEVER  # 请求太长,永远无法分配(直接标记失败)
    else:
        return AllocStatus.LATER  # 空间不足,但可等待后续释放

这里引入了 “水位线”(Watermark) 概念。它就像高速公路的应急车道,预留一部分空间不分配,用于应对突发情况(如某个序列突然需要更多块),避免频繁的“交通事故”(缓存驱逐/抢占)。这是一种以少量空间换取系统稳定性的经典设计。

4.2 对进行中请求(Decode)的容量评估:can_append_slot

对于running队列中的请求,每次Decode只需为每个活跃序列分配一个Token的位置。评估逻辑相对简单,但需要考虑最坏情况:

# 简化的评估逻辑
def can_append_slot_for_decode(seq_group):
    # 获取该seq_group中仍在运行的序列数量
    num_running_seqs = seq_group.num_unfinished_seqs()
    
    # 最坏情况:每个序列的最后一个物理块都满了,需要新分配一个块
    # 因此,需要的最大块数等于运行中的序列数
    num_blocks_needed = num_running_seqs
    
    num_free_blocks = self.gpu_allocator.get_num_free_blocks()
    return num_free_blocks >= num_blocks_needed

这种保守估计确保了调度决策的可靠性,即使所有序列同时需要新块,系统也有足够资源应对。

5. 实战中的权衡艺术与性能调优

理解了调度器的设计哲学后,我们来看看在实际部署中,如何根据业务需求调整这个“交通指挥官”的行为。vLLM通过 SchedulerConfig 暴露了几个关键参数:

配置参数作用调优建议
max_num_batched_tokens单次调度中,所有Prefill请求的Token总数上限。增大:提升Prefill吞吐,但可能增加单个Decode请求的等待时间,影响TPOT。减小:让系统更频繁地切换回Decode,有利于流式响应体验。
max_num_seqs单次调度中,同时处理的序列总数上限。受限于GPU内存和模型大小。需要平衡并发度和每个序列获得的计算资源。
delay_factor控制WAITING队列调度延迟的因子。设为0:禁用延迟,新请求尽快得到Prefill,优化TTFT,但可能损害吞吐。增大:让Prefill批处理更“饱满”,提升吞吐,但新请求等待时间变长。
num_gpu_blocks, num_cpu_blocksGPU/CPU上KV Cache块的数量。需要根据模型大小、上下文长度和预期并发度仔细测算。CPU块用于Swap,更大的CPU块空间意味着能容纳更多被换出的请求,减少重计算。

一个真实的性能权衡案例:

假设我们运营一个AI写作助手,用户交互模式是“短Prompt + 长生成”。我们发现用户对**首句生成速度(TTFT)**非常敏感,但对整体生成完成时间容忍度较高。

  • 初始配置:delay_factor=2.0,max_num_batched_tokens=2048。系统倾向于积累一定量的Prefill请求再处理,吞吐量不错,但部分用户反馈“开始思考”的时间有点长。
  • 优化尝试:将 delay_factor 降至 0.5,max_num_batched_tokens 降至 512。系统变得更“急切”地处理新请求,TTFT的P99指标显著下降,用户体验提升。但监控发现,GPU利用率出现周期性波动,整体吞吐量下降了约15%。
  • 最终平衡:经过A/B测试,我们选择 delay_factor=1.0,并启用了优先级队列(如果vLLM版本支持或自定义修改)。为VIP用户或短Prompt请求赋予更高优先级,使其更快进入running队列。在保证大多数用户TTFT体验的同时,将对吞吐量的影响控制在5%以内。

这个案例说明,没有放之四海而皆准的最优配置。调度器的参数是与业务场景强相关的,需要在吞吐、延迟和资源利用率这个“不可能三角”中,找到符合你业务目标的最佳平衡点。

6. 从vLLM调度器看系统设计范式

vLLM调度器的设计,为我们构建高性能、高并发服务系统提供了宝贵启示:

  1. 状态机是复杂流程管理的利器:清晰的WAITING、RUNNING、SWAPPED状态划分,使得调度逻辑模块化,易于理解和维护。每个状态对应明确的行为和转换条件。
  2. 队列是缓冲与解耦的关键:三个队列不仅组织了请求,更重要的是缓冲了负载的波动,使调度决策可以基于队列状态(而非瞬时请求)做出,提高了系统的平滑性和可预测性。
  3. 资源抽象与统一管理是基石:将KV Cache抽象为可管理的“块”(Block),并通过BlockManager进行统一分配、回收、换入换出,是调度策略得以实施的基础。这种将资源管理与业务逻辑分离的设计,极大地提升了系统的灵活性和可扩展性。
  4. 策略应具备可调节的“弹性”:delay_factor、watermark_blocks等参数的存在,使得调度策略不再是硬编码的算法,而是一个可以根据实际负载和业务目标进行调优的“弹性系统”。好的架构应该提供调节旋钮。
  5. 权衡是系统设计的常态:从Swap与Recompute的选择,到Prefill与Decode的调度优先级,处处体现着对计算、I/O、内存、延迟等多方面成本的权衡。优秀的系统设计不是追求单一指标最优,而是在明确约束下做出合理的折中。

回到我们开头的交通比喻。vLLM的调度器就像一位不仅熟记交通规则,更能洞察全局、预判流量的智能交通指挥官。它知道何时该放行新车辆以快速疏散匝道拥堵,何时该保障主干道车流畅通以提升整体运力,何时又该将故障车辆拖离现场以防止连环拥堵。

对于技术决策者而言,理解这套调度哲学的价值在于,当你在设计自己的分布式系统、资源调度服务时,可以借鉴其状态管理、队列缓冲、资源抽象和动态权衡的思想。对于架构师而言,深入vLLM调度器的细节,则是一次关于如何在高并发、资源受限环境下进行精细控制的绝佳学习。

最后,我想分享一个在压力测试中观察到的有趣现象:当并发请求数急剧飙升时,一个配置合理的vLLM调度器并不会让系统立刻崩溃,而是像一个有弹性的网络,吞吐量缓慢下降,延迟平滑上升,期间依然能有序处理大部分请求。这种“优雅降级”的能力,正是其稳健设计哲学的体现。在实际部署中,我通常会结合监控指标(如各队列长度、GPU利用率、TTFT/TPOT分位数)来动态观察调度器的行为,你会发现,这些冰冷的代码背后,是一个充满智慧与平衡的艺术系统。

更多推荐