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就是干这个的。它内部维护了一个请求队列,可以同时处理多个请求,互不阻塞。

我画了一张图,帮你理解两者的区别:

同步推理 请求1 请求2(等待) → 处理中 → 排队中 异步推理 请求1 请求2 → 并行处理 → 并行处理 核心区别 同步:一个请求处理完,下一个才能开始。适合离线批量、调试场景。 异步:多个请求同时处理,互不阻塞。适合线上API服务、实时交互场景。 性能:异步模式下,vLLM通过continuous batching技术,吞吐量可提升3-5倍。

那么,怎么用异步推理呢?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的内存管理机制,看看它是怎么做到“显存不够也能跑大模型”的。


专注资料整理