从原理到实践:PagedAttention 与推理服务部署

前言

去年我写过一篇纯 CPU 部署知识库的文章,收到不少反馈说”终于有人关注没有 GPU 的场景了”。但说实话,CPU 推理的体验确实差强人意——2-3 tokens/s 的速度,用户问一个问题要等半分钟,这在生产环境里几乎不可接受。

后来公司采购了一批 RTX 4090,我终于有机会认真研究 GPU 推理了。折腾了两周,把市面上主流的推理框架都试了一遍,最终锁定了 vLLM。这篇文章,我想把这两周踩过的坑、学到的东西,整理分享出来,希望能帮大家少走弯路。

什么是 vLLM?

vLLM 是由加州大学伯克利分校的研究团队开发的高性能大模型推理框架。其核心创新是 PagedAttention 技术,通过借鉴操作系统的虚拟内存分页机制来管理 KV Cache,大幅提升了显存利用率和推理吞吐量。目前 vLLM 已成为业界最流行的开源推理框架之一。


为什么选择 vLLM

先说结论:vLLM 在显存管理上的创新,确实带来了显著的性能提升。

我最初用的是 Hugging Face Transformers 原生推理。代码简单,model.generate() 一行搞定。但当我把服务上线后,问题来了——10 个用户同时访问,服务响应变慢。查了半天发现,原生推理是串行的,一个请求没处理完,后面的全在排队。

然后我试了 Text Generation Inference (TGI),Hugging Face 官方出品。确实比原生快很多,但配置相对复杂。更关键的是,我发现显存利用率还有提升空间——24GB 的 4090,跑个 7B 模型占了 18GB。

直到我遇到 vLLM,才明白其中的优化空间。

KV Cache 与显存占用

要理解 vLLM 为什么快,得先理解一个概念:KV Cache

KV Cache 是什么?

Transformer 模型在生成每个 token 时,需要计算自注意力(Self-Attention)。为了避免对历史 token 重复计算 Key 和 Value 向量,模型会把它们缓存起来,这就是 KV Cache。KV Cache 是一种典型的”空间换时间”策略。

问题是,KV Cache 的显存占用是动态的——输出越长,占用越大。

传统框架怎么处理的?预分配。假设你设置 max_length=2048,框架会一次性为每个请求分配 2048 个 token 的 KV Cache 空间,不管实际输出是 100 个还是 2000 个。

这导致了两个问题:

  • 显存浪费:大部分请求输出都很短,预分配的空间大量闲置
  • 并发受限:显存被少数长请求占满,新请求进不来

PagedAttention 的核心思想

vLLM 的创始团队来自伯克利,他们提出了一个想法:把操作系统的虚拟内存分页机制用到 KV Cache 管理上

传统方式像是给每个人分配一间固定大小的房间,不管你住不住得满。PagedAttention 则是把显存切成固定大小的”页”(比如 16 个 token 一页),按需分配。用完一页再分配下一页,释放时也是按页回收。

这带来了三个改进:

  • 显存利用率提升:不再有预分配浪费,每个 token 都物尽其用

  • 并发能力提升:同样的显存可以服务更多请求,因为短请求释放的页可以立即给新请求用

  • 支持复杂的采样策略:Beam search、parallel sampling 这些需要同时维护多个序列的场景,共享相同前缀的 KV Cache,显存占用大幅降低

简单来说:vLLM 让显存分配更加灵活高效。


环境准备与注意事项

硬件选择建议

我的测试环境:

  • GPU:NVIDIA RTX 4090 24GB
  • CPU:Intel i9-13900K(24核32线程)
  • 内存:64GB DDR5-5600
  • 存储:2TB PCIe 4.0 NVMe SSD

但这不是最优解。如果是企业用户,A10 (24GB)L4 (24GB) 也是不错的选择——价格适中,稳定性更好,而且支持 ECC 内存。

显存容量估算公式

所需显存 (GB) ≈ 模型参数量 (B) × 2 × (1 + 0.1 × 最大并发数)

以 7B 模型为例:

  • 单请求:7 × 2 × 1.1 ≈ 15.4GB
  • 10 并发:7 × 2 × 2 = 28GB(需要更大显存或量化)

软件环境与 CUDA 版本

# 第一步:确认 CUDA 版本
nvidia-smi
# 输出的 CUDA Version 是驱动支持的最高版本,不是实际安装版本

nvcc --version
# 这才是实际安装的 CUDA 版本

nvidia-smi 与 nvcc 的区别

nvidia-smi 显示的 CUDA Version 是 驱动支持的最高版本,不代表你已安装该版本。nvcc --version 才是实际安装的 CUDA Toolkit 版本。很多人被这个坑过。

vLLM 对 CUDA 版本要求严格

  • vLLM 0.4.x:需要 CUDA 12.1+
  • vLLM 0.5.x:需要 CUDA 12.4+(推荐)

我第一次安装时遇到过问题:服务器上有 CUDA 11.8,nvidia-smi 显示”CUDA Version: 12.2”,我以为没问题,结果 vLLM 报错。后来才知道那个 12.2 只是驱动支持的最高版本,实际用的还是 11.8。

正确的安装流程

# 1. 卸载旧版 CUDA(如果有)
sudo apt remove --purge cuda*
sudo apt autoremove

# 2. 安装 CUDA 12.4(以 Ubuntu 22.04 为例)
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt update
sudo apt install cuda-toolkit-12-4

# 3. 配置环境变量
echo 'export PATH=/usr/local/cuda-12.4/bin:$PATH' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
source ~/.bashrc

# 4. 验证
nvcc --version  # 应该显示 12.4

安装 vLLM

# 创建干净的虚拟环境
conda create -n vllm python=3.10 -y
conda activate vllm

# 安装 vLLM(会自动安装匹配的 PyTorch)
pip install vllm

# 验证安装
python -c "import vllm; print(f'vLLM version: {vllm.__version__}')"
python -c "import torch; print(f'PyTorch CUDA: {torch.version.cuda}')"

如果安装失败,大部分情况是 CUDA 版本问题。


快速上手

离线批量推理示例

先从最简单的场景开始——批量处理一组 prompt:

from vllm import LLM, SamplingParams
import time

# 初始化模型(第一次会从 HuggingFace 下载)
print("Loading model...")
start = time.time()
llm = LLM(
    model="Qwen/Qwen2.5-7B-Instruct",
    trust_remote_code=True,  # Qwen 模型需要这个
    dtype="float16",         # 半精度,平衡速度和精度
)
print(f"Model loaded in {time.time() - start:.1f}s")

# 采样参数
sampling_params = SamplingParams(
    temperature=0.7,    # 控制随机性,0=确定性,1=高随机
    top_p=0.9,          # 核采样,只从概率累积达到 90% 的 token 中采样
    max_tokens=512,     # 最大生成长度
    stop=["<|im_end|>"] # 停止符
)

# 准备一批 prompt
prompts = [
    "用简单的语言解释量子计算",
    "写一首关于编程的俳句",
    "Python 的 GIL 是什么?为什么它是个问题?",
    "解释为什么天空是蓝色的",
    "如何向一个 5 岁小孩解释人工智能",
]

# 批量推理
print(f"\nProcessing {len(prompts)} prompts...")
start = time.time()
outputs = llm.generate(prompts, sampling_params)
elapsed = time.time() - start

# 统计
total_tokens = sum(len(o.outputs[0].token_ids) for o in outputs)
print(f"\nGenerated {total_tokens} tokens in {elapsed:.1f}s")
print(f"Throughput: {total_tokens / elapsed:.1f} tokens/s")

# 打印结果
for output in outputs:
    print(f"\n{'='*60}")
    print(f"Prompt: {output.prompt[:50]}...")
    print(f"Response: {output.outputs[0].text[:200]}...")

运行这段代码,你会看到类似这样的输出:

Loading model...
Model loaded in 12.3s

Processing 5 prompts...

Generated 1847 tokens in 4.8s
Throughput: 384.8 tokens/s

384 tokens/s。相比 CPU 推理的 2-3 tokens/s,提升非常明显。

除了批量推理,vLLM 也支持在线服务

OpenAI 兼容 API

vLLM 内置了一个 OpenAI API 兼容服务器,这意味着你现有的代码几乎不用改就能迁移过来:

# 启动服务
python -m vllm.entrypoints.openai.api_server \
    --model Qwen/Qwen2.5-7B-Instruct \
    --host 0.0.0.0 \
    --port 8000 \
    --trust-remote-code

服务启动后,用 OpenAI SDK 调用:

from openai import OpenAI

# 只需要改 base_url,其他代码完全不变!
client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="not-needed"  # vLLM 默认不验证 key
)

# 和调用 GPT-4 一模一样的代码
response = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=[
        {"role": "system", "content": "你是一个资深的 Python 工程师"},
        {"role": "user", "content": "如何用 Python 实现一个高性能的 Web 爬虫?"}
    ],
    temperature=0.7,
    max_tokens=1024,
    stream=True  # 支持流式输出!
)

# 流式打印
for chunk in response:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

OpenAI 兼容的好处

你的应用代码不需要知道后端是 GPT-4 还是本地模型,只需要改一个 URL。灰度切换、A/B 测试、成本优化——所有这些都变得轻而易举。这也是 vLLM 被广泛采用的重要原因之一。


性能调优经验

把 vLLM 跑起来很容易,但要在生产环境稳定运行,还需要精细调优。这是我花了最多时间的部分。

关键参数说明

python -m vllm.entrypoints.openai.api_server \
    --model Qwen/Qwen2.5-7B-Instruct \
    --dtype float16 \
    --max-model-len 8192 \
    --gpu-memory-utilization 0.90 \
    --max-num-batched-tokens 32768 \
    --max-num-seqs 256 \
    --enable-chunked-prefill

让我逐个解释这些参数:

参数 说明 建议值
--dtype 数据类型,float16/bfloat16/auto 消费卡用 float16,A100/H100 用 bfloat16
--max-model-len 最大上下文长度 按需设置,太大会 OOM
--gpu-memory-utilization 显存利用率上限 测试 0.95,生产 0.88
--max-num-seqs 最大并发数 根据显存计算
--enable-chunked-prefill 分块预填充 推荐开启

什么是 Chunked Prefill?

当一个新请求进来时,vLLM 需要先处理整个 prompt(prefill 阶段),然后才能开始生成。如果 prompt 很长(比如 4000 tokens),prefill 会阻塞其他请求。

启用 chunked-prefill 后,长 prompt 会被切成小块,穿插在其他请求之间处理。首 token 延迟可能略增,但整体吞吐量和公平性大幅提升

调优流程参考

以下是我总结的一套调优流程:

Step 1:建立基准

# 先用默认参数跑,记录性能指标
python -m vllm.entrypoints.openai.api_server \
    --model Qwen/Qwen2.5-7B-Instruct

Step 2:压力测试

# benchmark.py
import asyncio
import aiohttp
import time

async def send_request(session, prompt):
    start = time.time()
    async with session.post(
        "http://localhost:8000/v1/chat/completions",
        json={
            "model": "Qwen/Qwen2.5-7B-Instruct",
            "messages": [{"role": "user", "content": prompt}],
            "max_tokens": 256
        }
    ) as response:
        result = await response.json()
        return time.time() - start

async def benchmark(concurrency=10, total_requests=100):
    prompts = ["用100字介绍Python语言"] * total_requests
    
    async with aiohttp.ClientSession() as session:
        start = time.time()
        tasks = [send_request(session, p) for p in prompts[:concurrency]]
        
        completed = 0
        latencies = []
        
        while tasks or completed < total_requests:
            done, tasks = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
            for task in done:
                latencies.append(task.result())
                completed += 1
                if completed < total_requests:
                    tasks.add(asyncio.create_task(
                        send_request(session, prompts[completed])
                    ))
        
        elapsed = time.time() - start
        
    print(f"Concurrency: {concurrency}")
    print(f"Total requests: {total_requests}")
    print(f"Total time: {elapsed:.1f}s")
    print(f"Throughput: {total_requests / elapsed:.1f} req/s")
    print(f"Avg latency: {sum(latencies) / len(latencies) * 1000:.0f}ms")
    print(f"P99 latency: {sorted(latencies)[int(len(latencies) * 0.99)] * 1000:.0f}ms")

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

Step 3:观察瓶颈

运行压测时,开另一个终端监控:

# 监控 GPU
watch -n 1 nvidia-smi

# 重点关注:
# - GPU 利用率:应该接近 100%
# - 显存占用:应该接近 gpu-memory-utilization 设定值
# - 功耗:满载时接近 TDP

Step 4:针对性调优

现象 瓶颈 调优方向
GPU 利用率低 批处理不够 增大 max-num-seqs
显存占用低 预留太多 增大 gpu-memory-utilization
P99 延迟高 长请求阻塞 启用 chunked-prefill
OOM 显存不够 减小 max-model-len 或量化

多卡部署配置

如果单卡性能不够,vLLM 支持多卡并行:

# 2 卡张量并行(模型切分到 2 张卡)
python -m vllm.entrypoints.openai.api_server \
    --model Qwen/Qwen2.5-72B-Instruct \
    --tensor-parallel-size 2

# 4 卡流水线并行(每张卡跑一部分层)
python -m vllm.entrypoints.openai.api_server \
    --model Qwen/Qwen2.5-72B-Instruct \
    --pipeline-parallel-size 4

张量并行 vs 流水线并行

  • 张量并行 (TP):把每一层的计算切分到多张卡,单个请求延迟敏感,适合交互场景
  • 流水线并行 (PP):把不同的层分到不同的卡,吞吐量优先,适合批处理场景
  • 混合并行:大模型 + 多卡,比如 72B 模型用 TP=2 × PP=2

我的 8×4090 集群配置是 72B 模型用 TP=4,剩下 4 张卡跑 4 个独立的 7B 实例做负载均衡。


RAG 集成示例

单独的 LLM 就像一个聪明但健忘的人——它知道很多通用知识,但对你公司的内部文档一无所知。RAG(检索增强生成)就是给它装上”外部记忆”。

什么是 RAG?

RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合检索和生成的技术。先从知识库中检索相关文档,再把检索结果作为上下文输入给 LLM,让模型基于这些”外部知识”生成回答。这样可以大幅减少幻觉,并让模型回答特定领域的问题。

结合我之前的知识库方案,把推理后端换成 vLLM:

from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings

# 加载向量数据库(假设已经构建好)
embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-small-zh-v1.5",
    model_kwargs={'device': 'cuda'}  # embedding 也用 GPU 加速
)
vectorstore = FAISS.load_local(
    "faiss_index", 
    embeddings,
    allow_dangerous_deserialization=True
)

# 连接 vLLM 后端
llm = ChatOpenAI(
    base_url="http://localhost:8000/v1",
    api_key="not-needed",
    model="Qwen/Qwen2.5-7B-Instruct",
    temperature=0.7,
    max_tokens=1024
)

# 自定义 prompt 模板
prompt_template = """你是一个专业的技术顾问。请根据以下参考资料回答用户的问题。
如果参考资料中没有相关信息,请诚实地说"我不知道",不要编造答案。

参考资料:
{context}

用户问题:{question}

请用清晰、专业的语言回答:"""

PROMPT = PromptTemplate(
    template=prompt_template,
    input_variables=["context", "question"]
)

# 构建 RAG Chain
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=vectorstore.as_retriever(
        search_type="mmr",       # 最大边际相关性,避免重复
        search_kwargs={"k": 5}   # 检索 5 个相关片段
    ),
    chain_type_kwargs={"prompt": PROMPT},
    return_source_documents=True
)

# 查询
result = qa_chain.invoke({"query": "vLLM 的 PagedAttention 是如何工作的?"})

print("Answer:", result["result"])
print("\nSources:")
for doc in result["source_documents"]:
    print(f"  - {doc.metadata.get('source', 'unknown')}: {doc.page_content[:100]}...")

RAG 调优技巧

  • chunk_size 很重要:太大会稀释相关性,太小会丢失上下文。我一般用 500-800。
  • 检索数量 k 的选择:k 太小可能漏掉关键信息,k 太大会引入噪声。先从 k=5 开始。
  • MMR vs 相似度:如果检索结果重复度高,用 MMR(最大边际相关性)会更好。
  • rerank 可以锦上添花:检索后用一个小模型对结果重排序,能显著提升准确率。

生产环境部署建议

从实验环境到生产环境,还需要考虑稳定性等问题。

Docker 部署

# Dockerfile
FROM nvidia/cuda:12.4.0-devel-ubuntu22.04

