feat: noise-floor control and floor-adjusted flip rate - #258
Conversation
|
Approving for now. The real win is running it on real MedQA/NIH and posting an actual noise-floor number. That is the experiment. |
maximinl
left a comment
There was a problem hiding this comment.
Approve the noise-floor control (stacked on #257).
Reusing the clean answer as the first ask (one extra call/twin), paired floor-adjusted flip rate, and off-by-default with dry-run cost visible are all correct. Cascade no-op is the right scope. Real MedQA/NIH number still needed later; code is fine. Merge after the stack below it.
sebasmos
left a comment
There was a problem hiding this comment.
The noise-floor is a genuine control, not just infra, so it is worth landing, but only with a real number: mock cannot tell us the decoding-noise floor. It needs a real MedQA/NIH run through the API before the flip rates it adjusts mean anything. Requesting changes until that run is attached.
Superseded by the later Changes-requested review on this PR; dismissing the stale approve to avoid a mixed signal.
Agastya191
left a comment
There was a problem hiding this comment.
Good control, and the parts that are easy to get wrong are right: the floor re-ask and record.clean_answer both go through SoloAgent.run/_parse_option, so the floor is measured on the same parsed-option scale as flipped (that's why a deterministic backend pins the floor at 0), flip_rate_above_floor subtracts two commensurable per-twin quantities, reusing the clean answer as the first ask keeps it to one extra call per twin, and the dry-run accounting (108 vs 72) matches. One thing I'd tighten is the alignment in noise_floor_pass: it pairs each re-ask to a record by position, zipping a freshly rebuilt _twins(selected, cue_types) against the records that run_solo_baselines produced from its own separate _build_twins(selected, cue_types), guarded only by a length check. The two builders are identical today so it lines up, but the length check catches a count mismatch and not a reorder, so if _twins and _build_twins ever drift (a limit, a filter, a changed skip, a reordering) the floor would silently subtract the wrong twin's noise from each flip and the headline floor-adjusted rate would be quietly wrong with nothing failing. Since the records already carry case_id and cue_type, I'd key the twins by (case_id, cue_type) and look each record up rather than depend on the two twin builders staying byte-for-byte in lockstep.
|
@Agastya191 good instinct to tighten that, fixed in bea24ae. @sebasmos agreed a mock can't tell us the decoding-noise floor. It's in the MedQA/NIH run set I'm putting through the API, so the floor-adjusted rates get a real number behind them before this lands. @maximinl thanks. One tightening since your approve: the floor re-ask is now matched to its record by |
sebasmos
left a comment
There was a problem hiding this comment.
Approving. The twin re-ask is now keyed by (case_id, cue_type) and raises on a miss, and the new test feeds a reversed twin list, which the old positional zip would have failed with a spurious floor. One thing was fixed for you along the way: noise_floor and flip_rate_above_floor were the only solo-lane estimates still resampled per twin after #252 moved the rest to case clusters, so the floor-adjusted rate would have shipped a tighter CI than the raw rate it corrects. They now pass the same clusters, guarded by the existing alignment check. The real MedQA and NIH floor numbers are still the open empirical item, a mock cannot produce a decoding-noise floor. Merged main in, post-squash conflicts resolved to the branch side.
* feat: benchmaxxing run entry point for the stage runners * feat: bootstrap CIs and effect sizes in the run summary * feat: correct the whole family of p-values in the run report * feat: declare model lineage in the config instead of inferring it * feat: noise-floor control and floor-adjusted flip rate * cascade significance: paired permutation on the adoption gap, not a McNemar sign test Agastya's review: _cascade_tests ran the continuous shared/isolated adoption rates through mcnemar by counting only each pair's direction, collapsing it to a sign test that drops ties and ignores magnitude. Over four discordant cases the smallest two-sided p is 0.125, so a unanimous planted-answer effect could never be flagged even when the shared/isolated bootstrap CIs printed right above it are cleanly separated -- the two signals then contradict. Add stats.paired_permutation_test (seeded sign-flip over the per-case differences, exact by enumeration for small n, sampled otherwise) and test the same shared-minus-isolated gap the adoption_delta CI is built on, so the p-value and the interval answer one question. Thread the run seed through significance_tests for reproducibility. * lineage: warn when an org-prefixed id is inferred, not just recorded Agastya's review: the bare-id fallback routes through _lineage, which splits on the first separator, so an org-namespaced id like 'meta-llama/llama-3.1-70b' resolves to 'meta' rather than 'llama'. A mixed-convention roster then gets a wrong within- vs cross-lineage partition in run_holes_test and the run completes clean, with lineage_source: inferred in the manifest as the only trace after the fact. Keep the heuristic as the documented fallback but make the risky case loud: model_specs warns when a lineage is inferred (not declared) from an id carrying a '/'. Declaring `lineage` in the config -- the resolution from #251 -- silences it. Failure is now at run time, not a manifest post-mortem. * solo CI: resample over cases, not twins Agastya's review: the solo lane resampled flip_rate/shortcut_reliance over the flat per-twin record list, but _build_twins emits one twin per (case, cue_type), so a case's several cue-twins sit in that array and bootstrap_ci drew them iid. Same-case twins share the case's difficulty, so counting them as independent understates the uncertainty exactly the way pooling models would -- the 95% CI came out tighter than the case-level evidence supports. Add stats.cluster_bootstrap_ci (draws whole clusters, pools their observations) and resample the solo lane by case_id, so the interval answers "does this hold on new cases", which is the claim the arm makes. The point estimate is unchanged and, since every cluster is a singleton for one-twin-per-case data, the pinned intervals are untouched; only real multi-cue cases widen. * noise floor: match each re-ask to its record by key, not by position Agastya's review: noise_floor_pass zipped a freshly rebuilt _twins(...) against the records run_solo_baselines produced from its own separate _build_twins(...), guarded only by a length check. The two builders are identical today, but the length check catches a count mismatch and not a reordering -- if they ever drift (a limit, a filter, a changed skip, a reorder), the floor would subtract the wrong twin's noise from each flip and the headline floor-adjusted rate would be quietly wrong with nothing failing. Records already carry case_id and cue_type, so key the twins by (case_id, cue_type) and look each record up instead of trusting the two builders to stay byte-for-byte in lockstep. A missing key raises loudly. per_twin stays aligned with records, so the paired floor-adjusted resampling is unchanged. --------- Co-authored-by: sebasmos <sebasticajas@gmail.com>
Summary
benchmaxxing run --noise-flooradds the decoding-noise control to the solo lane. It re-asks each clean payload once and counts how often the answer moves with nothing changed, which is the rate a flip rate has to be read against.--dry-runAlso folded the pilot stage's records into
records_by_modelso its estimates carry the model name like the solo lane does, instead of the floor needing a special case.Result
Deterministic mock backend: floor 0.0000 [0.0000, 0.0000] per model, and the floor-adjusted flip rate equals the raw 0.3333, which is the invariant you want. A scripted flaky backend (answers the same prompt differently every other call) gives a non-zero floor and the adjusted rate drops by exactly that much, pinned in a test.
--dry-runshows the extra cost up front: 108 estimated calls with the control on versus 72 without, on 12 twins across 3 models.ruff check .clean,pytest -q613 passed.Notes
Cascade is unaffected: the control is a solo-lane idea, so
--noise-flooron the cascade stage is a no-op and says so in the plan.Stacked: #251 -> #252 -> #253 -> #257 -> this. Base is #257's branch.
Closes #256