test(step-12): state the bag contract as what resolution produced, and the skill rule that goes with it
`test_bag_values_match_server_args` asserted `bag == field`. That holds today only because construction resolves in place; step 12 keeps the record raw, and the plan doc calls this test out as one that becomes **false by design** for every field resolution fills in. Rewritten against the resolved projection, which is the half that survives: the bag carries what resolution produced. The `bag == field` assertion stays as one line at the end, labelled as the tripwire -- when it starts failing for a resolution-written leaf, the flip has landed and the bag is the only place the effective value lives. The reference is an independent resolution of the same raw input (a fresh, never-published record) rather than `resolved_server_args_dict()`, which reads `vars(server_args)` back and therefore only restates the published instance. And the record goes through the real pipeline on a real mini config, published through `publish()`: the dummy-model path returns at the dummy boundary with every sampled leaf still raw, so the old comparison was raw==raw and vacuous (both Codex catches). Reproducibility (#34094) licenses the sibling as a stand-in for the pipeline output. The sample admits only leaves resolution writes on this input on both CI device shapes (attention_backend, page_size, chunked_prefill_size, mem_fraction_static), and the raw-differs guard asserts it per leaf -- a default-count threshold let supplied inputs like `model_path` (no dataclass default, so any path "differs") stand in for resolution work. Passthrough leaves (host, hicache_ratio, moe_runner_backend, model_path) move to a separate projection smoke that claims only what it checks: publish projected an unchanged field into its namespace. Between the two resolutions the test restores environ and the EnvField none-flags, so the sibling resolves the same pristine input rather than the first resolution's leftovers. And the class runs its body exactly once, like the other dual-resolve harnesses: a CI retry re-enters after the first attempt leaked process state, which is the hazard the pristine snapshot exists to rule out. docs(skill): a supplied-instance read is not automatically safe The whole-object rule said "keep the supplied-instance contract; don't rewrite the parameter reads unless the field is runtime-mutated". That is the right rule for the *object* and the wrong stopping point for the *field*: after step 12 the record carries the user's raw input, so `server_args.page_size` inside a runner-owned constructor reads the CLI default rather than the effective value. The rule now names that second case as step-12 debt with a guard attached (`test_supplied_instance_exposure_ratchet.py` fails on a new pair, so the decision is made when the read is written), and names the two shapes that stay parameter-form on purpose: a helper the resolution pipeline calls with a `resolved_view`, and a factory whose contract is "build X from the record you are handed".
This commit is contained in:
@@ -99,8 +99,10 @@ bag to override at all.
|
||||
instance: **constructor arguments** (`ModelRunner(draft_attention_backend=...)`,
|
||||
`MMEncoder(gpu_id=...)`) and **runner attributes holding the resolved value**
|
||||
(`model_runner.kv_cache_dtype_str`, `prefill_attention_backend_str`,
|
||||
`num_fused_shared_experts`) — threaded to consumers as arguments, never
|
||||
backfilled onto a shared object. The one sanctioned bend in that rule is
|
||||
`num_fused_shared_experts`, `linear_attn_backends`) — threaded to consumers as
|
||||
arguments, never backfilled onto a shared object. A per-runner choice also stays
|
||||
*out* of the bags: recording it there is how a second runner inherits the first
|
||||
one's answer, which is exactly the bug `linear_attn_backends` replaced. The one sanctioned bend in that rule is
|
||||
*scoped*: `ModelRunner._load_format_scope` exposes the draft's
|
||||
`--speculative-draft-load-format` through `get_model().override(load_format=...)`
|
||||
for exactly the duration of the draft build, because model construction
|
||||
@@ -116,9 +118,28 @@ bag to override at all.
|
||||
encode-server DP workers used to specialize a config copy for the same reason;
|
||||
their device now travels as `MMEncoder(gpu_id=...)`.)
|
||||
- **Whole-object passes** (`f(server_args)` handing the instance along) keep the
|
||||
supplied-instance contract; don't rewrite the parameter reads to bag reads unless the
|
||||
supplied-instance contract; don't rewrite the parameter reads unless the
|
||||
field is runtime-mutated (see the elastic-EP `ep_size` case in
|
||||
`eplb/expert_location.py`).
|
||||
`eplb/expert_location.py`) — **or the field is one that resolution fills in
|
||||
and the callee runs in a process that has published.** That second case is
|
||||
step-12 debt, not a style question: the record is destined to carry the
|
||||
user's raw input, so `server_args.page_size` inside a runner-owned
|
||||
constructor will read the raw pre-resolution value instead of the effective
|
||||
one. Debt means a decision, not automatically a bag read: pick where the
|
||||
value should come from — usually the `get_*()` bag, sometimes a runner stamp
|
||||
or a constructor argument (the per-mode attention pair and the encode-server
|
||||
`gpu_id` above are dispositions of exactly this debt). And the per-instance
|
||||
boundaries above stay exempt from this unless-clause: a multi-Engine site
|
||||
must not become a process-global bag read even for a resolution-filled
|
||||
field. `test_supplied_instance_exposure_ratchet.py`
|
||||
pins the remaining set — three spellings of the read: `server_args.field`,
|
||||
literal-name `getattr(server_args, "field", default)`, and the parked form
|
||||
(`self.x = server_args` in a method that takes the parameter, read as
|
||||
`self.x.field` anywhere in the class) — and fails on a new one, so the
|
||||
disposition gets picked when the read is written. Two shapes stay parameter-form on purpose: a helper the
|
||||
*resolution pipeline* calls with a `resolved_view` (its parameter happens to be
|
||||
named `server_args`), and a factory whose contract is "build X from the record
|
||||
you are handed" (`create_kt_config_from_server_args`, `DllmConfig.from_server_args`).
|
||||
|
||||
### `get_parallel()`: config leaves vs live topology
|
||||
|
||||
|
||||
Reference in New Issue
Block a user