Skip to content

perf(compile): analyze models as soon as their producers finish - #953

Merged
kvlonge merged 1 commit into
mainfrom
perf/dataflow-model-analysis
Oct 5, 2026
Merged

kvlonge merged 1 commit into
mainfrom
perf/dataflow-model-analysis

Conversation

@kvlonge

@kvlonge kvlonge commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Why

Model analysis ran in topological waves, so every wave waited for its slowest model and a new native thread pool was built per call (58 waves and pools on a ~1,400-model project). Stage 4 of CHI-482 (CHI-500); it also adds the shared pool and prepare/run split that early rule evaluation builds on.

Changes

  • Analyse each model as soon as its producers finish: ready models are batched longest-chain first, up to 4 batches in flight, Python preparation serialised under one lock while native work runs. Results, cache use, invalidation and the first error are unchanged; the wave path remains as the reference and fallback.
  • One native pool per compile shared by analysis and binding validation; the native call is split into a catalog-reading prepare step and a lock-free run step.
  • Ctrl-C: the main thread never takes the analysis lock, every started worker is joined, the interrupt is raised once, and a one-line stderr notice is printed while in-flight batches finish.

Verification

  • Quiet host, alternating, medians: ~1,400-model project cold 11.86 → 11.14 s wall (custom rules on 14.74 → 14.25 s), analysis 4.73 → 4.23 s; dense 3k/10k unchanged; peak memory unchanged or lower.
  • Oracle against the wave schedule and --no-cache on random projects and fixtures; determinism across worker counts and injected delays; interrupt, start-failure and pool-reuse tests.
  • One independent review plus follow-up; make test, make check-ci.

Replace wave-by-wave model analysis with a dependency-driven scheduler. A
model is analyzed once every producer shape it binds against is final, so a
slow model no longer holds back unrelated models in the next topological
level. Worker threads serialize Python preparation and projection under one
lock, while self-contained native analysis releases it, so the next ready
batch is prepared while earlier batches run natively. The calling thread
never takes that lock: it only starts and joins the workers, and on an
interrupt or start failure it cancels and joins every started worker before
re-raising.

Each compile catalog now owns one native analysis pool that every compact
analysis and binding batch reuses, instead of building a pool per call.
Compact analysis splits into a catalog-resolving prepare step and a run step
that no longer borrows the catalog, so catalog updates for later batches
proceed while earlier batches run. Spawned drainer tasks replace the pool
broadcast, so a batch no longer needs every pool thread to finish.

Every model sees exactly the producer shapes, cache entries, and changed
upstream signatures it saw under wave scheduling, so compile output does not
depend on completion order. An ordinary analysis error replays the wave
schedule to raise the same first error; an interrupt or other non-Exception
failure always wins and propagates without a replay.
@kvlonge
kvlonge enabled auto-merge (squash) October 5, 2026 18:19
@kvlonge
kvlonge merged commit 84b50fb into main Oct 5, 2026
34 checks passed
@kvlonge
kvlonge deleted the perf/dataflow-model-analysis branch October 5, 2026 18:38
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.

1 participant