ix shard: static profile-free strategy (walk-edge min-cut + predicted-cost rebalance) - #550
Draft
arthurpaulino wants to merge 1 commit into
Draft
ix shard: static profile-free strategy (walk-edge min-cut + predicted-cost rebalance)#550arthurpaulino wants to merge 1 commit into
arthurpaulino wants to merge 1 commit into
Conversation
arthurpaulino
force-pushed
the
ap/partition
branch
from
August 12, 2026 13:44
7771027 to
da3bb38
Compare
…-cost rebalance)
`ix shard <path.ixe> --shards N` now partitions an environment without
an out-of-circuit profiling run. The previous pipeline (unchanged, now
behind `--profile <path.ixprof>`) partitions the delta-unfold graph
recorded by `ix profile` and balances on Rust-kernel heartbeats; both
of those signals turn out to mis-model what the Aiur kernel actually
pays, and the static strategy beats it on every measured axis.
## Quality metric
Partition an env into N shards, typecheck every shard with the native
IxVM (real blake3), and read `Total FFT cost` from
`ix check --ixe <env> --ixes <manifest> --shard K --stats-out F`.
Lower mean = cheaper overall; lower stddev = better balance; the max
shard is the parallel wall-clock. All numbers below are from this
harness on Init (60,599 consts, 8 shards) and Init+Std (105,492
consts, 24 shards).
## What the Aiur kernel actually ingresses
The kernel's constant resolver (`get_ci`) is a memoized Aiur function
that fault-loads and blake3-verifies each constant at its first use, so
its distinct-key set IS the in-circuit ingress set. Enumerating it
during shard runs and comparing against (a) the shard's owned set,
(b) the static thin frontier (`ixon::shard_claim::walk_edges`), and
(c) a journal of the Rust kernel's delta-unfold targets showed, on two
probes (one binder-heavy, one with ~35k delta unfolds):
- owned and thin frontier are loaded completely, always;
- the blocks behind frontier projection wrappers are loaded always
(block granularity absorbs this term: wrappers share their block's
vertex);
- genuine below-frontier reduction read-through is 0.5-1.5% of loads;
- every cross-shard Rust delta-unfold target was loaded by Aiur, but
~90% of them were already thin-frontier members.
So Aiur ingress is ~99% determined by the static reference structure:
cutting reference edges is what costs, and the profiled delta graph is
nearly redundant with it. Under the baseline partition of Init the
delta objective saw 1.1 MB of cross-shard ingress while the realized
static frontier was 71 MB — the old min-cut was optimizing a shadow of
the real cost.
## Cost model
Least-squares fit of measured per-shard FFT cost over static features,
trained on 56 shards from 7 differently-shaped partitions of Init:
cost(S) ~ 28201 * owned_bytes(S)
+ 681 * sum_{b in S} size(b)^1.5
+ 38000 * frontier_bytes(S)
(sizes = serialized block bytes; mean |err| 4.7% on shards >= 3e11).
A frontier byte pays ingress only (blake3 + parse); an owned byte
additionally pays typechecking, which concentrates superlinearly in
large proof bodies — hence the per-block ^1.5 term. It is per-block,
not (sum size)^1.5: how bytes are packaged into constants is exactly
what it measures (ten 1 KB lemmas cost ~3x less than one 10 KB proof
at equal byte count), and per-block additivity is what makes it usable
as a movable weight. Applied unchanged to Init+Std at 24 shards the
model predicted per-shard cost with 4.5% aggregate bias — the
constants are Aiur-kernel physics, not env-specific.
## Why min-cut + rebalance, not min-cut alone
Byte-balanced static min-cut already dominates the baseline on mean
(cut the real frontier), but its realized stddev stalls at ~15%, and
two refinements measured WORSE:
- tightening the per-bisection tolerance 5% -> 2%: stddev 18.1% — the
residual is model error (bytes can't see the superlinear term), not
loose balance;
- using the fitted weight as the bisection balance weight: stddev
19.6% — per-cut tolerance compounds across log2(N) levels of
integer budget splits into 1.6-1.9x max/min drift regardless of the
weight's quality.
The fix is a global post-pass: greedily move blocks from the most to
the least expensive shard under the fitted model (frontier deltas
updated exactly per move; the model is additive so each move is O(deg))
until predicted costs are within 1% of the mean. It converges in tens
of moves from a byte-balanced start and is idempotent at the fixed
point. No per-bisection mechanism can do this job: the frontier term
each shard pays depends on the cut itself, which vertex-weight
balancing cannot see.
## Measured results
Init, 8 shards (mean / stddev / max shard, FFT cost units):
profiled baseline 1.020e12 / 17.7% / 1.391e12
static + rebalance 0.729e12 / 7.2% / 0.777e12 (-29% / -/ -44%)
Init+Std, 24 shards:
profiled baseline 6.317e11 / 30.6% / 1.019e12
static + rebalance 4.305e11 / 8.9% / 5.005e11 (-32% / - / -51%)
The baseline degrades with shard count (more shards = relatively more
frontier, which its objective can't see, and deeper bisection drift);
the static strategy holds.
## Implementation
- `ix_kernel::shard`: `shard_static` (byte-balanced min-cut over the
static profile through the existing multilevel partitioner, then
`rebalance_static`, then the usual `.ixes` manifest), model constants
documented at `STATIC_OWNED_PER_BYTE`. Rebalance bookkeeping
exactness is tested via second-pass idempotence.
- `crates/ffi`: `static_block_profile` builds a `BlockProfile` from a
`.ixe` alone — vertices are ingress units (Muts block / standalone
constant) with real serialized sizes, edges are block-level
`walk_edges`, and the byte balance weight rides the `subst` counter
(the partitioner's vertex weight is linear in op counters, so
vweight is exactly proportional to size). `rs_shard_env_static` is
the FFI entry; `rs_shard_static_graph` (`ix shard graph`) now shares
the same builder.
- CLI: the positional argument is the `.ixe`; `--profile <path.ixprof>`
selects the previous pipeline verbatim (verified byte-identical
output); a `.ixprof` passed positionally errors with a migration
hint; `--max-cycles`/`--max-ram` budgeting still requires
`--profile` (its cycle/RAM model is calibrated on profiled op
counters — a static cap mode is future work).
arthurpaulino
force-pushed
the
ap/partition
branch
from
August 12, 2026 14:17
da3bb38 to
ab6983a
Compare
arthurpaulino
marked this pull request as draft
August 12, 2026 14:34
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.
ix shard <path.ixe> --shards Nnow partitions an environment without an out-of-circuit profiling run. The previous pipeline (unchanged, now behind--profile <path.ixprof>) partitions the delta-unfold graph recorded byix profileand balances on Rust-kernel heartbeats; both of those signals turn out to mis-model what the Aiur kernel actually pays, and the static strategy beats it on every measured axis.Quality metric
Partition an env into N shards, typecheck every shard with the native IxVM (real blake3), and read
Total FFT costfromix check --ixe <env> --ixes <manifest> --shard K --stats-out F. Lower mean = cheaper overall; lower stddev = better balance; the max shard is the parallel wall-clock. All numbers below are from this harness on Init (60,599 consts, 8 shards) and Init+Std (105,492 consts, 24 shards).What the Aiur kernel actually ingresses
The kernel's constant resolver (
get_ci) is a memoized Aiur function that fault-loads and blake3-verifies each constant at its first use, so its distinct-key set IS the in-circuit ingress set. Enumerating it during shard runs and comparing against (a) the shard's owned set, (b) the static thin frontier (ixon::shard_claim::walk_edges), and (c) a journal of the Rust kernel's delta-unfold targets showed, on two probes (one binder-heavy, one with ~35k delta unfolds):So Aiur ingress is ~99% determined by the static reference structure: cutting reference edges is what costs, and the profiled delta graph is nearly redundant with it. Under the baseline partition of Init the delta objective saw 1.1 MB of cross-shard ingress while the realized static frontier was 71 MB — the old min-cut was optimizing a shadow of the real cost.
Cost model
Least-squares fit of measured per-shard FFT cost over static features, trained on 56 shards from 7 differently-shaped partitions of Init:
cost(S) ~ 28201 * owned_bytes(S)
+ 681 * sum_{b in S} size(b)^1.5
+ 38000 * frontier_bytes(S)
(sizes = serialized block bytes; mean |err| 4.7% on shards >= 3e11). A frontier byte pays ingress only (blake3 + parse); an owned byte additionally pays typechecking, which concentrates superlinearly in large proof bodies — hence the per-block ^1.5 term. It is per-block, not (sum size)^1.5: how bytes are packaged into constants is exactly what it measures (ten 1 KB lemmas cost ~3x less than one 10 KB proof at equal byte count), and per-block additivity is what makes it usable as a movable weight. Applied unchanged to Init+Std at 24 shards the model predicted per-shard cost with 4.5% aggregate bias — the constants are Aiur-kernel physics, not env-specific.
Why min-cut + rebalance, not min-cut alone
Byte-balanced static min-cut already dominates the baseline on mean (cut the real frontier), but its realized stddev stalls at ~15%, and two refinements measured WORSE:
The fix is a global post-pass: greedily move blocks from the most to the least expensive shard under the fitted model (frontier deltas updated exactly per move; the model is additive so each move is O(deg)) until predicted costs are within 1% of the mean. It converges in tens of moves from a byte-balanced start and is idempotent at the fixed point. No per-bisection mechanism can do this job: the frontier term each shard pays depends on the cut itself, which vertex-weight balancing cannot see.
Measured results
Init, 8 shards (mean / stddev / max shard, FFT cost units):
profiled baseline 1.020e12 / 17.7% / 1.391e12
static + rebalance 0.729e12 / 7.2% / 0.777e12 (-29% / -/ -44%)
Init+Std, 24 shards:
profiled baseline 6.317e11 / 30.6% / 1.019e12
static + rebalance 4.305e11 / 8.9% / 5.005e11 (-32% / - / -51%)
The baseline degrades with shard count (more shards = relatively more frontier, which its objective can't see, and deeper bisection drift); the static strategy holds.
Implementation
ix_kernel::shard:shard_static(byte-balanced min-cut over the static profile through the existing multilevel partitioner, thenrebalance_static, then the usual.ixesmanifest), model constants documented atSTATIC_OWNED_PER_BYTE. Rebalance bookkeeping exactness is tested via second-pass idempotence.crates/ffi:static_block_profilebuilds aBlockProfilefrom a.ixealone — vertices are ingress units (Muts block / standalone constant) with real serialized sizes, edges are block-levelwalk_edges, and the byte balance weight rides thesubstcounter (the partitioner's vertex weight is linear in op counters, so vweight is exactly proportional to size).rs_shard_env_staticis the FFI entry;rs_shard_static_graph(ix shard graph) now shares the same builder..ixe;--profile <path.ixprof>selects the previous pipeline verbatim (verified byte-identical output); a.ixprofpassed positionally errors with a migration hint;--max-cycles/--max-rambudgeting still requires--profile(its cycle/RAM model is calibrated on profiled op counters — a static cap mode is future work).