Skip to content

Routing and reliabilityAnalysis

Should you hedge LLM requests? Retry budgets and tail latency

Slow p99 on AI traffic tempts teams to send a second copy of the request. Whether that helps depends on where the tail comes from and on what a duplicate costs in tokens, rate-limit headroom and tool side effects. This post works through the trade-off and a retry budget that keeps both under control.

By INFRO Editorial

Published

9 min read

Luminous routing paths connecting one application node to many AI model endpoints

In short

Hedge only when the tail is a stall before the first token, you have rate-limit headroom, and the request has no side effects. Put a retry budget over every duplicate, and honour Retry-After as a minimum delay. For long outputs and 429s, hedging adds load without removing the cause.

Key takeaways

  • Hedging helps when the slow tail is a stall before the first token; it does nothing for requests that are slow because the answer is long.
  • Every hedge is a second billable request that also counts against your provider's request and token rate limits, so cap duplicates with a retry budget rather than a per-request attempt count.
  • Treat Retry-After as a minimum wait, add jitter, and stop when it exceeds your remaining deadline; never hedge in response to a 429.
  • Hedge the model call, never the tool execution: run the winner's tool calls once, with idempotency keys, and keep requests with side-effecting server-side tools out of hedging.
  • Decide with data: record attempt number, hedge winner and wasted tokens per request, then compare the p99 gain with the duplicate token spend.

What hedging is, and what it is not

A plain retry waits for an attempt to fail or time out, then sends another. A hedged request sends a second copy while the first is still running, keeps whichever answers first, and cancels the other. Failover is a third thing: it moves to another target after a failure, so only one attempt is live at a time.

The distinction matters for LLM traffic because the second copy is billable work. A duplicate chat completion consumes input tokens, starts generating output tokens, and counts against the same request-per-minute and token-per-minute limits as the original. Providers apply those limits per organization and per project, and a limit trips on whichever of requests or tokens runs out first.1 A hedge is therefore a deliberate trade: extra spend and extra rate-limit pressure, in exchange for a shorter tail.

Whether that trade pays depends on one question: why is the tail slow?

Find out why p99 is slow before you hedge

Hedging only helps when the slow request is slow by bad luck, so that a fresh copy would probably be fast. For LLM calls, the tail has several causes, and only some of them are like that.

Cause of the slow tailWould a duplicate help?Better lever
Stall before the first token (queueing, a cold or overloaded replica, a slow connection)Often yesHedge on time to first token
Long output (decode time scales with tokens generated)No, the copy is just as longLower max_tokens, shorter answers, a faster tier or model
Long prompt (prefill time grows with input size)NoTrim context, prompt caching
429 from your own rate limitNo, it adds load to a limit you already hitBack off, spread across keys, raise limits
Provider-wide incidentNo, both copies land on the same problemFailover to another target
Tool-heavy agent loop (many sequential calls)Not at the call levelBound the loop, cut steps

Split your latency histogram by time to first token and by total duration before you decide. If the p99 mass is in total duration for long answers, a hedge will double your cost and leave the p99 where it was. Our time to first token post covers how to measure the first-token component and set timeouts around it.

The cost side: duplicate token spend

Hedge cost has three parts: the duplicate's input tokens, whatever output it generated before you cancelled it, and any server-side tool fees it triggered.

Two cautions. First, do not assume that cancelling the HTTP connection stops generation or billing at your provider; I could not confirm that behaviour from the pages I read, so check your provider's documentation and reconcile against the usage your provider reports. Second, the hedge rate is not fixed. During a provider slowdown, many more requests cross the threshold, so the duplicate rate rises exactly when the provider is struggling. That is how a hedge becomes a retry storm, and it is why the budget below is not optional.

Set a retry budget, not an attempt count

"Retry up to three times" sounds safe per request. Across the fleet it is not: if the provider is fully down, every request makes four attempts and your load on it quadruples. A retry budget caps retries and hedges as a fraction of original traffic instead.

The mechanism is a token bucket. Every original request deposits a small amount (say 0.1 of a token); every retry or hedge withdraws one whole token; when the bucket is empty, you do not retry or hedge. With a ratio of 0.1, the worst case is about 10% extra load rather than a multiple of it.

