Skip to content

Evaluation and modelsGuide

A/B testing LLM models in production: splits, metrics, sample size

Swapping a model because it topped a leaderboard is how quality regressions ship. Testing it on a slice of real traffic is how they don't. A practical guide to splitting LLM traffic, choosing what to measure, and knowing when you have enough data.

By INFRO Editorial

Published

6 min read

A single amber path on a dark ground forking into two glass branches of slightly different brightness, representing traffic split between two models

In short

To A/B test an LLM in production, pass an offline evaluation first, then assign users (not requests) to arms with a stable hash so each user sees one model, route each arm to its model, and compare task success, user signals, latency, error rate and cost per successful task. For a success rate near 80%, detecting a two-point difference needs roughly 6,400 users per arm. Ramp from 1% upward, keep guardrail metrics that stop the test automatically, and wait out cache warm-up and novelty before deciding.

Key takeaways

  • Run an offline evaluation on your own cases first; an online test is for confirming, not for discovering basic failures.
  • Assign users, not requests, with a stable hash, so each person sees one model and their signals are comparable.
  • Decide with task success, user signals, p95 latency, error rate and cost per successful task, all fixed before the test starts.
  • A two-point change in an 80% success rate needs roughly 6,400 users per arm; small effects need large tests.
  • Ramp 1%, 5%, 25%, 50%, with guardrails that stop the test automatically, and discount the first days for cache warm-up and novelty.

How do you A/B test LLM models in production? Pass an offline evaluation first. Then split users — not individual requests — between the current model and the candidate with a stable hash, send each arm to its model, and compare a short list of metrics fixed in advance: task success, user signals, p95 latency, error rate and cost per successful task. Ramp the candidate up in steps with automatic stop conditions, and run until each arm has enough users to detect the change you care about.

Leaderboards tell you how a model does on someone else's tasks. A production test tells you how it does on yours, with your prompts, your users and your tools.

Before any traffic: the offline gate

OpenAI's guidance is to adopt eval-driven development, evaluating early and often and growing the eval set from production over time1. Anthropic's advice for building those tests: prefer more questions with automated grading over fewer hand-graded ones, structure questions so they can be graded automatically, and use a different model to grade than the one that generated the output2.

The gate is simple: the candidate must match or beat the incumbent on your offline set, including your known hard cases, before it sees a user. An online test is for confirming a result, not for discovering that the new model ignores your JSON schema. Evaluating LLMs on real workloads covers building the set.

One adjustment before the comparison: prompts are usually tuned for the incumbent. Give the candidate an honest prompt pass first, or you are testing your prompt's fit rather than the model.

Assignment: users, not requests

Split by user (or account, or conversation) in almost every product. A person who sees one model on one message and another on the next gives you signals you cannot attribute, and multi-turn context makes the arms leak into each other. Requests are a fine unit only for independent, stateless calls such as classification.

Make the assignment deterministic, so it survives restarts and needs no storage:

assign.py
import hashlib

def arm(user_id: str, experiment: str, candidate_share: float) -> str:
    """Stable assignment: the same user always lands in the same arm."""
    digest = hashlib.sha256(f"{experiment}:{user_id}".encode()).digest()
    bucket = int.from_bytes(digest[:8], "big") / 2**64   # uniform in [0, 1)
    return "candidate" if bucket < candidate_share else "control"

Including the experiment name in the hash means each experiment gets an independent split, so the same users are not always the ones who receive every new model.

The metrics that decide

Fix these before the test, with the direction and size of change that would make you ship:

MetricWhat it catchesNotes
Task successThe model does the jobYour own definition: resolved ticket, accepted suggestion, valid output
User signalsQuality as users feel itThumbs, regenerations, edits, abandonment, escalation to a human
Graded qualityQuality you can scoreA sample of answers scored against a rubric by a separate model, calibrated against human review
p95 latencyThe slow tailTime to first token for streamed features
Error rateReliabilityProvider errors, schema failures, refusals
Cost per successful taskEconomicsCost per request divided by success rate

OpenAI recommends calibrating automated scoring against human feedback so it stays trustworthy1; a model-graded quality score that nobody has checked against people is a number, not a measure.

Cost per successful task is the one teams forget. A candidate with a lower rate per token that needs more retries, longer outputs or more escalations can cost more per finished job. Cost per successful task shows the arithmetic.

How many users you need

For a success-rate metric, a standard approximation for 80% power at 5% significance is:

users per arm
n ≈ 16 × p × (1 − p) / δ²
# p = baseline success rate, δ = the absolute difference you want to detect

Two consequences. Small products cannot detect small differences online, so lean harder on offline evaluation and graded samples. And stopping the test the first time the dashboard looks good inflates false positives; decide the sample size first and look at the end.

Ramp, with automatic stops

  1. 1% for a day. Catches integration breakage: errors, timeouts, schema failures.
  2. 5%. First read on latency and cost under real load.
  3. 25%, then 50%. Enough traffic to read the decision metrics.

