三种大模型推理服务框架的选型对比

描述

 

一、问题背景

大模型推理框架的选型直接影响服务稳定性、推理成本和运维复杂度。SGLang、vLLM 和 TensorRT-LLM 是当前主流的三种推理服务框架,各有侧重:

  • vLLM:Python 实现,PagedAttention 显存管理,易部署,社区活跃
  • SGLang:基于 vLLM,增加 RadixAttention 缓存机制,适合结构化输出场景
  • TensorRT-LLM:NVIDIA 官方推理引擎,C++/CUDA 深度优化,支持多 GPU 推理

选型时不能只看 benchmark 数据,还要结合运维实际场景:服务可用性、故障恢复能力、监控可观测性、资源利用率和扩展性。本文从运维视角出发,提出五个核心问题,帮助你在选型前做出更准确的判断。

二、适用场景

本文适用于以下角色和场景:

  • 负责大模型推理服务部署的运维工程师
  • 负责评估推理框架性能和成本的 DevOps 工程师
  • 需要在生产环境落地 LLM 推理服务的系统管理员
  • 负责推理服务监控告警和故障排查的 SRE
  • 需要优化推理成本和资源利用率的架构师

涉及的技术栈:

  • NVIDIA GPU 服务器(A100、A800、H100、H800、4090、L4、L40S)
  • Docker / Kubernetes 容器化部署
  • Prometheus + Grafana 监控体系
  • Python 环境管理
  • CUDA / cuDNN 驱动依赖
  • HTTP / gRPC 推理服务接口
  • 负载均衡与流量调度
  • 日志聚合与指标采集

三、核心知识点

3.1 推理框架架构差异

vLLM 架构特点

  • Python 实现,易读易改
  • PagedAttention:将 KV Cache 切分为固定大小的块,减少显存碎片
  • 动态批处理:continuous batching,请求到达即处理
  • 支持张量并行和流水线并行
  • 无缓存机制,每次推理都重新计算 prefix

SGLang 架构特点

  • 基于 vLLM,保留 PagedAttention
  • 增加 RadixAttention:对 prompt prefix 进行缓存,多轮对话或结构化输出时减少重复计算
  • 内置约束解码能力,支持 JSON schema、正则表达式约束
  • 适合 prompt 复用率高的场景,如 Agent、Function Calling、批量结构化输出

TensorRT-LLM 架构特点

  • NVIDIA 官方推理引擎,C++ 实现
  • 深度集成 CUDA kernel 和 TensorRT 优化
  • 支持量化(INT8/FP8/INT4)、Inflight Batching、多 GPU Tensor Parallel
  • 模型需要编译为 TensorRT 引擎,部署流程复杂
  • 适合高吞吐、低延迟、多卡推理场景

3.2 显存管理机制

框架 显存管理方式 KV Cache 策略 碎片率
vLLM PagedAttention 动态分配,块管理
SGLang PagedAttention + RadixCache prefix 缓存复用
TensorRT-LLM Inflight Batching 固定预分配

3.3 推理性能影响因素

  • 并发处理能力:continuous batching vs static batching
  • 显存占用:KV Cache 占比、模型权重占比、临时变量占比
  • 吞吐量:每秒处理的 token 数(tokens/s)
  • 延迟:首 token 延迟(TTFT)、单 token 延迟、端到端延迟
  • 缓存命中率:prefix 缓存命中率直接影响重复场景性能

3.4 运维关注点

  • 服务启动时间:模型加载时间、引擎编译时间
  • 进程稳定性:OOM 风险、CUDA 错误恢复能力、异常请求处理
  • 监控指标暴露:Prometheus metrics、自定义指标、日志结构化
  • 故障恢复能力:进程重启后状态恢复、缓存重建时间、流量切换影响
  • 扩缩容能力:横向扩展、多副本部署、流量均衡

四、选型五问

问题一:服务崩溃后,流量切换需要多久?

4.1.1 问题场景

推理服务进程因 OOM、CUDA 错误、异常请求、依赖故障等原因崩溃。Kubernetes 或 Docker 自动重启容器,但模型加载需要时间。这段时间内:

  • 流量是否会打到未就绪的实例?
  • 健康检查能否及时发现服务不可用?
  • 负载均衡是否能快速摘除故障节点?
  • 缓存(如 SGLang RadixCache)重建需要多久?

4.1.2 各框架表现

vLLM

模型加载时间取决于模型大小和磁盘 I/O:

  • 13B 模型约 10-20 秒
  • 70B 模型约 30-60 秒
  • 加载完成后即可接受请求
  • 无缓存预热阶段

启动流程:


			   bashpython -m vllm.entrypoints.openai.api_server    --model /models/Llama-2-13b-chat-hf    --host 0.0.0.0    --port 8000    --gpu-memory-utilization 0.9    --max-model-len 4096 

观察启动日志:


			   INFO:     Started server process INFO:     Waiting for application startup. INFO:     Loading model weights... INFO:     Model loaded successfully INFO:     Application startup complete. INFO:     Uvicorn running on http://0.0.0.0:8000 

