Dayward AI
Week 3 · D20About 4 hours

Evaluation and Cost: Designing a Benchmark Set, Computing Pass Rate, and Reading Token Usage and Cache Hits

Tuning an agent by gut feel only makes it worse: design a benchmark set of tasks that can be scored automatically, get out a pass rate, average turn count, and token usage, compute the cache hit rate, then use a before-and-after comparison of one prompt change to show why evaluation must come before optimization.

Today's goals 0/3

Sign in to tick these off and save your progress.

Today's Goals

  1. Design an automatically-scorable agent task benchmark set, and explain what the scoring function should look at
  2. Compute four metrics: pass rate, average turn count, token usage, and cache hit rate
  3. Show the full evaluation-drives-optimization flow with one before-and-after comparison

Nineteen days of adding things to mca; today is the first time we turn around and measure whether it is any good. When you finish, scroll back up and tick the three goals off.

Plain-Language Walkthrough

The probation review: measure with a test set

Three months in, does the new hire go permanent?

The cheap way is to ask whoever trained them, "how is this person?" The answer is almost always "pretty good" — which sounds like information and is not, being decided by the most recent incident and the speaker's mood today. Serious companies hand over a fixed set of tasks instead and score the result against one ruler. The value of that set is not that it measures how clever the person is. It is that next quarter it is still the same set, so "better than last quarter" acquires a meaning for the first time.

Tuning an Agent is the same situation. You change one sentence in the system prompt, try it three times in a terminal, and decide it "feels smoother." Far too much went into those three runs: the model took a good path, you phrased the request more clearly, the sandbox repo still carried last run's edits. People who tune an Agent by feel make it worse over time, because every round they are treating noise as signal.

So today's build is deliberately plain: five tasks, each scorable by a piece of code, run in one shot, printing four numbers. You have seen task one many times — run the tests, fix the failing case, run them again to prove it is green — the baseline task since day four.

Four tests for whether a task belongs in the set

One: a definite initial state. Every task regenerates the sandbox repo first, then arranges it into its own shape if it needs to. Most often broken: run task one, skip the reset, run task two, and task two's result quietly carries task one's edits — with no warning.

Two: an end state a program can decide. The criterion must be something code evaluates to true or false: the test runner's exit code, whether a line is present in a file, whether a file was touched at all. "Answered well" is not a criterion. A benchmark set exists to be repeatable and unattended; the moment a human has to judge, it has decayed back into "pretty good."

Three: a difficulty gradient, including a negative case. If all five tasks are "fix it," you measured only whether the Agent is willing to touch things, and an Agent that edits whatever it sees scores full marks. So task two's correct behavior is to change nothing at all.

Four: it must run offline. Every step has a matching branch in the offline script, so the set runs end to end with no key. An evaluation that only runs with a real key ends up as "we ran it once before release."

The five tasks:

idDifficultyThe requestEnd-state criterion
fix-divide1Run the tests, fix the failing case, run them againTests green, and calc.js guards against division by zero
explain-only1Do not touch anything yet, just explain what is wrong with divideTests still red, and both files byte-for-byte unchanged
skill-wording2Handle divide's zero case the way the team skill says toTests green, and the thrown message matches the skill's wording exactly
refactor-twice2Add the guard to divide, then add a comment explaining itTests green, and both the guard and the comment are present
already-green3Fix the failing test case and run the tests again to confirmTests green, and the guard is still there (the starting point is already fixed)

One trap deserves naming, because it arrives with no error message: every time the thing under test grows a new rule, the set may quietly stop being valid. Add a hook that rejects a class of writes, tighten a permission rule, change the wording a skill mandates — and a task whose end state predates that change now encodes behavior the Agent is no longer allowed to produce. Nothing crashes; the pass rate drops, or worse, stays high while measuring the wrong thing. A benchmark set is a maintained artifact: when a rule lands, walk the tasks and ask which end states it just invalidated.

Score the end state, not the process

This is today's one sentence to take away, and the place people most often get it wrong. The end state is what the repo looks like once the task finishes; the process is what the Agent said, how many tool calls it made, and whether any failed on the way. Scoring must not look at the process, for three reasons, each worse than the last.

One: many paths are equally correct. Read then edit, test then edit, edit directly — same result. Scoring the process enshrines whichever path you imagined the day you wrote the set, so a shorter route loses points and the evaluation becomes an exam in resembling you.

