Skip to content

config: the runtime readers take the published bags - #36254

Merged
ch-wan merged 9 commits into
mainfrom
cheng/gc-p5-readers-take-bags
Aug 26, 2026
Merged

ch-wan merged 9 commits into
mainfrom
cheng/gc-p5-readers-take-bags

Conversation

@ch-wan

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

Copy link
Copy Markdown
Collaborator

Motivation

These are the readers that run after the config is published and still read the record.
Each one is a place where a value the user did not set — one that resolution decided — is
read from the wrong side. Two of them were user-visible:

  • The Ray driver sized its placement groups from a mix of both surfaces; one line read
    get_parallel().config.pp_size * server_args.tp_size. A launch that lets resolution
    decide dp_size computed the group size from None.
  • The embedding plan and /server_info reported cuda_graph_config = None for a server
    that was in fact running one, because the plan read the record instead of the resolved
    configuration.

What changes

Readers move to the published bags: the CP strategy binder now takes the three values it
needs (enable_prefill_cp, cp_size, cp_strategy) rather than a record; the embedding
plan reports the resolved configuration; the encoder, HiCache and metrics readers, the HTTP
entry (serving and observability), the last tp_size readers, and all 22 reads in the
Ray driver (ray/engine.py, ray/data_parallel_controller.py) take the bags.
_compute_world_size() no longer takes an argument.

Two process-init seeders change signature with them. initialize_moe_config was handed a
record it read resolution's answers off (moe_a2a_backend, deepep_mode, quantization,
the speculative pair); it takes no argument now and reads exec.moe / spec / model,
which every caller has published by the time it calls — the scheduler, the weight-cache
daemon and the one_batch work function. initialize_fp4_gemm_config had already stopped
reading its server_args parameter and drops it.

No supplied-instance read stays: the encoder's LoadConfig was the last one, and the
exposure ratchet's pin set is empty as of this PR, so the next such read is a new entry
that has to argue for itself. configure_logger also keeps its
record read: it runs before publish in the launcher and in the encoder HTTP entry, it is
called with stand-ins, and the multimodal_gen tree calls it with a different, same-named
ServerArgs class that has no bags.

New coverage: test_ray_driver_reads_the_bags.py pins the world-size arithmetic against a
published config, checks that the driver follows a post-publish override (which is what
distinguishes a bag read from a record read), and asserts the two Ray files contain zero
record reads — on the pre-conversion tree that assertion reports all 22.

How to verify

export PYTHONPATH=$PWD/python
python -m pytest -q test/registered/unit/test_ray_driver_reads_the_bags.py
python -m pytest -q test/registered/unit/test_supplied_instance_exposure_ratchet.py
python -m pytest -q test/registered/unit/entrypoints/test_server_info.py
python -m pytest -q test/registered/cp/test_cp_strategy_unit.py

The Ray path itself has no CI coverage (test/manual/test_ray_engine.py starts a real
cluster). It was run against a real cluster with a cached model:
TestRayEngineOfflineTP1 + TestRayEngineOfflineTP2 = 5 passed;
TestRayEnginePlacementGroup serves and shuts down, then the process dies in Ray's GCS
teardown timeout — origin/main dies the same way.

🤖 Generated with Claude Code


CI States

Latest PR Test (Base): ❌ Run #32966792220
Latest PR Test (Extra): ✅ Run #32966967988
Latest PR Test (AMD ROCm 7.2): ❌ Run #32966792281

@ch-wan
ch-wan force-pushed the cheng/gc-p5-readers-take-bags branch 2 times, most recently from fa26c5e to e267df3 Compare August 25, 2026 03:56
@ch-wan
ch-wan force-pushed the cheng/gc-p4-resolution-declarations branch from faac6ba to f17b3c4 Compare August 26, 2026 05:39
@ch-wan
ch-wan force-pushed the cheng/gc-p5-readers-take-bags branch from e267df3 to dfdd18e Compare August 26, 2026 05:39

@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: dfdd18e5fa

ℹ️ 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".

if parsers.reasoning_parser == "auto" or parsers.tool_call_parser == "auto":
resolve_auto_parsers(server_args)

publish(server_args, role="tokenizer")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid publishing before the integrated engine validates LoRA

When SMG gRPC is launched with LoRA enabled, the integrated servicer subsequently enters Engine._launch_subprocesses, which calls check_server_args() again. The first check here has already normalized lora_paths into LoRARef objects, and this publish marks the same record as published; the second check then either rejects those objects as invalid path entries or attempts another _late_resolution, which explicitly refuses published configs. Consequently LoRA-enabled gRPC servers fail during startup; validation/parser resolution needs to happen only once, immediately before the engine's existing publish boundary.

Useful? React with 👍 / 👎.

@ch-wan
ch-wan force-pushed the cheng/gc-p4-resolution-declarations branch from f17b3c4 to f3f4dae Compare August 26, 2026 06:48
@ch-wan
ch-wan force-pushed the cheng/gc-p5-readers-take-bags branch 2 times, most recently from c770839 to 2b10765 Compare August 26, 2026 06:56