# 安装 Python 和依赖
RUN apt-get update && apt-get install -y \
    python3.10 python3-pip git \
    && rm -rf /var/lib/apt/lists/*

# 安装 vLLM
RUN pip install vllm

# 预下载模型(可选,加速启动)
# RUN python -c "from huggingface_hub import snapshot_download; snapshot_download('Qwen/Qwen2.5-7B-Instruct')"

EXPOSE 8000

ENTRYPOINT ["python", "-m", "vllm.entrypoints.openai.api_server"]
CMD ["--model", "Qwen/Qwen2.5-7B-Instruct", "--host", "0.0.0.0", "--port", "8000"]
# 构建镜像
docker build -t vllm-server:latest .

# 启动容器
docker run -d \
    --gpus '"device=0"' \
    --name vllm-server \
    --restart unless-stopped \
    -p 8000:8000 \
    -v /data/models:/root/.cache/huggingface \
    -e CUDA_VISIBLE_DEVICES=0 \
    vllm-server:latest \
    --model Qwen/Qwen2.5-7B-Instruct \
    --gpu-memory-utilization 0.88 \
    --max-num-seqs 128

几个细节

  • --restart unless-stopped:容器挂了自动重启
  • -v /data/models:/root/.cache/huggingface:模型持久化,避免每次重启都下载
  • --gpu-memory-utilization 0.88:留点余量,生产环境稳定优先

多实例 + Nginx 负载均衡

单个 vLLM 实例的并发能力有限。要支撑高流量,需要多实例 + 负载均衡:

# 启动多个实例(不同端口)
for port in 8000 8001 8002 8003; do
    docker run -d \
        --gpus '"device='$((port-8000))'"' \
        --name vllm-$port \
        -p $port:8000 \
        vllm-server:latest
done
# /etc/nginx/conf.d/vllm.conf
upstream vllm_cluster {
    least_conn;  # 最少连接数负载均衡
    
    server 127.0.0.1:8000 weight=1 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:8001 weight=1 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:8002 weight=1 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:8003 weight=1 max_fails=3 fail_timeout=30s;
    
    keepalive 32;  # 保持长连接
}

server {
    listen 443 ssl http2;
    server_name llm-api.example.com;
    
    ssl_certificate /etc/ssl/certs/llm.pem;
    ssl_certificate_key /etc/ssl/private/llm.key;
    
    # 安全头
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    
    location /v1 {
        proxy_pass http://vllm_cluster;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Connection "";  # 启用 keepalive
        
        # 超时设置(LLM 推理可能很慢)
        proxy_connect_timeout 60s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
        
        # 缓冲设置
        proxy_buffering off;  # 流式响应需要关闭缓冲
    }
    
    # 健康检查端点
    location /health {
        proxy_pass http://vllm_cluster/health;
        proxy_connect_timeout 5s;
        proxy_read_timeout 5s;
    }
}

监控告警

# monitor.py
import requests
import time
import smtplib
from email.mime.text import MIMEText

class VLLMMonitor:
    def __init__(self, endpoints, alert_email):
        self.endpoints = endpoints
        self.alert_email = alert_email
        self.healthy = {ep: True for ep in endpoints}
    
    def check_health(self, endpoint):
        try:
            resp = requests.get(f"{endpoint}/health", timeout=10)
            return resp.status_code == 200
        except:
            return False
    
    def check_metrics(self, endpoint):
        """检查关键指标"""
        try:
            resp = requests.get(f"{endpoint}/metrics", timeout=10)
            # 解析 Prometheus 格式的指标
            metrics = {}
            for line in resp.text.split('\n'):
                if line and not line.startswith('#'):
                    parts = line.split(' ')
                    if len(parts) == 2:
                        metrics[parts[0]] = float(parts[1])
            return metrics
        except:
            return None
    
    def send_alert(self, message):
        print(f"ALERT: {message}")
        # 这里可以接入钉钉、飞书、PagerDuty 等
    
    def run(self, interval=30):
        print(f"Monitoring {len(self.endpoints)} endpoints...")
        while True:
            for endpoint in self.endpoints:
                is_healthy = self.check_health(endpoint)
                
                # 状态变化时告警
                if is_healthy != self.healthy[endpoint]:
                    if is_healthy:
                        self.send_alert(f"{endpoint} recovered")
                    else:
                        self.send_alert(f"🚨 {endpoint} is DOWN!")
                    self.healthy[endpoint] = is_healthy
                
                # 检查指标
                if is_healthy:
                    metrics = self.check_metrics(endpoint)
                    if metrics:
                        # GPU 显存使用率过高
                        gpu_util = metrics.get('vllm:gpu_cache_usage_perc', 0)
                        if gpu_util > 95:
                            self.send_alert(f"⚠️ {endpoint} GPU memory usage: {gpu_util:.1f}%")
                        
                        # 队列积压
                        pending = metrics.get('vllm:num_requests_waiting', 0)
                        if pending > 100:
                            self.send_alert(f"⚠️ {endpoint} request queue: {pending}")
            
            time.sleep(interval)

if __name__ == "__main__":
    monitor = VLLMMonitor(
        endpoints=[
            "http://localhost:8000",
            "http://localhost:8001",
        ],
        alert_email="[email protected]"
    )
    monitor.run()

性能测试数据

以下是在 RTX 4090 上的实测数据:

测试环境

  • GPU:NVIDIA RTX 4090 24GB
  • 模型:Qwen2.5-7B-Instruct
  • 输入:平均 200 tokens
  • 输出:最大 512 tokens

测试结果

指标 Transformers vLLM 提升
首 token 延迟 850ms 85ms 10x
单请求吞吐量 35 tok/s 95 tok/s 2.7x
10 并发吞吐量 42 tok/s 380 tok/s 9x
50 并发吞吐量 OOM 420 tok/s N/A
显存占用 18GB 14GB -22%

几个关键发现

  • 首 token 延迟是体验的关键:用户不在乎总耗时,在乎的是”有没有反应”。85ms 的首 token 延迟,用户几乎感觉不到等待。

  • vLLM 在高并发下优势更明显:单请求差距不大,50 并发时 Transformers 直接 OOM,vLLM 还游刃有余。

  • 显存效率是并发能力的基础:省下的 4GB 显存可以多服务 30% 的并发请求。


常见问题排查

Q1: CUDA out of memory

这是最常见的问题。解决方案按优先级:

# 1. 降低显存利用率
--gpu-memory-utilization 0.8

# 2. 减少最大序列数
--max-num-seqs 64

# 3. 缩短上下文长度
--max-model-len 4096

# 4. 使用量化模型
--model TheBloke/Qwen-7B-Chat-AWQ --quantization awq

Q2: 模型加载巨慢

首次加载需要从 HuggingFace 下载,国内网络你懂的。解决方案:

# 方案 1:设置镜像
export HF_ENDPOINT=https://hf-mirror.com

# 方案 2:提前下载到本地
huggingface-cli download Qwen/Qwen2.5-7B-Instruct --local-dir ./models/qwen

# 然后用本地路径启动
--model ./models/qwen

Q3: 输出质量不如预期

可能是采样参数没调好:

SamplingParams(
    temperature=0.7,      # 太高会胡说八道,太低会重复
    top_p=0.9,            # 和 temperature 配合使用
    repetition_penalty=1.1,  # 避免重复
    presence_penalty=0.1,    # 鼓励新话题
)

Q4: 流式输出有问题

确保 Nginx 配置正确:

proxy_buffering off;           # 关闭缓冲
proxy_cache off;               # 关闭缓存
chunked_transfer_encoding on;  # 启用分块传输

结语

写到这里,我想说的不只是 vLLM 的技术细节。

过去一年,大模型从实验室走向生产环境。我见过太多团队卡在”最后一公里”——模型效果不错,但推理成本太高、延迟太大、并发上不去。vLLM 的出现,把这道门槛大大降低了。

vLLM 降低了高性能推理的门槛,使得单卡部署也能支撑不错的并发量。

对于大模型落地,vLLM 是一个非常值得尝试的方案。它不是万能的——超长上下文、超低延迟、超大模型,都有更专业的方案。但对于 90% 的场景,它是最佳选择。


参考资料

  1. vLLM 官方文档
  2. PagedAttention 论文:Efficient Memory Management for Large Language Model Serving with PagedAttention
  3. vLLM GitHub 仓库
  4. Hugging Face Text Generation Inference

版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:《 vLLM 高性能推理系列——入门篇 》

本文链接:http://localhost:3015/ai/vLLM%E9%AB%98%E6%80%A7%E8%83%BD%E6%8E%A8%E7%90%86%E9%83%A8%E7%BD%B2.html

本文最后一次更新为 天前,文章中的某些内容可能已过时!