fix(#000057): real token usage + cost per input/output separately
fox 2026-05-21: (1) use REAL API token usage, not len//4; (2) the substrate prefills a large retrieved CONTEXT as INPUT while solo feeds ~nothing, so per-completion-token over-charges the substrate — and per- TOTAL-token UNDER-charges it (its mix is ~98% cheap prefill tokens). Measured n=30 qwen-nothink/4090: substrate prefills ~6.6k input tok/query (claim_lattice) vs solo ~52 — ~127x. Neither single per-token denominator is honest; prefill (parallel, cheap/tok) and decode (autoregressive, dear/tok) must be costed separately. - OpenAICompatibleClient stashes data['usage'] as .last_usage (non- invasive; return type unchanged). - watt_bench captures real prompt_tokens + completion_tokens per call (both arms), aggregates per cell, and energy_cogs reports gross + marginal per BOTH 1k-total-tok and 1k-completion-tok plus the context size. Prints the prompt/completion split. - 12 tests incl. the prompt-context artifact (per-total cheap, per- completion dear). Full suite 2540 passed. The clean per-input-tok / per-output-tok split rides bench/watt_calibrate (slope calibration; separate commit once validated live).
This commit is contained in:
parent
32aeb37086
commit
459060c774
3 changed files with 131 additions and 68 deletions
|
|
@ -14,17 +14,31 @@ from bench.watt_probe import classify_power_bands
|
|||
# ---- energy_cogs -------------------------------------------------------
|
||||
|
||||
def test_gross_cogs_one_kwh():
|
||||
c = energy_cogs(gpu_joules=3.6e6, window_s=100.0, tot_tok=1000,
|
||||
c = energy_cogs(gpu_joules=3.6e6, window_s=100.0, completion_tok=1000,
|
||||
idle_mean_w=30.0, price_per_kwh=0.33)
|
||||
assert c["available"] is True
|
||||
assert c["gross_usd"] == 0.33 # 1 kWh * $0.33
|
||||
assert c["gross_usd_per_1k_tok"] == 0.33
|
||||
assert c["gross_usd_per_1k_total_tok"] == 0.33
|
||||
assert c["gross_usd_per_1k_completion_tok"] == 0.33
|
||||
assert c["window_mean_w"] == 36000.0 # blend, honestly labelled
|
||||
|
||||
|
||||
def test_prompt_context_makes_per_total_cheap_per_completion_dear():
|
||||
# The fox correction: substrate prefills a big CONTEXT (prompt_tok).
|
||||
# Per TOTAL token processed it is cheap; charging it all to the few
|
||||
# completion tokens makes it look expensive — that's the artifact.
|
||||
c = energy_cogs(gpu_joules=3.6e6, window_s=100.0, completion_tok=1000,
|
||||
idle_mean_w=30.0, price_per_kwh=0.33, prompt_tok=9000)
|
||||
assert c["prompt_tokens"] == 9000
|
||||
assert c["total_tokens"] == 10000
|
||||
assert c["gross_usd_per_1k_total_tok"] == 0.033 # 0.33 / 10000 *1k
|
||||
assert c["gross_usd_per_1k_completion_tok"] == 0.33 # 10x dearer
|
||||
assert (c["gross_usd_per_1k_completion_tok"]
|
||||
> c["gross_usd_per_1k_total_tok"])
|
||||
|
||||
|
||||
def test_marginal_uses_serving_floor_when_present():
|
||||
# Serving floor (middle-idle 60W) is the right marginal floor, not idle.
|
||||
c = energy_cogs(gpu_joules=3.6e6, window_s=100.0, tot_tok=1000,
|
||||
c = energy_cogs(gpu_joules=3.6e6, window_s=100.0, completion_tok=1000,
|
||||
idle_mean_w=30.0, price_per_kwh=0.33,
|
||||
serving_floor_w=60.0, gen_draw_w=400.0, gen_duty=0.5)
|
||||
assert c["marginal_floor"] == "serving"
|
||||
|
|
@ -35,15 +49,16 @@ def test_marginal_uses_serving_floor_when_present():
|
|||
|
||||
|
||||
def test_marginal_falls_back_to_idle_without_serving_floor():
|
||||
c = energy_cogs(gpu_joules=3.6e6, window_s=100.0, tot_tok=1000,
|
||||
c = energy_cogs(gpu_joules=3.6e6, window_s=100.0, completion_tok=1000,
|
||||
idle_mean_w=30.0, price_per_kwh=0.33)
|
||||
assert c["marginal_floor"] == "idle"
|
||||
assert c["marginal_joules"] == 3.6e6 - 30.0 * 100.0
|
||||
assert c["marginal_usd_per_1k_tok"] < c["gross_usd_per_1k_tok"]
|
||||
assert (c["marginal_usd_per_1k_total_tok"]
|
||||
< c["gross_usd_per_1k_total_tok"])
|
||||
|
||||
|
||||
def test_marginal_clamps_to_zero_when_floor_exceeds_load():
|
||||
c = energy_cogs(gpu_joules=5000.0, window_s=100.0, tot_tok=10,
|
||||
c = energy_cogs(gpu_joules=5000.0, window_s=100.0, completion_tok=10,
|
||||
idle_mean_w=30.0, price_per_kwh=0.33,
|
||||
serving_floor_w=100.0)
|
||||
assert c["marginal_joules"] == 0.0
|
||||
|
|
@ -51,16 +66,18 @@ def test_marginal_clamps_to_zero_when_floor_exceeds_load():
|
|||
|
||||
|
||||
def test_no_floor_gives_gross_only():
|
||||
c = energy_cogs(gpu_joules=3.6e6, window_s=100.0, tot_tok=1000,
|
||||
c = energy_cogs(gpu_joules=3.6e6, window_s=100.0, completion_tok=1000,
|
||||
idle_mean_w=None, price_per_kwh=0.33)
|
||||
assert c["available"] is True
|
||||
assert "marginal_usd_per_1k_tok" not in c # no floor -> can't decompose
|
||||
assert "marginal_usd_per_1k_total_tok" not in c
|
||||
|
||||
|
||||
def test_unavailable_when_inputs_missing():
|
||||
assert energy_cogs(None, 100.0, 1000, 30.0, 0.33)["available"] is False
|
||||
assert energy_cogs(3.6e6, None, 1000, 30.0, 0.33)["available"] is False
|
||||
assert energy_cogs(3.6e6, 100.0, 0, 30.0, 0.33)["available"] is False
|
||||
# zero completion AND zero prompt -> no tokens at all
|
||||
assert energy_cogs(3.6e6, 100.0, 0, 30.0, 0.33,
|
||||
prompt_tok=0)["available"] is False
|
||||
|
||||
|
||||
def test_price_is_the_only_lever():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue