Skip to main content
PProfileMatch

Using LLMs via APIs: Latency, Cost Drivers and Choosing a Model

Generative AILesson 4 of 46 min read

What you'll learn

  • List the main drivers of LLM API cost and latency and the lever for each
  • Build a back-of-envelope cost and latency estimate before writing the feature
  • Add retries with backoff and jitter for transient failures
  • Compare candidate models on your own test set and choose on evidence

On this page

  1. What you pay for, and the lever for each
  2. Latency has two parts
  3. Failing gracefully
  4. Choosing between models on evidence
  5. Common mistakes and the symptom each one causes
  6. How this shows up in real projects and interviews

A team builds a contract-clause summariser, tests it on ten clauses, loves it, and launches. The first monthly bill is several times what anyone expected. Nothing malfunctioned. Every call re-sent a long set of instructions and three worked examples, and nobody had multiplied the per-call tokens by the number of calls. This lesson is the arithmetic you do before launch.

What you pay for, and the lever for each

Hosted models are typically billed by tokens, with input and output often priced differently, and output usually costing more per token. Prices and limits change and differ across providers, so the code below uses placeholders that you replace with the real figures from your provider's price sheet.

Cost driverWhy it growsLever
Instructions resent every callFixed prompt multiplied by request volumeShorten it; use any prompt-caching feature your provider offers
Few-shot examplesEach example is input tokens on every callKeep only examples that improve your test score
Retrieved or pasted documentsOften the largest part of the inputRetrieve fewer, better passages
Chat historyGrows every turnTrim or summarise
Output lengthBilled per token, and also the biggest latency factorAsk for concise output; cap maximum tokens
Retries and chainsEach retry or extra step is another callCache results; skip steps that code can do
Model sizeLarger models usually cost more per tokenUse the smallest model that passes your test set
cost_estimate.py
# Back-of-envelope cost and latency model for a contract-clause summariser.
# Every number below is a PLACEHOLDER. Replace with your provider's real
# price sheet and with latencies you measure yourself.
PRICE_PER_MILLION_INPUT = 1.00     # currency units per 1M input tokens (placeholder)
PRICE_PER_MILLION_OUTPUT = 4.00    # output is usually priced higher than input (placeholder)

def request_cost(input_tokens, output_tokens):
    return (input_tokens * PRICE_PER_MILLION_INPUT
            + output_tokens * PRICE_PER_MILLION_OUTPUT) / 1_000_000

def estimate_latency(output_tokens, time_to_first_token=0.8, tokens_per_second=60):
    """Latency is roughly: wait for the first token, then stream the rest."""
    return time_to_first_token + output_tokens / tokens_per_second

clauses_per_day = 5_000
instructions = 350        # fixed system prompt, resent on every call
clause_text = 600         # the clause itself
summary = 120             # generated summary

per_call = request_cost(instructions + clause_text, summary)
print(f"cost per call:  {per_call:.6f}")
print(f"cost per day:   {per_call * clauses_per_day:.2f}")
print(f"latency (est.): {estimate_latency(summary):.1f}s")

# What if we also paste 3 worked examples (900 tokens) into every prompt?
with_examples = request_cost(instructions + 900 + clause_text, summary)
print(f"with examples:  {with_examples * clauses_per_day:.2f} per day "
      f"({with_examples / per_call:.2f}x)")

# Output tokens dominate latency; capping them is the cheapest speed-up.
print(f"capped at 60 tokens: {estimate_latency(60):.1f}s")

Output

cost per call:  0.001430
cost per day:   7.15
latency (est.): 2.8s
with examples:  11.65 per day (1.63x)
capped at 60 tokens: 1.8s

The numbers are placeholders, so the useful part is the shape. Pasting three examples into every prompt raised the daily cost by more than half, which is invisible in a ten-clause demo and obvious at five thousand calls a day. Cutting output from 120 tokens to 60 shortened the estimated latency by a full second. These are the two cheapest optimisations in most projects: shorter prompts and shorter answers.

Latency has two parts

Response time is roughly the time to first token (network, queueing and processing your input) plus the generation time, which is the number of output tokens divided by the generation speed. Long outputs dominate. Practical consequences: stream tokens to the user so the interface feels responsive long before the answer is complete, put the most important information first, cap output length, run independent calls in parallel, and consider a smaller model for steps that do not need a large one. Measure real latency from your own region and at your own load rather than trusting a demo.

Failing gracefully

Network timeouts and rate-limit responses are routine on any hosted API. The standard answer is retries with exponential backoff and jitter, bounded in number, plus a timeout on every call and a fallback when retries run out. Retry only transient errors (timeouts, rate limits, temporary server failures), never malformed requests, which will fail the same way every time. Related ideas appear in rate limiting.

retries.py
import random
import time

class TransientError(Exception):
    """Stands in for a timeout or 'rate limited' response from any provider."""

def with_retries(fn, attempts=4, base_delay=0.5, sleep=time.sleep):
    for attempt in range(1, attempts + 1):
        try:
            return fn()
        except TransientError:
            if attempt == attempts:
                raise                                  # give up, let the caller decide
            delay = base_delay * 2 ** (attempt - 1)    # 0.5, 1.0, 2.0 ...
            delay += random.uniform(0, delay / 4)      # jitter so clients do not retry in lockstep
            print(f"attempt {attempt} failed, waiting {delay:.2f}s")
            sleep(delay)

calls = {"n": 0}
def flaky_model_call():
    calls["n"] += 1
    if calls["n"] < 3:
        raise TransientError("timeout")
    return "summary text"

random.seed(1)
print(with_retries(flaky_model_call, sleep=lambda s: None))   # no real waiting in the demo

Output

attempt 1 failed, waiting 0.52s
attempt 2 failed, waiting 1.21s
summary text

The demo passes a no-op sleep so it runs instantly; in production you would use the real one. If a request has side effects, such as sending an email, make it safe to repeat before you retry it.

Choosing between models on evidence

Public leaderboards and marketing claims measure other people's tasks. For your decision, build a test set from your real inputs (the method from the prompt testing lesson), run two or three candidate models with the same prompt and settings, and record for each: task accuracy, how often output fails validation, latency at the percentile you care about, and cost per successful task. The last metric matters because a cheaper model that needs two retries may cost more.

QuestionWhat to measureWarning sign
Is quality enough?Pass rate on your labelled cases, including edge casesGreat on easy cases, weak on the ones that matter
Is it fast enough?Time to first token and total time under realistic loadFine alone, slow under concurrency
Is it affordable?Cost per successful task at expected volumeSavings disappear once retries are counted
Is it reliable?Validation failure rate, timeouts, rate-limit hitsFrequent format drift
Is it acceptable to your data rules?Provider terms on data retention and regionUnclear handling of personal data

Common mistakes and the symptom each one causes

  • Estimating cost from a demo. Symptom: a surprise bill. Fix: multiply tokens per call by real volume before launch.
  • Ignoring output length. Symptom: slow responses and high cost from rambling answers. Fix: ask for concise output and cap max tokens.
  • Retrying everything. Symptom: duplicated side effects, or a retry storm that worsens a rate-limit problem. Fix: retry only transient errors, with backoff, jitter and a limit.
  • No timeout. Symptom: requests hang and tie up workers. Fix: set a timeout on every call.
  • Choosing a model from a leaderboard. Symptom: it underperforms on your data. Fix: evaluate on your own cases.
  • Hard-coding a model into many places. Symptom: switching or upgrading means a risky multi-file change. Fix: wrap calls behind one function and keep the model name in config.

How this shows up in real projects and interviews

Cost and latency estimates are part of any design discussion for an LLM feature, and a quick back-of-envelope calculation like the one above is a strong move in a system-design round. Expect questions such as "how would you cut cost by half without hurting quality?" (shorter prompts, caching, smaller model for easy cases, fewer calls) and "how do you choose a model?" (your own evaluation set, measured on quality, latency and cost per successful task). Caching of repeated requests is covered in the caching lesson. Be honest about what you cannot know in advance: real prices, limits and speeds change, so you build the measurement habit rather than memorise numbers.

Try it yourself

Adapt the cost script to a feature you might build. Replace the placeholder prices with the figures from your provider's price sheet, estimate the monthly cost at 1,000 and at 100,000 requests a day, and find the single change that saves the most.

Show a hint

Compare input and output cost separately. Often the largest saving is trimming a prompt that is resent on every call.

Key takeaways

  • Cost is tokens per call times calls, so resent instructions, examples and documents add up quickly.
  • Latency is time to first token plus output tokens divided by generation speed; output length dominates.
  • Retry only transient failures, with backoff, jitter, timeouts and a fallback.
  • Choose a model with your own labelled test set, measuring quality, latency and cost per successful task.
  • Keep model names and settings in one place so changing them is cheap.

Test yourself

A trick from this lesson's ride: Output length is the slow lane: write less, stream sooner. Four more tricks like it wait along the route, each followed by a question.

Ride this lesson

Frequently asked questions

What drives the cost of using an LLM API?

Mostly the number of input and output tokens across all your calls, with output often priced higher than input. Long fixed prompts, few-shot examples, pasted documents, chat history, retries and a larger model all push the bill up.

How can I reduce LLM latency?

Shorten the output, since generation time scales with output tokens, stream results to the user, run independent calls in parallel, use a smaller model where quality allows, and cache repeated requests. Measure from your own environment because network and load matter.

How do I choose between LLMs for my application?

Build a labelled test set from your real inputs, run each candidate with the same prompt, and compare accuracy, latency, reliability and cost per successful task. Check the provider's data-handling terms too, then pick the cheapest model that clears your quality bar.