Introduction to Time to First Token (TTFT)
When building applications powered by Large Language Models (LLMs), user experience hinges heavily on perceived responsiveness. While overall request latency matters, the most critical metric for streaming responses is Time to First Token (TTFT) — the elapsed time between sending a request and receiving the very first generated token from the model.
TTFT represents the moment a user sees the model "begin typing." A low TTFT creates the illusion of a fast, intelligent assistant, while a high TTFT leaves users staring at a loading spinner, eroding trust and engagement. In this tutorial, we'll explore what TTFT is, why it matters, how to measure it accurately, and best practices for optimizing and benchmarking it in production systems.
What Is Time to First Token?
TTFT measures the delay before the first token of a streamed response arrives at the client. It captures the cumulative cost of several pipeline stages:
- Network transit — time for the request to reach the inference server.
- Queueing — time spent waiting behind other requests in the scheduler.
- Prefill / prompt processing — the forward pass over the input prompt to compute the KV cache.
- First decode step — generating the initial output token from the processed prompt.
Unlike Time Per Output Token (TPOT) or end-to-end latency, TTFT is largely independent of the response length. A 10-token response and a 1,000-token response can share the same TTFT, because the bottleneck is prompt processing, not generation.
TTFT vs. Other Latency Metrics
To benchmark streaming performance holistically, you need to understand how TTFT relates to sibling metrics:
- TTFT — request start to first token.
- TPOT — time between consecutive output tokens during generation.
- End-to-end latency — request start to final token.
- Throughput — tokens per second across all concurrent requests.
A system can have excellent throughput but poor TTFT if it batches aggressively, delaying individual requests. Conversely, a system optimized for low TTFT may sacrifice overall throughput. Benchmarking TTFT in isolation helps you reason about this trade-off.
Why TTFT Matters
Research on perceived performance consistently shows that users tolerate waiting once they see activity begin. In chat interfaces, the first token acts as that signal. A TTFT under 500 milliseconds feels instantaneous; above 2 seconds, users begin to perceive lag; above 5 seconds, abandonment rates climb sharply.
Beyond UX, TTFT has direct business implications:
- Conversion rates in customer support bots drop as initial response delay increases.
- Voice assistants require sub-300ms TTFT to feel conversational; higher delays create awkward pauses.
- Agentic pipelines compound TTFT across multiple LLM calls, making per-step optimization critical.
- Cost — inefficient prefill often correlates with wasted compute, so optimizing TTFT frequently reduces spend.
For these reasons, TTFT has become a first-class metric in model serving frameworks like vLLM, TensorRT-LLM, and TGI, all of which expose it in their benchmarking tooling.
How to Measure TTFT
Measuring TTFT requires a streaming client that records timestamps at two points: when the request is dispatched and when the first token chunk arrives. Most modern LLM APIs support Server-Sent Events (SSE) or chunked HTTP responses for this purpose.
Basic TTFT Measurement with OpenAI-Style Streaming
The following Python example uses the OpenAI client to measure TTFT against any OpenAI-compatible endpoint:
import time
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy")
def measure_ttft(prompt: str, model: str = "meta-llama/Llama-3-8B") -> float:
start = time.perf_counter()
stream = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
stream=True,
max_tokens=512,
)
first_token_time = None
token_count = 0
for chunk in stream:
if chunk.choices[0].delta.content:
if first_token_time is None:
first_token_time = time.perf_counter()
token_count += 1
end = time.perf_counter()
ttft = first_token_time - start
e2e = end - start
print(f"TTFT: {ttft * 1000:.1f} ms")
print(f"End-to-end: {e2e * 1000:.1f} ms")
print(f"Tokens: {token_count}")
return ttft
measure_ttft("Explain quantum entanglement in three sentences.")
Key points about this measurement:
- Use
time.perf_counter()rather thantime.time()for monotonic, high-resolution timing. - The clock starts before the API call, capturing network and queueing overhead.
- The first non-empty
delta.contentmarks TTFT — some servers emit empty initial chunks, so guard against that.
Measuring TTFT with Raw HTTP/SSE
If you want to avoid SDK overhead or benchmark a custom endpoint, use httpx with streaming:
import time
import httpx
import json
def measure_ttft_raw(url: str, payload: dict) -> float:
start = time.perf_counter()
first_token_time = None
with httpx.Client(timeout=60.0) as client:
with client.stream("POST", url, json=payload) as response:
for line in response.iter_lines():
if not line or not line.startswith("data: "):
continue
data = line[len("data: "):]
if data == "[DONE]":
break
chunk = json.loads(data)
delta = chunk["choices"][0].get("delta", {})
content = delta.get("content", "")
if content and first_token_time is None:
first_token_time = time.perf_counter()
break
if first_token_time is None:
raise RuntimeError("No tokens received")
return first_token_time - start
payload = {
"model": "meta-llama/Llama-3-8B",
"messages": [{"role": "user", "content": "Hello"}],
"stream": True,
"max_tokens": 128,
}
ttft = measure_ttft_raw("http://localhost:8000/v1/chat/completions", payload)
print(f"TTFT: {ttft * 1000:.1f} ms")
This raw approach eliminates SDK parsing overhead and gives you the most accurate picture of server-side streaming behavior.
Building a Robust Benchmark Harness
A single TTFT measurement is noisy. To produce trustworthy numbers, you need a benchmark harness that runs multiple prompts, repeats trials, and reports distributional statistics.
import time
import statistics
from openai import OpenAI
from concurrent.futures import ThreadPoolExecutor, as_completed
client = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy")
PROMPTS = [
"Write a haiku about the ocean.",
"Summarize the plot of Hamlet in two sentences.",
"What are the benefits of functional programming?",
"Explain how a transformer model works.",
"Give me five ideas for a mobile app.",
]
def single_request(prompt: str, model: str) -> float:
start = time.perf_counter()
stream = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
stream=True,
max_tokens=256,
)
for chunk in stream:
if chunk.choices[0].delta.content:
return time.perf_counter() - start
raise RuntimeError("Empty stream")
def benchmark(model: str, trials_per_prompt: int = 5, concurrency: int = 1):
results = []
tasks = []
with ThreadPoolExecutor(max_workers=concurrency) as pool:
for prompt in PROMPTS:
for _ in range(trials_per_prompt):
tasks.append(pool.submit(single_request, prompt, model))
for future in as_completed(tasks):
results.append(future.result() * 1000) # to ms
results.sort()
print(f"Samples: {len(results)}")
print(f"Mean TTFT: {statistics.mean(results):.1f} ms")
print(f"Median TTFT: {statistics.median(results):.1f} ms")
print(f"P50: {results[len(results)//2]:.1f} ms")
print(f"P90: {results[int(len(results)*0.9)]:.1f} ms")
print(f"P99: {results[int(len(results)*0.99)]:.1f} ms")
print(f"Min: {results[0]:.1f} ms")
print(f"Max: {results[-1]:.1f} ms")
benchmark(model="meta-llama/Llama-3-8B", trials_per_prompt=10, concurrency=4)
This harness captures the full latency distribution, which is far more informative than a single mean. In production, the P99 TTFT is often the metric you should optimize for, because it represents the worst experience a meaningful fraction of users will encounter.
Concurrent Load Benchmarking
TTFT under load behaves very differently from TTFT in isolation. As concurrent requests increase, queueing time grows and prefill compute contends for GPU resources. Use a load generator to map TTFT against concurrency:
def sweep_concurrency(model: str, levels=(1, 2, 4, 8, 16, 32)):
for c in levels:
print(f"\n=== Concurrency: {c} ===")
benchmark(model, trials_per_prompt=3, concurrency=c)
sweep_concurrency("meta-llama/Llama-3-8B")
Plotting median and P99 TTFT against concurrency reveals the inflection point where your deployment begins to degrade — invaluable capacity-planning data.
Factors That Influence TTFT
Understanding the levers that move TTFT helps you interpret benchmark results and diagnose regressions:
- Prompt length — prefill cost scales roughly linearly with input tokens. A 4K-token prompt will have a much higher TTFT than a 20-token prompt.
- Batch size — continuous batching systems like vLLM group requests; larger batches improve throughput but can increase per-request TTFT.
- Model size — larger models require more prefill FLOPs and memory bandwidth, raising baseline TTFT.
- Hardware — GPU memory bandwidth, HBM capacity, and interconnect speed all affect prefill performance.
- Quantization — INT8 or INT4 weights reduce memory pressure and can lower TTFT, sometimes at a quality cost.
- Prefix caching — caching KV states for shared system prompts dramatically reduces TTFT for repeated prefixes.
- Network — cross-region calls add tens to hundreds of milliseconds; co-locate clients and servers where possible.
Best Practices for Benchmarking TTFT
1. Warm Up Before Measuring
Cold starts inflate TTFT due to model loading, CUDA kernel compilation, and KV cache allocation. Always issue a few throwaway requests before recording measurements:
# Warmup
for _ in range(3):
try:
single_request("Hello", model="meta-llama/Llama-3-8B")
except Exception:
pass
2. Use Realistic Prompts
Synthetic micro-prompts ("Hi") understate real-world TTFT. Curate a prompt set that mirrors your production traffic, including long system prompts, few-shot examples, and varied output lengths. Log anonymized production prompts and replay them in your benchmark.
3. Report Distributions, Not Averages
TTFT distributions are typically right-skewed. A mean of 400ms can hide a P99 of 3 seconds. Always report percentiles (P50, P90, P99) alongside the mean, and visualize histograms to spot multi-modal behavior caused by batching or caching effects.
4. Control for Client-Side Overhead
SDK parsing, JSON serialization, and TLS handshake all add latency. For the most accurate server-side TTFT, measure from a client on the same machine or network segment, and subtract a baseline measured with an empty prompt if needed.
5. Isolate Variables
When comparing configurations (e.g., FP16 vs. INT8), change one variable at a time. Run benchmarks on dedicated hardware with no other GPU workloads. Pin CPU cores and disable frequency scaling to reduce variance:
# Lock GPU clocks for consistent measurements
# nvidia-smi -lgc 1410,1410
# nvidia-smi --persistence-mode=1
6. Benchmark Continuously in CI
TTFT regressions sneak in with model updates, framework upgrades, and traffic pattern shifts. Integrate your benchmark harness into CI and alert on P99 TTFT increases beyond a threshold. Store results in a time-series database to track trends over weeks and months.
7. Measure Under Realistic Load
Idle benchmarks are misleading. Replay production traffic patterns — including bursty arrivals and mixed prompt lengths — to understand how TTFT behaves when the system is actually stressed. Tools like vegeta, k6, or custom async load generators work well here.
Optimizing TTFT
Once you can measure TTFT reliably, common optimization strategies include:
- Enable prefix caching in vLLM or TGI to skip prefill for repeated system prompts.
- Reduce prompt length by trimming unnecessary context, compressing few-shot examples, or using retrieval to inject only relevant tokens.
- Use a smaller draft model for speculative decoding, which can reduce effective TTFT for compatible workloads.
- Tune batch size limits to cap queueing delay at the cost of some throughput.
- Quantize weights to INT8/FP8 to reduce memory bandwidth pressure during prefill.
- Co-locate inference servers with clients or use edge deployments for latency-sensitive applications.
- Reserve capacity for high-priority traffic to prevent queueing behind large batch jobs.
Conclusion
Time to First Token is the single most impactful latency metric for streaming LLM applications because it directly governs perceived responsiveness. By building a disciplined benchmarking harness that measures TTFT across realistic prompts, concurrency levels, and percentile thresholds, you gain the visibility needed to diagnose regressions and guide optimization. Pair accurate measurement with targeted optimizations — prefix caching, prompt engineering, quantization, and capacity tuning — and you can deliver streaming experiences that feel instantaneous to users. Treat TTFT as a first-class citizen in your observability stack and CI pipeline, and your LLM-powered products will remain fast and trustworthy as they scale.