Continuous Batching深度解析:为什么vLLM比原生HuggingFace快10倍?
为什么vLLM快10倍? vLLM在2026年是推理服务的"标配"。它比原生HuggingFace Transformers快10倍不止。但大多数人只知道"vLLM很快",不知道它为什么快。 vLLM快的秘密只有两个:PagedAttention和Continuous Batching。 PagedAttention解决了KV Cache的显存碎片化问题,Continuous Batching解决了GPU利用率低的问题。 本文深入Continuous Batching的源码,拆解它的调度策略和实现细节。 传统批处理的问题 传统批处理(Static Batching)的工作方式是:攒够N个请求,组成一个batch,一起处理,处理完再攒下一批。 问题1:短请求被长请求阻塞。 请求A(10个输出token)和请求B(1000个输出token)在同一个batch中。请求A只需要10步就完成了,但它必须等待请求B完成1000步才能退出batch。GPU在等待请求B的999步时,请求A的输出位置是"空"的——浪费了GPU算力。 问题2:新请求需要等待。 请求C在batch处理过程中到达,但它必须等待当前batch处理完才能加入。即使GPU有闲置算力,也无法处理请求C。 问题3:显存利用率低。 每个请求预分配最大长度的KV Cache,但大多数请求用不到。显存利用率只有30-50%。 Continuous Batching的解决方案 Continuous Batching的核心思想:请求不是"一批一批"处理的,而是"流式"处理的。 每个请求在生成一个token后,就可以决定"继续"还是"退出"。新请求可以随时加入。 vLLM的调度循环(伪代码): while True: # 1. 从等待队列中取出新请求,加入运行队列 new_requests = waiting_queue.pop_all() running_queue.extend(new_requests) # 2. 对运行队列中的所有请求,生成一个token for request in running_queue: token = model.generate_one_token(request) request.output.append(token) # 3. 检查每个请求是否完成 for request in running_queue: if request.is_finished(): running_queue.remove(request) return_result(request) # 4. 回到步骤1 关键:每个请求生成一个token后,就检查是否完成。完成的立即退出,给新请求腾出空间。 没有"等待一个batch完成"的概念。 调度的"加减法" 加(Add): 新请求从等待队列进入运行队列。vLLM的调度器会检查:当前GPU显存是否足够?如果不够,即使有等待请求,也不添加(防止OOM)。 减(Remove): 完成的请求从运行队列退出。vLLM释放它的KV Cache(PagedAttention的"页"),供新请求使用。 ...