Rules that keep the budget honest:

  • One budget for retries and hedges together. Otherwise a retry loop and a hedge both spend your headroom without knowing about each other.
  • One layer retries. Official OpenAI SDKs automatically retry eligible 429 and 503 responses, subject to their settings.1 If your SDK, your service wrapper and your gateway each retry three times, the multiplication is nine or twenty-seven. Disable or bound all but one layer.
  • A deadline for the whole operation, not per attempt. OpenAI's guidance notes that a per-attempt timeout is not necessarily a deadline for the entire operation.1 Hand every attempt the remaining time, and stop when it is gone.
  • Scope budgets per route or model, so one failing model cannot spend the headroom of healthy ones.

The code below combines the budget with a hedge that fires only on time to first token. The start coroutine must resolve when the first token arrives, not when the answer finishes.

hedge.py
import asyncio, random

class RetryBudget:
    def __init__(self, ratio=0.1, cap=50.0):
        self.ratio, self.cap, self.tokens = ratio, cap, cap

    def on_request(self):
        self.tokens = min(self.cap, self.tokens + self.ratio)

    def try_spend(self):
        if self.tokens >= 1:
            self.tokens -= 1
            return True
        return False

async def hedged_first_token(start, budget, hedge_after, deadline_s):
    """start(attempt) resolves once the first token has arrived."""
    budget.on_request()
    loop = asyncio.get_running_loop()
    end = loop.time() + deadline_s
    tasks = [asyncio.create_task(start(1))]
    hedged = False
    try:
        while tasks:
            timeout = end - loop.time()
            if not hedged:
                timeout = min(timeout, hedge_after)
            if timeout <= 0:
                raise TimeoutError("operation deadline reached")
            done, _ = await asyncio.wait(
                tasks, timeout=timeout, return_when=asyncio.FIRST_COMPLETED
            )
            for t in done:
                tasks.remove(t)
                if t.exception() is None:
                    return t.result()      # winner; the rest are cancelled below
            if not done and not hedged:
                hedged = True
                if budget.try_spend():
                    tasks.append(asyncio.create_task(start(2)))
        raise RuntimeError("all attempts failed")
    finally:
        for t in tasks:
            t.cancel()

Honour Retry-After, and never hedge a 429

A 429 is the provider telling you that you are over a limit. A duplicate request is the wrong response. OpenAI documents Retry-After as the minimum number of seconds to wait before retrying a temporary rate-limit error, when present, and says to treat it as a minimum and add a small random delay so clients do not retry in step.1 The header itself is defined in HTTP semantics, in RFC 9110's section on response context fields.2

Several details matter in practice:

  • It also appears on 503. OpenAI documents that Retry-After may be present on 429s from a temporary rate limit and on 503s from temporary model overload.1
  • Not every 429 is retryable. The same page says the header does not mean quota, billing or other errors that need user action can be fixed by retrying, and a hard spend limit also returns a 429.1 Branch on the error code, not on the status alone.
  • slow_down is about ramp rate. A 429 with that code can occur even within your request and token limits, because traffic rose too quickly; as a rule of thumb, past 1 million input tokens per minute, OpenAI suggests raising traffic by no more than 50% every 15 minutes.1 Turning hedging on across a whole fleet at once is exactly such a ramp.
  • If the delay is longer than you can afford, stop. OpenAI's guidance is to defer the request rather than retry sooner when a server delay exceeds the configured maximum.1 Compare Retry-After with your remaining deadline and fail fast or move to another target.
delay.py
import random

def retry_delay(attempt, retry_after, remaining, base=0.5, cap=20.0):
    if retry_after is not None:
        delay = retry_after + random.uniform(0, 0.25)  # never shorter than the server asked
    else:
        delay = random.uniform(0, min(cap, base * 2 ** attempt))  # full jitter
    return delay if delay < remaining else None  # None: give up or fail over

For the broader picture of limits and headers by provider, see provider rate limits compared, and for why throttling should not trip a breaker, circuit breakers for LLM providers.

Keep non-idempotent tool calls safe

