惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
Cyberwarzone
Cyberwarzone
L
LINUX DO - 最新话题
N
News and Events Feed by Topic
T
Troy Hunt's Blog
Help Net Security
Help Net Security
S
Security @ Cisco Blogs
Google DeepMind News
Google DeepMind News
Security Archives - TechRepublic
Security Archives - TechRepublic
M
MIT News - Artificial intelligence
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V2EX - 技术
V2EX - 技术
Y
Y Combinator Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
大猫的无限游戏
大猫的无限游戏
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Microsoft Security Blog
Microsoft Security Blog
Cisco Talos Blog
Cisco Talos Blog
T
Threatpost
Recent Commits to openclaw:main
Recent Commits to openclaw:main
S
SegmentFault 最新的问题
I
InfoQ
H
Hacker News: Front Page
D
Docker
Scott Helme
Scott Helme
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Blog — PlanetScale
Blog — PlanetScale
人人都是产品经理
人人都是产品经理
博客园 - 叶小钗
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
Netflix TechBlog - Medium
AWS News Blog
AWS News Blog
Know Your Adversary
Know Your Adversary
博客园 - 【当耐特】
T
Tor Project blog
U
Unit 42
H
Heimdal Security Blog
Microsoft Azure Blog
Microsoft Azure Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
P
Privacy & Cybersecurity Law Blog
PCI Perspectives
PCI Perspectives
美团技术团队
O
OpenAI News
T
Tailwind CSS Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
B
Blog
GbyAI
GbyAI
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
MyScale Blog
MyScale Blog

Towards AI

