Skip to content

refactor(inference-optimizer): delete what the CLI session start, the multi-node restart and the KB field declaration re-checked or did twice - #2085

Merged
ZhengGong-amd merged 6 commits into
mainfrom
lean/shafeng/cc20-io-cli-pr
Oct 21, 2026
Merged

ZhengGong-amd merged 6 commits into
mainfrom
lean/shafeng/cc20-io-cli-pr

Conversation

@fengshaoyi-amd

Copy link
Copy Markdown
Contributor

What this does

Four functions were over the code-metrics gate at base, and none had a baseline entry, so the gate excused them as backlog: _resume_session (170 lines) and _seed_fresh_session (164) in cli/__init__.py, _restart_multinode (107) in multi_node/cli.py, and FieldDeclaration.__post_init__ (cognitive 32) in hyperloom_kb/schema.py. This PR brings each one under every limit, mostly by deleting what it did twice: status checks on a _short_poll result that is always SUCCEEDED, a second write of every restart key from a bag built for that one call, guards on values the function had already settled, and three type checks that FieldDeclaration.from_dict already makes. What is left of the two session functions splits along the jobs they ran in sequence.

Production code: +180 / −278 (net −98) across 3 files. Tests: none changed. The code-metrics baseline lowers one entry, module-lines of multi_node/cli.py (1,867 → 1,804), and gains none.

This continues the complexity cleanup of #2024, #2026, #2052, #2056 and #2057. The 6 commits are grouped by module and read one at a time; each commit message lists what it deleted and why that is safe.

Commit Module Production lines Baseline file
8c58162 drop the terminal-failure checks _short_poll already makes multi_node/cli.py +8 / −27
d1e78d1 write each restart key once in _restart_multinode multi_node/cli.py +21 / −68
46dbf24 split _resume_session into opening the session and opening its next leg cli/__init__.py +72 / −96
4e17daa split _seed_fresh_session into resolving the target GPU and creating the session cli/__init__.py +79 / −81
c5f41f6 lower the multi_node/cli.py module-lines baseline to 1804 baseline only 1 lowered
72ab28d delete the type re-checks FieldDeclaration repeats from from_dict hyperloom_kb/schema.py +0 / −6

cli/__init__.py and multi_node/cli.py are under src/hyperloom/inference_optimizer/; schema.py is under src/hyperloom_kb/.

Gate numbers per function

Measured with the gate's own collectors (ruff C901, complexipy, function lines), origin/main → this branch. Limits are 20, 30 and 80; bold values were over the limit at base.

Module Function Cyclomatic Cognitive Lines
cli/__init__.py _resume_session 14 → 3 19 → 5 170 → 48
_seed_fresh_session 16 → 7 27 → 11 164 → 69
multi_node/cli.py _restart_multinode 10 → 9 18 → 15 107 → 74
schema.py FieldDeclaration.__post_init__ 16 → 13 32 → 28 36 → 30

Also changed, never over at base (cyclomatic / cognitive / lines), all in multi_node/cli.py: _persist_multinode_restart 2 / 8 → 7 / 25 → 17; _resume_prior_launch 6 / 7 / 44 → 41; _start_pd_router 5 → 4 / 5 → 3 / 39 → 35; _submit_and_collect_pod_json 5 → 3 / 4 → 2 / 32 → 27; _restart_single_node 4 / 5 → 4 / 41; cmd_verify 3 / 2 → 1 / 36.

New functions, cyclomatic / cognitive / lines, all in cli/__init__.py: _open_resumed_session 7 / 6 / 50, _open_next_leg 6 / 7 / 44, _resolve_target_gpu 6 / 13 / 34, _create_fresh_session 2 / 1 / 55. Each takes one to three parameters.

Deleted: the _MultinodeLaunch dataclass in multi_node/cli.py.

