"], # 遇到代码块结束就停 repetition_penalty=1.1 # 防止重复写同一个函数 ) # 创意写作场景:要多样性 creative_params = SamplingParams( temperature=0.8, top_p=0.9, max_tokens=1024, presence_penalty=0.3 # 鼓励引入新概念 )

我的经验:做代码生成时,repetition_penalty设到1.05-1.15就够了。设太高(比如1.5),模型会开始胡言乱语。我曾经踩过这个坑,生成了一堆语法错误但「绝不重复」的代码。

3.2 批量处理中的性能调优

离线批处理的核心优势就是吞吐量。但很多人一上来就塞几千条数据,结果发现速度反而慢了。为什么会这样?因为vLLM的批处理不是简单的「把数据堆在一起」。它背后有一套复杂的调度机制。

我总结了三个调优方向:

  1. 批大小(batch size)不是越大越好

    vLLM内部有动态batching。你设的batch size只是「最大允许值」。实际运行时,vLLM会根据GPU显存自动调整。我建议从32开始,逐步增加到128或256,观察显存占用和吞吐量。

  2. max_num_seqs 和 max_num_batched_tokens

    这两个参数直接控制vLLM的调度行为。max_num_seqs控制同时处理的序列数,max_num_batched_tokens控制一次前向传播能处理的最大token数。

  3. 使用异步提交

    别用同步循环。用asyncio或者多线程来提交请求,让vLLM的调度器自己决定怎么合并批次。

来看一个性能调优的配置示例:

from vllm import LLM

llm = LLM(
    model="Qwen/Qwen2.5-7B-Instruct",
    max_num_seqs=256,          # 同时处理256个序列
    max_num_batched_tokens=8192, # 每次前向传播最多处理8192个token
    gpu_memory_utilization=0.9,  # 显存利用率
    enable_prefix_caching=True,  # 开启前缀缓存,长文本场景很有用
)

# 异步提交请求
async def batch_generate(prompts):
    tasks = [llm.generate_async(p) for p in prompts]
    results = await asyncio.gather(*tasks)
    return results

注意:enable_prefix_caching在vLLM 0.4.0之后才稳定。如果你用的是旧版本,建议升级。我曾经在生产环境里因为没开这个,处理长文本时显存直接爆了。

3.3 理解vLLM的异步处理机制

vLLM的异步机制,说白了就是「一边生成,一边调度」。它不像传统推理框架那样,等一个请求完全结束才处理下一个。vLLM用的是连续批处理(continuous batching)

我画了一张图,帮你理解这个流程:

vLLM 异步批处理流程 请求队列 [req1, req2, ...] 调度器 动态合并批次 当前批次 [req1, req3, req5] 模型推理 前向传播 输出 部分/完整结果 完成请求移出,新请求加入

这个机制的好处是:当一个请求生成了第一个token,它就可以被「踢出」当前批次,让新请求进来。这样GPU的利用率始终是满的。我在项目中测试过,相比传统批处理,连续批处理能把吞吐量提升2-3倍。

3.4 处理长文本输入

长文本是离线批处理的噩梦。你想想看,一个请求的输入是10k tokens,另一个是100 tokens。如果硬塞在一起,短的请求要等长的跑完,效率极低。

vLLM提供了几个解决方案:

  • 动态序列长度分组:vLLM会自动把长度相近的请求分到同一批次。你不需要手动排序。
  • 前缀缓存(Prefix Caching):如果多个请求共享相同的前缀(比如系统提示词),vLLM会缓存这部分计算结果。我建议在长文本场景下一定要开启。
  • 分块预填充(Chunked Prefill):把长文本的预填充阶段拆成多个小块,避免一次性占用太多显存。

实战建议:如果你要处理大量长文本(比如论文摘要、法律文档),我建议先做预处理:把输入截断到模型的最大上下文长度。vLLM虽然支持长文本,但超过上下文长度后,生成质量会断崖式下降。

来看一个处理长文本的完整示例:

from vllm import LLM, SamplingParams

llm = LLM(
    model="Qwen/Qwen2.5-7B-Instruct",
    max_model_len=32768,          # 设置模型最大上下文
    enable_prefix_caching=True,   # 开启前缀缓存
    gpu_memory_utilization=0.85,  # 给长文本留点余量
)

# 长文本输入
long_prompts = [
    "请总结以下论文:" + "A" * 15000,  # 15k tokens
    "请总结以下论文:" + "B" * 12000,  # 12k tokens
]

params = SamplingParams(
    temperature=0.3,
    max_tokens=2048,
)

# 批量生成
outputs = llm.generate(long_prompts, params)

for output in outputs:
    print(output.outputs[0].text[:100])

避坑指南:我曾经在处理一批法律文档时,发现显存占用忽高忽低。后来排查发现,是因为文档长度差异太大(从500到20000 tokens)。解决方案是:按长度分组,分批处理。vLLM虽然能自动调度,但极端长度差异还是会拖慢整体速度。

好了,这一章的内容就到这里。离线批处理的进阶技巧,说白了就是理解vLLM的调度机制,然后根据你的场景去调参。下一章我们会进入在线服务部分,到时候你会看到,同样的模型,部署方式一变,调优思路又完全不同了。


公众号:蓝海资料掘金营,微信deep3321