Building AI Agents in Rust — part 4 | Towards AI Building AI Agents in Rust — part 5 | Towards AI The Verified Identity Agent Bridge | Towards AI You Can’t Prompt Your Away Your LLM Problems | Towards AI The Free Agent Trap | Towards AI Your Agentic Loop Will Drift. Here Is the KL Divergence Equation That Measures How Far It Has Wandered From Its Original Instruction. | Towards AI Beyond Chat: Processing Images, PDFs, and Documents with the OpenAI Adapter in Oracle Integration Cloud | Towards AI Building AI Agents in Rust — part 3 | Towards AI Self-Hosting Airflow at Home: Automating Stock Price Data Collection | Towards AI The 76-Hour Frontier: How the Takedown of Claude Fable 5 Birthed the Military-Industrial-AI Complex | Towards AI I Trained a Markdown File to Boost GPT-5.5 by 23 Points — It Shouldn't Work | Towards AI We Replaced ChatGPT With a Local AI Server. Six Months of Honest Data. | Towards AI What Really Makes Cars Pollute? A Data Science Deep Dive into CO₂ Emissions | Towards AI Training GPT-2 From Scratch on a GTX1050 | Towards AI Principal Component Analysis (PCA): Theory, Mathematics, and Applications Build a Zero-Cost Web Automation Pipeline With OpenRouter, OpenClaw, and MediaUse I Gave Qwen3.7-Plus a Screenshot and It Found the Exact Pixel to Click for $0.40 Beyond the Prompt: Why Autonomous AI Agents Are Replacing the Chatbot Moonshot Cracked Claude Code’s Playbook with an MIT Terminal Agent and a $0.60 Model Connections, Roles, and Warehouses: Getting CoCo Desktop Production-Ready from Day One My First $5,000 Month Writing About AI Engineering on Medium Google Shrank Gemma 4 by 72% and Unsloth Fixed the 4-Bit Bug Nobody Else Caught on One 4090, and 4-Bit Shouldn’t Be This Good LangChain Explained: Understanding Models, Prompts, Chains, Memory, Indexes, and Agents TOON: Beyond JSON for LLMs Claude Code Casual, Pro, Elite: The Three Working Personas of Claude Code Mastery MiniMax M3 Decodes 1M Tokens 15x Faster — and It Shouldn’t Be This Cheap Using Amazon SQS for AI Agent Orchestration I Ran a 1.5B-Active Model on My Laptop That Embarrassed a 26B by 46 Points How to Build a Self-Improving Company with AI Part 3 — Implementation/Engine-Level: Choosing the Runtime That Gives You These for Free 3-Part Series: LLM Latency in Production (Part 1) Claude Code: The AI Coding Partner Changing How Developers Build Software Claude Code Pitfalls: Claude Code Won’t Do What You Told It: A Troubleshooting Catalog Full-Stack Data Scientists for the Agentic Coding World Building Production-Grade AI Skills with Snowflake Cortex AI Function Studio I Tried 10 AI Agent Frameworks in 2026 — Here’s the Honest Guide I Wish I Had Earlier How One Spring Boot Optimization Saved Our Startup $30,000 a Year Inside Palantir AIP: How the World’s Most Controversial AI Platform Actually Works What Is a Reverse Proxy? (And Why Every Backend Developer Should Care) What Claude Opus 4.8 Actually Changes If You’re Building Agents QWEN 3.7 Max Worked For 35 Hrs Straight And The Results Were Mind-blowing When LLMs Meet Knowledge Graphs on the Battlefield Fine-Tuning is Dead: Why Context Orchestration Won in 2026 5 Things Broke When I Shipped a RAG + MCP Agent to Production. Google Co-Scientist: Hyper Scaling Research and Discovery Microsoft Just Embarrassed Browser Web Agents — 1,000 Lines Made GPT-5.4 Beat Opus 4.6 on 200 Web Tasks The Modern Data Stack Is Broken — Here’s How to Fix It With AI, Governance, and Real Architecture Building Production MCP Servers: What the Spec Won’t Tell You When Should an Agent Stop? The Anatomy of Termination Harness Engineering: The Layer That Matters More Than the Model AI Engineers Who Can’t Debug Are Getting Fired (Here’s How I Debug with Claude Code) Claude Code Memory: Why You Keep Explaining the Same Thing to Claude (and the Five Layers That Fix It) Claude Code Subagents: The Claude Code Feature You Skip Every Day (And Why It Quietly Wrecks Your Sessions) Agentic AI and the SMB Banking Advantage Claude Code: Spec-Driven Development — Why Your AI Coding Sessions Fall Apart at Hour Three The Real Cost of Agentic AI Nobody Budgets For SVM : 40 must visit Interview Questions (Part 2) Your AI Agent Works Perfectly in the Demo. Here Are the 6 Ways It Dies in Production. Unleashing the Power of ONNX for Speedier SBERT Inference Terraform vs CI/CD for Serverless Deployments Merve Noyan Stopped Writing Training Scripts — Her Agent Just Fine-Tuned 18 Models Solo for $11.40 Why Your Sales Forecast Is Always 20% Wrong (And How To Make It 12% Wrong) Genetic Cubic n{C/A} Ratios For Elementary Robotics Design Top 20 AdaBoost Interview Questions & Answers (Part 2 of 2) Agentic AI Vs AI Agents — What Are the Key Differences? LAI #127: The Infrastructure Layer of AI Is Becoming the Product Anthropic Caught Its Own AI Planning to Blackmail Engineers RNNs Cannot Think What Transformers Think Cheaply. ICLR 2026 Proved the Gap Is Exponential. Time Series Made So Easy My Aunt Got It on the Second Read Claude Cowork 101 | Towards AI Is 3-Bit KV Cache the Holy Grail? A Reality Check on Google’s TurboQuant LangGraph Multi-Agent Architecture: Building a Self-Critiquing AI Debate System AutoML on Autopilot | Towards AI I Ran This Open-Source AI Tool on a Messy Codebase and Got 71x Fewer Tokens — Here Is Exactly What Happened Month in 4 Papers (April 2026) AI Kept Forgetting My Notes. Fixing That Taught Me How It Actually Works. How ChatGPT Makes You Addicted Crack ML Interviews with Confidence: K-Nearest Neighbors (KNN 20 Q&A) The Event-Driven Blueprint: How I Scaled a Spring Boot System to 10 Million Kafka Messages/Day Building Vector Search? Why FAISS Alone Isn’t Enough TAI #202: GPT-5.5 Moves Codex Into Real Work Machine Learning System Design -The Model Serving Triangle, With One Forward Pass Flowing Through Every Trade-off (Part3) AI Orchestration in Action: How MuleSoft and LLMs Fuel the Future of Enterprise AI GPT-4 Has 1.8 Trillion Parameters. It Uses 2% of Them Per Token. Part 20: Data Manipulation in Multi-Dimensional Aggregation A Fundamental Introduction to Genetic Algorithm -Part Two TAI #200: Anthropic’s Mythos Capability Step Change and Gated Release From Notebook to Production: Running ML in the Real World (Part 4) Sqribble’s Template‑Driven Document Automation Anthropic Just Shipped the Layer That’s Already Going to Zero Long-Term vs Short-Term Memory for AI Agents: A Practical Guide Without the Hype The L1 Loss Gradient, Explained From Scratch Your Postcode Is Deciding Your Care. I Built a Pipeline to Prove It. I Directed AI Agents to Build a Tool That Stress-Tests Incentive Designs. Here’s What It Found. Your System Prompt Is the Product — Not the Feature The LLM Wiki Trend Has a Retention Problem Nobody Mentions Top 20 Data Preparation Interview Questions and Answers (Part 2 of 2) LAI #122: Word Embeddings Started in 1948, Not With Word2Vec Top 15 Computer Vision Datasets [2026] 40 Generative AI Interview Questions That Actually Get Asked in 2026 (With Answers)
Part 2 — Serve-Level Speed: System Design That Stabilizes P95/P99
Editorial Team · 2026-06-03 · via Towards AI

