
本周初识 vllm 框架理解 vllm 的作用和基本原理能够使用 vllm 部署语言模型服务并通过代码实战体会纯 transformer 推理和 vllm 推理的吞吐量区别。实战代码地址https://github.com/wz940216/From0to1-MLLM-StudyLogvLLM 是一个 LLM 推理/服务引擎。它跑的仍然是 Transformer 模型比如 Llama、Qwen、Mistral只是把怎么调度请求、怎么管理 KV cache、怎么高效占满 GPU这件事做得比普通 transformers.generate() 更像生产级服务系统。举个例子Transformers 单独推理像是你自己开车一趟一趟送人vLLM 像调度中心把很多乘客动态拼车并且把车内座位管理得非常细。是一个集成了显存调度、管理、模型量化、算子融合、多线程/多进程、异步请求等技术的推理引擎。vLLM 也支持 LoRA、PEFT、LoCon 等 adapter 推理。普通 Transformers 推理在做什么以 Hugging Face transformers 为例典型流程是model AutoModelForCausalLM.from_pretrained(...) tokenizer AutoTokenizer.from_pretrained(...) output model.generate(...)模型生成 token 时本质是自回归先处理 prompt计算第一步所需的 hidden states。每生成一个 token就把这个 token 再喂回模型。为了避免每一步都重算历史 token 的 Key/Value会维护 KV cache。下一步 attention 只计算当前 token 的 query但会读取历史所有 token 的 cached K/V。这对单请求、实验、debug 很方便。但如果同时服务很多用户问题就来了每个请求长度不同、生成长度未知、KV cache 动态增长GPU 内存会被浪费得很厉害。vLLM 主要解决什么问题vLLM 解决的核心是多个请求怎么高效批处理每个请求的 KV cache 怎么放进 GPU 内存请求来了、结束了、变长了怎么动态调度怎么让 GPU 尽量别空着长上下文、多并发、多采样、beam search 怎么减少内存浪费beam search 在这里是第一次出现。官方的说 beam search 是一种生成策略通常用于生成更高质量的文本。它会在每一步生成多个候选 token称为 beams并根据概率选择最优的几个 beams 继续生成。这样可以探索更多的可能性但也会增加 KV cache 的需求因为每个 beam 都需要维护自己的缓存。但其实 beam search 就是在生成 token 中保留最优的 beam size 个候选序列并且每一步只维护 beam size 个最好的结果。例如beam size 3 我 喜欢 吃 ... 我 喜欢 看 ... 我 喜欢 学习 ...beam search 每一步会比较这些候选序列的分数保留分数最高的 beam size 条。不过有个细节只用概率相乘会偏爱短句。因为每多生成一个 token就要再乘一个小于 1 的概率整体概率通常会下降。所以实际 beam search 经常会加一个 长度惩罚 / 长度归一化score log概率总和 / 长度惩罚项这样不会让模型过分倾向特别短的输出。具体算的时候通常是累计每个 token 的 log probability再配合长度惩罚等修正。vLLM 论文Efficient Memory Management for Large LanguageModel Serving with PagedAttention指出LLM serving 的瓶颈很大程度来自 KV cache。模型权重常驻显存activation 只是短暂存在而 KV cache 会随请求数和序列长度增长管理不好会限制 batch size 和吞吐。vllm核心原理PagedAttentionvLLM 最核心的思想是 PagedAttention。普通做法容易把一个请求的 KV cache 当作一整段连续内存来管理。但问题是输出长度事先不知道。输入有的请求很短有的很长。如果预留最大长度会浪费显存。如果动态申请连续显存会导致显存碎片化找不到连续大块的显存可用有涉及到显存搬移。有时多个采样分支、beam search 中有大量共享前缀但普通连续 cache 不好共享。PagedAttention 借鉴操作系统的虚拟内存分页思想将显存分成了不同个 block 单元。每个请求的 KV cache 不再要求连续而是按需分配到不同 block 中。程序上看起来还是一个连续的 token 序列但物理显存中可以分散存放在固定大小的 KV block 里。PagedAttention request A logical blocks: block 0 - physical block 17 block 1 - physical block 03 block 2 - physical block 91也就是说一个请求逻辑上还是连续的 token 序列但物理显存中可以分散存放在固定大小的 KV block 里。这种分配的好处是按需分配生成到哪里KV block 分配到哪里动态申请动态释放。减少内部碎片不用为最大输出长度提前占满没占满一个 block 后分配下一个也就最后一块 block 可能出现内部碎片。减少外部碎片所有 block 大小一致更容易复用外部不需要一块很大的且连续的存储块。支持共享共享 prompt、beam search、多采样时多个序列可以引用同一批 KV block。c- opy-on-write分支需要改写时再将需要改的 block 复制一份改复制的那一份即可其他 block 不需要改变仍可复用。continuous batching普通 batching 常见做法是收集一批请求一起跑完。问题是 LLM 生成长度不同request A: 生成 10 token request B: 生成 300 token request C: 生成 50 token如果等整批结束短请求会被长请求拖住如果 padding 到一样长计算又浪费。vLLM 使用更细粒度的调度通常称为 continuous batching / iteration-level scheduling做法是这样的假如有四个 batch 请求A/B/C/Dstep 1: A B C D 一起生成 [A B C D]step 2: A 完成后将 E 加入 [A B C D] - [B C D E]step 3: C 完成F 加入 [B C D E] - [B D E F]step 4: B D E F 继续生成也就是每个 decoding step 都可以重新组织 batch。vLLM 架构中 engine core 会运行 scheduler管理 KV cache并协调 GPU worker 执行模型。vLLM 的优点1、吞吐高多并发下优势明显。原论文报告在评估场景中相对 FasterTransformer、Orca 达到 2-4 倍吞吐提升尤其长序列、大模型、复杂 decoding 更明显。2、显存利用率高PagedAttention 减少 KV cache 浪费。3、适合在线服务支持 OpenAI-compatible API server。4、调度能力强continuous batching、preemption、chunked prefill 等。5、长上下文更友好KV cache 是长上下文推理的主要压力点之一vLLM 正是围绕它优化。6、生产能力多tensor parallel、pipeline parallel、quantization、LoRA、prefix caching、speculative decoding、metrics 等。7、共享前缀省显存相同 prompt、多采样、beam search 等场景收益明显。vLLM 的缺点1、不是所有模型都支持Transformers 通常是模型支持最全的地方vLLM 对模型结构、attention 实现、tokenizer、特殊 forward 逻辑有要求。2、部署复杂度更高服务进程、GPU worker、scheduler、显存参数、并行参数都要调。3、小并发未必划算如果只是本地单条 prompt 测试Transformers 更轻、更直观。4、调参敏感max_num_batched_tokens、max_num_seqs、gpu_memory_utilization、并行策略会影响 TTFT、ITL、吞吐、OOM 风险。5、调试模型内部更麻烦研究时想改 attention、插 hook、看 hidden statesTransformers 更顺手。6、预占显存明显vLLM 通常会预留较多显存给 KV cache。极端情况下会 preempt/recompute也就是当显存不够时KV cache 空间不足会抢占请求为了不 OOM, 先暂停某些请求并释放它们的 KV cache后续再重算这能保证系统健壮性但会影响端到端延迟。为什么 vllm 不将 KV cache 先临时放到 CPU 内存后续再搬回 GPU 呢猜测也许是 KV catch 很大在没有 NVLink 的情况下搬运数据也比较占资源。什么时候用哪个用 Transformers 直接推理研究、实验、调模型结构。单用户、低并发。模型很新vLLM 暂不支持。需要完全控制 forward、logits、hidden states。本地简单 demo。用 vLLM要部署 API 服务。多用户并发。需要高吞吐、低成本。prompt/output 长度差异大。长上下文、多采样、beam search。想要 OpenAI-compatible server。要做生产监控、并行、量化、LoRA serving。vllm 应用vllm 的调用方式和 transformers 很像都是通过generate()来生成 token。一个简单的例子defbenchmark_vllm(args:argparse.Namespace)-BenchResult:fromvllmimportLLM,SamplingParams# 加载原始模型的 tokenizervllm 也有自己的 tokenizer但为了和 transformers 保持一致这里直接用原始模型的 tokenizertokenizerload_tokenizer(args.model_path)promptbuild_chat_prompt(tokenizer,args.prompt)sampling_paramsSamplingParams(max_tokensargs.max_new_tokens,temperatureargs.temperatureifargs.do_sampleelse0.0,top_pargs.top_p,)# vllm 的 LLM 类是一个高性能的推理引擎支持多并发、显存管理、量化等特性llmLLM(modelargs.model_path,tokenizerargs.model_path,trust_remote_codeTrue,dtypeargs.dtype,tensor_parallel_sizeargs.tensor_parallel_size,max_num_seqsargs.concurrency,gpu_memory_utilizationargs.gpu_memory_utilization,)ifargs.warmup0:llm.generate([prompt]*args.warmup,sampling_params,use_tqdmFalse,)prompts[prompt]*args.num_requests starttime.perf_counter()# generate() 方法会返回一个列表每个元素是一个生成结果对象包含生成的 token ids、logits 等信息outputsllm.generate(prompts,sampling_params,use_tqdmFalse)elapsedtime.perf_counter()-start output_tokenssum(len(item.outputs[0].token_ids)foriteminoutputs)returnBenchResult(backendvllm,num_requestsargs.num_requests,concurrencyargs.concurrency,elapsed_secelapsed,qpsargs.num_requests/elapsed,output_tokensoutput_tokens,tokens_per_secoutput_tokens/elapsed,)也可以起一个 server defrun_vllm_server(args:argparse.Namespace)-None:command[sys.executable,-m,vllm.entrypoints.openai.api_server,--model,args.model_path,--tokenizer,args.model_path,--served-model-name,args.served_model_name,--host,args.host,--port,str(args.port),--dtype,args.dtype,--tensor-parallel-size,str(args.tensor_parallel_size),--gpu-memory-utilization,str(args.gpu_memory_utilization),--trust-remote-code,]print(Starting vLLM server:)print( .join(command))subprocess.run(command,checkTrue)更多实战代码见仓库。以上笔记来源于我的仓库 https://github.com/wz940216/From0to1-MLLM-StudyLog.git我正在连载一个从零到一的多模态大模型学习笔记。如果你对多模态大模型感兴趣或者也在准备往大模型方向转可以点赞/Fork我的仓库 https://github.com/wz940216/From0to1-MLLM-StudyLog.git也可评论区留言交流后面我会继续把每周的学习记录、踩坑经验陆续更新到仓库和这里。