Across the 3 touched production files: C901 total 743 → 737, cognitive total 1,025 → 1,008, functions 196 → 200. Functions over a gate limit: cognitive 2 → 1, length 9 → 6. What is left is outside this work: in cli/__init__.py, _run_optimize (279 lines, cognitive 43), _reexport_session_environment (111), _export_partition_shape (101), _resolve_run_topology (92) and _resolve_agent_lineup (89); in multi_node/cli.py, build_parser (266). Open PRs change _run_optimize (#1450, #1546, #2062), _reexport_session_environment (#1979, #2055) and _resolve_run_topology (#1924), so this PR leaves them alone.

Module lengths: cli/__init__.py 2,778 → 2,752, multi_node/cli.py 1,870 → 1,804, schema.py 1,014 → 1,008.

What changes in each module

Multi-node restart (multi_node/cli.py)

Removed:

  • The status re-checks that five _short_poll callers made on the job it returned: a fail-fast block in _restart_multinode, a "router job ended" exit in _start_pd_router, two EXIT_TRANSIENT returns in _submit_and_collect_pod_json, and a log print in _restart_single_node and in cmd_verify.
  • The _MultinodeLaunch dataclass and the second write of the restart keys it carried. _persist_multinode_restart(state, args, *, kill_sub, router_sub, router_state) now writes only what the checkpoint leaves until the poll returns: the kill submission and, for a disaggregated launch, the PD fields, the router submission and the leg URLs. It takes five parameters instead of three; the bag's other four fields (the PID and log directories, the launch submission and the topology record) were keys the checkpoint had already written.
  • The re-derivation of tp, ep, pd_mode and extra_args from args at each use. The checkpoint writes them from the topology record.
  • The prev_sub parameter of _resume_prior_launch, which now reads the prior submission id from the state it already takes.

Session start (cli/__init__.py)

New names:

  • _open_resumed_session: check that --resume-from names a session under the workspace root, pin it, take its lock, run the resume guard, and load its manifest once state.json is known to exist.
  • _open_next_leg: refuse a target_reached stop without --force-resume, then begin the leg, grant --extend-hours, record the model-gate skip and print the restored budget.
  • _resolve_target_gpu: settle args.gpu_type from the probe and the --gpu-type hint, export the Magpie runner, refuse a framework that runner cannot serve, and check that provenance can name the ISA.
  • _create_fresh_session: create and lock the session directory, then write its manifest, launch info, partition shape, GPU power settings and seeded state.

_resume_session now runs the first two in order, and _seed_fresh_session the last two.

Removed:

  • Resume: the __import__ hop to reach _SESSION_SKELETON; the hand-written containment check, now session.paths.is_path_within, which owns that rule; a one-element stop-reason set and a --force-resume re-test that could not fail; two (state.current_best or {}); and four hand-written "state value, else manifest value" copies, now one comprehension over the same keys in the same order.
  • Fresh session: a second args.gpu_type = None and a runner_gpu_type or gpu_type fallback for a board without a Magpie runner; the empty-model fallbacks in the launch info and the partition model; and three copies of "exit 2 if this preflight fails", now one condition whose or keeps the short circuit.
  • Comments that restated a docstring or narrated the next line, and a "no backfill needed" tombstone. Every comment that states a constraint moved with its code or into the docstring of the function that now owns it. The Langfuse marker's except Exception moved into _create_fresh_session unchanged; no new handler was added.

KB field declaration (hyperloom_kb/schema.py)

FieldDeclaration.__post_init__ no longer re-validates the three attributes FieldDeclaration.from_dict already converts or rejects before it constructs: kind as a FieldKind, role as a FieldRole, and required, sensitive, many and group as booleans. Every check on a value that from_dict passes through raw stays: the name, the description, the declared values, every combination rule, and the search weight. The weight is still normalized to a float, so a declaration with an integer weight keeps one schema_ref across a round trip.

Kept at base: sweep_case

sweep_case (src/kernelforge/evaluation/benchmark/runner.py:581) is over the parameter limit with seven keyword parameters and stays at seven; its function-args baseline entry is unchanged. Its callers:

  • Production, src/:
    • runner.py:707-716, the python3 -m kernelforge.evaluation.benchmark.runner CLI (_sweep_cli). It passes all seven, taking warmup_iters, bench_iters and timeout_sec from --warmup, --iters and --timeout (:694-696).
    • kernelforge/orchestrator/specialist_probe.py:394-400 and :450 (probe_variant, then _measure_on_device). They pass driver_script, case_id, constants, prefix_constants and timeout_sec. The probe resolves sweep_case by name (:41-43, :197-200), and it refuses a primitive that does not accept those five keywords (:45-51, :218-231).
  • Production, scripts/: none.
  • Tests only: kernelforge/tests/evaluation/benchmark/test_sweep_case.py:42-44, whose call sites pass case_id, constants, timeout_sec and prefix_constants; and the probe stand-ins in kernelforge/tests/test_specialist_probe.py:401, :414 and :740, which have the same five-keyword signature.

No parameter can be derived from another, and warmup_iters and bench_iters are the only two that the probe and the tests never pass. The only way to five parameters without bundling arguments therefore deletes the --warmup and --iters CLI flags, a CLI interface change this PR does not make. Passing the existing BenchRun in place of driver_script, warmup_iters and bench_iters would only bundle arguments, and the probe's keyword check requires driver_script by name.

Why the deletions are safe

Line numbers are on this branch.

  • Status re-checks after _short_poll. _short_poll (multi_node/cli.py:267-300) returns a job only when its status is in _TERMINAL_OK_STATUSES, which is {"SUCCEEDED"} (:60, returned at :290-291). FAILED and STOPPED raise RuntimeError (:292-293), and a timeout raises TransientFailure (:295-299), so no caller can see another status. A FAILED or STOPPED job's RuntimeError reaches the multi-node CLI's dispatcher handler, except (RuntimeError, ValueError) in main (multi_node/cli.py:1786); in-process, the orchestrator catches it around cmd_restart_server and cmd_apply_tracelens_patch (orchestrator/actions/executors/_multi_node_server_lifecycle.py:507, :400). So the job logs those deleted branches meant to print on a failure were never printed, before or after this PR. Printing them would be a behaviour change, which this refactor does not make.

  • Restart keys. The early checkpoint (multi_node/cli.py:1302-1318) writes the PID and log directories, the launch submission, the framework, the model, tp, ep, pd_mode, extra_args and the topology record before the poll. tp, ep, pd_mode and extra_args come from the record _rayjob_topology_fingerprint builds (:1120-1128), with the expressions the checkpoint used at base. _persist_multinode_restart (:1258-1274) runs after the poll and the PD router, and writes only the keys the checkpoint cannot have yet. _resume_prior_launch (:1174-1214) reads the prior submission id from state (:1187), the value the deleted parameter carried.

  • Resume. is_path_within (session/paths.py:220-226) is the containment rule the hand-written check copied. SharedState.from_dict always yields a dict for current_best (orchestrator/state/shared_state.py:1103-1104).

  • Fresh session. Only an empty GPU type reaches the branch for a board without a Magpie runner, and args.gpu_type is already None there. The function has exited on an empty model before it writes the launch info and the partition model, and the quantization prelude only replaces args.model with a Path (cli/quantization.py:80). The preflight condition (cli/__init__.py:2197-2202) runs the three checks in the order the three copies did and stops at the first that fails.

  • KB field declaration. FieldDeclaration.from_dict (schema.py:468-495) converts or rejects kind (:476), role (:481) and the four flags (:488-491) before it constructs. Every declaration that comes from outside Python code reaches the dataclass only through ExperienceDeclaration.from_dict (schema.py:700) and then FieldDeclaration.from_dict, which is the path for:

    • declaration YAML and JSON files (hyperloom_kb/config.py:35, also behind collect/mapping.py:162);
    • KB database rows (http_service.py:449);
    • HTTP PUT bodies (http_service.py:1262);
    • spool files (remote.py:506);
    • remote export responses (remote.py:567).

    In the repo, the only direct constructors are RATIONALE_DEFAULTS (schema.py:500-502) and tests, and they pass FieldKind and FieldRole members and bool literals.

Observable effect

Nothing an operator sees changes. For every input the repo's own callers produce, these are the same, in the same order:

  • stdout and stderr lines and exit codes;
  • state.json, the session manifest and the launch info;
  • the multi-node state file and the exported environment;
  • stored KB declarations, their to_dict output and their schema_ref. An invalid kind, role or flag in a declaration document still raises the same SchemaValidationError from from_dict, before anything is constructed.

Fake-driven differential runs compared base and head with no difference:

  • _restart_multinode: 12 scenarios (aggregated and PD, fresh, resumed RUNNING, resumed serving, router without leg URLs, poll timeout, --print-logs). Saved state snapshots, log lines and return codes match.
  • _resume_session: 13 scenarios (plain, target_reached with and without --force-resume, other stop reasons, --extend-hours, outside the workspace, missing directory, resume blocked, no manifest, no state.json, stale AgentX state, resume conflict, null current_best). Output, exit codes, call order and state.json match.
  • _seed_fresh_session: 20 scenarios (missing model, model from MODEL_PATH, local and remote probe, a probe that disagrees with the hint, no GPU, atom, framework from FRAMEWORK, quantized model, AgentX, --profile-osl, no framework version, each preflight failing, Langfuse failing, a family-runner board, R9700 with vLLM). Output, exit codes, call order, exports and args match.
  • FieldDeclaration: 1,456 cases, every combination of kind, role, flags, values and search weight built in Python, plus valid and invalid from_dict documents. Results and error messages match.

The differences below are reachable only by input that no caller in the repo produces:

  • restart-server with a tp that is not an int.
    • Before: last_restart_tp in the multi-node state ended as the raw value, because the persist step overwrote the checkpoint's int(args.tp).
    • Now: it stays int(args.tp).
    • Callers: the CLI parses --tp with type=int (multi_node/cli.py:1536). The in-process caller passes tp=tp_int (orchestrator/actions/executors/_multi_node_server_lifecycle.py:446). Tests pass tp=8.
  • A _short_poll that returns a job whose status is not SUCCEEDED. Only a test double can; the real one raises instead (multi_node/cli.py:288-299).
    • Before:
      • for FAILED or STOPPED, _restart_multinode and _start_pd_router printed the job logs, logged an ERROR line and failed with exit code 1, and cmd_verify and _restart_single_node printed the job logs;
      • for any status but SUCCEEDED, _submit_and_collect_pod_json returned EXIT_TRANSIENT.
    • Now: each continues as for a SUCCEEDED job, and prints logs only with --print-logs.
    • Callers: the only test that replaces _short_poll returns SUCCEEDED (inference_optimizer/tests/test_external_multi_node.py:941).
  • --resume-from under a workspace root whose resolve() raises OSError or RuntimeError.
    • Before: a traceback from the containment check.
    • Now: is_path_within answers no, so the operator gets the existing "is not under $USER_DATA_PATH" error and exit 2.
    • Callers: the root comes from the operator's $USER_DATA_PATH through workspace_root() (session/paths.py:85). No caller or test in the repo sets one that cannot be resolved.
  • A resolved GPU type that is not empty but is blank.
    • Before: args.gpu_type became None, and the custom-entrypoint check received the blank string.
    • Now: the blank string stays in args.gpu_type, and the check receives "".
    • Callers: the type is probed or user_specified (gpu_types.py:122). Each comes from a resolver that returns a known board tag or None: _resolve_amd_gpu_type (gpu_types.py:153-162), _autodetect_gpu_type (gpu_types.py:125-150) and remote_autodetect_gpu_type (multi_node/_internal/gpu_probe.py:56-70, through _parse_gpu_type at :41-53).
  • Python code that constructs FieldDeclaration directly with a value its annotations forbid (the package ships py.typed):
    • A kind that is not a FieldKind, for example the string "number". Before: SchemaValidationError("field 'x' has an invalid kind"). Now: a later combination rule may raise its own SchemaValidationError, for example "only a string field declares values" for "string" with values; otherwise the object is built, and to_dict() and schema_ref raise AttributeError.
    • A flag that is not a bool, for example required="yes" or many=1. Before: SchemaValidationError("field 'x' required must be a boolean"). Now: the value is accepted and read for its truthiness, and to_dict() emits it as given. from_dict rejects it, so the declaration cannot survive a round trip.
    • A role that is not a FieldRole, for example the string "decision". Before: SchemaValidationError("field 'x' has an invalid role"). Now: with many=True, "a field with a role holds one value" is raised; otherwise the object is built, and to_dict() and schema_ref raise AttributeError.

Checklist

  • Description: above.
  • Linked issue(s): none.
  • Tests:
    • No test was added or changed.
    • The affected tests ran on Windows at 958874f, base against head. origin/main has since gained refactor(breakdown, kernelforge): delete what eleven over-gate functions re-checked #2052, a refactor of breakdown/ and kernelforge/rewrite_by_flydsl/ that changes none of the three files or the tests below.
    • Session start and multi-node restart: the 26 test files that reach the three functions, with -n 2, give 1,041 passed and 11 skipped on both sides, and the same 121 failed and 76 errors, with no failing id on one side only. These fail on base too, on Windows only, mostly in test_apply_gpu_power_settings.py, test_multi_node_scripts.py and test_external_multi_node.py.
    • On Windows the multi-node restart tests never reach _restart_multinode, because os.getuid is missing in state_file_safe_to_read. A local shim for that one call, used the same way on both sides, lets five multi-node files run it: 266 passed and the same 15 failures (POSIX group checks) on base and head.
    • KB declarations: the 24 test files that construct or load declarations (the 21 files of hyperloom_kb/tests plus test_experience_collect.py, test_experience_kb_integration.py and test_experience_kb_service.py) give 280 passed, 1 skipped and 19 failed on head. The same 19 fail on base: local-service startup (an SSL error on this host), SIGTERM, directory fsync and file-path tests.
    • Diff coverage: cli/__init__.py and multi_node/cli.py are on the coverage omit list (pyproject.toml, [tool.coverage.run] omit, :360-361), so diff coverage has no countable statements for them, and the schema.py change only deletes lines.
    • Linux CI on this PR is the authority.
  • Size/complexity triggers crossed:
    • 458 changed production lines (+180 / −278) is over the 400-line warning and under the 1,000-line budget, so this does not carry size-exception.
    • No added or changed function crosses a gate limit. _persist_multinode_restart takes five parameters, at the limit (three before, one of them the _MultinodeLaunch bag); the new functions take one to three.
    • Two functions are over the ~60-line trigger and under the 80-line gate. _restart_multinode (74, from 107) is the one restart sequence, in its order: kill or resume, launch, the early checkpoint, the poll, the PD router and the persist. _seed_fresh_session (69, from 164) is the fresh-session steps in their fixed order (model, quantization, framework, target GPU, workload exports, session creation, model gate and preflights), each a call or the environment exports it publishes for subprocesses.
    • One function is over the cyclomatic-10 trigger: FieldDeclaration.__post_init__ (13, from 16), one if per declaration rule that from_dict does not already enforce.
    • cli/__init__.py (2,752) and multi_node/cli.py (1,804) stay over the 1,200-line module limit they were already over; each gets shorter, and cli/__init__.py stays over its 2,569 baseline entry, as at base. schema.py (1,008) is on the report-only 800-line warning list.
  • If this simplifies or refactors:
    • Mechanisms removed: listed per module under What changes in each module.
    • Contract preserved: the CLI's output, exit codes and session files, the multi-node state file, every in-repo caller, and the stored KB declarations and the errors from_dict raises.
    • Tests: as above.
  • Observable effect: none; see the section above for why, and for the out-of-contract inputs that now behave differently.
  • Breaking changes: no. The changed signatures (_persist_multinode_restart, _resume_prior_launch) belong to private functions whose only callers are in the same file. FieldDeclaration keeps its fields, and from_dict keeps its errors.
  • PR addresses single concern: yes, bringing four over-gate functions under the gate by deleting what they did twice. The KB declaration change rides along because it is six deleted lines of the same kind, checks repeated from the one place that already makes them, too small to take its own CI round and review.
  • Root cause is upstream: no.

Known merge interactions

Each open PR that changes one of these files was trial-merged with git merge-tree --write-tree, once into origin/main alone and once into this branch. A conflict that also occurs against main alone is not introduced by this branch.

Gate

python scripts/code_metrics.py --base-ref origin/main (origin/main at e25cb2d) passes on the 4 touched files: 0 new, 0 worse and 0 out-of-date entries in every dimension, no new or worse duplication or dead code, and 0 findings from the comment, English-only, test-import, repeated-literal and A1–A12 checks. Baseline: one module-length entry lowered (multi_node/cli.py 1,867 → 1,804), +0.

The gate lists under Already over at the base: _run_optimize (279 lines), _reexport_session_environment (111), _resolve_run_topology (92) and _resolve_agent_lineup (89), and cli/__init__.py module length 2,778 → 2,752 (baseline entry 2,569). schema.py (1,008 lines) is on the report-only module-length warning list. ruff check and ruff format --check are clean on the three files.

fengshaoyi-amd and others added 6 commits October 21, 2026 14:26
…ready makes

_short_poll returns a job only when its status is SUCCEEDED; FAILED and STOPPED raise
RuntimeError("<label> terminal failure: ...") and a timeout raises TransientFailure. Five
of its seven callers nevertheless re-tested the returned status against
_TERMINAL_FAIL_STATUSES (and _submit_and_collect_pod_json also against
_TERMINAL_OK_STATUSES), so those branches could never run. Deleted from
_restart_multinode (the fail-fast block and its message), _start_pd_router (the "router
job ended" exit), _restart_single_node, cmd_verify and _submit_and_collect_pod_json;
--print-logs still prints the logs.

Safe: no production code path and no test returns a non-SUCCEEDED job from _short_poll
(the only test that replaces it, test_external_multi_node.py:941, returns SUCCEEDED).

Co-authored-by: Cursor <cursoragent@cursor.com>
_restart_multinode wrote the launch identity twice: the early checkpoint before the poll,
then _persist_multinode_restart rewrote the same pid/log dirs, submission id, framework,
model, tp, ep, pd_mode and topology from a _MultinodeLaunch bag built for that one call.
Deleted the bag and the rewrites; _persist_multinode_restart now writes only what the
checkpoint leaves until the poll returns (the unconditional last_kill_submission_id and,
under PD, the per-field pd_* keys, router submission and leg URLs) and saves. The
checkpoint takes tp, ep, pd_mode and extra_args from the topology record, which
normalizes them with the same expressions, instead of recomputing them; pd_mode is no
longer recomputed after the poll or inside _resume_prior_launch, whose derivable prev_sub
parameter it now reads from state itself. Dropped a comment that narrated the next line.

Safe: a fake-driven run of every path (aggregated and PD, fresh, resumed RUNNING,
resumed serving, router without leg URLs, poll timeout, --print-logs) saves the same
state snapshots, logs the same lines and returns the same codes on base and branch. The
one difference is out of contract: a non-int args.tp used to end in state.json as the
raw value and now stays int(args.tp) from the checkpoint; restart-server parses --tp
with type=int (multi_node/cli.py:1536) and the in-process caller passes tp=tp_int
(_multi_node_server_lifecycle.py:446).

Co-authored-by: Cursor <cursoragent@cursor.com>
…ning its next leg

_resume_session was 170 lines. Deleted first:
- the __import__("...session.paths", fromlist=[...]) hop to reach _SESSION_SKELETON,
  now a plain import next to the other session.paths names it already imported;
- the hand-written relative_to/ValueError containment check, now session.paths'
  is_path_within, which owns that rule;
- the one-element gated_terminal set and the force_resume re-test in the override
  note (a gated stop without --force-resume has already exited by then);
- (state.current_best or {}) twice: SharedState.from_dict always yields a dict;
- four hand-written "state value, else manifest value" copies for the model gate,
  now one comprehension over the same keys in the same order;
- a comment repeating the docstring, one narrating the mkdir loop and a "no
  backfill needed" tombstone.

What is left splits along its two jobs: _open_resumed_session validates, pins, locks
and guards the --resume-from directory and loads its manifest; _open_next_leg refuses
a target_reached stop without --force-resume, then begins the leg, grants
--extend-hours and prints the budget. Multi-line messages are joined at their line
breaks; every message is byte-identical.

Safe: a fake-driven run of 13 paths (plain, target_reached with and without
--force-resume, other stop, --extend-hours, outside the workspace, missing dir, resume
blocked, no manifest, no state.json, stale AgentX, resume conflict, null current_best)
prints the same stdout and stderr, exits with the same codes, makes the same calls in
the same order and leaves the same state.json on base and branch. One difference is
out of contract: a workspace root whose resolve() raises OSError or RuntimeError (a
symlink loop) now gets the "is not under $USER_DATA_PATH" error and exit 2 instead of
a traceback.

Co-authored-by: Cursor <cursoragent@cursor.com>
…U and creating the session

_seed_fresh_session was 164 lines. The GPU resolution (probe over the
--gpu-type hint, the Magpie runner export, the custom-entrypoint refusal
and the gfx-arch check) moves into _resolve_target_gpu, and the session
creation (directory, lock, benchmark binding, manifest, install event,
Langfuse marker, launch info, partition shape, GPU power, seeded state)
into _create_fresh_session. Both run where the code ran before, so the
side-effect order is unchanged.

Deleted guards nothing reaches:
- the second args.gpu_type = None in the no-runner branch, and the
  "runner_gpu_type or gpu_type" fallback: the resolved type is a known
  board tag or "" (_resolve_amd_gpu_type, _autodetect_gpu_type and
  remote_autodetect_gpu_type return a tag or None), and gpu_runner_type
  maps every tag to a non-empty runner, so that branch only runs for ""
  and args.gpu_type is already None there. LaunchInfo now reads
  args.gpu_type, which holds the same value.
- the empty-model fallbacks for LaunchInfo and PartitionModel: the
  function exits when args.model is empty, and the quantization prelude
  only replaces it with the exported checkpoint path.

The three preflight exits become one condition; "or" keeps the short
circuit, so a failing check still stops the later ones. Their comments
restated the callees' docstrings; the ordering constraint stays as one
comment. The "B3:" note above the atom check is dropped.

Out of contract: a GPU type that is non-empty but blank used to leave
args.gpu_type None and reach the custom-entrypoint check as the blank
string; it now stays in args.gpu_type and the check gets "". No
resolver produces one.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ne to 1804

The two multi-node commits removed 66 lines from multi_node/cli.py, so
its module-lines entry is out of date. Written by
scripts/code_metrics.py --update-baseline; no other entry changed.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ts from from_dict

FieldDeclaration.__post_init__ re-checked three things its only external
producer had already settled: that kind is a FieldKind, that required,
sensitive, many and group are booleans, and that role is a FieldRole.
Every declaration that comes from outside Python code (declaration YAML or
JSON through load_declaration, KB database rows, HTTP request bodies,
remote KB responses and spool files) reaches the dataclass only through
ExperienceDeclaration.from_dict -> FieldDeclaration.from_dict, which
converts kind with FieldKind(...) and role with FieldRole(...) and runs
_boolean on each flag before it constructs, raising the same
SchemaValidationError messages as before. The only other constructors
are RATIONALE_DEFAULTS and the tests, which pass enum members and bool
literals. The package ships py.typed, so the annotations state the
contract for any other Python caller.

Every check on a value from_dict passes through raw stays: the name,
description and declared values, the search weight (still normalized to
a float, which keeps the schema_ref of a declaration with an integer
weight stable across a round trip), and every combination rule.

Cognitive complexity 32 -> 28, cyclomatic 16 -> 13, 36 -> 30 lines.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

Copy link
Copy Markdown

Code metrics gate: PASSED

Judged: the 4 files this change touches. Files it does not touch never fail it; anything found there is listed under Outside this change for information.

Dimension Limit New Worse Baseline out of date Baseline entries vs base
Cyclomatic complexity fails > 20 0 0 0 55 +0
Cognitive complexity fails > 30 0 0 0 203 +0
Function length (lines) fails > 80 0 0 0 346 +0
Nested block depth fails > 5 0 0 0 20 +0
Parameter count fails > 5 0 0 0 95 +0
Module length (lines) warns > 800, fails > 1200 0 0 0 49 +0
Duplicated lines per file fails > 0 0 0 0 96 +0
Dead code (vulture) fails > 0 0 0 0 2 +0
Check Findings Effect
Comments 0
English only 0
Production code imports test code 0
Repeated literals 0
A1 Private name of another package 0
A8 Flag argument 0
A9 Tuple return 0
A10 Raw vocabulary comparison 0
A11 Hidden global write 0
A12 Pure forwarder 0
A12 Single-implementation abstraction 0

Module length warning: over 800 lines, within the 1200-line failure limit (not failing): 1

Module Lines Limit
src/hyperloom_kb/schema.py 1008 warns > 800

Already over at the base (backlog, not this change's): 5

Unit Dimension Base Now
src/hyperloom/inference_optimizer/cli/__init__.py:1782 _reexport_session_environment Function length (lines) 111 111
src/hyperloom/inference_optimizer/cli/__init__.py:2312 _resolve_agent_lineup Function length (lines) 89 89
src/hyperloom/inference_optimizer/cli/__init__.py:2207 _resolve_run_topology Function length (lines) 92 92
src/hyperloom/inference_optimizer/cli/__init__.py:2419 _run_optimize Function length (lines) 279 279
src/hyperloom/inference_optimizer/cli/__init__.py:1 Module length (lines) 2778 2752
98 findings in files this change does not touch

Outside this change (informational): 98

Unit Dimension Baseline Now
src/hyperloom/orchestrator/state/shared_state.py resolve_graded_comparison Cognitive complexity absent 34
src/hyperloom_kb/collect/mapping.py _experience Cognitive complexity absent 31
src/kernelforge/cli/forge_loop_command.py _prepare_measurement_driver Cognitive complexity absent 36
src/kernelforge/cli/forge_loop_command.py _read_kb_warm_start Cognitive complexity absent 40
src/kernelforge/cli/forge_loop_command.py forge_loop Cognitive complexity absent 31
src/kernelforge/loop/runner.py IterationLoop._collect_session_candidate Cognitive complexity absent 44
src/kernelforge/loop/runner.py IterationLoop._rule_on_attempt Cognitive complexity absent 38
src/hyperloom/inference_optimizer/multi_node/scripts/launch_multinode.py _build_parser Function length (lines) absent 84
src/hyperloom/inference_optimizer/multi_node/scripts/launch_multinode.py _spawn_rank_actors Function length (lines) absent 84
src/hyperloom_kb/sync.py GlobalSync.pull Function length (lines) absent 84
src/kernelforge/cli/forge_loop_command.py _RunReporter.build_result Function length (lines) absent 92
src/kernelforge/cli/forge_loop_command.py _prepare_measurement_driver Function length (lines) absent 136
src/kernelforge/cli/forge_loop_command.py _read_kb_warm_start Function length (lines) absent 149
src/kernelforge/loop/runner.py IterationLoop._close_iteration Function length (lines) absent 117
src/kernelforge/loop/runner.py IterationLoop._collect_session_candidate Function length (lines) absent 185
src/kernelforge/loop/runner.py IterationLoop._implementer_history Function length (lines) absent 112
src/kernelforge/loop/runner.py IterationLoop._rule_on_attempt Function length (lines) absent 102
src/kernelforge/loop/runner.py IterationLoop._source_attempt Function length (lines) absent 175
src/hyperloom_kb/http_service.py Module length (lines) absent 1525
src/kernelforge/cli/forge_loop_command.py Module length (lines) absent 2061
src/hyperloom/orchestrator/actions/executors/_geak_sweep.py sweep_via_geak Cognitive complexity 67 70
src/hyperloom/orchestrator/actions/executors/explore.py ExploreExecutor.__call__ Cognitive complexity 369 383
src/hyperloom/orchestrator/kernel/request_handlers.py _run_forge_gemm_tuning Cognitive complexity 124 125
src/hyperloom/orchestrator/phases/kernel.py KernelPhase._run_geak_kernel_phase Cognitive complexity 122 130
src/hyperloom/orchestrator/phases/prelude.py PreludePhase.maybe_enqueue_warm_replay Cognitive complexity 190 191

...and 73 more; the job log lists every row.

Verdict computed by the gate scripts in /home/runner/work/_temp/code-metrics-gate, not by this tree's copy.

Thresholds and sources: [tool.hyperloom.code_metrics] in pyproject.toml. Tools: complexipy 8.0.1, jscpd 5.4.1, ruff 0.16.2, vulture 2.16.

@fengshaoyi-amd fengshaoyi-amd changed the title refactor(inference-optimizer): split the CLI session start, and drop what the multi-node restart and the KB field declaration did twice refactor(inference-optimizer): delete what the CLI session start, the multi-node restart and the KB field declaration re-checked or did twice Oct 21, 2026
@fengshaoyi-amd
fengshaoyi-amd marked this pull request as ready for review October 21, 2026 07:28
@fengshaoyi-amd
fengshaoyi-amd requested a review from a team as a code owner October 21, 2026 07:28

@ZhengGong-amd ZhengGong-amd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed: gate numbers re-measured (ruff C901, complexipy, function lines) and match the description; each removed guard / "producer always sets it" claim checked against its producers and call sites; base vs head differential runs show no undisclosed behaviour change; tests covering the touched modules pass.

LGTM.

@ZhengGong-amd
ZhengGong-amd merged commit b772e94 into main Oct 21, 2026
42 checks passed
@ZhengGong-amd
ZhengGong-amd deleted the lean/shafeng/cc20-io-cli-pr branch October 21, 2026 09:35
soumitra-chatterjee-tech added a commit to soumitra-chatterjee-tech/Hyperloom that referenced this pull request Oct 27, 2026
_default_grid_for_framework() seeds a curated grid for atom and xdit and
returns [] for everything else. For an LLM framework that is fine: the model
reads the framework's source and emits default_grid variants. A `custom`
workload has no framework source to read, so the model cannot infer its knobs
and EXPLORE has nothing to sweep -- the workload runs and benchmarks, but is
not autonomously tunable. optimize-custom-workload.md already promises "leave
the rest for exploration" with nothing behind it.

Let the adapter declare its knob space in <benchmark-scripts-dir>/levers.json.
The scripts dir is the adapter's contract directory and is already persisted
and re-exported on --resume, so the declaration travels with the session.

Reshaped per review on AMD-AGI#1546:

- Gate on `framework == "custom"`, not framework_is_scriptable() -- the latter
  is {xdit, custom}, so xdit would not have been untouched.
- One source only. The inline env vars (HYPERLOOM_DECLARED_LEVERS, HLHPC_LEVERS),
  the legacy hlhpc_levers.json filename, and the {"levers"} / {"variants"}
  wrapper object forms are all gone.
- Validate at the launch boundary (next to _require_custom_entrypoint) and
  exit 2 on a malformed declaration. The executor no longer warns and
  continues: every silent path surfaced later as empty_grid, so an operator's
  typo read as "the model proposed nothing".
- Seed only declarations absent from the ledger's name_index. A re-proposal is
  still benchmarked, so re-prepending the full set each round re-spent the
  round's wall-clock budget ahead of the model's proposals.
- Validity is is_executable(normalize_proposal(v)), not "has a name" -- a
  name-only variant is benchmarked as a duplicate of the baseline.
- Folded into the existing framework-lever merge rather than a second copy of
  the prepend/dedupe block.
- provenance defaults to default_grid, so the session breakdown attributes
  these to the seed grid rather than to the orchestration agent. The redundant
  note setdefault is gone.

Tests cover behaviour rather than the parser: declarations sweep when the model
proposes nothing; vllm/xdit/sglang/atom are unaffected; an already-benchmarked
declaration is not re-seeded; a model proposal wins a name collision; and each
malformed shape raises rather than degrading to an empty grid.

Closes AMD-AGI#1544

Re-applied onto current main (2026-10-27) after AMD-AGI#2085 reworked CLI session start
and _resolve_grid moved into _ExploreRound. The merge point is now the existing
_lever_seeded_payload() call, so the declared levers are appended to
lever_payload rather than inlined into a second prepend/dedupe block. The
per-round ledger filter moved with them into declared_lever_payload().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants