# PR #36790 完整报告

- 仓库：`sgl-project/sglang`
- 标题：config: the derived parallel widths are computed from the leaves
- 合并时间：2026-08-29 01:18
- 原文链接：http://prhub.com.cn/sgl-project/sglang/pull/36790

---

# 执行摘要

- 一句话：派生并行宽度统一为叶子推导加落印，修正 get_parallel 读取
- 推荐动作：值得精读。PR body 对两个设计决策的论证非常清晰：为什么 " 落印而不是每次读取现算 "（弹性 EP 下公式失效），以及为什么 world_size 排除在落印集合之外（live getter 在每个时刻都正确）。建议关注 Codex 未解决的两个 P2（reset_context 与 snapshot_context 对 _derived 的处理），并在后续 PR 中补齐。

# 功能与动机

PR body 指出 get_parallel() 的统一规则被六个名字破坏："get_parallel() 的回答方式应该是：操作符配置的内容来自已发布的 parallel bag，只有进程组建立后才存在的内容来自 groups。但有六个名字违反了这条规则——它们都不是 parallel 叶子，而是叶子们的商，且没有 flag 直接设置它们，此前却是从刚刚由这些叶子构建的 group coordinator 上读回的 "。这使得 get_parallel().tp_size 抛 ValueError 而 get_parallel().attn_tp_size 抛 group getter 的裸 AssertionError，调用点无法区分。更关键的是弹性 EP：scale 时 ep_size / dp_size 会在已发布 bag 上被改写而 coordinators 保留构造宽度，每次读取现算公式会得到缩水甚至为 0 的宽度，因此必须 " 组构建时落印 "。

# 实现拆解

1. **集中派生算术 **（python/sglang/srt/runtime_context.py）：新增模块级函数 derive_attention_widths 与 derive_parallel_widths，把六个派生宽度的商计算收敛到一处。attn_dp_size 直接透传（已是有效宽度，DP 注意力关闭时为 1），避免二次消费 enable_dp_attention 开关；DCP 关闭时 attn_dcp_size 取 1 而非 0，保证除法与比较安全。

2. **ParallelContext 增加落印机制 **（runtime_context.py）：__slots__新增 _derived，并新增 stamp_derived_widths、clear_derived_widths、_derived_width 三个方法。attn_tp_size 等六个属性从 _v（override 或 live getter）改为 _derived_width（override → stamp → live group），fallback 分支保证未经过 initialize_model_parallel 安装组的进程仍能工作，两者都缺失时给出指明缺失方的命名错误。

3. **组构建与销毁同步落印 / 清印 **（python/sglang/srt/distributed/parallel_state.py）：initialize_model_parallel 先用 derive_parallel_widths 算出字典，attn_tp_size 与 moe_tp_size 均取自该字典构建 _ATTN_CP、_MOE_TP 等组，最后 stamp_derived_widths(**derived_widths) 一次落印；destroy_model_parallel 调用 clear_derived_widths 清印。

4. **DP 注意力路径联动 **（python/sglang/srt/layers/dp_attention.py）：initialize_dp_attention 直接从 compute_dp_attention_world_info 取回 attn_dp_size 并落印，删除此前重复的两行赋值；update_dp_attention_post_scale 在弹性 EP 切换 WORLD 时重落印 attn_dp_size；compute_dp_attention_world_info 复用 derive_attention_widths，只保留逐进程的 rank 计算（rank 不属于落印集合）；disable_dp_size 改为用 get_parallel().override(attn_dp_size=1) 包裹，使 scope 内两种拼法一致。

5. **测试配套 **（test/registered/unit/test_runtime_context.py）：新增 TestDerivedWidths，覆盖商值来自叶子、world_size 不落印、stamp 胜过 group、override 胜过 stamp、无 stamp 时 live group 兜底、双缺失时错误命名原因、rank helper 与 stamp 在三种拓扑上一致，并断言 parallel_state.py 与 dp_attention.py 不再携带第二份商算术副本。PR body 报告 159 个涉及 record 的注册测试与 base 失败集一致，24-shape 解析 dump 逐字段相同。

关键文件：
- `python/sglang/srt/runtime_context.py`（模块 运行时上下文；类别 source；类型 core-logic；符号 derive_attention_widths, derive_parallel_widths, stamp_derived_widths, clear_derived_widths）: 变更核心：新增 derive_attention_widths / derive_parallel_widths 集中全部派生算术，ParallelContext 增加 _derived 落印机制与 _derived_width 读取链，六个派生宽度属性从 group getter 改为落印读取。
- `python/sglang/srt/layers/dp_attention.py`（模块 注意力并行；类别 source；类型 core-logic；符号 update_dp_attention_post_scale, compute_dp_attention_world_info, initialize_dp_attention, disable_dp_size）: DP 注意力宽度与落印机制的联动点：初始化时落印 attn_dp_size，弹性 EP scale 后重落印，compute_dp_attention_world_info 复用 derive_attention_widths 消除第二份算术副本，disable_dp_size 用 override 保证 scope 内读取一致。
- `python/sglang/srt/distributed/parallel_state.py`（模块 并行状态；类别 source；类型 core-logic；符号 initialize_model_parallel, destroy_model_parallel）: 组构建入口与销毁出口：initialize_model_parallel 依据 derive_parallel_widths 构建组并统一落印，destroy_model_parallel 清印，保证宽度与组构建时刻永不漂移。
- `test/registered/unit/test_runtime_context.py`（模块 单元测试；类别 test；类型 test-coverage；符号 TestDerivedWidths, test_the_quotients_come_from_the_leaves, test_the_world_size_is_not_stamped, test_a_stamped_width_is_what_the_reader_answers_with）: 新增 TestDerivedWidths 测试类（+167 行），钉死商值推导、stamp/override/fallback 优先级、world_size 不落印、双缺失命名错误等七项契约，并验证 rank helper 与落印值在三种拓扑上一致。

