n4nAI

Claude Opus 4.5 streaming latency under concurrent load

An analysis of how Claude Opus 4.5 streaming latency behaves under concurrent load, with load-testing methodology and mitigation strategies for engineers.

n4n Team4 min read920 words

Audio narration

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

Understanding Claude Opus 4.5 streaming latency concurrent load is critical for anyone shipping real-time AI features. The model’s size means each request consumes significant compute, and when many streams compete for the same accelerator, the latency profile changes in ways that naive single-user benchmarks hide. This article presents a clear thesis: under concurrency, the dominant degradation is time-to-first-token, not inter-token delay, and you should architect around queueing rather than raw throughput.

Why streaming latency splits into two phases

Streaming an LLM response over SSE gives you two distinct metrics. Time-to-first-token (TTFT) measures from request send to the first byte of the response. Inter-token latency (ITL) measures the gap between subsequent tokens once the stream starts.

For a frontier-class model like Opus 4.5, prefill dominates TTFT. The model must process the full prompt through its attention layers before emitting anything. Token generation after that is memory-bandwidth bound but comparatively stable.

import time

def log_latency(first_token: float, prev_token: float, now: float, count: int):
    if count == 1:
        print(f"TTFT: {first_token*1000:.0f}ms")
    else:
        print(f"ITL[{count}]: {(now-prev_token)*1000:.0f}ms")

The concurrency pressure point

When you send one request, the provider allocates a slice of a GPU or TPU pod. Send 50 at once, and the scheduler queues them. Frontier models are not stateless functions; they hold KV caches that compete for high-bandwidth memory.

Claude Opus 4.5 streaming latency concurrent load scales poorly at the head of the queue. Each additional in-flight prompt adds prefill work that blocks others. Once a request starts streaming, its ITL usually stays within a narrow band because generation is sequential and the scheduler gives it a recurring time slice.

This is a structural property of transformer inference, not a bug. You cannot fix it by retrying faster.

Continuous batching and why it doesn’t save you

Modern inference servers use continuous batching to mix decode steps across requests. That helps utilization, but prefill remains largely atomic per request. A long prefill occupies the compute path for its duration, delaying subsequent prefills even if decodes are interleaved.

Estimating prefill cost

Roughly, prefill FLOPs scale as 2 * params * prompt_tokens. For a ~200B-parameter model, a 4K-token prompt is on the order of 1.6e15 FLOPs. Even on accelerators with quoted peak throughput, effective throughput for attention is lower. This is back-of-envelope reasoning, not a measured benchmark, but it explains why prefill queueing dominates under load.

Load-testing methodology that doesn’t lie

Most benchmarks publish average tokens/sec on a warm model. That hides the tail. To measure what users feel, fire aligned concurrent requests and record per-token timestamps.

We used the OpenAI-compatible chat completions endpoint with stream: true. The client below opens N connections simultaneously using asyncio and writes raw timestamps.

import asyncio, openai, time

async def stream_one(client, sem, prompt, n):
    async with sem:
        start = time.monotonic()
        first = None
        async with await client.chat.completions.create(
            model="claude-opus-4-5",
            messages=[{"role":"user","content":prompt}],
            stream=True,
        ) as resp:
            i = 0
            async for chunk in resp:
                i += 1
                now = time.monotonic()
                if first is None:
                    first = now
                    print(f"req {n} TTFT {(first-start)*1000:.0f}ms")
        return first - start

async def main(concurrency, prompts):
    client = openai.AsyncOpenAI(base_url="https://api.llm-gateway.example/v1", api_key="KEY")
    sem = asyncio.Semaphore(concurrency)
    await asyncio.gather(*[stream_one(client, sem, p, i) for i,p in enumerate(prompts)])

asyncio.run(main(32, ["Explain raft consensus"]*32))

The key is asyncio.Semaphore to cap real concurrency. Without it, your client loops will lie by serializing on event-loop overhead.

What we observed (qualitatively)

At low concurrency (1–4), TTFT tracks provider baseline. As concurrency climbs to 16–64, TTFT stretches. The exact multiple depends on the provider’s batching strategy and whether they pre-empt prefill. We saw queueing induced by outstanding prefills, not by generation.

ITL remained roughly constant past the first token. That confirms the bottleneck is prefill scheduling, not decode bandwidth. If you design for p95 user experience, you must budget for TTFT inflation under load.

Setting latency SLAs that survive contact with load

You need percentiles, not averages. Collect TTFT samples and compute p50/p90/p99.

import numpy as np

def report(latencies_ms):
    arr = np.array(latencies_ms)
    print(f"p50 {np.percentile(arr,50):.0f} "
          f"p90 {np.percentile(arr,90):.0f} "
          f"p99 {np.percentile(arr,99):.0f}")

Why p99 matters for streaming UX

First-token delay is what the user perceives as “thinking”. If p99 TTFT slides past a few seconds, interactive use feels broken even if p50 looks fine. Under Claude Opus 4.5 streaming latency concurrent load, the p99 is where the queueing pain shows.

Mitigation strategy 1: Bound concurrency explicitly

Do not fire unlimited parallel streams from a single service account. Set a max in-flight count per worker and use a queue. This turns unbounded latency into a bounded queue wait that you can monitor.

from asyncio import Queue, Semaphore

sem = Semaphore(8)  # hard cap
q: Queue = Queue()

async def worker():
    while True:
        item = await q.get()
        async with sem:
            await stream_one(item)

Mitigation strategy 2: Prompt caching and shorter contexts

Opus 4.5 supports prompt caching on long system prompts. A cached prefill skips recomputation of static prefixes, directly cutting TTFT. Forward cache-control hints if your gateway honors them.

{
  "model": "claude-opus-4-5",
  "messages": [
    {"role":"system","content":"You are a terse helper.","cache_control":{"type":"ephemeral"}}
  ],
  "stream": true
}

Shorter contexts also reduce prefill cost roughly linearly. Trimming tokens from a long prompt can drop TTFT proportionally under load.

Mitigation strategy 3: Use routing directives and fallback

If you control routing, send non-latency-sensitive jobs to a batch endpoint and reserve streaming for interactive paths. A gateway that honors client routing directives and forwards provider cache-control hints (like n4n.ai) keeps your caching effective across routes and can automatically fall back when a provider is degraded.

Client-side backpressure and UX patterns

Streaming latency is only half the story; perceived latency depends on UI. Show a spinner or partial skeleton before the first token arrives. In the browser, consume the stream without blocking the main thread.

const res = await fetch('/v1/chat/completions', {
  method: 'POST',
  body: JSON.stringify({ stream: true, model: 'claude-opus-4-5' })
});
const reader = res.body!.getReader();
// render "Generating…" immediately, update on each chunk

Do not buffer the entire response before rendering. The whole point of streaming is to decouple generation from display.

Tradeoffs you must accept

Lowering concurrency improves tail latency but reduces aggregate throughput. If you need to serve many users, you either provision more replicas or accept that some will wait in queue. There is no free lunch with frontier models.

Prompt caching reduces TTFT but requires stable prefixes; dynamic few-shot examples defeat it. Fallback adds routing complexity and possible output drift between providers. Shorter contexts help but may hurt quality.

Decisive takeaway

Claude Opus 4.5 streaming latency concurrent load is governed by prefill queueing, not token decoding. Measure TTFT at your target concurrency, cap in-flight requests, cache static prefixes, and route around degraded providers. Teams that treat streaming latency as a single number will ship a broken real-time experience; teams that engineer for the queue will ship something that holds up.

Tagsclaude-opus-4-5streaming-latencyconcurrencyload-testing

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 →