vLLM模型部署

 vLLM是什么

vLLM 是一个高性能、内存高效的 LLM(大语言模型)推理引擎,由 UC Berkeley 开发。它解决了大模型部署中最核心的问题:显存不够、推理太慢

与其他推理引擎对比

特性 vLLM Transformers llama.cpp TGI
显存效率 ⭐ 极高(PagedAttention) ❌ 低(静态 KV Cache) ✅ 高(GGUF) ✅ 高
吞吐量 ⭐ 极高(连续批处理) ❌ 低 ⚠️ 中等 ✅ 高
GPU 加速 ✅ 原生 CUDA ⚠️ 通过 llama.cpp
量化支持 AWQ/GPTQ/FP8/INT4 bitsandbytes GGUF 量化 AWQ/GPTQ
OpenAI API ✅ 内置 ⚠️ server 模式
多 GPU ✅ Tensor Parallel ⚠️ device_map
MoE 支持 ✅ 原生 ⚠️

什么时候选 vLLM?
• 需要高吞吐的生产环境(API 服务)✅
• 多 GPU 部署大模型(70B+)✅
• 需要 OpenAI 兼容接口 ✅
• 单卡部署量化模型 ✅
• 实验性研究/单次推理 → 选 Transformers
• 个人电脑/无 GPU → 选 llama.cpp

内存占用精确计算

模型权重占用缓存

# 精确公式:
# 显存 = 参数量 × 每参数字节数
#
# FP32  → 4 字节/参数
# FP16  → 2 字节/参数
# BF16  → 2 字节/参数
# INT8  → 1 字节/参数
# INT4  → 0.5 字节/参数
模型 参数量 FP16 INT8 INT4 (AWQ/GPTQ)
Qwen2.5-1.5B 1.5B 3 GB 1.5 GB 0.75 GB
Qwen2.5-7B 7B 14 GB 7 GB 3.5 GB
Llama-3.1-8B 8B 16 GB 8 GB 4 GB
Qwen2.5-14B 14B 28 GB 14 GB 7 GB
Qwen2.5-32B 32B 64 GB 32 GB 16 GB
Llama-3.1-70B 70B 140 GB 70 GB 35 GB
Qwen2.5-72B 72B 144 GB 72 GB 36 GB
Mixtral 8×7B (MoE) 47B 94 GB 47 GB 24 GB
DeepSeek-V3 (MoE) 671B ~400 GB ~200 GB ~110 GB

KV Cache 占用的显存

# 精确公式:
# KV Cache 显存 = 2 (K + V) × 层数 × 注意力头数 × 每头维度 × 精度 × 上下文长度
#
# 对于 Llama-3.1-8B:45 层, 32 头, head_dim=128
# = 2 × 45 × 32 × 128 × 2 (FP16) × 8192
# ≈ 6 GB(8192 上下文时)
#
# 简短公式:
# FP16 KV Cache (GB) ≈ 层数 × 头数 × head_dim × 2 × context_length / 536,870,912
模型 层数 头数 2K 4K 8K 32K 128K
Llama-3.1-8B 32 32 0.75 GB 1.5 GB 3 GB 12 GB 48 GB
Qwen2.5-7B 28 28 0.6 GB 1.2 GB 2.4 GB 9.6 GB 38 GB
Qwen2.5-72B (GQA) 80 8 (KV) 0.5 GB 1 GB 2 GB 8 GB 32 GB
DeepSeek-V3 (MLA) 67 ~0.1 GB ~0.2 GB ~0.4 GB ~1.6 GB ~6.4 GB

💡 关键发现:
• GQA(分组查询注意力)能大幅减少 KV Cache——KV 头数 = 注意力头数 / 组数
• MLA(多头潜在注意力,DeepSeek 用)把 KV Cache 压缩到几乎可忽略
• 上下文越长,KV Cache 占比越大——8K 以上 KV Cache 可能超过权重本身

总显存计算示例

# ═══════════════════════════════════════════════════════════
# 场景:Qwen2.5-7B, FP16, 8K 上下文, batch_size=4
# ═══════════════════════════════════════════════════════════
#
# 模型权重:  7B × 2 bytes = 14 GB
# KV Cache:  2.4 GB (单请求 8K) × 4 (batch) = 9.6 GB
# 激活值 + CUDA Graph + 其他: ~3 GB
# ─────────────────────────────────
# 总计:      ~26.6 GB(24GB 显卡放不下!)
#
# ═══════════════════════════════════════════════════════════
# 解决:降精度或缩上下文
# ═══════════════════════════════════════════════════════════
#
# 方案 A: AWQ 4-bit + 8K ctx + batch=4
#   权重: 3.5 GB + KV Cache: 9.6 GB + 其他: 2 GB = 15 GB ✅
#
# 方案 B: FP16 + 4K ctx + batch=2
#   权重: 14 GB + KV Cache: 2.4 GB + 其他: 3 GB = 19.4 GB ✅
#
# 方案 C: AWQ 4-bit + 4K ctx + batch=4
#   权重: 3.5 GB + KV Cache: 2.4 GB + 其他: 2 GB = 7.9 GB ✅ ← RTX 4060 都能跑

显存诊断命令

# 启动时查看显存分配 新版命令(推荐)
vllm serve Qwen/Qwen2.5-7B-Instruct \
  --gpu-memory-utilization 0.9

# 默认就会打印请求统计,无需额外参数
# 启动时终端会自动显示模型加载和显存分配信息
# 每次请求完成后自动打印一行处理日志

# 运行时查看显存占用
nvidia-smi -l 2                    # 每 2 秒刷新

# vLLM 暴露的指标端点(Prometheus 格式)
curl http://localhost:8000/metrics

# 查看模型实际 KV Cache 分配
curl http://localhost:8000/v1/internal/stat

常见 OOM 原因:
1. max-model-len 太高——上下文越长,KV Cache 越大。从 4096 开始尝试
2. gpu-memory-utilization 太高——设 0.85 留出余量
3. batch 太大——降低 max-num-seqs 或 max-num-batched-tokens
4. CUDA Graph 额外占显存——显存快满时加 --enforce-eager 禁用
5. 量化格式与加载方式不匹配——AWQ 模型没加 --quantization awq 会按 FP16 加载

安装与环境准备

# ═══════════════════════════════════════════════════════════
# 方式一:pip 安装(推荐,最稳定)
# ═══════════════════════════════════════════════════════════

# 基础安装(CUDA 12.1+)
pip install vllm

# 指定 CUDA 版本安装
pip install vllm==0.8.3 # 或最新版

# 使用国内镜像(中国大陆推荐)
pip install vllm -i https://pypi.tuna.tsinghua.edu.cn/simple

# ═══════════════════════════════════════════════════════════
# 方式二:源码安装(开发者、需要修改源码时)
# ═══════════════════════════════════════════════════════════
git clone https://github.com/vllm-project/vllm.git
cd vllm
pip install -e .

# ═══════════════════════════════════════════════════════════
# 方式三:Docker 安装(推荐生产环境)
# ═══════════════════════════════════════════════════════════
docker pull vllm/vllm-openai:latest

# ═══════════════════════════════════════════════════════════
# 验证安装
# ═══════════════════════════════════════════════════════════
python -c "import vllm; print(vllm.__version__)"

版本兼容性:
• vLLM 0.4.x → CUDA 11.8 / 12.1, PyTorch 2.1+
• vLLM 0.5.x → CUDA 12.1+, PyTorch 2.2+
• vLLM 0.6.x → CUDA 12.1+, PyTorch 2.3+, FlashAttention 2
• vLLM 0.8.x → CUDA 12.4+, PyTorch 2.6+, 支持 Hopper( H100 ) 架构
• 低版本 CUDA 请使用 Docker 镜像(自带 CUDA 环境)

环境检查

# 检查 CUDA 版本
nvidia-smi
nvcc --version

# 检查 PyTorch CUDA 可用性
python -c "import torch; print(f'CUDA可用: {torch.cuda.is_available()}, 显卡数: {torch.cuda.device_count()}')"

# 检查显存
python -c "import torch; [print(f'卡{i}: {torch.cuda.get_device_properties(i).total_memory/1e9:.1f}GB') for i in range(torch.cuda.device_count())]"

# 检查 vLLM 支持哪些模型架构
python -c "from vllm import ModelRegistry; print(ModelRegistry.get_supported_archs())"

vLLM核心技术

PagedAttention — vLLM 的灵魂

传统推理中,KV Cache 是一整块连续内存,预先分配最大可能大小。这导致:

  • 内部碎片:分配的比实际需要的多,浪费 60%~80%
  • 外部碎片:不同请求的 KV Cache 大小不一,内存无法复用

PagedAttention 借鉴操作系统分页内存管理的思想:

  • KV Cache 分成固定大小的"块"(Block),每块 16 或 32 个 token
  • 不需要连续的内存空间——物理上可以是零散的页面
  • 通过"页表"(Block Table)将逻辑地址映射到物理地址
  • 结果:显存利用率从 ~30% 提升到 ~95%

连续批处理(Continuous Batching)

传统批处理:

  • 收集一批请求 → 同时推理 → 等所有请求完成 → 返回结果
  • 短板效应:最慢的请求拖慢整批,短的请求在干等

vLLM 的连续批处理:

  • 每个请求独立管理,按 token 粒度调度
  • 每推理一步(一个 token)后重新调度
  • 完成的请求立即移出,新请求随时加入
  • GPU 始终在处理"有效计算",不浪费在等待上
  • 结果:吞吐量提升 2~4 倍

推理引擎 vs 训练框架

维度 推理(Inference) 训练(Training)
目标 快速生成文本 更新模型权重
显存需求 权重 + KV Cache 权重 + 梯度 + 优化器状态
批量大小 动态小批量 大批量
KV Cache 需要缓存(逐 token 生成) 不需要(训练时一次性计算)
典型框架 vLLM, TGI, llama.cpp DeepSpeed, Megatron, FSDP

Prefill 与 Decode 阶段

LLM 推理分两个阶段:

  • Prefill(预填充):一次性处理用户输入的 prompt,计算第一个输出 token。计算密集型,GPU 利用率高
  • Decode(解码):逐个生成后续 token,每次只算一个 token。内存访问密集型,GPU 利用率低

vLLM 的优化重点就在 Decode 阶段——通过批处理把多个请求的 Decode 合并计算,提高 GPU 利用率。

 投机解码(Speculative Decoding)

投机解码的核心思想:用小模型"猜"几个 token,再用大模型一次性验证。因为验证是并行的(大模型可以同时检查多个 token),而逐 token 生成是串行的,所以总速度更快。

投机解码流程:
  1. Draft Model(草稿模型):一个小模型(~0.5B),快速生成 K 个候选 token
  2. Target Model(目标模型):vLLM 部署的大模型,一次性验证这 K 个 token
  3. 从第一个不匹配的 token 处截断,接受前面的匹配 token
  4. 每步实际产出 1~K 个 token(取决于猜对的长度)

为什么能加速?
  · 大模型的 Prefill 是并行的 → 验证 K 个 token ≈ 生成 1 个 token 的时间
  · 小模型生成 K 个 token 的时间 ≈ 大模型生成 1 个 token 的时间
  · 如果平均每次猜对 3 个 → 吞吐提升 ~3×
  · 最关键:数学上无损失——验证通过的 token 和大模型自己逐 token 生成的结果完全一致

vLLM 支持 Eagle(一种高效 draft model)和 Medusa 两种投机解码方案。配置方式:--speculative-model <draft_model> --num-speculative-tokens 5

MTP — 多 Token 预测(Multi-Token Prediction)

MTP 是 DeepSeek-V3/R1 提出的一种训练阶段技术,让模型在训练时就学会"一次预测多个未来 token",而非传统的"只预测下一个 token"。推理时,每个 Decode 步骤可以同时产出多个 token。

维度 投机解码 MTP
原理 小模型猜 + 大模型验证(两个模型协作) 训练时就在模型内部加了多个预测头(单一模型)
需要额外模型 ✅ 需要 draft model ❌ 不需要(MTP 头是模型的一部分)
每步产出 1~K 个 token(取决于猜对率) 固定 K 个 token(通常 K=2~4)
精度 无损失(验证保证) 可能有微小损失(MTP 头的预测不如主头准)
代表 Eagle, Medusa DeepSeek-V3, DeepSeek-R1
vLLM 支持 ✅ 原生支持 ⚠️ 需模型本身支持 MTP(如 DeepSeek-V3)

vLLM 对于 DeepSeek-V3 等原生支持 MTP 的模型,可以直接利用其 MTP 头实现多 token 同时生成,无需额外配置 draft model。

前缀缓存(Automatic Prefix Caching / APC)

很多请求共享相同的系统提示词(System Prompt)。例如:"你是一个有用的助手……" 这段文字每次请求都一样。如果没有缓存,每个请求都要重新计算这段前缀的 KV Cache。

APC 的工作原理:
  · 将 prompt 的 token 序列按固定大小分块(如每 16 个 token 一块)
  · 对每个块计算哈希值作为"指纹"
  · 新请求到来时,查找是否有相同哈希的块 → 命中则直接复用 KV Cache
  · 结果:共享长 System Prompt 的场景下,TTFT(首 token 延迟)可降低 50%~90%

