Repository navigation
Give a model with no recorded tokens no cost, not $0 - #230
Merged
Merged
Conversation
model_cost_latency filled a missing total_cost_usd from PRICE_OVERRIDES_PER_1M with unrecorded token counts counted as 0, so Claude Fable 5, whose batch rows record no usage, came out at costUsd 0.0. The fill now needs recorded prompt and completion counts; otherwise cost is omitted. The paper's cost table looped only over models model_cost_latency returns, so it now seeds every board model, and Fable 5 keeps its modelStats fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The export now reports none of Fable 5's usage, so every driver test's fake exporter drops all four fields instead of reporting $0. The finish tests check that the carried row keeps the published key order. test_carried_usage.py pins the carry's precondition (the published row ends with the four fields, in CARRIED_USAGE's order). A property test runs release_20261006's carry on both exporter behaviours. A slow local test rebuilds the committed payload byte for byte through the real export and the carry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…orts $0 The finish drivers' comments said export_full_run reports $0 for Fable 5; it now reports none of its usage. The new docstrings no longer say why Fable 5's rows lack usage (PR #228's audit finds the batch-adapter account wrong); they say only that the frozen board rows record none. The paper's existing comment block is left as it was, and the seeding loop carries its own comment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…port Its two cost mutations popped or negated the exported Fable 5 row's costUsd, which the export no longer reports, so at target 44 they raised KeyError. There they now model an export that reports a cost after all (the old $0, or -0.0), which the carry must overwrite with no drift. @example pins both cases, plus a reordered Fable row, so the property no longer depends on Hypothesis sampling them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r1 executed a counterexample: one row records only a prompt count and another only a completion count. The model-level sums were then both recorded, so the fill still priced each missing count as $0. The fill now sums only rows that record both counts, as eval_no_tools prices no row missing either. Frozen output is bit-identical: every row of the two filled models records both counts. Also: - tests for split and mixed rows; - the property generator draws whole uncosted and untokened models, so the fill and the no-usage branches each get tens of cases per run; - a render-time assertion that every model the board prices shows a cost in the paper (it fails with ['claude-fable-5'] without the seeding loop); - docs/gpt61sol/design.md's "$0" sentence now in the past tense. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch was successfully deployed
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.
The bug
analysis.model_cost_latencyfills a missingtotal_cost_usdfromPRICE_OVERRIDES_PER_1Musing the model's summed prompt and completion tokens. It counted an unrecorded (NaN) sum as 0. Claude Fable 5's 1,984 rows in the frozen snapshot (paper/snapshot/20260501/runs/us_full_run_20260612_policyengine_4_16_1_populace/predictions.csv.gz) record no cost, tokens or latency, and Fable 5 has an override. So the function returnedcostUsd: 0.0, costPerHousehold: 0.0for it instead of nothing.Nothing published shows the $0:
CARRIED_USAGE).paper/index.qmdload_us_cost_latencytreats a cost of 0 or less as missing.An uncarried export (a
PARTIALstage, or any ad-hocexport_full_run) did show the $0 in the app.ModelLeaderboardrenders a cost whenevercostUsd != null, so Fable 5 appeared as "$0.00 total". Context:docs/latency_replay_audit.mdin #228, "What the published figures do with them".The fix
policybench/analysis.py: the override fill now prices only the rows that record both prompt and completion counts.eval_no_tools._reconstruct_token_costalready returns no cost for a row missing either count, so the fill now follows the same rule. A row missing either count adds nothing; it is not priced at $0 for the missing side. A model with no row to price gets no cost, and Fable 5, which records nothing, gets no entry at all. When every row records both counts, the arithmetic is unchanged, and so are the results, to the bit.paper/index.qmd:load_us_cost_latencylooped only over the modelsmodel_cost_latencyreturns. With Fable 5 gone from that output, Fable 5 would have lost itsmodelStatsfallback ($0.541 per household, 98 s). It would also have dropped out of the summary sentence, which names it the priciest model. The loader now seeds every board model first. A render-time assertion checks that every model the board prices shows a cost; it fails with['claude-fable-5']without the seeding loop. CI'spaperjob runs it.export_full_run(tests/test_finish_{argon,gpt61sol,haiku55,musewonder,adds0928}.py,tests/test_freeze_{argon,gpt61sol,haiku55,musewonder}.py) now drops all four Fable 5 fields, as the real exporter does now, instead of reporting a $0 cost. The finish tests assert that the carried row keeps the published key order.Invariants
Each is a property that holds for every input. Each has a test.
costUsdandcostPerHouseholdare both present or both absenttest_cost_is_present_exactly_when_recorded_usage_supports_it(Hypothesis, 300 examples)total_cost_usd, or when it has an override and one of its rows recorded both prompt and completion counts. A row missing either count is not priced. So no cost rests on a count nobody recorded, and a model with no usage gets no cost, not $0.test_price_override_without_recorded_tokens_gives_no_cost(the Fable 5 case),test_price_override_needs_both_token_counts,test_price_override_needs_both_counts_on_one_row,test_price_override_prices_only_rows_with_both_counts,test_recorded_zero_cost_and_recorded_zero_tokens_stay_zerocostPerHousehold=costUsd÷ the input's distinct householdscostUsd,costPerHousehold,latencySecondsandtotalTokens, except Fable 5's, equalmodel_cost_latencyon the frozen predictions, to the bit and with the same fields present. Fable 5's are carried, not computed.test_frozen_usage_reproduces_every_published_model_but_fable_5,test_frozen_fable_5_usage_is_carried_not_computedtest_every_driver_carries_fable_5s_four_fields_in_one_order,test_the_published_fable_5_row_ends_with_the_carried_fieldsrelease_20261006.export_payload's real carry restores the published row byte for byte whether the export omits Fable 5's usage or reports a $0 cost, and leaves every other row alonetest_the_carry_restores_the_published_row_whichever_way_the_export_reports_it(Hypothesis)export_full_runand the carry. The raw export reports none of Fable 5's usage.test_the_committed_payload_rebuilds_through_export_and_the_carryMutation checks:
analysis.pyfails 8 of the cost tests and the slow rebuild.release_20261006.CARRIED_USAGEfails tests 6 and 7.No published number changes
Before = origin/main bf845cb, after = this branch, run on the frozen snapshot (the live release's 50-model board):
model_cost_latencyentries for the 49 models other than Fable 5 (values asrepr, key order, model order)model_cost_latencyentry{costUsd: 0.0, costPerHousehold: 0.0}costUsd: 0.0)export_full_runFable 5 rowcostUsd: 0.0, costPerHousehold: 0.0export_full_run+release_20261006.export_payloadcarry vs the committeddata.json.gzScripts and outputs are in
~/reviews/policybench-cost-override-needs-usage/evidence/(before-after-diff.txt,capture_cost_latency.py,export_carry_check.py; the row-level fill's outputs are inafter-rowlevel/andexport-after-rowlevel/, identical toafter/). In the frozen predictions, the override path is reached by three models only:Driver audit
release_20261006.export_payload,release_20261013b.export_payloadverify_payloadrequires the same key order and the sameSTABLE_STATSbytes.CARRIED_USAGEorder, which is where the published row has them, becausecostUsd/costPerHouseholdwere the exporter's last keys. Shown byte-identical on real data.finish_{gpt61sol,haiku55,musewonder,argon}.build_payloadrelease_20261006.export_payload, which carries itself).costUsd, which the export no longer has. At target 44 its two cost mutations now model an export that reports a cost after all; the carry overwrites it with no drift.@examplepins those cases so they don't depend on sampling.freeze_{gpt61sol,haiku55,musewonder,argon}.rebuild_payloadbuild_payloadand requires the staged bytesfinish_adds0928.export/replay_base_referencesfreeze_snapshot.py(row["costUsd"]for every row)partialbuildsNo driver compares Fable 5's cost to 0. The
total <= 0checks in the finish drivers are aboutrun_state.jsontotals.Not changed
_sum_or_nan, unchanged), and a filled cost sums the rows that recorded both counts. Either way the cost covers those rows only. No override-filled model in the snapshot has partial coverage.analysis.pyat render time. This PR doesn't re-render, so that row reflects the last render until the next one. Past non-release PRs that changedanalysis.pydidn't re-render either (ea67093, 8f3c44c, e9e91df, fba3cfc); releases do. No displayed number changes.Tests
PYTHONHASHSEED=0 pytest -m "not slow" -n 4at 473ebb3: 5,160 passed, 58 skipped. The fixed hash seed works around the collection-order issue under Related; CI runs serially.tests/test_carried_usage.py, slow test included, at 473ebb3: 9 passed.Review
subfleet run --task review --tier hard, job 20261014-104306-pb230-r2, lane codex-4) at 473ebb3: AGREE, no findings. It read the implementation, consumers, driver paths and tests. It did not execute anything, and it relied on the evidence above for bytes, rendered numbers, CI and the Disclose why 330 frozen households record near-zero latency and two models record none #228 merge.Related
docs/gpt61sol/design.mdputs its "$0" sentence in the past tense.~/reviews/policybench-cost-override-needs-usage/resolve_228_conflicts.py):test_cost_latency,test_latency_replay_audit,test_carried_usage,test_disclosures,test_paper_resultsandtest_finish_gpt61solgive 374 passed, 4 skipped. The paper's cost table, its summary sentence and Disclose why 330 frozen households record near-zero latency and two models record none #228's new caption (with its dash assertions) are identical to Disclose why 330 frozen households record near-zero latency and two models record none #228 alone. This PR's seeding loop is what keeps Disclose why 330 frozen households record near-zero latency and two models record none #228's assertion that cost dashes are only zero-cost models true, so the two PRs depend on each other there.model_cost_latency"returns $0" for Fable 5. Its owner session has been told. Whichever PR lands second fixes the sentence.pytest -nfails at collection on main:tests/test_reference_audit_pins.pyparametrizes over sets. This was worked around here withPYTHONHASHSEED=0and split into its own task.axiom: n/a: benchmark cost accounting, no policy rule.
🤖 Generated with Claude Code