Skip to content

differential: a model's checks share its cap, and a kill keeps what finished - #1321

Merged
wshlavacek merged 5 commits into
lanl:mainfrom
Montana:fix/differential-model-cap
Oct 27, 2026
Merged

wshlavacek merged 5 commits into
lanl:mainfrom
Montana:fix/differential-model-cap

Conversation

@Montana

@Montana Montana commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #1291 (#1257).

Why

The pool kills a model's worker at its cap (--timeout 900 on the nightly), and every row of the model goes with it. On main, MODEL1006230049, 0053 and 0077 already take 716–751 s of that 900 (run 40440170341). So any check a pull request adds to every model tips them over, and every check they passed before reads TIMEOUT. The baseline gate counts each of those as a regression:

PR Path-agreement regressions all of them models
#1315 27 TIMEOUT, "cap of 900 s" MODEL1006230049, 0053, 0077
#1318 36 TIMEOUT, "cap of 900 s" the same, plus BIOMD0000000810, BIOMD0000000876
#1319 33 TIMEOUT, "cap of 900 s" the same three, plus BIOMD0000000810

None is a finding. #1315's own baseline run even had these three passing, so they are also flaky at the edge, depending on runner speed.

What

A deadline per model. check_all(job, locale, checks, deadline) takes 0.9 of the cap (CAP_SHARE):

  • every run has until then (run(timeout=), and the sparsity rule's one-interval runs);
  • the locale child process gets the smaller of its 600 s and what is left;
  • no run and no check starts once the deadline has passed. backend_as_asked builds and compiles Simulators without running them, so no run's timeout bounds it; on MODEL1006230049 it started past a 180 s deadline and took the worker to 203 s.

A check that doesn't finish is its own TIMEOUT, "not finished within the model's cap", and the model keeps every other row. A base that doesn't finish makes every row TIMEOUT rather than no_base, and a sensitivity run that times out is TIMEOUT rather than a refusal.

What a kill leaves. A constructor can still run past the deadline: deriving the sensitivity RHS takes 20 s, compiling C up to 50 s on these models. So each verdict is written as it is reached (on_verdict, then _keep, to a per-model file in a temp directory). When the pool kills a worker, to_results keeps every row it finished, and only the rest are TIMEOUT.

-            verdicts = D.check_all(job, spec["locale"], tuple(spec["checks"]))
+            deadline = time.monotonic() + spec["timeout"] * CAP_SHARE - (time.perf_counter() - t0)
+            verdicts = D.check_all(
+                job, spec["locale"], tuple(spec["checks"]), deadline,
+                lambda c, v: _keep(spec, c, v),
+            )

Without a deadline (any caller but the runner), nothing changes.

The cap itself (second commit)

With this PR merged into them, #1315, #1318, #1319 and #1320 still read 11–12 regressions. Main's own checks on MODEL1006230049 and 0053 did not fit the 810 s deadline: across five CI runs of the same checks those models took 689–894 s, so a 900 s cap flipped their rows with runner speed. The job's cap is now 1500 s (deadline 1350). It costs only the few models that use it: this PR's own job took 51 of its 180 minutes. Models that timed out at 900 s may now finish, which reads as improved, not as an alert.

A compile past bngsim's own limit (third commit)

With the 1500 s cap, this PR's nightly read 2 new crashes, both on BIOMD0000000385. Given the time to reach its codegen compile, its 4.2 MB of C ran past bngsim's own 600 s compile limit. codegen=True reports that as a RuntimeError, and the harness read it as EXCEPTION. Those rows were TIMEOUT at 900 s, and a timeout they stay.

_timed_out now reads a timeout:

  • anywhere in the exception's cause chain (the sensitivity build chains the compile's TimeoutExpired);
  • or in bngsim's fixed "Codegen compilation timed out" text, which codegen=True's refusal carries without chaining its cause.

The row's note says the compile timed out, not the model's cap. A compile that fails is still EXCEPTION. The model's other five rows went TIMEOUT → PASS ("improved", not an alert).

Tests: the in-text and the chained case are both TIMEOUT, and a compile failure stays EXCEPTION. Mutations, each caught: the chain walk off; the text match off.

Verified

End to end through the runner on the three models, at a deliberately tight --timeout 200 (3 workers, 194 s):