Author(s): Mehedi Hasan

Originally published on Towards AI.

You’ve quantized the model, switched to Flash Attention, and maybe even dropped to INT4. Your GPU kernels are now efficient. But users still complain that the app is “sometimes slow.” Welcome to serving hell, where the bottleneck is rarely the model and almost always the system around it.

The theme of this part: once the model is efficient, most production wins come from queueing discipline, traffic routing, and stability controls. P95 and P99 latency are not driven by tensor core utilization. They’re driven by queueing, noisy neighbors, long prompts stuck behind short ones, and slow clients holding onto GPU memory.

Part 2 — Serve-Level Speed: System Design That Stabilizes P95/P99
System Level Techniques to reduce LLM production latency

The Real Enemy Is Queueing, Not Compute

Here is the counterintuitive truth of production LLM serving: most latency is waiting time, not compute time. A request that takes 50ms of actual GPU work can easily spend 800ms in a queue because the batcher decided to wait for one more request, or because a 4K-token RAG prompt monopolized the prefill slot.

P95 and P99 latency are almost always caused by:

  • Queueing: Requests piling up behind a large batch, or when the KV cache pool is exhausted and no new slots are free
  • Noisy neighbors: One tenant submits a 10K-token prompt and stalls everyone else
  • Long prompts: Prefill dominates the GPU, starving decode steps
  • Slow clients: A streaming client on a 3G connection buffers tokens and pins GPU memory
  • Cold starts: A freshly scaled replica that hasn’t loaded weights or allocated KV cache

If you only optimize median latency, you miss the real user experience. Users remember the one time they waited three seconds for the first token. Your product metrics will look fine while your user trust erodes.

Measure the Right Things

Before you fix anything, you need metrics that split the problem correctly. Most teams log “total request time” and call it a day. That is useless.

Log these on every request:

  • Time-to-first-token (TTFT): the user’s perception of responsiveness
  • Time Per Output Token (TPOT): the standard industry metric for decode speed (e.g., 20ms/token). It is the inverse of tokens-per-second, making it easier to calculate SLAs
  • Prompt tokens and output tokens: separates prefill cost from decode cost
  • Queue wait time (KV Cache Starvation): time spent before the GPU starts work. Note: requests usually queue not because the GPU is at 100% compute, but because it has run out of PagedAttention blocks in the KV cache
  • Prefill time and decode time separately: tells you which phase to optimize
  • P95 and P99 per lane: not global P99, but per traffic lane (interactive vs. batch, short vs. long)