At every step, guardrail metrics stop the test automatically: error rate above a threshold, p95 latency above a ceiling, a cost per request far above the estimate. A test that needs a human to notice a regression will run all weekend.

Traps specific to LLM tests

  • Cold caches. A new model starts with no prompt cache, so its first days cost more and can be slower than steady state. Compare cost from the point the cache hit rate has settled.
  • Tokenizers. Different models count the same prompt differently, so compare cost in dollars, never tokens.
  • Novelty. Users react to a different writing style for a few days. Run at least one full week and check whether the effect holds in the second week.
  • Shared limits. Both arms may share provider rate limits; a busy arm can throttle the other. Watch 429s per arm.
  • Shadow mode is not free. Running the candidate on every request in the background without showing it doubles model cost for the test's duration. Use it on a sample.

Reading the result

When the test reaches its sample size, read it in a fixed order, so the result does not depend on which chart someone opened first:

  1. Guardrails. Did error rate, refusals or p95 latency cross a limit at any point? If so, the candidate fails regardless of quality.
  2. Primary metric. Is the difference in task success outside the noise? Report the confidence interval, not just the point estimate.
  3. Cost per successful task. Compute it per arm from real spend and real success. A small quality gain at a large cost increase is a business decision, not a statistical one.
  4. Segments. Check the result by plan, language and request size. A model that wins on average can lose badly for one customer segment, and that segment may be your largest account.
  5. Durability. Compare week one with week two. An effect that fades is usually novelty.

Write the decision down with the numbers that made it, then make the winning arm the default and remove the experiment's code path. A finished experiment left in the code is a routing rule nobody remembers.

Splitting traffic with INFRO

INFRO routes provide two ways to split traffic. A weighted route picks the first target at random in proportion to its weights and still fails over to the others, which suits a canary of independent requests. For a per-user experiment, keep the assignment in your app and send the arm in the request's metadata (for example "arm": "candidate"); a route rule that matches metadata.arm sends those requests to the candidate's targets and everything else to the default. Each request's record names the route and the connection that served it, with latency, status and an estimated provider cost, so cost and errors per arm come from the request log. Eval monitors can score a share of served chat answers against a rubric on a decision model you connect.

Summary

Gate on an offline evaluation, split by user with a stable hash, decide on metrics chosen in advance — cost per successful task among them — size the test before you start, ramp with automatic stops, and wait out cache warm-up and novelty before you call it.

Questions readers ask

How do you A/B test two LLMs?
Evaluate both offline on your own cases, then split real users between them with a stable hash of the user id, route each arm to its model, and compare pre-chosen metrics such as task success, user feedback, p95 latency, error rate and cost per successful task until you have enough users per arm to detect the difference you care about.
Should LLM A/B tests split by request or by user?
By user, in most products. A user who sees two different models in one session gives you mixed signals, and conversation context may be shared between turns. Split by request only for stateless, independent calls such as classification.
How long should an LLM A/B test run?
Until each arm has the sample size your minimum detectable effect requires, and at least one full weekly cycle so weekday and weekend traffic are both represented. Discount the first days if the new model starts with cold prompt caches or users react to novelty.

From the team that builds it

Where this shows up in INFRO

INFRO is a control plane in front of the provider accounts you already hold. The capabilities below are the parts of it this article touches, each with the documentation that defines its behaviour.

  • Health-aware routing

    Available with a plan

    Named routes use priority or weighted targets over your own connections, with health checks, retries and failover.

    Read the documentation for Health-aware routing
  • Routing rules

    Available with a plan

    If-rules on a route send matching requests to their own targets, by metadata, feature, end user, key, project, prompt size, streaming, images, tools or time of day. Failure rules decide per failure kind whether to retry, move on or stop.

    Read the documentation for Routing rules
  • Eval monitors

    Available with a plan

    Score a share of served chat answers against your rubric in the background with Jev or any other decision model you connect, recorded per request and summarized per served model.

    Read the documentation for Eval monitors
  • Usage and cost per request

    Available with a plan

    Every request records its model, latency, token or unit counts, and estimated provider cost, queryable by project and key.

    Read the documentation for Usage and cost per request

INFRO accounts are self-serve. Provider usage is billed to you by your providers; INFRO charges a software subscription and takes 0% of model spend. See plans.

Sources

Numbered in order of first citation. Each was opened and checked against the claim on the date shown.

  1. 1.Evaluation best practices (opens in a new tab) — OpenAI. Accessed Oct 20, 2026.
  2. 2.Define success criteria and build evaluations (opens in a new tab) — Anthropic. Accessed Oct 20, 2026.

About this article

Evaluation recommendations cite OpenAI's evaluation best practices and Anthropic's guide to building tests, opened on 2026-10-20. The sample-size rule is the standard approximation for comparing two proportions at 80% power and 5% significance. Example figures are invented and labelled. INFRO's routing behaviour is described from its documentation. Written by the team that builds INFRO.

Last checked against its sources on . The next review is due by . No separate named reviewer is recorded for this article.

How articles are written, sourced and corrected is set out in the editorial standards.