关键时间点:Model loaded successfully 到 Application startup complete 之间的时间。

健康检查配置:


			   yamllivenessProbe:   httpGet:     path: /health     port: 8000   initialDelaySeconds: 60   periodSeconds: 10   timeoutSeconds: 5   failureThreshold: 3 readinessProbe:   httpGet:     path: /v1/models     port: 8000   initialDelaySeconds: 30   periodSeconds: 5   timeoutSeconds: 3   failureThreshold: 2 

四、选型五问

问题一:服务崩溃后,流量切换需要多久?

4.1.1 问题场景

推理服务进程因 OOM、CUDA 错误、异常请求、依赖故障等原因崩溃。Kubernetes 或 Docker 自动重启容器,但模型加载需要时间。这段时间内:

  • 流量是否会打到未就绪的实例?
  • 健康检查能否及时发现服务不可用?
  • 负载均衡是否能快速摘除故障节点?
  • 缓存(如 SGLang RadixCache)重建需要多久?

4.1.2 各框架表现

vLLM

模型加载时间取决于模型大小和磁盘 I/O:

  • 13B 模型约 10-20 秒
  • 70B 模型约 30-60 秒
  • 加载完成后即可接受请求
  • 无缓存预热阶段

启动流程:


			   bash# vLLM 启动命令 python -m vllm.entrypoints.openai.api_server    --model /models/Llama-2-13b-chat-hf    --host 0.0.0.0    --port 8000    --gpu-memory-utilization 0.9    --max-model-len 4096 

观察启动日志:


			   INFO:     Started server process INFO:     Waiting for application startup. INFO:     Loading model weights... INFO:     Model loaded successfully INFO:     Application startup complete. INFO:     Uvicorn running on http://0.0.0.0:8000 

关键时间点:Model loaded successfully 到 Application startup complete 之间的时间。

健康检查配置:


			   yamllivenessProbe:   httpGet:     path: /health     port: 8000   initialDelaySeconds: 60   periodSeconds: 10   timeoutSeconds: 5   failureThreshold: 3 readinessProbe:   httpGet:     path: /v1/models     port: 8000   initialDelaySeconds: 30   periodSeconds: 5   timeoutSeconds: 3   failureThreshold: 2 

SGLang

除模型加载外,还需考虑 RadixCache 预热:

  • 冷启动时缓存为空
  • 缓存命中率从 0% 逐步上升
  • 缓存未命中时性能接近 vLLM
  • 缓存填充速度取决于请求量和 prefix 分布

启动命令:


			   bashpython -m sglang.launch_server    --model-path /models/Llama-2-13b-chat-hf    --host 0.0.0.0    --port 8000    --mem-fraction-static 0.8    --max-running-requests 256 

观察缓存状态:


			   bash# SGLang 提供的缓存统计接口 curl http://localhost:8000/get_server_info | jq .cache_info 

输出示例:


			   json{   "cache_hit_rate": 0.0,   "cache_total_tokens": 0,   "cache_used_tokens": 0 } 

服务重启后,cache_hit_rate 从 0 开始,需要一定时间恢复到稳态。

TensorRT-LLM

启动流程最复杂:

  1. 模型编译(首次部署)
  2. 加载编译后的引擎文件
  3. 初始化多 GPU 通信(如使用 Tensor Parallel)
  4. 预分配显存

编译时间(首次):

  • 13B 模型:5-15 分钟
  • 70B 模型:20-40 分钟

加载时间(已编译):

  • 13B 模型:20-40 秒
  • 70B 模型:60-120 秒

启动命令:


			   bash# 使用 Triton Inference Server docker run --gpus all --rm -it    -v /models:/models    -p 8000:8000 -p 8001:8001 -p 8002:8002    nvcr.io/nvidia/tritonserver:24.01-trtllm-python-py3    tritonserver --model-repository=/models/trtllm_repo 

观察就绪状态:


			   bash# Triton 健康检查 curl -v http://localhost:8000/v2/health/ready 

返回 200 表示就绪。

健康检查配置:


			   yamlreadinessProbe:   httpGet:     path: /v2/health/ready     port: 8000   initialDelaySeconds: 120  # TensorRT-LLM 需要更长启动时间   periodSeconds: 10   timeoutSeconds: 5   failureThreshold: 3 

4.1.3 实测对比

框架 模型大小 冷启动时间 热重启时间 缓存恢复时间
vLLM 13B 15s 15s
vLLM 70B 45s 45s
SGLang 13B 15s 15s 2-5 分钟
SGLang 70B 45s 45s 5-10 分钟
TensorRT-LLM 13B 30s 30s
TensorRT-LLM 70B 90s 90s

4.1.4 运维建议

vLLM

  • 启动快,适合频繁扩缩容
  • initialDelaySeconds 设置为模型加载时间 + 10 秒
  • 使用 /health 做 liveness,/v1/models 做 readiness