vLLM 默认开启 APC,无需额外配置。通过 --enable-prefix-caching 显式开启(新版已默认)。

分块预填充(Chunked Prefill)

长 prompt(如 32K token)的 Prefill 阶段计算量巨大,一次处理会长时间占用 GPU,阻塞其他请求的 Decode。Chunked Prefill 把长 prompt 切成多个小块,交错执行:

分块前(长 Prefill 阻塞):
  [-------- 32K Prefill (独占 GPU, 200ms) --------] → [Decode] → [Decode] → ...
  中途其他请求的 Decode 无法插队

分块后(交错执行):
  [4K Prefill] → [Decode 请求A] → [4K Prefill] → [Decode 请求B] → [4K Prefill] → ...
  每个 Prefill 小块只占 ~25ms,其他请求可以在间隙中插入 Decode

配置方式:--max-num-batched-tokens 8192(限制单次 Prefill 最多处理的 token 数)和 --enable-chunked-prefill

张量并行(Tensor Parallelism)— 多卡推理

当模型太大单卡放不下时,vLLM 使用张量并行把模型切到多张 GPU 上。不同于训练中的 DP/TP/PP 三维并行,推理并行更简单——因为不需要存梯度和优化器状态。

并行方式 原理 适用场景 vLLM 配置
张量并行 (TP) 把每层的权重矩阵切成 N 份放到 N 张卡上,每张卡算一部分再聚合 单卡放不下完整模型(如 70B→4×24GB) --tensor-parallel-size 4
流水线并行 (PP) 前一半层放 GPU-0,后一半放 GPU-1,流水式传递 层数极多(如 DeepSeek-V3 的 60+ 层) --pipeline-parallel-size 2

TP 是推理中最常用的多卡策略。例如 70B 模型 FP16 需要 ~140GB 显存,4×A100-40G 通过 TP=4 刚好装下。

量化推理 — 更小的模型,更多的吞吐

vLLM 支持多种权重量化格式,在几乎不损失精度的前提下大幅降低显存占用:

量化方式 精度 显存节省 (vs FP16) 速度 vLLM 配置
AWQ INT4(每通道缩放) ~75%(14GB→3.5GB) 接近 FP16 下载 AWQ 格式模型即可
GPTQ INT4(分组量化) ~75%(14GB→3.5GB) 接近 FP16 下载 GPTQ 格式模型即可
FP8 8-bit 浮点 ~50%(14GB→7GB) ⭐ 比 FP16 更快(H100 原生支持 FP8) --quantization fp8
INT8 8-bit 整数 ~50%(14GB→7GB) 略慢于 FP16 --quantization int8
BitsAndBytes INT4/INT8 ~50%~75% 较慢 --quantization bitsandbytes --load-format bitsandbytes

推荐:H100/H200 用户优先选 FP8(原生硬件加速,比 FP16 更快且显存减半);DGX Spark(Blackwell, 128GB 统一内存)同样优先选 FP8,而且 128GB 显存跑 70B 模型直接用 BF16 也绰绰有余;消费级显卡(RTX 3090/4090)优先选 AWQ(INT4 量化,24GB 可跑 70B 模型)。

调度策略(Scheduling Policy)

vLLM 支持多种请求调度策略,决定"先处理哪个请求、分配多少资源":

策略 行为 适用场景
FCFS(先到先服务) 默认策略,按请求到达顺序处理 通用场景
Priority(优先级) 高优先级请求优先获得 GPU 时间片 在线服务(VIP 用户优先)
Preemption(抢占) 高优先级请求可以"踢掉"正在处理的低优先级请求(被踢的请求 swap 到 CPU 内存) 需要严格 SLA 的场景

vLLM 的调度器会综合考虑每个请求的 prompt 长度、已生成 token 数、优先级等因素动态分配资源。Swap 机制允许把被抢占请求的 KV Cache 暂存到 CPU 内存,恢复时再搬回 GPU。

模型部署调用

OpenAI 兼容 API 服务方式

启动API服务

# ═══════════════════════════════════════════════════════════
# 最简启动(仅必要参数)
# ═══════════════════════════════════════════════════════════
vllm serve Qwen/Qwen2.5-7B-Instruct \
  --port 8000 \
  --host 0.0.0.0