Two: a failure inside the process is not a failed task. Task five, already-green, exists for this: the starting point is already fixed, so the first exact-replace necessarily misses and returns a failed tool result. The Agent then runs the tests, they are green, and the task genuinely is done. Scoring the process calls that a loss; scoring the end state calls it a win, and the end state is right. The self-test pins it down: tool failures greater than zero, verdict still pass.

Three, the dangerous one: process scoring degrades into "the model said it was done, so it is done." A regular expression over the model's closing paragraph hunting for "fixed" or "all green" always reports a beautiful pass rate, because models almost never admit they fell short. A scorer that always gives high marks is worse than no scorer at all: it lets you merge a pile of regressions with confidence.

A related discipline: the scorer runs the tests itself, and does not borrow the tools of the thing under test. Judging what run_command produced by calling run_command means any defect in the tool layer — truncation cutting off the failure summary, a timeout read as success — corrupts the scoring too, invisibly. The scorer does the dumbest possible thing: spawn a process, run node --test, believe only the exit code.

src/evals/grade.ts
export function runTests(repoDir: string): Promise<{ pass: boolean; output: string }> {
  return new Promise((resolve) => {
    // The scorer spawns its own process and trusts only the exit code. Borrowing the
    // tools of the thing under test means a broken tool corrupts the verdict too.
    const child = spawn('node', ['--test'], { cwd: repoDir, stdio: ['ignore', 'pipe', 'pipe'] })
    let output = ''
    const collect = (chunk: Buffer) => {
      output += chunk.toString('utf8')
    }
    child.stdout.on('data', collect)
    child.stderr.on('data', collect)
    const timer = setTimeout(() => child.kill('SIGKILL'), 30_000)
    child.on('close', (code) => {
      clearTimeout(timer)
      resolve({ pass: code === 0, output: output.slice(-2000) })
    })
  })
}

The scorer needs checking too, via the reverse assertion: feed it an input that ought to fail and confirm it reports failure. Self-test item two does exactly that — the untouched sandbox repo must be scored red. Without it, a scorer that runs nothing and returns pass sails green forever; that is the lab's first trap.

Four metrics: pass rate is the conclusion, the other three are the explanation

MetricWhat it answersWhat it cannot explain
Pass rateDid it get there. This is the only conclusionWhat it cost, how stable it is
Average turnsHow smoothly. Three turns and eight turns differHow expensive each turn is
Token usageInput and output spent this runHow much of it was cheap
Cache hit rateHow much of the input hit cacheIt is not itself a goal

The fourth is the only one that may not exist: sum the cached field inside each turn's returned usage, divide by the summed input tokens. Two details are easy to get wrong.

The denominator is input tokens, not total tokens. Caching applies to input only; input plus output gives a number that is permanently too small and wobbles with how talkative the model happened to be.

When every cached count is zero, report "unavailable" rather than 0.0 percent. Two possible causes: nothing hit cache, or the gateway you use does not return that field. The code cannot tell them apart, so it must not pretend it can. The offline script does not simulate caching, so that dashboard cell reads "unavailable" with a note pointing at your gateway's documentation. That is not laziness — it is the most common situation in the real world, which is why the branch is worth getting right offline first. How caching engages and how it is billed differs between gateways; go by the documentation of the one you use. This course guesses nothing.

src/evals/report.ts
// Denominator is input tokens: caching applies to input only, so using the
// total would make the rate swing with how long the outputs happened to be
const known = cachedTokens > 0
return {
  passRate: attempts.filter((a) => a.passed).length / n,
  avgRounds: sum((a) => a.rounds) / n,
  // All zeroes has two causes: nothing hit, or the gateway does not report it.
  // The code cannot tell them apart, so it must not pretend to.
  cacheHitRate: known && promptTokens > 0 ? cachedTokens / promptTokens : null,
  cacheNote: known ? 'hit rate = cached input tokens / input tokens' : 'unavailable: check your gateway docs',
}

One more engineering rule: do not count tokens a second time. The LimitTracker from day six already keeps books for three hard ceilings; cost reporting just feeds the same usage events into the same object. Two ledgers for one sum eventually produce "the ceiling says thirty thousand, the report says twenty-eight thousand," and nobody can say which is right. Same for the tool columns — take the call record the render layer already accumulated.

Cost: only tokens are facts, the money is something you supply

