执行摘要
- 一句话:为chunked prefill添加系统性脚本化测试套件
- 推荐动作:建议调度器维护者精读
test/registered/chunked_prefill/中的脚本化测试,理解框架的断言模式;对于其他模块开发者,scripted-runtime框架可以推广到其他子系统的细粒度测试。该PR的测试设计(精确步进+状态断言)值得作为项目测试标准。
功能与动机
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并行等。
实现拆解
-
scripted-runtime测试框架:在 python/sglang/test/scripted_runtime/ 下构建测试框架,核心包括 ScriptedContext(通过HTTP与引擎交互,逐yield驱动调度循环)、ScriptedReqHandle(封装请求状态)和 ScriptedTestCase(测试基类)。框架通过 ScriptedSchedulerHook 注入调度器的 on_run_batch 回调,记录每步的batch信息(rid、mode、chunks等),供测试断言。
-
手动脚本化测试套件:在 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_pages、chunks_done、status等)进行精确断言。
-
端到端测试:在 test/manual/chunked_prefill/ 中添加 test_e2e_*.py,使用完整的HTTP服务器 + GSM8K评估,验证chunked prefill在真实推理管道中的准确性和KV leak安全性。同时注册了11个GPU端到端测试到CI。
-
KV-canary PP夹具:添加 kv_canary 的pipeline parallelism测试夹具,在PP配置下验证SWA模型和常规模型的KV输出完整性。
-
配套修复与调整:在构建测试过程中发现并修复了若干问题:移除断言 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(模块 中止测试;类别 test;类型 test-coverage;符号 _drain_until_released, TestAbortBasic, test_abort_waiting_chunked_resume, _script_abort_waiting_chunked_resume): 展示了scripted-runtime测试的核心模式:每步yield驱动调度循环,精确断言KV/lock释放状态。测试了中止chunked请求的多种边界(刚分片、分片中、零yield后等)。
test/manual/chunked_prefill/test_scripted_special_case.py(模块 特殊场景测试;类别 test;类型 test-coverage;符号 _load_inquirer_pending_for_rid, TestSpecialCaseBasic, test_chunked_in_flight_no_idle, _script_chunked_in_flight_no_idle): 覆盖chunked prefill的特殊路径:in-flight不被idle、admission与chunked并发、abort排除chunked等。1253行测试,是最大的手动测试文件。
test/manual/chunked_prefill/test_scripted_kv_pressure.py(模块 KV压力测试;类别 test;类型 test-coverage;符号 TestKVPressureBasic, test_lock_refs_tight_concurrent_prefix, _script_lock_refs_tight_concurrent_prefix, test_kv_pressure_with_retract_resume): 测试KV压力下的chunked prefill行为:lock refs竞争、retract恢复、chunked batch后池状态恢复。验证极端条件下内存是否泄漏。
test/manual/chunked_prefill/test_scripted_chunk_size.py(模块 分片大小测试;类别 test;类型 test-coverage;符号 _expected_chunks, TestChunkSizeDefault, test_exact_chunk_size, _script_exact_chunk_size): 测试chunks_done计数精度和分片边界条件,包括刚好等于chunk size、超出1个token、N个chunks等。是验证分片逻辑正确性的基础测试。
test/manual/chunked_prefill/test_scripted_radix.py(模块 Radix测试;类别 test;类型 test-coverage;符号 TestRadixBasic, test_radix_full_prefix_hit_nine_reqs, _script_radix_full_prefix_hit_nine_reqs, test_radix_hit_full_prefix): 验证chunked prefill与radix缓存交互:完整前缀命中、部分命中后分片、驱逐后重新分片、resume路径等。
关键符号:_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
展示了scripted-runtime测试的核心模式:每步yield驱动调度循环,精确断言KV/lock释放状态。测试了中止chunked请求的多种边界(刚分片、分片中、零yield后等)。
import unittest
from 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"
评论区精华
风险与影响
- 风险:测试框架通过scheduler hook注入,不改变生产路径,无性能影响。大量新增CI测试(约100个测试用例)会增加运行时间,估算约10-15分钟。框架依赖调度器内部hook(
on_run_batch)和请求对象状态,若调度器接口变化需同步更新测试。测试中使用细粒度断言(如kv_pages == 0),可能因引擎行为微调而脆弱,但这也是测试的价值所在。整体风险低。
- 影响:对用户无直接影响;对开发者,scripted-runtime测试框架提供了可复用的测试方法论,可替代部分手动调试,提高chunked prefill相关修改的回归拦截能力;对CI,新增约100个测试用例,部分需要多GPU资源(PP测试),可能影响CI排队时间。团队应鼓励在调度器修改时运行此套件。
- 风险标记:大量新CI测试增加运行时间, 依赖调度器内部hook, 测试框架维护成本
关联脉络
- PR #27502 Add mixed-prefix gsm8k eval and its CPU unit test: 本PR的基础,引入了混合前缀评估,为chunked prefill端到端测试提供评测基础。
- PR #26991 [Scripted Runtime] Extracted chain from scripted_runtime_and_chunked_testing: 包含了scripted-runtime框架的早期提取,本PR在此基础上进一步发展并合并。
- PR #27512 [Fix] Fix multimodal pad-sentinel clamp for MiMo-V2: 修复了multimodal模型的pad-sentinel,本PR CI依赖此修复通过test_mimo_v2测试。
- PR #27528 [Fix] Fix FusedMoEWithLoRA hidden_size attribute: 修复了LoRA相关属性缺失,本PR CI依赖此修复通过LoRA测试。
参与讨论