快速上手:加载第一个模型

说实话,第一次用 vLLM 加载模型的时候,我心里还挺忐忑的。毕竟之前用其他推理框架,光是装依赖就能折腾半天。但 vLLM 给我的第一印象就是——干净利落。

今天咱们就拿 Qwen2.5-7B 来开刀。这个模型不大不小,刚好适合练手。你想想看,7B 参数在消费级显卡上也能跑,我自己的 4090 就跑得挺欢。

环境准备

先确认你装了 vLLM。我个人习惯用 pip 安装:

pip install vllm

嗯,这里要注意。如果你用的是 CUDA 11.8 或 12.1,vLLM 会自动匹配对应的 wheel 包。我遇到过有人用 CUDA 11.7 死活装不上,后来才发现是版本不匹配。

加载模型

加载模型就一行代码。但这一行背后,vLLM 做了很多事:

from vllm import LLM

llm = LLM(model="Qwen/Qwen2.5-7B-Instruct")

运行这段代码,你会看到控制台刷刷刷地输出日志。它先下载模型(如果本地没有),然后分配显存,最后初始化推理引擎。整个过程大概 30 秒到 1 分钟,取决于你的网络和硬盘速度。

小技巧: 如果你只是想测试,可以加个 dtype="float16" 参数。这样显存占用直接减半,我经常在调试时这么干。

单次推理(离线推理)

模型加载好了,咱们来问它一个问题。vLLM 的离线推理接口非常直观:

prompt = "请用一句话解释什么是大语言模型"
output = llm.generate(prompt)

print(output[0].outputs[0].text)

输出结果:

大语言模型是一种基于深度学习的人工智能模型,能够理解和生成人类语言。

你看,就这么简单。但这里有个坑——llm.generate() 返回的是一个列表,哪怕你只传了一个 prompt。我第一次用的时候就忘了取索引,直接打印整个对象,结果看到一堆乱码。

注意: llm.generate() 返回的是 RequestOutput 对象列表。每个 prompt 对应一个 RequestOutput,里面又包含多个 CompletionOutput(如果你设置了 n>1)。

理解输出结构

咱们来拆解一下这个输出对象。说白了,vLLM 的输出是一个三层嵌套结构:

output = llm.generate(prompt)

# 第一层:RequestOutput
request = output[0]

# 第二层:CompletionOutput(可能有多个)
completion = request.outputs[0]

# 第三层:具体内容
print(completion.text)        # 生成的文本
print(completion.token_ids)   # token ID 列表
print(completion.logprobs)    # 每个 token 的对数概率

为什么会这样设计?因为 vLLM 支持批量推理和多次采样。你想想看,如果你一次传了 10 个 prompt,每个 prompt 生成 3 个候选结果,那输出就是 10×3 的矩阵。这种结构刚好能容纳所有组合。

token 和 logprobs

token 是模型理解的最小单位。一个中文词可能被拆成 1 到 3 个 token。比如「大语言模型」这四个字,在 Qwen2.5 的 tokenizer 里会被拆成:

['大', '语言', '模型']

嗯,这里要注意。中文的 token 化跟英文不一样。英文一个单词通常是一个 token,但中文一个词可能被拆得更细。我在项目中遇到过,同样的 prompt,中文比英文多占 30% 的 token 长度。

logprobs 是每个 token 的对数概率。数值越大,说明模型对这个 token 越有把握。比如:

token: '大'    logprob: -0.12
token: '语言'  logprob: -0.08
token: '模型'  logprob: -0.15

你看,「语言」的 logprob 最高(-0.08),说明模型最确定这个词。而「模型」的 logprob 稍低,可能模型在「模型」和「系统」之间犹豫了一下。

实战经验: logprobs 在调试时特别有用。我曾经用它来检测模型是否在「胡编乱造」——如果某个 token 的 logprob 突然变得很低(比如 -5 以下),那大概率是模型开始跑偏了。

完整示例代码

最后,给你一个完整的离线推理脚本。我习惯把参数都显式写出来,这样调试时一目了然:

from vllm import LLM, SamplingParams

# 初始化模型
llm = LLM(
    model="Qwen/Qwen2.5-7B-Instruct",
    dtype="float16",
    max_model_len=4096
)

# 设置采样参数
sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=512,
    logprobs=5  # 返回 top-5 的 logprobs
)

# 准备 prompt
prompts = [
    "什么是机器学习?",
    "解释一下反向传播算法"
]

# 批量推理
outputs = llm.generate(prompts, sampling_params)

# 遍历结果
for i, output in enumerate(outputs):
    print(f"Prompt {i+1}: {output.prompt}")
    for completion in output.outputs:
        print(f"生成: {completion.text}")
        print(f"Token IDs: {completion.token_ids[:10]}...")
        print(f"Top logprobs: {completion.logprobs[0]}")
        print("-" * 50)

运行这段代码,你会看到每个 prompt 的生成结果,以及前几个 token 的 logprobs。我个人习惯把 logprobs 打印出来,看看模型在每个 token 上的置信度分布。

避坑指南: 我曾经把 max_tokens 设得太大(比如 4096),结果显存直接爆了。建议根据你的显存大小来设置,一般 7B 模型 512 到 1024 个 token 就够用了。

知识体系图

下面这张图总结了本章的核心逻辑。从加载模型到理解输出,每一步都有对应的数据结构和参数:

vLLM 离线推理流程 加载模型 LLM(model="...") 设置采样参数 SamplingParams(...) 执行推理 llm.generate() 输出 输出结构详解 RequestOutput prompt + outputs 列表 CompletionOutput text + token_ids + logprobs text: 生成的文本 token_ids: Token ID 列表 logprobs: 对数概率 每个 prompt 对应一个 RequestOutput 每个 RequestOutput 包含 n 个 CompletionOutput(n 由 SamplingParams 决定) 每个 CompletionOutput 包含 text、token_ids、logprobs 三个字段 关键参数:temperature(随机性)| top_p(核采样)| max_tokens(最大长度)| logprobs(返回概率数)

这张图把整个流程串起来了。从左到右,先加载模型,再设置参数,然后执行推理,最后解析输出。输出结构是三层嵌套,每一层都有明确的用途。

好了,现在你已经掌握了 vLLM 离线推理的基本操作。加载模型、单次推理、理解输出结构——这三步走完,你就正式入门了。剩下的就是多跑几个例子,熟悉各种参数的效果。

专注资料整理