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 故障排查 具备生产运维能力

核心原则回顾

  1. 从简单开始:先用Ollama验证模型,再迁移到生产框架
  2. 量化是必选项:70B+模型必须量化才能经济部署
  3. 内存是关键:PagedAttention让显存利用率从60%提升到95%
  4. 分布式要谨慎:TP限制在单节点,跨节点用PP
  5. 降级是保险:永远保留一条CPU兜底链路
  6. 监控驱动优化:基于指标调参,而非猜测

参考资源


文章标签

故障排查 性能调优 生产运维 监控告警 基准测试 故障演练 安全检查 最佳实践

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