【随笔】连续批处理:长短请求怎样在迭代之间补位

昨天的推测解码减少同一请求逐Token调用目标模型的轮次。今天换一个视角:服务里同时有许多请求,有的只生成一句话,有的还在写长文。连续批处理在每轮迭代边界更新请求集合,让已完成请求退出,让等待请求补入可用位置。

本文先用四个请求看清调度差异,再用完整程序验证,最后连接vLLM中的Token预算、KV缓存和延迟指标。它改变请求怎样共享计算资源,不修改回答应该遵循的业务规则。

一、为什么固定批次会留下空位

设服务一次最多推进两个请求,A需要2轮、B需要5轮;C需要2轮、D需要1轮,四者已经在等待。这里暂把每个请求每轮生成一个Token看作一个调度单位。

固定批次先处理A和B。A在第2轮完成,B还要继续3轮;如果此时不补入C,这个批次的有效工作量就下降。等B完成后,才开始C和D,总共需要7轮。

连续批处理在A完成后,下一轮就让C加入。B和C共同推进;C完成后D补位,最终第5轮四者全部结束。关键是批次成员能在迭代之间变化,而不是必须等整批结束。 Orca提出的迭代级调度就是这一方向的重要工作。Orca论文

图:A完成退出,B继续推进,等待中的C在下一轮补入。

二、调度器到底维护什么

结构上可以分成等待队列、运行中的请求状态、执行引擎和输出通道。调度器检查资源后选择本轮参与者,执行引擎推进计算,再把结果交回调度器;完成或取消的请求释放资源,其余请求保存状态进入下一轮。

请求状态包含已经生成的Token、采样参数、停止条件,以及模型计算需要的KV缓存等。批次成员改变,不表示把不同用户的上下文拼成一个语义上下文。实现必须保持各请求的位置、注意力边界和缓存映射正确。

传统动态批处理通常在请求进入时短暂等待,凑成一批;连续批处理进一步在生成过程的迭代边界重新组织参与者。两者可以配合,不能仅凭接口一次接收多个请求就判断引擎具备迭代级调度。

三、把补位过程画成时间线

下面每列代表一轮,彩色格表示该请求在本轮得到推进。A、B先进入;A结束后C接替,C结束后D接替。空白格只表示这个教学模型中的空位,不代表真实GPU完全空闲。

图:同样的四个请求,固定批次需要7轮,连续补位在5轮内完成。

静态方案有效工作共10个请求轮次,占用7×2个槽位,利用比例为10/14;连续方案用5×2个槽位,比例为10/10。这两个比例描述下面的离散模型,不能直接当作GPU利用率或线上加速倍数。真实每轮耗时还取决于上下文长度、批量大小、模型和硬件。

四、可运行的调度模拟

程序把剩余轮数存入active,refill决定每轮是否允许从队列补位。完成时间取该轮结束时刻,所有请求在第0轮前到达,容量为2;这样只改变调度策略,方便比较。

python 复制代码
from collections import deque
jobs = [("A", 2), ("B", 5), ("C", 2), ("D", 1)]
def simulate(refill):
    queue = deque(jobs)
    active = []
    done = {}
    trace = []
    tick = 0
    while queue or active:
        if refill or not active:
            while queue and len(active) < 2:
                name, n = queue.popleft()
                active.append([name, n])
        trace.append("+".join(x[0] for x in active))
        tick += 1
        for item in active:
            item[1] -= 1
            if item[1] == 0:
                done[item[0]] = tick
        active = [x for x in active if x[1] > 0]
    return tick, done, trace
for mode in (False, True):
    ticks, done, trace = simulate(mode)
    print("continuous" if mode else "static", ticks, done)
    print(" | ".join(trace))
assert simulate(False)[0] == 7
assert simulate(True)[0] == 5
assert simulate(True)[1] == {"A": 2, "C": 4, "B": 5, "D": 5}
print("PASS")

实际运行输出:

text 复制代码
static 7 {'A': 2, 'B': 5, 'D': 6, 'C': 7}
A+B | A+B | B | B | B | C+D | C
continuous 5 {'A': 2, 'C': 4, 'B': 5, 'D': 5}
A+B | A+B | B+C | B+C | B+D
PASS

这个例子中,B并未更早完成,改善主要来自C和D提前进入。平均完成轮数从5降到4,整体结束从第7轮提前到第5轮。它说明调度收益可以来自减少排队与空位,而不需要让单个请求每一步计算更快。