@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: 2b10765f8f

ℹ️ 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".

Comment on lines +1021 to +1023
server_args = server_args.replace_resolved(
"benchmark.one_batch", cuda_graph_config=explicit
)

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 typed CUDA graph config for resolved inputs

When a programmatic caller passes a ServerArgs that has already run resolve_once(), replace_resolved() preserves the resolved marker and merely appends this raw dictionary as the new declaration, so the following resolve_once() is a no-op. Publishing in latency_test() therefore installs a dict as get_exec().graph.cuda_graph_config, while model initialization immediately expects attributes such as .decode and .prefill, causing the one-batch benchmark to fail. Keep the replacement as a CudaGraphConfig for already-resolved records, or otherwise ensure the merged dictionary is parsed before publication.

Useful? React with 👍 / 👎.

@ch-wan
ch-wan force-pushed the cheng/gc-p4-resolution-declarations branch from f3f4dae to 71043dd Compare August 26, 2026 07:22
@ch-wan
ch-wan force-pushed the cheng/gc-p5-readers-take-bags branch from 2b10765 to 33b799a Compare August 26, 2026 07:22

@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: 33b799a43b

ℹ️ 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".

Comment thread python/sglang/benchmark/one_batch.py Outdated
# JSON (resolution applies that last), so the size is merged into that JSON.
# It travels as a copy: a field assignment is the shape the mutation ratchet
# pins at zero, and this record is unresolved, so the copy is a plain one.
if getattr(server_args, "_resolution_finished", False):

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 Check the actual resolved-state marker

When a programmatic caller has already called resolve_once(), this branch still never runs because ServerArgs records completion in _declarations_materialized, not _resolution_finished (the latter appears nowhere else in the repository). The fallback consequently supplies a plain dict to replace_resolved() while preserving the resolved state, so the subsequent resolve_once() is a no-op and publication exposes an untyped CUDA graph config to model initialization. Fresh evidence beyond the existing comment is that the attempted fix checks a nonexistent marker; use the real marker or otherwise preserve a CudaGraphConfig for resolved inputs.

Useful? React with 👍 / 👎.

@ch-wan
ch-wan force-pushed the cheng/gc-p4-resolution-declarations branch from 71043dd to 0eb2984 Compare August 26, 2026 07:32
@ch-wan
ch-wan force-pushed the cheng/gc-p5-readers-take-bags branch 2 times, most recently from 40bbed0 to de7d5ac Compare August 26, 2026 07:37
ch-wan added 9 commits August 26, 2026 12:05
`resolve_image_processor_backend` already had one caller passing `get_mm()` and
three passing a record; all four pass the bag now, and the parameter is named
for what it is. The FlashInfer all-reduce fusion resolver and the draft
attention-backend fallback read their leaves directly and take no config at all
-- the fusion one was reaching for `get_server_args()`, which is a global record
read that only escaped the ratchet because it handed the whole object to a
helper. `reserve_rope_cache_for_long_sequences` reads `model.context_length` and
the two `spec` counts.

The FlashInfer fusion test drives `_resolve_backend(backend, is_multi_node)`
directly: the arch dispatch is what those cases are about, and the entry above
it now takes no arguments.

Five (file, field) pairs leave the supplied-instance exposure pin.
`init_cp_strategy(server_args)` was called from two places that cannot read the
same source: resolution calls it inside `__post_init__`, where the bags do not
exist yet, and `get_cp_strategy` calls it lazily in a worker process, where the
record is not where the resolved sizes live -- that path was reaching for
`get_server_args()` and handing the whole object over, which is how a global
record read escapes the ratchet.

It takes `enable_prefill_cp`, `cp_size` and `cp_strategy` now. Resolution passes
them off its view, the lazy path off `get_parallel().config`, and the unit tests
pass them directly instead of building a `SimpleNamespace` per case.
`resolved_embedding_plan` is the `/server_info` and gRPC readback of the
embedding runtime knobs, and it read `cuda_graph_config`,
`chunked_prefill_size`, `disable_radix_cache`, `is_embedding` and
`prefill_only_disable_kv_cache` off the record -- which now holds the raw input,
so the plan would have reported `None` for the graph config of a server running
one.

The two callers pass `resolving_view(record)`, and the parameter is `config`
rather than `server_args`: the function's contract is "something that answers
with the resolved configuration", which is why it was duck-typed to begin with.