Why per-lane matters: If you mix a 50-token chat query with a 4K-token legal document summary, your global P99 will be dominated by the long prompt. You’ll optimize the wrong thing. Split your metrics by lane and optimize each lane independently.

Practical implementation: Most teams pipe these into Prometheus or Datadog. The key is tagging every metric with lane, model, quantization, and gpu_type. If you can’t segment, you can’t diagnose.

Traffic Shaping: Separate Your Lanes

The single most effective serving optimization is also the simplest: don’t let different workloads fight over the same GPU.

Interactive vs. Batch

Interactive traffic (chat, streaming UI) needs low TTFT. Batch traffic (background summarization, embedding generation) needs high throughput. They want opposite things from the scheduler.

The rule: Run them on separate replicas, or at minimum, separate queues with different scheduling policies.

In vLLM, you can approximate this with separate engine instances:

# Interactive lane: small max batch, prioritize TTFT
python -m vllm.entrypoints.api_server \
--model your-model \
--max-num-seqs 4 \
--max-model-len 4096 \
--port 8000

# Batch lane: larger batch, tolerate higher latency
python -m vllm.entrypoints.api_server \
--model your-model \
--max-num-seqs 16 \
--max-model-len 8192 \
--port 8001

In TGI, the scheduler is less configurable per instance, so the cleanest approach is separate deployments behind a router.

Prompt-Length Lanes

Even within interactive traffic, a 100-token prompt and a 3K-token RAG prompt should not share a queue. The long prompt will stall the short one during prefill.

Fast lane: Prompts under ~512 tokens. Strict max wait time (5–10ms), small batch cap. Slow lane: Prompts over ~512 tokens. Longer wait time allowed (50–100ms), larger batch cap acceptable.

Router logic (pseudocode concept):

if prompt_tokens < 512 and streaming == true:
route_to_fast_lane()
else:
route_to_slow_lane()

The threshold depends on your model and GPU. Measure where your prefill time starts to dominate TTFT and draw the line there.

The Modern Alternative: Chunked Prefill

While routing by prompt length is a great architectural defense, modern inference engines (like vLLM 0.4+ and TGI) now solve this at the scheduler level via Chunked Prefill. Instead of computing a 3,000-token prefill in one giant block (which starves all other requests of decode steps), the engine breaks the prefill into smaller chunks (e.g., 512 tokens). It computes one chunk, runs a decode step for active streams, computes the next chunk, and so on. (We’ll cover which engines support this in Part 3.)

Continuous Batching and SLA Caps

Static batching is dead. Today, engines use Continuous Batching (or iteration-level scheduling). Instead of waiting for a batch to fill, the scheduler greedily injects new requests the moment a single token finishes and KV cache frees up.

The danger of continuous batching is that greedy schedulers can ruin TTFT. Set your maximum wait times to your TTFT SLA. If your interactive SLA is 100ms, configure the router so no request waits more than 80ms in the queue, leaving 20ms for the actual prefill compute. (Note: this 80/20 math only applies to the fast-lane with short prompts; long prompts will need dedicated SLA tracking.)

In vLLM, this is handled internally, but you control the tradeoff via --max-num-seqs (max concurrent sequences) and --max-model-len. For more explicit control, some teams run custom dispatchers:

# Dispatcher pseudocode
MAX_WAIT_MS = 10
MAX_BATCH = 8

while True:
batch = queue.collect_until(
max_items=MAX_BATCH,
timeout_ms=MAX_WAIT_MS
)
if batch:
submit_to_gpu(batch)

In TGI, the --max-waiting-tokens and --max-batch-total-tokens flags serve a similar purpose:

text-generation-launcher \
--model-id your-model \
--max-waiting-tokens 20 \
--max-batch-total-tokens 8192

Fairness and Admission Control

Without fairness controls, one user can submit 100 requests and fill the entire batch, spiking everyone else’s latency.

Rate limiting (HTTP 429): Per-tenant or per-API-key token limits. Not just requests per minute, but tokens per minute, because a 4K prompt is 80x more expensive than a 50-token prompt. If a single tenant spikes, return 429 Too Many Requests. This triggers SDKs to auto-retry safely.

