Prhub

#33013 config: read resolved config via namespace accessors

原始 PR 作者 ch-wan 合并时间 2026-08-01 06:06 文件变更 187 提交数 1 评论 10 代码增减 +1110 / -923

执行摘要

配置读取迁移至命名空间访问器,覆盖 160 文件 628 处

SGLang 运行时存在多 runner(target / draft)、多引擎共进程、PD 分离等场景,ServerArgs 本质是"启动时 pristine 快照",无法表达解析后、按作用域变化的配置。此前 draft worker 配置自己的 kv-cache dtype 却不发布到全局,导致 draft 端注意力后端继承 target 的 dtype 字符串而选错 fp8 / fp4 路径(见 Issue #32251)。PR body 明确说明这是 "re-lands the reader migration reverted in #32100",并在 RFC #30696 框架下,让所有读者通过命名空间访问器读取"当前作用域解析后的值",而非全局启动记录;同时通过 writer / reader 同 commit 翻转保证覆盖声明对命名空间读者立即可见。

值得精读。这是 SGLang 配置系统的一次架构级迁移,核心看点包括:命名空间访问器与 bag 写入(get_context().override)的读写配对规则、"bag 存意图、runner 存解析值"的 kv-cache dtype 数据契约、以及机械化替换后如何用 bot review 兜底语义等价性。关注 model_runner.pyconfigure_kv_cache_dtypeeagle_worker_v2.py_override_worker_state 两个 writer/reader 协同范例。

讨论亮点

Review 中 Codex bot 发现 4 处机械化替换引入的真实缺陷,作者逐一确认并修复:

  • P1(scheduler.py Ray 路径)SchedulerActor.__init__ 在 Ray 部署下不执行 publish(),而 init_ipc_channels 已改为无条件读 get_observability(),会导致非 NPU Ray scheduler actor 启动即抛 ValueError("config namespace 'observability' not published")。作者在 #33012(栈的 role PR)中补上 publish(server_args, role="scheduler")
  • P2(allocation_sizing.pyget_alloc_len_per_decode(server_args=None) 的默认回填参数被误改写为进程级读取(if server_args is None: server_args = get_server_args() 未被别名追踪豁免),导致 publish 前调用或私有 ServerArgs 计算错误。作者修复并审计出另外两处同类问题(spec_need_hidden_statesDeepSeekV2 _can_dual_stream_graph),并让 sweep 工具跳过参数名绑定。
  • P2(entrypoints/openai/realtime/session.pygrpc_bridge.py:多引擎进程下 get_serving() / get_lora() 是 last-publish-wins,会读到其他引擎的配置。作者将 entrypoints/ 目录整体排除 sweep,realtime 的 served_model_name 切回 self.server_args,LoRA enablement 改读 handle 的 tokenizer_manager.server_args
  • P2(data_parallel_controller.py:多条没有对应 resolved 回复的评论指出,Ray DP controller 在多引擎进程下用 get_exec().moe.elastic_ep_backend 会受后发布引擎影响,建议保留 self.server_args 检查——该点未在可见评论中确认修复,属未决疑虑。

实现拆解

实现按 5 步拆解:

  1. AST 机械化读取替换(主体):基于 AST 且别名感知的脚本,把 get_server_args().FIELDself.server_args.FIELD、以及局部别名读取统一翻转为命名空间访问器(get_exec() / get_memory() / get_spec() / get_schedule() / get_disagg() / get_observability() / get_device() / get_model() / get_lora() / get_serving() / get_mm() 等),按字段的 NS 元数据路由,共 628 处、160 个文件。涉及 python/sglang/srt/managers/scheduler.pymodel_runner.pykv_cache_configurator.pyeagle_worker_v2.pymodel_loader/loader.pybatch_result_processor.pymodels/deepseek_v2.pymodels/deepseek_v4.pymodels/inkling.py 等核心路径。

  2. 按规则保留的读取(非遗漏):明确排除 4 类读取——并行命名空间叶子(留作后续)、per-runner fork 字段(attention backends / context_length / load_format / skip_tokenizer_init / kv_cache_dtype,解析值属于 runner)、per-instance manager 文件(tokenizer 家族和 multimodal processors,多引擎进程下必须留在 self.server_args)、解析管线自身与构造边界、参数形式读取。check_cuda_graph_backend 保持 stdlib-only 模块契约并保留软门(未发布配置返回 False 而非抛错)。

  3. writer 侧 co-flip(读写同 commit)declare_load_time_override 改为通过 get_context().override 写入 config bags,使命名空间读者立即看到声明;draft 构建期声明落在 draft 自己的 bag 上并随其销毁。eagle_worker_v2.pyapply_runtime_state / _override_worker_stateself.server_args.override 切到 get_context().override,并翻转 27 处 *.server_args.speculative_* 读取(13 个注意力后端 + 6 个 graph runner 改为 get_spec() / get_exec().graph)。

  4. kv-cache dtype 数据契约重构(model_runner.pyget_model().kv_cache_dtype bag 叶子现在恒为 RAW 用户意图;解析后的值移到 model_runner.kv_cache_dtype / model_runner.kv_cache_dtype_str。删除 _record_kv_cache_dtype bag 回写方法,configure_kv_cache_dtype 不再发布 resolved 值;KVCacheConfigurator 新增 kv_cache_dtype_str 构造参数,_build_fp4_quant_method 由 runner 传入的字符串解析量化名;PD 握手改走 kv_args,kv / pool configurator 通过构造参数读取 runner 值。

  5. 测试与验证配套test/registered/unit/test_runtime_context.py 更新 test_declare_load_time_override_writes_throughtest_declare_load_time_override_writes_the_bag,验证 override 写 bag 行为。验证手段包括全量单测相对栈底座双向零回归、GPU smoke(EAGLE adaptive spec 实时 step 切换 + 普通 dense 模型)、mutation / writer / legacy ratchet 全绿(writer ratchet 由 49 降至 39)。约 15 个此前 module-skip 的单测仍跳过,由后续 PR 用真实 publish fixtures 恢复。

文件 模块 状态 重要度
python/sglang/srt/managers/scheduler.py 调度器 modified 7.79
python/sglang/srt/model_executor/model_runner.py 模型执行 modified 7.79
python/sglang/srt/mem_cache/kv_cache_configurator.py 缓存配置 modified 7.6
python/sglang/srt/speculative/eagle_worker_v2.py 投机解码 modified 6.95
python/sglang/srt/model_loader/loader.py 模型加载 modified 6.78
python/sglang/srt/managers/scheduler_components/batch_result_processor.py 批处理 modified 6.74
test/registered/unit/test_runtime_context.py 运行时上下文 modified 6.28

关键符号

configure_kv_cache_dtype _record_kv_cache_dtype _build_fp4_quant_method apply_runtime_state _override_worker_state init_tokenizer _collect_shard_config get_alloc_len_per_decode init_ipc_channels _init_pools

关键源码片段

python/sglang/srt/speculative/eagle_worker_v2.py core-logic

speculative 读取批量翻转到 `get_spec()`,且 writer 侧 `apply_runtime_state` / `_override_worker_state` 从 `self.server_args.override` 切到 `get_context().override`,是读者 / 写者 co-flip 的代表性文件。

def _override_worker_state(
    self,
    speculative_num_steps: int,
    speculative_num_draft_tokens: int,
    cuda_graph_bs: list[int] | None = None,
):
    """adaptive spec 图捕获前的运行时覆盖:writer 与 reader 同 commit 翻转。    读者已迁移到 get_spec() / get_exec().graph 命名空间访问器,写者就必须同步
    从 self.server_args.override 切到 get_context().override——否则命名空间读者
    看不到声明。draft 构建期的声明落在 draft 自己的 bag 上并随其销毁,
    server_args 始终保持 pristine 启动记录。
    """
    dw = self._draft_worker
    backup = (
        self.speculative_num_steps,
        self.speculative_num_draft_tokens,
        get_spec().speculative_num_steps,
        get_spec().speculative_num_draft_tokens,
        get_exec().graph.cuda_graph_bs_decode,
        get_exec().graph.disable_cuda_graph,
    )
    self.speculative_num_steps = speculative_num_steps
    self.speculative_num_draft_tokens = speculative_num_draft_tokens
    dw.speculative_num_steps = speculative_num_steps
    dw.speculative_num_draft_tokens = speculative_num_draft_tokens
    get_context().override(
        "adaptive_spec.capture_override",
        speculative_num_steps=speculative_num_steps,
        speculative_num_draft_tokens=speculative_num_draft_tokens,
        cuda_graph_bs_decode=cuda_graph_bs,
        **({"disable_cuda_graph": True} if not cuda_graph_bs else {}),
    )
    # 图捕获完成后在 finally 中恢复 backup 记录的旧值

评论区精华

Ray scheduler actor 未 publish 导致命名空间读取启动失败 正确性

Codex bot 指出 `SchedulerActor.__init__` 在 Ray 路径下直接构造 `Scheduler`,未走 `run_scheduler_process` 的 `publish()`,`init_ipc_channels` 中无条件的 `get_observability()` 读取会抛 `ValueError`,导致所有非 NPU Ray scheduler actor 启动失败。

结论:作者确认属实,修复落在栈的 role PR #33012:actor 在 `configure_scheduler_process` 后对可能替换过的 `server_args` 执行 `publish(server_args, role="scheduler")`。 · 已解决

默认回填参数被机械替换误改写 正确性

`get_alloc_len_per_decode(server_args=None)` 中 `if server_args is None: server_args = get_server_args()` 的默认回填模式未被别名追踪豁免,参数读取被重写为进程级 bag 读取,导致 publish 前调用抛错。

结论:作者修复为 " 显式传入时全部读传入对象,全局访问器仅用于缺省默认值 ",并树级审计出另外两处同类问题(`spec_need_hidden_states`、`DeepSeekV2 _can_dual_stream_graph`),sweep 工具跳过参数名绑定。 · 已解决

entrypoints 层应保持实例作用域 设计

realtime `session.py` 的 `served_model_name` 与 `grpc_bridge.py` 的 LoRA enablement 被改为读全局 bag,但多引擎共进程时 bag 是 last-publish-wins,会拒绝本引擎的合法更新或隐藏本引擎已注册的 LoRA adapter。

结论:作者确认属实,将 `entrypoints/` 目录整体排除 sweep:realtime 读回 `self.server_args.served_model_name`,grpc bridge 改读 handle 的 `tokenizer_manager.server_args.enable_lora`。 · 已解决

Ray DP controller 的实例配置竞争 正确性

`data_parallel_controller.py` 的 `update_active_ranks` 改用 `get_exec().moe.elastic_ep_backend`,但多个 Ray Engine 共进程时 `get_exec()` 反映最后发布的引擎,可能用后引擎的配置处理前引擎的 `ActiveRanksOutput`,绕过 `dp_active` mask。

结论:Codex 建议保留 `self.server_args` 检查;可见评论中未见作者明确回复,属于未决疑虑。 · unresolved

风险与影响

技术风险集中在 4 个方面:

  • 机械替换的语义等价性:跨 160 文件的 AST 替换存在误改写风险,review 中已实际发现 4 处(allocation_sizing.py 参数回填、realtime/session.pygrpc_bridge.py、Ray actor publish)。即使作者审计过默认回填模式,仍可能在未覆盖的边界(如 if x is None 赋值模式之外的参数绑定)残留同类问题。
  • 多引擎 / 多 runner 的 last-publish-wins 竞争data_parallel_controller.pyget_exec() 读取在多个 Ray Engine 共进程时反映的是最后发布的引擎配置,可能绕过 dp_active mask 改变 inactive worker 槽位,该点未见最终修复确认。
  • kv-cache dtype 契约变更的兼容性get_model().kv_cache_dtype 从"解析后值"变为"原始意图",任何未随本 PR 迁移的第三方读取者或遗留代码会拿到 raw 值;_record_kv_cache_dtype 删除后,draft 场景的 dtype 选择完全依赖 model_runner.kv_cache_dtype_str 在 backend 初始化前被正确设置。
  • 栈内依赖与独立合入风险:Ray publish 修复落在 #33012,若本 PR 单独合入而栈未齐,Ray 部署路径会回归;约 15 个单测保持 module-skip,由后续 PR 恢复,当前 CI 无法覆盖这部分行为。

影响范围:

  • 对用户:功能语义不变,但 Ray 部署、speculative decoding(adaptive 切换)、多模态 / 多引擎共进程等路径存在启动期回归风险;kv-cache dtype 在 draft / target 不同配置下的选择行为被修正(预期内变化)。
  • 对系统:配置读取从"全局启动快照"走向"作用域化命名空间",为多引擎、多 runner、PD 分离等场景铺路;server_args 成为纯 pristine 记录,所有运行时解析值通过 bag 访问器读取。
  • 对团队:影响最大——新增代码必须遵循"读者走命名空间访问器、写者走 get_context().override"的新约定,且 per-runner / per-instance 字段的边界已有明确规则沉淀;writer ratchet 将这种约束固化到 CI。
核心配置路径变更 跨 160 文件机械替换 多引擎 last-publish-wins 竞争 曾有 revert 历史 Ray 启动路径需栈配合

关联 Issue

#32251 fix(attention): read per-runner kv cache dtype off model_runner

完整报告

参与讨论