Prhub

#28504 Skip empty non-idle output batches

原始 PR 作者 weireweire 合并时间 2026-06-26 02:40 文件变更 1 提交数 1 评论 5 代码增减 +2 / -3

执行摘要

避免在非空闲批次中发送空输出

PR body 指出:'With stream_interval > 1, output streaming is still invoked every decode step. On steps where no request reaches its output interval, the previous has_reqs gate still emitted an empty BatchTokenIDOutput(rids=[]), adding scheduler-to-detokenizer/tokenizer work on the decode hot path.'

值得合入的微优化,逻辑清晰且风险低。可学习其将判定逻辑从外部参数内聚到内部状态的设计思路。

讨论亮点

PR 未产生人工 review 评论。机器人评论指出本次变更是简化 to_payload 并移除了冗余参数。审核者 ShangmingCai 表示 'Nice Catch. LGTM.' 并通过了审批。

实现拆解

  1. 移除对外暴露的 has_reqs 参数:在 _GenerationStreamAccumulator.to_payload() 方法中删除 has_reqs 参数(文件 output_streamer.py 第 500 行)。
  2. 调用处同步调整:在 _stream_output_generation() 中移除 has_reqs=bool(reqs) 参数传递(第 160 行)。
  3. 判定逻辑内聚:将空批次判定条件从 not (has_reqs or is_idle_batch) 改为 not (self.rids or is_idle_batch) ,利用累积器内部已经收集的 rids 列表(即实际有输出的请求)来决定是否返回 None 。若 self.rids 为空且非空闲批,则返回 None ,跳过发送。空闲批的旧行为完全保留。
文件 模块 状态 重要度
python/sglang/srt/managers/scheduler_components/output_streamer.py 输出流 modified 5.51

关键符号

_GenerationStreamAccumulator.to_payload _GenerationStreamAccumulator._stream_output_generation

关键源码片段

python/sglang/srt/managers/scheduler_components/output_streamer.py core-logic

唯一修改的文件:移除 has_reqs 参数并将空批次判定逻辑从外部输入改为内部 self.rids 检查,实现核心优化。

# python/sglang/srt/managers/scheduler_components/output_streamer.py
​
    # 修改后的 _stream_output_generation 调用 to_payload 时不再传递 has_reqs
    def _stream_output_generation(self, reqs: List[Req], is_idle_batch: bool):
        # ... 构造 acc 对象 ...
        for req in reqs:
            if req is skip_req:
                continue
            if req.finished() and req.finished_output:
                continue
            acc.accept(req=req)
            self._maybe_log_time_stats(req=req)
​
        # Send to detokenizer
        payload = acc.to_payload(
            dp_rank=self.ps.dp_rank,
            is_idle_batch=is_idle_batch,
            # 原 `has_reqs=bool(reqs)` 已移除,逻辑内聚到 to_payload 内部
        )
        if payload is not None:
            self.send_to_detokenizer.send_output(payload)
​
    # to_payload 方法:不再依赖外部 has_reqs 参数,改为检查内部 self.rids 是否为空
    def to_payload(
        self, *, dp_rank: int, is_idle_batch: bool
    ) -> Optional[BatchTokenIDOutput]:
        # 原条件:if not (has_reqs or is_idle_batch): return None
        # 新条件:如果 self.rids 为空且不是空闲批,则返回 None(跳过空输出)
        if not (self.rids or is_idle_batch):
            return None
        dp_ranks = [dp_rank] * len(self.rids) if self.rids else None
        return BatchTokenIDOutput(
            rids=self.rids,
            http_worker_ipcs=self.http_worker_ipcs,
            # ... 其他字段保持不变 ...
        )

评论区精华

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

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

风险与影响

风险极低。变更仅 5 行,逻辑等价:原条件是 has_reqs or is_idle_batch ,其中 has_reqs 由调用方传入 bool(reqs) ;新条件是 self.rids or is_idle_batch 。在非空闲批时,has_reqs 为 True 但累积器可能尚未处理任何请求(即 self.rids 为空),此时旧行为会发送空输出,新行为跳过发送,这正是预期优化。空闲批行为不变。唯一潜在风险是当累积器内部 rids 被修改但不一致时,但累积器仅在 accept() 中追加,逻辑安全。

影响范围局限于 output_streamer.py 中的 _GenerationStreamAccumulator 类。用户侧:stream_interval > 1 时减少不必要的 detokenizer 通信,可能轻微降低延迟和 CPU 负载。系统侧:调度器解码热路径减少一个空消息的序列化/发送开销。团队侧:代码更简洁,职责更内聚。无兼容性问题。

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论