Admission control (HTTP 503): If the server’s global queue depth exceeds a threshold, reject new requests with a 503 Service Unavailable + Retry-After header. A fast 503 is always better than a request that hangs for 30 seconds and times out.

# Admission control pseudocode
if queue_depth > MAX_QUEUE_DEPTH:
return HTTP_503(retry_after=5)

Per-tenant fairness: Some engines support token-bucket or round-robin scheduling across tenants. If yours doesn’t, shard by tenant ID at the router level.

Streaming Stability

Streaming is non-negotiable for UX, but it introduces a stability risk: slow clients create backpressure.

If a client on a slow connection reads tokens at 1 token per second, but your model generates at 50 tokens per second, the server buffers 49 unacknowledged tokens. These tokens sit in cheap CPU RAM, but there is a hidden, fatal cost: the active connection pins the user’s KV cache state in precious GPU VRAM, preventing new users from taking that GPU slot.

Backpressure

Implement a max unacknowledged token limit using a bounded queue. If the client falls behind, the queue fills up, and you cancel the generation to free the KV cache:

# Pseudocode: bounded backpressure using asyncio.Queue
MAX_UNACKNOWLEDGED = 32

async def stream_tokens(generator, client):
# The queue acts as our bounded backpressure buffer
queue = asyncio.Queue(maxsize=MAX_UNACKNOWLEDGED)

# Start background task to drain queue to client (network I/O)
asyncio.create_task(drain_to_client(queue, client))

async for token in generator:
try:
# If client is slow, the queue fills. put_nowait raises QueueFull.
queue.put_nowait(token)
except asyncio.QueueFull:
# Backpressure limit reached; free the GPU KV cache
generator.cancel()
break

Cancel on Disconnect

If a user closes their browser tab, you must stop generating immediately. Otherwise, you burn GPU cycles and memory for a ghost request.

In FastAPI/Starlette:

async def generate(request):
async for token in model.stream():
if await request.is_disconnected():
model.cancel()
break
yield token

The API Gateway Trap: Your FastAPI or Python backend will never know the user disconnected unless your API Gateway (Nginx, AWS API Gateway, Cloudflare) is explicitly configured to eagerly propagate connection closures. If the gateway holds the connection open, your GPU will happily burn cycles generating a ghost response.

AWS Specific Caveat: AWS API Gateway (REST/HTTP) has a hard, unchangeable 29-second integration timeout. If your LLM stream takes longer than 29 seconds to finish, AWS will forcefully sever the connection with a 504 Timeout, killing the generation even if the user is still waiting. For LLMs, you usually must bypass API Gateway entirely (e.g., routing directly through an Application Load Balancer or using WebSockets).

Critical point: Cancel-on-disconnect is not a UX nicety. It is a multi-tenant stability requirement. One leaked generation can occupy a GPU slot for 30 seconds.

Cold-Start Avoidance

Cold starts are the silent killers of P99. When a new replica spins up — whether from autoscaling or a deployment — you pay four penalties:

  1. Model weight loading: Reading multi-GB from disk to GPU
  2. GPU memory allocation: CUDA context initialization and PagedAttention KV cache pool allocation
  3. Kernel warmup: First-run kernel compilation and CUDA graph capture
  4. Empty caches: No prefix KV cache, no attention plan cache

Warm Replicas

For interactive traffic, set your autoscaler minimum to at least 1 warm replica per GPU type. Never scale to zero for real-time workloads.

In Kubernetes, standard HPA based on nvidia.com/gpu utilization is a trap (LLMs often show 100% utilization even when underloaded). Instead, scale on queue depth or concurrent requests using KEDA:

# KEDA ScaledObject scaling on vLLM queue depth
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: vllm-scaler
spec:
minReplicaCount: 2 # Never scale to zero!
maxReplicaCount: 10
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus-server:9090
# The exact metric exported by vLLM for queued requests
metricName: vllm:num_requests_waiting
threshold: '10' # Scale up when >10 requests are waiting

Warmup Prompts

