request logger、schedule simulator 与离线复盘#

前面的维护章节已经把 request logger、request dump / crash dump、metrics exporter 和 tracing 分别讲出来了,但如果书稿停在这里,仍然还少一条特别像“大书后半程”才会补上的工程闭环:

线上抓到的请求日志,怎样继续变成离线调度复盘材料?

这不是“还有没有更多日志”的问题,而是更像工程工作流的问题:你怎样把线上请求样本真正拿去做脱离线上环境的策略分析,又怎样把现场证据从“事故材料”升级成“可反复试验的数据集”。只要这条闭环成立,维护层就不再只是教你怎样抓现场,而开始教你怎样把现场证据继续变成下一步决策材料。

先把这条链放回维护主线#

request logger -> schedule simulator 这条链最容易被低估,因为很多系统书会在讲完日志、trace 和 dump 之后就停住,仿佛维护者拿到证据就结束了。真实工程里并不是这样。真正高价值的维护闭环往往还包括:

  • 用线上材料构造离线实验
  • 用离线实验比较不同调度或路由策略
  • 再把结论带回线上改动

SGLang 源码里已经给了这条链的雏形,所以它特别值得被单独写成一章,而不是散在 request logger 或 simulator 的局部说明里。

这条链真正长成什么样#

下面这张图的作用,是把“线上 finished request”怎样继续流进离线调度实验压成一条最短路径:

flowchart LR
    Online["online request"] --> Logger["RequestLogger"]
    Logger --> File["request.finished JSON lines"]
    File --> Loader["load_from_request_logger(...)"]
    Loader --> Sim["schedule_simulator"]
    Sim --> Metrics["offline scheduling metrics"]

图里最关键的不是“有五个节点”,而是它明确说明:request logger 不只是值班现场的读物,也可以成为调度策略实验的样本源。

RequestLogger 最值得抓的是 finished request,而不是全量事件#

srt/utils/request_logger.py 里有两个很显眼的入口:

  • log_received_request(...)
  • log_finished_request(...)

对这一章来说,真正更关键的是后者。原因非常直接:schedule simulator 的默认 loader 主要消费的就是:

  • event == "request.finished"

并从其中提取:

  • rid
  • prompt_tokens
  • completion_tokens

这说明 request logger 的工程价值,不只是把请求过程记下来,而是至少留下了足够多的 request-level 完成事实,使它能够被当成离线调度输入,而不只是人类肉眼阅读的日志。

configure_logging(...) 真正决定的是你能不能抓到可用样本#

前面的章节已经把 /configure_logging 解释成运行时控制面的一部分。放回这条链再看一次,它的价值更具体:你可以在问题窗口期调高 request logger 粒度,然后收集一段更适合离线分析的 finished request 记录。

也就是说,在线控制面和离线模拟器并不是两套互不相关的工具。前者决定你能否抓到高质量样本,后者决定你能否把这些样本进一步变成实验材料。一本好系统书到这里,应该主动把两者接起来,而不是分散在不同章节里各自存在。

load_from_request_logger(...) 透露了一个很克制的设计取舍#

debug_utils/schedule_simulator/data_source/data_loader.py 的 loader 最值得注意的地方,不是它读取了多少字段,而是它刻意只读取了最小必要字段:

  • request_id
  • input_len
  • output_len

然后把它们构造成 SimRequest。这说明 schedule simulator 的设计目标从来不是“重演整条业务语义”,而是更克制地提取:

  • 足够支撑调度模拟的最小请求形状

这背后的工程取舍非常清楚:

  • 收益是样本更轻、更容易批量回放
  • 代价是它不能替代完整 replay 或 crash dump

也正因为有这层取舍,request logger -> schedule simulator 才更像“调度行为复盘”,而不是“功能 correctness 重放”。

这就是它和 crash dump / replay 最根本的边界#

很多人第一次看到这些离线工具时,很容易把它们都归成一类。更稳的理解是:

crash dump / request dump#

更适合回答:

  • 某条请求为什么在真实 runtime 里崩了、挂了、输出错了

schedule simulator#

更适合回答:

  • 给定一批线上真实请求形状,不同调度 / 路由策略大致会怎样表现

这意味着:

  • dump / replay 更偏功能与故障重现
  • request logger -> simulator 更偏调度策略实验

如果把两者混成同一种“离线工具”,就会在使用上产生完全错误的期待。

schedule_simulator 真正抽取的是哪一层现实#

entrypoint.py 和 data loader 路径看,当前 simulator 更关注的是:

  • input_len
  • output_len
  • router policy
  • scheduler policy
  • max_total_tokens
  • num_gpus_per_engine
  • num_engines

这说明它的主要用途不是精确复刻线上所有 runtime 细节,而是用真实请求形状去做:

  • 路由 / 调度策略层的相对比较

这很像一本好系统书应该主动提醒读者的那种边界:

  • 模拟器不等于线上系统
  • 但它能回答一类线上很难直接试的问题

为什么它特别适合做“策略前置验证”#

如果你准备动的是:

  • route sticky 策略
  • scheduler policy
  • token budget 相关排队启发式

那么直接在线上试不仅慢,而且风险高。request logger 给你一批真实请求形状,而 simulator 则给你一个更安全的试验场。这意味着这条链天然适合做:

  • 策略前置验证
  • 回归前的粗筛
  • 某些假设是否值得继续深挖的低成本判断

也就是说,它把维护者从“先改再看”推进到了“先用真实样本试验,再决定值不值得改”。这正是这章最像成熟工程书的地方。

这条闭环怎样把前文重新接起来#

它其实把很多前文散落的能力重新串成了一条维护闭环:

  • request logger 负责留下结构化 finished request 记录
  • 调度与内存章节解释模拟器真正试的是哪类策略问题
  • 运行时控制面解释你怎样在线调整日志粒度
  • 代码导读解释 simulator 主要覆盖哪一层,而不是哪一层

也就是说,这章不只是多介绍一个工具,而是在把“线上证据”和“离线实验”正式编成同一条维护链。

最容易出现的三种误判#

第一,误以为 request logger 只能拿来看日志。
它也可以成为离线调度样本源。

第二,误以为 schedule simulator 能替代 crash replay。
它更适合调度策略比较,而不是功能 correctness 重演。

第三,误以为线上请求材料越全越适合喂给模拟器。
模拟器的价值恰恰在于它只取最小必要维度,保持实验轻量。

真正顺着源码读这条闭环时,更稳的顺序#

建议按下面顺序:

  1. RequestLogger.log_finished_request(...)
  2. TokenizerManager.configure_logging(...)
  3. debug_utils/schedule_simulator/data_source/data_loader.py
  4. debug_utils/schedule_simulator/entrypoint.py
  5. 再回头看 simulator 的 router / scheduler policy 实现

这样读,你先知道样本从哪里来,再知道它如何被缩成模拟请求,最后才去看模拟策略本身。这比一上来就冲进 simulator 入口文件更容易形成稳定地图。

小结#

request logger 和 schedule simulator 真正值得被放在同一章的原因,不在于它们恰好能接起来,而在于它们一起补齐了维护层里一条特别重要的后半程闭环:

  • request logger 负责把真实请求结束事实留下来
  • schedule simulator 负责把这些事实变成离线策略实验样本

到这里,扩展与调试部分就不只是在教你怎样抓现场证据,也开始教你怎样把现场证据继续变成离线调度复盘材料。