model before now
MODEL1006230049 9 TIMEOUT 5 PASS, 1 SKIP; grid, sparsity_covers_jacobian, backend_as_asked TIMEOUT
MODEL1006230053 9 TIMEOUT 5 PASS; cc_vs_mir, grid, sparsity_covers_jacobian, backend_as_asked TIMEOUT
MODEL1006230077 9 TIMEOUT 5 PASS; the same 4 TIMEOUT

At the nightly's 900 s their checks finish (716–751 s) inside the 810 s deadline as before, so main's verdicts are unchanged. A check a pull request adds then runs in what is left, and is TIMEOUT on its own row, which is a new case and not a regression.

Tests (python/tests/test_differential_shapes.py):

  • a run past the deadline does not start;
  • a cap spent after the base times out each check alone (no exception, no pass), and clears the deadline;
  • no check starts past the deadline (a planted check_backend is never called);
  • a base past the deadline is TIMEOUT on every row;
  • no deadline bounds nothing;
  • the runner's worker hands each model its cap;
  • a killed model keeps the rows it finished;
  • each verdict is heard as it is reached.

Mutations, each caught:

  • the run unbounded;
  • the timeout not mapped;
  • a sensitivity timeout read as a refusal;
  • check_all setting no deadline;
  • the deadline not cleared;
  • _states unbounded;
  • a base timeout read as no_base;
  • the worker passing no deadline;
  • a check starting past the deadline;
  • kept rows ignored;
  • nothing kept;
  • verdicts not heard.

Suites: parity_checks/tests and the shapes give 1,103 passed, 28 skipped, 1 xfailed. One local failure, test_jacobian_characterization_gate, needs main's fill_dense_fd_jacobian_half_step (#1002), which my local build predates; CI builds fresh. ruff 0.14.0 is clean.

Once this lands I'll merge it into #1315, #1318 and #1319, and order each one's new checks after main's, so they run in what is left of the cap.

…inished

The pool kills a model's worker at its cap (900 s on the nightly), and
every row of the model with it. MODEL1006230049, 0053 and 0077 take
716-751 s of that on main, so any check a pull request added to every
model turned all their rows TIMEOUT: lanl#1315, lanl#1318 and lanl#1319 each read
27-36 regressions, none of them a finding.