还可以把容量改为1:两种策略都只能串行推进,补位优势消失。若所有请求长度完全相同,固定批次也不会因提前完成产生同样的空位。这些对照帮助区分机制与特定输入带来的结果。

五、真实引擎还要处理Prefill和显存

Prefill根据输入计算初始状态,Decode根据已有状态逐步生成。长输入的Prefill可能占用较长计算时间,若直接与正在流式输出的请求竞争,就会影响后者相邻Token间的等待。

vLLM V1支持分块Prefill,把长输入拆成较小的调度工作,与Decode请求组合。启用分块Prefill时,调度器先安排Decode,再利用剩余Token预算安排Prefill;无法放进剩余预算的Prefill会被分块。vLLM优化说明

序列数量、Token预算和KV容量是不同的约束。 max_num_seqs限制一轮能容纳的序列数量;max_num_batched_tokens限制本轮安排的Token工作量。即使前两项有余量,KV缓存空间不足也可能阻止接纳新请求,并引起抢占或重新计算。

因此"有空位就补入"是机制解释,工程实现还需满足资源预算、请求依赖与调度策略。将最大并发直接调高,可能换来更长排队、更多显存压力,甚至降低满足延迟目标的有效吞吐。

六、怎样做一次有意义的验收

先固定模型、量化方式、硬件、输入输出长度分布与采样参数,保存引擎版本和完整启动配置。使用相同请求集和到达方式比较配置,每次只改变一类预算,避免同时扩大并发和改变请求长度。

指标 回答的问题
TTFT 从请求到达至首个输出Token等了多久
ITL 流式输出相邻Token之间间隔多久
端到端延迟 一个请求从到达到完成需要多久
输出Token吞吐 单位时间完成多少生成工作
尾延迟 最慢的一部分请求是否仍满足目标

分别测试短问答、长输入短输出、短输入长输出和混合负载。逐步增加到达率,观察等待队列、抢占和延迟何时快速上升;压测客户端也要有足够能力发送请求并记录流式时间,不能把客户端串行等待当成服务器吞吐瓶颈。

生产约束还包括取消请求后及时释放缓存、限制异常长输出、为不同业务设置公平性和优先级,以及拒绝超过容量的请求。连续批处理提高共享资源的组织效率,并不自动解决过载控制。

前缀缓存复用相同输入的计算结果,推测解码减少单请求生成轮次,连续批处理安排不同请求共同推进。三者可以组合,但应分别建立基线,再评估组合效果。

七、🧠 思维导图

