vllm源码讲解
说实话,刚接触vllm那会儿,我也被它那个PagedAttention机制给唬住了。网上那些文章,动不动就是“革命性突破”、“性能提升百倍”,看得我头皮发麻。我抱着试试看的心态去扒源码,结果发现,哎?好像也没那么玄乎。
咱们今天不聊虚的,就聊聊这玩意儿到底是怎么把显存省下来的。
你想想,传统的LLM推理,KV Cache是干嘛的?就是把你之前生成的token的Key和Value存起来,方便后续计算注意力机制。问题出在哪?出在“连续内存分配”上。这就好比你去住酒店,前台给你安排房间,必须是连着的。如果你要住1000间房,酒店得找一大片空地。可现实是,很多房间是空的,或者只住了一个人,这就造成了巨大的浪费。
vllm源码讲解里最核心的改动,就是把这“连续的房间”给打碎了。
我看了下它的Scheduler部分,逻辑其实挺简单的。它不再一次性给每个请求分配一大块连续的显存,而是把显存切成了一个个小的Block。每个Block大小固定,比如16个token。
这就好比把酒店改成了胶囊旅馆。不管你是住一晚还是住一个月,都给你一个小格子。需要的时候,你就去拿格子;不用的时候,格子就空着。
这里有个坑,我得吐槽一下。很多初学者看源码,看到PagedAttention的CUDA Kernel就晕了。其实不用怕,你只需要知道,它通过一个Page Table来映射逻辑上的token到物理上的Block。
比如,你生成了第1个token,它存在Block 1;生成了第2个token,存在Block 2。如果Block 1满了,就申请Block 3。这个映射关系,就是Page Table干的活。
我自己在本地跑demo的时候,发现一个有趣的现象。当并发请求很少的时候,vllm的优势并不明显。甚至因为多了个Page Table的管理开销,速度还稍微慢了一点点。但当并发上来,或者单个请求特别长的时候,那个优势就出来了。
我记得有一次压测,并发量到50的时候,传统的vllm源码讲解里提到的那种连续分配方式,显存直接OOM(Out Of Memory),服务直接挂掉。而vllm呢?它只是把那些不活跃的请求的Block换出到CPU内存里,或者干脆复用空闲的Block。显存占用稳如老狗。
当然,vllm也不是完美的。它的代码结构,说实话,有点乱。
特别是那个MultiStepScheduler,为了追求极致的吞吐量,搞了很多复杂的逻辑。我看的时候,差点没看晕。有些变量命名也不太直观,比如req_to_token这种,虽然能猜出来是请求到token的映射,但总觉得不够优雅。
还有,它对硬件的依赖挺强的。你得有支持PagedAttention的GPU驱动,而且CUDA版本也得够新。我之前为了凑环境,折腾了两天,差点放弃。
但即便如此,我还是得说,vllm是目前开源界最实用的推理引擎之一。它没有搞那些花里胡哨的噱头,就是实打实地解决显存碎片化问题。
对于咱们这种搞落地的工程师来说,能省显存就是省钱,就是能多跑几个模型,就是能多接几个用户。这就够了。
别被那些复杂的架构图吓住。你就把它想象成一个精明的酒店经理,把每一寸空间都利用到极致。
最后,提个小建议。如果你想深入理解vllm源码讲解,别光看文档。去跑通那个demo,然后打断点,看看那个Page Table是怎么变化的。亲眼看到内存是怎么被复用,比你读十遍文章都管用。
虽然代码里有些小bug,或者文档更新不及时,但这都不影响它的好用。毕竟,能解决问题的代码,就是好代码。
咱们做技术的,别太矫情。能用,好用,就行。