执行摘要
- 一句话:调度器新增权重版本跟踪,打通 HTTP 与换权重路径
- 推荐动作:值得精读,尤其是幂等版本记录(同版本/
None 短路)、通过声明式 _COMMUNICATOR_SPECS 注册新 RPC、以及换权重成功后统一回写的接入方式。从事在线权重更新、RL 数据归因或调度器内部状态观测的工程师应重点关注;同时可参考 issue 评论中 Codex 对 CI 失败的系统性 triage 方法。
功能与动机
提交信息明确指出:POST /update_weight_version 只写了 tokenizer 侧的 weight_version,而换权重路径从未回写,因此调度器自身记录停留在启动时的值,任何按调度器读取该值的功能(如 /server_info、权重版本观测)读到的是过期数据。该 PR 正是要修复这一状态不一致,并为同一系列中 per-token weight-version span 的正确归因提供真实数据源。
实现拆解
- 在
python/sglang/srt/managers/io_struct.py 新增 UpdateWeightVersionReqOutput(BaseReq, kw_only=True) 空响应类型,与已有的 UpdateWeightVersionReqInput 配对,补齐 RPC 协议定义。
- 在
python/sglang/srt/managers/scheduler.py 中:
- 在
init_request_dispatcher 注册 (UpdateWeightVersionReqInput, self.handle_update_weight_version);
- 新增
handle_update_weight_version 调用 record_weight_version_change 并返回空响应;
- 新增
record_weight_version_change 幂等方法:None 或与当前版本相同则直接返回,否则记录旧版本并通过 get_context().override("scheduler.weight_version", ...) 写入 serving 上下文,同时输出日志。这样调度器侧版本状态具备幂等更新能力,避免无意义的上下文写入。
- 在
python/sglang/srt/managers/scheduler_components/weight_updater.py 新增 record_weight_version_after_update 辅助方法,并在 update_weights_from_disk、update_weights_from_distributed、update_weights_from_tensor、update_weights_from_ipc 的成功分支调用;失败分支不记录,防止把从未生效的版本标记到后续 token 上。
- 在
python/sglang/srt/managers/tokenizer_control_mixin.py 的声明式 _COMMUNICATOR_SPECS 列表中新增 ("update_weight_version", UpdateWeightVersionReqOutput),并实现 async update_weight_version:先 await update_weight_version_communicator(obj) 把新版本转发给调度器,再调用已有的 _update_weight_version_if_provided 更新 tokenizer 本地副本,保证两侧一致。
- 在
python/sglang/srt/entrypoints/http_server.py 中,update_weight_version 端点由原来的 record_config_updates 本地记录改为 await _global_state.tokenizer_manager.update_weight_version(obj),完成端到端打通。
- 测试配套:新增
test/registered/unit/managers/test_scheduler_weight_version_tracking.py,用 serving/context stub 验证新版本采纳、同版本与 None 版本短路;用 updater stub 验证 disk 成功/失败、draft 失败、分布式成功等路径的版本记录行为。
关键文件:
python/sglang/srt/managers/scheduler.py(模块 调度器;类别 source;类型 core-logic;符号 handle_update_weight_version, record_weight_version_change): 核心改动:注册并实现新的 RPC 处理 handle_update_weight_version,新增幂等的 record_weight_version_change,统一调度器侧版本状态。
python/sglang/srt/managers/scheduler_components/weight_updater.py(模块 权重更新;类别 source;类型 core-logic;符号 record_weight_version_after_update, update_weights_from_disk, update_weights_from_distributed, update_weights_from_tensor): 四条换权重路径的成功分支都通过 record_weight_version_after_update 回写版本,是调度器版本能跟着实际换权重推进的关键。
python/sglang/srt/managers/tokenizer_control_mixin.py(模块 控制转发;类别 source;类型 core-logic;符号 update_weight_version): 声明式通信器注册表中新增 update_weight_version,并提供 async 方法把新版本先转发给调度器再更新本地副本。
python/sglang/srt/entrypoints/http_server.py(模块 HTTP入口;类别 source;类型 entrypoint): HTTP 入口从仅 record_config_updates 改为异步调用 tokenizer_manager.update_weight_version,完成端到端打通。
python/sglang/srt/managers/io_struct.py(模块 协议结构;类别 source;类型 core-logic;符号 UpdateWeightVersionReqOutput): 新增 UpdateWeightVersionReqOutput 响应类型,补齐 RPC 协议的定义。
test/registered/unit/managers/test_scheduler_weight_version_tracking.py(模块 单元测试;类别 test;类型 test-coverage;符号 TestSchedulerRecordWeightVersionChange, TestRecordWeightVersionAfterUpdate, test_a_new_version_is_adopted, test_same_version_is_a_noop): 新增测试覆盖调度器版本记录的幂等与换权重后回写行为,是本次变更的主要质量保障。
关键符号:handle_update_weight_version, record_weight_version_change, record_weight_version_after_update, update_weight_version
关键源码片段
python/sglang/srt/managers/scheduler.py
核心改动:注册并实现新的 RPC 处理 handle_update_weight_version,新增幂等的 record_weight_version_change,统一调度器侧版本状态。
def handle_update_weight_version(
self, recv_req: UpdateWeightVersionReqInput
) -> UpdateWeightVersionReqOutput:
# 新的 RPC 入口:tokenizer 侧通过该消息把新版本号广播给调度器。
# 幂等更新由 record_weight_version_change 保证。
self.record_weight_version_change(new_version=recv_req.new_version)
return UpdateWeightVersionReqOutput()
def record_weight_version_change(self, new_version: Optional[str]) -> None:
# None 表示“本次更新不携带版本号”,视为 no-op;
# 相同版本重复发布也直接短路,避免无意义的上下文写入。
if new_version is None or new_version == get_serving().weight_version:
return
# 记录旧版本,再通过 context override 写回 serving 上下文。
# 这里统一走 "scheduler.weight_version" 键,后续读取方(如
# per-token weight-version spans)读到的都是调度器侧的最新版本。
old_version = get_serving().weight_version
get_context().override("scheduler.weight_version", weight_version=new_version)
logger.info(f"Weight version changed. {old_version=} {new_version=}")
python/sglang/srt/managers/scheduler_components/weight_updater.py
四条换权重路径的成功分支都通过 record_weight_version_after_update 回写版本,是调度器版本能跟着实际换权重推进的关键。
def record_weight_version_after_update(self, weight_version: Optional[str]) -> None:
# 所有换权重路径在成功后统一调用,把新版号交回调度器记录。
# 失败路径不调用,避免记录一个从未生效的版本导致后续 token 被错误标记。
self.scheduler.record_weight_version_change(new_version=weight_version)
def update_weights_from_disk(self, recv_req: UpdateWeightFromDiskReqInput):
"""In-place update of the weights from disk."""
with self._observe_weight_load("disk"):
success, message = self.tp_worker.update_weights_from_disk(recv_req)
tp_success = success
if success and self.draft_worker is not None:
success, message = self.draft_worker.update_weights_from_disk(recv_req)
if tp_success:
self.flush_cache_after_weight_update(recv_req)
if success:
# 仅当 target 和 draft 都成功后记录版本,保证引擎状态一致。
self.record_weight_version_after_update(recv_req.weight_version)
else:
logger.error(message)
return UpdateWeightFromDiskReqOutput(
success=success, message=message, num_paused_requests=0
)
评论区精华
无 review 评论,但 issue 评论记录了两轮 Codex 自动 CI triage:
风险与影响
- 风险:
scheduler.py 的 record_weight_version_change 通过 get_context().override 修改全局 serving 上下文,依赖 get_serving() 与 get_context() 在调用时可用;若并发调用或与其它 override 顺序冲突,可能干扰调度器状态。
weight_updater.py 的 record_weight_version_after_update 直接访问 self.scheduler,若该字段为 None(默认值)会抛 AttributeError;当前构造链总是由 Scheduler 传入自身,但测试仅用 stub,未覆盖 scheduler=None 的风险路径。
- 新增
UpdateWeightVersionReqInput 分发与 UpdateWeightVersionReqOutput 协议类型属于新契约,滚动升级期间旧 scheduler 可能无法识别该消息。
- 换权重成功路径现在会写入调度器版本,若调用方传入的
weight_version 与实际权重内容不符,会污染版本标签(属于调用方责任)。
- 影响:影响面中等:新增一条 tokenizer 到 scheduler 的 RPC 消息;POST /update_weight_version 的行为从仅记录本地配置变为同时驱动调度器状态;disk/distributed/tensor/ipc 四条换权重路径在成功后自动推进调度器版本。对使用在线权重更新与 RL 归因的场景,调度器侧观测值将与实际生效版本一致,为 PR 35926 的 per-token weight-version spans 提供正确数据源。无性能与兼容性破坏。
- 风险标记:调度器全局状态变更, 新增 RPC 协议契约, 权重更新成功路径行为变化, 依赖 serving context 可用性
关联脉络
- PR #35926 Report per-token weight-version spans in generation meta info: 同属权重版本观测链路,本 PR 保证调度器侧版本真实,35926 才能正确给每个 token 打上版本跨度标签;两者都改 scheduler.py 与 io_struct.py。
- PR #35927 Support gated launch to defer startup memory allocation: 同一作者 fzyzcjy 的 op 系列,共享调度器与 server 生命周期路径。
- PR #35929 Report the whole server's world size in the scheduler's internal state: 同一系列中调度器内部状态扩展,与本 PR 一样在 scheduler.py 上增强状态可见性。
参与讨论