Nineteen days have printed token counts and never a price. Money appears today for the first time, so the terms have to be nailed down.

There is not a single gateway's price anywhere in the code — not one number. Unit prices come from environment variables, defaulting to zero. Three reasons: price lists change, so a price in code is a fact with an expiry date; the same model costs different amounts on different gateways, and this course has refused to bind to any one of them since day one; and the practical one — a default of zero makes "no unit price configured" look like "free," so when it is unset that column reads "no unit price set (cost unavailable, not zero)" rather than a tidy 0.00.

TextText
LLM_PRICE_CURRENCY=USD     # just a label, the code converts nothing
LLM_PRICE_IN=1             # per million input tokens, taken from your own bill
LLM_PRICE_OUT=4            # per million output tokens
LLM_PRICE_CACHED=0.1       # per million cached input tokens

For the cached portion, the lab subtracts it out of the input and prices it separately — one common arrangement only, and rules differ, so go by the billing page of the gateway you use. What matters: discuss tokens and relative proportions first, money second. Tokens are what you can directly optimize and they do not expire; convert everything into currency and every conclusion has to be recomputed the next time a price list moves.

Change one thing, run again: the smallest closed loop

With a benchmark set the loop closes: measure a baseline, change exactly one thing, measure again, read the difference. The critical part is exactly one thing: change the prompt and the truncation ceiling together, see an improvement, and you cannot tell which caused it — so next time you carry both forward, and one may be doing harm.

The lab's A/B uses the same five tasks and the same scorer, differing only by one extra sentence at the end of the system prompt: "before editing any file, run the tests once." Offline, one run per task:

VariantPass rateAverage turnsTotal tokensCache hit rate
Baseline100 percent (5/5)3.6about 28075unavailable
One extra prompt line100 percent (5/5)3.6about 28415unavailable

Reproducible here are the pass rate, the average turn count, and the stable difference of plus 341 between the two rows (about 1.2 percent). The totals themselves wobble by around 0.1 percent, from exactly one source: the test output fed back in carries the test runner's millisecond timing line. The hit-rate column is permanently unavailable offline, and elapsed time is not reproducible at all, so nothing in the lab asserts on it.

The conclusion may not be the one you expected: an offline A/B measures cost, not effect. The offline script's answers are fixed, so a changed prompt does not make it take a different route, the pass rate cannot move, and the only thing that grew is tokens — 1.2 percent more money for nothing at all.

That is a property of evaluation rather than a flaw in the lab, and today's second takeaway: what an A/B can measure depends on whether the layer under test is actually varying. To measure behavior change you must go through a real gateway. And the moment you do, a third thing shows up.

Variance: run the same task three times before concluding anything

A real model will not do the same task the same way twice: different path, different turn count, different tokens, and now and then a pass where the last run failed. So "pass rate went from 80 to 84 percent after my change" proves nothing on its own — first you need to know how much it wanders when the same code runs three times untouched. If it wanders eight points by itself, a four-point "gain" is noise pointing a convenient direction.

So the set must support repetition. The lab uses the plainest measure: the range as a proportion of the mean. Offline, the range is essentially zero — turn counts identical, tokens differing only by that timing line. The number looks beautiful and means the opposite: near-zero variance offline says this evaluation is not testing the model at all, only your own code. A good regression test, not a model evaluation. Both are useful; do not confuse them:

  • Offline evaluation answers "is my code still behaving the way it did." Fast, deterministic, fit for every commit.
  • Real evaluation answers "did it get better after the model or prompt changed." Slow, noisy, must be repeated, and costs money every run — all the more reason to get the scorer, the metrics and the dashboard right offline first, rather than debugging your own statistics with paid calls.

The dashboard: the report is data, the display is display

The last step is almost free: each evaluation writes a JSON report to disk, the dashboard runs on local port 3120, and the page fetches that endpoint itself.

One decision is worth stating: why not just build the numbers into the HTML. Because the report is data and the dashboard is one presentation of it. Split apart, the same JSON can be a CI gate (pipeline red when the pass rate drops below a line), the before side of the next comparison, or input to a script of your own — none of which requires parsing a page of HTML. The moment you reshape the report to make the page look nicer, that boundary is gone.

TextText
work/evals/r-20260914063251-j2h9.json   one per evaluation; dashboard and gate both read this
http://127.0.0.1:3120/                  static page, fetches the endpoint below itself
http://127.0.0.1:3120/api/runs          the run list plus the latest full report

