Skip to content

test(lfm): lever-0 census — the tenant-width bill of a per-table aggregator leg - #955

Merged
MauroToscano merged 7 commits into
per-table-gpufrom
pt/census
Sep 7, 2026
Merged

test(lfm): lever-0 census — the tenant-width bill of a per-table aggregator leg#955
MauroToscano merged 7 commits into
per-table-gpufrom
pt/census

Conversation

@MauroToscano

@MauroToscano MauroToscano commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Measures lever 0 — what a per-table aggregator pays to open a wrap proof whose hash matrix is 316/436/612 columns wide instead of 3,056 — by closed form over the real AIR shapes, plus an emission arm at fixture scale. No proving, no box time on the critical path.

Box status: green. All five tests pass on box B at a8d7d081 (5 passed; 0 failed; 0 ignored, 1.68s) and make lint at the same head exits 0. Logs: relays/C_rerun2_a8d7d081.log.

An earlier run at 1de0e3b4 found a bug in the census itself — the BLAKE3 tenant named HasherKind::Blake3, which instantiates the BLAKE3 socket as LFM_HASH (2,980 main + 599 aux), a chip the real proof does not carry. hash_pin::BLOCK_HASHER is Test on a BLAKE3-pinned build and the record agrees (LFM_HASH: 4 rows, 0 used, 28 main, 3 aux). Fixed in 84940a3f and pinned by the_blake3_tenant_socket_matches_the_record. It inflated the BLAKE3 baseline by 1,185 blocks/query (1,201 for the socket against the idle chip's 16) and so made lever 0 look bigger than it is; the table below is post-fix.

The ratio

Per-query blocks over the aggregator's whole leg, at the recorded per-table wrap's widths and heights (bench_cache/optladder_2026-08-21/TIP/tip-wrappt-24.stdout, wrap preset blowup 4 / 110 q). The keccak axis is held fixed on both sides of each ratio, because it moves the answer more than the hash does.

tenant sub-proofs blocks/query factor leg @110q
keccak family present on both sides (today's shape)
BLAKE3 15 5,252 1.000 577,720
RPX 14 4,067 0.774 447,370
RPO 14 4,097 0.780 450,670
Poseidon 14 4,141 0.788 455,510
keccak family retired on both sides
BLAKE3 12 4,167 1.000 458,370
RPX 11 2,982 0.716 328,020
RPO 11 3,012 0.723 331,320
Poseidon 11 3,056 0.733 336,160

MEMORY-MODEL §7 lever 0 predicts 0.370× for RPX. That reading takes "1,247 hash-matrix blocks of 1,846 per query" — a batched per-query bill — and applies its share to the per-table format. In the per-table format every table walks its own Merkle path, so the path terms are 63% of the bill and the hash matrix is 24-30% of it, not 68%.

The model contradicts its own multiplier, which is a stronger check than my arithmetic disagreeing with its arithmetic. §1.2 puts the per-table/batched compression ratio at the aggregator at 2.44×, and the leaf term is format-invariant (each table's leaf is absorbed once per query either way; only the paths multiply). A hash matrix at 67.6% of the batched bill is therefore necessarily 67.6/2.44 = 27.7% of the per-table one. That is what the census measures.

Agreement with the record, which is what says the method is sound and only the reading was wrong:

quantity this census recorded delta
hash-matrix blocks/query 1,245 (MEASURED, box) 1,247 (P3-CENSUS-AB) −0.2%
per-table bill, blocks/query 5,252 5,136 (564,947 / 110 q) +2.3%

The fan-in rule, applied

The block is 10 epochs of 2^22 (measured guest, 39.6M cycles), so 10 wraps. The fan-in decision does not move with that count: a level-1 node costs f · leg + glue, a function of the fan-in alone, so the wrap count sets how many nodes there are and not how big the biggest one is.

Rule: ≥ 25% headroom under 2^21 at fan-in 3 ⇒ 3, else 2.

tenant fan-in 2 fan-in 3
RPX 1,364,450 = 65.1% of 2^21, headroom 34.9% 1,811,820 = 86.4%, headroom 13.6%
RPO 1,371,050 = 65.4%, headroom 34.6% 1,821,720 = 86.9%, headroom 13.1%
Poseidon 1,380,730 = 65.8%, headroom 34.2% 1,836,240 = 87.6%, headroom 12.4%
RPX, keccak retired 1,125,750 = 53.7%, headroom 46.3% 1,453,770 = 69.3%, headroom 30.7%

All four rows are box-MEASURED at 1de0e3b4 and are unaffected by the socket fix, which touched only the BLAKE3 baseline.

⇒ fan-in 2. Three independent reasons and they agree. The rule fails at fan-in 3 on today's shape (13.5% < 25%). The glue term is known to be under-counted by 34-42% (MEMORY-MODEL §1.3(iii)), a band wider than fan-in 3's entire headroom; fan-in 2 survives it. And under the emission arm's own measured glue read multiplicatively — the pessimistic bound — a fan-in 3 node lands at 2,109,260 invocations against the 2^21 cliff of 2,097,152: it crosses, and doubles its hash table. Fan-in 2 lands at 1,406,173, still 32.9% clear.

glue reading fan-in 2 fan-in 3
additive, production-anchored (MEMORY-MODEL §1.3(iii), +469,710/node) 1,364,450, 34.9% clear 1,811,820, 13.6% clear — fails the rule
multiplicative, measured (×1.5716, the pessimistic bound) 1,406,173, 32.9% clear 2,109,260 — crosses 2^21
query-invariant (glue is the fixed spine only) 894,740, 57.3% clear 1,342,110, 36.0% clear

Fan-in 2 clears the rule under all three. Fan-in 3 clears only under the third, which is the least defensible because it assumes the glue has no per-query term at all. The recommendation does not rest on the 0.58% by which fan-in 3 crosses in the second row: it already fails the rule in the first.

The tree is treated as flat in node size: every level reuses the wrap leg, on the assumption that a leg over a level-1 aggregator proof costs about the same, since the chip set and widths are identical and only LFM_HASH's height moves one power of two. That is the modelling assumption behind reusing one leg cost per level, not a measured figure. Under it the trade is 11 proofs of similar size (fan-in 2) against 7 similar but 33% bigger each (fan-in 3).

fan-in 2:  10 → 5 → 3 → 2 → 1   11 aggregator proofs, 4 levels, every node ~1.36M invocations, 2^21
fan-in 3:  10 → 4 → 2 → 1        7 aggregator proofs, 3 levels, every node ~1.81M invocations, 2^21

Level-1 RPX memory over 10 wraps, both affine laws, non-hash floor as a band (863.7 cells/invocation MEASURED from the recorded wrap census, to 1,727.4 DERIVED from §4's aggregator back-out):

fan-in cells RSS fitted RSS anchored
2 1.86-3.04B 60-97 GiB 52-85 GiB
3 2.25-3.81B 72-122 GiB 63-106 GiB

Both fit the 128 GiB envelope; only fan-in 2 fits once the glue band is applied.

Two findings this surfaced

F3 — the wrap program's one keccak permutation costs three sub-proofs. program_id is deliberately keccak over bytes whatever the configuration commits under, and the recorded wrap program's instruction mix reads keccak 1. That single row instantiates LFM_KECCAK (736+88), KECCAK_RND (1,480+516) and KECCAK_RC, all of which the aggregator must then open: 1,031 leaf blocks per query — 25.3% of RPX's 4,067-block bill, 19.6% of BLAKE3's 5,252. Retiring it is what takes fan-in 3 from 13.5% to 30.6% headroom.

F4 — the algebraic emitter arm cannot be exercised on a BLAKE3-pinned branch. sub_proof.rs:149 (SubProofShape::opening_words) and fri.rs:164 (FriShape::query_words) size their arena stride from proof_arena::words_per_root(), which reads WrapHash::production(), while epoch_verify.rs advances its cursor by edsl::digest_words(b), which reads the builder. An explicit WrapHash::Algebraic build therefore strides 1 against a declared stride of 2 and trips emit_table_verification's own assertion. It costs this PR nothing — both arms are emitted under WrapHash::Blake3 and the block counts are equal at rate 8, which the new hash-invariance test re-checks over exactly these groups — but it blocks the per-table aggregator emitter. Fix: thread the builder's digest width into both functions.

What is in the diff

One new file, prover/src/lfm/per_table_census_tests.rs, plus its mod line. Four tests:

  • the_lever_zero_factor_is_measured_per_tenant — the gate: the ratio table, the decomposition, the falsification.
  • the_per_table_aggregator_tree_applies_the_rule — the tree, the cliffs, the RSS band, the rule applied under the imported glue constant.
  • the_block_rule_is_hash_invariant_on_every_tenant_group — guards the rate-8 premise the whole method rests on.
  • the_blake3_tenant_socket_matches_the_record — guards the baseline against the socket-vs-commitment-hash confusion the box run caught, and ties the tenant socket to hash_pin::BLOCK_HASHER.
  • the_tenant_leg_emits_and_censuses#[ignore]d emission arm; puts the closed forms through the real emitter at fixture scale and measures the glue, sweeping the query count so the glue can be split rather than quoted as one percentage.

The glue, and why one percentage was not enough

Box B measured emitted − closed form at +56-60% of the leg across all eight tenants (fixture heights 2^12, 2 queries). That is not a number that can be extrapolated. The glue is F + P·q: F is the query-invariant per-table spine — the OOD blocks, the 128 terminal coefficients per table, the layer roots, the grinding check — and P is the per-query remainder. At 2 queries the two are indistinguishable, and they reach production's 110 queries by factors 55 apart: the difference between the glue being under 1% of the leg there and 57% of it.

Structurally most of it must be F. For the 14-table RPX tenant the OOD current block alone is Σ num_total_cols = 3,595 extension cells absorbed per leg, and the terminal coefficients add 14 × 128 = 1,792 more — both query-invariant, and together already larger than the whole 3,003-compression glue measured at q=2. So the +57% is a fixture artifact of running only two queries.

The emission arm sweeps q over {2, 4, 8}, fits the line through the endpoints, and asserts the middle point lands within 5% of it, since that affine split is what licenses the extrapolation at all. Measured, box B at a8d7d081:

tenant fit middle-point residual at 110 queries
BLAKE3 glue(q) = 4398 + 2.5·q +0.11% glue 4,673 = 1.1% of the leg
RPX 2998 + 2.3·q +0.16% 3,255 = 1.1%
RPO 3043 + 2.3·q +0.15% 3,300 = 1.1%
Poseidon 3109 + 2.3·q +0.15% 3,366 = 1.1%
RPX/no-kec 1563 + 1.8·q +0.23% 1,765 = 1.2%
RPO/no-kec 1608 + 1.8·q +0.23% 1,810 = 1.2%
Pos/no-kec 1674 + 1.8·q +0.22% 1,876 = 1.2%
BLAKE3/no-kec 2963 + 2.0·q +0.13% 3,183 = 1.1%

The glue is a fixed spine with a per-query slope of about two compressions. The structural prediction above holds to the digit: +57% at two queries, 1.1% at 110. ⚠ It is taken at fixture heights, where each table commits 4 FRI layers against production's 12 and walks 13 Merkle levels against 21-25, so F is understated at real heights. Correcting for the eight extra layer roots and their ζ samples across 14 tables puts F at roughly 3,222 for RPX, and the glue at production heights and 110 queries at ≈3,600 per leg, about 0.8% of the leg.

⚠ This falsifies two of the three glue readings, and the second one was load-bearing

The multiplicative reading is dead: the per-query slope is ~2.3 compressions, not the ~2,000 a ×1.5716 multiplier would require. Its row in the table above should be read as a bound that has now been ruled out, not as a live scenario.

More seriously, the production-anchored additive reading — MEMORY-MODEL §1.3(iii)'s +469,710 per node, which is what put fan-in 3 at 13.6% headroom — is a residual against a different and coarser leg model. Its own note says the model "counts the 6 verify legs, misses the glue", so the shortfall was attributed wholesale to glue without decomposition. The residual measured here is against a closed form that is pinned to the emitter by epoch_verify_tests, and it is 23× smaller per leg. Those are not the same quantity, and the smaller one is the better-grounded.

Under the measured glue, with production-height legs:

fan-in node invocations padded height headroom hash cells
2 2 × (447,370 + 3,600) = 901,940 2^20 14.0% 340.8M
3 3 × 450,970 = 1,352,910 2^21 35.5% 681.6M

⇒ How to read the verdict

Fan-in 2 is the conservative default, not a measured result. The leg is measured; the glue is not, and the verdict turns on the glue.

The two glue numbers do not contradict each other, and this is the reason: this PR's emission arm emits ONE leg, and a single-leg fixture structurally cannot contain inter-leg glue — the binding between the legs of a real multi-leg aggregator has no counterpart in it. So the ~1.1% measured here is a lower bound on a different quantity, and §1.3(iii)'s constant is a residual against a coarser model. Neither settles it.

  • If the inter-leg glue is at the single-leg level, fan-in 3 clears with ~36% headroom and the rule selects 3.
  • If it is anywhere near the imported constant, fan-in 3 sits at 13.6% and the rule selects 2.
  • Fan-in 2 is safe under both, at half the hash cells, which is why it is the default here.

Fan-in is decided by F from the production-height emission below; fan-in 2 is the default until it prints. The tree test is named the_per_table_aggregator_tree_applies_the_rule rather than anything with "measured" in it, precisely so the name cannot be read as a claim the evidence does not support.

The deciding measurement

the_rpx_leg_emits_at_production_heights (#[ignore]d) re-fits glue(q) = F + P·q at the recorded per-table wrap's own heights, so the verdict stops resting on a height extrapolation from 2^12. It emits at one leg and at two, because a single-leg fixture is exactly what could not answer the open question: F(2 legs) − 2·F(1 leg) is the inter-leg glue, the part of a multi-leg node that is not its legs added up. The emitter is generalised to N legs, every sub-proof of every leg taking its own fork index over the whole set.

The query sweep stays at {2, 4, 8} deliberately. F is the query-invariant intercept, so measuring it needs production heights, not production queries: a 110-query emission at these heights pads LFM_BLAKE3 to 2^19 × 3,112, roughly 13 GB for that chip alone, and yields the same intercept that 8 queries does at about 800 MB. The rule is then applied at 110 queries with the measured F and P, extending the per-leg increment linearly to fan-in 3 — the one modelled step, named as such.

It is a lower bound on the inter-leg glue, and the verdict is safe in one direction only. The two-leg node shares a transcript, a Phase A and a LogUp closure over a fork space of 2 × tables, but carries no binding legs: no register chain, no attestation join, no published-root compare. Those are lane A's design, and inventing them here would measure a guess rather than the machine. So if fan-in 3 misses the rule even at this bound, it misses; if it clears here, lane A's real node still decides.

cargo test --release -p lambda-vm-prover --lib -- lfm::per_table_census_tests::the_rpx_leg_emits_at_production_heights --exact --nocapture --ignored
cargo test --release -p lambda-vm-prover --lib -- lfm::per_table_census_tests:: --include-ignored --nocapture --test-threads=1

Open risks

The non-hash floor is a band, not a number; the emission arm is what closes it. Every non-hash chip height is held at its recorded value under the algebraic tenant, which is conservative for BITWISE and LFM_BITDEC. The 2^22-epoch correction to the wrap-proof shape is unapplied and runs in the helpful direction, a few percent at most. make lint exits 0 at a8d7d081. The one number still imported rather than measured is the inter-leg glue; the ratio column above is the box's own per-sub-proof output with the bogus 2,980-column LFM_HASH sub-proof replaced by the record's 28-column one.

…egator leg

Measures what a per-table aggregator pays to open a wrap proof whose hash
matrix is 316/436/612 columns wide instead of 3,056, by closed form over the
real AIR shapes plus an emission arm at fixture scale. No proving.

The correction it exists to make: MEMORY-MODEL lever 0 reads P3-CENSUS-AB's
"1,247 hash-matrix blocks of 1,846 per query" and concludes 0.370x. 1,846 is a
BATCHED per-query bill. In the per-table format every table walks its own
Merkle path, so the path terms are 63% of the bill and the hash matrix is 30%,
not 68%.

Second axis the census surfaced: the recorded wrap program emits exactly one
keccak permutation (program_id is keccak over bytes whatever the configuration
commits under), and that one row instantiates LFM_KECCAK, KECCAK_RND and
KECCAK_RC — three sub-proofs at 736+88 and 1,480+516 columns. Both readings are
carried because that axis, not the hash, decides the aggregator's fan-in.

Emission runs under WrapHash::Blake3 on both arms: SubProofShape::opening_words
and FriShape::query_words size their arena stride from the production pin while
emit_table_verification advances by the builder's digest width, so an explicit
algebraic build trips the stride assertion on a BLAKE3-pinned branch. It changes
no count — blocks_for's algebraic and BLAKE3 arms are equal at rate 8, which the
new hash-invariance test re-checks over exactly these groups.
…alsification

The keccak family has to be held fixed on both sides of a ratio for the ratio to
mean anything, so the census now carries a keccak-less BLAKE3 tenant beside the
recorded one and prints both like-for-like pairs.

Also prints the check that matters more than my arithmetic: MEMORY-MODEL §1.2
puts the per-table/batched compression ratio at the aggregator at 2.44x, and the
leaf term is format-invariant, so a hash matrix at 67.6% of the batched bill is
necessarily 27.7% of the per-table one. That is what the census measures. The
0.370x is the model disagreeing with itself.
The block is 10 epochs of 2^22 on the measured 39.6M-cycle guest, not the 18
PLAN T2 carried from the older one.

The fan-in decision does not move with the count: a level-1 node costs
f*leg + glue, a function of the fan-in alone, so the wrap count sets how many
nodes there are and not how big the biggest one is. What the tree walk makes
visible is the node COUNT beside the node SIZE — 11 aggregator proofs at fan-in
2 against 7 at fan-in 3.
The box run caught this: the BLAKE3 tenant named HasherKind::Blake3, which
instantiates the BLAKE3 SOCKET as LFM_HASH — 2,980 main + 599 aux — a chip the
real proof does not carry. hash_pin::BLOCK_HASHER is Test on a BLAKE3-pinned
build, and the record agrees: LFM_HASH at 4 rows, 0 used, 28 main, 3 aux. Under
a byte hash the emitter's Merkle work lowers to the dedicated LFM_BLAKE3 chip
and emits no Instr::Hash at all, so the socket idles.

The socket permutation and the commitment hash are orthogonal axes and this is
what confusing them costs: +1,196 leaf blocks per query on the BLAKE3 baseline,
which the whole census is a ratio against. It made lever 0 look BIGGER than it
is — 0.632x printed against the true 0.774x. Decision numbers are RPX's own leg
and did not move: fan-in 3 at 13.6% headroom, fan-in 2 at 34.9%.

the_blake3_tenant_socket_matches_the_record asserts the widths against the
recorded census so this cannot regress silently.

Also prints the tree at 10 and 18 wraps, which makes the invariance explicit:
a level-1 node is f*leg + glue and carries no wrap count, so only the node
count moves.
The emission arm measured glue at +56-60% of the leg and that was quoted as one
percentage, which is not a number that can be extrapolated: glue(q) = F + P*q,
and F and P reach production's 110 queries by factors 55 apart — the difference
between the glue being under 1% of the leg there and 57% of it.

So the arm now sweeps q over {2,4,8}, fits the line through the endpoints, and
checks it against the middle point (asserted within 5%, since the split is what
licenses the extrapolation). It prints F, P and the extrapolation to 110
queries, with the caveat that fixture heights understate F: 4 FRI layers per
table against production's 12, 13 Merkle levels against 21-25.

The decision block now applies the measured glue multiplicatively as well —
the pessimistic bound, where none of the +57.2% decays with the query count.
Under it a fan-in 3 node crosses 2^21 outright and doubles its hash table,
while fan-in 2 still clears the rule at 32.9%. That is asserted; the crossing
itself is only printed, because its margin is 0.58% and a tripwire that flips
on a 0.6% drift is noise.
…ement

Reviewer's decision-grade point, accepted: the tree test's verdict rests on
GLUE_ADDITIVE, imported from the batched six-leg model, which this PR does not
measure. The emission arm's own ~1.1% at 110 queries does not replace it,
because that arm emits ONE leg and a single-leg fixture structurally cannot
contain INTER-LEG glue — the binding between the legs of a real multi-leg
aggregator has no counterpart in it. The two numbers are not in conflict and
neither settles the question.

So the test is renamed from _is_derived_from_the_measured_leg to _applies_the_rule
(the leg is measured; the glue is not, and the verdict turns on the glue), the
constant's comment says outright that it is imported, and the printed verdict
now reads: fan-in 2 is the conservative default; if the inter-leg glue is at the
single-leg level, fan-in 3 clears with ~36% headroom and the rule selects 3;
lane A's first real two-leg emission settles it.

Nits in the same commit:
- module doc: the socket bug inflated the baseline by 1,185 blocks/query
  (1,201 against the idle chip's 16), not 1,196, and the true ratio is 0.774x
  not 0.776x
- the tree header said '18 wraps' while WRAPS = 10; it now names the constant
- slots come from airs.rs (BLAKE3_SLOT, KECCAK_SLOT, KECCAK_RND_SLOT,
  KECCAK_RC_SLOT) instead of literals, so a reordering of the frozen chip order
  cannot silently re-point the census; HASH_SLOT stays local because airs.rs
  exports none, and says so
- the socket guard now also asserts BLAKE3_TENANT_SOCKET == hash_pin::BLOCK_HASHER,
  so a re-pinned branch fails here rather than measuring a shape nothing proves
The deciding measurement, per the lead's go. the_rpx_leg_emits_at_production_heights
re-fits glue(q) = F + P*q at the recorded per-table wrap's own heights, so the
fan-in verdict stops resting on a height extrapolation from 2^12.

It emits at ONE leg and at TWO, because a single-leg fixture is exactly what
could not answer the open question: F(2 legs) - 2*F(1 leg) is the inter-leg
glue, the part of a multi-leg node that is not its legs added up. The emitter
is generalised to N legs (tenant_node_program) with every sub-proof of every
leg getting its own fork index over the whole set, which is what a real
multi-leg aggregator does.

The query sweep stays at {2,4,8} on purpose: F is the query-INVARIANT
intercept, so measuring it needs production HEIGHTS, not production queries.
A 110-query emission at these heights pads LFM_BLAKE3 to 2^19 x 3,112 -- about
13 GB for that chip alone -- and yields the same intercept that 8 queries does
at ~800 MB.

The rule is then applied at 110 queries with the measured F and P, extending
the per-leg increment linearly to f = 3 (the one modelled step, named as such).

Stated in the doc, the printout and the assertions: this is a LOWER BOUND on
the inter-leg glue. The two-leg node shares a transcript, a Phase A and a
LogUp closure but carries no binding legs -- no register chain, no attestation
join, no published-root compare -- because those are lane A's design and
inventing them here would measure my guess rather than the machine. Safe in
one direction: if fan-in 3 misses the rule even here, it misses.
@MauroToscano
MauroToscano merged commit c34dee4 into per-table-gpu Sep 7, 2026
11 of 13 checks passed
@MauroToscano
MauroToscano deleted the pt/census branch September 7, 2026 19:30
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