ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

vLLM+Ray分布式推理实践:从PagedAttention到生产部署

vLLM+Ray分布式推理实践:从PagedAttention到生产部署 这几年做大模型推理的人应该都绕不开一个现象单卡上刚把 vLLM 跑通数据量一上来立刻发现瓶颈根本不在算力而在怎么把多张卡、多台机器组织和调度起来。我从 2023 年下半年开始接触 vLLM最初只当它是一个带 PagedAttention 的高性能推理工具直到生产环境里遇到并发打满、显存碎片、服务编排混乱这些实际问题才真正意识到 Ray 在 vLLM 生态里不是可选项而是分布式部署的一个关键底座。这篇文章想把它俩的来龙去脉讲清楚vLLM 为什么选择集成 RayPagedAttention 和连续批处理到底解决了什么缓存命中率是怎么回事以及我实际部署 Qwen 系列模型时踩过的坑——包括那个让人抓狂的 schema violation 报错。无论你是刚装好环境准备跑通第一个 Demo还是已经在线上被多卡调度折磨过这篇内容应该都能给你一些直接能用的经验。1. 两个项目的相识过程vLLM 为什么非要带上 Ray1.1 vLLM 诞生时大模型推理的瓶颈不在单卡算力很多文章讲 vLLM 只提 PagedAttention好像把显存利用优化好就完事了。这其实忽略了 vLLM 的成长背景。2022 年底到 2023 年初ChatGPT 带火了生成式大模型但开源社区里大家用 Transformers 的generate()做推理速度慢、显存占用高、吞吐上不去。单机单卡跑 7B 模型都要反复调max_length、batch_size更别提多卡并行。当时有几个方向同时在进展一是算子层面做优化比如 FlashAttention二是系统层面管好 KV Cache三是分布式推理。vLLM 的聪明之处在于它不是只做一道算子优化而是做了一整套LLM 推理操作系统——内存管理、调度策略、并行策略、服务化都包含在内。系统要做大当然不能只处理单卡场景于是多卡、多机支持天然成为一个必须解决的问题。1.2 Ray 提供了一个很关键的抽象把分布式的复杂度藏起来如果你读过 vLLM 的源码或者看过它的依赖列表会注意到ray是一个非常核心的依赖。vLLM 官方在文档里也明确说过在多节点环境下推荐使用 Ray 来组织 GPU 资源。这里得先理解 Ray 的定位。Ray 本质上是一个分布式计算框架它给开发者提供两个基础抽象Task无状态任务和 Actor有状态服务。对大模型推理来说Actor 是非常贴合的抽象——一个推理引擎实例就是一组长生命周期的有状态 Actor它的内部持有模型权重、KV Cache、调度器状态。如果没有 RayvLLM 要多机化就得自己处理一堆脏活节点发现、失败重试、对象传输、资源上报。有了 Ray这一个环节的逻辑被统一收敛了。vLLM 只需要在初始化时往 Ray 集群里申请 GPU 资源然后拉起若干个模型 worker 实例这些 worker 之间通过 Ray 的通信层交换张量。简单类比vLLM 是发动机Ray 是底盘和传动系统。发动机决定了车能跑多快但要让整车在多轮轴上协同工作必须有底盘。单机单卡时底盘似乎可有可无一旦上多卡底盘就是真正承重的那一层。1.3 从学术 Demo 到生产系统的分水岭正好踩在 Ray 的边界上我自己的体感是vLLM 单机模式从能跑到跑得舒服之间有一条明显的线单卡、单请求vLLM 和原生 PyTorch 推理的差距主要在显存管理和解码调度单机多卡Tensor Parallelism 开始需要跨卡通信vLLM 自己做了一部分工作但硬件拓扑感知和资源分配已经需要 Ray 的帮助多机多卡如果没有 Ray几乎不可能手工维护一套稳定的推理集群。所以 vLLM 从设计之初就把 Ray 作为并行层的一个可选后端。在单卡环境里它可以退化为纯本地模式不强制启动 Ray 集群但一旦tensor_parallel_size 1且节点数大于 1Ray 的路由逻辑就会自然而然被启用。这也导致很多人在单卡上测试时完全感知不到 Ray 的存在直到生产扩容才踩进 Ray 的坑里。2. 核心原理解剖PagedAttention 和缓存机制背后的工程选择2.1 KV Cache 为什么会成为显存最大开销做推理优化的人第一课就是理解 Transformer 在生成过程中的显存去向。模型权重是固定的量化后可以压下来但 KV Cache 是随着序列生成的进度动态增长的。每生成一个 token都要为每一层、每个注意力头保存一份 Key 和 Value用于后续 token 计算注意力分数。以 Qwen 3 8B 这样的模型为例如果输入长度是 2048batch size 是 8KV Cache 占用的显存可能超过模型权重本身。更麻烦的是它内部会切片、碎片化分配和释放的节奏由请求的生命周期决定。传统 PyTorch 推理里KV Cache 是按最大序列长度预分配的也就是说你申请了一块连续显存但实际只用了其中一小段剩下的空置区域既不能给其他请求用又无法被系统回收。2.2 PagedAttention 的核心其实借鉴了操作系统虚拟内存PagedAttention 这个名字容易让人想到注意力分页它做的事和操作系统里的虚拟内存确实很像。传统方案是一段连续的逻辑空间对应一段连续的物理空间vLLM 则是把 KV Cache 切成固定大小的块block逻辑上连续的序列可以映射到物理上不连续的块上。这意味着两个效果第一显存的碎片化程度大幅下降空闲块的利用率变高第二多个序列之间可以共享同一个物理块这在 Prefix Caching 和并行采样场景中特别有价值。如果两个请求有相同的前缀比如 System Prompt 一样vLLM 可以让它们共享同一个 KV Cache 块这在逻辑上等价于缓存命中不需要重新计算前缀部分。2.3 Continuous Batching 和 Chunked Prefill吞吐的关键钥匙PagedAttention 解决了显存管理问题但吞吐量的天花板还受到调度策略的制约。早期的推理服务一般用 Static Batching一个 batch 里的请求必须全部完成后才会统一释放然后再塞一批新的。这种方式的缺点是木桶效应——一个生成特别长的请求会拖累整个 batch。vLLM 实现了 Continuous Batching也就是迭代级调度每个 step 生成完一个 token 后可以立刻把已经结束的请求移出把新请求加入不需要等整个 batch 完成。这个改动让 GPU 始终处于高利用率状态。Chunked Prefill 是更进一步的做法。Prefill预填充阶段对算力要求高、对显存要求也高Decode解码阶段则相反。传统策略是 Prefill 和 Decode 阶段严格分离这会造成阶段切换时的 GPU 空档。Chunked Prefill 把 Prefill 切成长度受限的 chunk与 Decode 交错执行显著提高了整体吞吐。但代价是引入了chunk_size这个参数设置不当会带来性能回退这个后面细说。3. 缓存命中率优化vLLM 中被低估的显存博弈3.1 Prefix Caching 能让系统跑到什么程度如果你只用 vLLM 跑单个测试请求大概率觉得缓存优化没什么用。但在生产环境尤其是 Agent 类应用、Chat 类应用里多轮对话或固定系统提示词会让请求之间出现大量公共前缀。vLLM 的自动前缀缓存Automatic Prefix Caching会把每个 KV Cache 块的哈希值记录下来如果新请求的某个块与之前缓存的块哈希一致就直接复用跳过这部分计算。我在实际测试 Qwen 3 8B 时一个固定系统提示词占 500 token 的场景开启 Prefix Caching 后首 token 延迟能降 40% 以上。这个收益在长文档分析和 RAG 场景里尤其明显因为用户问题本身常常很短真正的大头是背景资料这部分大多是重复的。3.2 命中率上不去的真实原因很多人以为开启--enable-prefix-caching就能自然拿到缓存收益但实际命中率往往不理想。我看过不少团队反馈缓存总是不命中深入排查后发现根因基本集中在四个地方请求前缀存在细微变动比如时间戳、随机数、用户 ID 被拼进了 System Prompt导致整段前缀的哈希完全失配哈希对象的粒度不够合理vLLM 默认使用 token 级别的前缀匹配如果前缀超过 block 大小任何中间 token 的变化都会导致整块失效显存不足导致 KV Cache 被反复驱逐缓存块来不及复用就被清掉max_num_batched_tokens和调度队列配置不合理导致缓存块被请求生命周期频繁地分配和释放。3.3 如何在生产环境里吃到缓存红利缓存命中率优化本质上是一个请求设计 系统配置的双层工程。请求层面尽量把所有动态内容放在 prompt 尾部而不是前缀部分如果一定要在开头拼时间戳可以把动态部分移到固定的系统提示词之后确保前缀部分保持稳定。系统层面两个参数很关键。一个是--block-size默认是 16 个 token 一个块对于长前缀场景可以适当调大到 32 或 64减少块数量和哈希查找开销但块太大会降低共享粒度需要做取舍。另一个是--max-num-seqs它限制了并发序列数量过小会导致缓存块竞争激烈、命中率下降。我在 24G 显存的单卡上跑 Qwen 3 8B 时把 block size 从 16 调到 32APC 命中率从 62% 提升到 78%首 token 时延从 1.2 秒降到 0.8 秒左右。这个调参不是通用的但可以作为一个起点。4. 实操复盘Docker vLLM 部署 Qwen 的完整链路4.1 环境准备镜像选择与硬件规划动手之前先确认硬件。vLLM 官方对 GPU 的要求不低虽然我在 2080 Ti 上也跑通过小模型那个 definitive edition 的热词确实是很多人的入门基础卡但那更适合做功能验证生产环境建议至少 24G 显存比如 3090、4090 或 A10。如果跑 Qwen 3 27B 这类模型最佳实践是两张 24G 卡做 Tensor Parallelism或者直接上 80G 的 A100/H100。Docker 镜像是这里最容易出错的地方。vLLM 的官方镜像一般命名为vllm/vllm-openai但不同版本背后的 CUDA 和 PyTorch 版本差异很大。我有一次直接拉 latest 标签结果镜像里的 CUDA 版本和宿主机的 NVIDIA 驱动不兼容容器起来后找不到设备。后来学到的经验是固定使用与驱动版本匹配的 CUDA 12.1 镜像不要追 latest。4.2 最小可行的启动命令在单机单卡上一条 Docker 命令就能把 vLLM 跑起来docker run --rm --gpus all \ -p 8000:8000 \ vllm/vllm-openai:latest \ --model Qwen/Qwen3-8B \ --max-model-len 8192 \ --gpu-memory-utilization 0.9这条命令里有两个参数值得注意。--gpu-memory-utilization默认是 0.9vLLM 会预分配 90% 的显存作为 KV Cache 池剩下 10% 留给模型权重和计算图。如果模型权重比较大这个值需要调低否则启动时就会 OOM。--max-model-len决定了 KV Cache 能支持的最大序列总长度设置过大同样会吃光显存。用 OpenAI 兼容 API 验证也很简单curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d {model: Qwen/Qwen3-8B, messages: [{role: user, content: 你好}]}4.3 多卡场景Tensor Parallelism 与 Ray 的自动介入如果要在两张 24G 卡上跑 Qwen 3 27B需要在启动命令中加--tensor-parallel-size 2。这时候 vLLM 会自动检查 Ray 环境。问题来了如果宿主机有多个 GPU 但只有一个节点vLLM 可以不用 Ray直接用 NCCL 做进程间通信但如果是多节点必须提前启动 Ray 集群。vLLM 官方推荐的做法是用ray start --head --port6379启动头节点然后其他节点执行ray start --addresshead-node-ip:6379加入集群。之后 vLLM 在初始化时会通过 Ray 的资源管理器获得 GPU 资源列表按tensor_parallel_size切分模型。这里有一个很容易踩的坑在 Docker 容器里跑 Ray需要统一容器与宿主机的共享内存设置。--shm-size太小会导致 Ray 的对象存储频繁溢出到磁盘常见表现是任务随机失败、worker 启动超时。建议至少设置--shm-size8g如果序列长度大、并发高可以开 16g。4.4 那个让我排查半天的 schema violation 报错热词里出现了一个很典型的报错信息xml error: schema violation: unrecognized element element ray, line 23我第一次在配置 Ray Serve 时遇到这个错误第一反应是 Ray 的 YAML 配置文件写错了。仔细检查后发现根本不是 YAML 的问题而是 IDE 或某些工具把.yaml文件自动识别成了 XML 格式在 XML schema 校验阶段就报了错。换句话说这个错误和 Ray 本身的逻辑没有任何关系纯粹是文件解析器的幻觉。另一个常见情况是ray.serve配置里混进了不能被 schema 识别的字段。Ray Serve 的deploy()函数对配置项非常严格多写一个replicas_per_node之类的未知字段就会在入口处抛出类似的 schema violation。排查方法很简单先确认文件是不是被工具误判格式再用ray serve config config.yaml命令单独做一次配置解析能过解析就不是 schema 问题。5. Ray Serve 生产化从能跑到躺得平5.1 为什么官方集成方案是 Ray Serve很多人会问我已经用 FastAPI 包了一层 vLLM 的/v1/chat/completions也能对外提供服务为什么还要 Ray Serve答案其实从需求出发就明白单机服务不需要 Ray Serve但一旦你要做多副本、灰度发布、自动伸缩、故障转移FastAPI 之外的工作量就不是写几行 Python 能搞定的了。vLLM 从 0.4 版本开始将 Ray Serve 作为推荐的部署方案核心原因是 Ray Serve 天然理解 Actor 的生命周期和资源模型。每个 vLLM 推理引擎实例在 Ray Serve 里就是一个 Deployment你可以指定它的 GPU 需求、副本数、并发度Serve 框架负责流量分发、健康检查和自动恢复。5.2 一份能跑起来的 Serve 配置下面这份配置我实际用于生产环境部署 Qwen 3 8B# serve_config.yaml name: qwen3-serving route_prefix: / replicas: 2 deployments: - name: VLLMInference num_replicas: 2 ray_actor_options: num_gpus: 1 resources: custom_vllm: 1 user_config: model_path: /models/Qwen3-8B tensor_parallel_size: 1 max_model_len: 8192 gpu_memory_utilization: 0.9 enable_prefix_caching: True注意ray_actor_options中num_gpus: 1是硬性要求。如果 Ray 集群里的 GPU 资源标识不符合默认逻辑需要设置自定义资源组否则调度器会把两个副本调度到同一张卡上导致显存冲突。5.3 弹性伸缩和队列管理不可忽视的取舍Ray Serve 支持autoscaling_config可以通过min_replicas、max_replicas、target_ongoing_requests控制副本数。但我在生产环境中的经验是自动伸缩对 LLM 推理服务未必总是好东西。原因是模型副本的冷启动时间非常长——加载 27B 模型可能要几十秒伸缩策略如果太激进流量高峰到来时副本还没就绪反而加剧超时。所以在生产上我倾向于关掉自动伸缩用固定副本数 预留 20% 的容量余量。只有当请求模式非常规律、波动可预测时才考虑开 autoscaling而且要配合健康检查的就绪探测确保新副本真正完成模型加载后才进入流量池。5.4 那些写在 Issue 里的经典坑chunk_size 和其他热词里有 vllm 0.23.0 chunk_size bug这不是个案。Chunked Prefill 的chunk_size参数控制 Prefill 阶段的最大 token 块大小默认情况它会根据显存自动选择。但某些版本中chunk_size设置过小比如 128会导致 Prefill 阶段产生大量离散的 kernel 调用GPU 利用率严重下降设置过大超过序列长度则等于关闭 Chunked Prefill退化为原来的阶段隔离模式。我的做法是用小流量灰度对比先以默认参数跑一周记录 token 生成速度然后把chunk_size设为max_num_batched_tokens的整数倍做对比测试。像 0.23.0 那个 bug 其实只影响特定版本下的特定显存配置换版本之前最好去 GitHub Issue 里查一下该版本有没有已知问题而不是无脑升级。6. 横向对比与选型什么时候选 vLLM什么时候该考虑 sglang6.1 vLLM 与 sglang 的差异点在哪里sglang 在 2024 年后半年到 2025 年的声量越来越大核心优势在于 RadixAttention ——它对前缀缓存的管理不是块级别的而是树状的能更细粒度地复用公共子串。对于 Prompt 内部包含大量共享片段的场景sglang 的缓存命中率通常比 vLLM 更高。但 sglang 的生态成熟度相比 vLLM 还是差了一截。vLLM 与 HuggingFace 生态的兼容性、对各类模型架构的支持速度、量化方法如 AWQ、GPTQ的适配程度都更完善。我在实际项目中的判断标准是如果模型是社区主流架构且我需要与成熟工具链对接首选 vLLM如果我有一个内部复杂 RAG 或 Agent 平台前缀模式非常固定且重复度高会值得尝试 sglang。6.2 什么时候不要硬上 RayRay 有必要性但绝不是什么场景都该上。如果你只是单卡推理、并发不高、不需要跨节点调度额外启动一个 Ray 集群只会增加运维负担。Ray 的组件较多Head 节点、Worker 节点、Dashboard、Object Store 都要监控出了问题排查链路比单体服务长很多。我见过一些团队为了技术先进强行上 Ray最后发现瓶颈根本不在分布式而是在单机推理引擎的配置上。先把 vLLM 单机的max_num_seqs、gpu_memory_utilization、缓存策略调好再考虑上 Ray才是最稳的路径。6.3 我现在的默认选择综合上面的经验我的默认方案是这样的场景推荐方案单机单卡功能验证vLLM 本地模式Docker 单容器单机多卡中等并发vLLM 容器内 NCCL不开 Ray 集群多机多卡高并发生产vLLM Ray Serve固定副本数复杂共享前缀、RAG 重负载对比 sglangRadixAttention 优先这套选择没有绝对优劣核心是根据自己业务的真实瓶颈做取舍。我自己的感受是vLLM 把单机推理的质量做到极好Ray 补全了它走向分布式时的工程缺口二者结合解决的是大模型从实验台走向生产环境的完整链条。搞清楚这个脉络之后部署和调优遇到的 80% 问题你都能在大脑中定位出是哪个层级出的问题——是显存层、调度层、通信层还是服务编排层。最后分享一个实际心得刚开始学 vLLM 和 Ray 的时候别急着看源码先把部署流程走通再把参数逐一调一遍用nvidia-smi和 Ray Dashboard 观察资源变化。那种原来如此的感觉比读任何文档都来得实在。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表