The dashboard has a closing move: if you start it, you close it. It is a long-running server; leave it up and the process never exits and the port stays held. Calling close() is not enough — the browser's keep-alive connections hold the callback off forever, so existing sockets must be destroyed too. Same rule as parking the child process on day fifteen: anything you start, somebody has to be responsible for stopping.

Source Reading

Hands-On Lab

🧪 D20 lab: an automatically scored benchmark set and a dashboard for pass rate, turns, tokens and hit rate

Code location: labs/my-coding-agent-21days/day-20-evals-and-cost

The lab builds the five tasks, the scorer, the four metrics, the A/B variants and the dashboard in one pass, all runnable offline. All five exercises are traps that never raise an error and only hand you a flattering number: a scorer that always passes, an always-true byte comparison, a second token ledger, a hit rate whose denominator is the total and which prints zero instead of "unavailable," and a fake A/B where the variant never took effect. The starter passes six of fourteen unmodified.

  1. Make the scorer actually run the tests, and watch self-test item two go from red to green — it reverse-confirms that the untouched repo really is red.
  2. Add the byte-for-byte comparison, and watch the negative-case task finally decide whether a file that should not move has moved.
  3. Feed usage into day six's ledger object, and watch the token column stop being zero, with input and output reconciling line by line.
  4. Change the hit rate's denominator to input tokens and return "unavailable" when everything is zero, and watch that dashboard cell turn from 0.0 percent into a sentence.
  5. Actually splice the variant's sentence into the system prompt, watch the two variants' token totals differ for the first time, then run with --repeat=3 and look at the variance.

Acceptance is five ticks: all fourteen self-test items pass; the untouched repo is scored red; the negative-case task makes zero tool calls with both files byte-for-byte unchanged; task five has a tool failure in the process and an end-state verdict of pass; both dashboard routes respond, and the port is released afterwards.

Interview Questions

Today's three questions test design judgment about benchmark sets and metrics:

  1. How do you design a benchmark set for a Coding Agent? Should the scoring function look at the end state or the process?
  2. Which metrics would you use to judge an Agent? Why is pass rate not enough?
  3. With unstable results across repeated runs of one task, how do you judge whether a change helped under that noise?

Full prompts, analyses and key points are in this course's day-twenty question bank. Question three discriminates most: most answer "run it a few times and average," and few say "measure the baseline's own spread first, then check whether the change exceeds it."

Checklist and Tomorrow

  • I can name the four tests a benchmark task must pass, and why the set needs a negative case
  • I can explain why scoring looks only at the end state, and give an example of a failed process with a passing end state
  • I can say why "the model said it was done" is the most dangerous kind of scoring
  • I can say why the scorer must not borrow the tools of the thing under test, and what a reverse assertion is for
  • I can state what each of the four metrics answers, and why pass rate alone will lie
  • I can say that the cache hit rate's denominator is input tokens, and why all-zero must report "unavailable"
  • I can explain why unit prices must be supplied by the reader, and why a default of zero must not read as free
  • I can say why an A/B changes exactly one thing, and what a fake A/B looks like
  • I can explain what offline and real evaluation each answer, and why near-zero offline variance is not good news
  • I can say why the report and the dashboard split into a data layer and a display layer

Tomorrow is the last day, D21, "Packaging and Release: a Global Command, a Config Directory, Versioning and Updates — a Twenty-One-Day Retrospective." Today's set has one more use there: run it before you release, so that "it installs and completes one real task" also becomes a judgment with an exit code, instead of one more round of "it worked on my machine."

