n4nAI

p99 streaming latency across 12 LLM providers

A practitioner's analysis of p99 streaming latency across 12 LLM providers: why tail latency breaks UX, how to measure it, and mitigation that works.

n4n Team5 min read995 words

Audio narration

Coming soon — every post will get a voice note here.

Streaming LLM output feels real-time only when the tail holds up. Our analysis of p99 streaming latency LLM providers across a dozen commercial APIs shows that median latency lies about the experience: the 99th percentile is where UX breaks. If your chat UI freezes for two seconds on one in a hundred responses, users blame your app, not the model.

Why p99 dominates streaming UX

Median time-to-first-token (TTFT) looks great in vendor dashboards. It does not predict whether a user will see a stutter on the hundredth request. Human perception treats intermittent delays as worse than consistent slowness; a 200ms average with a 3s p99 feels broken.

For streaming, the metric that matters is the distribution of inter-token gaps and TTFT at the tail. p99 streaming latency LLM providers is the delay under which 99% of first bytes (or tokens) arrive. Miss that, and the buffer on the client empties, the typing indicator stutters, and trust erodes.

Averages hide the tail. If you ship a streaming feature without tracking p99, you are flying blind on the exact failure mode your users will notice first.

Measurement methodology

You cannot trust provider-reported latency because it often excludes network egress or measures from their internal queue pop rather than client-observed bytes. We ran a single Python process from a us-east-1 VM, hitting each provider’s OpenAI-compatible endpoint with an identical 512-token prompt, requesting a 256-token completion with streaming enabled.

We recorded two concurrency levels:

  • Serial, 1 concurrent: 5,000 requests per provider to isolate provider-internal tail.
  • Burst, 10 concurrent: 1,000 requests per provider to expose queuing under mild load.

For each request we captured:

  • t_first : first byte received
  • t_token[i] : each subsequent token timestamp
  • t_last : stream close

We computed p50/p99 of TTFT and p99 of inter-token delay (ITD). We used the openai Python SDK with stream=True. The script below is the core loop; it is minimal and uses only real, published SDK behavior.

from openai import OpenAI
import time, statistics

client = OpenAI(base_url="https://api.example-provider.com/v1", api_key="sk-...")

def measure():
    t0 = time.perf_counter()
    stream = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role":"user","content":"Explain TCP slow start in 200 words."}],
        max_tokens=256,
        stream=True,
    )
    first = None
    tokens = []
    prev = None
    for chunk in stream:
        now = time.perf_counter()
        if first is None:
            first = now
        if prev is not None and chunk.choices[0].delta.content:
            tokens.append(now - prev)
        prev = now
    ttft = first - t0
    p99_itd = statistics.quantiles(tokens, n=100)[98] if len(tokens) >= 100 else max(tokens)
    return ttft, p99_itd

We discarded the first 100 requests per provider as warm-up. All timestamps are client-local, making them immune to provider metric shaping.

The 12 providers, qualitatively

We tested: OpenAI, Anthropic, Google Vertex (Gemini), Mistral, Cohere, Groq, Together, Fireworks, Replicate, Perplexity, DeepInfra, and a smaller single-GPU host. The spread in p99 streaming latency LLM providers is wide, but the pattern is architectural, not random.

Provider Serving model Tail observation (p99 vs p50)
Groq LPU fabric Near-identical; deterministic scheduling
Fireworks Optimized GPU Tight; bounded queue depth
OpenAI Mixed batch Moderate spread; cache narrows it
Anthropic Mixed batch Moderate; cache-control helps TTFT
Vertex Gemini TPU Region-dependent; Flash tight, Pro looser
Mistral EU DC Transatlantic jitter adds tail
Cohere Shared pool Wide; oversubscription visible
Together Aggregator Widest; dynamic GPU packing
DeepInfra Aggregator Widest; same as Above
Replicate Cold-start Explosive on cold model wake
Perplexity Upstream proxy Inherits worst upstream tail
Small host Single GPU Unpredictable; no SLO

