Prhub

#34263 config: the last runner-side instance reads read the bags

原始 PR 作者 ch-wan 合并时间 2026-08-15 15:37 文件变更 16 提交数 1 评论 5 代码增减 +202 / -44

执行摘要

runner 侧最后 6 处 server_args 直读迁移到配置 bags

配置系统正在从“启动记录 server_args 直读”迁移到“已发布 bags + override 感知 accessor”:解析管线结束后,业务代码若仍直接读启动记录,post-publish override 将无法生效。PR body 逐条列出了 6 处读取各自存在的原因,其中 DraftBackendFactory 通过 getattr(self.server_args, backend_name) 按运行时计算的名字读取 split backend,再手动回退 base 字段,正是配置普查(config census)文档记录过的盲点。PR 的目标是把非 tokenizer-manager 族的 self.server_args.X 读取压缩到只剩文档化的 5 处(encode server 自身记录、nixl connector 的 rank 运算、GrammarManager 的 handed instance)。

值得精读。该 PR 是“把派生配置值从启动记录安全迁到 bags”的范本:单 bag 派生值抽 *_of(cfg) helper 并让 ServerArgs 委托、跨 bag 派生值采用双定义 + 分层一致性测试、以及用 attention_backends() 配对索引替代 getattr 运行时名字读取,都是可复用的设计决策。建议结合 #34269 的 bag 契约测试与 #34264 的 attention backend 配对迁移一起阅读,能看到同一模式的演进脉络。

讨论亮点

该 PR 的 review 全部由作者自查完成(COMMENTED 状态),核心讨论围绕 3 个“diff 外问题”与 1 个文档 nit:

  • compute_post_capture_kv_resize 双 reserve 分叉风险(已修复):review body 指出 kv_pool_runtime.py 仍调 model_runner.server_args.pre_capture_activation_reserve_mb(...),而配置器已改 bag 版本,post-publish override 会让预 profile 松弛量与 capture 后 headroom 分叉。作者在 issue comment 中确认折叠进本 PR,两处现在共用 bag-backed 版本。
  • modelexpress_transport_of 文档言过其实(已修复):inline nit 指出 docstring 声称“ServerArgs 属性与 post-publish accessor 都走同一个 parse”,但 ServerArgs.modelexpress_transport 仍读实例缓存的 _parsed_modelexpress_config。作者随后改写 docstring,使其只声称“transfer-engine 门控的共享解析”,并保留 seed 侧缓存。
  • 死字段/停放记录清理(已修复)RemoteInstanceWeightTransporter 保留了无人读取的 server_args 字段与构造 kwarg,DraftBackendFactory 停放了一份不再查询的记录;本 PR 删除字段与参数,并同步更新 4 个调用点。
  • 流程反思:作者坦承这 3 个 diff 外问题搁置一天的原因是 review sweep 只扫 inline comments、未读 review body 的 “Issues outside the diff” 章节——“那是 channel miss,不是 disagreement”,并承诺将该渠道纳入后续 sweep。

实现拆解

