Back to the map
ProductionAdvanced5h

Cost & latency engineering

Same quality, an order of magnitude cheaper.

Every model call has a price, charged per token sent and received, and a delay before the answer arrives. This topic is the work of lowering both without the answers getting worse: sending fewer tokens, not paying twice for the part of the prompt that never changes, sending the easy requests to a smaller model, and starting to show the answer while it is still being written.

A feature that costs four pence a request is fine across a hundred test runs and unaffordable across ten million. Prototypes get cancelled at this step more often than they fail on quality. Latency has its own arithmetic: past a few seconds people stop waiting, and an agent that makes six calls in sequence is six times as far past it.

It rests on tokenization, which is the unit you are billed in, and on prompt caching, usually the single largest saving available. It needs observability first, because you cannot cut what you have not measured. In AI system design it is the standard follow-up question once the architecture is on the board.

Measure cost per completed task, not per call — a cheap call that has to be retried three times is not cheap.

In more detailThe levers, roughly by return: prompt caching on stable prefixes, routing easy requests to smaller models, batching, trimming context, and only then changing model. Cost and latency often pull in opposite directions, and streaming changes perceived latency without changing spend at all. Measure cost per completed task, not per request.

Section 1 of 2, 2h

Find out where the money goes

By the end: You can say exactly which part of your system is expensive before changing anything.

  • Cost per completed task, not per call
  • Instrumenting every call
  • Finding the expensive path
  • Latency and where it actually comes from

Almost every attempt to make an AI feature cheaper starts by optimising the wrong thing. This section is about finding out first: instrumenting every call, and counting cost per completed task rather than per request, because a cheap call that has to be retried three times is not cheap. The same measurement answers the latency question, and the answer is usually somewhere nobody suspected.

The right unit#

Cost per API call is the wrong number. A cheaper model that needs three attempts and a retry is not cheaper. Measure cost per completed task, meaning the whole job done correctly, including retries and failed runs.

Model A:  $0.02 per call, succeeds 60% of the time
Model B:  $0.05 per call, succeeds 95% of the time

Cost per success:
  A:  0.02 / 0.60 = $0.033
  B:  0.05 / 0.95 = $0.053

A is cheaper, but only if a failure is free. If a failure costs a
retry, a support ticket or a lost customer, the maths changes.

Instrument everything#

Record tokens and cost for every call from the first day of a project. Adding it later means you cannot answer the first question anyone asks, which is where the money is going.

log.info("llm_call", extra={
    "task_id": task_id,
    "step": "extract",
    "model": model,
    "input_tokens": usage.input_tokens,
    "output_tokens": usage.output_tokens,
    "cached_tokens": usage.cache_read_input_tokens,
    "cost_usd": cost,
    "latency_ms": elapsed,
})
task_id is what lets you sum a whole job later. Without it you only have a pile of calls.

Where the cost usually is#

  • A long system prompt sent on every call, uncached.
  • Conversation history resent every turn, growing quadratically.
  • Oversized tool results that stay in context for the rest of the run.
  • A frontier model used for work a small one would do.
  • Retries on failures that could have been avoided.

Latency is not the same problem#

Latency splits in two. Time to first token is the prefill pass over your prompt: it is compute-bound and it grows faster than the prompt does, because attention compares every token with every other one, so doubling a prompt more than doubles the wait before anything appears. After that, each output token costs roughly the same fixed amount, so the rest of the time is proportional to how much the model writes. A long prompt is not cheap in time; it is the most reliable thing to cut when the first token is slow.

Streaming changes perception, not cost

Streaming shows tokens as they arrive, so the wait feels much shorter. Total time and total cost are unchanged. It is a real improvement to the experience and does nothing for the bill.

Where people go wrong

Optimising before measuring. Teams routinely spend a week shortening prompts when the actual cost was one oversized tool result being resent every turn.

Build this: Add cost logging to an existing feature, run fifty real tasks, and produce a breakdown by step. Name the single most expensive step before you change anything.

Open this section on its own page
Section 2 of 2, 3h

The levers, in order

By the end: You can cut cost substantially without trading away quality, and you know which changes do trade.

  • Prompt caching, the free win
  • Trimming what you send
  • Model routing
  • Batching, and when quality has to give

Now that you know where the money actually goes, this is the list of things to do about it, deliberately ordered. The early entries are free: they cut the bill without touching quality, and they are worth exhausting before you consider anything that trades. The later ones — sending less, routing easy work to a cheaper model, batching — do trade, and this section is about knowing which you are choosing.

Free wins first#

Work in order. The early levers cost nothing in quality, and it is worth exhausting them before making any trade.

1. Prompt caching#

Providers can cache the processed form of a stable prompt prefix, and cached input is far cheaper than fresh input. If you have a long system prompt, tool definitions or few-shot examples, this is usually the single largest saving available.

Stable, cacheable, put first:
  system prompt
  tool definitions
  few-shot examples

Volatile, put last:
  conversation history
  the user's question
  anything with a timestamp

One byte breaks it

Caching is prefix-matched from the start. A timestamp, a request id, or a randomly ordered JSON key anywhere early invalidates everything after it. Check cached token counts in your usage data. If they are always zero, something in your prefix is moving.

2. Send less#

  • Trim tool results before they enter the context.
  • Summarise or drop old conversation turns.
  • Retrieve five relevant chunks instead of fifty mediocre ones.
  • Delete instructions the model already follows. Long prompts accumulate lines nobody has tested.

3. Route by difficulty#

