Skip to content

fix(aiter): self-heal the compiled registry so a shipped CSV cannot fail every boot - #1532

Merged
xiaofei-zheng merged 7 commits into
mainfrom
bugfix/yunkai/baseline-aiter-registry-selfheal
Sep 18, 2026
Merged

xiaofei-zheng merged 7 commits into
mainfrom
bugfix/yunkai/baseline-aiter-registry-selfheal

Conversation

@BaoYunkai

@BaoYunkai BaoYunkai commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Problem

aiter resolves a tuned table two ways, and jit/core.py::get_config_file has
exactly these two branches:

  • env set -> precisely the :-joined paths. The shipped default is not
    prepended and model overlays are not discovered.
  • env unset -> configs/model_configs/*{tuned_file_name}*.csv (excluding
    untuned) merged on top of the shipped default.

A round tunes one operator, so it sets only that operator's AITER_CONFIG_*.
Every other variable is therefore unset and takes the second branch, pulling in
overlays cut on another host -- tables whose kernelNames are absent from this
machine's compiled module_*.so. fmoe_ck sets only AITER_CONFIG_FMOE, so
bpreshuffle takes the unset branch and merges the dsv3 overlay.

Serving then aborts at load with a registry mismatch. Because the offending
table is shipped rather than produced by the run, every retry hits the same
wall: the session fails at boot with nothing to roll back.

Fix

Resolve the CSV set a boot will actually load, by aiter's own rule.
csvs_aiter_will_load implements the two branches above, and
prepare_serving_so_for_csvs consumes its expanded list. It runs from the
baseline executor ahead of materialize, on every lane. An uncovered module is
unlinked so the next boot rebuilds it.

This deliberately has no early return for a round with no AITER_CONFIG_* set:
no env is exactly the case that needs the unset-branch check, and it is the
case PRELUDE boots in. A :-joined env is also now expanded rather than
Path(...).is_file()-tested, which had silently read a multi-path value as
covered.

On integrate, a registry mismatch also drops the modules the error names.
The env does not always reach the module at fault -- AITER_CONFIG_FMOE maps
to no serving module at all -- so the module is read out of the kernel the
error named.

An earlier revision also audited every shipped CSV once at KERNEL entry. That
is removed: it rested on the premise this PR's own code disproves, it scanned
tables no boot can resolve to, and one uncovered table there moved the whole
jit/build aside -- a full recompile for every following lane, on a healthy
install. It also sat below the if geak_enabled: ... return branch, so it
never ran on the default backend.

Test plan

  • test_baseline_aiter_registry_preflight.py, test_kernel_integrate_and_report.py (123 passed)
  • src/hyperloom/orchestrator/ (1295 passed, 1 skipped -- the skip needs hypothesis, absent on this box)
  • ruff check . + ruff format --check . on the changed files
  • CHANGELOG Unreleased entry

@BaoYunkai
BaoYunkai requested a review from a team as a code owner September 16, 2026 11:40
@ZhengGong-amd

Copy link
Copy Markdown
Collaborator

The fix is one function, not three layers

The failure is real, but all three commits are guessing at the same unknown, and the guess has an exact answer already pinned in this repo.

The invariant

Every JIT kernelName in the CSV set this boot will load must exist in the matching compiled module_*.so; otherwise unlink and rebuild first.

The only non-trivial term is "the CSV set this boot will load". Each commit guesses it differently:

guess why it's wrong
1 / #1457 CSVs the round's env names misses the overlays an unset env pulls in
2 reverse-engineered from the error prose post-hoc, and breaks when aiter rewords
3 all 142 shipped CSVs includes tables this boot will never read

It doesn't need guessing. aiter/jit/core.py::get_config_file has exactly two branches, and kernelforge/data/local_knowledge/framework/aiter/overall/config_files_and_merge.md (pinned to ROCm/aiter@b467ce342) transcribes them:

  • env SET → precisely the :-joined paths. Shipped default not prepended, model overlays not discovered.
  • env UNSETconfigs/model_configs/*{tuned_file_name}*.csv (excluding untuned) merged on top of the shipped default.

So the PR description's premise — "aiter merges configs/ and configs/model_configs/ at import ... regardless of what the round tuned" — is not what aiter does. The incident is still real: fmoe_ck sets only AITER_CONFIG_FMOE, so bpreshuffle takes the UNSET branch and merges the dsv3 overlay. But the causal chain is much narrower than the fix assumes.

What to build

def csvs_aiter_will_load(configs_dir: Path, tuned_file_name: str, value: str) -> list[Path]:
    """The CSV set this boot resolves to, by aiter's own two-branch rule."""
    if value.strip():
        return [Path(p) for p in value.split(":") if p.strip()]
    overlays = sorted(
        p for p in (configs_dir / "model_configs").glob(f"*{tuned_file_name}*.csv") if "untuned" not in p.name
    )
    return [configs_dir / tuned_file_name, *overlays]

Feed its output to the existing serving_modules_cover_csv / prepare_serving_so_for_csvs. That covers every case the three layers were chasing:

  • PRELUDE boots with no env → UNSET branch → the dsv3 overlay is checked. This is the only way that round ever gets protected, and commit 3 can't do it (it runs at KERNEL entry, PRELUDE is upstream of that).
  • A round pins an env → SET branch → only the tables it will actually read. No rebuild for tables that won't load, and it converges.
  • The error names a kernel from a variable the round never set → that variable took the UNSET branch, so it was already in scope. registry_mismatch_modules has nothing left to do.

It also closes a silent hole the current preflight inherits: Path("a.csv:b.csv").is_file() is False, so prepare_serving_so_for_csvs continues and treats a :-joined env as covered.

Concretely

  1. Read get_config_file on the box and confirm the two branches. If the knowledge card is stale, fix the card first — the argument for this PR depends on which one is right.
  2. Add csvs_aiter_will_load to _aiter_jit.py; have prepare_serving_so_for_csvs consume the expanded list. Three existing call sites unchanged.
  3. Delete registry_mismatch_modules, audit_serving_so_against_aiter_configs, _audit_aiter_serving_so, and the also_modules parameter. (Keep the _compiled_registry_error_text split — that refactor stands on its own.)
  4. Keep commit 1's call site (baseline _run_once, ahead of materialize) — it's the right boundary. Drop the if not csv_envs: return guard: no env is exactly the case that needs the UNSET-branch check.
  5. Leave the post-hoc aiter_jit_registry_mismatch classifier as a warning. Self-healing belongs before boot, not in a retry.

Net effect should be one new function, one call site, and a pile of deletions.

Three findings worth keeping regardless

_audit_aiter_serving_so never runs on the default backend. It sits at kernel.py:651, below if geak_enabled: ... return. docs/reference/kernel-execution-path.md:70 and :124: "GEAK branch — the documented default ... Nothing below this line executes", "under the default geak backend _on_enter_kernel returns before the lane is reached." An install-level fact behind a non-default, lane-specific branch, with no signal when it's skipped.

The stated cost omits the remedy. "40s on a healthy tree" is the scan. _invalidate_jit_build moves the whole jit/build aside, so every aiter module recompiles afterwards. And the .so count then drops below COLD_START_KERNEL_THRESHOLD = 20, so every subsequent baseline is reclassified COLD and gets the 9000s budget. (FWIW the 40s itself is avoidable — on an equivalent synthetic tree the current name.encode() in data scan takes 35.8s vs 0.45s for extracting the .so string table once. Moot if the CSV set drops from 142 to 1–3.)

except OSError is too narrow for files you don't own. csv_jit_kernel_rows catches only OSError, but the audit reads 142 shipped CSVs. One with a BOM raises UnicodeDecodeError, one with an overlong field raises csv.Error; both escape _audit_aiter_serving_so and skip the entire KERNEL entry, GEMM tuning included.

Smaller

  • docs/reference/kernel-execution-path.md enumerates the KERNEL-entry sequence line by line; a new step that moves jit/build has to land there. CHANGELOG.md too — a silent COLD-start reclassification is user-visible.
  • test_run_once_preflights_before_it_materializes_the_config asserts on byte offsets in the source text. A rename breaks it and a real reordering can slip past it — record call order with monkeypatch instead.
  • The destructive path is untested: _invalidate_jit_build is monkeypatched out in the invalidate test, and the other audit test takes the skip branch.
  • Three docstrings repeat "aiter merges ... unconditionally / regardless of what the round tuned". Fix with step 1.

A round that starts sglang boots against whatever get_config_file resolves each
AITER_CONFIG_* to, and a kernel the compiled module never registered raises from
inside graph capture. #1457 gave the GEMM integrate lane a coverage check before its
own boots, but the baseline executor only learned to name the failure
aiter_jit_registry_mismatch afterwards, with nothing to undo it -- and PRELUDE's
first measurement and every FRAMEWORK variant boot through there.

Which CSVs a boot loads is not a guess. aiter/jit/core.py::get_config_file has two
branches: set, and it takes the ':'-joined paths verbatim with no shipped default and
no overlay discovery; unset, and it merges configs/model_configs/*{tuned_file_name}*.csv
(minus untuned) on top of the shipped default. A check keyed on the CSVs a round names
therefore cannot see the overlay an unset variable pulls in, which is exactly how
fmoe_ck -- setting only AITER_CONFIG_FMOE -- took the unset branch for bpreshuffle,
merged the dsv3 overlay and failed both integrate attempts in 20260915T083736Z-36368bab
on a kernel it had never tuned.

Resolve each variable through that rule and feed the result to the existing coverage
check. A round that pins a table gets only what it will read; one that pins nothing
gets the overlays checked, which is the only way PRELUDE is ever protected. It also
closes the hole where Path("a.csv:b.csv").is_file() is False, so a ':'-joined env was
silently treated as covered.

csv_jit_kernel_rows now also survives the tables it does not own: the shipped CSVs can
carry a BOM (UnicodeDecodeError) or an overlong field (csv.Error), and either escaping
would skip the whole check.
@BaoYunkai
BaoYunkai force-pushed the bugfix/yunkai/baseline-aiter-registry-selfheal branch from aafd95c to 0ff4b9e Compare September 17, 2026 08:45
…s at KERNEL entry

aiter merges configs/ and configs/model_configs/ at import, so a table shipped for
another model -- DeepSeek, GLM -- naming a kernel this install never compiled fails
every boot in the session. The three env-keyed checks cannot see it: they compare
only the CSVs a round names, and no round names a shipped table.

On this install 6 of 142 shipped CSVs outran 5 modules, which is how fmoe_ck failed
both integrate attempts in 20260915T083736Z-36368bab on a bpreshuffle kernel it had
not tuned, and why the same class of failure kept returning after being fixed twice.

Audit once at phase entry, where it is an install-level fact rather than a lane's:
resolve every shipped CSV's kernels by name, unlink the modules that do not provide
them, and let the next boot rebuild. 40s on a healthy tree, and every lane that
follows inherits a consistent one.
…env's

A round tuning one CSV still boots against every CSV aiter merges, so the kernel a
compiled-registry miss names can belong to a variable the round never set -- and
AITER_CONFIG_FMOE maps to no serving module at all. The env-keyed drop then unlinks
nothing and the one retry repeats the failure verbatim, which is how fmoe_ck failed
both attempts in session 20260915T083736Z-36368bab on a bpreshuffle kernel it had
not tuned, leaving GEMM with no e2e verdict for any tuner.

Read the module out of the kernel the error names and unlink it alongside the env's
own. The error text is the only thing that identifies the module when the env cannot.

@haishuok0525 haishuok0525 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.

What this PR does

Boots no longer walk into aiter tuned CSVs whose kernelNames are missing from the host compiled module_*.so.

  • Resolve CSVs the way get_config_file does (pinned :-list vs unset → shipped default + model overlays) and run prepare_serving_so_for_csvs from the baseline executor before materialize.
  • On integrate aiter_jit_registry_mismatch, also drop modules named in the error text (registry_mismatch_modules), not only env-mapped ones.
  • Once at Forge GEMM KERNEL entry, audit shipped configs/ + model_configs/ and unlink uncovered GEMM modules.

Checked: _aiter_jit.py resolution vs aiter's set/unset branches, baseline preflight call site, integrate retry also_modules, KERNEL audit placement, new preflight tests. Description matches the final three-commit diff.

Blocking issues

  1. Missing CHANGELOG.md Unreleased entry. Recent merged fix(...) PRs (#1542, #1528, #1526, #1522) update Unreleased; this user-visible session-boot fix does not. Please add a short Fixed bullet under Unreleased.

@xiaofei-zheng

Copy link
Copy Markdown
Collaborator

What this PR does

Stops a boot from loading an aiter tuned CSV whose kernelNames are absent from the compiled module_*.so. Three layers: csvs_aiter_will_load resolves the CSV set by get_config_file's own two-branch rule (pinned :-list vs unset -> shipped default + matching model_configs overlays) and prepare_serving_so_for_csvs runs from the baseline executor before materialize; integrate's registry-mismatch retry also drops modules named in the error text; and a KERNEL-entry audit reconciles every shipped CSV against the compiled modules.

Checked: csvs_aiter_will_load against config_files_and_merge.md:40-66, AITER_ENV_TO_TUNED_FILE vs AITER_ENV_TO_SERVING_MODULES coverage, the _run_once call site ordering, registry_mismatch_modules / also_modules plumbing, the KERNEL-entry call site, and the new preflight tests. CI is green.

Blocking issues

  1. The PR description still asserts the premise this PR's own code disproves. Problem says "aiter merges configs/ and configs/model_configs/ at import ... regardless of what the round tuned". csvs_aiter_will_load (_aiter_jit.py:659-684) implements the opposite: a set env is taken verbatim and neither prepends the shipped default nor discovers overlays. The same wrong statement is still in three docstrings/comments that were not updated with the code: _aiter_jit.py:743-747 ("aiter loads configs/*.csv plus configs/model_configs/*.csv unconditionally"), phases/kernel.py:258, request_handlers.py:5827.

  2. Fix bullet 1's "It is a no-op for the common case with no tuned CSV" is no longer true. _prepare_aiter_serving_so (baseline.py:513-551) has no early return, and prepare_serving_so_for_csvs (_aiter_jit.py:687-738) now iterates the whole AITER_ENV_TO_SERVING_MODULES table, so a round with no AITER_CONFIG_* at all takes the unset branch for every entry and scans the shipped default plus its overlays on every PRELUDE and FRAMEWORK boot. That is the intended behaviour, but it is the opposite of what the description promises.

  3. Fix bullet 3's stated effect does not happen under the default backend. await self._audit_aiter_serving_so() sits at phases/kernel.py:651, below if geak_enabled: await self._run_geak_kernel_phase(...); return (phases/kernel.py:640-644). shared_state.py:464 defaults kernel_optimizer = "geak" and _raw_kernel_backend_order only leaves GEAK when KERNEL_OPT_BACKEND_ORDER=forge is set explicitly; docs/reference/kernel-execution-path.md:70-71 states it directly -- "GEAK branch -- the documented default ... Nothing below this line executes". So "Audit once at KERNEL phase entry ... every lane that follows inherits a consistent one" and the quoted 40s cost apply only to the forge opt-in, not to the default install the Problem section describes.

  4. Where the audit does run, it acts on the disproved premise and is destructive. _aiter_config_csvs (_aiter_jit.py:742-752) globs every configs/*.csv and configs/model_configs/*.csv, including tables no boot in the session will ever resolve to under either branch. Any one of them that is uncovered makes audit_serving_so_against_aiter_configs unlink the modules and call _invalidate_jit_build (_aiter_jit.py:789-793), which moves the whole jit/build aside and forces a full aiter recompile for every lane that follows. The counts in this PR's own description (6 of 142 shipped CSVs outrunning 5 modules) mean this fires on a healthy install. Please either scope the audit to the CSV sets csvs_aiter_will_load says are reachable, or drop it.

  5. The CHANGELOG entry omits the KERNEL-entry audit. The new Unreleased bullet covers the two-branch resolution and the integrate-side drop only. The audit is the step with the user-visible side effect (a full JIT rebuild), so it belongs in the entry -- or should be removed per (4).

BaoYunkai added 2 commits September 18, 2026 14:52
The audit globbed every shipped configs/*.csv and configs/model_configs/*.csv,
on the premise that aiter merges them all at import. csvs_aiter_will_load, added
by the first commit, disproves that: a pinned AITER_CONFIG_* is taken verbatim
and an unset one resolves to the shipped default plus only its matching
overlays. So the audit checked tables no boot in the session can resolve to, and
any one of them being uncovered unlinked modules and moved the whole jit/build
aside -- a full aiter recompile for every lane that follows, on a healthy
install: 6 of 142 shipped CSVs outrun 5 modules on this image.

It could not deliver the stated effect either. The call sat below the
`if geak_enabled: ... return` branch in _on_enter_kernel, and GEAK is the
default backend, so "audit once at KERNEL entry so every lane inherits a
consistent tree" only ever applied to the forge opt-in.

Deleted: audit_serving_so_against_aiter_configs, _aiter_config_csvs,
_audit_aiter_serving_so, its call site, and the three tests that covered them.
The boot-time check the first commit added is where this belongs -- it runs
ahead of materialize on every lane, and resolves the CSV set by aiter's own
rule rather than by globbing.

The integrate-side comment still said a round tuning one CSV "boots against
every CSV aiter merges". It now states the rule the code implements.
…e-aiter-registry-selfheal

# Conflicts:
#	CHANGELOG.md

@xiaofei-zheng xiaofei-zheng 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.

Re-reviewed at 616d21db1

All five blocking points from the previous round are addressed.

The KERNEL-entry audit is gone (d701c2b1). audit_serving_so_against_aiter_configs, _aiter_config_csvs, _audit_aiter_serving_so, its call site and the three tests that covered it are deleted -- grep confirms no reference survives, and phases/kernel.py has dropped out of the diff entirely. That removes both the destructive behaviour (a shipped table no boot can resolve to moving the whole jit/build aside, on an install where 6 of 142 CSVs outrun 5 modules) and the dead-on-default-backend call site under if geak_enabled: ... return. The boot-time check that remains resolves a reachable CSV set, so an unlink converges instead of recompiling on every entry.

The disproved premise is out of the prose. The PR description is rewritten around get_config_file's two branches and states plainly why the audit was dropped. The integrate-side comment (request_handlers.py:5826-5831) now describes the unset-branch rule the code implements instead of "boots against every CSV aiter merges". The two docstrings carrying the old claim went with the deleted functions.

Fix bullet 1 no longer claims a no-op; the description now says the absent early return is deliberate and names PRELUDE as the case that needs it.

CHANGELOG matches the final diff: two-branch resolution plus the integrate-side drop, no audit.

Checked this round: csvs_aiter_will_load (_aiter_jit.py:659-684) against config_files_and_merge.md:40-66, AITER_ENV_TO_TUNED_FILE vs AITER_ENV_TO_SERVING_MODULES coverage, the _run_once ordering ahead of materialize_config_with_envs, registry_mismatch_modules / also_modules on the integrate retry, and the deletion being clean. CI green (30 success, 2 skipped).

No blocking issues. LGTM.

@xiaofei-zheng
xiaofei-zheng merged commit bd08b62 into main Sep 18, 2026
32 checks passed
@xiaofei-zheng
xiaofei-zheng deleted the bugfix/yunkai/baseline-aiter-registry-selfheal branch September 18, 2026 15:16
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.

4 participants