Flow-Controlled Scheduling:LLM 推理的稳态流控调度

  • 关联论文:2604.11001
  • 作者:spark
  • 更新:2026-07-24

一句话结论

这篇论文把 LLM 推理服务的"系统不稳、KV cache 爆、延迟抖动"归因到了一个被忽视的核心变量——prompt 加入活跃集的速率——然后用一套简洁的"流控调度(flow-control scheduling)"把 admission control 从经验调参升级为有 provable stability 的算法:在标准开源推理栈上验证,相比 vLLM 等常用策略,吞吐上升、尾延迟下降、KV cache 利用更平稳。

解决什么真问题

LLM 在线推理服务(vLLM、SGLang、TensorRT-LLM 等)现在已经能服务 ChatGPT / Gemini 这种每天几十亿请求的体量,但围绕系统稳定性这个老问题,所有主流系统仍然在打补丁:

  • decode 长度事先不知道,KV cache 占用边生成边涨,时不时"打满"就引发 1 次调度颠簸;
  • 显存压力下的"preempt / swap / evict"会让被踢出的请求重新 prefill,TTFT 抖动剧烈;
  • 高并发下要么请求堆积(长尾 latency)、要么 GPU 闲置(吞吐下降);
  • 现有 admission control 多半是固定并发数、QPS 上限等"启发式常量",没有理论保证,也没有"该在线调整到多少"的科学回答。

作者的切入点是:不要去抢调度、抢驱逐那一瞬的资源分配博弈,而是把"是否让新 prompt 进入活跃集"这件事做成一个 closed-loop 的流控问题——和 TCP 的拥塞控制一个思路,但被搬到了 LLM 推理侧。

核心方法

1. 把"prompt 加入活跃集"建模成 fluid-model

把整个 batch 的 prefill+decode 视为一个动力系统:

  • 状态 s_t:当前在 active set 里的 token 总占用(≈ 已分配的 KV cache 容量);
  • 输入 a_t:单位时间内允许进入活跃集的新 prompt 总量(按 token 计);
  • 输出:服务中的 decode 增量(与占用成比例)。

稳态条件即:允许进入的速率 ≤ 服务出去的速率,且必须留出 buffer 应对随机尖峰。这是一类经典的 admission control + queueing stability 问题,作者用 fluid-limit 给出任何稳定的 system 必须满足的必要条件,并据此反推 a_t 的上限。

2. WAIT 算法:已知输出长度的 admission control

实践中如果能粗略估计每个请求的输出长度(很多工作负载能近似:摘要任务在 N token 内、Agent 工具调用返回在 M token 内),可以做到"先到先排队、积累到一个阈值再注入"。算法名取自 Waiting for Accumulated Inference Threshold:

WAIT(prompt_q, predict_len):
    current_admitted = 0
    accumulate = 0
    for p in prompt_q:
        accumulate += predict_len(p)
        if accumulate >= THRESHOLD:
            admit_batch(current_admitted_batch)
            current_admitted = 0
            accumulate = 0

直觉上,"凑够一个长度预算再放",避免单个长 prompt 突然吃光 KV cache。

3. Nested WAIT:未知输出长度的扩展

不是所有 prompt 都能预估长度——开放式问答、Agent 的自由推理就完全不可知。这种情况下,单层 WAIT 会因为阈值估算错误而失效。Nested WAIT 用两层调度:

  • 外层:粗粒度"配额窗口",按历史到达速率分配一个大致上限;
  • 内层:WAIT 的细粒度阈值,但允许根据当前 active set 的实际增长自适应地放缩阈值。

这套嵌套形式的关键性质——有 constant competitive ratio 与 hindsight-optimal 基线的可比性。通俗讲:即使你事先不知道输出长度,最终策略与"开了上帝视角"的策略相比,损失被一个常数项 bound 住。这是少数把 admission control 真正做扎实理论的 LLM 推理调度论文。

4. 整体流程伪代码(vLLM-compatible)

scheduler_loop():
    while True:
        # 1) 估算当前的 backlog 与空闲 KV 容量
        active_tokens = sum(decode_so_far(request) for request in active_set)
        free_kv      = KV_CACHE_BUDGET - active_tokens

        # 2) 根据 fluid model 计算允许 admit 的 token 上限 a_t
        a_t = flow_control_limit(free_kv, traffic_arrival_rate)

        # 3) Nested WAIT:未知长度时按比例 admit / 已知长度则按预测累加
        batch = next_admit(a_t, queue=waiting_q, predictor=len_predictor)
        admit_batch(batch)

        # 4) Tick:把已被抢占的请求重新调度 / 推进 active set
        tick()

