Skip to content

feat: wire compile_model into modelmaker (explicit opt-in) + 4 review follow-ups - #37

Open
musicalplatypus wants to merge 31 commits into
TexasInstruments:mainfrom
musicalplatypus:pr/compile-model-followups
Open

feat: wire compile_model into modelmaker (explicit opt-in) + 4 review follow-ups#37
musicalplatypus wants to merge 31 commits into
TexasInstruments:mainfrom
musicalplatypus:pr/compile-model-followups

Conversation

@musicalplatypus

@musicalplatypus musicalplatypus commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

This is part of a 4-PR stack: #34#35#36#37 (this PR). Depends on #34, #35, #36 — please merge those first; this PR's diff includes their commits until it does.

Summary

Three independent reviews (plan-quality, code-correctness, and an independent re-derivation of the technical claims) of the compile_model_if_enabled wiring in #35 surfaced four follow-up items, all closed here:

  1. Crash fix: run_quant_train_only: True (a supported radar config) crashed with AttributeError when no prior float-training call had populated a module-global dataset cache — identical latent bug in timeseries_classification, fixed in both.
  2. Test hardening: three new regression tests from feat: wire compile_model_if_enabled into radar, image, and audio classification #35 used a weak inspect.getsource + substring check that would pass under several realistic regressions (the call deleted outright, or left present as a comment/dead branch; its return value discarded; the call reordered relative to another). Replaced with real behavioral tests that patch compile_model_if_enabled, drive the real main(), and assert on its call arguments and that its return value is actually used. One initial gap in the rewrite (reordering against move_model_to_device specifically wasn't caught) was itself caught by re-review and fixed.
  3. Resource leak fix: radar_classification/train.py's main() never called shutdown_data_loaders(), leaking persistent DataLoader worker processes — a real leak once main() became the live entrypoint (fix: radar_classification's run() dispatched to a dead debug function, not main() #34). Verified via an automated whitespace-normalized diff that the ~110-line reindent introduced zero logic changes beyond the fix itself.
  4. Modelmaker integration: compile_model_if_enabled was wired into the tinyml-tinyverse scripts (feat: wire compile_model_if_enabled into radar, image, and audio classification #35) but never reachable from the actual tinyml-modelmaker product path for radar/vision/audio — only timeseries had the params/argv wiring. Extended compile_model (field + --compile-model argv wiring) to all three, each verified end-to-end through the real modelmaker run() path, not just unit tests.

Also fixed along the way: radar's default lr_scheduler value wasn't valid for init_lr_scheduler (any default-params radar run through modelmaker crashed on optimizer setup).

One unrelated one-line fix is bundled in — a pre-existing stale mock in test_anomalydetection_train_device_crash.py that didn't accept the input_shape kwarg compile_model_if_enabled now passes (from #35). Found and fixed independently by someone else working in the same shared checkout while this PR's work was in progress.

Policy decision (this is the part that changes behavior, not just fixes bugs)

Revised 2026-08-14. This PR originally also wired apply_hardware_defaults into radar/vision/audio, so compile_model would auto-enable on CUDA the same way it already does for timeseries. That auto-enable has since been reverted for these three modules.

Why: apply_hardware_defaults infers "what the user explicitly set" via isinstance(args[0], dict). When a caller passes a YAML file path instead of a dict — a normal, supported way to call init_params(), and how at least one downstream tool invokes it — that check silently evaluates to "nothing was explicitly set." An explicit compile_model: 0 / native_amp: false in the YAML gets silently overridden on any CUDA machine. This was independently found and reproduced against this exact function by a sibling project auditing it from the consumer side, and it's a correctness bug serious enough (silent override of an explicit user setting) that the auto-enable isn't worth keeping until apply_hardware_defaults can correctly introspect YAML-path configs, not just dict configs.

compile_model remains a reachable, explicit-opt-in field (default 0) with its --compile-model argv wiring fully intact for radar/vision/audio — set it directly in your config to turn compilation on. timeseries, unaffected by this change (predates this PR stack), keeps its existing auto-enable-on-CUDA behavior. compile_model is not currently exposed in any module's UI/descriptions block, so this is a YAML-only override either way.

Full rationale and history recorded in docs/superpowers/plans/2026-08-14-wire-compile-model-into-modelmaker.md's Decision section.

CI

CI is red on all three OS jobs — pre-existing on main, same root cause and same non-involvement as noted in #34; unchanged by this PR.

Test plan

  • Full tinyml-tinyverse and tinyml-modelmaker suites pass locally (only pre-existing, unrelated failures remain — confirmed by name and diff scope; CI failures above are the same known pre-existing ones)
  • Each of the 4 follow-up items plus the lr_scheduler bugfix has its own regression test
  • Manual E2E verification for the modelmaker wiring, through the real product path
  • Added a guard test per module (radar/vision/audio) proving compile_model stays 0 even with CUDA mocked available, to catch any future re-wiring of apply_hardware_defaults into these modules

t5fkg8d44d-beep and others added 27 commits August 13, 2026 12:43
Post-fix (main() live per Task 1): CPU 0.621s/epoch, MPS 1.066s/epoch
(1.72x slower) -- gap did not close vs pre-fix's 1.90x. Root cause:
compile_model_if_enabled/apply_hardware_defaults are never called
anywhere in radar_classification/train.py (only wired into the
timeseries_* reference scripts), so torch.compile/AMP were off for
all four runs regardless of which function run() dispatches to.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Whole-plan review flagged that the Goal/Architecture sections still
asserted the disproven premise (main() wired to compile_model_if_enabled)
130+ lines above the actual Results. Ledger and Results were already
honest; this fixes the presentation for a top-down reader.
CUDA beats CPU 4.8x on GX10 even without compile (opposite of the Mac's
MPS-loses-to-CPU picture). torch.compile's inductor backend failed to
build on GX10 (Triton/gcc CUDA-codegen toolchain issue) and fell back to
eager cleanly per the existing warmup-fallback mechanism -- so the CUDA
compile question is still open there. CPU's aot_eager backend engaged
successfully and gave a small (~3%) win, opposite in sign from the Mac's
~23% CPU loss for the same backend on the same model.
…ompile wiring

CUDA still beats CPU on GX10 without compile, but only 1.3x here (vs
radar's 4.8x) -- CNN_LENET5's conv/pool ops give GX10's CPU real work to
do, unlike radar's pure linear stack. Same Triton/gcc inductor build
failure reproduces identically to Task 1, confirming it's an
environment-level GX10 toolchain issue, not model-specific. CPU aot_eager
regressed ~104% here, a bigger relative hit than either machine saw for
radar's smaller model.
…ompile wiring

CUDA beats CPU 2.2x on GX10 without compile. GX10's CPU aot_eager is a
clear net loss (+75%) for audio, flipping sign from the Mac's small CPU
win -- the first case where the same backend/model combo disagrees
between machines. Same Triton/gcc inductor build failure reproduces for
the third module in a row, confirming it's systemic to this GX10
environment. Needed real torchaudio==2.9.0 (matching torch's version,
unlike 2.11.0's ABI mismatch) plus a soundfile-backed load() patch in the
benchmark driver only, since torchaudio's default torchcodec backend
needs FFmpeg, which GX10 doesn't have installed.
Whole-plan review found the code across all 3 tasks clean but flagged
several doc-only defects: a stale leftover paragraph contradicting the
real GX10 section above it, a synthesis claim with the GX10 CPU
win/loss grouping backwards, a false "first sign-flip" claim that
contradicted Task 1's own recorded radar sign-flip, an imprecise safety
argument for the audio torchaudio.load->soundfile patch, two
contradicting ad-hoc mechanistic explanations for GPU-vs-CPU advantage,
and one garbled/self-contradicting sentence.

Fixed all of the above inline, replaced the synthesis with a data table
(9 aot_eager measurements) and the stronger, better-supported summary
the review recommended, and empirically closed the one open risk
(checked the GX10 audio run's logged accuracy: 100%, confirming the
soundfile patch didn't corrupt input). Added a progress ledger and spun
out the two follow-up items the review flagged (sampling-rate/sample-rate
naming collision, GX10 Triton/gcc inductor build failure) as separately
tracked rather than left buried in a closed plan.
… modules

python3.12-dev was missing on GX10 (no Python.h anywhere on the box),
which is what Triton's cuda_utils.c build needed. Fixed on GX10 by the
repo owner. Re-ran --compile-model 1/CUDA for radar, image, and audio:
inductor now builds and engages with zero fallback warnings on all
three. All three show large regressions at 30 epochs (radar +24%,
image +407%, audio +373%), most likely one-time autotuning overhead
dominating short runs rather than steady-state cost -- flagged as an
observed number, not isolated from warmup in this pass.
Peer review caught that the code fix (a99f903) shipped without its own
plan doc ever being committed -- it stayed untracked, breaking the
convention plans 1 and 2 followed.
…eview

Independent peer review found 4 statements left stale by an earlier
correction pass (they still said the GX10 inductor question was
"unmeasured"/"blocked" after the addendum had already resolved it), a
garbled sentence, a noise-floor figure misapplied to discount a more
rigorous result than the one it came from, and -- most importantly --
that the headline "aot_eager is a consistent net loss" conclusion
didn't survive isolating one-time compile warmup from steady-state cost.

Empirically re-checked with fresh benchmarks: image_classification's
apparent CPU/MPS regressions (+124%/+29%) are mostly or entirely warmup
dilution -- steady-state compile is actually ~37-50% faster on both
devices once warmup is excluded. Rewrote Task 2's Results and the
whole-plan synthesis to reflect this; the corrected picture is more
mixed and run-length-dependent than either "consistent loss" or
"consistent win," which is the honest conclusion the data supports.
Plan 1 (harden-compile-work-followups): fix the run_quant_train_only
crash (radar + timeseries_classification), strengthen the 3 weak
compile_model_if_enabled tests, fix radar's DataLoader worker leak.

Plan 2 (wire-compile-model-into-modelmaker): close the gap where
compile_model_if_enabled was wired into tinyml-tinyverse but never
reachable from the actual tinyml-modelmaker product path for
radar/vision/audio, unlike timeseries.
…-defaults

Adds compile_model=0 to radar/params.py's training dict and calls
apply_hardware_defaults from init_params, matching timeseries's existing
pattern. Wires --compile-model into radar_base.py's train argv builder so
the field actually reaches the trainer. Without this, --compile-model was
unreachable from the modelmaker (product) path even though
tinyml-tinyverse's radar_classification.train.main() already supports it.
radar/params.py defaulted training.lr_scheduler to 'constantlr', which
isn't one of init_lr_scheduler's three real scheduler names (StepLR,
CosineAnnealingLR, ExponentialLR) or its special 'none' value (which is
what actually produces a genuinely constant LR). Any radar training run
through modelmaker with default params crashed during optimizer setup.

Found by an implementer subagent during manual E2E verification while
wiring compile_model into radar's modelmaker layer (task-1-report-planE.md,
spawned as a separate tracked item); every other module (timeseries,
audio, vision) already defaults to a supported value.
…e-defaults

Adds compile_model=0 to vision/params.py's training dict and calls
apply_hardware_defaults from init_params, matching timeseries's and
radar's (Task 1) existing pattern. Wires --compile-model into
image_base.py's train argv builder so the field actually reaches the
trainer. Without this, --compile-model was unreachable from the
modelmaker (product) path even though tinyml-tinyverse's
image_classification.train.main() already supports it.
create_data_loaders() sets persistent_workers=True whenever
args.workers > 0 (the default of 8). image_classification/train.py and
all four timeseries_* reference scripts already wrap their training
body in try/finally to call shutdown_data_loaders() on exit.
radar_classification/train.py's main() never did this -- moot while
main_debug (dead code) was the live entrypoint, but a real worker-process
leak now that main() is live.

Wraps the training body (everything after create_data_loaders through
the end of main()) in try/finally, matching image_classification's
exact wrapping boundaries. No logic changes, only indentation plus the
new try/finally lines.
…-defaults

Adds compile_model=0 to audio/params.py's training dict and calls
apply_hardware_defaults from init_params, matching timeseries's, radar's
(Task 1), and vision's (Task 2) existing pattern. Wires --compile-model
into audio_base.py's train argv builder so the field actually reaches the
trainer. Without this, --compile-model was unreachable from the
modelmaker (product) path even though tinyml-tinyverse's
audio_classification.train.main() already supports it.
Add a shared call-order recorder to the radar/image/audio compile-wired
tests so they assert move_model_to_device runs before
compile_model_if_enabled. The prior version mocked move_model_to_device
as a bare no-op with no observable side effect, so no assertion could
distinguish compile running before vs. after the device move -- a gap
an independent reviewer proved empirically by swapping the two calls in
radar_classification/train.py and showing the test still passed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nabled mock

The mock's lambda only accepted 3 positional args, but train.py's main()
now calls compile_model_if_enabled(model, args, logger, input_shape=...),
matching the pattern already used in radar/image/audio classification.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
apply_hardware_defaults auto-raises compile_model to 1 when
torch.cuda.is_available() -- so test_init_params_carries_compile_model_field
in each of test_{radar,vision,audio}_compile_model_param.py asserted
compile_model == 0 unconditionally, which fails wherever CUDA is actually
present (including GX10, this project's own CUDA test machine). Found by
an independent whole-plan review, which proved it by simulating CUDA
availability and showing all three return 1.

Fixed by wrapping init_params() in
patch('torch.cuda.is_available', return_value=False), matching the
pattern the pre-existing test_hardware_defaults_integration.py already
uses for the same reason.

Also relocated test_radar_lr_scheduler_default.py from tinyml-tinyverse's
test suite to tinyml-modelmaker's -- it imports tinyml_modelmaker (the
wrong dependency direction for that package) and tests a modelmaker
params default, not tinyverse code.
Whole-plan review flagged this as a decision needing the repo owner's
explicit call rather than silent inheritance from timeseries's pattern.
Decision: keep auto-enable, for consistency with timeseries and because
the measured regressions were on very short synthetic benchmarks where
warmup dominates -- real runs should amortize it. Recorded with
rationale and a documented fallback if that assumption doesn't hold.
…n doc

An independent PR review caught a private Tailscale hostname, SSH key
path, and username committed to a doc already live on open PRs against
a public upstream repo. Genericized all references to the remote GX10
machine and local venv paths -- no change to the technical content,
only removed identifying infrastructure details.
@musicalplatypus musicalplatypus changed the title fix: crash, resource leak, weak tests, and modelmaker gap found reviewing the compile_model work feat: wire compile_model into modelmaker (auto-on for CUDA) + 4 review follow-ups Aug 14, 2026
t5fkg8d44d-beep and others added 3 commits August 14, 2026 09:09
Claimed main_debug() alone lacked compile_model_if_enabled/apply_hardware_defaults,
implying main() already had it -- at this commit neither function does (that's
added in a later change). Corrected to describe what's actually true at this
point: main() is the one that CAN be extended with it, not that it already is.
Stripped the 'For agentic workers: REQUIRED SUB-SKILL...' header
(addressed to an agent, not useful to a human reader of a merged PR)
and normalized all checkboxes from - [ ] to - [x] across all 5 plan
docs -- every task they describe is actually done; the unchecked
boxes were misleading in a shipped record.
apply_hardware_defaults infers "what the user explicitly set" by checking
isinstance(args[0], dict) -- when a caller passes a YAML file path instead
of a dict (a normal, supported way to call init_params()), that check
silently evaluates to "nothing was explicitly set," so an explicit
compile_model: 0 / native_amp: false in the YAML gets overridden on any
CUDA machine. This was independently reproduced against a sibling
project's analysis of the same function (mmcli's ANALYSIS-cuda-auto-
defaults.md, finding F-1).

Remove the apply_hardware_defaults call from radar/vision/audio's
init_params() -- timeseries, which predates this session's changes, is
unaffected and keeps the auto-enable behavior. compile_model stays a
reachable, explicit-opt-in field (default 0) with its --compile-model
argv wiring fully intact for all three modules; only the CUDA
auto-flip is removed.

Adds a regression test per module proving compile_model stays 0 even
when CUDA is mocked available, to guard against silently re-wiring
apply_hardware_defaults back in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@musicalplatypus musicalplatypus changed the title feat: wire compile_model into modelmaker (auto-on for CUDA) + 4 review follow-ups feat: wire compile_model into modelmaker (explicit opt-in) + 4 review follow-ups Aug 14, 2026
…tion

Supersedes (without deleting) the 2026-08-14 "keep auto-enable" decision
recorded earlier the same day, with the rationale for the reversal --
F-1 from a sibling project's independent analysis of
apply_hardware_defaults, reproduced against radar/vision/audio in
7651819. Keeps the audit trail of both decisions rather than rewriting
history.

Co-Authored-By: Claude Sonnet 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