Prhub

#27506 Add more testing for chunked prefill

原始 PR 作者 fzyzcjy 合并时间 2026-06-09 20:19 文件变更 42 提交数 627 评论 28 代码增减 +7928 / -21

执行摘要

为 chunked prefill 添加系统性脚本化测试套件

PR body指出:'Adds the scripted-runtime test harness (one forward per script yield, precise per-step engine-state assertions over an HTTP-server + scheduler-hook driven engine) and a chunked-prefill test suite, plus kv-canary PP fixtures and the registered scripted-runtime/chunked CI tests.' 这些测试覆盖了先前缺乏细粒度验证的chunked prefill调度路径,包括中止、radix缓存交互、KV压力场景和PP并行等。

建议调度器维护者精读test/registered/chunked_prefill/中的脚本化测试,理解框架的断言模式;对于其他模块开发者,scripted-runtime框架可以推广到其他子系统的细粒度测试。该PR的测试设计(精确步进+状态断言)值得作为项目测试标准。

讨论亮点
  • assert num_examples导致大面积CI失败:作者在#27502中添加的硬断言 num_examples > available test lines 因上游通过切片静默处理而触发多个注册测试失败。经讨论删除该断言,保留切片行为。
  • PP cross-mb in-flight exclusion是否为死代码:作者从另一个分支cherry-pick了_in_flight_other_mb_rids修复,后在main上确认该代码是dead defensive,因stateless-scheduler分支的重写确实引起PP+chunked KV损坏但main无此问题。最终删除该代码及其测试。
  • test_mimo_v2超时预先存在:作者通过跨分支rerun确认main上也超时,推断为大型模型下载超时的基础设施波动。后续合并#27512后通过。

