06 - 大模型推理故障排查与性能调优:从理论到落地
·
06 - 大模型推理故障排查与性能调优:从理论到落地
本文是《大模型推理框架深度解析》系列的第六篇(完结篇),提供完整的生产部署Checklist、故障演练方案与性能调优技巧。
写在前面
经过前面五篇的学习,你已经掌握了:
- 三大框架的选型原则
- 量化技术的原理与对比
- KV Cache与批处理的优化
- 分布式推理的并行策略
- 生产架构的设计方法
本文作为系列完结篇,将提供一份可直接落地的生产部署Checklist,以及常见问题的排查与解决方案。
一、生产部署Checklist
1.1 部署前准备
模型准备
- 验证模型文件完整性(SHA256校验)
- 确认量化方案与硬件匹配
- 测试模型在目标框架的加载
- 准备备用模型(不同量化版本)
- 验证模型许可协议允许生产使用
资源配置
- GPU节点标签与亲和性配置
- 显存配额设置(limit = request,防止超售)
- 共享存储(PVC)容量与性能验证
- 网络策略(Pod间通信放行)
- 节点污点与容忍配置
服务配置
- 健康检查端点配置(liveness/readiness)
- 优雅关闭超时(>模型卸载时间)
- 资源监控埋点验证
- 日志聚合配置(JSON格式)
- 分布式追踪(OpenTelemetry)
高可用
- 多副本反亲和性规则
- PodDisruptionBudget配置
- HPA指标与阈值验证
- 降级链路就绪测试
- 故障转移时间测试
1.2 部署验证
#!/bin/bash
# deploy-check.sh - 部署验证脚本
set -e
echo "=== 部署验证开始 ==="
# 1. 检查Pod状态
echo "[1/5] 检查Pod状态..."
kubectl get pods -n inference -l app=vllm-llama-70b
kubectl wait --for=condition=ready pod -l app=vllm-llama-70b -n inference --timeout=300s
# 2. 检查服务暴露
echo "[2/5] 检查服务暴露..."
kubectl get svc -n inference vllm-service
curl -s http://vllm-service.inference.svc.cluster.local:8000/health | grep -q "ok" && echo "健康检查通过"
# 3. 检查GPU资源
echo "[3/5] 检查GPU资源..."
kubectl top pod -n inference -l app=vllm-llama-70b
kubectl exec -n inference deploy/vllm-llama-70b -- nvidia-smi
# 4. 基础功能测试
echo "[4/5] 基础功能测试..."
curl -s -X POST http://vllm-service.inference.svc.cluster.local:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{"model": "llama-3.1-70b", "prompt": "Hello", "max_tokens": 10}' | jq .
# 5. 性能基线测试
echo "[5/5] 性能基线测试..."
python benchmark.py --endpoint http://vllm-service.inference.svc.cluster.local:8000
echo "=== 部署验证完成 ==="
二、故障演练方案
2.1 演练1:GPU OOM场景
目标:验证系统在显存不足时的行为
# 步骤1:注入高并发负载
echo "注入高并发负载..."
locust -f load_test.py \
--host http://vllm-service:8000 \
-u 100 -r 10 \
--run-time 5m &
# 步骤2:监控OOM事件
echo "监控OOM事件..."
kubectl logs -f deployment/vllm-llama-70b -n inference | grep -i "out of memory" &
# 步骤3:观察HPA扩容
echo "观察HPA扩容..."
watch kubectl get hpa vllm-hpa -n inference
# 预期行为验证
echo "=== 验证点 ==="
echo "1. vLLM返回507错误码(或队列等待)"
echo "2. HPA在60秒内触发扩容"
echo "3. 新Pod就绪后流量自动切换"
echo "4. 超限请求路由到降级链路"
echo "5. 告警触发(PagerDuty/钉钉/飞书)"
2.2 演练2:模型文件损坏
目标:验证模型加载失败的恢复能力
# 步骤1:模拟模型损坏
kubectl exec -n inference deploy/vllm-llama-70b -- \
rm /models/model.safetensors.index.json
# 步骤2:触发滚动更新
kubectl rollout restart deployment/vllm-llama-70b -n inference
# 步骤3:观察恢复过程
echo "=== 验证点 ==="
echo "1. 新Pod模型加载失败,Readiness探针失败"
echo "2. 流量切换到其他健康副本"
echo "3. 备用模型自动加载(如配置)"
echo "4. 告警通知运维人员"
echo "5. 服务整体可用性不受影响"
2.3 演练3:网络分区
目标:验证分布式TP在节点间通信失败时的行为
# 步骤1:模拟跨节点网络故障
# 使用NetworkPolicy隔离Pod间通信
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: network-partition
namespace: inference
spec:
podSelector:
matchLabels:
app: vllm-llama-70b
policyTypes:
- Ingress
- Egress
ingress: []
egress: []
EOF
# 步骤2:观察故障检测
echo "=== 验证点 ==="
echo "1. TP组内通信失败,NCCL报错"
echo "2. Pod进入CrashLoopBackOff"
echo "3. 降级链路接管流量"
echo "4. 告警触发"
# 清理
kubectl delete networkpolicy network-partition -n inference
2.4 演练4:节点故障
目标:验证节点宕机时的服务连续性
# 步骤1:标记节点为不可调度
NODE=$(kubectl get pods -n inference -l app=vllm-llama-70b -o jsonpath='{.items[0].spec.nodeName}')
kubectl cordon $NODE
# 步骤2:删除该节点上的Pod(模拟节点故障)
kubectl delete pod -n inference -l app=vllm-llama-70b --field-selector spec.nodeName=$NODE
# 步骤3:观察Pod重新调度
echo "=== 验证点 ==="
echo "1. Pod被驱逐,进入Terminating状态"
echo "2. 新Pod调度到其他可用节点"
echo "3. 服务在30秒内恢复"
echo "4. 无数据丢失"
# 恢复节点
kubectl uncordon $NODE
三、性能调优指南
3.1 性能基线测试
#!/usr/bin/env python3
"""
benchmark.py - 性能基线测试脚本
"""
import asyncio
import time
import statistics
from openai import AsyncOpenAI
import argparse
class LLMBenchmark:
def __init__(self, endpoint: str):
self.client = AsyncOpenAI(
base_url=f"{endpoint}/v1",
api_key="dummy"
)
async def benchmark_ttft(self, prompt: str, max_tokens: int = 100) -> float:
"""测试Time To First Token"""
start = time.time()
stream = await self.client.completions.create(
model="llama-3.1-70b",
prompt=prompt,
max_tokens=max_tokens,
stream=True
)
first_token_time = None
async for chunk in stream:
if first_token_time is None:
first_token_time = time.time()
break
return first_token_time - start
async def benchmark_throughput(self, concurrency: int, requests: int) -> dict:
"""测试并发吞吐"""
prompts = ["Explain quantum computing in simple terms."] * requests
async def single_request(prompt: str) -> float:
start = time.time()
response = await self.client.completions.create(
model="llama-3.1-70b",
prompt=prompt,
max_tokens=100
)
elapsed = time.time() - start
tokens = len(response.choices[0].text.split())
return tokens / elapsed
semaphore = asyncio.Semaphore(concurrency)
async def bounded_request(prompt: str):
async with semaphore:
return await single_request(prompt)
results = await asyncio.gather(*[bounded_request(p) for p in prompts])
return {
"avg_tps": statistics.mean(results),
"p50": statistics.median(results),
"p99": sorted(results)[int(len(results) * 0.99)]
}
async def run_all(self):
"""运行全部测试"""
print("=== LLM性能基线测试 ===\n")
# TTFT测试
print("[1/3] TTFT测试...")
ttft_results = []
for i in range(10):
ttft = await self.benchmark_ttft("Hello, how are you?")
ttft_results.append(ttft)
print(f" 请求{i+1}: {ttft*1000:.1f}ms")
print(f" P50 TTFT: {statistics.median(ttft_results)*1000:.1f}ms")
print(f" P99 TTFT: {sorted(ttft_results)[9]*1000:.1f}ms\n")
# 吞吐测试(低并发)
print("[2/3] 吞吐测试(并发=10)...")
result = await self.benchmark_throughput(concurrency=10, requests=50)
print(f" 平均吞吐: {result['avg_tps']:.1f} tokens/s")
print(f" P50吞吐: {result['p50']:.1f} tokens/s")
print(f" P99吞吐: {result['p99']:.1f} tokens/s\n")
# 吞吐测试(高并发)
print("[3/3] 吞吐测试(并发=50)...")
result = await self.benchmark_throughput(concurrency=50, requests=100)
print(f" 平均吞吐: {result['avg_tps']:.1f} tokens/s")
print(f" P50吞吐: {result['p50']:.1f} tokens/s")
print(f" P99吞吐: {result['p99']:.1f} tokens/s\n")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--endpoint", default="http://localhost:8000")
args = parser.parse_args()
benchmark = LLMBenchmark(args.endpoint)
asyncio.run(benchmark.run_all())
3.2 性能基线参考
| 模型 | 硬件 | TTFT P50 | TTFT P99 | 吞吐 | GPU利用率 |
|---|---|---|---|---|---|
| Llama-3.1-70B-AWQ | 4x A100 | 80ms | 150ms | 800 tok/s | 85% |
| Llama-3.1-70B-GPTQ | 4x A100 | 70ms | 130ms | 950 tok/s | 90% |
| Llama-3.1-70B-GGUF | 64C CPU | 500ms | 2000ms | 50 tok/s | N/A |
3.3 常见问题与优化
问题1:TTFT过高
症状:用户等待时间过长
排查:
# 检查队列长度
curl -s http://localhost:8000/metrics | grep vllm_request_queue_length
# 检查batch大小
curl -s http://localhost:8000/metrics | grep vllm_batch_size
解决方案:
# 限制batch大小
--max-num-batched-tokens 4096
# 启用优先级调度
--scheduling-policy priority
# 增加GPU数量或降低并发
问题2:ITL不稳定
症状:流式输出卡顿
排查:
# 检查ITL分布
curl -s http://localhost:8000/metrics | grep vllm_time_per_output_token
解决方案:
# 限制并发序列数
--max-num-seqs 128
# 启用 chunked prefill
--enable-chunked-prefill
问题3:GPU利用率低
症状:GPU空闲时间多
排查:
nvidia-smi dmon -s u
解决方案:
# 增加batch大小
--max-num-batched-tokens 16384
# 启用前缀缓存
--enable-prefix-caching
# 调整gpu-memory-utilization
--gpu-memory-utilization 0.90
问题4:显存OOM
症状:服务崩溃
排查:
# 检查KV Cache使用率
curl -s http://localhost:8000/metrics | grep vllm_gpu_cache_usage
解决方案:
# 降低显存利用率上限
--gpu-memory-utilization 0.80
# 减少最大上下文长度
--max-model-len 16384
# 使用量化模型
--quantization awq
四、日志与追踪
4.1 结构化日志配置
# vLLM日志配置
import logging
import json
class JSONFormatter(logging.Formatter):
def format(self, record):
return json.dumps({
"timestamp": self.formatTime(record),
"level": record.levelname,
"message": record.getMessage(),
"module": record.module,
"request_id": getattr(record, "request_id", None)
})
# 配置vLLM日志
logging.basicConfig(
level=logging.INFO,
handlers=[
logging.StreamHandler()
]
)
logging.getLogger().handlers[0].setFormatter(JSONFormatter())
4.2 分布式追踪
# OpenTelemetry配置
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
trace.set_tracer_provider(TracerProvider())
otlp_exporter = OTLPSpanExporter(endpoint="otel-collector:4317")
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(otlp_exporter)
)
tracer = trace.get_tracer(__name__)
# 在请求处理中添加追踪
@tracer.start_as_current_span("llm_inference")
async def handle_request(request):
with tracer.start_as_current_span("prefill"):
# prefill逻辑
pass
with tracer.start_as_current_span("decode"):
# decode逻辑
pass
五、安全与合规
5.1 访问控制
# 网络策略
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: vllm-network-policy
namespace: inference
spec:
podSelector:
matchLabels:
app: vllm-llama-70b
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: api-gateway
ports:
- protocol: TCP
port: 8000
5.2 速率限制
# Kong速率限制
plugins:
- name: rate-limiting
config:
minute: 100
policy: redis
redis_host: redis
5.3 内容审核
# 输入/输出审核
class ContentModerator:
def check_input(self, prompt: str) -> bool:
# 检查敏感词、Prompt注入等
pass
def check_output(self, text: str) -> bool:
# 检查有害内容
pass
六、系列总结
经过六篇文章的学习,我们系统性地掌握了大模型推理框架的方方面面:
| 文章 | 核心内容 | 收获 |
|---|---|---|
| 01 | 框架概览与选型 | 建立清晰的框架认知 |
| 02 | 量化技术 | 理解GGUF/AWQ/GPTQ的差异 |
| 03 | KV Cache与批处理 | 掌握内存优化核心技术 |
| 04 | 分布式推理 | 学会TP/PP/EP并行策略 |
| 05 | 生产架构 | 设计高可用的混合架构 |
| 06 | 故障排查 | 具备生产运维能力 |
核心原则回顾
- 从简单开始:先用Ollama验证模型,再迁移到生产框架
- 量化是必选项:70B+模型必须量化才能经济部署
- 内存是关键:PagedAttention让显存利用率从60%提升到95%
- 分布式要谨慎:TP限制在单节点,跨节点用PP
- 降级是保险:永远保留一条CPU兜底链路
- 监控驱动优化:基于指标调参,而非猜测
参考资源
文章标签
故障排查 性能调优 生产运维 监控告警 基准测试 故障演练 安全检查 最佳实践
更多推荐

所有评论(0)