人工智能实战:大模型系统如何做全链路性能优化?从请求进入到 GPU 推理的端到端瓶颈分析与落地方案
一、问题场景:单点都优化了,为什么系统还是不够快?
做到第10篇时,我们已经完成了很多优化:
text
1. 模型启动时加载,避免重复初始化
2. vLLM 提升推理吞吐
3. Redis 队列削峰填谷
4. 多 GPU 扩展
5. 限流、熔断、降级
6. Prometheus 监控
7. Kubernetes GPU 部署
看起来每一层都已经做了优化。
但上线压测时,仍然出现一个让人头疼的问题:
text
单独看每个模块都不慢;
但用户端到端响应就是不够快。
接口日志显示:
text
Gateway 耗时:20ms
队列入队:5ms
Worker 处理:正常
vLLM 推理:1.2s
总耗时:却经常达到 4s ~ 8s
这说明一个很关键的问题:
text
系统慢,不一定是某一段慢,而是全链路累积慢。
很多大模型系统的性能问题,不是靠"调一个参数"能解决的,而是需要从请求进入系统开始,逐段拆解。
这篇文章就围绕一个核心目标:
text
如何做大模型系统全链路性能优化。
二、真实问题:为什么"局部优化"解决不了整体慢?
传统优化经常是这样:
text
接口慢 → 优化接口
模型慢 → 换 vLLM
GPU 满 → 加 GPU
队列堵 → 加 Worker
这些动作都可能有效,但也可能制造新问题。
例如:
text
增加 Worker
可能导致:
text
更多请求同时打到 vLLM
GPU 抢占更严重
P99 反而升高
又比如:
text
加大队列长度
可能导致:
text
请求不失败了
但用户等得更久
所以全链路性能优化的第一原则是:
text
先拆链路,再优化瓶颈。
三、大模型请求的完整链路
一个典型大模型请求链路如下:
text
Client
↓
Nginx / Ingress
↓
API Gateway
↓
参数校验
↓
限流 / 熔断
↓
Redis Queue
↓
Worker
↓
vLLM Server
↓
GPU 推理
↓
结果返回
我们要优化的不是某一段,而是每一段的耗时构成。
端到端耗时可以拆成:
text
Total Latency =
网络耗时
+ 网关处理耗时
+ 排队等待耗时
+ Worker 取任务耗时
+ 模型推理耗时
+ 输出传输耗时
如果你没有按这个公式拆解,就很容易误判。
四、建立全链路 Trace ID
第一步不是优化代码,而是给每个请求加 Trace ID。
没有 Trace ID,你无法串联:
text
Gateway 日志
Worker 日志
vLLM 日志
Nginx 日志
五、FastAPI 增加 Trace ID
python
import uuid
import time
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
app = FastAPI()
class TraceMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
trace_id = request.headers.get("X-Trace-Id", str(uuid.uuid4()))
request.state.trace_id = trace_id
start = time.time()
response = await call_next(request)
cost = round((time.time() - start) * 1000, 2)
response.headers["X-Trace-Id"] = trace_id
response.headers["X-Cost-Ms"] = str(cost)
print({
"trace_id": trace_id,
"path": request.url.path,
"cost_ms": cost
})
return response
app.add_middleware(TraceMiddleware)
这样每个请求都会有:
text
X-Trace-Id
后续日志全部带上它。
六、分段记录耗时
在 Gateway 中记录:
python
import time
def now_ms():
return int(time.time() * 1000)
@app.post("/chat")
async def chat(req: dict, request: Request):
trace_id = request.state.trace_id
t0 = now_ms()
# 参数校验
prompt = req.get("prompt")
max_tokens = req.get("max_tokens", 128)
t1 = now_ms()
# 入队
job = queue.enqueue("tasks.llm_task", trace_id, prompt, max_tokens)
t2 = now_ms()
print({
"trace_id": trace_id,
"stage": "gateway",
"validate_ms": t1 - t0,
"enqueue_ms": t2 - t1
})
return {
"trace_id": trace_id,
"job_id": job.id
}
在 Worker 中记录:
python
def llm_task(trace_id: str, prompt: str, max_tokens: int):
t0 = now_ms()
# 模拟调用 vLLM
result = call_vllm(prompt, max_tokens)
t1 = now_ms()
print({
"trace_id": trace_id,
"stage": "worker",
"llm_call_ms": t1 - t0
})
return result
这样就可以得到完整链路耗时。
七、建立性能基线
优化前必须先建立基线。
建议记录以下指标:
text
1. Gateway 耗时
2. 队列等待时间
3. Worker 执行时间
4. vLLM 推理时间
5. 总耗时
6. input tokens
7. output tokens
可以设计一个日志格式:
python
{
"trace_id": "xxx",
"input_tokens": 512,
"output_tokens": 128,
"queue_wait_ms": 1200,
"llm_ms": 1800,
"total_ms": 3500
}
八、压测脚本:构造不同类型请求
为了真实发现瓶颈,压测不能只用一种 prompt。
python
from locust import HttpUser, task, between
import random
class LLMUser(HttpUser):
wait_time = between(0.5, 2)
@task
def chat(self):
short_prompt = "解释一下Transformer。"
medium_prompt = "请详细解释Transformer中的自注意力机制,并举一个例子。"
long_prompt = "请写一篇关于大模型系统部署的技术文章,包含架构、监控、性能优化、稳定性治理。" * 20
prompt = random.choice([
short_prompt,
medium_prompt,
long_prompt
])
self.client.post("/chat", json={
"prompt": prompt,
"max_tokens": random.choice([64, 128, 256, 512])
})
启动:
bash
locust -f locustfile.py --host=http://127.0.0.1:8000
观察不同请求类型下的:
text
P95
P99
Queue Wait
LLM Latency
Token 分布
九、常见瓶颈 1:排队时间过长
如果你发现:
text
total_ms 很高
llm_ms 正常
queue_wait_ms 很高
说明问题不在模型,而在队列。
可能原因:
text
1. Worker 不足
2. GPU 已经满载
3. 请求速率超过处理能力
4. 长任务堵塞短任务
解决方案不是直接加 Worker,而是先判断 GPU 是否已满。
如果 GPU 未满:
text
可以增加 Worker
如果 GPU 已满:
text
增加 Worker 只会制造更多争抢
更合理的方案是:
text
1. 请求分级队列
2. 限制 max_tokens
3. 增加 GPU 副本
4. 拒绝过载请求
十、常见瓶颈 2:长 prompt 拖慢系统
长 prompt 会带来两个成本:
text
1. Prefill 成本
2. KV Cache 成本
Prefill 阶段负责处理输入 token。
如果 prompt 很长,哪怕输出很短,也会慢。
建议在 Gateway 侧限制:
python
MAX_PROMPT_CHARS = 4000
MAX_TOKENS = 256
if len(prompt) > MAX_PROMPT_CHARS:
raise ValueError("prompt too long")
max_tokens = min(max_tokens, MAX_TOKENS)
更进一步,可以做请求分级:
python
def classify_request(input_tokens: int, max_tokens: int):
estimated_cost = input_tokens + max_tokens * 2
if estimated_cost < 512:
return "short"
elif estimated_cost < 2048:
return "medium"
else:
return "long"
然后路由到不同队列:
python
request_type = classify_request(input_tokens, max_tokens)
if request_type == "short":
queue = short_queue
elif request_type == "medium":
queue = medium_queue
else:
queue = long_queue
这样可以避免:
text
长任务拖慢短任务。
十一、常见瓶颈 3:同步等待导致接口占用
错误做法:
text
用户请求进来后,一直等待模型生成完成
这会占用连接和服务线程。
对于长文本生成,更好的方式是:
text
1. 短任务同步返回
2. 长任务异步提交
3. 前端轮询或 WebSocket 获取结果
示例:
python
if request_type == "short":
return call_llm_sync(prompt, max_tokens)
else:
job = queue.enqueue("tasks.llm_task", prompt, max_tokens)
return {
"mode": "async",
"job_id": job.id
}
十二、常见瓶颈 4:没有流式输出
如果生成内容较长,用户不一定需要等全部生成完。
可以使用 Streaming。
在 vLLM OpenAI 接口中可以使用:
json
{
"stream": true
}
FastAPI 示例:
python
from fastapi.responses import StreamingResponse
import httpx
import json
async def stream_vllm(payload):
async with httpx.AsyncClient(timeout=None) as client:
async with client.stream("POST", VLLM_URL, json=payload) as resp:
async for line in resp.aiter_lines():
if line:
yield line + "\n"
@app.post("/chat/stream")
async def chat_stream(req: dict):
payload = {
"model": MODEL_NAME,
"messages": [
{"role": "user", "content": req["prompt"]}
],
"max_tokens": req.get("max_tokens", 256),
"temperature": 0.7,
"stream": True
}
return StreamingResponse(
stream_vllm(payload),
media_type="text/event-stream"
)
流式输出不能减少总计算量,但能显著改善用户感知延迟。
用户体验从:
text
等 8 秒看到完整结果
变成:
text
1 秒内看到第一个 token
这在产品体验上差别很大。
十三、常见瓶颈 5:HTTP 超时配置不合理
大模型接口天然比普通接口慢。
如果 Nginx、Ingress、Gateway 的超时设置太短,会出现:
text
模型还在生成
上游连接已经断开
Nginx 配置示例:
nginx
location / {
proxy_pass http://llm_gateway;
proxy_connect_timeout 60s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_buffering off;
}
如果使用流式输出:
text
proxy_buffering 必须关闭
否则前端可能收不到实时 token。
十四、常见瓶颈 6:缓存完全没做
很多业务场景中,用户会重复问类似问题。
例如:
text
"什么是Transformer?"
"Transformer是什么?"
"解释一下Transformer"
可以做简单缓存。
python
import hashlib
import time
cache = {}
def cache_key(prompt: str, max_tokens: int):
raw = f"{prompt}:{max_tokens}"
return hashlib.md5(raw.encode()).hexdigest()
def get_cache(key):
item = cache.get(key)
if not item:
return None
value, expire_at = item
if time.time() > expire_at:
cache.pop(key, None)
return None
return value
def set_cache(key, value, ttl=300):
cache[key] = (value, time.time() + ttl)
在接口中使用:
python
key = cache_key(prompt, max_tokens)
cached = get_cache(key)
if cached:
return {
"answer": cached,
"cache_hit": True
}
result = call_llm(prompt, max_tokens)
set_cache(key, result)
return {
"answer": result,
"cache_hit": False
}
缓存适合:
text
FAQ
固定知识问答
系统提示词生成
结构化分析结果
不适合:
text
强实时问题
个性化上下文
高随机性创作
十五、常见瓶颈 7:系统 Prompt 太长
很多 AI 应用会在每次请求前拼接一大段系统 Prompt。
例如:
text
你是一个专业助手......
你必须遵守......
你需要按照......
以下是知识库上下文......
这会导致每次请求都重复消耗 token。
优化方式:
text
1. 精简系统 Prompt
2. 把固定规则压缩成短指令
3. 知识库上下文只取 TopK
4. 避免把无关历史对话全部塞进去
错误示例:
python
context = "\n".join(all_docs)
推荐:
python
context = "\n".join(top_k_docs[:5])
大模型系统里,最贵的不是代码,而是:
text
每一次重复传入的 token。
十六、优化前后验证结果
在一次内部压测中,优化前指标:
text
平均延迟:3.8s
P95:9.5s
P99:18s
队列等待 P95:5.2s
首 token 时间:3.5s
逐步优化后:
text
1. 限制 max_tokens
2. 请求分级队列
3. 长任务异步化
4. 开启流式输出
5. 增加缓存
6. 精简系统 Prompt
优化后:
text
平均延迟:1.9s
P95:4.1s
P99:7.5s
队列等待 P95:1.3s
首 token 时间:0.8s
注意:
text
没有一个优化是"银弹"。
性能提升来自全链路叠加。
十七、踩坑记录
坑 1:只看模型推理耗时
很多人只看:
text
vLLM latency
但用户感受到的是:
text
端到端延迟
必须看:
text
total latency
坑 2:盲目增加 Worker
Worker 多不一定快。
如果 GPU 已经满了,增加 Worker 只会让系统更拥挤。
坑 3:没有区分短任务和长任务
这是大模型系统里最常见的性能坑。
短任务被长任务拖慢,用户体验会非常差。
坑 4:所有请求都同步等待
同步模式简单,但不适合长文本生成。
需要按任务类型区分:
text
短任务同步
长任务异步
坑 5:流式输出没有关 proxy_buffering
如果 Nginx 缓冲没关,后端虽然流式返回,前端仍然可能一次性收到。
坑 6:系统 Prompt 越写越长
很多团队为了修复模型行为,不断往系统 Prompt 里加规则。
最后系统 Prompt 变得很长,所有请求成本都上升。
十八、适合收藏的全链路优化 Checklist
text
请求入口:
[ ] 是否有 Trace ID
[ ] 是否限制 prompt 长度
[ ] 是否限制 max_tokens
[ ] 是否区分短任务和长任务
队列层:
[ ] 是否统计 queue_wait_ms
[ ] 是否有最大队列长度
[ ] 是否有短/中/长分级队列
[ ] 是否有过载拒绝策略
推理层:
[ ] 是否使用 vLLM
[ ] 是否控制 max_model_len
[ ] 是否监控 tokens/s
[ ] 是否监控 input/output tokens
体验层:
[ ] 是否支持流式输出
[ ] 长任务是否异步化
[ ] 是否有缓存
[ ] 是否有降级结果
网关层:
[ ] Nginx 超时是否足够
[ ] 是否关闭 proxy_buffering
[ ] 是否记录端到端耗时
监控层:
[ ] 是否监控 Avg/P95/P99
[ ] 是否监控 GPU
[ ] 是否监控 Queue Size
[ ] 是否监控错误率
十九、经验总结
这篇文章想强调一个核心观点:
text
大模型系统的性能优化,不是模型优化,而是系统工程优化。
单点优化只能解决局部问题。
真正的线上性能来自:
text
1. 请求成本可控
2. 队列等待可控
3. 推理吞吐可控
4. 输出体验可控
5. 监控指标可控
如果你只看模型推理时间,很容易忽略:
text
队列等待
网关超时
长 prompt
缓存缺失
同步阻塞
系统 Prompt 冗余
这些才是很多线上系统真正慢的原因。
二十、后续优化建议
可以继续深入:
text
1. 接入 OpenTelemetry 做 Trace
2. 使用语义缓存降低重复请求
3. 用请求成本模型做智能路由
4. 使用 KEDA 按队列长度扩容 Worker
5. 使用多 vLLM 副本做负载均衡
6. 对不同业务配置不同 max_tokens
7. 结合 RAG 做上下文裁剪
8. 用模型网关统一管理多模型
一句话总结:
text
大模型系统不是把模型包成接口,而是要把每一个 token、每一次排队、每一次等待都纳入工程控制。