课程0基础Agent开发课 / MLOps与模型部署 / vLLM生产部署-高性能LLM推理服务
— 13 min read

vLLM生产部署-高性能LLM推理服务

Ollama 让本地跑大模型变得极其简单,但它的设计目标是开发者单机使用,不是高并发服务。当多个用户同时发请求时,Ollama 是串行处理的——一个请求跑完才处理下一个,QPS(Queries Per Second,每秒处理的请求数量)极低,根本撑不住生产流量。

vLLM 生产部署:高性能 LLM 推理服务

1.1 为什么需要 vLLM:先说 Ollama 的局限

Ollama 让本地跑大模型变得极其简单,但它的设计目标是开发者单机使用,不是高并发服务。当多个用户同时发请求时,Ollama 是串行处理的——一个请求跑完才处理下一个,QPS(Queries Per Second,每秒处理的请求数量)极低,根本撑不住生产流量。

vLLM 是目前工业界最主流的开源 LLM 推理引擎,设计目标就是高吞吐量(每秒处理请求数量多)服务。同等硬件下,vLLM 的吞吐量比 Ollama 高出 10-30 倍,能真正支撑生产环境的并发请求。


1.2 核心技术:为什么 vLLM 快

vLLM架构与PagedAttention原理图
vLLM 通过 PagedAttention 管理 KV Cache 和连续批处理提升推理吞吐量的架构原理

1.2.1 PagedAttention:解决内存碎片问题

LLM 推理中,每个请求都需要维护一个 KV Cache(Key-Value Cache,键值缓存,存储模型在生成过程中已处理过的上下文信息,避免重复计算),记录已生成的 token 的注意力状态。传统方案为每个请求预分配一块连续的内存,问题是:

  • 不知道请求最终会生成多少 token,要么预分配过大(浪费),要么不够用(截断)
  • 大量细小的内存碎片无法利用,实际可用内存远小于物理内存

PagedAttention 借鉴操作系统的虚拟内存分页机制,把 KV Cache 切成固定大小的页(block),按需分配,不同请求的 KV Cache 可以分布在不连续的内存块中。这让 GPU 显存利用率从 60% 提升到 90%+,同等显存下能同时处理更多请求。

1.2.2 连续批处理(Continuous Batching)

传统批处理(将多个请求打包在一起同时处理,提高 GPU 利用率)等所有请求都完成才处理下一批。Continuous Batching 是流水线模式:某个请求生成完毕后,立即把新请求插入这个槽位,GPU 始终保持满负荷运行。

vLLM 连续批处理

Req1 完成

立即插入 Req4

Req2 完成 → 插入 Req5

GPU 持续满负荷

传统批处理

Req1+Req2+Req3

全部完成才处理下一批

Req4+Req5+Req6


1.3 部署:Docker 启动 OpenAI 兼容 API

vLLM 提供 OpenAI 兼容的 REST API,现有代码几乎不用改动。

bash
# 前置条件:有 NVIDIA GPU,已安装 CUDA 和 Docker + NVIDIA Container Toolkit

# 部署 Qwen2.5-7B-Instruct
# --model:Hugging Face 模型 ID,首次启动会自动下载
# --tensor-parallel-size:使用几张 GPU,多卡并行
# --max-model-len:最大上下文长度,受显存限制,不能超过模型支持的最大值
# --gpu-memory-utilization:显存利用率,0.9 表示使用 90% 显存给 KV Cache
docker run --gpus all \
    -v ~/.cache/huggingface:/root/.cache/huggingface \
    -p 8000:8000 \
    --ipc=host \
    vllm/vllm-openai:latest \
    --model Qwen/Qwen2.5-7B-Instruct \
    --tensor-parallel-size 1 \
    --max-model-len 8192 \
    --gpu-memory-utilization 0.90 \
    --served-model-name qwen2.5-7b

如果需要访问 HuggingFace 受限模型(如 LLaMA),需要传入 token:

bash
docker run --gpus all \
    -e HUGGING_FACE_HUB_TOKEN=your_hf_token \
    -v ~/.cache/huggingface:/root/.cache/huggingface \
    -p 8000:8000 \
    --ipc=host \
    vllm/vllm-openai:latest \
    --model meta-llama/Llama-3.1-8B-Instruct \
    --tensor-parallel-size 1

验证服务启动:

bash
curl http://localhost:8000/v1/models
# 返回已加载的模型列表

curl http://localhost:8000/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "qwen2.5-7b",
        "messages": [{"role": "user", "content": "你好"}],
        "max_tokens": 100
    }'

1.4 用 OpenAI SDK 调用 vLLM

因为 vLLM 实现了 OpenAI 兼容 API,只需修改 base_urlapi_key

python
# vllm_client.py
from openai import OpenAI
import asyncio

# api_key 随便填,vLLM 默认不做鉴权(生产环境需要在前置 Nginx 层加鉴权)
client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="not-needed",
)


def chat(message: str, model: str = "qwen2.5-7b") -> str:
    """同步调用,适合脚本场景。"""
    response = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": message}],
        max_tokens=512,
        temperature=0.7,
    )
    return response.choices[0].message.content


def stream_chat(message: str, model: str = "qwen2.5-7b"):
    """流式调用,适合 Web 服务场景,边生成边返回。"""
    stream = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": message}],
        max_tokens=512,
        stream=True,
    )
    for chunk in stream:
        if chunk.choices[0].delta.content:
            print(chunk.choices[0].delta.content, end="", flush=True)
    print()


# vLLM 特有的额外参数(通过 extra_body 传递)
def chat_with_vllm_params(message: str) -> str:
    """
    vLLM 支持一些 OpenAI API 没有的参数,通过 extra_body 传递。
    best_of:生成 N 个候选,返回最好的一个(适合质量要求高的场景)
    use_beam_search:使用束搜索,比贪婪解码质量更高但更慢
    """
    response = client.chat.completions.create(
        model="qwen2.5-7b",
        messages=[{"role": "user", "content": message}],
        max_tokens=256,
        extra_body={
            "best_of": 2,          # 生成 2 个候选取最优
            "use_beam_search": False,
        }
    )
    return response.choices[0].message.content


if __name__ == "__main__":
    print(chat("用一句话解释什么是向量数据库"))
    stream_chat("列举 5 个 Python 最佳实践")

1.5 量化部署:显存不够时的解决方案

量化(Quantization)把模型权重从 FP16(每参数 2 字节)压缩到 INT4(0.5 字节),显存占用降低 75%,推理速度略有下降,但精度损失可以接受。

量化方式 压缩比 精度损失 推荐场景
FP16(原始) 1x 显存充足,追求最高精度
GPTQ INT8 2x 极小 平衡精度和显存
AWQ INT4 4x 显存紧张,可接受轻微精度损失
GGUF(llama.cpp) 2-8x 小到中 CPU 推理或极低显存

vLLM 原生支持 AWQ 和 GPTQ 量化模型(AWQ 即 Activation-aware Weight Quantization,激活感知权重量化;GPTQ 即 Generative Pre-trained Transformer Quantization,两者都是将模型压缩到 INT4 精度的主流算法,精度优于简单的 INT4 截断):

bash
# 使用 AWQ 量化版 Qwen(HuggingFace 上有现成的量化模型)
docker run --gpus all \
    -v ~/.cache/huggingface:/root/.cache/huggingface \
    -p 8000:8000 \
    --ipc=host \
    vllm/vllm-openai:latest \
    --model Qwen/Qwen2.5-7B-Instruct-AWQ \
    --quantization awq \
    --max-model-len 8192

各主流模型的显存需求(FP16,含 KV Cache):

模型 参数量 FP16 显存 AWQ INT4 显存
Qwen2.5-7B 7B ~18GB ~6GB
Qwen2.5-14B 14B ~32GB ~10GB
LLaMA3.1-8B 8B ~18GB ~6GB
DeepSeek-R1-7B 7B ~18GB ~6GB

1.6 多卡并行

单卡不够时,vLLM 支持 Tensor Parallelism(张量并行,将模型的矩阵计算切分到多张 GPU 上同时进行)把模型分布到多张 GPU 上:

bash
# 4 卡并行部署 70B 模型
docker run --gpus all \
    -v ~/.cache/huggingface:/root/.cache/huggingface \
    -p 8000:8000 \
    --ipc=host \
    vllm/vllm-openai:latest \
    --model meta-llama/Llama-3.1-70B-Instruct \
    --tensor-parallel-size 4 \   # 需要 4 张 GPU
    --max-model-len 16384

--tensor-parallel-size 必须是 GPU 数量的因数,通常设为 GPU 数量本身。


1.7 性能调优

python
# benchmark.py:测试 vLLM 服务的吞吐量
import asyncio
import time
from openai import AsyncOpenAI

client = AsyncOpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")


async def single_request(prompt: str) -> float:
    """发送单个请求,返回耗时(秒)。"""
    start = time.time()
    await client.chat.completions.create(
        model="qwen2.5-7b",
        messages=[{"role": "user", "content": prompt}],
        max_tokens=200,
    )
    return time.time() - start


async def benchmark(concurrency: int = 10, total_requests: int = 100):
    """
    并发压测:同时发送 concurrency 个请求,观察吞吐量和延迟。
    通过调整 concurrency,找到服务的最佳并发数。
    """
    prompts = ["用 100 字介绍机器学习"] * total_requests
    semaphore = asyncio.Semaphore(concurrency)  # 控制最大并发数

    async def bounded_request(prompt):
        async with semaphore:
            return await single_request(prompt)

    start = time.time()
    latencies = await asyncio.gather(*[bounded_request(p) for p in prompts])
    total_time = time.time() - start

    print(f"并发数: {concurrency}")
    print(f"总请求数: {total_requests}")
    print(f"总耗时: {total_time:.1f}s")
    print(f"吞吐量: {total_requests / total_time:.1f} req/s")
    print(f"P50 延迟: {sorted(latencies)[len(latencies)//2]:.1f}s")
    print(f"P99 延迟: {sorted(latencies)[int(len(latencies)*0.99)]:.1f}s")


asyncio.run(benchmark(concurrency=20, total_requests=100))

1.8 vLLM vs Ollama 选型

维度 Ollama vLLM
目标场景 开发者本地使用 生产高并发服务
安装难度 极低(一键安装) 中(需要 Docker + CUDA)
并发能力 低(串行) 高(Continuous Batching)
显存利用率 一般 高(PagedAttention)
量化支持 GGUF AWQ / GPTQ
OpenAI 兼容
Windows 支持 否(需要 Linux)
监控和指标 内置 Prometheus 指标

1.9 小结

vLLM 的核心优势来自两个技术创新:PagedAttention 把显存利用率从 60% 提升到 90%+,Continuous Batching 让 GPU 始终保持满负荷运行。这两者共同带来了 10-30 倍于 Ollama 的吞吐量提升。

生产部署的关键配置:--tensor-parallel-size 根据 GPU 数量设置,--max-model-len 根据业务需要和显存限制权衡,--gpu-memory-utilization 默认 0.9 通常是最优值,不建议调到更高(留出一些余量给 CUDA 运行时)。

下一章讨论 AI Agent 的安全与对齐问题,这是生产环境中最容易被忽视、但一旦出问题就非常严重的方向。

本页目录