执行摘要
- 一句话:移除冗余req_pool_indices_cpu保护并整合GLM-5 HiSparse端到端测试
- 推荐动作:值得精读。展示了如何基于上游修复进行死代码清理,以及如何设计回归测试验证内部契约。
map_last_loc_to_buffer的None参数防护也值得关注。
功能与动机
28514修复了hisparse decode-batch builder使其正确设置req_pool_indices_cpu主机镜像,因此之前用于同步的防御性代码变为死代码。独立的GLM-5 hisparse端到端测试需要单独的8GPU服务器启动,可以通过组合测试框架复用服务器实例,节省CI时间。
实现拆解
- 移除
schedule_batch.py中prepare_for_decode和merge_batch内根据设备张量重建req_pool_indices_cpu的延迟同步代码;同时精简filter_batch空批处理分支,仅保留清空reqs列表,因为is_empty()已能正确识别空批次。
- 重构单元测试
test_schedule_batch_req_pool_indices.py:删除原有针对ScheduleBatch方法的测试,新增TestHisparseDecodeBatchReqPoolCpu(验证_build_hisparse_decode_batch正确填充镜像)和TestHisparseCoordinatorReqPoolCpu(验证map_last_loc_to_buffer在缺少镜像时抛出TypeError,确保契约不被违反)。
- 新建端到端测试文件
test_dsa_glm5_hisparse.py,继承DefaultServerBase和GSM8KMixin,复用现有的组合测试服务器实例,参数与旧测试一致(8GPU、hisparse、flashmla_sparse等)。
- 删除旧的独立端到端测试文件
test_dsa_models_hisparse.py,避免重复。
关键文件:
python/sglang/srt/managers/schedule_batch.py(模块 调度器核心;类别 source;类型 core-logic;符号 prepare_for_decode, filter_batch, merge_batch): 核心调度批处理逻辑,移除了三处冗余的req_pool_indices_cpu保护代码,是PR的主要源码变更。
test/registered/unit/managers/test_schedule_batch_req_pool_indices.py(模块 调度批处理;类别 test;类型 test-coverage;符号 TestHisparseDecodeBatchReqPoolCpu, test_build_hisparse_decode_batch_populates_req_pool_indices_cpu, TestHisparseCoordinatorReqPoolCpu, test_host_bookkeeping_requires_req_pool_indices_cpu): 重构了单元测试,新增对hisparse builder和coordinator的回归测试,确保移除保护后镜像契约仍然有效。
test/registered/models_e2e/test_dsa_glm5_hisparse.py(模块 GLM5 HiSparse;类别 test;类型 test-coverage;符号 TestGLM5HiSparse): 新增的端到端测试,将GLM-5 HiSparse测试纳入组合测试框架,避免了单独启动8GPU服务器。
test/registered/8-gpu-models/test_dsa_models_hisparse.py(模块 DSA模型测试;类别 test;类型 deletion;符号 TestGLM5DPHiSparse, setUpClass, tearDownClass, test_a_gsm8k): 删除旧的独立端到端测试,其功能已由新的组合测试替代。
关键符号:prepare_for_decode, filter_batch, merge_batch, _build_hisparse_decode_batch, map_last_loc_to_buffer
关键源码片段
python/sglang/srt/managers/schedule_batch.py
核心调度批处理逻辑,移除了三处冗余的req_pool_indices_cpu保护代码,是PR的主要源码变更。
# python/sglang/srt/managers/schedule_batch.py
# 关键变更:prepare_for_decode 中移除 req_pool_indices_cpu 的延迟同步保护
def prepare_for_decode(self):
self.forward_mode = ForwardMode.DECODE
bs = len(self.reqs)
# 移除以下保护代码:
# if self.req_pool_indices_cpu is None and self.req_pool_indices is not None:
# self.req_pool_indices_cpu = self.req_pool_indices.detach().cpu().to(dtype=torch.int64)
# 原因:上游 _build_hisparse_decode_batch 已保证 host 镜像与 device 张量同步
# Decode embeds the last output token via embed_tokens; clear the stale
# prefill-time tensor so it doesn't leak into ForwardBatch.
self.input_embeds = None
# ... 后续保持不变 ...
# filter_batch 空批处理分支极大简化
def filter_batch(
self,
chunked_req_to_exclude: Optional[Union[Req, List[Req]]] = None,
keep_indices: Optional[List[int]] = None,
):
if keep_indices is None:
if isinstance(chunked_req_to_exclude, Req):
chunked_req_to_exclude = [chunked_req_to_exclude]
elif chunked_req_to_exclude is None:
chunked_req_to_exclude = []
keep_indices = [
i
for i in range(len(self.reqs))
if not self.reqs[i].finished()
and self.reqs[i] not in chunked_req_to_exclude
]
if keep_indices is None or len(keep_indices) == 0:
# Filter out all requests. Stale tensors are left as-is: is_empty()
# keys off reqs, so callers drop the batch before a forward reads them.
self.reqs = []
return
# ... 正常过滤分支不变 ...
test/registered/unit/managers/test_schedule_batch_req_pool_indices.py
重构了单元测试,新增对hisparse builder和coordinator的回归测试,确保移除保护后镜像契约仍然有效。
# test/registered/unit/managers/test_schedule_batch_req_pool_indices.py
# 新增测试:验证 _build_hisparse_decode_batch 正确填充 req_pool_indices_cpu 镜像
class TestHisparseDecodeBatchReqPoolCpu(unittest.TestCase):
def test_build_hisparse_decode_batch_populates_req_pool_indices_cpu(self):
# _build_hisparse_decode_batch 基于正常 extend 路径构造 ScheduleBatch,
# 因此它必须保持 req_pool_indices_cpu 主机镜像与设备张量同步。
# 缺少镜像会导致 hisparse 解码时的 bookkeeping 崩溃
# (map_last_loc_to_buffer -> _grow_device_buffers 索引 req_pool_indices_cpu)。
scheduler = Scheduler.__new__(Scheduler)
scheduler.device = "cpu"
scheduler.req_to_token_pool = types.SimpleNamespace(device="cpu")
scheduler.token_to_kv_pool_allocator = None
scheduler.tree_cache = None
scheduler.model_config = types.SimpleNamespace(
is_encoder_decoder=False, vocab_size=32
)
scheduler.enable_overlap = False
scheduler.spec_algorithm = types.SimpleNamespace(is_none=lambda: True)
scheduler.future_map = MagicMock()
reqs = [
_make_req(req_pool_idx=4, origin_input_ids=[1, 2, 3], output_ids=[7]),
_make_req(req_pool_idx=9, origin_input_ids=[1, 2], output_ids=[8]),
]
with patch(
"sglang.srt.managers.scheduler.SamplingBatchInfo.from_schedule_batch",
return_value=MagicMock(),
):
batch = scheduler._build_hisparse_decode_batch(reqs)
# 断言镜像与设备张量一致,而不是硬编码输入值的拷贝
self.assertIsNotNone(batch.req_pool_indices_cpu)
self.assertTrue(
torch.equal(batch.req_pool_indices_cpu, batch.req_pool_indices.cpu())
)
评论区精华
PR未产生review评论。作者通过issue评论触发两次rerun测试(test_schedule_batch_req_pool_indices.py和test_dsa_glm5_hisparse.py均通过),验证了变更的正确性。
风险与影响
- 风险:核心风险是被移除的保护代码在其他未覆盖路径中仍有依赖。但:
1) 上游#28514已确保hisparse builder总设置镜像;
2) prepare_for_decode和merge_batch的移除处是防御性回退,而非主要逻辑;
3) filter_batch空处理精简后,is_empty()检查确保调用者不会访问空张量。新增的回归测试覆盖builder和coordinator的镜像契约,风险可控。
- 影响:对用户无直接影响。对团队:CI效率提升(合并e2e测试减少额外服务器启动);代码可维护性提升(死代码移除);测试覆盖更精准(从模拟测试转向集成测试+契约测试)。
- 风险标记:依赖上游修复, 测试覆盖重构
关联脉络
- PR #28514 Fix hisparse decode-batch builder sets req_pool_indices_cpu: 前置修复,确保hisparse builder正确设置req_pool_indices_cpu,使本PR的清理成为可能。
参与讨论