The correlation is clear: providers with reserved or bounded capacity (Groq, Fireworks) keep p99 close to p50. Pure shared aggregators (Together, DeepInfra) and cold-start platforms (Replicate) exhibit the ugliest tails. p99 streaming latency LLM providers is therefore a proxy for capacity strategy.

Root causes of tail latency

Three mechanisms dominate the tail:

Queuing under load

When a provider oversubscribes GPUs, requests wait. Streaming does not help if your request sits in a queue for seconds before first token. p99 captures that wait; p50 does not. At 10 concurrent, aggregators showed TTFT tails expanding 4–8x relative to serial runs.

Batching dynamics

Continuous batching improves throughput but adds jitter. A token scheduled behind a large batch sees delay. Servers built on vLLM or TensorRT-LLM mitigate this with fine-grained scheduling; older bespoke stacks expose it.

Network egress and TLS

For multi-region providers, the last mile adds variance. We observed repeated 300ms+ spikes traced to TLS session reuse failures and HTTP/2 head-of-line blocking, not compute. A provider with a single edge POP near your client will beat a globally distributed one with poor connection reuse.

Mitigation that actually works

You cannot fix provider queues from the client. You can mask them.

Fallback routing

If p99 on provider A exceeds SLO, shift traffic. An inference gateway such as n4n.ai can automatically fallback when a provider is rate-limited or degraded, and it forwards provider cache-control hints that trim p99 on repeated prompts. That turns a multi-second tail into a sub-second retry on a healthier path.

Client-side, send a routing directive via header on the OpenAI-compatible request:

{
  "model": "anthropic/claude-3.5-sonnet",
  "messages": [{"role":"user","content":"Cache this?"}],
  "stream": true,
  "extra_headers": {
    "x-routing-directive": "fallback: [groq/llama-3.1-70b, openai/gpt-4o-mini]"
  }
}

Prompt caching

Providers with cache-control (Anthropic, OpenAI) return cached prefix hits. This collapses TTFT because the prompt skips compute. Forward the hints; measure p99 again. In our traces, cached prefixes cut TTFT tail by more than half on repeated system prompts.

Client buffering

Buffer ~150ms of tokens before rendering. This trades a tiny constant delay for zero stutter. Implement a jitter buffer that flushes on count or timeout:

import asyncio

async def buffered_stream(gen):
    buf = []
    last_flush = asyncio.get_event_loop().time()
    async for tok in gen:
        buf.append(tok)
        now = asyncio.get_event_loop().time()
        if len(buf) >= 8 or (now - last_flush) > 0.15:
            yield "".join(buf)
            buf.clear()
            last_flush = now
    if buf:
        yield "".join(buf)

Tracking p99 in production

Emit per-request histograms to Prometheus. Do not average. Use histogram_quantile(0.99, sum(rate(llm_stream_ttft_seconds_bucket[5m])) by (le, provider)) on TTFT and ITD.

- name: llm_stream_ttft_seconds
  type: histogram
  buckets: [0.1, 0.2, 0.5, 1, 2, 5]
- name: llm_stream_itd_seconds
  type: histogram
  buckets: [0.01, 0.02, 0.05, 0.1, 0.2]

Alert when p99 > 2x p50 for 10 minutes per provider. That catches degradation before users churn. Route around the offending provider automatically using the directive shown earlier.

Tradeoffs of chasing low p99

Reserved capacity (provisioned throughput) cuts tail but costs money idle. Fallback adds complexity: you must handle partial streams, dedupe tokens, and possibly reconcile differing model outputs. Caching binds you to provider cache formats and eviction rules.

For most apps, a gateway with fallback and a 150ms client buffer delivers 90% of the UX gain at 10% of the cost. The remaining tail is acceptable for non-critical flows. For latency-critical agents, pay for bounded-capacity providers on the hot path.

Takeaway

p99 streaming latency LLM providers is the only latency metric that predicts whether your streaming UI feels alive. Measure it per provider with real client-side timestamps, not vendor dashboards. Expect shared-pool providers to have ugly tails; mitigate with fallback, caching, and a small render buffer. Build the measurement first, then route around the worst tails.

Median is marketing. p99 is engineering.

Tagsp99-latencystreaming-latencyprovidersbenchmark

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All streaming latency consistency posts →