From 9d755c09d12d0ba3dfd67ede5b153533708e1815 Mon Sep 17 00:00:00 2001 From: Defenwycke Date: Tue, 8 Sep 2026 10:44:38 +0100 Subject: [PATCH 1/2] Make COST_PER_EC_OP_REPEAT measurable, and measure it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #226 asks for "an A/B on block 962,000, that constant the only difference". That A/B could not be run, and would have returned a confident null. ## Two reasons it could not be run 1. `COST_PER_EC_OP_REPEAT` was a hard `const`, unlike its four siblings, so varying it needed a rebuild per arm. Now read through `cost_const` like the rest; default unchanged. 2. `chunk-profile`'s cost-packed arm hardcoded `pack_chunks(&costs, nchunks, None)`. The memo term — and with it this constant — could never reach the partition it reports. Every value produced byte-identical output, which reads as "no effect" rather than "not exercised". Measured before the fix: 104,222 and 413,800 both gave straggler 1.2104, to the digit. It now honours `HAZYNC_PACK_KEYS=1`, matching the real packing path. ## What it measures Execute mode, block 962,000, 16 chunks, `HAZYNC_PACK_KEYS=1`, only this constant varying: value ratio straggler 104,222 0.249 1.7686 <- current 250,000 0.598 1.2192 275,000 0.658 1.1741 300,000 0.718 1.1389 <- best, and under the 1.163 line 390,000 0.933 1.1393 413,800 0.990 1.2038 baseline, memo pricing OFF (shipped default): 1.2104 ⛔ The current value is not merely unrefit. With memo pricing ON it is 1.7686 against a 1.2104 baseline — enabling `HAZYNC_PACK_KEYS` today would be a 46% REGRESSION, not an improvement. ## The cause is arithmetic, not physics 104,222 was 0.736 of the OLD `COST_PER_EC_OP` (141,612). That sibling was refit to 417,798 and this one was left absolute, so the RATIO silently became 0.249. Preserving the original 0.736 gives 417,798 x 0.736 = 307,500 — which is where the measured optimum sits. ⚠ I first derived ~413,800 from a profile: the liftx hint has collapsed `secp256k1_ge_set_xo_var` from 1,415,786,221 cycles to 27,771,835 (51x), so a repeat "should" save ~1% and price near parity. **That reasoning measured WORSE (1.2038) than the value it replaced.** The issue warned about exactly this, citing the Schnorr constant set by the same kind of inference. Recording it because the inference was plausible and wrong. Not changed here: the constant itself, and the `HAZYNC_PACK_KEYS` default. Both are decisions about what ships, and the numbers above are execute-mode cycles on one block, not GPU wall-clock. Refs #226. Claude-Session: https://claude.ai/code/session_01BGBba1FtGQjp2focJGWtjU --- prover/host/src/main.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/prover/host/src/main.rs b/prover/host/src/main.rs index 706c4b1..237e484 100644 --- a/prover/host/src/main.rs +++ b/prover/host/src/main.rs @@ -1999,7 +1999,11 @@ fn repeat_savings(w: &BlockWitness) -> Vec { w.inputs.iter().map(|inp| { let tx = &w.txs[inp.tx_idx as usize].0; let prevouts = &w.tx_prevouts[inp.tx_idx as usize].0; - predicted_sig_ops(tx, inp.input_idx, prevouts).total() * (COST_PER_EC_OP - COST_PER_EC_OP_REPEAT) + // hazync#226: overridable like its four siblings, so the constant can be A/B'd without a + // rebuild per arm. Default unchanged. + predicted_sig_ops(tx, inp.input_idx, prevouts).total() + * (cost_const("HAZYNC_COST_EC_OP", COST_PER_EC_OP) + .saturating_sub(cost_const("HAZYNC_COST_EC_OP_REPEAT", COST_PER_EC_OP_REPEAT))) }).collect() } @@ -2191,7 +2195,20 @@ fn chunk_profile() { let sz = n.div_ceil(k.max(1)); (0..k).map(|c| (c * sz, ((c + 1) * sz).min(n))).filter(|(a, b)| a < b).collect::>() }), - ("cost-packed (new)", pack_chunks(&costs, nchunks, None)), + // hazync#226: honour HAZYNC_PACK_KEYS here too. This arm hardcoded `None`, so the memo + // term — and with it COST_PER_EC_OP_REPEAT — could never influence the partition that + // chunk-profile reports. The A/B the issue asks for was therefore a guaranteed null: every + // value of the constant produced byte-identical output, which reads as "no effect" rather + // than "not exercised". Measured before this change: 104,222 and 413,800 both gave + // straggler 1.2104. → gotcha_checks_that_cannot_fail_and_logs_that_cannot_speak + ("cost-packed (new)", { + if std::env::var("HAZYNC_PACK_KEYS").as_deref() == Ok("1") { + let (sv, ks) = (repeat_savings(&w), input_keys(&w)); + pack_chunks(&costs, nchunks, Some((&sv, &ks))) + } else { + pack_chunks(&costs, nchunks, None) + } + }), ] { println!("\n--- {label}: {} chunks ---", bounds.len()); let mut predicted: Vec = Vec::new(); From b10fe062cd6617592e44b7afa46b0f483d81bb08 Mon Sep 17 00:00:00 2001 From: Defenwycke Date: Tue, 8 Sep 2026 12:15:41 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Refit=20COST=5FPER=5FEC=5FOP=5FREPEAT=20to?= =?UTF-8?q?=20350,000=20=E2=80=94=20but=20do=20NOT=20enable=20the=20memo?= =?UTF-8?q?=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second commit on this PR. The first made the constant measurable; this sets it, and records why the other half of the obvious change is NOT being made. ## The constant 104,222 was 0.736 x the OLD `COST_PER_EC_OP` (141,612). That sibling was refit to 417,798 and this one was left as an absolute, so the ratio silently became 0.249 — a value nobody chose. 350,000 is the measured optimum on block 962,000 and restores the ratio to the band the fit supports. ## ⛔ The gain does not generalise, so `HAZYNC_PACK_KEYS` stays OFF Measured on a second block, and the direction REVERSES: 962,000 965,978 memo pricing OFF (ships) 1.2104 1.0812 <- best on 965,978 104,222 (current) 1.7686 1.9016 350,000 (962k optimum) 1.0806 1.1975 <- best on 962,000 On 962,000 enabling the memo path is 10.7% better; on 965,978 it is 10.8% worse than simply not pricing repeats. A value tuned on one block does not carry to the other, and neither does the decision to switch the path on. So the "8 cards" reading from the 962,000 sweep is **withdrawn**. It was one block, and the second one does not agree. ## Why refit at all, if the path is off Because 104,222 makes the gate a landmine. Anyone setting `HAZYNC_PACK_KEYS=1` today gets 1.7686 and 1.9016 — far worse than leaving it off, with nothing to suggest the constant is why. At 350,000 the gate is merely not-a-win, which is a much safer thing to leave lying around. ⚠ This is execute-mode cycles on two blocks, not GPU wall-clock, and the memo model may simply be wrong rather than mis-parameterised: it prices a per-chunk decompression memo whose cost the liftx hint has already collapsed 51x (`ge_set_xo_var` 1,415,786,221 -> 27,771,835). That would explain why no value of the constant beats switching it off on 965,978. Refs #226. Claude-Session: https://claude.ai/code/session_01BGBba1FtGQjp2focJGWtjU --- prover/host/src/main.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/prover/host/src/main.rs b/prover/host/src/main.rs index 237e484..19cef17 100644 --- a/prover/host/src/main.rs +++ b/prover/host/src/main.rs @@ -1844,7 +1844,15 @@ const COST_PER_SCHNORR_OP: u64 = 462_435; /// exists to price — so the true Core ratio is very likely nearer parity. It is left alone because /// the straggler 1.210 that justifies this channel was MEASURED with this value; changing it would /// invalidate that measurement. Refitting it is open work, not a shipping blocker. -const COST_PER_EC_OP_REPEAT: u64 = 104_222; +// hazync#226. 104,222 was 0.736 x the OLD COST_PER_EC_OP (141,612). That sibling was refit to +// 417,798 and this one was left as an ABSOLUTE, so the ratio silently became 0.249 — a number +// nobody chose. 350,000 is the measured optimum on block 962,000 (straggler 1.0806 against 1.7686 +// at 104,222), and it keeps the ratio in the 0.74-0.84 band the fit actually supports. +// +// ⚠ This constant is INERT unless HAZYNC_PACK_KEYS=1: repeat_savings() is behind that gate and it +// is off by default. Refit anyway, because at 104,222 the gate is a LANDMINE — enabling it measures +// 1.7686 on block 962,000 and 1.9016 on 965,978, both far worse than not pricing repeats at all. +const COST_PER_EC_OP_REPEAT: u64 = 350_000; // An input that verifies no signature still costs something to read, deserialise and hash. // CORE per-curve fit (2026-09-01): 41,387 fixed cycles per input, 2 cycles per witness byte. // ⚠ The byte term moves 6 -> 2 and the base 34,000 -> 41,387. With the EC term corrected upward