实现分 5 步完成,整体遵循“先建共享 helper,再补 bag accessor,最后迁移消费点并清理死数据”的顺序:

  1. 共享 *_of helper 落地python/sglang/srt/arg_groups/overrides.py,+37 行):新增 modelexpress_transport_of(cfg),统一 modelexpress_config(JSON 字符串 / dict / None)的解析并默认 "nixl";新增 remote_instance_transfer_engine_of(cfg, load_format),先短路 start_seed_via_transfer_engine,再按 (load_format or cfg.load_format) == "remote_instance" 失败关闭,最后看 backend 是否为 transfer_engine 或 modelexpress + transport 组合。ServerArgs.modelexpress_transport 保留自己的实例缓存解析(_parsed_modelexpress_config),但 ServerArgs.remote_instance_weight_loader_use_transfer_engine 改为委托该 helper(server_args.py 的 16 行改动)。

  2. runtime_context.py 新增两个 bag accessor(+47 行):remote_instance_transfer_engine_enabled(load_format) 是单 bag(model 叶子)派生值,直接委托 remote_instance_transfer_engine_of(get_model(), load_format)pre_capture_activation_reserve_mb(gpu_mem) 是跨 disagg / schedule / exec.graph / spec 四个 bag 加并行规模的派生值,无法单点共享,因此采用 mamba 式的“双定义”——ServerArgs 保留 publish 前版本,runtime_context 提供 publish 后版本,由分层一致性测试钉死相等。

  3. 消费点迁移scheduler.pyprocess_input_requests 改读 get_mm().mm_feature_transportbase_spec_worker.py_build_hicache_draft_plan 改读 get_memory().enable_hierarchical_cachedraft_utils.py_create_backend 改用 attention_backends() 配对按名字索引(草稿 runner 自身 stamp 仍优先);model_runner.pymaybe_init_remote_instance_transfer_engineremote_instance_weight_transporter.pymaybe_register_and_publish_weight_info 改调 bag accessor;kv_pool_runtime.pycompute_post_capture_kv_resizekv_cache_configurator.py 统一走 bag 版 pre_capture_activation_reserve_mb

  4. 删除死数据RemoteInstanceWeightTransporter 去掉无人读取的 server_args dataclass 字段与构造 kwarg;DraftBackendFactory 去掉停放的 server_args 记录和构造参数,4 个调用点(eagle_worker_v2.pymulti_layer_eagle_worker_v2.py、两个 attention-unittest kit 的 runner)同步移除传参。

  5. 测试配套test/registered/unit/test_runtime_context.py 扩展 _FakeResolvedArgs(新增 model / disagg / schedule / exec.graph / parallel 叶子),新增 test_activation_reserve_matches_the_member(7 组配置 × 3 档 gpu_mem 子测试)与 test_remote_instance_transfer_engine_matches_the_member(3 backend × 3 transport × 2 load_format × 2 seed 标志 × 3 override 全组合);test_gpu_feature_transport.py 的调度器 MM 传输测试从 SimpleNamespace 伪造 server_args 改为 _publish 发布真实 bag 后再断言。全栈验证:8 分区 CPU 测试 6758 例 / 332 bad,相对栈基 6752 / 332 零新增失败;GLM-4.7-Flash + Qwen3-Next GDN 端到端与基线 byte-identical。

文件 模块 状态 重要度
python/sglang/srt/runtime_context.py 配置上下文 modified 7.55
python/sglang/srt/arg_groups/overrides.py 覆盖注册表 modified 7.43
test/registered/unit/test_runtime_context.py 配置测试 modified 6.59
python/sglang/srt/speculative/draft_utils.py 草稿后端 modified 6.45
python/sglang/srt/model_executor/model_runner_components/remote_instance_weight_transporter.py 权重传输 modified 6.18
python/sglang/srt/server_args.py 启动配置 modified 6.09
python/sglang/srt/model_executor/model_runner.py 模型运行 modified 5.94
python/sglang/srt/speculative/base_spec_worker.py 推测工作器 modified 5.39
python/sglang/srt/model_executor/model_runner_components/kv_pool_runtime.py 缓存池 modified 5.33
test/registered/unit/multimodal/test_gpu_feature_transport.py 特性传输 modified 5.25
python/sglang/srt/managers/scheduler.py 调度器 modified 5.17
python/sglang/srt/mem_cache/kv_cache_configurator.py 缓存配置 modified 4.7
python/sglang/srt/speculative/eagle_worker_v2.py 推测工作器 modified 4.3
python/sglang/srt/speculative/multi_layer_eagle_worker_v2.py 推测工作器 modified 4.3
python/sglang/test/kits/attention_unittest/runner_modes/speculative_draft_extend_runner.py 测试工具 modified 3.18
python/sglang/test/kits/attention_unittest/runner_modes/speculative_draft_runner.py 测试工具 modified 3.18

关键符号

remote_instance_transfer_engine_enabled pre_capture_activation_reserve_mb modelexpress_transport_of remote_instance_transfer_engine_of DraftBackendFactory._create_backend ServerArgs.remote_instance_weight_loader_use_transfer_engine compute_post_capture_kv_resize maybe_register_and_publish_weight_info maybe_init_remote_instance_transfer_engine test_activation_reserve_matches_the_member test_remote_instance_transfer_engine_matches_the_member

关键源码片段

python/sglang/srt/arg_groups/overrides.py core-logic

新增 modelexpress_transport_of 与 remote_instance_transfer_engine_of 两个共享 helper,成为 transfer-engine 门控的唯一事实来源:ServerArgs member 委托它,runtime_context accessor 也委托它。