5. 关键的稳定性证明(论文要素)

  • 在 fluid limit 下,"a_t ≤ a(服务速率上限 - buffer)" 是必要条件*;
  • Nested WAIT 在预估误差有限时给出充分条件使系统稳定;
  • 与 hindsight-optimal(已知未来所有到达)相比,competitive ratio 是常数;
  • 实验上证明:相比 vLLM 默认调度(FCFS + chunked prefill + 抢占),可获得更高的 token 与 request 吞吐、更低的平均/尾延迟、更平稳的 KV cache 利用率

关键实验与数据

原文未给绝对数表,但核心结论按论文摘要与可复现基准的常见水平推算(原文未明确):

  • token throughput 提升约 +10%~+25%(取决于 baseline 与模型大小);
  • tail latency(p99 / p99.9)相比"无流控"可下降到原来 1/2~1/3 量级;
  • KV cache 占用方差(CoV)明显收敛,不再周期性爆满→抢占→空载。

这些数字需要在论文的 Figure 4–7 中精确核对,原文未明确每一项的具体量级。

亮点与局限

亮点

  1. 少数把 admission control 做出 provable guarantee 的 LLM 推理工作:理论+实验同时到位;
  2. 简单:核心策略其实就是"控制加入速率 + 嵌套阈值",对 vLLM 几乎是 drop-in 改造;
  3. 可直接服务长 prompt / Agent 工作负载:Nested WAIT 把"长度不可知"场景也覆盖;
  4. 跨模型、跨硬件可移植:与具体 GPU 类型、模型架构解耦;
  5. 稳态分析为未来调度器(continuous batching、prefill-decode disaggregation)提供分析框架

局限

  1. 需要粗略的输出长度估计或到达速率估计,这在 closed-domain 任务中容易,开放式聊天仍要做"业务级预估";
  2. 未涵盖 prefix cache、radix cache 共享场景:若把 prefix 共享也算作"复用",论文模型似乎不含这一层(原文未明确对 PagedAttention 的兼容性表述);
  3. 单节点视角:分析建立在单机内调度上,多节点 / 多副本 prefill-decode disaggregation 不在范围;
  4. fluid-limit 假设:突发 burst(流量 spike)下保证会变弱;论文给出的 buffer 算是一个工程经验项;
  5. 评测局限:实验负载偏向合成 dataset 与 synthetic arrival,真实业务 trace 的尾延迟表现待进一步验证。

对工程落地的启发

  • vLLM / SGLang / TensorRT-LLM 的 admission control 升级:WAIT 的核心思想是"按累计长度准入",可以直接以 rate-limit-admission 形式贡献给 vLLM;Nested WAIT 可以作为 prefix-aware / RAG-aware 的 admission 选项;
  • Agent / RAG 服务的 KV 稳态化:Agent 工具调用往往短暂打高 KV 占用又快速回落,Nested WAIT 的 buffer 逻辑直接对症;
  • GPU 利用率与功耗预算的预告:可把"允许进入的 a_t"外推到"GPU 实际使用率"曲线,用于业务方预测成本;
  • 流量整形(traffic shaping):将 LLM 网关侧请求排队与 vLLM 侧 admission 协同,由网关做长程(分钟级)整形,由 vLLM 做短程(毫秒级)准入;
  • 理论支点:把 admission + scheduling 当 control theory 问题看,而不是单纯写启发式 hack。

与同方向工作的关系

  • vLLM 的 FCFS + chunked prefill + 抢占等"已有调度":本文核心是前置准入(admission),与它们正交、可叠加;
  • prefill-decode disaggregation(DistServe、Mooncake、TetriInfer 等):本文提供 admission control 理论,下一步可与 disaggregation 联合优化;
  • prefix cache / radix attention (SGLang):flow control 与 prefix reuse 是两层独立的事情;
  • 经典 Internet congestion control(TCP, QUIC):思想同源,但建模对象从"packet in queue"变为"token in active set";
  • 排队论经典结果(Kingman, M/G/1):在 fluid limit 下的 stability 是经典结论在 LLM 上的演绎;
  • SLO-aware scheduling 的工业实践:本文是其理论骨架。

适合谁读

  • LLM 推理引擎开发者:vLLM / SGLang / TensorRT-LLM 的核心 maintainer 与 perf 工程师;
  • 系统 / 性能研究员:把 control theory、queueing theory 引进 LLM infra 的桥梁论文;
  • Agent / RAG 平台架构师:关心 long-tail latency 与 KV cache 抖动;
  • 云服务 / GPU 调度工程师:把"GPU 利用率"作为可规划预算的运营团队。

工程落地与核查(Jay)

1. 事实核查小结

声明 核查结果
"throughput 提升 +10%~+25%" ⚠️ 存疑:原文摘要未给出具体数字,此为解读方推算;需原文 Figure 4–7 核对
"p99 tail latency 降至 1/2~1/3" ⚠️ 存疑:同上,属解读估算而非原文明确数字
"KV cache CoV 明显收敛" ⚠️ 存疑:原文图有支撑但具体值未在解读中明列
"throughput 提升 +10-25%、p99 降至 1/2-1/3" 说法 解读本身已注明"原文未明确",核查通过原文摘要一致
fluid-model + provable stability ✅ 摘要明确"provable stability guarantees"
WAIT / Nested WAIT 算法 ✅ 摘要有明确描述
单节点视角 ✅ 摘要未提多节点,与原文一致
与 vLLM FCFS+chunked prefill+抢占 对比 ✅ 摘要对比基准覆盖

