Sb/cluster - #623
Draft
samuelburnham wants to merge 4 commits into
Draft
Conversation
…pelines crates/ffi/src/numa.rs: topology from sysfs and sched_getaffinity, per-thread pinning (sched_setaffinity + set_mempolicy MPOL_BIND), one Rayon pool per domain, cgroup memory limit, resident bytes per node. Knobs IX_NUMA, IX_NUMA_POLICY, IX_NUMA_THREADS, IX_NUMA_PACK. Stage 1: `ix prove --lookahead` runs one execute-next-while-proving pipeline per NUMA domain inside a single process (leaves split by measured peak, IX_PROVE_LANES), sharing the environment and proving systems. Stage 2: every join is placed on a domain under a per-lane budget (90% of the node, capped by --max-ram and the cgroup limit), at most two per node, solo tail unpinned. Dependency-free joins run on per-lane queues that prepare the next slot's execution record while the current one proves; with packing on a lane runs two such queues when both fit. Static RAM weights per join shape: direct/mixed 180 GiB (a direct join is ~200 GiB resident after main's function groups), lifts/structural 195 GiB + 1.25 MiB per subject with a 390 GiB floor above 65,536 subjects. Parallel proof import; every slot logs its node's resident peak; `ix aggregate --texray`. IX_AGGREGATE_SHARDS=a-b,c aggregates one subtree of a manifest from an existing run's leaf proofs (experiments; leaf claims do not depend on the manifest size). Rebased on main's function groups (#619/#620): the verify command's backend construction compiles with the IxVM and ix_aggr groupings.
After function groups (#619) CircuitType::Function { idx } enumerates circuits, but raw_of still read record.function_queries[idx], charging circuit i with function i's rows. Projections came out 0.83-1.72x (median 1.30x) off on Mathlib shards while measured peaks were unchanged, splitting shard 217 (481 GiB projected, 284 real) at prove time. Sum the members' query counts instead. Shard 0: 378 -> 250 GiB projected vs 248 measured.
The composed verdict (no --shard, proofs supplied) reconstructed every shard claim in Lean, one shard at a time on one core (~15 s per Mathlib shard, 58 min for the 246-leaf manifest before it was killed). Route it through the Stage 2 import instead: a verify_only mode of the native aggregate entry reconstructs all claims in Rust, binds each proof to its shard by claim digest, verifies every proof in parallel (IxVM or healed ix_aggr) and requires exactly one valid proof per shard. Mathlib, 246 proofs: 1.3 s claims + 0.3 s verification inside a 54 s process (environment load). --record index writes are kept; --shard K is unchanged.
docs/shard-pipeline.md: proving a whole environment on one NUMA box (shard -> refine -> Stage 1 -> Stage 2 under one cgroup slice, THP always), a self-contained end-to-end reproduce section (build, per-boot box setup, slice, every command, what to look for in the logs, resume semantics, subtree experiments), and the measured Mathlib budget: 4 h 36 min total on the rebased binary (Stage 1 2 h 44 min, Stage 2 1 h 52 min), 6 h 20 min before main's function groups, 28 h in production. docs/numa-slot-pinning.md: the pinning design and the calibration behind the slot weights.
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.
Prove Mathlib on one metal-48xl box in 4 h 36 min: NUMA lanes, lane pipelines, native verify, peak-model fix
Summary
This branch makes
ix proveandix aggregateuse a large NUMA box as threeisolated proving domains instead of one flat machine, and fixes two things
found on the way. Measured end to end on Mathlib (679,499 constants, 246 leaf
shards) on an r8i.metal-48xl (Xeon 6975P-C, 96 cores / 192 threads, 1.5 TiB,
three sub-NUMA domains of ~504 GiB):
The root proof
b060ee48…verifies all 679,499 constants with 0 undischargedassumptions in 5 s. Root and leaf proofs halved in size with main's function
groups (#619/#620), which this branch is rebased on.
What the branch does
NUMA lanes (
crates/ffi/src/numa.rs, new). Detects the topology(sysfs ∩
sched_getaffinity), pins threads withsched_setaffinity+set_mempolicy(MPOL_BIND), builds one Rayon pool per domain, reads the cgroupmemory limit, and reports resident memory per node. Env knobs
IX_NUMA,IX_NUMA_POLICY,IX_NUMA_THREADS,IX_NUMA_PACK.Why: a single proof cannot use more than one domain (whole box 1.08–1.18x over
one node, the prover is memory-latency bound), but three pinned lanes scale
3.0x. Unpinned in-process slots reached only ~1.7x.
Stage 1: one process, one lane per domain.
ix prove --lookaheadsplitsthe selected leaves across lanes (LPT by measured peak,
IX_PROVE_LANES) andruns the existing execute-next-while-proving pipeline once per lane, sharing
the environment and proving systems. Replaces three
numactlprocesses.Stage 2: NUMA-pinned scheduler and lane pipelines. Every join is placed
on a domain under a per-lane budget (90 % of the node, capped by
--max-ramand the cgroup limit); at most two joins share a node; the dependency tail
runs unpinned. The dependency-free joins (all direct joins) run on per-lane
queues that prepare the next slot's execution record while the current one
proves, hiding ~30 s per join; with packing on, a lane runs two such queues
when both fit (2 × (180 + 40) GiB ≤ 453 GiB), so Mathlib's 105 direct joins
run six-wide. RAM weights are static per join shape: direct/mixed 180 GiB
(measured ≈ 200 GiB resident after function groups), lifts/structural
195 GiB + 1.25 MiB per subject with a 390 GiB floor above 65,536 subjects
(the subject term is the join's own claim work; the old flat weight OOM-killed
a node). Proof import is parallel; every slot logs its node's resident peak.
Native
ix verify --ixes(composed verdict). The Lean path reconstructedeach shard's claim on one core (~15 s per Mathlib shard, an hour for the
manifest). It now runs through the Stage 2 import: claims in Rust, every proof
bound to its shard and verified in parallel, exactly one valid proof per
shard — 1.6 s of work inside a 54 s process (environment load).
Peak-model fix (
crates/aiur/src/synthesis.rs). After #619 the prover RAMprojection charged circuit i with function i's rows. Projections came out
0.83–1.72x off (median 1.30x) while measured peaks were unchanged, and one
Mathlib leaf that needs 284 GiB was projected at 481 GiB and split at prove
time. The model now sums a circuit's member rows; shard 0 projects 250 GiB
against 248 GiB measured. This is a regression in main and could go upstream on
its own, as could the native verify.
Experiment knob.
IX_AGGREGATE_SHARDS=a-b,caggregates only that subtreeof a manifest from an existing run's leaf proofs (leaf claims do not depend on
the manifest size). It is what made the A/B measurements below cheap.
Docs.
docs/shard-pipeline.mddocuments the workflow (shard →refine→Stage 1 → Stage 2, one cgroup slice, THP
always) and has a self-containedend-to-end reproduce section: build, per-boot box setup, every command, what
the first log lines should show, resume semantics, and how to run a Stage 2
experiment on a subtree.
docs/numa-slot-pinning.mdis the design andcalibration note. The raw logs and per-run records behind the numbers below
are kept outside the PR.
Measurements behind the decisions
enabled=always(87 M → 0.66 M first-touch faults); set per bootNot in this branch
Half-pools (rejected for complexity), PoW substitution in the recursion
parameters (measured only), kernel pinning.
Checks
lake build ixandlake build IxTestsbuild; the shard-pipeline runner(
IxTests --ignored shard-pipeline, whose summary is now per NUMA lane) andthe ffi/aiur test suites pass; workspace clippy with warnings denied and
rustfmt are clean.
Reproduce
Section 5 of
docs/shard-pipeline.mdis the runbook: THPalwaysandnuma_balancing=0per boot, one cgroup slice sized from the machine,ix shard→ix shard refine→ix prove --lookahead→ix verify --ixes→ix aggregate --direct-joins --jobs 0 --max-ram 1350→ix verify --aggregate,each a single command under
systemd-run --scope, all resumable by rerunning.