上周搭了个大模型服务,用户一上来就问:"你为什么这么慢?"
我盯着屏幕看推理时间——7秒一个token。心里咯噔一下,这不行啊。回头一看配置,一个Llama-2-7B,单张A100。理论上应该能跑飞快,结果慢得像卡在80年代的拨号上网。
问题出在哪?
推理为什么这么慢?
先搞清楚大模型是怎么干活的。
每次生成一个token,模型都要看之前的所有token。这就意味着,生成第100个token时,模型要把前99个token都算一遍。这就是自回归解码,天然O(n²)复杂度。
更糟糕的是显存带宽。你算得再快,数据从显存搬到GPU核心要时间。现代推理的瓶颈不是计算,是带宽。
我第一次踩坑时以为加GPU就能解决。结果两张A100并在一起,性能提升不到40%。因为数据还是得从显存搬运到计算核心,带宽没变。
KV Cache:不要重复算
KV Cache是第一道防线。
原理很简单:每层attention都要算Q、K、V三个矩阵。K和V只依赖之前的token,生成新token时没必要重新算。存下来就行。
看一下代码:
# 假设我们有一个7B模型,hidden_size=4096,num_layers=32 # 每个token的KV cache大小计算 kv_cache_per_token = 2 * 32 * 4096 * 2 # K和V,32层,4096维,FP16 print(kv_cache_per_token / 1024 / 1024) # 0.5 MB/token
生成4096个token,光KV cache就2GB显存。
第一次我傻乎乎地不用KV cache,跑着跑着OOM了。加上后显存占用翻倍,但推理速度直接提升3-5倍。
代价就是显存占用。Llama-2-7B本身13GB,加上KV cache,长文本场景下轻松破30GB。单张A100装不下。
PagedAttention:把显存利用率从40%拉到90%
vLLM的核心创新就是这个。
传统KV cache分配是静态的——预分配固定大小。假设你要支持2048上下文,不管用户发多少字,显存都给满了。结果显存利用率只有40%。
PagedAttention借鉴操作系统虚拟内存的思想:把KV cache分页,动态申请和释放。用户发100字,就只申请100字的显存。
代码看一下:
# vLLM启动命令 python -m vllm.entrypoints.api_server \ --model meta-llama/Llama-2-7b-hf \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.9 \ --block-size 16
--block-size 16就是页大小,默认16个token一页。
实测下来,vLLM比HuggingFace Transformers快3-5倍,显存利用率从40%提到90%。
我一开始不信,以为vLLM团队吹牛。自己跑了一遍,跪了。同样4096上下文,vLLM显存占用只有原来的60%,吞吐量直接翻倍。
投机解码:小模型猜,大模型验证
投机解码是最近两年的新玩意。
原理是这样:用一个小模型(比如1.3B)快速生成一堆token,然后用大模型(比如7B)验证。验证过了就用,不过就回退。
听起来多此一举?其实不是。
小模型推理速度快,因为参数少。大模型验证是并行的,一次能验多个token。整体下来,2-3倍加速。
代码:
# vLLM开启投机解码 python -m vllm.entrypoints.api_server \ --model meta-llama/Llama-2-7b-hf \ --speculative-model TinyLlama/TinyLlama-1.1B-Chat-v1.0 \ --num-speculative-tokens 5
--num-speculative-tokens 5表示小模型一次猜5个token。
我实测下来,Llama-2-7B配合1.1B小模型,推理速度从50 tokens/s提升到130 tokens/s。当然,前提是小模型猜得准。如果猜错太多,反而会拖后腿。
连续批处理:动态拼batch
传统批处理是静态的——等凑够32个请求再一起跑。用户少的时候,GPU就空着。
连续批处理(Continuous Batching)是动态的:来一个就跑一个,中间有请求结束了,立刻塞新的进去。GPU永远不闲着。
vLLM默认就开,不用额外配置。
我之前用HuggingFace Transformers,用户一少,GPU利用率掉到20%。切换到vLLM,哪怕只有一个用户,GPU利用率也能维持在70%以上。
量化:INT8/FP8,显存减半速度翻倍
量化就是把模型参数从FP16压缩到INT8或FP8。
FP16→INT8,显存占用减半,带宽需求减半,推理速度翻倍。代价是精度掉1-2个点,实际效果差别不大。
vLLM支持AWQ和GPTQ两种量化格式:
# vLLM加载量化模型 python -m vllm.entrypoints.api_server \ --model TheBloke/Llama-2-7B-AWQ \ --quantization awq
我试过AWQ 4-bit量化,Llama-2-7B从13GB压到4GB,推理速度从50 tokens/s提升到90 tokens/s。精度肉眼几乎看不出来。
实战建议:什么场景用什么组合
搞懂了原理,剩下的就是组合拳。
短对话(512 token以内):
长文档问答(2048-4096 token):
高并发在线服务:
成本敏感场景:
最后说句大实话
推理优化这东西,理论再多不如实测。
我一开始看各种论文,觉得投机解码应该能飞起来。结果自己的数据集上,小模型猜错率高达30%,反而比不用还慢。换成别的数据集,又好用了。
核心建议:先测自己的数据,再选方案。
vLLM是个好起点,默认配置就够用80%场景。剩下的20%,一个个试:投机解码、量化、连续批处理。监控显存利用率和吞吐量,哪个好用哪个。
GitHub链接:
别相信理论数据,相信你自己的benchmark。