Five (file, field) pairs leave the exposure pin, which is down to four -- the
launcher's pre-publish env setup and the auto-parser late resolution, both of
which read the record because that is the only thing that exists at those points.
All four read the record because the record is the only thing that exists where
they run: the NCCL environment setup and the auto-parser late resolution both
happen in the launcher before the publish. Written down next to the pin so the
next person does not have to re-derive it, and so a new entry has to come with
the same kind of reason.
The biggest remaining clusters of "read a field off a handed record", all of
them past their process entry's publish:

  * the encoder's five modules read `get_parallel().config.tp_size` and
    `get_serving().host` / `.port` -- `runtime.py` was already mixing
    `get_parallel().config.dp_size` with `server_args.tp_size` on one line;
  * `get_allocator_type()` reads the two HiCache leaves and takes no config,
    which drops the parameter from `_get_allocator_type` and its 14 call sites
    in the hybrid pool assembler;
  * the unified radix cache's write and prefetch policies, the detokenizer and
    gRPC metrics flags, the XPU and runner-backend memory-saver checks, the
    CP DSA split, the FP4 GEMM backend and the EP redundant-expert count.

Both exposure pins are now at their floor: four entries in `_EXPOSED` and three
in `_OVERRIDDEN_AND_READ`, all of them the launcher's pre-publish env setup and
the auto-parser late resolution.
`_setup_and_run_http_server` runs after `Engine._launch_subprocesses` has
published, so its host, port, log level and metrics flag come from
`get_serving()` / `get_observability()` -- 28 reads, including the two
`enable_metrics` gates in the app setup.

The DP controller keeps its record reads: its declared namespace set is
`{exec, parallel, device, disagg}`, so reading `serving` or `observability`
there would be refused under `SGLANG_ROLE_NAMESPACES=enforce`. Narrowing that
set was the point of declaring it, and widening it to move a `host` read is the
wrong trade.
Six sites, split by what they are actually asking. The dual-chunk attention
backend shards over the *live* group, so it reads `get_parallel().tp_size` like
the rest of the head-count arithmetic in the tree. The runner windows, the
`/v1/loads` accelerator count, the NIXL rank arithmetic and the tokenizer's
worker division all want the launch width in a process that holds no model
groups, so they read `get_parallel().config.tp_size` and are registered with
that reason.
`RayEngine` publishes as part of `Engine._launch_subprocesses` and lays the
actors out afterwards, so all 22 record reads in the two driver modules were
reading the raw input where the bag was already available -- and both files were
already mixing the two, `_compute_world_size` multiplying
`get_parallel().config.pp_size` by `server_args.tp_size` on one line. A launch
that leaves `dp_size` to resolution would have sized the placement group from
`None`.

Both modules are at zero record reads now, with a local `parallel =
get_parallel().config` where a function reads several. `_compute_world_size`
takes no argument. The four new configured-size reads are registered with their
reason: the driver is sizing the actors that will hold the process groups, so
there is nothing live to ask.

The Ray path has no CI coverage (`test/manual/test_ray_engine.py` boots a real
cluster), so `test_ray_driver_reads_the_bags` pins it three ways: the world-size
arithmetic against a published config, the same arithmetic following a
post-publish `override` -- which is what separates a bag read from a record read
-- and a file-scoped check that neither module reads a field off an instance. It
reports all 22 reads on the pre-conversion tree.

Verified against a real cluster with a cached model: `TestRayEngineOfflineTP1`
and `TestRayEngineOfflineTP2` pass (5 tests), and the custom-placement-group case
launches, serves and shuts down -- it dies afterwards on a Ray GCS teardown
timeout that `origin/main` hits identically.
Nine reads in eight files, each already mixing bag reads with a record read:
the KV configurator's memory-saver flag, the dist-init host in `bootstrap`, the
gRPC and sidecar hosts, the rust server's transport width and hosts, the
in-process HTTP engine adapter, and the MindSpore runner's host.

`initialize_moe_config` and `initialize_fp4_gemm_config` go with them, and they
change signature: both were handed a record they read resolution's answers off
(`moe_a2a_backend`, `deepep_mode`, `quantization`, the speculative pair) or, in
the fp4 case, no longer read at all. They take no argument now and read
`exec.moe` / `spec` / `model` / `exec.kernel`, which every caller has published
by the time it calls -- the scheduler, the weight-cache daemon, and the
`one_batch` work function. The five `layers/moe/utils.py` exposure pins go with
the conversion, and the three tests that used to hand it a stand-in publish one.

`configure_logger` keeps its record read, and this is the reason: it runs before
the publish in the launcher and in the encoder HTTP entry, it is called with
stand-ins, and `multimodal_gen` calls it with a *different* `ServerArgs` class
that has no bags at all. A bag read there would raise on three separate paths.

What is left of the 172 resolution-named instance reads this clearing started
from is 39, in five places, all structural: the launcher before its publish
(19), the DP controller whose declared namespace set excludes `serving` and
`observability` (14), the auto-parser late resolution (4), the multimodal
processor's per-instance `base_gpu_id`/`tp_size` (engines sharing a process each
have their own), and `configure_logger`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation hicache Hierarchical Caching for SGLang ready-to-merge The PR is ready to merge after the CI is green. unified-radix-cache

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant