一份从 "跑不起来" 到 "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 |
六、总结
我在这个项目中学到了什么
- 环境兼容性是消费级显卡最大的敌人:CUDA 版本、驱动版本、flashinfer 之间的依赖关系极其敏感,需要耐心排查。
- 显存管理是推理引擎的核心 :通过
nvtop观察显存占用的动态变化,能直观理解 PagedAttention 的工作原理。 - 压测数据是调优的依据:没有数据支撑的"调参"都是玄学。亲手跑出性能曲线后,才能真正理解吞吐量与延迟的 trade-off。
- 消费级显卡一样能跑推理:虽然达不到 A100 的吞吐量,但对于学习、原型验证和小规模服务部署,RTX 4060 完全够用。
相关资源
如果你也在消费级显卡上部署 vLLM,欢迎交流讨论!