SGLang

  • 缓存恢复期间性能下降,需要预留更长的流量预热时间
  • 考虑使用多副本,避免所有实例同时重启
  • 监控缓存命中率,低于阈值时触发告警
  • 缓存恢复期间可能出现延迟毛刺,需要调整超时配置

TensorRT-LLM

  • 启动慢,不适合频繁扩缩容
  • 建议使用固定副本数 + 自动扩容策略,避免频繁重启
  • initialDelaySeconds 至少设置为 120 秒
  • 多 GPU 场景下,需要确保 NCCL 通信正常SGLang

除模型加载外,还需考虑 RadixCache 预热:

  • 冷启动时缓存为空
  • 缓存命中率从 0% 逐步上升
  • 缓存未命中时性能接近 vLLM
  • 缓存填充速度取决于请求量和 prefix 分布

启动命令:


			   bashpython -m sglang.launch_server    --model-path /models/Llama-2-13b-chat-hf    --host 0.0.0.0    --port 8000    --mem-fraction-static 0.8    --max-running-requests 256 

观察缓存状态:


			   bashcurl http://localhost:8000/get_server_info | jq .cache_info 

输出示例:


			   json{   "cache_hit_rate": 0.0,   "cache_total_tokens": 0,   "cache_used_tokens": 0 } 

服务重启后,cache_hit_rate 从 0 开始,需要一定时间恢复到稳态。

SGLang

除模型加载外,还需考虑 RadixCache 预热:

  • 冷启动时缓存为空
  • 缓存命中率从 0% 逐步上升
  • 缓存未命中时性能接近 vLLM
  • 缓存填充速度取决于请求量和 prefix 分布

启动命令:


			   bashpython -m sglang.launch_server    --model-path /models/Llama-2-13b-chat-hf    --host 0.0.0.0    --port 8000    --mem-fraction-static 0.8    --max-running-requests 256 

观察缓存状态:


			   bashcurl http://localhost:8000/get_server_info | jq .cache_info 

输出示例:


			   json{   "cache_hit_rate": 0.0,   "cache_total_tokens": 0,   "cache_used_tokens": 0 } 

服务重启后,cache_hit_rate 从 0 开始,需要一定时间恢复到稳态。

TensorRT-LLM

启动流程最复杂:

  1. 模型编译(首次部署)
  2. 加载编译后的引擎文件
  3. 初始化多 GPU 通信(如使用 Tensor Parallel)
  4. 预分配显存

编译时间(首次):

  • 13B 模型:5-15 分钟
  • 70B 模型:20-40 分钟

加载时间(已编译):

  • 13B 模型:20-40 秒
  • 70B 模型:60-120 秒

启动命令:


			   bashdocker run --gpus all --rm -it    -v /models:/models    -p 8000:8000 -p 8001:8001 -p 8002:8002    nvcr.io/nvidia/tritonserver:24.01-trtllm-python-py3    tritonserver --model-repository=/models/trtllm_repo 

观察就绪状态:


			   bashcurl -v http://localhost:8000/v2/health/ready 

返回 200 表示就绪。

健康检查配置:


			   yamlreadinessProbe:   httpGet:     path: /v2/health/ready     port: 8000   initialDelaySeconds: 120   periodSeconds: 10   timeoutSeconds: 5   failureThreshold: 3 

4.1.3 实测对比

框架 模型大小 冷启动时间 热重启时间 缓存恢复时间
vLLM 13B 15s 15s
vLLM 70B 45s 45s
SGLang 13B 15s 15s 2-5 分钟
SGLang 70B 45s 45s 5-10 分钟
TensorRT-LLM 13B 30s 30s
TensorRT-LLM 70B 90s 90s

实测环境:

  • GPU: NVIDIA A100 80GB
  • 磁盘: NVMe SSD
  • 网络: 10Gbps
  • Kubernetes: v1.28

测试方法:


			   bash# 记录启动时间 time kubectl rollout restart deployment/vllm -n inference # 观察 Pod 状态变化 kubectl get pods -n inference -w # 记录从 Terminating 到 Running 的时间 

观察要点:

  1. Terminating → Pending:Pod 被删除,等待调度
  2. Pending → ContainerCreating:调度完成,拉取镜像
  3. ContainerCreating → Running:容器启动,模型加载
  4. Running → Ready:健康检查通过,开始接收流量

4.1.4 运维建议

vLLM

  • 启动快,适合频繁扩缩容
  • initialDelaySeconds 设置为模型加载时间 + 10 秒
  • 使用 /health 做 liveness,/v1/models 做 readiness
  • 配置示例:

			   yamllivenessProbe:   httpGet:     path: /health     port: 8000   initialDelaySeconds: 60   periodSeconds: 10   timeoutSeconds: 5   failureThreshold: 3 readinessProbe:   httpGet:     path: /v1/models     port: 8000   initialDelaySeconds: 30   periodSeconds: 5   timeoutSeconds: 3   failureThreshold: 2 

SGLang

  • 缓存恢复期间性能下降,需要预留更长的流量预热时间
  • 考虑使用多副本,避免所有实例同时重启
  • 监控缓存命中率,低于阈值时触发告警
  • 缓存恢复期间可能出现延迟毛刺,需要调整超时配置
  • 配置示例:

			   yamlreadinessProbe:   httpGet:     path: /health     port: 8000   initialDelaySeconds: 45   periodSeconds: 5   successThreshold: 2 

使用 successThreshold: 2 确保服务稳定后再接入流量。

TensorRT-LLM

  • 启动慢,不适合频繁扩缩容
  • 建议使用固定副本数 + 自动扩容策略,避免频繁重启
  • initialDelaySeconds 至少设置为 120 秒
  • 多 GPU 场景下,需要确保 NCCL 通信正常
  • 配置示例:

			   yamlreadinessProbe:   httpGet:     path: /v2/health/ready     port: 8000   initialDelaySeconds: 120   periodSeconds: 10   timeoutSeconds: 5   failureThreshold: 3 

4.1.5 故障切换验证

模拟服务崩溃:


			   bash# 找到推理服务进程 ps aux | grep vllm # 强制杀死进程 kill -9  

观察 Kubernetes 重启行为:


			   bash# 实时查看 Pod 状态 kubectl get pods -w -n inference # 查看事件 kubectl describe pod  -n inference 

观察流量切换:


			   bash# 查看 Service Endpoints kubectl get endpoints  -n inference -o yaml # 持续发送请求,观察错误率 while truedo   curl -s -o /dev/null -w "%{http_code} " http://:8000/v1/models   sleep 1 done 

正常情况下:

  • Pod 重启后,Not Ready 状态持续时间应等于 initialDelaySeconds + 首次健康检查成功时间
  • Endpoints 中的 IP 应在健康检查失败后被移除
  • 流量应在健康检查恢复后重新路由

异常情况:

  • initialDelaySeconds 过短:流量打到未就绪实例,返回 502/503
  • failureThreshold 过大:故障节点摘除时间过长
  • 缓存恢复期间:延迟升高,超时请求增加

验证脚本:


			   bash#!/bin/bash POD_NAME="vllm-7d6f4b8c9-xyz" NAMESPACE="inference" SERVICE_NAME="vllm-service" echo "Killing pod $POD_NAME..." kubectl delete pod "$POD_NAME" -n "$NAMESPACE" & sleep 5 echo "Monitoring endpoint changes..." kubectl get endpoints "$SERVICE_NAME" -n "$NAMESPACE" -w & echo "Sending test requests..." while truedo   RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" http://vllm-service.inference.svc.cluster.local:8000/v1/models)   TIMESTAMP=$(date +%T)   echo "$TIMESTAMP$RESPONSE"   if [ "$RESPONSE" != "200" ]; then     echo "  ERROR DETECTED"   fi   sleep 1 done 

预期输出:


			   1030: 200 1031: 200 1032: 503  ERROR DETECTED 1033: 503  ERROR DETECTED 1034: 503  ERROR DETECTED ... 1015: 200 1016: 200 

503 错误持续时间应在 30-60 秒之间(取决于 initialDelaySeconds 配置)。

4.1.6 健康检查最佳实践

分离 Liveness 和 Readiness

  • Liveness 检查进程是否存活,失败时 Kubernetes 重启容器
  • Readiness 检查服务是否就绪,失败时从 Endpoints 中移除

vLLM 配置示例:


			   yamllivenessProbe:   httpGet:     path: /health     port: 8000   initialDelaySeconds: 60   periodSeconds: 10   timeoutSeconds: 5   failureThreshold: 3 readinessProbe:   httpGet:     path: /v1/models     port: 8000   initialDelaySeconds: 30   periodSeconds: 5   timeoutSeconds: 3   failureThreshold: 2 

区别:

  • Liveness 的 failureThreshold 更大,避免频繁重启
  • Readiness 的 periodSeconds 更短,更快发现服务不可用
  • Liveness 的 initialDelaySeconds 更长,给模型加载足够时间

自定义健康检查脚本

对于复杂场景,可以编写自定义健康检查:


			   bash#!/bin/bash check_gpu() {   nvidia-smi > /dev/null 2>&1   return $? } check_model_loaded() {   curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/v1/models | grep -q "200"   return $? } check_memory() {   used=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits)   total=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits)   ratio=$(echo "scale=2; $used / $total" | bc)   if (( $(echo "$ratio > 0.98" | bc -l) )); then     return 1   fi   return 0 } check_cache_hit_rate() {   hit_rate=$(curl -s http://localhost:8000/get_server_info | jq -r '.cache_info.cache_hit_rate // 0')   uptime=$(awk '{print int($1)}' /proc/uptime)   if [ "$uptime" -gt 300 ] && (( $(echo "$hit_rate < 0.2" | bc -l) )); then     echo "Cache hit rate too low: $hit_rate"     return 1   fi   return 0 } if ! check_gpu; then   echo "GPU check failed"   exit 1 fi if ! check_model_loaded; then   echo "Model not loaded"   exit 1 fi if ! check_memory; then   echo "Memory usage too high"   exit 1 fi echo "Health check passed" exit 0 

Kubernetes 中使用:


			   yamllivenessProbe:   exec:     command:       - /scripts/health_check.sh   initialDelaySeconds: 60   periodSeconds: 10   timeoutSeconds: 10   failureThreshold: 3 

注意:

  • 脚本执行时间要短(< 5秒),否则会超时
  • 避免在健康检查中执行复杂计算
  • 健康检查失败时,输出明确的错误信息

4.1.7 优雅关闭

推理服务停止时,需要确保正在处理的请求完成,而不是直接杀死进程。

vLLM 支持 SIGTERM 信号进行优雅关闭:


			   bash# 发送 SIGTERM 信号 kill -TERM  # 观察日志 tail -f /var/log/vllm/server.log 

Kubernetes 中配置优雅关闭时间:


			   yamlspec:   terminationGracePeriodSeconds: 60   containers:     - name: vllm       lifecycle:         preStop:           exec:             command: ["/bin/sh""-c""sleep 10"] 

这样在 Pod 终止前,会先等待 10 秒让服务自行清理,然后再发送 SIGTERM。

观察优雅关闭日志:


			   bashdocker logs  --tail 50 -f 

应该看到类似输出:


			   INFO: Shutting down INFO: Waiting for application shutdown. INFO: Draining 5 in-flight requests... INFO: Request 1/5 completed INFO: Request 2/5 completed INFO: Request 3/5 completed INFO: Request 4/5 completed INFO: Request 5/5 completed INFO: Application shutdown complete. INFO: Finished server process 

优雅关闭时间线:

  1. 收到 SIGTERM 信号
  2. 停止接受新请求
  3. 等待正在处理的请求完成
  4. 清理资源(关闭数据库连接、保存缓存等)
  5. 退出进程

如果超过 terminationGracePeriodSeconds,Kubernetes 会发送 SIGKILL 强制杀死进程。

验证优雅关闭:


			   bash#!/bin/bash POD_NAME="vllm-7d6f4b8c9-xyz" NAMESPACE="inference" echo "Sending requests in background..." for i in {1..10}; do   curl -X POST http://vllm-service.inference.svc.cluster.local:8000/v1/completions      -H "Content-Type: application/json"      -d '{"model":"llama2-13b","prompt":"长文本生成测试","max_tokens":1024}' & done sleep 5 echo "Deleting pod..." kubectl delete pod "$POD_NAME" -n "$NAMESPACE" echo "Waiting for requests to complete..." wait echo "All requests completed" 

如果优雅关闭生效,所有请求应该成功返回结果。

4.1.8 启动依赖检查

服务启动前,检查依赖是否满足:


			   bash#!/bin/bash set -e echo "=== Pre-start Checks ===" echo "Checking CUDA environment..." if ! nvidia-smi > /dev/null 2>&1; then   echo "ERROR: nvidia-smi not found or GPU not available"   exit 1 fi gpu_count=$(nvidia-smi --list-gpus | wc -l) echo "Found $gpu_count GPU(s)" echo "Checking model files..." if [ ! -d "/models/Llama-2-13b-chat-hf" ]; then   echo "ERROR: Model directory not found"   exit 1 fi required_files=("config.json" "pytorch_model.bin" "tokenizer.json" "tokenizer_config.json") for file in "${required_files[@]}"do   if [ ! -f "/models/Llama-2-13b-chat-hf/$file" ]; then     echo "ERROR: Required file $file not found"     exit 1   fi done echo "Model files OK" echo "Checking disk space..." available=$(df -BG /models | tail -1 | awk '{print $4}' | sed 's/G//') if [ "$available" -lt 50 ]; then   echo "WARNING: Low disk space: ${available}GB" fi echo "Checking GPU memory..." free_mem=$(nvidia-smi --query-gpu=memory.free --format=csv,noheader,nounits | head -1) if [ "$free_mem" -lt 20000 ]; then   echo "ERROR: Insufficient GPU memory: ${free_mem}MB (need at least 20000MB)"   exit 1 fi echo "GPU memory OK: ${free_mem}MB available" echo "Checking GPU driver version..." driver_version=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader | head -1) echo "Driver version: $driver_version" if [[ "$driver_version" < "530.30.02" ]]; then   echo "WARNING: Driver version may not support CUDA 12.1" fi echo "Checking CUDA version..." cuda_version=$(nvcc --version | grep release | awk '{print $5}' | sed 's/,//') echo "CUDA version: $cuda_version" echo "Checking Python environment..." if ! python -c "import torch; print(torch.cuda.is_available())" | grep -q "True"then   echo "ERROR: PyTorch CUDA not available"   exit 1 fi echo "PyTorch CUDA OK" echo "Checking vLLM installation..." if ! python -c "import vllm" 2>/dev/null; then   echo "ERROR: vLLM not installed"   exit 1 fi echo "vLLM installed OK" echo "=== All checks passed ===" echo "Starting vLLM server..." exec python -m vllm.entrypoints.openai.api_server    --model /models/Llama-2-13b-chat-hf    --host 0.0.0.0    --port 8000    --gpu-memory-utilization 0.9    --max-model-len 4096 

将此脚本作为容器启动命令:


			   yamlcontainers:   - name: vllm     image: vllm/vllm-openai:latest     command: ["/scripts/start_with_checks.sh"]     volumeMounts:       - name: models         mountPath: /models       - name: scripts         mountPath: /scripts 

这样可以在启动失败时快速定位原因。

问题二:显存不足时,服务是直接 OOM 还是可以优雅降级?

4.2.1 问题场景

推理服务运行过程中,显存占用受以下因素影响:

  • 模型权重大小(固定)
  • KV Cache 大小(动态,取决于并发请求数和序列长度)
  • 临时变量(activation、中间结果)
  • 缓存(如 SGLang RadixCache)

当并发请求数增加或出现超长序列时,显存可能耗尽。此时服务可能:

  • 直接 OOM,进程崩溃
  • 拒绝新请求,返回 429 或 503
  • 排队等待,增加延迟
  • 驱逐部分 KV Cache,降低性能

4.2.2 各框架表现

vLLM

vLLM 启动时通过 --gpu-memory-utilization 参数控制显存使用上限:


			   bashpython -m vllm.entrypoints.openai.api_server    --model /models/Llama-2-13b-chat-hf    --gpu-memory-utilization 0.9 
  • 默认值 0.9,保留 10% 显存用于临时变量
  • vLLM 会根据剩余显存动态调整 KV Cache 块数量
  • 当 KV Cache 满时,拒绝新请求,返回 503

观察显存使用:


			   bashnvidia-smi --query-gpu=index,name,memory.used,memory.total --format=csv -l 1 curl http://localhost:8000/metrics | grep vllm_gpu_cache 

关键指标:


			   vllm_gpu_cache_usage_perc{gpu="0"} 0.85 vllm_num_requests_running 12 vllm_num_requests_waiting 3 

当 vllm_gpu_cache_usage_perc 接近 1.0 时,服务开始拒绝新请求。

拒绝请求时的日志:


			   WARNING: Request rejected due to insufficient KV cache space 

客户端收到:


			   json{   "error": {     "message": "No available slots for new requests",     "type": "service_unavailable"   } } 

4.2.3 显存使用详解

显存占用构成:


			   总显存 = 模型权重 + KV Cache + 临时变量 + 系统开销 

以 Llama-2-13B 为例(FP16):

  • 模型权重:13B × 2 bytes = 26GB
  • KV Cache:取决于 max_model_len 和 max_num_seqs
  • 临时变量:约 2-4GB
  • 系统开销:约 1GB

计算 KV Cache 大小:


			   KV Cache = 2 × num_layers × hidden_size × max_model_len × max_num_seqs × sizeof(dtype) 

对于 Llama-2-13B(40 层,5120 hidden size):


			   KV Cache = 2 × 40 × 5120 × 4096 × 32 × 2 bytes = 107GB 

这显然超过了单卡显存,所以 vLLM 会根据实际可用显存动态调整 max_num_seqs

查看 vLLM 实际分配的 KV Cache 块数:


			   bashcurl http://localhost:8000/metrics | grep vllm_num_gpu_blocks 

输出:


			   vllm_num_gpu_blocks{gpu="0"} 2048 

每个块的大小取决于 block_size 参数(默认 16)。

4.2.4 SGLang 显存管理

SGLang 的显存管理类似 vLLM,但增加了 RadixCache:


			   bashpython -m sglang.launch_server    --model-path /models/Llama-2-13b-chat-hf    --mem-fraction-static 0.8 
  • --mem-fraction-static:静态分配的显存比例(模型权重 + RadixCache)
  • 动态部分用于 KV Cache
  • 当 KV Cache 满时,可能驱逐部分 RadixCache

显存不足时的策略:

  1. 优先驱逐最久未使用的 RadixCache
  2. 仍不足时拒绝新请求

观察缓存驱逐:


			   bashcurl http://localhost:8000/get_server_info | jq .cache_info 

输出:


			   json{   "cache_hit_rate": 0.75,   "cache_eviction_count": 128,   "cache_total_tokens": 1048576,   "cache_used_tokens": 921600 } 

cache_eviction_count 增加表示发生了缓存驱逐。

4.2.5 TensorRT-LLM 显存管理

TensorRT-LLM 使用固定大小的 KV Cache 预分配:


			   json{   "max_batch_size": 32,   "max_input_len": 2048,   "max_output_len": 2048,   "max_beam_width": 1 } 
  • 启动时根据配置预分配显存
  • 运行时不会动态调整
  • 当请求数超过 max_batch_size 或序列长度超过 max_input_len 时,排队等待或拒绝

观察请求排队:


			   bashcurl http://localhost:8002/metrics | grep nv_inference_queue_duration_us 

输出:


			   nv_inference_queue_duration_us{model="llama2_13b",version="1"} 12500 

排队时间过长表示并发能力不足。

4.2.6 OOM 风险对比

框架 OOM 风险 降级策略 客户端感知 恢复时间
vLLM 拒绝新请求 503 错误 立即
SGLang 驱逐缓存 + 拒绝请求 缓存命中率下降 + 503 数分钟
TensorRT-LLM 请求排队 延迟增加 无需恢复

4.2.7 实战验证

压测场景:


			   bashecho "POST http://localhost:8000/v1/completions" | vegeta attack    -duration=60s    -rate=50/s    -body='{"model":"llama2-13b","prompt":"介绍一下北京","max_tokens":1024}'    -header="Content-Type: application/json"    | tee results.bin | vegeta report 

观察显存使用变化:


			   bashnvidia-smi dmon -s mu -c 60 

输出示例:


			   # gpu   mclk   pclk    sm   mem   enc   dec # Idx    MHz    MHz     %     %     %     %     0   1215   1410    85    92     0     0     0   1215   1410    88    95     0     0     0   1215   1410    90    98     0     0 

当 mem 列接近 100% 时,观察服务行为。

vLLM 表现

查看日志:


			   bashdocker logs  --tail 100 -f 

日志中出现:


			   WARNING: KV cache is full, rejecting new requests 

客户端收到 503 错误率上升:


			   Requests      [total, rate]         3000, 50.00 Duration      [total]               60s Latencies     [mean, 50, 95, 99]    456ms, 412ms, 892ms, 1.2s Success       [ratio]               90.5% Status Codes  [code:count]          200:2715  503:285 

SGLang 表现

类似 vLLM,但缓存驱逐后缓存命中率下降:


			   bashcurl http://localhost:8000/get_server_info | jq .cache_info.cache_hit_rate 

输出从 0.75 降到 0.42。

TensorRT-LLM 表现

请求排队,延迟增加:


			   Latencies     [mean, 50, 95, 99]    1.2s, 980ms, 2.8s, 4.5s Success       [ratio]               100% 

成功率 100%,但 P95/P99 延迟明显升高。

4.2.8 显存优化策略

调整并发数限制


			   bashpython -m vllm.entrypoints.openai.api_server    --model /models/Llama-2-13b-chat-hf    --max-num-seqs 16 

减少 --max-num-seqs 可以降低 KV Cache 占用,但会降低吞吐量。

调整序列长度限制


			   bashpython -m vllm.entrypoints.openai.api_server    --model /models/Llama-2-13b-chat-hf    --max-model-len 2048 

减少 --max-model-len 可以降低 KV Cache 占用。

使用量化模型


			   bashpython -m vllm.entrypoints.openai.api_server    --model /models/Llama-2-13b-chat-hf    --quantization awq 

AWQ/GPTQ 量化可以将模型权重从 FP16 降到 INT4,显著降低显存占用。

量化后的显存占用:

  • FP16:26GB
  • INT8:13GB
  • INT4:6.5GB

但量化会带来精度损失和额外的计算开销。

使用 Tensor Parallel

对于大模型,使用多 GPU 分摊显存:


			   bashpython -m vllm.entrypoints.openai.api_server    --model /models/Llama-2-70b-chat-hf    --tensor-parallel-size 4 

70B 模型需要约 140GB 显存,使用 4 张 A100 可以容纳。

4.2.9 运维建议

vLLM

  • 监控 vllm_gpu_cache_usage_perc,超过 0.9 时触发告警
  • 503 错误率超过 5% 时考虑扩容或限流
  • 调整 --max-model-len 限制最大序列长度
  • 调整 --max-num-seqs 限制最大并发数
  • 生产环境建议设置 --gpu-memory-utilization 0.85,留出更多缓冲

SGLang

  • 监控缓存命中率,低于阈值时告警
  • 缓存驱逐频繁时,考虑增加 --mem-fraction-static
  • 平衡 RadixCache 和 KV Cache 的显存分配
  • 缓存命中率低于 0.3 时,SGLang 的优势不明显

TensorRT-LLM

  • 监控排队时间,超过阈值时扩容
  • 调整 max_batch_size 和 max_input_len 配置
  • 避免超长序列请求
  • 预分配策略更稳定,但灵活性差

通用建议

  • 在上线前进行压测,确定系统容量上限
  • 设置合理的限流规则,避免突发流量打垮服务
  • 监控显存使用趋势,提前扩容
  • 建立显存告警机制,显存使用率超过 90% 时告警
  • 准备降级方案,如自动切换到更小的模型

问题三:如何判断当前瓶颈是显存、计算还是 I/O?

4.3.1 问题场景