#mermaid-svg-Q1KwR214Bm7ZPPty{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Q1KwR214Bm7ZPPty .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Q1KwR214Bm7ZPPty .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Q1KwR214Bm7ZPPty .error-icon{fill:#552222;}#mermaid-svg-Q1KwR214Bm7ZPPty .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Q1KwR214Bm7ZPPty .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Q1KwR214Bm7ZPPty .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Q1KwR214Bm7ZPPty .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Q1KwR214Bm7ZPPty .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Q1KwR214Bm7ZPPty .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Q1KwR214Bm7ZPPty .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Q1KwR214Bm7ZPPty .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Q1KwR214Bm7ZPPty .marker.cross{stroke:#333333;}#mermaid-svg-Q1KwR214Bm7ZPPty svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Q1KwR214Bm7ZPPty p{margin:0;}#mermaid-svg-Q1KwR214Bm7ZPPty .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Q1KwR214Bm7ZPPty .cluster-label text{fill:#333;}#mermaid-svg-Q1KwR214Bm7ZPPty .cluster-label span{color:#333;}#mermaid-svg-Q1KwR214Bm7ZPPty .cluster-label span p{background-color:transparent;}#mermaid-svg-Q1KwR214Bm7ZPPty .label text,#mermaid-svg-Q1KwR214Bm7ZPPty span{fill:#333;color:#333;}#mermaid-svg-Q1KwR214Bm7ZPPty .node rect,#mermaid-svg-Q1KwR214Bm7ZPPty .node circle,#mermaid-svg-Q1KwR214Bm7ZPPty .node ellipse,#mermaid-svg-Q1KwR214Bm7ZPPty .node polygon,#mermaid-svg-Q1KwR214Bm7ZPPty .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Q1KwR214Bm7ZPPty .rough-node .label text,#mermaid-svg-Q1KwR214Bm7ZPPty .node .label text,#mermaid-svg-Q1KwR214Bm7ZPPty .image-shape .label,#mermaid-svg-Q1KwR214Bm7ZPPty .icon-shape .label{text-anchor:middle;}#mermaid-svg-Q1KwR214Bm7ZPPty .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Q1KwR214Bm7ZPPty .rough-node .label,#mermaid-svg-Q1KwR214Bm7ZPPty .node .label,#mermaid-svg-Q1KwR214Bm7ZPPty .image-shape .label,#mermaid-svg-Q1KwR214Bm7ZPPty .icon-shape .label{text-align:center;}#mermaid-svg-Q1KwR214Bm7ZPPty .node.clickable{cursor:pointer;}#mermaid-svg-Q1KwR214Bm7ZPPty .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Q1KwR214Bm7ZPPty .arrowheadPath{fill:#333333;}#mermaid-svg-Q1KwR214Bm7ZPPty .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Q1KwR214Bm7ZPPty .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Q1KwR214Bm7ZPPty .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Q1KwR214Bm7ZPPty .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Q1KwR214Bm7ZPPty .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Q1KwR214Bm7ZPPty .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Q1KwR214Bm7ZPPty .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Q1KwR214Bm7ZPPty .cluster text{fill:#333;}#mermaid-svg-Q1KwR214Bm7ZPPty .cluster span{color:#333;}#mermaid-svg-Q1KwR214Bm7ZPPty div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Q1KwR214Bm7ZPPty .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Q1KwR214Bm7ZPPty rect.text{fill:none;stroke-width:0;}#mermaid-svg-Q1KwR214Bm7ZPPty .icon-shape,#mermaid-svg-Q1KwR214Bm7ZPPty .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Q1KwR214Bm7ZPPty .icon-shape p,#mermaid-svg-Q1KwR214Bm7ZPPty .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Q1KwR214Bm7ZPPty .icon-shape .label rect,#mermaid-svg-Q1KwR214Bm7ZPPty .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Q1KwR214Bm7ZPPty .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Q1KwR214Bm7ZPPty .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Q1KwR214Bm7ZPPty :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 连续批处理
迭代边界调度
请求状态
资源预算
性能验收
完成退出与等待补位
前缀与KV缓存
序列数与Token预算
显存与抢占
TTFT与ITL
吞吐与尾延迟

八、总结

总结要点

连续批处理的核心是迭代边界上的成员更新。 完成者退出,等待者在资源允许时补位,长短请求不必始终绑成固定批次。

补位依赖完整的请求状态与资源管理。 Token预算、序列数和KV容量共同决定一轮能安排多少工作。

收益要用负载和延迟目标衡量。 同时检查吞吐、首Token等待、输出间隔和尾延迟,才能判断配置是否适合真实业务。

下一篇继续看分块Prefill:长提示词怎样拆分计算,减少对流式输出的干扰。

👉 如果你觉得这篇文章对你有所帮助,欢迎点赞、收藏、分享!😊

相关推荐
最强小杰35 分钟前
DeepSeek V4 Flash + Codex CLI 接入教程:原生 Responses API 适配,从配置到跑通收藏这篇就够了(2026)
java·前端·python·ai
牧羊人.33344 分钟前
卷积神经网络之VGG
人工智能·深度学习·神经网络·算法·cnn
Flynn Liang1 小时前
Spring AI 2.0 深度解析:ChatClient、Advisor 链与 MCP——Java 工程师的 AI 落地最优解
java·人工智能·spring·大模型·架构设计·spring ai
灿飞1 小时前
Kotlin与Java虚拟机六:基础与类型系统:一切皆对象,但对象有代价
java·kotlin
Martina_03211 小时前
AI生成3D模型一合并网格,材质就串色?用6步检查材质槽、UV与图集重映射
图像处理·人工智能·3d·材质·uv·游戏策划·1024程序员节
蓝鲨硬科技1 小时前
Token之后,物理AI需要什么样的度量衡?
大数据·人工智能
计算机毕设定制辅导-无忧学长1 小时前
《基于微信小程序健康习惯激励系统设计与实现》
java·spring boot·mysql·微信小程序·uniapp·健康习惯激励系统
人工智能AI技术1 小时前
Agent架构新思路:多Agent正在过时,单循环Main Loop才是新方向
人工智能
GlobalInfo1 小时前
管道检测机器人行业调查、市场规模、排名分析报告2026-2032
大数据·人工智能·机器人