Repository navigation
Conversation
|
Warning This PR has more than 500 changed lines and does not include a spec. Large features and architectural changes benefit from a short spec before implementation review. If this PR is a bug fix, small cleanup, or otherwise does not warrant a spec, feel free to ignore this message. |
Documentation blast radius (advisory)These regions may need doc/spec/book updates based on changed paths. Changed files in this PR: 18
|
PCS Profile Benchmark
13 of 13 profiles passed. Times are medians of Each sample verifies the same proof first with the configured multi-threaded pool and then with one thread. Both timings reuse the same verifier setup. Merge-base comparisons are available for Benchmark shards
Public opening statements
One-hot profiles generate deterministic witnesses with one Direct evaluates the public setup contribution during Stage 2. Recursive carries the same check through a Stage 3 setup-product sumcheck. Both modes execute the complete fold schedule and terminal verification. The chunked profiles Generated profiles may select different A, B, and D ring dimensions at different fold levels. The short profile names omit those dimensions. Each sample generates deterministic witnesses and opening points, prepares setup, commits, proves, serializes the proof, checks its size, prepares verifier setup, and verifies the claimed openings. It does not test malformed proofs. Phase time
Proof size and protocol shape
Grinding retries are rejected attempts at each fold, listed in measured-run order. Zero means the first sampled nonce was accepted. Each multi-group profile has two precommitted nv16 singleton polynomials and two polynomials in the displayed final group. Native proof accounting
Memory and setup size
Deltas are shown only for profiles with a matching merge-base case. Negative is smaller or faster. The uploaded |
CI test timing
Run summary
Wall time spans 2 parallel nextest slice shards. Slowest tests
Regressions vs main
New slow testsNo new tests ≥30s vs main baseline. |
V6 ranks schedules by padded setup first. With no explicit policy budget, V6 planning now searches under a power-of-two setup budget that starts at one field element. A pass that finds no schedule records the smallest setup it rejected, and the next pass uses that setup padded to a power of two. Fold candidates whose own level setup exceeds the budget are dropped when they are generated, and pending schedules over the budget are dropped in `child_choice`. An explicit policy budget keeps its single pass, and V5 is unchanged. Exactness: - Admission under a power-of-two budget B is the same as padded setup <= B. Every V6 comparison that removes a candidate keeps a side whose padded setup is no larger: both projection dominance rules, both primary strict-dominance checks, the root and recursive guided bounds (`is_strictly_worse_than`, `setup_envelope_is_strictly_worse_than`, `is_strictly_worse_for_recursive_parent`, `..._payload`), and the level prune, whose coordinates include the level setup. So an over-budget candidate never removes an in-budget one. - No selection is a bounded top-k. The setup-first and payload-first lists in each `ParentObservableKey` bucket are dominance frontiers with no size limit, built by retain-then-push. The bounded split domain (`BoundedBalancedExtremesV1`), the precommit opening-product cap and the grinding query limit depend only on geometry, never on candidate outcomes. Memo eviction only forces recomputation, and every pass starts with a fresh memo, including the setup-prefix cache, so nothing computed under one budget is read under another. - Together, these mean the in-budget part of every frontier equals the in-budget part of the unbudgeted frontier. Insertion order is unchanged, an in-budget insert is rejected or evicts members only because of in-budget members, and an over-budget insert never touches in-budget members. - Setup is a maximum over levels, the terminal and offloaded children, so it never decreases as a partial schedule grows. Offloaded edges charge the same level setup as direct edges. Rejecting a partial schedule or level fold over B therefore never rejects a completion within B. Terminal candidates are not filtered, but they reach the root only through `child_choice`. - A pass that selects W returns the unbudgeted winner, because that winner's padded setup is at most W's, so it fits and stays in the frontier. A pass that rejects nothing is exactly the unbudgeted search. A skipped budget below the next padded rejected setup admits nothing new. If the next budget overflows, the last pass runs without a budget. The starting budget affects only the number of passes. Diagnostics report each pass's budget, generated candidates and descriptor builds (`setup_budget_passes=`). The search-work counters sum over all passes. Tests: one-hot recursive nv 14/16/18 on the three restricted domains of the unpruned-oracle fixtures must select the same descriptor bytes and estimate as one pass with a non-binding explicit budget. The final budget must equal the selected padded setup, and earlier budgets must increase strictly and stay below it. An ignored test checks the same for fp32_dense_recursive nv20/22 and fp128_dense_recursive nv20; it takes about 100 s. fp64_dense_recursive nv21: 100.6 s -> 1.85 s. nv25: 129 s -> 15.4 s. The output is byte-identical to main.
The generator now records four deterministic search-work counters per row (suffix calls, completed states, generated fold candidates and descriptor builds). `--planner-cost-snapshot` writes them and `--planner-cost-baseline` fails when a row is added or removed or any counter grows by more than a quarter. Preplanned scalar rows carry the counters of the search that produced them. The tracked baseline is artifacts/schedule-planner-cost.tsv. The drift job checks it and bounds the regeneration step at 20 minutes.
855dca2 to
dfe4da3
Compare
The test that compares the combined terminal-and-fold search with the two independent searches used a recursive configuration. Under the recursive selection rule the combined search returns the independent searches as they are, so the comparison held trivially, and the two-walk search that direct configurations use had no equivalence test. The test now runs both selection rules, each with and without keeping the split frontier. Every case must match the independent searches exactly and keep an L2-routed fold, as before.
- Raise the schedule regeneration timeout from 20 to 35 minutes. CI measured the step at about 18 minutes, and runner variance alone has moved it by about 1.3x, so 20 minutes left little headroom. - Say what the planner cost baseline actually counts: search size (suffix calls, completed states, candidates, descriptors). Slower per-candidate pricing does not move those counters; only the timeout catches it. - Correct the suffix memo comment: entries are optima under the context's setup budget, which is not in the memo key, so each budget pass needs a fresh memo. - Accumulate setup-prefix cache hits and misses across budget passes, like the other diagnostics counters, instead of keeping only the last pass. Diagnostics only; planner output is unchanged.
|
Minor: an unreachable guard in the widening step.
|
The setup budget records only sizes above the pass limit, so the `next > limit` filter on the padded widening step could never remove a value. Replace it with a debug assertion that documents the invariant; overflow remains the only path to the final unbudgeted pass.
Summary
The schedule planner is the offline tool that chooses protocol parameters for every supported configuration. Its output is checked in under
artifacts/schedules/andartifacts/schedule-catalog.tsv. This PR makes it much faster for recursive configurations, without changing any output, and adds a CI check that catches growth in the planner's search size.1. A setup-size ceiling for recursive configurations (
perf(planner)commit)Recursive configurations have a fixed selection rule: of all candidate schedules, the one with the smallest public setup wins, and the setup size is first rounded up to a power of two. Until now the planner built and compared every schedule of every size, and only then kept the smallest.
The planner now searches with a ceiling on setup size:
A configuration that sets an explicit setup budget still runs once, as before. Direct (non-recursive) configurations are unchanged.
2. A CI check on planner search size (
feat(planner)commit)artifacts/schedule-planner-cost.tsv.Motivation
The planner has been getting slower with each new protocol option, and nothing in CI noticed. On main, the drift job regenerates every artifact on every PR, which takes about 23 minutes. A recent regression on #255 made one row ten times slower, and it was found only after the job hit its 6-hour timeout. The new timeout would stop such a run after 35 minutes; the search-size counters catch only the part of a regression that makes the search larger.
Why the ceiling gives exactly the same schedule. The full argument is in the first commit message.
Measured results. All runs are on an M4 Max, comparing main
e423a3903and this branch side by side.fp64_dense_recursivenv21fp64_dense_recursivenv25Work counts for the two benchmark rows:
On CI the regeneration step took about 17.6 minutes on this branch (runs 40244375273 and 40250457121), against 23 to 30 minutes on main with the same runner type. That is less than the 2× local speedup suggested.
What this PR does not fix. Direct configurations now account for 79% of planning time, and this PR leaves them alone. Two ideas for them were measured and dropped:
Caching their partial results with a key that leaves out the current error bound would never hit. On one row, 340,087 lookups had 340,087 distinct keys.
A setup ceiling would repeat pruning that the planner already does for their first level.
Skipping subproblems that cannot beat the best schedule found so far (branch and bound). We tested the strongest version possible. The starting best was the final winner, and each subproblem's bound was its true best cost, taken from a finished run. Real bounds can only be weaker.
Every run picked the same schedule, but the work saved is too small, so branch and bound does not help direct configurations.
Reusing repeated candidate searches. For each subproblem the planner searches candidates twice, once using the source's measured coefficient size and once without it. Subproblems that differ only in that size repeat the second search exactly, 2.6 to 3 times each. Caching it gave identical output and cut direct-row planning time by 10% (864 s to 778 s summed over 74 rows). But the repeats are spread across the whole search, so the cache must be large: peak memory for the full catalog rose from 2.6 GB to 5.4 GB. Capped at 65,536 entries it saved 6.5% for 0.5 GB on the heaviest row. Neither is worth it, so it is not in this PR.
Their remaining cost is spread over shared parameter-sizing code, a few percent per function. Similar caches of witness sizing and edge pricing would each repeat the memory trade-off above for at most about 10%.
Testing
--cargo-profile ci-test --profile ci.akita-planner --features catalog-gen(lib, bins, tests): 157 passed. This includes the unpruned-search comparison tests and the exhaustive tests. The ignored catalog-row test also passes when run.akita-params,akita-schedules,akita-challenges: 309 passed.akita-config --test trusted_schedule_artifact: 26 passed.af430d53a(timeout, comments, docs, cache-count accumulation):cargo test -p akita-planner --lib -- diagnostics::, 3 passed.cargo fmt --all --checkpassescargo clippymode passes: all three CI configurations.Other checks:
artifacts/schedules/, and 115 of 115 catalog rows matchartifacts/schedule-catalog.tsv.artifacts/schedule-planner-cost.tsvbyte for byte with 2, 4 and 6 workers.taplo fmt --check,typos,cargo machete --with-metadata, both Rust line-cap scripts,check-crate-deps.shfor the six crates,check-shared-field-identity.sh,check-external-schedule-artifacts.shandscripts/tests(179 passed).New tests
padded_setup_budget_passes_match_one_unbounded_pass, intest/adaptive_search/setup_budget.rs(0.9 s):padded_setup_budget_matches_one_unbounded_pass_on_catalog_rows(ignored, about 100 s):fp32_dense_recursivenv 20 and 22 and onfp128_dense_recursivenv 20.diagnostics.rs::setup_budget_passes_record_work_since_their_start: checks the per-pass work counts and the diagnostics text.bin/planner_cost_snapshot.rs:Changed tests
No assertion is weakened.
generated_families/tests.rs, two tests now read the schedule from the newPreplannedSchedulestruct. One of them also checks that a work count was recorded.emit/materialize.rs::emitter_rejects_preplanned_rows_after_runtime_geometry_changesbuilds aPreplannedSchedulewith zero cost. Its assertions are unchanged.candidate/recursive/tests.rs::combined_terminal_and_fold_views_match_independent_searches(test(planner)commit) used only a recursive configuration. For those, the combined search returns the two independent searches unchanged, so the comparison could not fail. It now also runs the direct selection rule, which is the only path that uses the combined search. Its assertions are unchanged.Deleted tests: none.
Security Considerations
The planner runs offline. It changes how fast schedules are found, not which schedules are chosen, and the regenerated artifacts are byte-identical.
Breaking Changes
None. No protocol, transcript, proof format, descriptor or version changes.
#255 touches the same planner files, so whichever of the two lands second needs a rebase.
🤖 Generated with Claude Code