After loading weights, run a few representative prompts through the model before marking the replica healthy. This triggers kernel compilation, CUDA graph capture, and attention plan caching. You also want to force the engine to allocate its maximum PagedAttention KV cache pool upfront, preventing Out-Of-Memory (OOM) crashes under real traffic.

def warmup(model):
shapes = [
("Hi", 1), # short chat: 1 token forces prefill only
("Summarize: " + "x"*2000, 1) # long RAG: prefill without burning decode time
]
for text, max_new in shapes:
# max_new_tokens=1 forces the prefill without burning decode time
model.generate(text, max_new_tokens=max_new)

Health check trick: Only return HTTP 200 from /health after warmup completes. Your load balancer will not route traffic to a cold replica.

Warm Prefix Caches

If you use prefix caching (from Part 1), precompute your top system prompts at boot time. This ensures the first real user gets a cache hit and forces the KV cache pool to be fully allocated.

# Precompute common prefixes and allocate KV pool at startup
for system_prompt in COMMON_SYSTEM_PROMPTS:
# max_new_tokens=1 forces the prefill without burning decode time
model.generate(system_prompt, max_new_tokens=1)

Output Control

Unbounded decode is the fastest way to ruin throughput. If every request decodes 1,024 tokens “just in case,” your GPU spends most of its time on low-value tail tokens.

Adaptive Max Output Tokens

Set max_new_tokens based on task type and prompt length:

  • Extraction / classification: 64–128 tokens
  • Chat / Q&A: 256–512 tokens
  • Reasoning / coding: 512–1,024 tokens

If the prompt is already huge, tighten the cap further. A 6K-token RAG prompt with a 1K-token output is 7K total tokens. The same prompt with a 256-token output is 6.25K total — a 10% reduction in total work.

def choose_max_tokens(task: str, prompt_tokens: int) -> int:
base
= {"extract": 96, "chat": 384, "reason": 768}.get(task, 256)
if prompt_tokens > 4000:
base = min(base, 256)
return base

Stop Sequences

Use stop sequences to cut generation short when the model is clearly done. Common patterns:

  • Tool call boundaries: "<<function_calls>", "</invoke>"
  • Structured output delimiters: "}", "```"
  • Reasoning markers: "Final Answer:", "###"

This is especially effective with JSON mode or tool use. The model often starts generating padding or repetition after the valid output ends.

Early Stopping Heuristics

For non-structured tasks, consider stopping when:

  • The model repeats the same token 3+ times
  • Entropy drops below a threshold (model is stuck in a loop)
  • A confidence score on the EOS token exceeds a threshold

These are advanced and require careful validation, but they can cut 20–30% of wasted decode time.

How to Stop P99 from Ruining Your Product

Here is the practical order that actually works in production:

  1. Split your metrics by lane. You cannot optimize what you cannot see. Separate interactive vs. batch, short vs. long prompt.
  2. Route traffic into separate lanes. Interactive gets its own replicas or queue. Long prompts never block short ones.
  3. Add continuous batching with a max wait. Protect TTFT. A small batch that ships fast is better than a full batch that waits.
  4. Implement admission control. Reject requests when queue depth is high. A fast 503 is better than a slow timeout.
  5. Add cancel-on-disconnect and backpressure. Stop burning GPU on ghost requests and slow clients. Fix your API gateway to propagate disconnects.
  6. Keep warm replicas and run warmup prompts. Cold starts are P99 spikes you can eliminate entirely.
  7. Cap output length by task type. Most requests don’t need 1,024 tokens. Save decode for the ones that do.
  8. Add prefix caching and warm it at boot. If you have long system prompts, this is the highest-ROI TTFT win.

The mindset shift: Stop thinking about “how fast is my model?” Start thinking about “how predictable is my latency distribution?” A median of 100ms with a P99 of 3 seconds is worse than a median of 200ms with a P99 of 400ms.

But here is the catch: building all of this custom routing and queueing logic from scratch is exhausting. In Part 3, we’ll map all of these needs to specific inference engines — vLLM, TGI, TensorRT-LLM, and llama.cpp. We’ll look at which engines give you chunked prefill, continuous batching, and prefix caching out-of-the-box, and which ones force you to build the architecture yourself.

Published via Towards AI