Skip to content

config: a parallel size has one spelling; a patched scope declares its own - #36621

Merged
ch-wan merged 1 commit into
mainfrom
cheng/gc-p4
Aug 27, 2026
Merged

config: a parallel size has one spelling; a patched scope declares its own#36621
ch-wan merged 1 commit into
mainfrom
cheng/gc-p4

Conversation

@ch-wan

@ch-wan ch-wan commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Motivation

PR 4 of a five-PR series on top of the raw-input ServerArgs work (#36250#36255), based on f775db03aaa. Each builds on the previous one; review them in order.

  1. cheng/gc-p1 — config: resolution declares, and nothing writes a field
  2. cheng/gc-p2 — config: every handler declares its cuda-graph decisions
  3. cheng/gc-p3 — config: a parallel leaf with no live counterpart is read bare
  4. cheng/gc-p4 — config: a parallel size has one spelling; a patched scope declares its own ← this PR
  5. cheng/gc-p5 — config: the record is not an object that gets passed around

They are grouped by how they have to be read, not by topic: PR 3 is 118 files of one mechanical rewrite, reviewed by checking the rule and sampling; PR 4 is the design change that rewrite made possible, and its production files each need reading.

CI for the whole series runs on a separate vehicle PR, whose branch sits one placeholder commit above PR 5: #36623.

After #36620, get_parallel().config served only five names — the ones that were
both a live ParallelContext property and a parallel config leaf: tp_size,
pp_size, attn_cp_size, dcp_size, moe_dp_size. Bare meant "ask the
process group", .config meant "what the launch asked for", and every reader
had to know which one it wanted.

This deletes the distinction. get_parallel().tp_size answers from the
published configuration, and so do the other four. get_parallel().config is
gone, and so are the five live properties.

Modifications

``055e31a5711 · 62 files (48 production, 14 test) · +480 / −1327

Why one spelling is enough

Because the groups are built at exactly the configured widths. Checked at every
assignment to the five module globals in parallel_state.py:

  • _TP, _PP — built from tensor_model_parallel_size / the stage count.
  • _ATTN_CP_ATTN_CP = _TP only under attn_cp_size == tp_size, where the
    widths are equal by the condition; otherwise built with attn_cp_size ranks.
  • _DCP — groups of decode_context_parallel_size, which is the configured
    dcp_size carried through the scheduler's parallel snapshot.
  • _MOE_DP_MOE_DP = _TP under moe_dp_size == tp_size (equal again), and
    _MOE_DP = _ATTN_CP under attn_cp_size > moe_dp_size. That last branch is
    the one place a live width differs from the configured one.

And because exactly one place replaces a group after construction.

The scope that replaces a group declares it

patch_tensor_parallel_group runs a scope under a different TP group — draft
workers of speculative decoding run the draft model at the target's attention-TP
width. It now overrides the three members that change:

    _TP = tp_group
    try:
        with get_parallel().override(
            tp_size=tp_group.world_size,
            tp_rank=tp_group.rank_in_group,
            tp_group=tp_group,
        ):
            yield

A reader inside the scope gets the patched width; outside it gets the launch's.
draft_tp_context goes back to one line.

A specific communicator's width is asked of that communicator

get_moe_cp_size() returns _get_moe_dp_group().world_size. That is the answer
for a reader that wants the group it will actually collectively operate on,
which is what the _MOE_DP = _ATTN_CP branch makes different from the
configuration.

What the flip does to existing readers

Every previously-bare read of the five names now answers the configuration. On
origin/main those are 246 tp_size, 51 attn_cp_size, 21 dcp_size, 6
pp_size and 7 moe_dp_size sites (AST census, including the
parallel = get_parallel() alias form). For the first four the value is
identical by the construction audit above. The seven moe_dp_size sites were
read one at a time:

site what it wanted outcome
qwen3_moe.py, mellum.py assert attn_cp_size % moe_dp_size == 0 was x % x == 0 under the alias; now compares real values. No launch changes: resolution already asserts moe_dp_size == 1 whenever the two differ, so it holds either way.
qwen2_moe.py assigns self.moe_dp_size and never reads it.
expert_pack_loader.py "ServerArgs invariants were not applied" the configuration is what it means. The divergent case cannot reach it: tp_size % attn_cp_size == 0, so attn_cp_size > 1 implies tp_size > 1, and the tp_size != 1 clause on the line above fires first.
ipc_loader.py the IPC weight-cache fingerprint changed to get_moe_cp_size(). attn_cp_size is not in CacheConfig, so collapsing this to the configured value lets two engines with different MoE sharding collide on one cache key.
loader.py "dp" in the shard config changed to get_moe_cp_size(). The same dict already carries the configured value under "moe_dp_size"; the live width was the point of the second entry.
dumper.py debug dump changed to get_moe_cp_size(). It sits next to moe_dp_rank, which is still live — a live rank beside a configured size misleads whoever is reading the dump.

What else moved

  • __getattr__ answers any parallel leaf from the published bag and raises the
    same ValueError("... not published") before publish, so the 32 leaves do not
    need 32 properties.
  • 99 .config.<size> reads collapse to bare names.
  • compute_world_size(config) becomes
    compute_world_size(*, enable_dp_attention, dp_size, tp_size, pp_size) — the
    Ray driver calls it before any group exists.
  • Two registered scheduler tests move off the old interfaces:
    compute_world_size is called with the four keywords, and the
    get_parallel() / get_exec() / get_context() stand-ins in
    test_scheduler_internal_state_world_size.py and
    ..._env_vars.py are replaced by a published context
    (override_server_args), which is what those tests were reaching for.
  • The sglang-runtime-context skill is rewritten where it documented the
    deleted API: the namespace table's parallel row, the dedicated
    get_parallel() section, the "which accessor answers this question" entry for
    a size, the guard inventory's _CONFIGURED_SIZE_CALL_SITES paragraph, and the
    dynamo note that called __getattr__ "error-only" (config: a parallel leaf with no live counterpart is read bare #36620 made it the read path,
    and measured that it traces under fullgraph=True).
  • _CONFIGURED_SIZE_CALL_SITES (48 entries) and its scanner leave
    test_global_config_read_ratchet.py (−460 lines), and
    test_launch_path_reads_configured_sizes.py is deleted (−424). Both recorded
    which sites needed the config tier; there is no tier left to choose.

Accuracy Tests

No model-output change: this series moves where a configuration value is read
from, not what resolution decides. The equivalent check for that claim is a
resolution dump — every field's resolved value for 24 launch shapes (plain, tp2,
tp4_pp2, dp2, EAGLE, NEXTN, page32, page64_chunk2k, cuda-graph knobs,
disaggregation, deterministic, hierarchical cache, symmetric memory, …) — taken
in both trees and compared field by field:

