DEV Community

Quinn Li
Quinn Li

Posted on

Divide Your Free Tokens by the Acceptance Rate

Divide Your Free Tokens by the Acceptance Rate

A free token is not worth a token. It is worth whatever fraction of your calls your checker actually keeps. If six of ten responses fail your own validation, your real budget is not the number on the free-tier page — it is that number times your acceptance rate, and your real latency is the tail of three attempts instead of the mean of one.

So do the division first. Before you promise a quota, a deadline, or a demo, measure two things on your own tasks: how often the free capacity produces output you can keep, and how long the rejects cost you in wall clock. Everything else is arithmetic.

Two multipliers hide behind the word free

Money is the easy part. Free capacity has a price of zero, so it always wins a pure cost comparison, which is exactly why cost comparisons mislead you here. The currency you spend is attempts and seconds.

Attempts burn two budgets at once. They consume the allowance that was supposed to serve other work, and they consume the concurrency slots that make the queue move. Ten teams doing this independently turn a comfortable allowance into a busy afternoon.

Seconds are worse, because they are not symmetric. A single call that takes 900 ms is not thirty calls that take 900 ms when three of them retry — near saturation the retried calls rejoin the same line, so your tail grows faster than your average. The mean stays polite while the p95 quietly breaks your deadline.

A harness small enough to trust

The instrument you need is boring: a fixed task set, a deterministic checker, and a loop that spends attempts until the checker says yes or the attempt budget runs out. No model in the grading step — otherwise you are measuring vibes.

#!/usr/bin/env python3
"""accept_rate.py — tokens and wall clock per *accepted* output.

Run: python3 accept_rate.py --tasks tasks.jsonl --attempts 3 --out results.jsonl
Wire call_model() to the client you are evaluating.
"""
import argparse, json, statistics, time


def call_model(prompt: str) -> dict:
    """Adapter. Replace this body with your client.

    Must return: text, tokens_in, tokens_out
    """
    raise NotImplementedError("wire your client here")


def accepts(task: dict, text: str) -> bool:
    """Deterministic checker. Cheap, strict, and the same for every run."""
    for needle in task.get("must_include", []):
        if needle not in text:
            return False
    if task.get("json"):
        try:
            json.loads(text)
        except json.JSONDecodeError:
            return False
    return len(text.strip()) >= task.get("min_chars", 1)


def run_task(task: dict, attempts: int) -> dict:
    spent = 0
    wall = 0.0
    ok = False
    used = 0
    for used in range(1, attempts + 1):
        t0 = time.monotonic()
        r = call_model(task["prompt"])
        wall += time.monotonic() - t0
        spent += r["tokens_in"] + r["tokens_out"]
        if accepts(task, r["text"]):
            ok = True
            break
    return {"id": task["id"], "ok": ok, "attempts": used,
            "tokens": spent, "wall_s": round(wall, 3)}


def summarize(rows: list) -> dict:
    kept = [r for r in rows if r["ok"]]
    walls = sorted(r["wall_s"] for r in kept)
    p95 = walls[min(len(walls) - 1, int(0.95 * len(walls)))] if walls else None
    spent = sum(r["tokens"] for r in rows)
    return {
        "n": len(rows),
        "acceptance_rate": round(len(kept) / len(rows), 3),
        "tokens_spent": spent,
        "tokens_per_accepted": round(spent / max(1, len(kept)), 1),
        "wall_p50_accepted_s": round(statistics.median(walls), 2) if walls else None,
        "wall_p95_accepted_s": p95,
    }
Enter fullscreen mode Exit fullscreen mode

Sixty tasks is enough to see the shape and short enough to rerun after every prompt change. Keep the task file in git next to the checker. If you cannot review a diff of your eval set, you do not have one.

Read the output as a haircut, not a score

Three numbers matter and the rest are decoration. tokens_per_accepted is your true per-unit price. acceptance_rate is how much of the advertised allowance survives contact with your requirements. wall_p95_accepted_s is the number your users experience whenever the queue is warm.

Then apply the haircut to whatever allowance you were handed, including the operator-stated free allowance of 10 million tokens for the project described below:

allowance = 10_000_000     # stated by the operator; verify the current terms
per_accept = 1_850         # illustrative, from your own run — never from a blog
print(allowance // per_accept)   # ~5,405 accepted outputs before overhead
Enter fullscreen mode Exit fullscreen mode

The arithmetic is the point, not the constant. Substitute your own per_accept and watch the usable number move by a third. That gap is why a gross allowance is a marketing figure and a net allowance is an engineering one.

When free capacity is the wrong bet

Free loses in a small number of recognizable situations, and it loses badly enough that no amount of allowance fixes it.

Situation Why free capacity loses Cheaper alternative
Deadline shorter than wall_p95_accepted_s Retries re-enter the same queue Pay for the shorter path, or shrink the task
Reject costs a human review Attempts spend salary, not tokens Deterministic checker first, then pay
Output must be reproducible for audit Softmax sampling is not a ledger Pin seeds or pin a paid endpoint
Sustained throughput above the queue Tail grows faster than the mean Committed capacity
The task is the model quality itself You are evaluating, not producing Paid endpoint as the reference arm

The last row deserves attention. Free capacity is excellent for evaluation and bad for promises. Run your harness on it, find the acceptance rate, and only then decide what production runs on.

Where MonkeyCode sits in this workflow

MonkeyCode is described by its operator as an open-source project that offers free model access — including the 10-million-token allowance referred to above — and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach.

In this workflow, both fit as instruments rather than as guarantees. The free model access is the arm you measure first, because zero marginal cost makes it the right place to discover your acceptance rate without watching a meter. The free server option is a reasonable host for the harness itself: clone your repo, create a virtualenv, and run the loop, since the script keeps no state that matters and writes a single JSONL file you can copy off the box.

python3 -m venv .venv && . .venv/bin/activate
git clone <your-harness-repo> && cd harness
python3 accept_rate.py --tasks tasks.jsonl --attempts 3 --out results.jsonl
scp results.jsonl you@laptop:~/runs/$(date +%F)/
Enter fullscreen mode Exit fullscreen mode

Two things to check yourself rather than trust. Read the current terms of the free allowance and the free server before you build a budget on them — availability, quotas, and hardware are operator decisions that change. And if the request, queueing, and retry behavior live in code you can read, read it instead of inferring it from your p95. Inference is what you do when the source is closed.

Limitations, and who should not do this

This method measures one model family against one task set on one day. Provider behavior drifts, so a week-old per_accept is a historical document, not a forecast. The checker is also the weak link: a loose checker inflates the acceptance rate and quietly converts a cost study into a wish.

Skip this entirely if you have a hard latency SLO you cannot renegotiate, if every rejected output requires a human to adjudicate it, or if your workload needs bit-reproducible outputs for audit. In those cases free capacity is not a cheaper option — it is a different product with different terms.

Everyone else should run the harness once before writing the plan. Sixty tasks, one afternoon, and a number you can defend. The allowance on the pricing page is a ceiling; the number your checker returns is a budget, and only one of those survives contact with a deadline.

Top comments (0)