终于有人系统聊这个了。以前我们在本地搭大模型推理服务最头疼的就是并发一高就卡死或者显存直接OOM。后来测试了vLLM高吞吐推理引擎同样的硬件吞吐量翻了几倍显存占用还降了不少。这篇文章就把我在本地部署vLLM、把它当高并发推理服务用的一整套经验写出来包括原理、配置参数、压测结果和一堆踩坑记录给后面要搞本地推理服务的同学做个参考。1. 本地高并发推理服务为什么难搞先说背景。去年我们团队接了一个内部工具需求要给几百个用户提供基于开源大模型的文本生成能力。规则很简单用自己的服务器不用外部API数据不出内网。听起来不难真正做起来才发现问题全卡在“服务化”这一步。很多人把模型下载下来、写个Python脚本调transformers库能出结果就觉得完事了。但真实场景根本不一样。真实场景是多个用户同时发请求每个请求的输入长短不一生成的长度也不一样。用最朴素的transformers实现每一个请求都得独占一整块显存而且GPU计算的时候其他请求只能排队等着。并发到5个、10个响应延迟就开始拉胯并发到20个显存直接爆炸。最开始我们试过用FastAPI包一层异步接口每个请求起一个独立进程跑推理。结果就是GPU利用率低得可怜显存复制开销巨大一台A100恨不得同时只服务两三个请求剩下的全在排队。更离谱的是每个进程都要加载一遍模型权重700B那种大模型就不用想了哪怕是个7B的小模型四五个进程也把显存吃干了。第一版服务上线之后内部用户反馈一句话总结能跑但慢得没法用。最长一次请求等了三四分钟中间还出现过几个请求互相抢显存导致的随机OOM。那段时间我基本就是到处查“为什么并发推理这么难”、“怎么提高推理吞吐量”最后才接触到vLLM整个方案才算活过来。2. 从核心原理看懂vLLM凭什么快2.1 PagedAttention让显存不再被浪费vLLM高吞吐推理引擎最核心的底牌就是PagedAttention。这个名字的灵感来自操作系统的虚拟内存分页机制。传统推理方式会为每个请求预先分配好一整块连续的KV Cache显存空间但生成过程中实际用到多少只有模型自己知道。结果就是预分配的空间经常大量闲置显存利用率低请求一多就OOM。PagedAttention的思路是把KV Cache切分成固定大小的块按需分配不要求连续。每个请求只占它真正用到的那几块用完还可以释放。这个改动直接解决了显存碎片化的问题让我同一个GPU上能同时跑的请求数翻了好几倍。实际测试下来同样一张显卡之前并发5个请求就爆显存用上vLLM之后并发二三十个请求依然稳定。这不是什么玄学优化而是KV Cache管理方式变了每一寸显存都被尽量用起来。2.2 Continuous Batching消灭了排队空转第二个关键机制是Continuous Batching翻译过来就是连续批处理。传统batching是静态的攒够一批请求统一跑一遍等这一批全部结束后再处理下一批。问题在于每个请求的生成长度完全不一样有的生成50个token就结束了有的要生成500个token。静态batching会长时间等慢的请求快的请求完成了也只能干等着GPU算力被白白浪费。Continuous Batching不一样它能在iteration级别动态决定哪些请求进入当前batch。一个请求生成结束了立刻把它的显存和GPU计算资源腾出来让给队列里其他请求。这个过程不需要等整个batch全部结束。用个生活化的类比传统的静态batching就像在食堂排队一锅菜要等所有人打完才能炒下一锅。Continuous Batching则是来了一个就炒一个炒好一份端走一份锅子永远在干活。就这么一个机制层面的改动GPU利用率直线上升队列排队时间肉眼可见地缩短。2.3 其他不可忽视的加速手段除了上面两个杀手锏vLLM还集成了一些引擎层面的优化CUDA Graphs减少kernel launch的开销把一系列GPU操作固化成一张图一次提交全部执行。小batch场景下提升非常明显。连续批处理与投机解码vLLM内部对输出token的采样也做了并行化处理某些场景下配合投机解码Speculative Decoding还能进一步加速。底层算子优化针对常见模型结构融合了一些算子减少了显存读写次数。所以vLLM并不只是KV Cache管理做得好它是从内存管理、调度策略、kernel执行层面整体优化了一遍才能在高并发场景下保持稳定高吞吐。3. 本地部署实操从零开始跑通vLLM服务原理看再多不如实际跑一遍。下面是我在本地Linux服务器上的完整部署过程包括环境、命令、参数以及我为什么这么配。3.1 环境准备与硬件要求先说结论vLLM对硬件有一定要求但不是非得顶级卡才能玩。我的验证环境是这样的配置项我的实测环境备注GPUNVIDIA A100 40GB实际7B模型用20GB左右显存就够驱动535.104.05建议470.xx以上CUDA12.1vLLM约0.4版本之后对CUDA 12支持很成熟Python3.103.8以上都能跑3.10最稳系统Ubuntu 22.04其他Linux发行版也可以模型某7B Chat模型你也可以换自己的开源模型如果你手头是消费级显卡比如24GB显存的卡跑7B模型加量化版本也完全可行。我后面在另一台机器上用AWQ量化后的模型跑过吞吐虽然比A100低但作为内部测试服务完全够用。3.2 安装vLLM及相关依赖安装vLLM有好几种途径我推荐直接用pip安装官方预编译wheel省心省事。# 创建虚拟环境强烈建议避免污染系统Python python3 -m venv vllm_env source vllm_env/bin/activate # 安装vLLM pip install vllm # 验证安装 python -c import vllm; print(vllm.__version__)这里有个坑vLLM对Python和PyTorch版本有对应要求。如果你是conda用户建议直接用官方给出的conda依赖文件创建环境conda env create -f environment.yml conda activate vllm如果是源码编译安装那就要先装好CUDA toolkit和匹配的PyTorch。我不太建议新手走源码编译浪费时间不说还容易在算子编译阶段出幺蛾子除非你要改vLLM源码。3.3 用OpenAI兼容接口启动推理服务vLLM自带一个推理服务器而且接口是OpenAI兼容的。这意味着你原来调用OpenAI API的代码只需要改一下base_url就能直接切到本地vLLM服务上业务代码不用大改。这一点在实际落地时非常香。启动命令如下python -m vllm.entrypoints.openai.api_server \ --model /data/models/llama-7b-chat \ --served-model-name local-chat \ --host 0.0.0.0 \ --port 8000 \ --tensor-parallel-size 1 \ --max-model-len 4096 \ --gpu-memory-utilization 0.9 \ --max-num-seqs 32启动之后服务会监听8000端口。用curl测一下curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: local-chat, messages: [{role: user, content: 你好}], max_tokens: 128 }看到正常的JSON返回说明服务已经跑通了。3.4 关键启动参数逐个解读上面那条启动命令里的参数每一个都值得细说。--model指定模型路径可以是HuggingFace上的模型ID也可以是本地目录。我建议务必先下载到本地再用本地路径不然每次启动都要联网拉权重内网环境直接GG。--served-model-name是暴露给调用方的模型名字。客户端请求里的model字段要和它一致。这个名字可以随便起按你的业务命名就行。--tensor-parallel-size控制张量并行度。单卡就是1多卡就设成卡数。比如你有4张A100设成4模型权重和计算会自动切成4份跑在4张卡上。如果只有1张卡却设成2会直接报错。--max-model-len是模型能处理的最大序列长度指的是输入输出的总长度。4096够大多数场景用。设得太大KV Cache预分配得就多并发上限就会被挤压。--gpu-memory-utilization很关键。它表示vLLM最多能用多少比例的显存。0.9意味着90%的显存都归vLLM管理剩下的留给其他进程。如果你机器上还要跑别的服务这个值就得调低一点。--max-num-seqs是同时处理的最大序列数量。这个值直接决定并发上限。设成32意味着最多同时处理32个请求多余的排队等待。并不是越大越好太大会导致每个请求分到的算力变少单个请求延迟上升。最佳配置不是固定的得根据业务特点不断调。后面我会讲怎么根据实际压测结果来定。3.5 用Python客户端调用服务端跑起来之后客户端调用非常简单。因为接口是OpenAI兼容的直接用openai库就行from openai import OpenAI client OpenAI( base_urlhttp://localhost:8000/v1, api_keyEMPTY, # vLLM本地服务不校验key但字段不能少 ) completion client.chat.completions.create( modellocal-chat, messages[{role: user, content: 介绍一下vLLM}], max_tokens512, temperature0.7, ) print(completion.choices[0].message.content)这个兼容性带来的好处是线上代码切到本地服务几乎零成本。我之前写过一些调用GPT接口的业务逻辑直接改base_url就全部复用省了重写客户端的时间。4. 高并发场景下的吞吐量与延迟调优4.1 我实测的压测结果光说vLLM快没有说服力我直接放一组我在同环境下的压测数据。测试环境单张A100 40GB加载7B模型max-model-len设为4096。配置并发数总吞吐量 (tokens/s)单请求平均延迟 (s)显存占用原生transformers每进程一批84223.132GBvLLM默认配置323677.833GBvLLM调优后648254.936GB注意看吞吐量的变化。vLLM在并发调大之后吞吐量还能继续涨这是因为Continuous Batching把GPU算力压榨得更彻底了。原生方案并发一高基本就是排队和显存瓶颈吞吐几乎不再涨。vLLM调优后的单请求平均延迟反而比默认配置低是因为我调整了max-num-seqs和调度策略让计算资源更平滑地分配到每一个请求上而不是让一部分请求长时间饥饿。4.2 影响吞吐量的关键参数配置接下来是重点我调优时反复修改过的几个参数。第一个是max-num-seqs。这个值决定了同时进到引擎里的序列数。开太小GPU利用率上不去开太大每个序列分到的batch空间变小反而影响单请求生成速度。我实测下来7B模型在40GB显存上max-num-seqs在32到64之间效果最好。如果你的服务偏重单请求低延迟就选小值偏重总吞吐量就选大值。第二个是gpu-memory-utilization。这个值越大vLLM能用的KV Cache空间越大能同时服务的请求就越多。但我曾经设成0.95导致系统显存不足因为模型权重、激活值、CUDA context等也要占显存。建议从0.9开始观察显存是否吃紧再逐步调整。第三个是max-model-len。这个参数影响内存占用。每增加一档max-model-lenKV Cache预分配的显存就多一截。如果你的业务请求基本是短输入短输出就没必要设太大。比如对话场景设置2048就比设置8192能换来更高的并发上限。4.3 针对业务场景的调度策略选择vLLM还提供了一个调度策略参数--scheduling-policy默认是fcfs先来先服务。如果你的业务中存在大量短请求和少量长请求混合的情况fcfs会导致短请求偶尔等待时间偏长。还有一种策略是优先级调度。不过我在实际项目中并没有在vLLM这一层做太多优先级控制而是把优先级判断放在上游网关。短任务和长任务分别走不同的vLLM实例避免相互干扰。比如内部有两个模型服务一个处理短文本分类一个处理长文档摘要分开部署之后指标反而更稳定。4.4 和普通推理框架的优劣对比为了让大家有个更直观的判断我把常见的方案和vLLM做了一张对比表维度原生transformersTensorRT-LLMvLLM部署难度低高中低吞吐量低高高动态并发支持弱中强生态兼容性最好一般好社区活跃度-中高显存利用效率低中高高TensorRT-LLM在延迟方面表现很好但它的编译和优化流程更繁琐对模型结构的约束更多。vLLM的优点是开箱即用同时性能不落下风所以我个人更推荐vLLM作为本地高并发推理服务的首选引擎。5. 本地部署中遇到的典型问题与排查踩过的坑多了自然就有了经验。这里挑几个高频问题你们遇到的时候可以直接对着查。5.1 显存不足怎么办症状启动时报CUDA out of memory或者跑一会儿就OOM。排查思路先看gpu-memory-utilization是不是设得太高降低到0.85试试。检查是否只有vLLM在使用GPU用nvidia-smi确认其他进程没占显存。确认max-model-len是否过大尝试缩小。如果用的是多卡机器检查tensor-parallel-size是否设置正确。我遇到过最坑的一次是机器上还跑着一个监控脚本偷偷占走了几个GB显存结果vLLM启动后俩进程争抢双双OOM。用nvidia-smi一查就发现了那之后我都会在启动前先看一眼显存占用。5.2 请求排队严重症状并发稍微一高大量请求就长时间排队响应延迟飙升。原因通常是max-num-seqs设置过小或者模型加载时显存预留不足导致KV Cache空间受限。可以试着调大max-num-seqs调大gpu-memory-utilization检查服务端日志确认是不是有请求异常卡住5.3 输出质量异常症状生成的内容质量不稳定或者某些请求返回空内容。这不一定全是vLLM的问题。先检查请求参数里的temperature、top_p等是否合理。遇到过一些客户端代码把max_tokens设成0结果服务端直接返回空排查了半天才发现是参数问题不是引擎问题。另外vLLM对某些模型的repetition penalty支持情况不一样如果你发现某个模型在vLLM上生成时重复明显可以尝试调整penalty参数或者在模型加载时指定trust_remote_code。5.4 常见报错与解决速查表报错信息常见原因解决方案CUDA out of memory显存不足调低gpu-memory-utilization或降低并发The models max seq len exceeds...max-model-len设置太小调大max-model-lenTokenizer not found模型路径有误或未下载完整检查模型路径和文件完整性RuntimeError: NCCL error多卡通信异常检查CUDA_VISIBLE_DEVICES和驱动ValueError: max_seq_len must be...参数冲突核对max-model-len与模型config5.5 一个实际踩坑案例我记得有一次部署新模型启动过程一切正常但一旦并发上来服务就莫名其妙重启日志里也看不到明显错误。折腾了一下午最后发现是机器上的Docker容器资源限制没配置好vLLM所在容器的shm_size设置得太小导致进程内共享内存崩溃。解决方式很简单在docker run的时候增加--shm-size参数或者把vLLM直接跑在裸机环境。因为vLLM在运行时会用到一些共享内存机制来加速批处理调度容器默认的64MB根本不够用。这类问题不直接在vLLM日志里暴露排查起来特别费时间。所以如果你的服务容器化部署第一件事就是确认好shm-size。6. 进阶服务化架构中的vLLM实践因为我们的业务确实要面向大量内部用户提供服务所以除了单纯把vLLM跑起来我还做了一些服务化层面的工作。6.1 用nginx做负载均衡单机vLLM吞吐再高也有限。当并发需求超过单卡能力时我加了nginx做负载均衡。vLLM实例部署在多个GPU机器上nginx统一对外入口按策略分发请求。upstream vllm_backend { least_conn; server 192.168.1.10:8000; server 192.168.1.11:8000; server 192.168.1.12:8000; } server { listen 80; location / { proxy_pass http://vllm_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }选择least_conn算法而不是默认轮询是因为我们每个请求的耗时方差很大轮询会把长请求集中到一台机器上least_conn则会把新请求分给当前空闲的实例。6.2 用Redis做请求队列和限流vLLM自身有排队机制但那种排队是进程内的不适合跨机器的统一流量控制。我在网关层接了一个轻量级的请求队列每个用户请求先写入Redis队列独立worker从队列拉取请求按优先级转发到vLLM服务通过令牌桶算法对用户维度做限流防止单用户刷爆资源这套方案让模型服务的流量变得可控vLLM实例不会因为突发流量被打崩同时也方便记录日志和埋点统计。6.3 动态扩缩容的探索后来我们尝试了一种动态扩缩容方案根据队列长度和实例的GPU利用率动态决定是否在Kubernetes集群中扩容新的vLLM Pod。有两个先决条件一个是模型文件必须放到共享存储比如共享文件系统或对象存储Pod启动时直接从共享存储加载模型而不是各自下载另一个是vLLM实例的配置参数必须标准化扩出来的新Pod和现有Pod行为一致。实测下来这套方案在并发高峰时能自动扩展实例数量扛过去之后再缩容。成本和稳定性之间取得了不错的平衡。7. 一些值得分享的实践经验文章最后分享几个我实际用下来的心得算是一些零散但重要的经验吧。第一先压测再调参。很多人部署完vLLM就直接上线问起来就是“感觉挺快”。但“感觉”不能代替数据。我强烈建议上线前先做一轮压测把并发数、tokens/s、延迟分位数都记下来再根据数据调参。没有数据调优就是盲人摸象。第二监控一定要做。vLLM每请求的耗时、排队时间、显存使用、GPU利用率全部监控起来。我一般用PrometheusGrafana做面板把vLLM的指标和系统指标都拉上去。出问题的时候能在几分钟内定位到瓶颈而不是一个个日志翻。第三多版本模型服务隔离。如果线上要同时服务多个模型建议用不同端口甚至不同实例避免不同模型的显存分配互相干扰。我见过把两个不相关模型塞进一个vLLM实例导致互相拖累的情况得不偿失。第四学会看日志。vLLM的日志非常详细启动时会把配置参数打全运行时会把调度信息也打出来。遇到问题不要急着搜报错先花五分钟看日志很多问题日志里已经写得很清楚了。第五社区和源码是你的老师。vLLM社区迭代速度很快很多优化和bug修复都在最新的版本里。我建议保持每两三个月更新一次vLLM版本更新前先看changelog。另外遇到奇怪行为时翻一下GitHub的issue区大概率别人已经遇到过并给出了解决方案。vLLM让本地高并发推理服务从“勉强能跑”变成了“真正可用”。但这并不意味着部署完就万事大吉它仍然需要你根据业务场景持续调优和运维。希望我这段时间的实践经验能帮你少踩几个坑早点把服务稳定跑起来。