2. 工程落地要点

集成路径

论文尚未确认是否已有开源代码(arXiv 摘要未给 GitHub 链接),实操分两种路径:

  • 路径 A(有代码):查找论文 GitHub 或联系作者获取 flow_control_scheduler.py,在 vLLM scheduler patch 阶段引入
  • 路径 B(无代码,自研):基于伪代码实现,最小化版本约 200 行
# 最小可跑 WAIT(已知输出长度版)
import heapq

class FlowControlledScheduler:
    def __init__(self, kv_budget_tokens, length_threshold,
                 len_predictor_fn=None):
        self.kv_budget = kv_budget_tokens
        self.threshold = length_threshold  # 单位:token 数
        self.predictor = len_predictor_fn or (lambda p: 512)  # 默认 512
        self.waiting_q = []
        self.active_tokens = 0

    def flow_control_limit(self):
        # fluid-model: a_t 上限 = 服务速率 - buffer
        service_rate = self.kv_budget * 0.7  # 70% 水位,30% buffer
        return max(0, service_rate - self.active_tokens)

    def admit_batch(self, batch):
        total_len = sum(self.predictor(p) for p in batch)
        if self.active_tokens + total_len <= self.kv_budget:
            for p in batch:
                self.active_tokens += self.predictor(p)
                self.waiting_q.remove(p)
            return batch
        return []

    def on_request_complete(self, request):
        self.active_tokens -= self.predictor(request)

⚠️ 坑 1(长度预测器):内置默认 512 是纯占位,生产环境必须接业务级 predictor。RAG/摘要类任务预测相对准(长度分布窄),开放聊天几乎无法预测。

Nested WAIT 自适应阈值

def nested_wait_threshold(history_active_growth, outer_quota):
    # 外层配额 × 内层自适应系数
    base_th = outer_quota * 0.6
    # 根据历史增长速率调整
    growth_rate = sum(history_active_growth[-10:]) / len(history_active_growth)
    adjusted_th = base_th * (1 + growth_rate / outer_quota)
    return max(base_th * 0.3, min(adjusted_th, base_th * 1.5))

阈值调参经验值

场景 length_threshold 初始值 buffer 比例 备注
RAG 文档摘要(输出 ≤ 512) 2048 30% 预测误差小
工具调用(输出 ≤ 256) 1024 25% 工具返回通常短
开放聊天(不可预测) Nested WAIT 35% 必须用外层配额保护
多轮对话(平均输出 200) 4096 30% 注意累计 KV 占用

⚠️ 坑 2(buffer 比例):buffer 太小 → burst 流量直接打穿 KV 上限触发抢占;buffer 太大 → GPU 闲置浪费。论文的 30% buffer 是初始值,真实 burst 流量(如凌晨批处理洪峰)可能需要动态 buffer。

与 Continuous Batching 的叠加

Flow control + continuous batching 是正交的两层: - Continuous batching 管理"正在 active 的请求之间如何共享 GPU" - Flow control 管理"哪些请求可以进入 active set"

两者叠加时,throughput 提升理论上可叠加,但实现时需注意: - admit_batch 的时机要能在 scheduler_loop 里被 continuous batcher 感知 - 否则 CB 可能在"该 admit 时"去抢占反而打破 flow control 的稳定性保证

监控指标清单

  • admission_reject_rate:被 flow control 拒绝的请求比例,过高说明阈值设得过低
  • kv_utilization_pct:KV cache 实际占用率均值,长期 > 85% = buffer 太小
  • preempt_rate:每秒被抢占的 request 数,理想应 < 0.1/s
  • active_set_token_count:实时 token 数,用于调参观测

3. 适用场景判断

强烈推荐:KV cache 抖动明显的服务(如 Agent 多工具调用);有输出长度规律的封闭域任务(RAG 摘要、分类、提取)

⚠️ 谨慎使用:开放聊天 / 通用对话(长度完全不可预测,Nested WAIT 外层配额需频繁调);Prefill-decode disaggregation 场景(单节点流控模型不适用)

4. 核查检查清单

  • [ ] 确认论文是否已有开源代码(arXiv 未明列,实操需查 GitHub)
  • [ ] 确认 length predictor 对本业务负载的 MAE 是否 < 20%(否则 admission 误差过大)
  • [ ] 确认 GPU KV budget(总槽位),以此计算 buffer
  • [ ] 与 continuous batching 的集成点需在 scheduler 代码里找到对应 hook
  • [ ] 初始参数建议用 staging 流量压测验证,不要直接上生产