读 request_logger 与 schedule_simulator#

这章解决什么问题#

前面的代码导读已经把请求主线、cache 树、输出尾部和运行时控制面都落回了源码,但还有一条很有工程价值、同时也很像“大书后半程才会出现”的闭环没有被放进代码导读:

  • 线上请求日志怎样真正落盘
  • 这些日志怎样再被离线调度模拟器消费

也就是说,如果你已经抓到了线上 request 日志,源码里下一步该去哪里读,才能把它变成调度策略实验材料?

这章的目标,就是把:

  • srt/utils/request_logger.py
  • TokenizerManager.configure_logging(...)
  • debug_utils/schedule_simulator/

收成一条正式的源码阅读路径。

为什么这条链值得放进代码导读#

因为它不是纯维护技巧,也不是纯工具手册,而是一条真实的源码工作流:

  • 运行时怎样决定日志口径
  • 日志里哪些字段会被保留下来
  • 这些字段怎样被 loader 缩成模拟请求
  • 模拟器又怎样把它们送进 router / scheduler policy

从技术书角度看,这条链特别有价值,因为它说明:

  • 线上证据并不是终点
  • 还可以继续变成离线实验输入

一张图:这不是两件工具,而是一条连续链#

这张图解决的理解障碍是:很多读者会把 request logger 和 simulator 看成两套完全分开的工具,实际上它们在源码里是可以接起来的。

flowchart LR
    Req["runtime request"] --> Log["RequestLogger.log_finished_request(...)"]
    Log --> File["request.finished JSON lines"]
    File --> Loader["load_from_request_logger(...)"]
    Loader --> Sim["schedule_simulator"]
    Sim --> Policy["router / scheduler policy replay"]

图比纯文字多解释的一点是:日志不是只给人看,也可以成为策略实验的输入格式。

第一棵树:RequestLogger 应该怎么读#

如果要稳,最先抓的其实只有三处:

  1. configure(...)
  2. log_received_request(...)
  3. log_finished_request(...)

这样读的好处是,你会立刻看清:

  • 运行中日志行为是否可变
  • 首尾日志分别保留了什么
  • finished request 为什么会成为后续模拟的样本源

metadata 为什么是 RequestLogger 的真正核心#

RequestLogger 初始化时会立刻算:

  • metadata = self._compute_metadata()

这组 metadata 里最重要的是:

  • max_length
  • skip_names
  • out_skip_names

这说明日志器本质上不是“把对象直接打印出来”,而是先决定:

  • 哪些字段保留
  • 哪些字段裁掉
  • 哪些字段需要做截断

从工程角度看,这特别重要,因为后面的 simulator loader 只会看到被保留下来的 finished request 记录。

也就是说,logger 本身就在定义“未来离线实验能看见什么”。

log_finished_request(...) 为什么比 log_received_request(...) 更关键#

因为离线 simulator 默认只吃:

  • event == "request.finished"

而 finished log 里才会有真正稳定的:

  • prompt_tokens
  • completion_tokens
  • rid

这说明 request logger 的 finished 分支,不只是排障日志出口,也是后续离线分析的数据出口。

从技术书角度看,这是一处很值得点出来的“同一机制在不同层级重复利用”的设计。

configure_logging(...) 怎样把运行时和 logger 接起来#

TokenizerManager 里:

  • configure_logging(obj)

会直接调用:

  • self.request_logger.configure(...)

同时还可能更新:

  • dump_requests_folder
  • dump_requests_threshold

这说明日志控制面不是 request logger 这棵树自己的附属功能,而是被正式拉进运行时控制面里的。

换句话说:

  • 你在线上调日志粒度
  • 其实就是在改变后续会进入“离线可分析材料”的请求样本口径

第二棵树:load_from_request_logger(...) 为什么设计得这么克制#

debug_utils/schedule_simulator/data_source/data_loader.py 很值得读,因为它透露出非常清楚的边界:

  • 只读 request.finished
  • 只抽 request_id
  • input_len = prompt_tokens
  • output_len = completion_tokens

这说明 simulator 的目标不是复刻完整请求语义,而是构造:

  • 最小够用的调度样本

这是一个非常值得技术书强调的取舍:

  • 如果把完整请求语义都带进来,模拟器会过重
  • 只取长度信息,则更适合做路由 / 调度策略实验

schedule_simulator/entrypoint.py 应该怎么读#

这份入口文件的阅读价值不在 argparse 本身,而在它把模拟问题明确切成了几组独立维度:

  • 数据来源:真实 request logger 或 synthetic
  • router:random / round_robin / sticky
  • scheduler:当前至少是 fifo
  • num_gpus_per_engine
  • num_engines
  • max_total_tokens

这说明 simulator 的重点不是“完全复现线上”,而是:

  • 给定某批请求形状,比较不同策略在同一资源模型上的相对行为

从系统阅读角度看,这是一种非常清晰的职责边界。

为什么这条链对整本书特别有回扣价值#

它会把很多前文重新接起来:

  • request logger:解释线上 finished request 记录从哪里来
  • 调度与内存:解释模拟器试的到底是哪类资源和策略问题
  • 运行时控制面:解释你怎样在线上调整采样窗口和日志粒度
  • codebase walkthrough:解释这些工具最终还是回到具体 router / scheduler policy 实现

也就是说,它不只是“再多一个工具介绍”,而是把线上维护和离线实验正式连成了一条工程闭环。

这一层最容易出现的误判#

1. 以为 request logger 只是值班日志#

它同样在给离线调度实验准备样本。

2. 以为 simulator 能复刻完整 runtime correctness#

它更专注于长度和策略维度,不是功能 replay。

3. 以为 logger 粒度只影响人类阅读体验#

它也会影响后续被离线工具消费的样本质量。

如果你要顺着源码读这条链,推荐顺序是什么#

建议按下面顺序:

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

这样读,你先搞清样本是怎样产生的,再搞清样本如何被压缩成模拟请求,最后才看策略实验本身。

小结#

这一章真正要补齐的,是代码导读里一条很有工程价值的闭环:

  • request logger 负责把线上请求结束事实变成结构化记录
  • schedule simulator 则负责把这些记录缩成调度实验输入

到这里,代码导读就不只是在教你“线上系统怎么跑”,也开始教你“线上证据怎样继续被拿去做离线策略分析”。