0 differences across 24 shapes × 478 shared fields, against f775db03aaa.

The one field the series has and the base does not is grpc_worker_threads: on
main it is a public non-field slot assigned in _handle_deprecated_args, and
this series makes it a declared field. Its value is 4 on both sides.

Every guard also runs at each commit of the series, not only at the head — a
PR that is green only on top of its successors is not reviewable on its own. The
set is the config guards plus every registered test the series touches, ~33 files
per boundary, all green.

No GPU accuracy run. Everything above is CPU-side: resolution, projection and the
guards. A launch-path change that only shows up with real process groups is not
covered by any of it.

Speed Tests and Profiling

No benchmark run, and none is expected to move: nothing here changes a kernel, a
schedule, or the shape of any batch. What changes is the source of a
configuration read — a published dataclass attribute instead of a process-group
getter or an accessor hop.

The one place that could have mattered is torch.compile: gate helpers read
parallel leaves inside compiled forwards, and object.__getattribute__
graph-breaks. That was measured rather than assumed — the reads this series
introduces trace under torch.compile(fullgraph=True), which is pinned by a
regression test.

Checklist

Review and Merge Process

  1. Ping Merge Oncalls to start the process. See the PR Merge Process.
  2. Get approvals from CODEOWNERS and other reviewers.
  3. Trigger CI tests with comments or contact authorized users to do so.
    • Common commands include /tag-and-rerun-ci, /tag-run-ci-label, /rerun-failed-ci
  4. After green CI and required approvals, ask Merge Oncalls or people with Write permission to merge the PR.

🤖 Generated with Claude Code


CI States

Latest PR Test (Base): 🚫 Run #33110887601
Latest PR Test (Extra): 🚫 Run #33110886999
Latest PR Test (AMD ROCm 7.2): ❌ Run #33110887336

@ch-wan

ch-wan commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 48f229f54c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1a4efe4398

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# tokenizer creates MmItemMemoryPool. A live attention subgroup
# size could leave acknowledgements missing and strand the lease.
ipc_consumer_count = max(get_parallel().config.tp_size, 1)
ipc_consumer_count = max(get_parallel().tp_size, 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the launch TP size for IPC acknowledgements

When Kimi image materialization runs inside patch_tensor_parallel_group with a smaller draft/attention TP group, that scope now overrides get_parallel().tp_size, so this passes the subgroup width instead of the tokenizer pool's full configured consumer count. In encoder-DP mode only the selected rank reconstructs each image, leaving the other acknowledgement slots unset and eventually preventing bounded IPC leases from being recycled. Preserve access to the launch TP width here; the same regression exists in kimi_k3.py.

Useful? React with 👍 / 👎.

Base automatically changed from cheng/gc-p3 to main August 27, 2026 19:56
…s own

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ch-wan
ch-wan merged commit fd40a33 into main Aug 27, 2026
6 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

diffusion SGLang Diffusion documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant