vLLM 在 RTX 4060 上的推理性能调优全记录

一份从 "跑不起来" 到 "31.4 req/s" 的完整实战笔记

写在前面

前段时间我在学习大模型推理部署时,遇到了一个很尴尬的局面:网上教程大多基于 A100 / H100,但作为个人开发者,我手头只有一台 RTX 4060 Laptop(8GB 显存) 的笔记本。vLLM 官方文档看起来很美好,但真要自己玩起来,网络问题、CUDA 版本不兼容、flashinfer 编译失败一个都没少硬吃。

这篇文章记录了我从零开始在 RTX 4060 上部署 Qwen2.5-1.5B 推理服务的完整过程,以及最终的性能压测数据。如果你也正在消费级显卡上折腾 vLLM,希望这份记录能帮你少走一些弯路。


一、环境概览

项目 配置
显卡 NVIDIA GeForce RTX 4060 Laptop (8GB)
系统 Ubuntu Desktop 24.04 LTS
驱动版本 595.71.05
CUDA 版本 13.2
Python 3.12
vLLM 0.25.1
模型 Qwen/Qwen2.5-1.5B-Instruct

二、部署过程中的坑

1. HuggingFace 下载失败(代理问题)

vLLM 在加载模型时需要访问 HuggingFace,但国内网络环境需要代理。我用的是 SOCKS5 代理,而 vLLM 底层使用的 httpx 库默认不支持 SOCKS 协议。

解决方案

bash 复制代码
pip install 'httpx[socks]'
export ALL_PROXY=socks5://127.0.0.1:10808

代理的话我是用的 v2rayN,感兴趣的朋友也可以自己试试别的。 你也可以通过设置~/.bashrc来方便地为终端挂上代理。

2. flashinfer 编译失败(CUDA 版本不兼容)

这是最大的坑。vLLM 默认使用 flashinfer 作为采样后端,但在 CUDA 13.2 环境下,flashinfer 0.6.x 的 JIT 编译会报错:

vbnet 复制代码
error: class "cub::_V_300302_SM_890::BlockAdjacentDifference<...>" has no member "FlagHeads"

解决方案:禁用 flashinfer,切换到 PyTorch 原生后端。

bash 复制代码
export VLLM_USE_FLASHINFER_SAMPLER=0
export VLLM_ATTENTION_BACKEND=FLASH_ATTN

3. RM / NVML 版本不匹配

某次启动时遇到 RM has detected an NVML/RM version mismatch,原因是 NVIDIA 驱动更新后未重启,感觉是电脑待机太久导致的(就是没关机,但合上屏幕了,这种久了似乎网络什么的都容易产生问题)。

解决方案:重启系统即可。

bash 复制代码
sudo reboot

三、显存画像:模型到底吃了多少?

启动 API Server 后,通过 nvtop 监控显存占用(nvtop可通过sudo apt install nvtop获取,这是一个TUI):

bash 复制代码
python -m vllm.entrypoints.openai.api_server \
    --model Qwen/Qwen2.5-1.5B-Instruct \
    --gpu-memory-utilization 0.6 \
    --max-model-len 1024 \
    --enforce-eager

显存占用构成

组成部分 大小
模型权重 (BF16) ~2.98 GiB
KV Cache 池 ~1.29 GiB
PyTorch 上下文 + 碎片 ~0.5 GiB
总计 ~4.7 GiB

在 8GB 显存上,还剩下约 3GB 余量,说明 gpu_memory_utilization=0.6 是一个相对保守但稳定的配置。


四、性能压测:数据说话

使用 Python 脚本模拟并发请求,测试不同并发数下的吞吐量和延迟。

压测脚本核心逻辑

python 复制代码
def benchmark(num_requests=20, concurrency=4):
    with ThreadPoolExecutor(max_workers=concurrency) as executor:
        results = list(executor.map(send_request, prompts))
    # 统计平均延迟、P99、吞吐量

脚本示例(benchmark.py):

python 复制代码
import time
import requests
import concurrent.futures

def send_request(prompt):
    start = time.perf_counter()
    try:
        resp = requests.post(
            "http://localhost:8000/v1/completions",
            json={
                "model": "Qwen/Qwen2.5-1.5B-Instruct",
                "prompt": prompt,
                "max_tokens": 30,
                "temperature": 0.7
            },
            timeout=60
        )
        latency = time.perf_counter() - start
        return {"success": True, "latency": latency, "status": resp.status_code}
    except Exception as e:
        return {"success": False, "error": str(e), "latency": time.perf_counter() - start}

def benchmark(num_requests=10, concurrency=16):
    prompts = ["Why is the sky blue?"] * num_requests
    
    print(f"REQUEST: 发送 {num_requests} 个请求,并发数 {concurrency}")
    start_time = time.perf_counter()
    
    with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as executor:
        results = list(executor.map(send_request, prompts))
    
    total_time = time.perf_counter() - start_time
    
    # 统计
    success_results = [r for r in results if r["success"]]
    latencies = [r["latency"] for r in success_results]
    
    if latencies:
        avg_latency = sum(latencies) / len(latencies)
        p99 = sorted(latencies)[int(len(latencies) * 0.99)] if len(latencies) >= 100 else max(latencies)
        throughput = len(success_results) / total_time
        print(f"\n成功数: {len(success_results)}/{num_requests}")
        print(f"平均延迟: {avg_latency:.3f}s")
        print(f"P99 延迟: {p99:.3f}s")
        print(f"总耗时: {total_time:.3f}s")
        print(f"吞吐量: {throughput:.2f} req/s")
    else:
        print("Request Error")

if __name__ == "__main__":
    benchmark(num_requests=20, concurrency=32)

压测结果

API Server max_num_seqs 并发数 (workers) 平均延迟 P99 延迟 吞吐量
4 4 0.461s 0.520s 8.65 req/s
16 8 0.474s 0.507s 14.05 req/s
16 16 0.515s 0.540s 20.11 req/s
32 32 0.613s 0.631s 31.40 req/s

性能曲线

scss 复制代码
吞吐量 (req/s)
     35 |                            ● (32, 31.4)
     30 |
     25 |          ● (16, 20.1)
     20 |
     15 |    ● (8, 14.0)
     10 | ● (4, 8.7)
      5 |
      0 +------------------------------------
          0    8    16    24    32    并发数

五、关键结论

1. vLLM 的 Continuous Batching 效果显著

max_num_seqs=16 的配置下,并发数从 4 提升到 16,吞吐量从 8.65 req/s 增长到 20.11 req/s(+133%),而 P99 延迟仅从 0.52s 微升到 0.54s。这说明 vLLM 能高效地将多个请求合并为 batch 处理,充分利用 GPU 计算资源。

2. RTX 4060 的性能极限

max_num_seqs=32 时吞吐量达到峰值 31.40 req/s,但平均延迟上升至 0.613s(+19%)。此时显存带宽已成为主要瓶颈,继续增加并发将导致延迟非线性上升。

3. 生产环境配置建议

场景 推荐配置 预期性能
追求低延迟 max_num_seqs=4 P99 ~520ms,8.65 req/s
平衡性价比 max_num_seqs=16 P99 ~540ms,20.11 req/s
追求高吞吐 max_num_seqs=32 P99 ~631ms,31.40 req/s

六、总结

我在这个项目中学到了什么

  1. 环境兼容性是消费级显卡最大的敌人:CUDA 版本、驱动版本、flashinfer 之间的依赖关系极其敏感,需要耐心排查。
  2. 显存管理是推理引擎的核心 :通过 nvtop 观察显存占用的动态变化,能直观理解 PagedAttention 的工作原理。
  3. 压测数据是调优的依据:没有数据支撑的"调参"都是玄学。亲手跑出性能曲线后,才能真正理解吞吐量与延迟的 trade-off。
  4. 消费级显卡一样能跑推理:虽然达不到 A100 的吞吐量,但对于学习、原型验证和小规模服务部署,RTX 4060 完全够用。

相关资源


如果你也在消费级显卡上部署 vLLM,欢迎交流讨论!

相关推荐
纪伊路上盛名在1 天前
NVML ERROR_ RM has detected an NVML_RM version mismatch
linux·数据库·gpu·驱动
GPUStack3 天前
怎么优雅地在GPUStack上使用minerU?
ai·大模型·llm·gpu·vllm·gpu集群·gpustack
Smoothcloud润云5 天前
国内GPU算力租赁平台横向测评:资源、成本、稳定性三维对比
人工智能·ai·云计算·gpu算力·gpu
GPUStack6 天前
Day 0 实测|在 GPUStack 上部署 Inkling-BF16:8 卡 H20-141G 推理性能测试
ai·大模型·llm·gpu·vllm·gpu集群·sglang·gpustack
Smoothcloud润云11 天前
具身智能数据集有哪些?机器人训练常用数据集整理
人工智能·机器人·gpu算力·gpu
GPUer11 天前
同一个灵魂,两副躯体——GPU 内存管理为何是 CPU MM 的“平行宇宙”
gpu
算力百科小星11 天前
企业AI训练算力成本结构分析:自建机房与云算力的经济性比较
gpu算力·gpu
武子康14 天前
🔥 vLLM / SGLang / TGI / TensorRT-LLM / Triton 真实分工:分层定位 + 5 步决策路径
人工智能·llm·gpu
武子康17 天前
调查研究-224 Prefill 与 Decode 分离:高并发 LLM Serving 的下一层架构
人工智能·ai·架构·llm·gpu·vllm·sglang