关键符号：derive_attention_widths, derive_parallel_widths, stamp_derived_widths, clear_derived_widths, _derived_width, initialize_model_parallel, destroy_model_parallel, update_dp_attention_post_scale, compute_dp_attention_world_info, initialize_dp_attention, disable_dp_size

## 关键源码片段

### `python/sglang/srt/runtime_context.py`

变更核心：新增 derive_attention_widths / derive_parallel_widths 集中全部派生算术，ParallelContext 增加 _derived 落印机制与 _derived_width 读取链，六个派生宽度属性从 group getter 改为落印读取。

```python
# runtime_context.py：派生宽度的单一算术来源。
# 这些名字没有任何 flag 直接设置，都是叶子配置的商；
# 此前从刚构建的 group coordinator 上读回，现在改为
# 组构建时由 derive_parallel_widths 落印（stamp）。
def derive_attention_widths(
    *, tp_size: int, attn_cp_size: int, dp_size: int, enable_dp_attention: bool
) -> tuple:
    """返回 (attn_dp_size, attn_tp_size)，由叶子推导。"""
    attn_dp_size = dp_size if enable_dp_attention else 1
    return attn_dp_size, tp_size // attn_dp_size // attn_cp_size


def derive_parallel_widths(
    *,
    tp_size: int,
    attn_cp_size: int,
    attn_dp_size: int,
    moe_ep_size: int,
    moe_dp_size: int,
    dcp_size: int,
    dcp_enabled: bool,
) -> dict:
    """全部派生宽度，供组构建与落印共用。"""
    return {
        # attn_dp_size 已是有效宽度（DP 注意力关闭时为 1），
        # 直接透传；这里不再消费 enable_dp_attention 开关，
        # 否则调用方传入原始 dp_size 会得到 tp/dp/cp 而非 tp/1/cp。
        "attn_dp_size": attn_dp_size,
        "attn_tp_size": derive_attention_widths(
            tp_size=tp_size,
            attn_cp_size=attn_cp_size,
            dp_size=attn_dp_size,
            enable_dp_attention=True,
        )[1],
        "moe_ep_size": moe_ep_size,
        "moe_tp_size": tp_size // moe_ep_size // moe_dp_size,
        "dcp_enabled": dcp_enabled,
        # DCP 关闭时取 1 而不是 0，保证除法与比较安全。
        "attn_dcp_size": dcp_size if dcp_enabled else 1,
    }


class ParallelContext:
    __slots__ = ("_overrides", "_config", "_derived")

    def stamp_derived_widths(self, **widths) -> None:
        """组按这些宽度构建完成后记录；读取方以落印为准。"""
        self._derived.update(widths)

    def clear_derived_widths(self) -> None:
        self._derived.clear()

    def _derived_width(self, name, getter):
        # 读取顺序固定：override -> stamp -> live group。
        # 弹性 EP 会在已发布 bag 上改写 dp_size / ep_size，
        # 但 coordinator 保留构造宽度，每次现算公式会得到
        # 缩水甚至为 0 的宽度；落印保证读到的就是组构建时的宽度。
        overrides = self._overrides
        if name in overrides:
            return overrides[name]
        if name in self._derived:
            return self._derived[name]
        # 兜底：未经过 initialize_model_parallel 安装组的进程仍可工作；
        # 两者都不存在时（组未初始化会由 getter 抛出断言），
        # 读路径会转化为指明缺失方的命名错误而非裸断言。
        return getter()

```

### `python/sglang/srt/layers/dp_attention.py`

DP 注意力宽度与落印机制的联动点：初始化时落印 attn_dp_size，弹性 EP scale 后重落印，compute_dp_attention_world_info 复用 derive_attention_widths 消除第二份算术副本，disable_dp_size 用 override 保证 scope 内读取一致。

