Repository navigation
Stop Rest after cancellation, run Contrib after Baseline at 11 jobs, and share YAML runners by memory - #10231
Merged
hua7450 merged 4 commits intoOct 15, 2026
Conversation
… split household tests - Rest shard 2 used a job-level always(), which kept it running (or let it start) after a manual or concurrency cancellation. Use !cancelled() so it still runs after a shard-1 failure but stops when the run is cancelled. - Group the 11 Baseline groups onto 5 runners and the 9 Contrib groups onto 4, by name, each runner running its groups as sequential steps. Later groups use !cancelled() so an earlier failure does not hide their results. No job depends on another, so re-running a failed runner re-runs only that runner. PR CI drops from 27 to 16 runners, at most 14 at once; both suites still finish before the Rest shard chain. Release CI uses the same matrices. - Split the household Baseline tests: the root files and income/household run one file per subprocess and other subfolders one subprocess each. The former quarter batches peaked at 15.1 GB on the 16 GB runner; locally measured batches (within 1% of CI) now peak at 7.1 GB. - Update the runner-budget, Rest, make-test and batching guards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As one process, baseline/gov/hhs peaked at 15.1 GB on the 16 GB runner in this PR's first CI run (38656284185), the same failure margin as the old household batches. Run each hhs subfolder in its own subprocess, with loose yamls as a trailing batch, like the irs suite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hua7450
marked this pull request as draft
October 15, 2026 01:48
The grouped-runner layout passed CI but needed hand rebalancing as groups grow, and its per-file household branch and gov/hhs split were static fixes for memory that keeps growing. Replace them with mechanisms that adapt: - test_batched.py adds a pytest plugin to every YAML batch subprocess. At each file boundary it reads the process's peak memory (VmHWM; the peak physical footprint on macOS) and, past 8,000 MB, hands the files it has not started to a fresh subprocess. run_batch continues them, merges any inherited JUnit report, and passes only if every part passes. A file always runs whole. Household and gov/hhs return to their upstream layouts. - Baseline and Contrib keep one job per group and add max-parallel 5 and 4, so the Pull request workflow runs at most 14 jobs at once; GitHub starts the next group as one finishes, so lanes balance from real durations. - Release Rest shard 2 also skips when shard 1 was skipped. Measured against the full model locally, household ran as 8 subprocesses at the default budget (peaks 8.1-10.8 GB, previously 15.2 GB in one) and as 20 at a 3,000 MB budget, with exactly 644 cases each time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hua7450
marked this pull request as ready for review
October 15, 2026 02:34
Baseline and Contrib each run 6 groups at once, Contrib once Baseline passes, so the Pull request workflow runs at most 11 jobs at a time. A Baseline failure skips Contrib, and re-running the failed group runs Contrib for the first time without repeating finished work. POLICYENGINE_BATCH_WORKERS=2 lets test_batched.py run a second batch subprocess per runner while MemAvailable allows: admission at 6 GB with a 20 s stagger, a 4 GB hand-off floor at file boundaries, and eviction of the newest subprocess below 2 GB with an exclusive rerun. Over three fork runs this used 32% fewer runner-minutes for the same 34,794 cases with no runner shutdowns; a fixed two-process split crashed a runner in both runs. Plain --workers N and local runs are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 tasks done
This was referenced Oct 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
PR CI runs about 25 jobs at once and about 11.3 runner-hours per run, which competes for the organization's runners when many PRs arrive together. Two failures also recur as tests grow:
This PR changes CI only: no model, parameter, test-case or partner-contract changes, and no test is removed or skipped. Nothing in the new layout depends on hand-tuned groupings, so test growth needs no rearranging.
1. The second Rest shard stops when a run is cancelled
Full Suite - Rest (Python + variables)had a job-levelif: ${{ always() }}. A job-levelalways()is still true after a manual cancel or acancel-in-progresssupersede, so the job kept running, or even started, after the cancel. Two stale runs needed GitHub's force-cancel API: 38625576761 and 38627128097.The job now uses
!cancelled(), so it still runs after a shard-1 failure and its aggregate check still reports. In release CI it also skips when shard 1 was skipped because the release lock failed. The two shards stay sequential.2. Baseline first, then Contrib, 6 groups at a time
needsBaseline. Most PRs change baseline policy, so their Baseline result arrives first.strategy.max-parallel: 6. GitHub starts the next group on a fresh runner as one finishes, so the lanes balance from real durations, with nothing to re-pair as groups grow.push.yaml) uses the same order, strategies, env and steps; a guard test checks that they match.Contrib still runs on every PR. From April to October, 54 failed PR runs failed in Contrib while Baseline passed: baseline changes that broke contrib reform tests (for example Kentucky's family size credit change broke
contrib/states/ky/hb13).3. Two memory-aware YAML subprocesses per runner
A 16 GB, 4-CPU runner running one YAML subprocess is about 25% busy. The Baseline and Contrib jobs now set
POLICYENGINE_BATCH_WORKERS: 2, andtest_batched.pyruns a second batch subprocess only while the runner's memory allows:MemAvailableis at least 6,000 MB, and at most one starts every 20 seconds while the last one loads the model.--workers N(the partners target) behaves as before; localmake testis unchanged.Fork experiment, 20 groups × 5 variants, three runs (38687143268, 38689387981, 38710504296); runner-minutes for the 20 groups per run:
Three or four subprocesses are no faster because memory, not CPU, runs out first. Every memory-aware job passed exactly the same cases as one subprocess.
4. YAML batches size themselves to a memory budget
Memory accumulates across the YAML files one subprocess runs; on the last PR run single batches peaked at about 15 GB on the 16 GB runner (household 15.2 GB,
gov/hhs15.1 GB, New York 14.3 GB).test_batched.py's subprocess wrapper adds a small pytest plugin to every YAML batch (policyengine-corecallspytest.main(..., plugins=[...])through the module, so no Core change):VmHWMon Linux; on macOS the lifetime peak physical footprint, which counts compressed memory);run_batchcontinues them in a fresh subprocess; the batch passes only if every part passes;--junitxmlreport is merged across parts;POLICYENGINE_BATCH_MEMORY_BUDGET_MBoverrides the budget, and 0 runs one file per subprocess.Verification
baseline-tests.yamlandcontrib-tests.yaml, Contrib after Baseline. All 20 groups passed; Baseline finished at 34 minutes and Contrib at 68, with 345 Baseline + Contrib runner-minutes (515 on this PR's previous CI run) and at most 6 of their jobs at once.always()control kept running in every cancel case.--workers, the floor hand-off (Linux), the 11-job cap, Contrib after Baseline, and PR/release parity. Five deliberate mutants of the scheduler (no rerun reservation, no admission, no stagger, a lost reservation, an empty split) each fail them.Designs tried first
Test plan
make formatchangelog.d/Hub microsimulation impact
None: only CI workflows, batch routing, code-health guards and the changelog change.
axiom: n/a: CI job routing
🤖 Generated with Claude Code