推理服务性能不佳时,可能的瓶颈:

  • 显存瓶颈:KV Cache 不足,频繁拒绝请求或驱逐缓存
  • 计算瓶颈:GPU 利用率高,无法处理更多请求
  • I/O 瓶颈:模型加载慢、磁盘读写慢、网络传输慢
  • CPU 瓶颈:tokenize、decode、数据预处理慢
  • 锁竞争:多线程/多进程争抢资源

4.3.2 观察指标

GPU 指标


			   bashnvidia-smi dmon -s pucvmet -c 60 

输出:


			   # gpu   pwr gtemp mtemp    sm   mem   enc   dec  mclk  pclk # Idx     W     C     C     %     %     %     %   MHz   MHz     0   280    72     -    95    85     0     0  1215  1410 

关键列:

  • sm:GPU 计算利用率
  • mem:显存控制器利用率
  • pwr:功耗
  • gtemp:GPU 温度

显存占用


			   bashnvidia-smi --query-gpu=index,memory.used,memory.total,utilization.gpu,utilization.memory --format=csv -l 1 

进程级显存占用


			   bashnvidia-smi pmon -c 60 

CPU 利用率


			   bashtop -b -n 1 | grep python ps aux | grep python | awk '{print $3, $4, $11}' 

磁盘 I/O


			   bashiostat -x 1 

输出:


			   Device            r/s     w/s     rkB/s     wkB/s   %util nvme0n1         15.00    2.00   1920.00     64.00   8.50 

关键列:

  • r/sw/s:每秒读写次数
  • rkB/swkB/s:每秒读写 KB 数
  • %util:设备利用率

网络 I/O


			   bashiftop -i eth0 nethogs 

4.3.3 瓶颈判断逻辑

显存瓶颈

  • memory.used 接近 memory.total
  • vLLM 日志中出现 “KV cache is full”
  • 503 错误率上升
  • vllm_gpu_cache_usage_perc 接近 1.0

计算瓶颈

  • utilization.gpu 持续 > 90%
  • sm 持续 > 90%
  • 功耗接近 TDP 上限
  • 温度持续 > 80°C

I/O 瓶颈

  • 启动慢:%util 高,rkB/s 高
  • 运行时慢:网络流量高,CPU 等待 I/O

CPU 瓶颈

  • CPU 利用率 > 80%
  • GPU 利用率 < 50%
  • 大量时间花在 tokenize/decode

4.3.4 实战案例

案例一:显存瓶颈

现象:


			   bashcurl http://localhost:8000/metrics | grep vllm_gpu_cache_usage_perc vllm_gpu_cache_usage_perc{gpu="0"} 0.98 nvidia-smi --query-gpu=memory.used,memory.total --format=csv memory.used [MiB], memory.total [MiB] 79872 MiB, 81920 MiB 

日志:


			   WARNING: KV cache is full, rejecting new requests 

判断:显存不足,KV Cache 满。

解决方案:

  1. 减少 --max-num-seqs
  2. 减少 --max-model-len
  3. 增加 GPU 数量
  4. 使用量化模型

案例二:计算瓶颈

现象:


			   bashnvidia-smi dmon -s mu # gpu    sm   mem # Idx     %     %     0    95    65     0    96    68 

显存占用不高(65-68%),但 GPU 利用率持续 > 94%。

判断:计算能力不足,GPU 满负荷运行。

解决方案:

  1. 增加 GPU 数量
  2. 使用 Tensor Parallel 多卡推理
  3. 降低并发数,减少排队时间

案例三:CPU 瓶颈

现象:


			   bashtop -b -n 1 | grep python 12345 root  20  0  52.3g  12.5g  1.2g S 780.0  15.3  45:23.45 python 

CPU 利用率 780%(多核),GPU 利用率只有 40%。

判断:tokenize/decode 成为瓶颈。

解决方案:

  1. 增加 tokenizer 并发数
  2. 使用更快的 tokenizer
  3. 预处理阶段使用批处理

案例四:I/O 瓶颈

现象:


			   bashtime docker start  real    2m15.432s iostat -x 1 Device  %util nvme0n1  98.5 

判断:模型加载受磁盘 I/O 限制。

解决方案:

  1. 使用 NVMe SSD
  2. 将模型放在本地磁盘
  3. 使用 tmpfs

4.3.5 综合监控脚本


			   bash#!/bin/bash echo "=== GPU Status ===" nvidia-smi --query-gpu=index,name,memory.used,memory.total,utilization.gpu,utilization.memory,temperature.gpu,power.draw --format=csv echo "" echo "=== vLLM Metrics ===" curl -s http://localhost:8000/metrics | grep -E "vllm_gpu_cache_usage|vllm_num_requests" echo "" echo "=== CPU Usage ===" ps aux | grep python | grep vllm | awk '{print "CPU:", $3"%", "MEM:", $4"%"}' echo "" echo "=== Disk I/O ===" iostat -x | grep nvme0n1 

运行:


			   bashchmod +x monitor_inference.sh ./monitor_inference.sh 

 


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分