Run multi-node inference workloads using Ray LLM Deep Learning Containers
https://aws.amazon.com/blogs/machine-learning/run-multi-node-inference-workloads-using-ray-llm-deep-learning-containers/📌 用 Ray LLM DLC 在 EKS 上做 Prefill/Decode 分離推理
TL;DR:AWS 推出支援多節點的 Ray LLM Deep Learning Container,讓你不必手動拼裝 GPU 相依性,就能跑 prefill/decode 分離的 LLM 推理服務。
LLM 推理有兩種性格完全不同的階段:prefill 負責一次性消化整段輸入、吃的是算力;decode 負責逐 token 生成、吃的是記憶體頻寬。把兩者塞在同一張 GPU 上,長 prompt 的運算很容易拖慢正在進行中的生成請求。AWS 這篇文章示範了如何把兩個階段拆開,分別跑在不同節點上。
🤔 為什麼要把 prefill 和 decode 拆開跑
當模型太大塞不進單一 GPU、需要更高輸出量,或是想避免長 prompt 干擾正在生成的回應時,客戶通常得自己搭建相依性:挑選相容的 GPU 堆疊函式庫,並在某個元件版本飄移時除錯,這是會吃掉大量工程時間、卻無法直接產生價值的工作。AWS Deep Learning Containers(DLC)原本就是為了解決訓練與推理場景的這類問題而生,而這次推出的多節點 Ray LLM DLC,把同樣的做法延伸到多節點推理。
🧩 架構:一個 OpenAI 相容端點,背後兩種節點各司其職
這套 DLC 內建 vLLM 作為推理引擎、Ray Serve LLM 作為服務層、NIXL 負責跨節點的 KV cache 傳輸,以及 EFA 堆疊(libfabric 與 aws-ofi-nccl)負責節點間的高頻寬通訊。
實際部署在一個 Amazon EKS 叢集內:一臺 CPU 系統節點(m7i.xlarge)跑 KubeRay operator 與 Ray head pod;兩臺 GPU 工作節點(g6.8xlarge)各自負責 prefill 與 decode 階段,並且兩臺 GPU 工作節點的所在的節點群組被限定在單一可用區(Availability Zone),因為 EFA 無法跨可用區運作。
一筆請求的流動路徑大致是:使用者請求先抵達 CPU 系統節點上的 Ray head → 轉給 prefill 工作節點,平行處理整段輸入 prompt 並建立 KV cache → KV cache 透過 NIXL、經由 EFA 的 libfabric 後端傳給 decode 工作節點 → decode 節點逐 token 生成回應並回傳。
由於 Ray Serve LLM 內建應用程式建構器,整個服務應用其實就是一份 YAML:一個名為 build_pd_openai_app 的輔助函式,接收 prefill 設定與 decode 設定,就能把兩者之間的 KV cache 傳輸串接起來,背後統一對外暴露一個 OpenAI 相容端點,不需要寫 handler class,也不需要自建映像檔。
幾個關鍵設定值得注意:max_model_len: 4096 讓上下文長度對齊實測的 KV cache 大小;dtype: bfloat16 與 gpu_memory_utilization: 0.90 控制精度與 vLLM 可佔用的顯示記憶體;enforce_eager: true 跳過 CUDA graph 擷取以降低峰值 GPU 記憶體用量(效果依模型與輸入而異)。真正讓分離架構運作起來的關鍵,是 kv_transfer_config:兩端都使用 NixlConnector,kv_role 設為 kv_both,並各自配置獨立的 engine_id。文中特別強調,最關鍵的一行設定是 kv_connector_extra_config: { backends: ["LIBFABRIC"] }——沒有這行,NIXL 會預設走 UCX,即使底層有 EFA,速度也只會停在 TCP 等級。
工作節點的 pod 各自請求一張 GPU 與一個 EFA 介面(nvidia.com/gpu: "1"、vpc.amazonaws.com/efa: "1"),並以 privileged: true 執行,因為 NIXL 需要把 CUDA 顯示記憶體註冊給 EFA 網卡,單靠 IPC_LOCK 權限並不夠。
📊 權重載入時間從約 145 秒降到約 2 秒
文中提到,針對 Qwen3.5-9B 模型,在 g6.8xlarge 搭配上述快取設定下,重啟時的模型權重載入時間從約 145 秒降到約 2 秒。
🎯 實務啟示
這套方案特別適合 RAG、文件摘要、agentic 應用這類同時存在長 prompt 與延遲敏感生成的場景。如果團隊已經在用 EKS 搭配 KubeRay,直接拉這個 DLC 映像檔,可以省掉自行兼容 CUDA、vLLM、NIXL、EFA 版本相依性的工作,把時間留給模型與應用邏輯本身。
⚠️ 限制
GPU 節點群組必須限定在單一可用區內,因為 EFA 無法跨可用區運作;整篇文章聚焦在既有工具(Ray Serve、vLLM、NIXL、EFA)的整合與部署流程,屬於基礎設施工程落地,而非新的演算法突破。
🔗 來源
- 標題:Run multi-node inference workloads using Ray LLM Deep Learning Containers
- 作者/機構:Ananth Raghavendra(AWS)
- 連結:https://aws.amazon.com/blogs/machine-learning/run-multi-node-inference-workloads-using-ray-llm-deep-learning-containers/
#AWS #RayServe #vLLM #LLMInference #KubernetesML #EKS #PrefillDecode #NIXL #DeepLearningContainers #DistributedInference
原始資料 AWS ML · 收集於 2026-10-12
摘要原文
This post shows how to deploy one large language model (LLM) across two GPU nodes on Amazon Elastic Kubernetes Service (Amazon EKS) with the multi-node Ray LLM Deep Learning Containers . It uses separate prefill and decode nodes behind one OpenAI-compatible endpoint. This kind of multi-node setup is common for tensor, pipeline, and data parallelism, as well as prefill/decode disaggregation inference workloads. In a previous post , we introduced the Ray Serve DLC and used it to stand up a small model behind an HTTP endpoint on a single GPU node. You had a working inference endpoint, and the container keeps the GPU stack compatible, including CUDA libraries, so you did not need to write boilerplate code. Ray is a distributed compute framework, so Ray Serve deployments can span multiple nodes. Customers use multi-node Ray Serve for several reasons. Some want to run foundation or open-weight models that are too large for a single GPU. Others need more throughput than one replica can deliver. Still others want to separate the two phases of inference to improve performance. In each case, customers must build the dependency chain themselves: choosing compatible libraries for the GPU stack and debugging issues when a component drifts out of alignment. This is undifferentiated work that takes time and effort away from delivering models. AWS Deep Learning Containers (DLCs) have long solved this kind of problem for training and inferencing workloads. DLCs are pre-built, performance-optimized Docker images that bundle a framework, its dependencies, and the GPU stack into a tested, patched combination you can pull and use immediately. With the launch of the Ray LLM DLC for multi-node deployments, that same approach now extends to multi-node inference. You get a container purpose-built for serving models behind an HTTP endpoint, maintained and tested by AWS, with the full inference stack already assembled. This post walks through implementing multi-node disaggregated inference using multi-node Ray LLM DLC: deploying a single large language model across two GPU-powered nodes on Amazon EKS cooperating behind a single endpoint. While one of the GPU nodes handles the prefill phase, the other GPU node handles the decode phase. Separating the phases prevents compute-heavy long prompts from stalling token generation for requests already in progress. This approach is well suited for workloads such as Retrieval Augmented Generation (RAG), document summarization, and agentic applications. We use the multi-node Ray LLM DLC, which ships Ray Serve LLM , NVIDIA Inference Transfer Library (NIXL) , and the Elastic Fabric Adapter (EFA) stack, and exposes an OpenAI-compatible API with no application code. The complete code is available in the accompanying repository . LLM inference has two distinct phases. Prefill processes the entire input prompt in parallel and builds the key-value (KV) cache. Decode then generates the response one token at a time, reading and extending that cache. Prefill is compute bound, and Decode is bound by memory bandwidth. When both phases share one GPU, they contend for the same resources, and the bursty, compute-heavy prefill work interferes with the steady, latency-sensitive decode work. Separating the prefill phase from the decode phase means you can select the hardware and serving properties for each phase, improving overall performance at scale. PD disaggregation places the two phases on separate nodes, so each phase runs without interfering with the other and can be scaled independently. The trade-off is that the KV cache built during prefill must be handed to the decode node, and that transfer should be fast enough not to erase the benefit. That is exactly why the interconnect between the nodes matters. AWS DLCs are pre-built, performance-optimized Docker images that bundle a framework, its dependencies, and the GPU stack into a tested, patched combination you can pull and use immediately. All components are validated together before each release, so there is no version drift between the CUDA runtime, the framework, and the serving layer. Security patches are applied at build time. The Ray LLM DLC extends this to distributed LLM serving. It ships vLLM as the inference engine, Ray Serve’s HTTP as the serving layer, NIXL for KV-cache transfer between nodes, and the EFA stack ( libfabric and aws-ofi-nccl ) for high-bandwidth node-to-node communication. Because the Ray Serve LLM library includes a built-in application builder, the entire serving application is a block of YAML. There’s no handler class, no model archiver, and no custom image to build. A single helper, build_pd_openai_app , takes a prefill configuration and a decode configuration and wires the KV-cache transfer between them behind one OpenAI-compatible endpoint. The following diagram shows how the deployment is laid out on Amazon EKS. A user request reaches the Ray head on the CPU system node, and the two GPU worker nodes run the prefill and decode phases of the model. Figure 1: Multi-node inference architecture on Amazon EKS, with the Ray head on a CPU system node and the prefill and decode phases on two GPU worker nodes The deployment is an EKS cluster inside a virtual private cloud (VPC). Nodes run in private subnets with outbound access through a NAT Gateway. A CPU system node (m7i.xlarge) hosts system workloads, the KubeRay operator, and the Ray head pod, which is CPU-only. Two GPU worker nodes (g6.8xlarge) run the Ray workers: one serving the prefill phase, and the other serving the decode phase. Both nodes pull the Ray LLM DLC from Amazon Elastic Container Registry (Amazon ECR) . KubeRay watches the RayService you apply and wires the head-to-worker join automatically. Ray Serve places the prefill and decode deployments on the two GPU workers. The KV cache flows from the prefill worker to the decode worker over NIXL, using EFA through NIXL’s libfabric backend. The GPU node group is pinned to a single Availability Zone (AZ) because EFA cannot cross AZs. To follow along with this post, you need: Verify that your AWS credentials are active with the following command: The application is the serveConfigV2 block in manifest/rayservice.yaml . It uses build_pd_openai_app , which takes a prefill_config and a decode_config and connects the KV-cache transfer between them: A few settings carry most of the weight. max_model_len: 4096 sizes the context to fit the measured KV cache on the GPU. dtype: bfloat16 and gpu_memory_utilization: 0.90 set precision and how much VRAM vLLM can claim. enforce_eager: true skips CUDA graph capture to reduce peak GPU memory (model- and input-dependent). The kv_transfer_config setting is what makes disaggregation work: both phases use NixlConnector with kv_role: kv_both and a distinct engine_id (prefill on one, decode on the other). The critical setting is kv_connector_extra_config: { backends: ["LIBFABRIC"] } . Without that line, NIXL defaults to UCX, which runs at TCP speed even on EFA. The rest of the manifest, in rayClusterConfig , describes the pods. The head pod is CPU-only ( num-gpus: "0" ) and pinned to the system node group. Each worker pod requests one GPU and one EFA interface ( nvidia.com/gpu: "1", vpc.amazonaws.com/efa: "1" ) and targets the GPU nodes through nodeSelector: { role: gpu-worker } . It runs privileged: true because NIXL registers CUDA VRAM with the EFA NIC. The IPC_LOCK capability alone isn’t enough. The workers set FI_PROVIDER=efa , read VLLM_HOST_IP from the pod IP, and mount a shared-memory volume. Memory limits sit well above the roughly 18 GiB checkpoint so the OS page cache can hold it. For Qwen3.5-9B model loading on g6.8xlarge with the preceding cache configuration, the model weights loading drops from around 145 seconds to about 2 seconds on restart. All scripts share a single configuration file, scripts/env.sh . The script contains defaults for CLUSTER_NAME , REGION , K8S_VERSION , and NAMESPACE .
由 claude-code/sonnet 自動生成