# ═══════════════════════════════════════════════════════════
# 生产部署常用配置(带上所有关键参数)
# ═══════════════════════════════════════════════════════════
vllm serve Qwen/Qwen2.5-7B-Instruct \
  # ─── 服务参数 ───
  --port 8000 \                                  # ★ API 监听端口
  --host 0.0.0.0 \                               # ★ 绑定所有网卡(允许外部访问)
  --api-key sk-xxxxx \                          # 设置 API 密钥(不设则不校验)
  --served-model-name my-qwen \                 # ★ 对外暴露的模型名(客户端用这个名字请求)
  # ─── 模型参数 ───
  --dtype auto \                                 # ★ 数据类型: auto/bfloat16/float16/float32
  --max-model-len 8192 \                         # ★ 最大上下文长度(可超模型原始限制)
  --quantization awq \                           # ★ 量化: awq/gptq/fp8/squeezellm/None
  --trust-remote-code \                          # ★ 自定义架构模型必加(Qwen/DeepSeek/ChatGLM)
  # ─── 显存与并行参数 ───
  --gpu-memory-utilization 0.90 \              # ★ GPU 显存利用率(0.9=90%,留 10% 给系统)
  --tensor-parallel-size 1 \                   # ★ 张量并行(单卡=1,4 卡=4)
  --pipeline-parallel-size 1 \                # 流水线并行(默认=1,层数极多时用)
  # ─── KV Cache 参数 ───
  --max-num-seqs 256 \                          # ★ 最大并发请求数(默认 256)
  --max-num-batched-tokens 8192 \              # 单步最多处理 token 数(影响吞吐)
  --enable-prefix-caching \                      # ★ 启用前缀缓存(系统提示词复用)
  # ─── 调度参数 ───
  --disable-log-requests                         # 不记录请求日志(减少 IO)

# ═══════════════════════════════════════════════════════════
# DGX Spark (128GB 统一内存) 推荐配置
# ═══════════════════════════════════════════════════════════
vllm serve Qwen/Qwen2.5-72B-Instruct \
  --port 8000 \
  --dtype auto \
  --max-model-len 32768 \                       # 128GB 显存可以开更长上下文
  --gpu-memory-utilization 0.95 \              # 统一内存更充裕,可以提高利用率
  --tensor-parallel-size 1 \                   # DGX Spark 单 GPU,不需要 TP
  --max-num-seqs 512 \                          # 大显存支持更多并发
  --enable-prefix-caching

参数详解

参数 默认值 详细说明
--port 8000 API 服务监听端口。客户端通过 http://IP:8000/v1 调用
--host 127.0.0.1 绑定 IP。0.0.0.0 = 允许外部访问;127.0.0.1 = 仅本机
--served-model-name (同 model) 对外暴露的模型名称。设成 gpt-4 后客户端用 model="gpt-4" 即可,隐藏真实模型名
--dtype auto 模型精度。auto=自动检测模型配置;bfloat16/float16=半精度;float32=全精度
--max-model-len 模型默认值 最重要的参数之一。上下文窗口大小。可以超过模型原始训练长度(如 Llama-3-8B 原生 8K,设 32K 也可以推理,但精度可能下降)
--gpu-memory-utilization 0.90 最重要的参数之一。vLLM 可以使用的 GPU 显存比例。设太低浪费显存,设太高(0.98+)可能 OOM。建议 0.85~0.95
--quantization None 量化方式。awq/gptq/fp8/squeezellm。H100/DGX Spark 用 fp8,消费级显卡用 awq
--tensor-parallel-size 1 GPU 数量。单卡=1。70B 模型 FP16 需要 ~140GB,2×A100-80G 设 2
--max-num-seqs 256 最大同时处理的请求数。增大 = 更高吞吐但更多显存给 KV Cache。显存紧张时减小此值
--enable-prefix-caching False 开启 APC(自动前缀缓存)。共享 System Prompt 场景下 TTFT 大幅降低
--api-key (无) 设置 API 访问密钥。不设则任何能访问端口的客户端都可以调用
--trust-remote-code False 允许执行模型仓库中的自定义 Python 代码。Qwen/DeepSeek/ChatGLM/Phi 等必须加

日常最需要调的三个参数:
① --max-model-len — 决定上下文窗口多大(开销大头在 KV Cache,8192→32768 显存翻几倍)
② --gpu-memory-utilization — 决定 vLLM 能用多少显存(设太高 OOM,设太低吞吐上不去)
③ --max-num-seqs — 决定最多同时处理多少请求(越大 = KV Cache 预留越多 = 单请求可用显存越少)

调用API服务

from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")

response = client.chat.completions.create(
    model="my-qwen",

    # ─── 消息体(必填) ───
    messages=[
        {"role": "system", "content": "你是一个精通 Python 的编程助手"},
        {"role": "user", "content": "用Python写一个快速排序"},
        # 也可以传入历史对话实现多轮
        # {"role": "assistant", "content": "好的,快速排序是..."},
        # {"role": "user", "content": "能加注释吗?"},
    ],

    # ─── 输出长度控制 ───
    max_tokens=2048,                              # ★ 生成的最大 token 数(不含 prompt)
    max_completion_tokens=2048,                    # 同上(新版 OpenAI API 参数名)
    # stop=["```", "\n\n\n"],            # ★ 停止词 — 遇到这些字符串立即停止

    # ─── 随机性与多样性 ───
    temperature=0.7,                             # ★ 温度 (0~2)。0=确定性输出,1=标准随机,>1=更随机
    top_p=0.9,                                   # ★ 核采样 — 只从累积概率 top_p 的 token 中选
    top_k=50,                                    # ★ Top-K 采样 — 只从概率最高的 K 个 token 中选
    seed=42,                                     # ★ 随机种子 — 相同 seed + 相同参数 = 相同输出

    # ─── 惩罚参数(控制重复) ───
    frequency_penalty=0.0,                        # 频率惩罚 (-2~2)。正值惩罚高频 token,减少重复
    presence_penalty=0.0,                          # 存在惩罚 (-2~2)。正值惩罚已出现过的 token,鼓励新话题
    repetition_penalty=1.0,                        # 重复惩罚 (>1 惩罚重复)。KaiChang/Vicuna 时代常用,OpenAI 风格用上面两个

    # ─── 流式输出 ───
    # stream=True,                        # ★ 流式输出 — 逐 token 返回,用户体验更好
    # stream_options={"include_usage": True},  # 流式模式下也返回 usage 统计

    # ─── 其他 ───
    n=1,                                        # 生成几个候选回答(>1 时返回多个 choices)
    # logprobs=True,                      # 返回每个 token 的 log 概率(调试用)
    # top_logprobs=5,                     # 返回 top-5 候选 token 的 log 概率
)

print(response.choices[0].message.content)
print(f"使用: {response.usage}")  # {"prompt_tokens": 45, "completion_tokens": 512, "total_tokens": 557}

流式输出 — 逐字显示

from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")

stream = client.chat.completions.create(
    model="my-qwen",
    messages=[{"role": "user", "content": "写一首关于AI的诗"}],
    temperature=0.7,
    max_tokens=512,
    stream=True,                                # ★ 开启流式输出
)

for chunk in stream:                             # 每次迭代拿到一个新生成的 token
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

生成参数速查表

参数 类型/范围 默认值 什么时候调
temperature float, 0~2 1.0 越低越确定(代码/数学用 0.1~0.3),越高越有创意(写作/头脑风暴用 0.8~1.2)
top_p float, 0~1 1.0 核采样。设为 0.9 表示只考虑累积概率前 90% 的 token。通常和 temperature 二选一
top_k int, 1~N -1(不限制) 只从概率最高的 K 个 token 中选。设 40~50 是常见值。vLLM 特有参数
max_tokens int 16 限制生成长度。设太小回答被截断,设太大浪费资源。建议 512~4096
seed int 随机 固定随机种子 = 相同输出。调试、评测、复现实验时必须设
frequency_penalty float, -2~2 0 正值惩罚已出现的 token(按频率)。模型老重复同一句话时调到 0.3~0.5
presence_penalty float, -2~2 0 正值惩罚已出现的 token(不论频率)。想让模型聊新话题时调到 0.3~0.5
repetition_penalty float, >1.0 1.0 vLLM 特有。1.1 = 轻微惩罚重复,1.2 = 明显惩罚。>1.3 可能影响语法
stop str 或 list 遇到这些词立即停止生成。常用: ["\n\n", "Human:", "###"]
stream bool False 开启后逐 token 返回(Server-Sent Events)。Web 端必开
n int 1 一次生成几个候选回答。>1 时 response.choices 有多个元素
logprobs bool False 返回每个 token 的对数概率。调试、置信度评估时开启

💡 常见场景的参数组合:
• 代码生成: temperature=0.1, top_p=0.95, max_tokens=4096(确定性高,输出长)
• 创意写作: temperature=0.9, top_p=0.95, frequency_penalty=0.3(有创意但不重复)
• 翻译: temperature=0.3, top_p=0.9, seed=42(确定性强,可复现)
• 对话聊天: temperature=0.7, top_p=0.9, presence_penalty=0.3(自然但不跑题)