Interview questions

  • How do you design a benchmark set for a coding agent, and should the grader look at the end state or at the process?怎么给一个 Coding Agent 设计基准集?判分函数该看终态还是过程?
    Common in ChinaCommon overseasBasic#benchmark-design#grading

    How to reason about it · think before answering

    1. This tests whether you have actually assembled a benchmark set. People who only read papers answer with words like coverage and diversity; people who built one start from the shape of the grader, because that is the only thing that decides whether the conclusion is true.
    2. How to break it down - first the criteria for admitting a task, then the end-state-versus-process question, then how the grader itself gets checked.
    3. Four criteria for a task - a well-defined initial state (rebuild the sandbox before every attempt, or leftovers from the previous task make results unexplainable); an end state a piece of code can judge true or false; a spread of difficulty including at least one negative task; and offline runnability where possible, otherwise the evaluation degrades into a manual pre-release run.
    4. The negative task is the one people skip. If every task is fix this, all you measure is willingness to act, and an agent that edits everything it sees scores full marks. And asserting the tests are still red is not enough - the model could have deleted the test file and they would still be red; you also need the untouched files to be byte-for-byte identical.
    5. The answer is end state. Three reasons - many different routes are equally correct, so grading the process enshrines the one route you happened to think of; a failure inside the process does not mean the task failed (in the task whose starting point is already fixed, the first exact-replace necessarily misses, yet the end state is correct); and worst of all, process grading easily degenerates into taking the model's word for it, and models almost never admit they failed.
    6. A related discipline - the grader runs the tests itself rather than reusing the agent's own command tool. Judging a system with the system under test means any flaw in the tool layer corrupts the verdict invisibly. The grader also needs a reverse assertion - feed it an input that must fail and confirm it fails, otherwise an always-true grader stays green forever.
    7. Likely follow-ups - if process metrics do not decide the grade, should you still collect them (yes, they explain rather than conclude); how to score genuinely subjective tasks; how to keep the benchmark from being overfitted.

    分析过程 · 先想清楚再作答

    1. 这题在考「你有没有自己攒过一套基准集」。只读过论文的人会答「多样性、覆盖度」这类词;攒过的人第一句就会说判分函数的形状,因为那是唯一会决定结论真假的东西。
    2. 怎么拆:先给出题的几条判据,再单独回答终态还是过程,最后说判分器自己怎么被检查。
    3. 出题四条:每道题有明确的初始状态(跑之前必须清场重建,否则上一题的残留会让结果无法解释);终态必须能被一段代码判真假;难度要有梯度而且必须有负例;能离线跑的尽量离线跑,否则评估最后会退化成上线前手动跑一次。
    4. 负例这一条最容易被跳过。全是「把它修好」的题,量出来的只是「它敢不敢动手」,一个见什么改什么的 Agent 会拿满分。而负例题光断言「测试还是红的」不够——模型可能把测试文件删了,测试照样红;必须再加一条「不该动的文件逐字节没被动过」。
    5. 结论是看终态。三条理由:同一件事有很多条路都对,按过程判等于把你想到的那条路当成唯一答案;过程里的失败不等于任务失败(起点已经是好的那道题,第一次精确替换必然失配,但终态达标);最危险的是过程判分很容易退化成「模型说它做完了就算做完」,而模型几乎从不承认自己没做到。
    6. 还有一条同源纪律:判分器自己跑测试,不借用被测对象的工具。用被测对象的工具去判它自己,工具层一有毛病判分会跟着一起错。判分器也要配反向断言——拿一个应该失败的输入喂给它,确认它真的判失败,否则一个恒真的判分器可以一路绿到底。
    7. 可预期的追问:过程指标既然不判分那还要不要收(要,它们是解释不是结论);怎么给「答得好不好」这类主观题打分;基准集自己怎么防止被过拟合。

    Key points

    • Four admission criteria - defined initial state, automatically decidable end state, a spread of difficulty, and at least one negative task
    • Rebuild the initial state before every attempt, or leftovers make results unexplainable
    • Grade the end state - many routes are correct, in-process failures are not task failures, and process grading degenerates into trusting the model's own report
    • A negative task needs a byte-for-byte unchanged assertion, not just still red tests
    • The grader runs tests itself rather than through the agent's tools, and needs a reverse assertion so it cannot be always-true

    答题要点

    • 出题四条:明确的初始状态、可自动判定的终态、有难度梯度、必须有负例
    • 每次尝试都从头重建初始状态,否则上一题的残留会让结果无法解释
    • 判分看终态:多条路都对、过程失败不等于任务失败、过程判分易退化成「模型说做完了就算做完」
    • 负例题不能只断言「测试还是红的」,要加逐字节未改动这条判据
    • 判分器自己跑测试不借用被测对象的工具,并且要配反向断言防止它恒真
  • Which metrics would you use to judge a coding agent, and why is pass rate not enough?你会用哪几个指标衡量一个 Coding Agent 的好坏?为什么通过率不够?
    Common in ChinaCommon overseasIntermediate#metrics#cost#cache

    How to reason about it · think before answering

    1. This tests whether you have actually made decisions from these numbers. Reciting four names is easy; the hard part is saying which one is the conclusion, which ones only explain it, and which one may not exist at all.
    2. How to break it down - layer them first (one conclusion, three explanations), then say what each answers and cannot answer, then spend time on the cache metric's traps.
    3. Pass rate is the only conclusion, but alone it misleads. Two runs both at a hundred percent, one averaging three rounds and the other seven and a half, are not the same agent - the second costs more than twice as much and is far likelier to hit the round limit halfway through a real repository. Average rounds answers how smoothly, tokens answer how much.
    4. Cache hit rate is the only one of the four that may not exist. Two traps - the denominator must be input tokens, not total tokens, because caching only applies to input and a total denominator drifts with how talkative the model was; and an all-zero hit count has two possible causes, genuinely no hits or a gateway that does not return the field, so when code cannot tell them apart it must report unavailable rather than 0.0 percent.
    5. On cost - only tokens are facts, money is something the operator fills in. Unit prices vary by gateway and over time, so hard-coding them bakes an expiring fact into the code, and the default must visibly distinguish no price configured from zero cost, otherwise a 0.00 reads as free. Cache billing rules differ between providers, so defer to the documentation of whichever gateway you use.
    6. One engineering point - keep a single ledger. The loop already accounts for usage to enforce its hard limits, so cost statistics should reuse that same object rather than counting again; otherwise the two numbers eventually disagree and nobody can explain why.
    7. Likely follow-ups - how to turn these metrics into a CI gate; how to judge a change where cost rose but so did pass rate; whether average or p95 rounds is the more useful number.

    分析过程 · 先想清楚再作答

    1. 这题在考「你有没有真的拿这些数做过决定」。背得出四个名词不难,难的是说清哪个是结论、哪几个是解释,以及哪一个可能根本取不到。
    2. 怎么拆:先分层(一个结论加三个解释),再逐条说它回答什么、解释不了什么,最后专门讲缓存那一条的坑。
    3. 通过率是唯一的结论,但它单独看会骗人:两次评估都是百分之百通过,一次平均三轮、一次平均七轮半,后者多花一倍多的钱,而且在真实仓库里更容易撞上轮数上限半途而废。所以平均轮数是「顺不顺」,token 是「花了多少」。
    4. 缓存命中率是这四个里唯一可能不存在的。两个坑:分母必须是输入 token 而不是总 token,因为缓存只对输入生效,拿总量当分母会得到一个随模型这次话多话少乱晃的数;命中数全是 0 有两种可能——真的没命中,或者这个网关不回传这个字段,代码分不清就该报「不可用」而不是 0.0%。
    5. 成本这一条的口径:只有 token 是事实,钱是使用者自己填进去的。单价随网关与时间变,写进代码等于给代码加一个会过期的事实;默认值必须显式区分「没配单价」与「零成本」,否则一个 0.00 会被当成不要钱。缓存怎么计费各家规则不同,以所用网关的文档为准。
    6. 工程上还有一条:这些数只记一本账。循环里本来就有一套用量记账(硬上限靠它),成本统计应该复用同一个对象而不是另数一遍,否则迟早出现两处口径对不上、谁也说不清的情况。
    7. 可预期的追问:怎么把这些指标做成持续集成的门禁;成本涨了但通过率也涨了该怎么判;平均轮数和 p95 轮数哪个更该看。

    Key points

    • Pass rate is the only conclusion; rounds, tokens and cache hit rate merely explain it
    • Two agents both at a hundred percent, one at three rounds and one at seven and a half, differ in cost and in risk of stalling
    • Cache hit rate's denominator is input tokens, not total; report unavailable rather than 0.0 percent when the hit count is zero
    • Only tokens are facts - prices come from the operator, the default must distinguish unset from zero, and cache billing follows the gateway's own docs
    • Keep one usage ledger by reusing the loop's accounting instead of counting again

    答题要点

    • 通过率是唯一的结论,轮数、token、缓存命中率都是解释
    • 同样百分之百通过,平均三轮与平均七轮半不是一个东西:代价与半途而废的风险都不同
    • 缓存命中率的分母是输入 token 不是总 token;命中数全为 0 时报「不可用」而不是 0.0%
    • 只有 token 是事实:单价由使用者填、默认值要区分「没配」与「零成本」,缓存计费规则以网关文档为准
    • 用量只记一本账,复用循环里那套记账,别另数一遍
  • Results for the same task vary between runs. How do you tell whether a change actually helped, given that noise?同一道题多次运行结果不稳定,你怎么在这种噪声下判断一次改动是不是真的有效?
    Common in ChinaCommon overseasDeep dive#variance#ab-testing#evaluation

    How to reason about it · think before answering

    1. This tests statistical instinct and experiment design, and it separates candidates sharply. Most answer run it a few times and average, which solves half the problem - the average itself has spread, and without knowing that spread you cannot interpret a difference.
    2. How to break it down - measure the noise first, then make the change, then the discipline around the experiment.
    3. Step one is measuring the baseline's own noise - with the code untouched, run the whole benchmark three to five times and see how far pass rate and rounds move. If it swings eight points on its own, a four-point improvement is just noise pointing the other way. Skip this step and every later comparison is void.
    4. Step two is the change, and only one change at a time. Alter the prompt and the truncation limit together and a better result tells you nothing about which one helped; next time you carry both forward, possibly including one that hurts.
    5. Step three is the criterion - the difference must clearly exceed the baseline spread to count. When unsure, add repetitions or add tasks rather than re-reading the same run. Task count is itself part of the noise - a five-task benchmark has a pass-rate granularity of twenty percent and cannot resolve anything smaller than one task.
    6. A counterintuitive point - near-zero variance in an offline evaluation is not good news. It means the run is not testing the model at all, only your own code. That makes it a good regression test but not a model evaluation. Real evaluation is slow, noisy and costs money per run, which is exactly why the grader and the reporting code should be debugged offline first.
    7. Likely follow-ups - how to judge a change where both cost and pass rate rose; whether fixing a random seed removes this noise (with most gateways it does not); how to avoid overfitting to the benchmark over time.

    分析过程 · 先想清楚再作答

    1. 这题在考统计直觉与实验设计,区分度很高。多数人会答「多跑几次取平均」,那只解决了一半——平均值本身也有散布,不知道散布多大就没法判断差值。
    2. 怎么拆:先量噪声,再谈改动,最后谈实验设计上的几条纪律。
    3. 第一步是量基线自己的噪声:同一套代码原地不动,把基准集连跑三到五遍,看通过率与轮数在多大范围里晃。如果它自己就能晃出八个百分点,那四个点的「提升」只是噪声换了个方向。这一步没做,后面所有对比都不成立。
    4. 第二步才是改动,而且一次只改一处。同时改了提示词和截断上限,结果变好了你也不知道是哪一处起的作用,下一次会把两处一起带走,其中可能有一处是负作用。
    5. 第三步是判据:改动带来的差值要明显超过基线的散布才算数;不确定就加大重复次数或者加题,而不是反复盯着同一次结果解读。题目数量本身也是噪声的一部分——五道题的通过率颗粒度是 20%,天然分辨不出小于一题的差别。
    6. 还有一条反直觉的:离线评估里抖动接近零不是好消息。那说明这一轮根本没在测模型,只在测你自己的代码——它是一个好的回归测试,但不是一次模型评估。真实评估慢、有噪声、每跑一次都要花钱,所以更要先在离线下把判分器与统计代码调对,别拿真实调用去调试自己的报表。
    7. 可预期的追问:怎么判「成本涨了但通过率也涨了」;固定随机种子能不能消掉这种噪声(多数网关消不掉);怎么防止长期照着基准集调导致过拟合。

    Key points

    • Measure the baseline's own spread first - run the same code three to five times and see how far pass rate and rounds move
    • A change only counts when its difference clearly exceeds that spread; otherwise it is noise pointing the other way
    • Change one thing at a time, or you cannot tell which one mattered
    • When unsure, add repetitions or tasks - a five-task benchmark resolves pass rate only in twenty-point steps
    • Near-zero offline variance means you are testing your code, not the model - a regression test rather than an evaluation

    答题要点

    • 先量基线自己的散布:同一套代码连跑三到五遍,看通过率与轮数晃多大
    • 改动带来的差值必须明显超过那个散布才算数,否则是噪声换了个方向
    • 一次只改一处,否则分不清是哪一处起的作用
    • 不确定就加重复次数或加题;五道题的通过率颗粒度是 20%,分辨不出更小的差别
    • 离线抖动接近零说明没在测模型,只在测自己的代码——它是回归测试不是模型评估

Comments