def modelexpress_transport_of(cfg: Any) -> str:
    """解析 config 形状对象请求的 modelexpress transport。    modelexpress_config 是 JSON 字符串(或已解析的 dict),不是独立叶子;
    这里是 transfer-engine 门控与未来 bag 读取方的共享解析。
    ServerArgs.modelexpress_transport 保留自己的实例级缓存解析
    (_parsed_modelexpress_config),两侧规则一致但缓存归属 seed 侧。
    """
    raw = cfg.modelexpress_config
    if raw is None:
        parsed = {}
    elif isinstance(raw, str):
        parsed = json.loads(raw)
    else:
        parsed = raw
    return parsed.get("transport", "nixl")
​
​
def remote_instance_transfer_engine_of(cfg: Any, load_format: Any = None) -> bool:
    """远程实例权重加载是否走 transfer engine(publish 前/后共用)。    load_format 优先于 config 内的值:草稿 runner 在
    --speculative-draft-load-format 下需要自己的 transfer engine 判定。
    所有输入都是 model 叶子,因此同时服务 ServerArgs member 与 bag accessor。
    """
    # seed 阶段显式要求走 transfer engine,直接短路
    if cfg.remote_instance_weight_loader_start_seed_via_transfer_engine:
        return True
    # 非 remote_instance 加载格式,失败关闭
    if (load_format or cfg.load_format) != "remote_instance":
        return False
    backend = cfg.remote_instance_weight_loader_backend
    # transfer_engine 后端直接放行;modelexpress 后端需进一步看 transport 字段
    return backend == "transfer_engine" or (
        backend == "modelexpress"
        and modelexpress_transport_of(cfg) == "transfer_engine"
    )
test/registered/unit/test_runtime_context.py test-coverage

扩展 _FakeResolvedArgs 并新增两个分层一致性测试,钉死 ServerArgs member 与 bag accessor 在输入矩阵上完全相等——这是双定义模式能长期安全的前提。

def test_activation_reserve_matches_the_member(self):
    # 验证跨 bag 派生值:publish 前(ServerArgs member)与 publish 后
    # (runtime_context accessor)必须在整个输入矩阵上相等,
    # 否则解析管线前后的决策会分叉
    from types import SimpleNamespace
​
    from sglang.srt.runtime_context import pre_capture_activation_reserve_mb
​
    graph = SimpleNamespace(decode=SimpleNamespace(max_bs=64))
    cases = (
        dict(disaggregation_mode="null", chunked_prefill_size=8192),
        dict(disaggregation_mode="null", chunked_prefill_size=-1),
        dict(disaggregation_mode="null", chunked_prefill_size=-1, max_prefill_tokens=1024),
        dict(disaggregation_mode="decode", max_running_requests=32),
        dict(disaggregation_mode="decode", max_running_requests=None),
        # decode 分离 + 草稿 token 参与 activation 估算的分支
        dict(disaggregation_mode="decode", max_running_requests=None, speculative_num_draft_tokens=4),
        dict(disaggregation_mode="null", chunked_prefill_size=8192, tp_size=8, pp_size=2),
    )
    for case in cases:
        for gpu_mem in (None, 20 * 1024, 80 * 1024):
            with self.subTest(gpu_mem=gpu_mem, **case):
                args = _FakeResolvedArgs(cuda_graph_config=graph, **case)
                get_context().set_server_args(args)
                self.assertEqual(
                    ServerArgs.pre_capture_activation_reserve_mb(args, gpu_mem),
                    pre_capture_activation_reserve_mb(gpu_mem),
                )
python/sglang/srt/speculative/draft_utils.py dependency-wiring

DraftBackendFactory 不再持有 server_args:_create_backend 用 attention_backends() 配对索引替代 getattr 运行时名字读取,这是配置普查盲点的直接修复。

class DraftBackendFactory:
    def __init__(
        self,
        draft_model_runner,
        topk: int,
        speculative_num_steps: int,
        seed_dsa_topk_from_draft_extend: bool = False,
    ):
        # 不再持有 server_args:后端选择改从已发布 bags 读取
        self.draft_model_runner = draft_model_runner
        self.topk = topk
        self.speculative_num_steps = speculative_num_steps
        self.seed_dsa_topk_from_draft_extend = seed_dsa_topk_from_draft_extend
        # 草稿 runner 自己的后端优先,而不是进程级配置
        self.draft_attn_backend = draft_model_runner.draft_attention_backend
​
    def _create_backend(
        self, backend_name: str, backend_map: dict, error_template: str
    ):
        # 旧实现用 getattr(self.server_args, backend_name) 按运行时名字取值,
        # 再手动回退 base 字段;attention_backends() 返回的 pair 已带
        # base 回退,这里按名字索引即得到相同语义,且跟随 post-publish override。
        # 工厂只会收到 prefill_attention_backend / decode_attention_backend 两个名字。
        prefill_backend, decode_backend = attention_backends()
        configured = (
            decode_backend
            if backend_name == "decode_attention_backend"
            else prefill_backend
        )
        backend_type = self.draft_attn_backend or configured
​
        if backend_type not in backend_map:
            raise ValueError(error_template.format(backend_type=backend_type))
​
        return backend_map[backend_type]()

评论区精华

modelexpress_transport_of 文档字符串言过其实 documentation

作者自查 nit:docstring 声称“ServerArgs 属性与 post-publish accessor 都走同一个 parse”,但实际 ServerArgs.modelexpress_transport 仍读实例缓存的 _parsed_modelexpress_config,且当时没有专门的 post-publish accessor,只有 remote_instance_transfer_engine_of 调用该 helper。

结论:docstring 改写为“transfer-engine 门控与未来 bag 读取方的共享解析”,ServerArgs 保留 seed 侧实例缓存;二次 re-review 确认表述与实现一致。 · 已解决

compute_post_capture_kv_resize 的 reserve 与配置器分叉风险 正确性

review body 指出 kv_pool_runtime.py 的 compute_post_capture_kv_resize 仍调 model_runner.server_args.pre_capture_activation_reserve_mb,而配置器已改 bag 版本;post-publish override(如 speculative_num_draft_tokens、max_running_requests、chunked_prefill_size)会让预 profile 松弛量与 capture 后 headroom 两个 reserve 分叉,违背 accessor 文档声称的 override 跟随语义。

结论:作者将三个 diff 外问题全部折叠进本 PR:compute_post_capture_kv_resize 改为调用 bag-backed pre_capture_activation_reserve_mb,两处 reserve 同源。 · 已解决

RemoteInstanceWeightTransporter 死字段与 DraftBackendFactory 停放记录 设计

review body 指出搬运器保留了无人读取的 server_args 字段与构造 kwarg,DraftBackendFactory 也停放了一份不再查询的记录;这两处是本次迁移引入的“孤儿”,应随 seam 一起删除。

结论:字段、构造 kwarg、构造参数全部删除,4 个调用点(两个 eagle worker + 两个 attention-unittest kit)同步去参;后续 re-review 确认无残留。 · 已解决

review sweep 渠道遗漏的流程反思 other

作者说明三个 diff 外问题搁置一天的原因:review sweep 只读 inline comments,未读 review body 的 “Issues outside the diff” 章节——这是渠道 miss 而非技术分歧。

结论:作者承诺将 review body 的 diff 外章节纳入后续 sweep 范围,并在此后的 re-review 中逐条闭环。 · 已解决

风险与影响

  1. DraftBackendFactory._create_backend 语义依赖命名约定:新实现按 backend_name == "decode_attention_backend" 决定取 decode 还是 prefill,review 确认工厂只会被传入这两个名字;若未来新增第三个 backend 名,会静默落到 prefill 分支。
  2. pre_capture_activation_reserve_mb 双定义需长期同步ServerArgs 与 runtime_context 各持一份实现,任何一方新增输入叶子而未同步测试矩阵,分层一致性测试都可能漏检。
  3. modelexpress_transport_of 每次调用重新 json.loads:相比 ServerArgs._parsed_modelexpress_config 的实例缓存多一次解析,但仅出现在转移引擎门控与测试路径,热路径影响可忽略。
  4. 语义变化面:所有迁移点现在跟随 post-publish override,若下游有代码隐性依赖启动记录不变量,行为会变;端到端 byte-identical 验证覆盖了 GLM-4.7-Flash 与 Qwen3-Next GDN 两条当前路径,可基本排除回归,但未来新 override 场景需注意。

影响面覆盖调度器 MM 传输门控、推测解码草稿后端选择、远程实例权重传输引擎初始化、KV 池 resize 预留等核心路径,涉及 16 个文件(+202/-44)。行为上从“读启动记录”变为“读已发布 bags”,语义应与现有路径等价,且后随 post-publish override——这是后续 6 个栈成员的配置边界基础。非 tokenizer-manager 族 self.server_args.X 读取由 11 处降至 5 处。无外部 API / 协议变更,对用户无感知;对团队而言,*_of helper + 双定义 + 分层测试成为后续配置迁移的可复用范式。

核心路径变更 配置语义跟随 override 双定义需测试钉住 跨模块调用点联动

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论