Most workloads are mostly easy. Send the easy cases to a small model and escalate only when needed. The saving is large because the split is usually lopsided.

def answer(question):
    result = small_model(question)

    if result.confidence < 0.7 or result.needs_escalation:
        return large_model(question)

    return result
Route on a signal you can measure. Guessing which questions are hard is itself a hard problem.

Verify with your eval set that the routed system still passes. Routing is only free if quality holds, and the small model will fail on a category you did not anticipate.

4. Batch#

Most providers offer a batch mode at roughly half price for work that can wait hours. Anything offline, such as nightly enrichment, backfills or eval runs, should use it.

Then, and only then, trade quality#

  • Lower the reasoning effort or thinking budget where the model supports it.
  • Shorten the maximum output length, having first asked for brevity in the prompt.
  • Move to a smaller model across the board, with eval numbers to justify it.

Where people go wrong

Switching to a cheaper model as the first move. It is the change most likely to hurt quality and is usually unnecessary, because caching and trimming often get you most of the way with no trade at all.

Build this: Take a feature and cut its cost in half using only the free levers, then prove with your eval set that quality is unchanged. Report the before and after for cost, latency and pass rate.

Open this section on its own page

Where you use this

A topic is easier to care about when you can see what it is for. These projects lean on it at a named stage.

An eval harness that fails the build
  • Stage 7, Fail the pull request: Knowing what a full run costs before it runs on every push, and caching what repeats.
Fine-tune a small model
  • Stage 6, Decide honestly whether it was worth it: Costing each option properly, including the tokens the strong prompt spends on every single call.
Serve a model, then make it cheaper
  • Stage 2, Measure it honestly: Finding where the time and the money actually go, before touching anything.
  • Stage 5, Caching: Where caching sits in the order of levers, and what it is worth relative to the others.

Now actually do it

Reading a topic is not learning it. These are the hours that count, and each one tells you when you are finished.

1Halve the cost with free levers only

5h

Measure first and name the most expensive step. Then apply caching, trimming and routing without touching model quality. Prove with your eval that quality held.

Done when: Cost halved, pass rate unchanged, and you have the before and after numbers for both.

5 hours of practice

Words used here

3 terms this topic teaches, each with a one-line definition if one of them stopped you.

Latency
How long one request takes end to end. What a user experiences, as distinct from how much the system gets through overall.
Time to first token
How long from sending a request until the first token comes back, dominated by prefill and therefore by prompt length. The number that decides whether a streaming interface feels responsive.
Time per output token
The gap between successive tokens once generation is under way. Combined with time to first token and the length of the answer, it fully predicts how long a response takes.

Interview questions

6 questions, grouped by the section that teaches the answer. An answer you have read is not an answer you can give, so use Test yourself and say it out loud first.

Find out where the money goes

How do you measure the cost of an AI feature?

Cost per completed task rather than per call, including retries and failed runs. Tag every call with a task id so a whole job can be summed, and record tokens, cost and latency for each.

What drives latency in an LLM call?

Two different things. Time to first token is prefill over the prompt, which is compute-bound and superlinear in prompt length because attention is quadratic, so a long prompt delays the first token more than proportionally. Everything after that is decode, roughly a fixed cost per output token, so it scales with how long the answer is. Shorten the prompt to fix a slow start; cap output tokens to fix a slow finish.

Does streaming make a response faster?

It makes it feel faster by showing tokens as they arrive. Total time to the complete answer and total cost are unchanged.

The levers, in order

Your feature costs $0.40 per conversation and needs to be $0.10. What do you do?

Measure first and find which step dominates. Then in order: prompt caching on the stable prefix, trim what is sent including history and tool results, route easy cases to a smaller model, batch anything that can wait. Only after those do you trade quality, and with eval numbers to show the cost of the trade.

Then they push further

Most of the cost turns out to be a 6,000-token system prompt sent every turn. Now what?

Prompt caching first, since that prefix is identical every time. Then shrink it: move rarely needed material into tools or retrieval so it is only loaded when relevant.

Would you switch to a cheaper model?

For some requests, possibly. Route easy requests to a smaller model and keep the larger one for hard ones, but only after the eval shows the smaller model is good enough on those requests. Otherwise you trade quality for cost without knowing it.

What gets marked up. Marked up for measuring where the tokens go before changing anything, and for checking quality with evals before downgrading the model.

How does prompt caching work and how is it broken?

The provider caches the processed form of a stable prompt prefix and charges much less to reuse it. It is prefix-matched, so any change early in the prompt invalidates the rest. A timestamp or request id in the system prompt silently disables it, and the tell is cached token counts sitting at zero.

Then they push further

How would you check that caching works in production?

Read the cached token counts the API returns with each response and track the hit rate over time. A sudden drop usually means someone put something variable near the top of the prompt.

What gets marked up. Marked up for knowing the cache matches an exact prefix, so anything variable, such as a timestamp, must come after the stable parts.

When is model routing a bad idea?

When you cannot measure which requests are hard, or when a failure is expensive. Routing adds a decision that can be wrong, so it needs an eval showing quality holds and a fallback path when the small model is not confident.

These questions on their own page, one link per question

Resources

Free to watch and read, picked for this topic.

Stop 34 of 50 on the AI Engineer route

0 of 50 topics done · 0h behind you, 603h to go

Next on this routePrompt injection & guardrailsUntrusted text reaching a model that holds permissions.6h

What you have marked done is stored in this browser only. It does not reach a server, and clearing site data will clear it.