Python API 方式

# ═══════════════════════════════════════════════════════════
# 最简单的 vLLM 推理代码
# ═══════════════════════════════════════════════════════════
from vllm import LLM, SamplingParams

# 1. 加载模型
# • model: HuggingFace 模型名或本地路径
# • 首次运行会自动从 HF 下载模型权重
llm = LLM(
    model="Qwen/Qwen2.5-7B-Instruct",  # 模型名称或路径
    tensor_parallel_size=1,              # GPU 数,1=单卡
    dtype="auto",                         # 自动选择数据类型
    max_model_len=8192,                  # 最大上下文长度
    gpu_memory_utilization=0.9,           # GPU 显存利用率
)

# 2. 配置生成参数
sampling_params = SamplingParams(
    temperature=0.7,        # 温度:越高越随机,越低越确定
    top_p=0.9,               # Top-p 采样:累积概率到 0.9
    max_tokens=1024,        # 最大生成长度
    stop=["</s>", "<|im_end|>"],  # 停止词
)

# 3. 推理
outputs = llm.generate(["Hello, how are you?"], sampling_params)

# 4. 输出结果
for output in outputs:
    prompt = output.prompt
    generated_text = output.outputs[0].text
    print(f"输入: {prompt}")
    print(f"输出: {generated_text}")

量化方法

量化(Quantization) 是把模型权重从高精度(FP16/BF16)压缩到低精度(INT4/INT8/FP8)的技术。目的是:

  • 减少显存占用:FP16→INT4,显存降到 1/4
  • 加速推理:低精度计算更快
  • 降低功耗:更少的数据搬运

vLLM 支持的量化格式

格式 精度 显存节省 精度损失 适用场景
FP16 / BF16 16-bit 1×(基准) 显存充足、追求最佳质量
FP8 8-bit ~2× 极微 H100/Hopper 原生支持,速度最快
INT8 (W8A8) 8-bit ~2× 需要平衡速度和质量的场景
AWQ 4-bit ~4× 最推荐:精度好、速度快、生态广
GPTQ 4-bit ~4× 早期格式,生态成熟但加载较慢
INT4 (GGUF) 4-bit ~4× llama.cpp 生态,vLLM 0.8+ 支持
NF4 (bitsandbytes) 4-bit ~4× QLoRA 训练用的格式,推理也能用
# ═══════════════════════════════════════════════════════════
# 方式一:直接下载社区量化好的模型(推荐)
# ═══════════════════════════════════════════════════════════

# 很多模型社区已经做好的 AWQ 版本:
# TheBloke/Llama-2-7B-AWQ
# Qwen/Qwen2.5-7B-Instruct-AWQ
# TechxGenus/Meta-Llama-3.1-8B-Instruct-AWQ

vllm serve Qwen/Qwen2.5-7B-Instruct-AWQ \
  --quantization awq \
  --max-model-len 8192


# ═══════════════════════════════════════════════════════════
# 方式二:自己量化(需要 GPU)
# ═══════════════════════════════════════════════════════════

# 安装 AutoAWQ
pip install autoawq

# 量化脚本
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

model_path = "Qwen/Qwen2.5-7B-Instruct"
quant_path = "./models/Qwen2.5-7B-AWQ"

model = AutoAWQForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path)

# 量化配置
quant_config = {
    "zero_point": True,      # 使用零点量化
    "q_group_size": 128,    # 分组大小,越小精度越高
    "w_bit": 4,              # 量化位数
    "version": "GEMM",       # GEMM 通用 / GEMV 快速
}

# 执行量化(需要 GPU,约需 20GB 显存)
model.quantize(tokenizer, quant_config=quant_config)

# 保存量化模型
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)

量化精度对比

模型 FP16 AWQ 4-bit GPTQ 4-bit FP8
Llama-3.1-8B 16 GB ~5 GB ~5 GB ~8 GB
Qwen2.5-7B 14 GB ~4.5 GB ~4.5 GB ~7 GB
Llama-3.1-70B 140 GB ~40 GB ~40 GB ~70 GB
Qwen2.5-72B 144 GB ~41 GB ~41 GB ~72 GB
DeepSeek-V3 (671B MoE) ~400 GB ~110 GB ~110 GB ~200 GB