```python
# dp_attention.py：弹性 EP 后重落印 + DP 禁用 scope 的一致性。
def update_dp_attention_post_scale(new_dp_size: int, new_dp_rank: int):
    global _ATTN_DP_SIZE, _ATTN_DP_RANK
    _ATTN_DP_SIZE = new_dp_size
    _ATTN_DP_RANK = new_dp_rank
    # scale-up 后 coordinators 仍保持构造宽度，必须同步重落印，
    # 否则 get_parallel().attn_dp_size 会回答旧宽度。
    get_parallel().stamp_derived_widths(attn_dp_size=new_dp_size)
    get_flags().dp.use_world_group_for_gather = True
    logger.debug(
        "[Elastic EP] dp_attention switched to WORLD: dp_size=%d dp_rank=%d",
        new_dp_size,
        new_dp_rank,
    )


@contextmanager
def disable_dp_size():
    """DP 注意力在 scope 内被禁用。

    供 speculative decoding 的 draft worker 使用：draft 模型与
    target 模型以不同宽度运行。这里同时替换模块级全局量与
    runtime context 的派生宽度，确保同一个名字的两种拼写
    （get_attention_dp_size 与 get_parallel().attn_dp_size）
    在 scope 内不会产生分歧。
    """
    global _ATTN_DP_SIZE
    assert _ATTN_DP_SIZE is not None, "dp attention not initialized!"
    old_dp_size = _ATTN_DP_SIZE
    _ATTN_DP_SIZE = 1
    try:
        # override 优先于 stamp，scope 内读取方恒为 1。
        with get_parallel().override(attn_dp_size=1):
            yield
    finally:
        _ATTN_DP_SIZE = old_dp_size

```

# 评论区精华

四条 review 评论均来自 chatgpt-codex-connector[bot]，全部标注为 P2。其中三条针对早期 commit，一条涉及 world_size 落印的设计边界：Codex 建议不要落印局部 world_size（否则 scale joiner 会丢失 expanded WORLD 大小），作者在 PR body 中已论证 world_size 刻意不纳入落印集合——它不是叶子的商，live getter 在每个时刻都正确，并有 test_the_world_size_is_not_stamped 钉死该行为。disable_dp_size 相关的评论在 head 版本中已解决（改用 override 包裹）。reset_context 未清理 _derived 与 snapshot_context 按引用快照 _derived 两条则未在 PR 内处理。

- scale joiner 的 world_size 是否应保留 expanded WORLD (design): 作者在 PR body 中已论证 world_size 刻意不纳入落印集合：它不是叶子的商，live getter 在每个时刻都正确（try_admit_scale_ranks 扩展 WORLD 后、scale-joiner 以 tp * pp 布局而 WORLD 为 ep_join_rank_offset + tp * pp 时），并有 test_the_world_size_is_not_stamped 钉死该行为。
- reset_context 未清理 _derived 可能导致跨生命周期状态泄漏 (correctness): 未在 PR 内处理；建议后续在 reset_context 中同步调用 clear_derived_widths。
- disable_dp_size scope 内 attn_dp_size 应反映禁用状态 (correctness): head 版本已解决：disable_dp_size 改为 with get_parallel().override(attn_dp_size=1) 包裹，override 优先于 stamp，scope 内两种拼法一致。
- snapshot/restore 对 _derived 按引用快照导致 restore 不还原 (correctness): 未在 PR 内处理；建议快照时拷贝 _derived 字典（与其它可变上下文状态一致）。

# 风险与影响

- 风险：
 1. **核心路径变更**：initialize_model_parallel / destroy_model_parallel 是分布式初始化必经路径，本 PR 在其中增加落印 / 清印，任何组构建流程改动都可能影响 stamp 一致性。
 2. **异常语义变化**：派生宽度未初始化时的错误从 group getter 的裸 AssertionError 变为命名错误，依赖旧异常类型的调用方需要适配。
 3. **生命周期状态残留**：reset_context 不清理 _derived、snapshot_context 对 _derived 按引用快照（Codex P2 未解决），测试隔离或 restore 场景可能读到过期拓扑宽度。
 4. **弹性 EP 同步面**：目前只有 update_dp_attention_post_scale 重落印 attn_dp_size，未来若新增其他 scale 路径而漏掉重落印，会读到旧宽度；moe_ep_size 改为 stamp 后，若调度器需要 " 实时 ep_size" 需改用其他 API。
 5. **回归证据**：PR 自报 159 个注册测试失败集与 base 一致、24-shape 解析 dump 逐字段相同，正常路径风险较低。
 - 影响：对普通推理用户无行为感知；对系统而言，所有经 get_parallel() 读取派生宽度的代码路径从 " 依赖组存在时刻 " 变为 " 可提前且一致读取 "，弹性 EP scale 后读取更稳定，speculative decoding 的 disable_dp_size scope 保持两种拼法一致。对团队而言，本 PR 确立了 " 叶子发布 + 商派生 + 组构建时落印 " 的配置读取模式，与 config 系列重构（PR 36791、36792）同一演进方向，后续新增宽度类配置时有明确范本。
 - 风险标记：核心路径变更 , 弹性 EP 宽度读取语义变化 , 生命周期状态清理缺失 , 异常语义变化

# 关联脉络

- PR #36792 config: the forwarding slots go; the dispatcher calls the family directly: 同属 config 系列重构，都在收敛配置命名空间的发布与读取语义（ServerArgs 转发槽位移除、调度器直连钩子函数），与本 PR 同一演进方向。
- PR #36791 config: three cache and pool readers take the bags: 同属 config bag 重构系列，统一配置读取器的单一数据来源，与本 PR 在 " 配置命名如何被读取 " 上脉络一致。