实现拆解

  1. scripted-runtime测试框架:在 python/sglang/test/scripted_runtime/ 下构建测试框架,核心包括 ScriptedContext(通过HTTP与引擎交互,逐yield驱动调度循环)、ScriptedReqHandle(封装请求状态)和 ScriptedTestCase(测试基类)。框架通过 ScriptedSchedulerHook 注入调度器的 on_run_batch 回调,记录每步的batch信息(rid、mode、chunks等),供测试断言。

  2. 手动脚本化测试套件:在 test/manual/chunked_prefill/ 中添加8个测试模块,涵盖chunked prefill的核心场景:test_scripted_abort.py(中止与资源释放)、test_scripted_radix.py(radix缓存命中与驱逐)、test_scripted_kv_pressure.py(KV压力与恢复)、test_scripted_multi_req.py(多请求并发)、test_scripted_regression.py(回归场景)、test_scripted_invariants.py(引擎不变量)、test_scripted_chunk_size.py(分片尺寸边界)、test_scripted_lifecycle.py(请求生命周期)等。每个测试类继承 ScriptedTestCase,静态脚本方法通过 yield 驱动引擎一步,并在每一步后对请求状态(kv_pageschunks_donestatus等)进行精确断言。

  3. 端到端测试:在 test/manual/chunked_prefill/ 中添加 test_e2e_*.py,使用完整的HTTP服务器 + GSM8K评估,验证chunked prefill在真实推理管道中的准确性和KV leak安全性。同时注册了11个GPU端到端测试到CI。

  4. KV-canary PP夹具:添加 kv_canary 的pipeline parallelism测试夹具,在PP配置下验证SWA模型和常规模型的KV输出完整性。

  5. 配套修复与调整:在构建测试过程中发现并修复了若干问题:移除断言 num_examples > available test lines(#27502)、修正 chunks_done 计数逻辑、调整 flush_cache 行为、修复 kv_pages 报告规则等;移除PP cross-mb in-flight exclusion死代码。

文件 模块 状态 重要度
test/manual/chunked_prefill/test_scripted_abort.py 中止测试 added 7.48
test/manual/chunked_prefill/test_scripted_special_case.py 特殊场景测试 added 7.48
test/manual/chunked_prefill/test_scripted_kv_pressure.py KV 压力测试 added 7.48
test/manual/chunked_prefill/test_scripted_chunk_size.py 分片大小测试 added 7.49
test/manual/chunked_prefill/test_scripted_radix.py Radix 测试 added 7.49

关键符号

_drain_until_released _expected_chunks ScriptedTestCase ScriptedContext.start_req ScriptedContext.abort ScriptedContext.pause_generation ScriptedContext.continue_generation ScriptedContext.flush_cache ScriptedContext.engine_stats ScriptedContext.batch_composition ScriptedContext.list_active_reqs ScriptedReqHandle.is_chunking ScriptedReqHandle.chunks_done ScriptedReqHandle.kv_pages ScriptedReqHandle.lock_refs

关键源码片段

test/manual/chunked_prefill/test_scripted_abort.py test-coverage

展示了 scripted-runtime 测试的核心模式:每步 yield 驱动调度循环,精确断言 KV/lock 释放状态。测试了中止 chunked 请求的多种边界(刚分片、分片中、零 yield 后等)。

import unittestfrom sglang.test.scripted_runtime.context import ScriptedContext
from sglang.test.scripted_runtime.req_handle import ScriptedReqHandle
from sglang.test.scripted_runtime.test_case import ScriptedTestCase
from sglang.test.scripted_runtime_chunked_helpers import (
    DEFAULT_CHUNK_SIZE, DEFAULT_MAX_STEPS,
    VERY_LONG_PROMPT_LEN, base_engine_kwargs,
    run_until, run_until_finished,
)
​
​
def _drain_until_released(t: ScriptedContext, *handles: ScriptedReqHandle):
    # 轮询最多 12 步,直到所有 handle 释放 KV pages 和 lock_refs
    for _ in range(12):
        if all(
            h.kv_pages == 0 and h.lock_refs == 0
            and (h.req is None or h.req.req_pool_idx is None)
            for h in handles
        ):
            return
        yield
​
​
class TestAbortBasic(ScriptedTestCase):
    ENGINE_KWARGS = base_engine_kwargs(chunked_prefill_size=DEFAULT_CHUNK_SIZE)
​
    def test_abort_waiting_chunked_resume(self):
        self.server.execute_script(self._script_abort_waiting_chunked_resume)
​
    @staticmethod
    def _script_abort_waiting_chunked_resume(t: ScriptedContext):
        # 启动一个足够长的请求,使其进入 chunked prefill 状态
        r = t.start_req(
            prompt_len=VERY_LONG_PROMPT_LEN, max_new_tokens=2, prompt_token=100
        )
        yield from run_until(r, lambda h: h.is_chunking)
​
        pages_before = r.kv_pages
        assert pages_before > 0, "chunked req 在分片过程中应持有 KV pages"
​
        # 发送 abort,然后等待资源完全释放
        t.abort(r)
        yield from _drain_until_released(t, r)
​
        assert r.kv_pages == 0, "abort 必须释放所有 KV pages"
        assert r.req is None or r.req.req_pool_idx is None, "abort 必须释放 req 行"
        assert r.lock_refs == 0, "abort 必须释放 lock refs"

评论区精华

assert num_examples 导致 CI 大面积失败 正确性

作者在 #27502 中添加硬断言,但上游通过切片静默处理,导致 9 个 CI 任务因 num_examples > dataset size 而失败。

结论:删除硬断言,保留切片行为。 · 已解决

PP cross-mb in-flight exclusion 是否为死代码 设计

作者从另一个分支 cherry-pick 了 _in_flight_other_mb_rids 修复,但脚本化测试发现 main 上此代码是 dead defensive,仅 stateless-scheduler 分支真正需要。

结论:删除 _in_flight_other_mb_rids 代码及其测试。 · 已解决

test_mimo_v2 超时是预先存在的 question

作者通过跨分支 rerun 确认 main 上同样超时,推断为基础设施波动或大型模型下载超时。

结论:确认为预先存在的超时,非 PR 引入。 · 已解决

风险与影响

测试框架通过scheduler hook注入,不改变生产路径,无性能影响。大量新增CI测试(约100个测试用例)会增加运行时间,估算约10-15分钟。框架依赖调度器内部hook(on_run_batch)和请求对象状态,若调度器接口变化需同步更新测试。测试中使用细粒度断言(如kv_pages == 0),可能因引擎行为微调而脆弱,但这也是测试的价值所在。整体风险低。

对用户无直接影响;对开发者,scripted-runtime测试框架提供了可复用的测试方法论,可替代部分手动调试,提高chunked prefill相关修改的回归拦截能力;对CI,新增约100个测试用例,部分需要多GPU资源(PP测试),可能影响CI排队时间。团队应鼓励在调度器修改时运行此套件。

大量新 CI 测试增加运行时间 依赖调度器内部 hook 测试框架维护成本

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论