4、vLLM核心概念:LLM类与SamplingParams详解、异步推理(AsyncLLMEngine)与同步推理的区别
这一章,我们来啃vLLM最核心的两个概念:LLM类和SamplingParams。说白了,这两个东西就是你用vLLM做推理时,天天都要打交道的对象。另外,同步推理和异步推理的区别,也是很多新手容易踩坑的地方。我当年刚接触vLLM时,就因为没搞懂这俩的区别,线上服务直接卡死过一次……嗯,咱们今天一次性把它讲透。
核心一句话:LLM类是模型加载和推理的入口,SamplingParams是控制生成行为的参数集合。同步推理适合调试,异步推理适合生产。
4.1 LLM类:模型推理的“总开关”
LLM类,你可以把它理解成vLLM的“大脑”。它负责加载模型、管理显存、调度推理请求。我个人习惯在项目初始化时,只创建一个LLM实例,然后在整个服务生命周期里复用。为什么?因为每次重新加载模型,光显存分配就要花好几秒,线上服务可等不起。
先看一个最基础的用法:
from vllm import LLM, SamplingParams
# 加载模型
llm = LLM(model="Qwen/Qwen2.5-7B-Instruct")
# 准备输入
prompts = ["请介绍一下vLLM", "什么是异步推理?"]
# 设置采样参数
sampling_params = SamplingParams(temperature=0.7, top_p=0.9)
# 执行推理
outputs = llm.generate(prompts, sampling_params)
# 打印结果
for output in outputs:
print(output.outputs[0].text)
你看,就这么几行代码,一个推理任务就完成了。但这里有个细节:llm.generate() 默认是同步阻塞的。也就是说,它会等所有请求都生成完,才返回结果。这在调试时没问题,但生产环境里,如果有一个长文本生成任务,其他请求就得排队等着。我曾经在一个聊天机器人项目里就吃过这个亏——用户发一条消息,结果等了30秒才回复,体验极差。
我的建议:如果你在做API服务,千万别直接用LLM类的同步generate方法。后面我会讲怎么用异步引擎来解决这个问题。
4.2 SamplingParams:控制生成行为的“遥控器”
SamplingParams,说白了就是告诉模型“怎么生成”。它控制着生成文本的多样性、长度、重复惩罚等行为。我见过不少新手,直接把temperature设成0,结果模型翻来覆去就输出那几句话。你想想看,这跟死循环有什么区别?
常用的参数我整理了一张表:
| 参数名 | 作用 | 典型值 | 我的经验 |
|---|---|---|---|
| temperature | 控制随机性,值越大越随机 | 0.7 ~ 1.0 | 创意写作用0.9,事实问答用0.3 |
| top_p | 核采样,只保留概率累积到p的token | 0.9 | 配合temperature使用效果更好 |
| max_tokens | 最大生成token数 | 512 / 1024 | 别设太大,否则显存容易爆 |
| repetition_penalty | 重复惩罚,值越大越不容易重复 | 1.1 ~ 1.2 | 写长文本时建议开启 |
| stop | 停止词,遇到这些词就停止生成 | ["\n", "。"] | 可以传列表,多个停止词 |
来看一个实际例子:
from vllm import SamplingParams
# 适合创意写作的参数
creative_params = SamplingParams(
temperature=0.9,
top_p=0.95,
max_tokens=1024,
repetition_penalty=1.15
)
# 适合事实问答的参数
factual_params = SamplingParams(
temperature=0.3,
top_p=0.8,
max_tokens=256,
stop=["\n\n", "。"]
)
注意:temperature和top_p不要同时设为极端值。比如temperature=0且top_p=0.1,模型几乎只会输出最高概率的那个token,生成内容会非常死板。我曾经调试一个代码生成模型时,就因为这个组合,模型死活只输出"print('hello')"……排查了半天才发现是参数问题。
4.3 同步推理 vs 异步推理:一个等,一个不等
同步推理,就是“发请求 -> 等结果 -> 返回”。整个过程是阻塞的。你想想看,如果同时有10个请求进来,同步模式会一个一个处理,后面的请求只能干等着。这在离线批量处理时没问题,但线上服务绝对不行。
异步推理,则是“发请求 -> 立即返回一个句柄 -> 后台慢慢处理”。vLLM的AsyncLLMEngine就是干这个的。它内部维护了一个请求队列,可以同时处理多个请求,互不阻塞。
我画了一张图,帮你理解两者的区别:
那么,怎么用异步推理呢?vLLM提供了AsyncLLMEngine类。不过说实话,直接操作AsyncLLMEngine有点复杂,它需要你手动管理请求的发送和结果收集。我个人更推荐用vLLM自带的OpenAI兼容API服务,它底层就是异步的,你只需要像调OpenAI API一样调它就行。
但如果你想自己写异步代码,可以参考这个简化版:
from vllm import AsyncLLMEngine, SamplingParams
from vllm.engine.arg_utils import AsyncEngineArgs
# 创建异步引擎
engine_args = AsyncEngineArgs(model="Qwen/Qwen2.5-7B-Instruct")
engine = AsyncLLMEngine.from_engine_args(engine_args)
# 异步生成
async def generate_async(prompt):
sampling_params = SamplingParams(temperature=0.7, max_tokens=512)
results = []
# 使用异步生成器
async for result in engine.generate(prompt, sampling_params, request_id="req_1"):
results.append(result)
return results
避坑指南:我曾经在生产环境里,直接用LLM类的同步generate方法处理并发请求。结果QPS一上来,显存直接爆了。后来换成AsyncLLMEngine,配合asyncio,同样硬件条件下,QPS从5提升到了30。所以,记住一句话:调试用同步,上线用异步。
4.4 总结:什么时候用哪个?
最后,我帮你梳理一下选择逻辑:
- 离线批量处理(比如晚上跑数据、批量生成文本):用LLM类 + 同步generate。简单直接,代码好写。
- 线上API服务(比如聊天机器人、实时翻译):用AsyncLLMEngine或OpenAI兼容API。高并发、低延迟。
- 调试和测试:用LLM类 + 同步generate。方便打印日志、打断点。
- 需要流式输出(比如打字机效果):必须用异步模式。同步模式做不到逐token返回。
嗯,这一章的内容就到这里。核心就是搞懂LLM类和SamplingParams怎么用,以及同步异步的区别。下一章我们会深入vLLM的内存管理机制,看看它是怎么做到“显存不够也能跑大模型”的。