config: the resolution callbacks into the record go to zero - #36972
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9dc0fcde79
ℹ️ 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".
| return True | ||
|
|
||
|
|
||
| def cutedsl_moe_max_num_tokens(server_args: Any) -> int: |
There was a problem hiding this comment.
Update the remaining CuTeDSL budget caller
When Qwen3.5 FlashInfer MNNVL CuTeDSL fusion prepares its workspace, resolve_max_m() still calls server_args.cutedsl_moe_max_num_tokens() in qwen35_flashinfer_fusion.py:40. This commit removes that ServerArgs method and only introduces this free function, so a real ServerArgs instance raises AttributeError during prepare_qwen35_flashinfer_fusion() before CUDA-graph capture. Update that caller to invoke the new helper.
Useful? React with 👍 / 👎.
92e4892 to
4c96ae3
Compare
9dc0fcd to
e961cff
Compare
4c96ae3 to
0f598b2
Compare
5f745be to
71dc8ec
Compare
0f598b2 to
829770e
Compare
fce9c77 to
93788d5
Compare
Twenty-four members of `ServerArgs` were still called back into from
`arg_groups/` -- 96 call sites, 503 lines. Fourteen of them have no other
caller at all: not elsewhere in `srt`, not in a test. They are resolution-time
helpers that ended up on the record for historical reasons, and they move to
the family that calls them.
`generate_{decode,prefill,cpu}_*_batch_sizes` and
`apply_cuda_graph_disaggregation_roles` join `cuda_graph_hook`;
`reserve_for_graph_mb`, `reserve_for_deepep_a2a_mb` and
`adjust_mem_fraction_for_vlm` join `memory_hook`; the two dispatch-token
budgets join `moe_hook`; `is_mistral_native_format` joins `model_path_hook`;
`is_attention_backend_not_set` and `get_default_attn_backend` join
`overrides`. The two `_set_default_dsa_*` members were one-line forwards to a
post-process pass, like the ninety-three slots #36792 cut, and are now that
call. `ServerArgs` goes from 5277 to 4896 lines.
`is_attention_backend_not_set` takes the view rather than the record. Every
read in it is a view read, three of its call sites hold a view rather than a
record, and nine of the other ten already had one in scope.
`get_default_attn_backend` needs both overlays, so it keeps the record; the one
call site that has only a view reaches the record through `record_of`.
Two things this makes visible:
- `get_default_attn_backend` read `tp_size` off the record -- raw input, not
what resolution decided. `test_no_hook_reads_a_field_off_the_record` walks
`arg_groups/` and could not see it while it was a method on the class. It
reads through the view now.
- `hisparse_hook._is_hip` imported `is_hip` *from `server_args`*, so that the
tests would have a stable patch target. That made a configuration module the
home of a platform probe. It asks `utils.common` now, and the eight patches
name `hisparse_hook._is_hip`.
Twelve `is_attention_backend_not_set` accessor stubs on fake records are
retired. Four of them recomputed the predicate from the fake's own fields --
exactly what the function now does -- and the rest are replaced by the field
values that make it true or false. A stubbed accessor stops intercepting the
moment its member moves, so it was hiding what these fixtures actually said.
The 62-shape resolution probe is byte-identical to the base commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`_resolved`, `_declare` and `_late_resolution` were one-line forwards to `arg_groups.overrides`: `resolved_view`, `declare_resolution` and `declare_late_resolution`. Sixty-one call sites went through them -- a hook handed the record, then calling back into it to reach a function in its own package. Every call site now names the function. `ServerArgs` keeps no declaration member, and the record is one step closer to holding only what the operator typed. The scanners that pin the declaration channels follow. Each declarer is called by bare name now, so their node-type guards say `ast.Name`; `_DECLARERS` drops the member spelling. One assertion changes meaning rather than shape: the late channel used to be invisible to the keyword scan, because `server_args._late_resolution(...)` was not one of the declarer names, and the census asserted it added fields the keyword scan missed. The two spellings have converged, so it is now a subset by construction -- the census says that instead, and its own floor is what pins it. Forty call sites were *not* rewritten. Three test classes define their own `_resolved` / `_declare` helper, and `self._resolved(model_path, **kwargs)` in a `TestCase` is that helper, not the record's seam. A rewrite keyed on the attribute name alone takes both; the receiver has to be checked against what the enclosing class defines. The 62-shape resolution probe is byte-identical to the base commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… leave the record
`_resolved_attention_backends` and `get_attention_backends` were two names for
one computation -- `attention_backends_of(resolved_view(record))` -- with
fifteen call sites between them. They are gone; every site names the function.
`use_mla_backend` and `should_report_expert_balancedness` become free functions
in `arg_groups.overrides`, where the rest of the resolution vocabulary lives.
`ServerArgs` goes from 4868 to 4844 lines and answers 25 fewer callbacks.
The interesting part is the seams, not the move.
`use_mla_backend` and `attention_backends_of` were bound at module scope in six
and six modules. A `from ... import` name is a *copy*, so a test that patched
the source module reached none of them, and one that patched a single hook
reached only that one. Both are imported at their call sites now, so
`patch("...overrides.use_mla_backend")` is the one seam, and the twelve tests
that patched `ServerArgs.use_mla_backend` name it.
Three shapes the mover could not see, each caught by a different check:
- `post_capture_kv_sizing_planned` read `self.use_mla_backend` as a *value*,
not a call, because `ModelRunner` overwrites the name with a bool on the
record. An AST scan for calls does not see that. The probe did.
- `kv_cache_hook` had `use_mla_backend = server_args.use_mla_backend()`, so the
rewrite made the local shadow the function it now calls. The local is
`uses_mla`.
- `ModelRunner`, `kv_cache_configurator` and the attention registry carry their
own `use_mla_backend` attribute -- forty-odd references that must NOT move.
Six fixtures stop stubbing the accessors and supply what the functions read: a
model configuration whose `attention_arch` is or is not MLA, and the three
attention-backend fields. Two patch the function at its module instead.
The 62-shape resolution probe is byte-identical to the base commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…cord
`get_model_config` was the last big callback: fifty-six call sites reached into
`ServerArgs` for it, and it is the one member that memoises -- `_model_config`
plus `_model_config_built_from`, the path the memo was filled at.
It becomes `arg_groups.overrides.model_config_of(record)`. The memo stays on the
record, alongside `_resolved_overrides` and `_cuda_graph_config_locked`: it is
resolution scratch, it has to survive the pickle to a child, and there is no
per-record store anywhere else at resolution time. What leaves is the callback.
`model_config_of` takes a view as readily as the record. Ten of the call sites
are override providers that hold a `ResolvedView`, and a view is a read overlay
of exactly one record -- unwrapping it in the function beats making ten callers
reach back through it.
The imports are per call site, so `patch("...overrides.model_config_of")` is one
seam rather than twelve module-level copies.
Eighteen fixtures stop stubbing the accessor and seed the memo instead --
`_model_config=<config>` rather than `get_model_config=lambda: <config>`. That
is the affordance the memo already documented ("a configuration a fixture
supplied carries no key and is handed back as it is"), so the fixtures now use
the mechanism instead of standing in front of it. Two cases could not: one
wanted `None` as the answer, which as a memo means "not built yet", so it
patches the function; two seeded configurations made a `patch.object` redundant
and it is gone.
Three guards scanned for a `.get_model_config()` attribute call to find where
the configuration is first built -- the pin that keeps resolution-decided fields
ahead of the build. They match a bare-name call now.
The 62-shape resolution probe is byte-identical to the base commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`post_capture_kv_sizing_planned`, `cutedsl_moe_max_num_tokens` and `max_prefill_buffer_tokens` were the last members `arg_groups/` called back into. Each is a pure function of the resolving view -- no memo, no state -- and each already had a published sibling in `runtime_context` for the readers that run after publish. Only the pre-publish half was still a method. `mamba_cache_chunk_size` and `max_speculative_num_draft_tokens` follow them. Both are the same shape, and both were missed by the first census because a property read is an attribute access, not a call. Their published siblings stay where they are; the post-publish readers in `pool_configurator` and `tokenspeed_mla_backend` switch to those siblings, which is where they belonged. They all join the resolution vocabulary in `arg_groups.overrides`. `flashinfer_autotune` was on the wrong side of publish and is corrected rather than moved: it already asks `get_disagg()`, so the pre-publish helper would have missed a post-publish override of `chunked_prefill_size` or `max_prefill_tokens` -- which is what the pair of spellings exists to prevent. Its test patches the two module bindings the way it already patches `get_disagg`. `post_capture_kv_sizing_planned` now calls `use_mla_backend` unconditionally. It used to prefer `getattr(server_args, "use_mla_backend", None)` on the theory that `ModelRunner` writes that name onto the record; it writes it onto the *runner*, so the branch never fired in production -- and on a fixture still carrying the old stub, `bool(callable)` was True without calling anything. `is_post_capture_kv_active` keeps the resolution spelling: unlike the others, this one has no published sibling to ask. `server_args_variant` validated its keyword names against `ServerArgs` members, which let `use_mla_backend` through only because it was a method. It is not a member -- the runner computes it on itself and the attention kits copy that bool onto the record -- so the validator names that case explicitly. `arg_groups/` now touches **nothing** on `ServerArgs`: no method call and no property read. The record is 4663 lines and 27 `def` members here, down from 5656 and 59 on main; the two balancedness predicates that remain leave in the next PR, which is what takes it to 4653 and 25. The 62-shape resolution probe is byte-identical to the base commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
93788d5 to
8c0c14f
Compare
…ect#36972) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ect#36972) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ect#36972) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The series
Stacked, each PR based on the one above it. Read them in order; every boundary is
self-sufficient and green on its own.
main, not for mergeMotivation
After #36792 the resolution pipeline lived in
arg_groups/, but it still reached back into therecord for a great deal of what it needed. A census of the package found 96 call sites across 24
ServerArgsmembers, 503 lines — a hook handed the record, then calling a method on it to reacha function that belongs in its own package. That first census counted method calls; a property
read is an attribute access, so two of them were invisible to it and are included here.
Splitting on "what is this member for" gives two very different shapes:
srt, not in a test. 352lines of resolution-time helper that ended up on the record for historical reasons.
get_model_configis 27 lines with 56call sites. There the work is the callers, not the code.
This PR takes both to zero.
Modifications
Five steps, one per commit.
The resolution-only helpers move to the family that calls them.
generate_{decode,prefill,cpu}_*_batch_sizesandapply_cuda_graph_disaggregation_rolesjoincuda_graph_hook;reserve_for_graph_mb,reserve_for_deepep_a2a_mbandadjust_mem_fraction_for_vlmjoinmemory_hook; the two dispatch-token budgets joinmoe_hook;is_mistral_native_formatjoinsmodel_path_hook;is_attention_backend_not_setandget_default_attn_backendjoinoverrides. The two_set_default_dsa_*members were one-lineforwards to a post-process pass and are now that call.
The declaration seams stop being members.
_resolved,_declareand_late_resolutionwereone-line forwards to
resolved_view,declare_resolutionanddeclare_late_resolution; 61 callsites went through them. Every call site names the function.
The attention-backend pair is one function.
_resolved_attention_backendsandget_attention_backendswere two names forattention_backends_of(resolved_view(record)).use_mla_backendandshould_report_expert_balancednessbecome free functions.The model configuration is built by a function.
get_model_configbecomesoverrides.model_config_of. The memo stays on the record beside_resolved_overridesand_cuda_graph_config_locked— it is resolution scratch, it has to survive the pickle to a child,and there is no per-record store anywhere else at resolution time. What leaves is the callback.
The last three computed reads follow.
post_capture_kv_sizing_planned,cutedsl_moe_max_num_tokensandmax_prefill_buffer_tokensare pure functions of the resolvingview, and each already had a published sibling in
runtime_contextfor readers that run afterpublish. Only the pre-publish half was still a method.
And the last two properties.
mamba_cache_chunk_sizeandmax_speculative_num_draft_tokensare the same shape — a derived value with a published sibling — and were only missed because a
property read is not a call. Lifting them is what makes the census claim true for reads as well as
calls; the post-publish readers in
pool_configuratorandtokenspeed_mla_backendmove to theruntime_contextsiblings, which is where they belonged.arg_groups/now touches nothing onServerArgs— no method call and no property read. Therecord goes from 5277 to 4653 lines and from 54 methods to 25.
model_hookandmoe_hookalso pick up the import hoist that PR 5 does everywhere else: the twolifted properties join the same module-scope import block, and splitting the two changes by hunk
would leave an intermediate commit that does not compile.
What this makes visible
Two defects that only a package-side guard can see, because both were invisible while the code was
a method on the class:
get_default_attn_backendreadtp_sizeoff the record — raw input, not what resolutiondecided.
test_no_hook_reads_a_field_off_the_recordwalksarg_groups/and now reports it. Itreads through the view.
hisparse_hook._is_hipimportedis_hipfromserver_args, so the tests would have a stablepatch target. That made a configuration module the home of a platform probe. It asks
utils.commonnow.And one thing about test seams worth stating, because it decides where every future patch goes.
use_mla_backendandattention_backends_ofwere bound at module scope in six modules each. Afrom ... importname is a copy, so patching the source module reached none of them, andpatching one hook reached only that one — which is why the tests patched
ServerArgs.use_mla_backend, the class attribute, instead. Both are imported at their call sitesnow, so
patch("...overrides.use_mla_backend")is one seam.Three things the move made visible in the runtime:
flashinfer_autotunereads the published bags. It is unambiguously a post-publish reader —it already asks
get_disagg()two lines down — so the pre-publish helper would have missed apost-publish override of
chunked_prefill_sizeormax_prefill_tokens, which is the wholereason the pair of spellings exists. Its test patches the two module bindings, the way it already
patches
get_disagg.post_capture_kv_sizing_plannedcallsuse_mla_backendunconditionally. It used to prefergetattr(server_args, "use_mla_backend", None)on the theory thatModelRunnerwrites that nameonto the record; it writes it onto the runner (
model_runner.py:370), so the branch never firedin production — and on a fixture still carrying the old stub,
bool(callable)wasTruewithoutcalling anything.
is_post_capture_kv_activekeeps the resolution spelling on purpose. Unlike the other twocorrected sites it has no published sibling to ask, so the pre-publish function is the only
existing spelling — worth stating, because the pattern elsewhere is the opposite.
server_args_variantvalidated its keyword names againstServerArgsmembers, which letuse_mla_backendthrough only because it was a method. It is not a member — the runner computesit on itself and the attention kits copy that bool onto the record they hand in — so the
validator names that case explicitly rather than relying on a method to exist.
Thirty accessor stubs on fake records are retired. Where a fixture stubbed
is_attention_backend_not_set, it now carries the three backend fields the predicate reads; whereit stubbed
get_model_config, it seeds_model_config, which is the affordance the memo alreadydocumented. A stubbed accessor stops intercepting the moment its member moves, so these were
hiding what the fixtures actually said.
Accuracy Tests
The 62-shape resolution probe is byte-identical to the base commit at every one of the five
commits, and again after the review round above. The registered config suites report the same failure set as
main, file for file (thedifferential is run per commit, not only at the tip).
Speed Tests and Profiling
None. A moved function is the same code one frame over.
Checklist
Review and Merge Process
The largest of the four, and the one to read commit by commit — each is a single kind of move and
the boundaries are green on their own. The two defects above and the module-scope-copy seam are
the parts that are not mechanical.
CI States
Latest PR Test (Base): 🚫 Run #33249798874
Latest PR Test (Extra): 🚫 Run #33249798768
Latest PR Test (AMD ROCm 7.2): ❌ Run #33249798866