HTTP semantics classify methods by whether repeating them has the same intended effect; the definitions are in RFC 9110's sections on idempotent methods and on POST.2 A chat completion is a POST. Calling the model twice with the same prompt has no side effect beyond cost and a different answer, so hedging the model call is acceptable. Executing what the model asks for is where duplicates hurt.

Three rules:

  1. Hedge the model call, never the tool execution. The tool runner is a separate step. It acts on exactly one response, the winner's, and never on both.
  2. Give every side-effecting tool an idempotency key derived from stable identifiers, such as run id plus step index plus tool-call id, so a retried execution after a timeout cannot repeat the effect. The key lives at your tool or the downstream API; the model provider does not provide it for you.
  3. Exclude requests with server-side side effects from hedging. If the provider runs tools itself (search, fetch, code sandboxes), a duplicate runs and bills them twice. See what server-side tools cost an agent.

Streaming adds one more rule. OpenAI's guidance is that HTTP error responses apply before the stream starts, that an error after streaming begins can arrive as a stream event, and that you should not automatically replay a request after consuming output.1 So the hedge window ends at the first token. After that, you are in recovery territory, covered in streaming LLM failure modes.

Where a gateway fits, and what to measure

The pages I read for INFRO describe two relevant behaviours. Connection failover tries the same model on another of your own connections, and a route tries its targets in priority or weighted order, both only before the first token.3 Rules on a route can match on fields such as stream and has_tools, and failure rules decide per failure kind whether to retry, move on or stop.4 That gives you a place to keep retry policy out of application code, and to treat tool-carrying requests differently. Those pages do not describe hedged duplicate requests, so if you hedge, build it in a layer you own and keep it under the same budget. Per-request latency, estimated cost and traces help judge the result, and spans can be exported to your own collector. Note that OpenTelemetry's GenAI conventions have moved to a separate repository, so pin the version you instrument against.5

Whatever layer you use, record these per request: attempt number, whether a hedge fired, which attempt won, time to first token for each, and tokens spent by the loser. Then compare the p99 gain with the duplicate spend over a week of traffic.

  • Latency split by first-token wait versus total duration
  • Exactly one layer retries; SDK and gateway retries are bounded
  • Retry budget covers retries and hedges together
  • Retry-After treated as a minimum, with jitter and a deadline check
  • 429 and quota errors never trigger a hedge
  • Tool execution runs once, with idempotency keys
  • Hedge rate, winner and wasted tokens are logged and alerted on

If the p99 gain is small or the wasted tokens are large, drop the hedge and fix the cause: shorter outputs, a faster tier, or a second target to fail over to.

Questions readers ask

Is hedging the same as failover?
No. Failover sends the request to another target after the first one has failed or timed out, so only one attempt is live at a time. A hedge starts a second attempt while the first is still running and keeps whichever answers first, so for a short period you pay for both.
Can I hedge streaming requests?
Only up to the first token. Before any output has reached your client, either attempt can win and you can cancel the other. After output has started, switching means replaying a request whose output was already consumed, which OpenAI's guidance says not to do automatically.1
What delay should I hedge at?
Start from a high percentile of your own time-to-first-token, measured per route and model, and tighten only while the duplicate rate and wasted tokens stay inside your budget. There is no universal number; a fixed one copied from another system will be wrong for your model mix.

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.

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.Rate limits (opens in a new tab) — OpenAI. Accessed Nov 1, 2026.
  2. 2.RFC 9110: HTTP Semantics (opens in a new tab) — IETF / RFC Editor. Accessed Nov 1, 2026.
  3. 3.Failover & fallbacks (opens in a new tab) — INFRO Docs. Accessed Nov 1, 2026.
  4. 4.Routes (opens in a new tab) — INFRO Docs. Accessed Nov 1, 2026.
  5. 5.Generative AI semantic conventions (moved) (opens in a new tab) — OpenTelemetry. Accessed Nov 1, 2026.

About this article

Read on 2026-11-01: RFC 9110 (plain-text edition, which was truncated before sections 9.2.2 and 10.2.3, so only the table of contents entries for those sections are cited), OpenAI's rate-limits guide, INFRO's failover, routes and docs pages, and the OpenTelemetry GenAI conventions page, which now only points to a new repository. Provider billing for cancelled requests and published hedging studies were not confirmed and are left out.

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.