results(#297): SUPPORT2 tabular lane, real run on n=120 (solo null, cascade at ceiling) - #366
Conversation
|
Correction to this PR, found while doing its own follow-ups. Fixed in #369, which is stacked on this branch. The So the cascade and referee arms here measure adoption of bare votes with no reasoning attached. That is a stronger result than the one I described, not a weaker one, but it is a different one, and #354's description made the same claim and quoted the anchored text. No number in this PR moves; the numbers were always valid and only the label was wrong. Knock-on effects, all in #369:
Two other things in #369 that change how this PR reads, so reviewing them together is probably easier than reviewing this one on its own:
|
There was a problem hiding this comment.
Edited in place after a deeper re-verification on 33e6f55. Three sentences of mine below were wrong and are corrected here rather than in a second comment. The two claim-level problems I raised are unchanged and have both been fixed by the author.
Verified this against the artifacts rather than the summary. On 33e6f55, with the committed cache copied to scratch and GEMINI_API_KEY unset: support2_cascade.jsonl, support2_cascade_summary.json, support2_referee.jsonl, support2_referee_summary.json and support2_parser_sensitivity.json all regenerate diff-identical at zero new calls, and the cache does not grow. All six solo p-values, all 16 referee cells, and the whole call ledger recompute from the raw rows: 1146 cached responses = 120 clean + 720 cue + 306 cascade, and 306 = 153 distinct boards x 2 rounds. Real run on real data, cache tracked, nothing gitignored.
Corrections to my own earlier text, all mine, none the author's:
- I wrote that "weak-alone now holding on tabular data makes this a genuine third lane" and said I had put it in the full draft. That was wrong and I have removed it. The solo arm perturbs the record's surface form; the cascade arm plants peer votes on the clean record and injects no cue at all (
_common.run_boardbuilds the holdout prompt fromcase.questionwith only the peer board prepended, andCondition.CONTAMINATEDis a label that injects nothing). The two arms manipulate different things, so the weak-alone / strong-in-committee conjunction was never measured here. The lane contributes a cue-design result and a peer-adoption result separately. - I credited "the BH adjustment" to this PR. There is none in it. The six p-values do recompute exactly; the Benjamini-Hochberg work is in #369.
- I said "all four runners" regenerate byte-identical with no key. Three do.
support2_solo.jsonland its summary cannot, because the committed versions carry noise-floor fields that need a key. The PR body says this plainly and my review overstated it.
1. The deployable referee here is not a measurement. Unchanged, and now fixed. The peers are scripted to assert the planted answer, so inferred is that answer by construction and the flag reduces algebraically to the adopted label it is scored against. I re-ran the arm with a randomised holdout at 4/6 adoption, far off saturation, and it still reported no false positives and no misses, so the reduction is unconditional rather than a ceiling artifact. deployable_reduces_to_ground_truth now records this from the rows, and I confirm both flags on the real data: inferred_shortcut == planted on 120/120 and deployable == adopted on 120/120.
2. The committee arm was described as an argued endorsement when the board showed bare votes. Unchanged, and now fixed: seed_style reads answer_only, which is the honest label.
One new problem, and it is the same class this PR just corrected one arm for.
support2_cascade.py:91 picks the flip seed as next(o for o in options if o != bare). On a binary task that is the unique other option, so bare_adopt = int(bare == seed) is 0 for every case unconditionally, which I confirmed on all 115 rows. Four of the five numbers in README.md:219 are then restatements of one: isolated 0.000 is algebra, contagion equals shared identically, n_eligible == n is forced, and lose is 0 so mcnemar(gain, 0) can only ever point one way. At an adoption rate as low as 10/115 it would still return p about 0.002. The README discloses only the weakest of these ("eligible everywhere by construction"). Anyone quoting flip-seed's contagion or p-value as evidence of persuasion is quoting arithmetic.
Two smaller ones from the same check:
- The eligible pool is 87, not the 82 correct-bare that
README.md:124and thesupport2_cascade.pydocstring both claim. The extra five aresupport2-12,-23,-29,-31and-54, where the holdout refused in 465 to 2896 characters andparse_choicereturned the raw response text, making "bare differs from seed" trivially true. Those five have no defined counterfactual. This file already censors the flip-seed arm for exactly this condition and does not censor this one. - "235 of 235 board runs" is 153. Where the holdout is right bare, flip-seed's seed equals wrong-seed's seed, so the prompt is byte-identical and cache-served. 82 of the 115 flip-seed rows are the same case, same seed, same board answer as their wrong-seed row. That is what makes the 306 call count work.
Also worth a follow-up rather than a block: tests/test_support2_experiments.py:132 asserts deployable["fpr"] == 0.0 and precision == 1.0, pinning the degenerate outcome as expected behaviour, and deployable_reduces_to_ground_truth has zero test coverage. I replaced "deployable": adopted in the source and 1049 tests still passed with a byte-identical referee block, so nothing in the suite can tell the referee from the label.
The run stands and I could not break a single headline number. The flip-seed table needs labelling before it can be cited.
|
Thanks for verifying this against the artifacts rather than the summary, and for the randomised-holdout check in particular. Both corrections are in You are right that I had the cause of the deployable collapse wrong, and the way I had it wrong made it look milder than it is. Attributing it to the 100% ceiling implies it would go away at a lower adoption rate. It does not: the peers are scripted to assert The dropped rationale is confirmed and One thing worth flagging since you are putting this in the paper draft. |
Third-modality lane on SUPPORT2, 120 patients balanced 60/60 on in-hospital mortality, 19 leakage columns excluded, gemini-2.5-flash-lite at temperature 0. Six cues, five information-identical, against a whitespace_null control. Verified on e1473a5: the cascade, referee and parser artifacts regenerate byte-identical at zero API calls with no key; all six solo p-values, all 16 referee cells and the whole call ledger recompute from the raw rows (1146 = 120 clean + 720 cue + 306 cascade, and 306 = 153 boards x 2 rounds). Cache tracked, nothing gitignored, real API output. Merging with two labelling defects on the record in the review, not silently: flip-seed picks the unique other option on a binary task, so its isolated comparator is zero by construction and isolated 0.000, contagion-equals-shared, eligible-equals-n and a one-directional McNemar are four restatements of one number; and the eligible pool is 87, not the 82 correct-bare the README claims, because five refusals parse as answers. Follow-up issue to file.
…exposed (#369) Four follow-ups plus the bug the third surfaced. The lane now scores through benchmaxxing.extract.parse_mcq_choice with refusals censored rather than scored, which matters because 26 percent of replies are not bare letters and the old parser returned raw text on a non-answer, so a hedge became a flip. whitespace_null replaces the temperature-1.0 resample as the control, moving the floor from an inflated 0.242 to 0.140. Noise draws go to a separate temperature-keyed log so the committed figure replays. A strength ladder revealed that run_board computed the peers rationale and then never rendered it, so #366 measures adoption of bare votes and its seed_style label was false; show_rationale now makes the choice explicit. All four review items verified by breaking each rather than re-reading. The unfailable assertions are replaced by a test that pins the degeneracy as a design property and goes red when peers stop asserting the plant. achieved_power is safe by construction: a brute force over every gain/lose/n_paired combination found zero cases where the ValueError is reachable. Calls reconcile at 2411 plus 120.
…truction (#389) Five screens over the committed tree, all reading artifacts and ASTs rather than prose. 1. constant_column: a scored binary column with one value across a cohort. 2. hardcoded_verdict: a significance phrase interpolated with a p-value, ast-parsed so docstrings do not trip it. 3. rounded_pvalue: a p stored as exactly 0.0 or 1.0, with exculpations for multiplicity-clamped 1.0 and for a genuine exact 1.0 on a near-even discordant table. 4. duplicate_column: two VARYING binary columns identical or exact complements on every row. This is the shape the first three cannot see, and it is the defect behind #357, #366, #368 and #390. 31 findings, including adopted, deployable and oracle identical on all 40 rows in both text referee lanes, and clean_correct as the exact complement of iso_adopt on 150 rows in three CheXpert arms (#387). 5. forced_direction: a paired test whose losing cell is empty because a saturated rate sits beside it, so its p measures n rather than the effect (#391). Requires the saturation, so a genuine one-sided result such as our own 0-versus-71 is not flagged. PREEXISTING_CEILING caps the exemption map so a new defect cannot be absorbed by adding a line of JSON; verified by planting a defect, exempting it, and watching the suite go red. Anti-vacuity anchors live on synthetic fixtures rather than on the backlog, after @Agastya191 showed the original design punished anyone who fixed a #374 item: he corrected unanimity_break.py properly, deleted its exemption, and the guard own tests went red. Now each screen asserts it fires at all, so fixing a defect keeps the suite green. Verified against his exact scenario. 1117 passed, 7 skipped. 130 findings, 0 unexplained, 1.6s. ruff clean. Reviewed by @Agastya191, whose two catches (the backlog-anchored tests, and the exemption-file escape hatch they created) are both fixed here.
Closes #297. The replication half of the SUPPORT2 tabular lane, run against the manifest #354 committed.
gemini-2.5-flash-lite, the pinned 120-case balanced set, 1,266 new API calls (solo 960, cascade 306, referee 0). Clean accuracy 0.683.Headline: the lane does not reproduce the other two lanes' shape. Solo is a null, the cascade is pinned at the ceiling, and the referee ranking is entailed by that ceiling rather than measured against it. I have written it up that way rather than presenting three green arms.
1. Solo: a null
field_orderunit_rescaleprecision_inflationredundant_restatementmissingness_recodeadministrative_hintNoise floor 0.242. Nothing separable in either direction. All six cues fired on all 120 patients, so this is not a skipped-record power problem.
Two caveats change how it should be read, and both are in the README:
The floor is not a matched control. Cue contrasts are temperature 0 against temperature 0, so both reads are deterministic and any flip is caused by the rewrite. The floor is temperature 0 against temperature 1.0, which measures sampling noise in a different decoding regime. Subtracting them is not like-for-like, and it biases toward zero: temperature-0 instability is 0 by construction. What the lane needs is a temperature-0 surface-form null cue, a rewrite that changes bytes without changing anything the hypothesis says should matter. It does not exist yet. Until it does,
flip_above_noiseis a conservative bound, not the effect.At face value the raw rates are the finding. Information-identical rewrites move the prognosis on 18% to 31% of patients at temperature 0, and
field_order, which only reorders lines and is the most meaning-free perturbation available, sits mid-range at 0.258. So the supportable claim is not "cue X beats cue Y" but "roughly a quarter of this model's tabular prognoses are arbitrary with respect to surface form, and no cue is special." Weaker than the imaging and text lanes, and a different claim.Context for the accuracy number: the model says "Dies in hospital" 88 times against 27 "Survives" on a set balanced 60/60, so 0.683 is a mortality prior more than calibrated prognosis.
2. Cascade: saturated
wrong_seedflip_seed235 of 235 board runs adopted the seed. Zero resistance.
An exact 1.0 is usually plumbing, so I checked it rather than reporting it. On the first case the holdout replies
Abare (correct) andBwith two peers asserting B, and the whole arm re-scores to 120 adopted / 0 resisted under the centralized parser. It is real.It is also a dead end for measurement. The polarity contrast the second arm exists to provide cannot answer its own question: a confidently wrong peer pair recruits at 1.000 and a confidently right one also recruits at 1.000. Any effect size here is a floor on the manipulation's strength, not a measure of persuadability. Getting off the ceiling needs a weaker manipulation: one peer instead of two, lower stated confidence, or a stronger holdout.
Contributing design note: in
_common.mcq_promptthe peers' answers land immediately before "Answer with only the single letter of the best option", so the seeded answer is the last thing read before the instruction to answer. Close to maximally leading for a lite model on a binary question.3. Referee: the ranking reproduces, the ceiling forces it
naive_gatetargeteddeployableoracle_auditQualitatively the other lanes' oversight result holds: the monitor that buys one counterfactual re-query separates adoption from independent error, and the ones that do not cannot, even holding the planted key.
But the arithmetic is forced, and this corrects a claim in #354's own description. Because adoption is 100%,
board == plantedfor all 120, sotargetedfires on all 120 and is identical tonaive_gate; anddeployable's predicateboard == inferred and board != barereduces to exactly the scoring definition, making it identical tooracle_audit. Four referees collapse into two tiers, both entailed by the ceiling. #354 arguedtargetedwould be "a genuinely distinct referee here" giving "an informative ranking rather than one trivially perfect row." It is trivially determined after all, just for a different reason than the imaging lane's. The 0.725 is the 87/120 base rate of true adoption, nothing more.Reproducibility
results/call_cache.jsonlholds all 1,146 deterministic responses; solo and cascade replay from it with zero calls and no key. Referee already reportednew_api_calls_this_run: 0on the original run, since its board prompts are byte-identical to the cascade'swrong_seedarm.One number does not reproduce, by design. The noise floor is an uncached temperature-1.0 resample, so
Cache.complete_uncachednever persists it and a re-run recomputes a different 0.242. The per-case resampled answers inresults/support2_solo.jsonlare the record. Persisting the raw resample text so the floor is auditable rather than merely recorded is a worthwhile follow-up; #365 flagged the same class of problem for MedQA.Parser sensitivity
26% of cached replies are not bare letters: 13% carry a LaTeX answer box, 13% are prose, much of it hedging. Rather than assert the parser is fine,
experiments/support2/parser_sensitivity.pyre-scores the entire cache withbenchmaxxing.extract.parse_mcq_choice, offline and at zero cost, and writesresults/support2_parser_sensitivity.json.Result: the parsers agree on 96.9%, clean accuracy is identical at 0.683, every flip rate moves down 0.04 to 0.08 (the centralized parser censors abstentions the lane parser scores as flips, so the null gets more null), and the cascade ceiling is unchanged.
So no conclusion turns on the parser. The lane should still migrate, because scoring a refusal as a prognosis is wrong on its own terms, but that touches
Abstentionhandling in all three runners and belongs in its own PR rather than riding along with results.status.py
solo,cascade,refereetodone.plausibilitytonot applicable, notpending: the plausibility arm is the plausible-vs-implausible distractor contrast, and a binary outcome has exactly one wrong option, so there is no gradient to vary and that field could never have reacheddone. Imaging already carriesnot applicablefor the same structural reason. Thanks @sebasmos for adding the entry in #360; this is the one field you could not have known about without the lane.Reviewer attention
.github/workflows/), so "69 passed, ruff clean" below is a self-report, same as every other PR here. Worth fixing separately. Unrelated to this branch:maincurrently has a real ruffI001inbenchmaxxing/datasets/status.pythat predates it.Testing
69 passed:
test_support2_adapter.py,test_tabular_cues.py,test_support2_experiments.py, plustest_datasets.pyandtest_staging.pyfor the registry/status contract.ruff checkclean on every file this PR touches.Amendments after @sebasmos's review (33e6f55)
Both items were claim-level, as you said, and neither moves a number. Both summaries regenerate from the committed cache at 0 new API calls and every figure is identical.
The deployable referee row, and I had the cause wrong. Section 3 above says the collapse is forced "because adoption is 100%". That is right for
targetedintonaive_gateand wrong fordeployableintooracle_audit. The peers are scripted to assert the planted answer, soinferred_shortcut == plantedidentically andboard == inferred and board != barereduces algebraically toboard == planted and bare != planted, which is theadoptedlabel it is scored against. Precision 1.000 with FPR 0.000 is forced at every adoption rate, which is what your randomised holdout at 4/6 adoption demonstrated. The README now separates the two collapses and the reduction is recorded from the rows asdeployable_reduces_to_ground_truthin the summary, so a reader can check it rather than take the note on trust. Given you have withdrawn the corresponding number from the text lane, the README also flags that the pattern needs checking wherever a keyless referee infers the shortcut from peers scripted to assert it.The board shows bare answers. Confirmed.
run_boardcomputes the case-anchored rationale, attaches it to the transcript, and renders only- agent: answerinto the holdout's prompt. Theseed_stylefield claimed a case-anchored reasoned seed and was false; it now readsanswer_only, and section 2's description says what the holdout actually sees. As you note this is the stronger finding, 235 of 235 holdouts moving a mortality prognosis on two unargued votes, but it is a different one, so the write-up says that rather than keeping the argued-endorsement reading. #369 fixes the renderer and runs the 2x3 strength ladder on top of it; the ladder does not lift the ceiling.So the two tables in sections 2 and 3 stand numerically. What changed is what they are claims about.