Prhub

#32389 [Bugfix] Fix prefill suspension caused by delayed negotiate_should_allow_prefill invocation

原始 PR 作者 silencejade 合并时间 2026-07-26 14:59 文件变更 1 提交数 1 评论 3 代码增减 +5 / -1

执行摘要

修复 delayed negotiate 导致 prefill 挂起

PR body 指出:当启用 prefill-delayer、chunked-prefill 和 dp-attention 时,如果请求没有被拆分,第一次调用 negotiate_should_allow_prefill 只在执行 add_one_req_ignore_eos 期间发生,导致 max-running-req 被设置为 0,后续无法调度新的 prefill batch,prefill 执行一直挂起直到下一个 decode 阶段结束。

值得精读。这是一个典型的“缺失上下文导致状态错误”的调度 bugfix,展示了在复杂调度组合下参数传递的重要性。关注点:negotiate_should_allow_prefill 的参数契约与调用方职责划分。建议后续为该场景补充单元测试,防止回归。

讨论亮点

该 PR 无实质性技术讨论。sglang-npu-bot 直接 APPROVED。在 issue 评论中,sglang-npu-bot 说明一个 GPU 测试因环境占用失败(base-c-test-4-gpu-h100),其余测试全部通过,由于改动仅涉及混合 PD 场景下的调度,不影响其他场景,因此合并。另外 gemini-code-assist 自动评论表示已停止审查。

实现拆解

  1. 定位问题:在 python/sglang/srt/managers/schedule_policy.py 的 add_one_req_ignore_eos 方法中,当 prefill-delayer 启用时,调用 negotiate_should_allow_prefill 仅传入了 local_prefillable=True,未传递任何调度上下文参数。
  2. 分析根因:negotiate_should_allow_prefill 内部需要根据当前系统状态(如 running_batch 大小、最大并发限制等)计算 max-runing-req。由于缺失这些参数,协商函数内部可能使用默认值或错误值,导致 max-running-req 被置为 0,从而拒绝所有后续 prefill 请求。
  3. 实施修复:在调用处补传 4 个关键参数——running_batch(当前 running batch 的请求数)、max_prefill_bs(最大 prefill batch size)、max_running_requests(最大 running 请求数)、waiting_queue_len(等待队列长度),使协商逻辑基于真实负载做出决策。
  4. 影响验证:修复后,prefill 不再被挂起,调度循环能正常推进。改动仅影响调度策略,不影响模型推理逻辑,且未新增测试,依赖现有 CI 覆盖。
文件 模块 状态 重要度
python/sglang/srt/managers/schedule_policy.py 调度器 modified 5.19

关键符号

add_one_req_ignore_eos

关键源码片段

python/sglang/srt/managers/schedule_policy.py core-logic

该文件是调度策略核心,本次 PR 唯一改动文件。修复了 add_one_req_ignore_eos 调用 negotiate_should_allow_prefill 时未传调度状态参数导致 prefill 挂起的问题。

# 以下代码位于 add_one_req_ignore_eos 方法内,
# 用于在将请求加入 can_run_list 之前,与 prefill-delayer 协商是否允许本次 prefill。
# 修复前只传了 local_prefillable 参数,导致 prefill-delayer 无法感知
# running_batch 等真实状态,将 max-running-req 误设为 0,
# 从而使得后续调度循环因无空位而挂起 prefill。
# 现在补传 4 个调度状态参数,确保协商基于真实系统负载。
if (self.prefill_delayer_single_pass is not None) and (
    not self.prefill_delayer_single_pass.negotiate_should_allow_prefill(
        local_prefillable=True,
        running_batch=self.running_batch.batch_size(), # 当前正在运行的 batch 大小
        max_prefill_bs=self.max_prefill_bs, # 允许的最大 prefill batch size
        max_running_requests=self.max_running_requests, # 允许的最大 running 请求数
        waiting_queue_len=self.waiting_queue_len, # 当前等待队列长度
    )
):
    # 协商失败,拒绝该请求的本次调度
    return AddReqResult.OTHER

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

改动位于调度核心路径(schedule_policy.py 的 add_one_req_ignore_eos),虽然只是补传参数,但 Negotiation 逻辑的行为会随之变化。风险点包括:

  • 当 prefill-delayer 与其他调度特性(如 chunked-prefill、dp-attention)组合时,参数语义必须正确,否则可能引发新的调度偏差;
  • 缺少针对该场景的专项单元测试,回归风险依赖于现有 CI 覆盖;
  • 如果 running_batch 或 waiting_queue_len 等属性在调用时为 None 或未初始化,可能引入异常,但代码中已有默认路径,实际风险较低。

影响范围集中在启用了 prefill-delayer + chunked-prefill + dp-attention 的用户,修复后 prefill 不再挂起,吞吐和服务可用性恢复。对其他场景(未启用 prefill-delayer)无影响,因为补传参数仅在 prefill-delayer 分支内生效。改动面小(1 文件、+5/-1),团队维护成本低。

核心路径变更 缺少测试覆盖

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论