离线批处理基础:离线推理的概念
离线批处理,说白了就是「批量干活」。
我刚开始接触大模型时,总觉得在线服务才是王道。后来发现,很多场景根本不需要实时响应。比如数据分析、内容审核、批量翻译——这些任务你等个几秒甚至几分钟都无所谓。
离线推理的核心就一句话:把数据准备好,让模型一次性处理完。没有网络延迟,没有并发压力,你只需要关心吞吐量和成本。
嗯,这里要注意:离线不等于低效。恰恰相反,离线批处理往往能榨干 GPU 的每一分算力。
离线推理 vs 在线推理
| 维度 | 离线推理 | 在线推理 |
|---|---|---|
| 响应时间 | 秒级到分钟级 | 毫秒级 |
| 吞吐量 | 高(批量处理) | 低(单条处理) |
| 资源利用率 | 高 | 低(有闲置) |
| 适用场景 | 数据分析、批量生成 | 聊天机器人、实时翻译 |
使用 LLM 类加载模型
vLLM 里加载模型,核心就是 LLM 这个类。我个人习惯把它叫做「模型管家」——你给它一个模型名字,它帮你搞定一切。
from vllm import LLM
# 加载模型
llm = LLM(model="Qwen/Qwen2.5-7B-Instruct")
就这么简单?对,就这么简单。但背后 vLLM 做了很多事:
- 自动检测 GPU 显存
- 分配 KV cache 空间
- 启用 PagedAttention 优化
- 自动选择量化方式(如果有)
我在项目中遇到过一个问题:显存不够用。后来发现,vLLM 默认会预留一部分显存给 KV cache。你可以通过 gpu_memory_utilization 参数调整:
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
gpu_memory_utilization=0.9 # 默认0.9,可以调到0.95
)
注意:别把 gpu_memory_utilization 设成 1.0。我曾经这么干过,结果模型加载到一半直接 OOM。留点余量给系统和其他进程,安全第一。
单条文本生成
模型加载好了,接下来就是生成。单条文本生成是最基础的操作:
# 单条生成
output = llm.generate("请用一句话介绍深度学习")
print(output[0].outputs[0].text)
你可能会问:为什么返回的是一个列表?因为 vLLM 的设计哲学就是「一切皆批处理」。即使你只传一条文本,它也会包装成列表处理。
输出的结构是这样的:
# output 是一个 RequestOutput 对象列表
# 每个 RequestOutput 包含:
# - request_id: 请求ID
# - prompt: 输入文本
# - outputs: CompletionOutput 列表
# - text: 生成的文本
# - token_ids: 生成的 token ID 列表
# - cumulative_logprob: 累计对数概率
我记得第一次用的时候,被这个嵌套结构搞晕了。后来写了个小工具函数:
def get_text(output):
return output[0].outputs[0].text
print(get_text(output))
批量文本生成
离线批处理的精髓来了。批量生成,说白了就是一次喂给模型多条文本:
prompts = [
"什么是机器学习?",
"解释一下 Transformer 架构",
"Python 和 Java 有什么区别?",
"写一首关于秋天的诗",
"如何优化数据库查询?"
]
outputs = llm.generate(prompts)
for i, output in enumerate(outputs):
print(f"问题 {i+1}: {prompts[i]}")
print(f"回答: {output.outputs[0].text}")
print("-" * 50)
你想想看,如果一条一条处理,每次都要重新加载模型、分配显存。批量处理就不一样了——vLLM 会把多条请求合并成一个 batch,一次性喂给 GPU。
经验之谈:批量大小不是越大越好。我试过把 1000 条文本一次性丢进去,结果显存爆了。建议先小批量测试,比如 16 条、32 条,逐步增加。vLLM 会自动做动态批处理,但你得给它留够空间。
理解生成参数
生成参数,说白了就是控制模型「怎么说话」的旋钮。我重点讲三个最常用的:temperature、top_p、max_tokens。
temperature:控制随机性
temperature 的取值范围是 0 到 2(甚至更高)。它控制模型输出的「冒险程度」:
- 低温度(0.1 - 0.3):输出保守、确定性强。适合事实性问答、代码生成。
- 中温度(0.7 - 0.9):输出有创意但不过分。适合对话、故事生成。
- 高温度(1.0 以上):输出随机性强,可能跑偏。适合创意写作、头脑风暴。
为什么会这样?因为 temperature 影响的是 softmax 函数的「陡峭程度」。温度越低,概率分布越尖锐,模型越倾向于选最高概率的词。
from vllm import SamplingParams
# 低温度:确定性输出
params_deterministic = SamplingParams(temperature=0.1, max_tokens=100)
# 中温度:平衡输出
params_balanced = SamplingParams(temperature=0.7, max_tokens=100)
# 高温度:创意输出
params_creative = SamplingParams(temperature=1.2, max_tokens=100)
我的习惯:做代码生成时用 0.1,做文案生成时用 0.8。如果发现输出太死板,就调高一点;太发散,就调低一点。没有绝对正确的值,只有适合你场景的值。
top_p:核采样
top_p 是另一种控制随机性的方式。它做的事情是:只保留累积概率达到 p 的 top 词,然后重新归一化。
举个例子:假设模型预测下一个词的概率分布是:
- "是":0.5
- "为":0.3
- "在":0.1
- "有":0.05
- "对":0.05
如果 top_p=0.9,那么只保留「是」「为」「在」这三个词(累积概率 0.9),其他词被丢弃。
我个人习惯把 top_p 和 temperature 搭配使用:
# 常用组合
params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=200
)
避坑指南:我曾经同时把 temperature 设得很高(1.5)又把 top_p 设得很低(0.5),结果模型输出乱七八糟。后来才明白,这两个参数是互补的——一个控制概率分布的「形状」,一个控制「候选词范围」。建议先固定一个,调另一个。
max_tokens:控制输出长度
max_tokens 是最直观的参数——限制模型最多生成多少个 token。
注意:token 不是字。中文里一个 token 大约 0.5-1 个字。所以 max_tokens=100 大概对应 50-100 个汉字。
# 短输出
params_short = SamplingParams(max_tokens=50)
# 中等输出
params_medium = SamplingParams(max_tokens=200)
# 长输出
params_long = SamplingParams(max_tokens=1024)
嗯,这里有个坑:max_tokens 设得太小,输出可能被截断;设得太大,浪费算力。我一般根据任务预估:
| 任务类型 | 建议 max_tokens |
|---|---|
| 短问答 | 50 - 100 |
| 代码生成 | 200 - 500 |
| 文章摘要 | 100 - 300 |
| 长文本生成 | 512 - 2048 |
完整示例:批量生成 + 参数控制
最后,给你一个完整的实战代码:
from vllm import LLM, SamplingParams
# 1. 加载模型
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
gpu_memory_utilization=0.9
)
# 2. 配置生成参数
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=200
)
# 3. 准备批量数据
prompts = [
"解释一下什么是注意力机制",
"用 Python 写一个快速排序",
"翻译成英文:今天天气真好",
"写一段产品介绍:智能手表"
]
# 4. 批量生成
outputs = llm.generate(prompts, sampling_params)
# 5. 输出结果
for i, output in enumerate(outputs):
print(f"输入: {prompts[i]}")
print(f"输出: {output.outputs[0].text}")
print("=" * 60)
核心要点回顾:
- 离线批处理适合非实时场景,能最大化 GPU 利用率
- LLM 类是 vLLM 的入口,加载模型只需一行代码
- 单条和批量生成用同一个接口,vLLM 自动处理
- temperature 控制随机性,top_p 控制候选范围,max_tokens 控制长度
- 参数没有绝对正确值,根据场景调优