check_all now takes a deadline, 0.9 of the cap. Every run has until it
(run(timeout=)), no run or check starts past it, and a check that does
not finish is its own TIMEOUT, said why. Each verdict is written as it is
reached, so a worker the pool still kills (one compiling C past the
deadline, which no run's timeout bounds) leaves the runner every row it
finished; only the rest are TIMEOUT.
Montana added a commit to Montana/bngsim that referenced this pull request Oct 27, 2026
… left of the cap (lanl#1308)

Stacked on lanl#1321, whose deadline keeps a slow model's rows: the check
added here runs last, so main's rows are reached before it.
Montana added a commit to Montana/bngsim that referenced this pull request Oct 27, 2026
…leaks

restart, reuse and batch run after every check main runs, each run and
run_batch with what is left of the model's cap; run_until takes no
timeout, and a kill at the cap still leaves the model's other rows.
Montana added a commit to Montana/bngsim that referenced this pull request Oct 27, 2026
…-vs-run

steady_vs_run and newton_vs_run run after every check main runs, the
runs to equilibrium bounded by what is left of the cap. A reference run
the cap stops is this check's TIMEOUT, not a missing reference;
steady_state() takes no timeout, and a kill still leaves the other rows.
Montana added a commit to Montana/bngsim that referenced this pull request Oct 27, 2026
…ution

lanl#1321 bounds every run by the model's cap, which replaces the deadline
6357188 threaded through stable and the arms alone. The order stays:
main's checks, then the attribution's arms, then stable, each starting
only with time left. A stable the cap stops is TIMEOUT, as any check's
is under lanl#1321, where it was SKIP.
With lanl#1321, PRs stacked on this one still read 11-12 regressions: main's
own checks on MODEL1006230049 and 0053 did not fit the 810 s deadline of
a 900 s cap. Across five runs of the same checks those models took
689-894 s, so the cap flipped their rows with runner speed. 1500 s puts
the deadline at 1350, and costs only the few models that use it.
Montana added a commit to Montana/bngsim that referenced this pull request Oct 27, 2026
…degen

check_all keeps lanl#1321's deadline and its verdicts-as-reached; an SSA job
is routed to check_ssa inside it, so its verdicts are kept the same way.
Montana added a commit to Montana/bngsim that referenced this pull request Oct 27, 2026
An NF job is routed to check_nf inside lanl#1321's check_all, as an SSA job
to check_ssa, so its verdicts are kept the same way.
Montana and others added 3 commits October 27, 2026 09:15
…rash

With the 1500 s cap this PR's nightly read 2 new crashes, both
BIOMD0000000385: given the time to reach its codegen compile, 4.2 MB of C
ran past bngsim's 600 s compile limit, which codegen=True reports as a
RuntimeError. Those rows were TIMEOUT at 900 s, and a timeout they stay:
one anywhere in the cause chain, or in codegen=True's refusal, which
carries the compile's timeout in its text and not as its cause. The other
five rows of the model went TIMEOUT -> PASS.
…before the cap

Three things the review of lanl#1321 found, each pinned by a test that fails
without the fix:

- The locale child's own 600 s limit read as the model's cap: TIMEOUT,
  "not finished within the model's cap". Before the cap it read EXCEPTION,
  and a run a comma-decimal locale stalls, where the run under C finished,
  is that check's finding: from a DIFF or SKIP baseline the gate alerts on
  a new crash, and only files a TIMEOUT as changed. Only a limit the
  deadline set is the cap's now.
- A worker that died after keeping rows gave the rest the note "cap of
  900 s": nothing reached the cap. They now say the worker died, and its
  exit code.
- A worker that raised past check_all dropped the rows it had kept, so a
  check that passed read EXCEPTION. What a worker finished stands however
  it ends; one block in to_results instead of two.

The spent-cap test now asks for TIMEOUT on every row (it allowed SKIP, so a
check skipping past the deadline went unseen), and the docs name the
timeout verdict.
@wshlavacek
wshlavacek merged commit 23106e8 into lanl:main Oct 27, 2026
30 of 32 checks passed
wshlavacek added a commit to Montana/bngsim that referenced this pull request Oct 28, 2026
…onent of a number alone (lanl#1305)

A model's checks share its cap (lanl#1321), and every check added since runs
after main's. The transforms ran in main's loop, ahead of locale: four
more runs that could tip a slow model's locale row (MODEL1006230049, 0053,
0077 already take 689-894 s), which the baseline holds as PASS, to TIMEOUT.

_subst matched names inside numeric literals: with a parameter named e1,
2e1*e1 became 2mt_p1*mt_p1, which does not load.

The branch also merges lanl/main, which it conflicted with (check_all's
deadline, lanl#1321).
wshlavacek added a commit to Montana/bngsim that referenced this pull request Oct 28, 2026
)

verdicts.json of this PR's Path-agreement run 40721476951, on 18c677c
merged with main at 9a26f5b. Every row main's checks give is the same as
in main's scheduled night 40712357505 (lanl#1367 re-baselines main from it),
plus this PR's 1,353 jacobian_values rows. Against the old file:

- BIOMD0000000338|klu_vs_dense PASS -> EXCEPTION, from main (lanl#1302, lanl#1304);
- BIOMD0000000385|ajac_vs_fdjac and |klu_vs_dense TIMEOUT -> PASS (lanl#1321);
- MODEL1112050001|jacobian_values SKIP -> PASS (its base ran this time);
- MODEL1006230049|jacobian_values TIMEOUT -> SKIP, no analytic Jacobian;
- the seven models Model.jacobian() refuses stay SKIP (18c677c).
wshlavacek added a commit to Montana/bngsim that referenced this pull request Oct 28, 2026
…a26f5b (lanl#1302, lanl#1304)

verdicts.json of scheduled run 40712357505 on main at 9a26f5b, byte for
byte. Three cases move, and no others:

- BIOMD0000000338|klu_vs_dense PASS -> EXCEPTION. Since lanl#1302, the
  forced-dense jacobian="fd" leg fails at t = 0.72 (CV_CONV_FAILURE) on
  every Linux runner; the KLU leg completes (ajac_vs_fdjac, the same KLU
  run bit for bit, passes). The model is lanl#1304's.
- BIOMD0000000385|ajac_vs_fdjac and |klu_vs_dense TIMEOUT -> PASS. The old
  file is lanl#1291's run, where the pool killed every row at the 900 s cap;
  with lanl#1321's per-check deadlines and the 1500 s cap these two finish.
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