⚠️ 量化注意事项:
• 不是所有模型都有量化版——优先在 HF 搜 "模型名 + AWQ"
• 量化模型用 --quantization 指定格式,否则 vLLM 可能按 FP16 加载导致 OOM
• AWQ 推荐优先于 GPTQ——AWQ 加载更快,精度略好
• INT4 量化后质量下降——7B AWQ ≈ 6.5B FP16 的水平,大多数场景可接受
• 代码/数学任务对量化更敏感——这些场景建议用 8-bit 或保持 FP16

多 GPU 与分布式部署

张量并行(Tensor Parallelism)

将模型的一层切成多份,每张 GPU 算一份,通过通信同步结果。

# 2 张 GPU 跑 70B 模型
vllm serve meta-llama/Meta-Llama-3.1-70B-Instruct \
  --tensor-parallel-size 2 \
  --port 8000

# 4 张 GPU(适合 70B AWQ)
vllm serve Qwen/Qwen2.5-72B-Instruct-AWQ \
  --tensor-parallel-size 4 \
  --quantization awq \
  --port 8000
模型 单卡 24G 2×24G 4×24G 8×24G
7B (FP16) ✅ 勉强
7B (AWQ) ✅ 流畅
14B (FP16)
32B (AWQ)
70B (FP16) ⚠️ 勉强
70B (AWQ)
72B (FP16) ⚠️
72B (AWQ) ✅ 勉强

流水线并行(Pipeline Parallelism)

将模型的不同层分配到不同 GPU,层间串行计算。

vllm serve \
  --model deepseek-ai/DeepSeek-V3 \     # 60+ 层 MoE,PP 才能发挥优势
  --pipeline-parallel-size 2 \        # 前半层放 GPU-0,后半层放 GPU-1
  --tensor-parallel-size 4 \           # 常和 TP 组合使用
  --port 8000

⚠️ TP vs PP:
• TP(张量并行):每步计算都需要 GPU 间通信(NVLink 最佳),通信量大
• PP(流水线并行):只需在层边界传输中间结果,通信量小
• 推荐:同一台机器用 TP(NVLink 快),多台机器用 PP(网络慢)
• 也可以组合使用:--tensor-parallel-size 2 --pipeline-parallel-size 2

多节点部署

当模型大到单台机器装不下时(如 70B 模型 FP16 需要 ~140GB,而单机只有 4×A100-40G = 160GB 但实际可用 ~140GB),需要跨多台机器做张量并行。vLLM 通过 Ray 实现跨节点分布式推理。

关键理解:工作节点不运行 vllm serve,只运行 Ray。vLLM 通过 Ray 自动把 tensor-parallel worker 分布到集群中的 GPU 上:

跨节点 TP 的架构:
  节点 1(Head):启动 Ray Head + vllm serve
    → vLLM 通过 Ray 分发 TP worker 到节点1的GPU + 节点2的GPU
    → 对外暴露 API(只有 Head 有 API)

  节点 2(Worker):只启动 Ray Worker,加入集群
    → 不运行 vllm serve!Ray 自动把 Head 分配的计算任务调度过来

# ═══════════════════════════════════════════════════════════
# 节点 1(主节点 + 推理服务)
# ═══════════════════════════════════════════════════════════
# Step 1: 启动 Ray 集群的 Head 节点
ray start --head --port=6379

# Step 2: 启动 vLLM(Ray 自动发现全部 GPU)
vllm serve meta-llama/Meta-Llama-3.1-70B-Instruct \
  --tensor-parallel-size 8 \
  --distributed-executor-backend ray \
  --port 8000

# ═══════════════════════════════════════════════════════════
# 节点 2~N(工作节点 — 只需要加入 Ray 集群!)
# ═══════════════════════════════════════════════════════════
# 只需要一步:连接到 Head
ray start --address='<节点1的IP>:6379'

# 之后什么都不用做!Head 上的 vLLM 会自动发现 Worker 的 GPU
# 验证集群: ray status  → 应该看到节点 1 + 节点 2 的所有 GPU

💡 实践建议:
• 4 张卡以内 → 单机 TP 就够了,不需要 Ray
• 4~8 张卡 → 单机 TP + PP 组合
• 8 张卡以上 → 多节点 + TP + PP(需要 Ray 做跨节点分布式)
• 跨节点需要高速网络(InfiniBand/RoCE),普通千兆会成瓶颈
• Worker 节点绝对不要跑 vllm serve!只跑 ray start!

posted @ 2026-07-22 09:09  黄艺龙  阅读(5)  评论(0)    收藏  举报