Run multi-node inference workloads using Ray LLM Deep Learning Containers
Quick Summary
AWS는 Ray LLM Deep Learning Containers를 이용해 Amazon EKS의 두 GPU 노드에 LLM의 prefill과 decode를 분리하고, NIXL·EFA로 KV 캐시를 전달하며 하나의 OpenAI 호환 엔드포인트로 제공하는 배포 방법을 설명한다.
🖼️ 인포그래픽

🖼️ 4컷 인포그래픽

💡 한 줄 요약
AWS는 Ray LLM Deep Learning Containers를 이용해 Amazon EKS의 두 GPU 노드에 LLM의 prefill과 decode를 분리하고, NIXL·EFA로 KV 캐시를 전달하며 하나의 OpenAI 호환 엔드포인트로 제공하는 배포 방법을 설명한다.
📌 핵심 요약
- 멀티노드 Ray Serve는 단일 GPU에 담기 어려운 모델, 단일 복제본을 넘는 처리량, 추론 단계 분리 등에 활용된다. Ray LLM DLC는 AWS가 함께 검증하고 유지보수하는 추론 스택을 제공해 GPU 라이브러리와 의존성 조합을 직접 구성하는 부담을 줄인다.
- Prefill은 입력 전체를 병렬 처리해 KV 캐시를 만드는 연산 중심 단계이고, decode는 캐시를 읽고 확장하며 토큰을 순차 생성하는 메모리 대역폭 중심 단계다. 두 단계를 분리하면 자원 간섭을 줄이고 독립적으로 확장할 수 있지만, KV 캐시 전송이 충분히 빨라야 이점을 유지할 수 있다.
- 예제는 Amazon EKS에서 CPU 시스템 노드 m7i.xlarge에 KubeRay와 Ray head를 배치하고, 두 g6.8xlarge GPU 노드가 각각 prefill과 decode를 담당하게 한다. EFA는 가용 영역을 넘을 수 없어 GPU 노드 그룹을 단일 가용 영역에 고정하며, 두 인스턴스를 위한 최소 64 vCPU 서비스 할당량이 필요하다.
- Ray LLM DLC는 vLLM, Ray Serve LLM, NIXL, EFA 스택을 포함한다. build_pd_openai_app이 YAML의 prefill_config와 decode_config를 연결하며, NIXL에 LIBFABRIC 백엔드를 지정하지 않으면 기본 UCX가 EFA 환경에서도 TCP 속도로 동작한다고 원문은 설명한다.
- Qwen/Qwen3.5-9B 예제는 max_model_len: 4096, dtype: bfloat16, gpu_memory_utilization: 0.90, enforce_eager: true를 사용한다. 배포는 EKS 클러스터 생성, GPU 노드 추가, KubeRay 설치, RayService 적용 순서이며, Serve 앱이 정상화되면 안정적인 Service를 통해 포트 8000으로 접근한다.
🧩 주요 포인트
- Prefill과 decode의 병목 차이 → 단계별 자원 선택과 확장이 가능해지며, 긴 프롬프트가 진행 중인 토큰 생성을 지연시키는 문제를 줄이는 구조다. 원문은 RAG, 문서 요약, 에이전트 애플리케이션에 적합하다고 설명한다.
- NIXL의 LIBFABRIC 설정과 EFA의 단일 가용 영역 제약 → 단계 분리의 성능 이점은 GPU 배치뿐 아니라 KV 캐시를 전달하는 통신 경로와 배치 조건에도 달려 있다.
- 검증된 Ray LLM DLC와 build_pd_openai_app의 YAML 구성 → 별도 애플리케이션 코드 없이 OpenAI 호환 서비스를 구성할 수 있지만, Qwen/Qwen3.5-9B의 메모리 설정과 두 g6.8xlarge를 위한 최소 64 vCPU 할당량 등 실행 조건은 맞춰야 한다.
🧠 상세 정리
1. 멀티노드 추론의 필요성과 DLC의 역할
이 글은 이전의 단일 GPU Ray Serve DLC 배포 예제를 확장해, Amazon EKS의 두 GPU 노드가 하나의 LLM 서비스를 함께 제공하는 방법을 설명한다. 멀티노드 구성은 텐서·파이프라인·데이터 병렬화와 prefill/decode 분리 추론에 사용되며, 고객은 단일 GPU에 담기 어려운 모델을 실행하거나 복제본 하나 이상의 처리량을 확보하기 위해 이를 선택한다. 그러나 GPU 스택에 맞는 라이브러리를 선택하고 구성 요소의 버전이 어긋날 때 문제를 해결하는 작업은 모델 제공에 쓸 시간과 노력을 소모한다. AWS DLC는 프레임워크, 의존성, GPU 스택을 사전 구축된 성능 최적화 Docker 이미지로 묶고, 검증과 패치를 거친 조합을 제공해 이러한 부담을 줄여 왔다. 멀티노드 Ray LLM DLC는 이 접근을 분산 추론으로 확장하며, AWS가 유지보수하고 테스트하는 HTTP 모델 제공용 스택을 제공한다.
2. Prefill과 decode 분리의 이점과 전송 비용
LLM 추론의 prefill 단계는 전체 입력 프롬프트를 병렬 처리하면서 키·값 캐시인 KV 캐시를 만들고, decode 단계는 이 캐시를 읽고 확장하면서 응답을 한 토큰씩 생성한다. Prefill은 연산 능력에, decode는 메모리 대역폭에 병목이 생기는 단계이므로 같은 GPU에서 실행하면 서로 다른 작업이 자원을 놓고 경쟁한다. 특히 순간적으로 발생하는 무거운 prefill 작업은 꾸준한 처리와 짧은 지연이 필요한 decode를 방해할 수 있다. 두 단계를 별도 노드에 배치하면 단계별 하드웨어와 서빙 속성을 선택하고 각각 독립적으로 확장할 수 있으며, 원문은 RAG, 문서 요약, 에이전트 애플리케이션을 적합한 사용 사례로 제시한다. 다만 prefill에서 만든 KV 캐시를 decode 노드에 전달해야 하므로, 이 전송 비용이 분리의 이점을 상쇄하지 않도록 노드 간 연결 속도가 중요하다.
3. 컨테이너에 포함된 추론·통신 스택
Ray LLM DLC는 vLLM을 추론 엔진으로, Ray Serve의 HTTP 기능을 서비스 계층으로 제공하며, NIXL을 노드 간 KV 캐시 전송에 사용한다. 고대역폭 노드 간 통신을 위한 EFA 스택에는 libfabric과 aws-ofi-nccl이 포함된다. AWS는 각 릴리스 전에 구성 요소를 함께 검증해 CUDA 런타임, 프레임워크, 서빙 계층 사이의 버전 불일치를 방지하고, 이미지 빌드 시 보안 패치를 적용한다고 설명한다. Ray Serve LLM 라이브러리에는 애플리케이션 빌더가 내장되어 있어 전체 서빙 애플리케이션을 YAML 블록으로 구성할 수 있으며, 별도 핸들러 클래스나 모델 아카이버, 사용자 정의 이미지를 만들 필요가 없다. build_pd_openai_app은 prefill 설정과 decode 설정을 받아 두 단계의 KV 캐시 전송을 연결하고, 하나의 OpenAI 호환 엔드포인트로 노출한다.
4. Amazon EKS의 노드 배치와 EFA 제약
배포 대상은 VPC 안의 Amazon EKS 클러스터이며, 노드는 프라이빗 서브넷에서 실행되고 NAT Gateway를 통해 외부로 연결된다. CPU 시스템 노드 m7i.xlarge는 시스템 워크로드, KubeRay operator, CPU 전용 Ray head pod를 호스팅하며, 사용자 요청은 이 Ray head에 도달한다. 두 g6.8xlarge GPU 작업 노드는 각각 prefill과 decode를 담당하는 Ray worker를 실행하고 Amazon ECR에서 Ray LLM DLC 이미지를 가져온다. KubeRay는 적용된 RayService를 감시해 head와 worker의 연결을 자동으로 구성하고, Ray Serve는 두 GPU worker에 각 단계의 배포를 배치한다. KV 캐시는 NIXL의 libfabric 백엔드를 통해 EFA를 사용하여 prefill worker에서 decode worker로 전달된다. EFA는 가용 영역을 넘을 수 없으므로 GPU 노드 그룹은 단일 가용 영역에 고정된다.
5. 사전 준비와 공통 환경 설정
실습에는 과금이 활성화된 AWS 계정과 대상 리전에서 두 g6.8xlarge 인스턴스를 실행할 수 있는 서비스 할당량이 필요하며, 원문은 최소 64 vCPU를 요구한다. 도구로는 자격 증명이 설정된 AWS CLI, Kubernetes 제어 영역과 통신하는 kubectl, EKS 클러스터를 생성·관리하는 eksctl이 필요하다. KubeRay operator 설치에는 Helm을, 매니페스트의 변수 치환에는 GNU gettext에 포함된 envsubst를 사용한다. AWS 자격 증명이 활성 상태인지 확인하는 명령은 aws sts get-caller-identity다. 모든 배포 스크립트는 scripts/env.sh를 공유하며, 이 파일에는 CLUSTER_NAME, REGION, K8S_VERSION, NAMESPACE뿐 아니라 시스템·GPU 노드 그룹, KubeRay 버전, 모델의 기본값이 들어 있다. 프로젝트 루트에서 스크립트 디렉터리로 이동해 env.sh를 불러오고, 변경할 변수는 스크립트 실행 전에 export하여 기본값을 덮어쓴다.
6. Qwen 모델과 KV 캐시 전송의 YAML 설정
manifest/rayservice.yaml의 serveConfigV2는 build_pd_openai_app을 사용하며, prefill_config와 decode_config 모두 model_id를 qwen3.5-9b, model_source를 Qwen/Qwen3.5-9B로 지정한다. 각 단계는 복제본 하나를 사용하고, max_model_len: 4096은 GPU에서 측정된 KV 캐시에 맞춰 컨텍스트 크기를 정한다. dtype: bfloat16과 gpu_memory_utilization: 0.90은 정밀도와 vLLM이 사용할 수 있는 VRAM 비율을 설정하며, enforce_eager: true는 CUDA 그래프 캡처를 생략해 최대 GPU 메모리 사용량을 줄이지만 효과는 모델과 입력에 따라 달라진다. 두 단계의 kv_transfer_config는 NixlConnector와 kv_role: kv_both를 사용하고, engine_id를 각각 prefill과 decode로 구분한다. 특히 kv_connector_extra_config에서 backends: ["LIBFABRIC"]을 지정하는 것이 중요하며, 원문은 이 설정이 없으면 NIXL이 기본 UCX를 사용해 EFA에서도 TCP 속도로 동작한다고 설명한다.
7. Worker pod의 장치·권한·메모리 구성
rayClusterConfig에서 head pod는 num-gpus: "0"으로 설정되어 시스템 노드 그룹에 고정되고, 각 worker pod는 GPU 하나와 EFA 인터페이스 하나를 요청한다. Worker는 nodeSelector의 role: gpu-worker로 GPU 노드를 선택하며, 자원 요청에는 nvidia.com/gpu: "1"과 vpc.amazonaws.com/efa: "1"이 사용된다. NIXL이 CUDA VRAM을 EFA NIC에 등록해야 하므로 worker는 privileged: true로 실행되며, 원문은 IPC_LOCK 권한만으로는 충분하지 않다고 명시한다. Worker는 FI_PROVIDER=efa를 설정하고 pod IP에서 VLLM_HOST_IP를 가져오며, 공유 메모리 볼륨을 마운트한다. 메모리 한도는 약 18 GiB인 체크포인트보다 충분히 크게 잡아 운영체제 페이지 캐시가 이를 보관하도록 한다. 원문에서 제시한 Qwen3.5-9B·g6.8xlarge·캐시 구성에서는 재시작 시 모델 가중치 로딩 시간이 약 145초에서 약 2초로 줄었다.
8. 배포 순서와 서비스 접근
먼저 deploy_cluster.sh로 VPC, OIDC 공급자, 핵심 애드온과 CPU 시스템 노드 그룹을 포함한 EKS 클러스터를 생성하며, 이 스크립트는 반복 실행 가능한 방식으로 작성되어 신규 실행에 15~20분이 걸린다. 다음 deploy_node_group.sh는 EFA를 활성화한 두 g6.8xlarge를 단일 가용 영역에 배치하고 role=gpu-worker 라벨을 지정하며, EFA device plugin도 설치하는 이 단계는 약 5~20분, 특히 첫 실행에는 더 오래 걸릴 수 있다. install_kuberay.sh는 Helm으로 KubeRay operator를 시스템 노드 그룹에 설치해 GPU 노드를 추론에 사용할 수 있게 하며, 약 1~2분이 소요된다. deploy_ray_service.sh는 envsubst로 이미지·모델·이름 변수를 매니페스트에 반영해 적용하고, head의 Ready 상태, 두 worker의 Running 상태, Serve 앱의 RUNNING 상태를 차례로 기다리며 각 노드가 모델을 로딩하므로 수분이 걸릴 수 있다. deploy_ray_service.sh status로 pod가 배치된 노드와 사용 가능한 GPU 용량을 확인할 수 있다. Serve 앱이 정상화되면 KubeRay가 안정적인 <rayservice-name>-serve-svc Service를 생성하고, 예제는 kubectl port-forward -n inference svc/ray-llm-serve-svc 8000:8000으로 접근한다. 제공된 source_body는 이 포트 전달 명령 뒤에서 끊겨 있어 이후의 실제 모델 호출 절차는 확인할 수 없다.
🧾 핵심 주장 / 시사점
- PD 분리는 연산 중심 prefill과 메모리 대역폭 중심 decode의 차이를 활용하는 설계다. 단계별 자원 간섭을 줄이는 효과와 KV 캐시 전송 비용을 함께 고려해야 한다.
- EFA를 활성화하는 것만으로 전송 경로가 완성되지는 않는다. 원문이 강조하는 NIXL의 LIBFABRIC 지정과 단일 가용 영역 배치가 통신 구성의 핵심 조건이다.
- 검증된 컨테이너와 YAML 애플리케이션 빌더는 의존성 구성 및 코드 작성 부담을 줄인다. 다만 약 145초에서 약 2초로 줄어든 재시작 로딩 시간은 제시된 모델·인스턴스·캐시 조건에서의 결과로 해석해야 한다.
✅ 액션 아이템
- RAG, 문서 요약, 에이전트 애플리케이션에서 prefill과 decode의 자원 간섭 및 KV 캐시 전송 비용을 고려해 단계 분리의 적용 적합성을 검토한다.
- NIXL의 LIBFABRIC 백엔드 지정과 EFA를 사용하는 GPU 노드의 단일 가용 영역 배치 조건을 확인한다.
- Qwen/Qwen3.5-9B의 max_model_len: 4096 등 메모리 설정과 두 g6.8xlarge를 위한 최소 64 vCPU 서비스 할당량을 확인한다.
❓ 열린 질문
- RAG, 문서 요약, 에이전트 애플리케이션에서 prefill과 decode를 분리했을 때 KV 캐시 전송 비용을 포함한 성능 이점은 어느 정도인가?
- NIXL의 LIBFABRIC 설정과 EFA의 단일 가용 영역 제약은 목표 배포 환경에서 충족되는가?
- Qwen/Qwen3.5-9B의 max_model_len: 4096과 gpu_memory_utilization: 0.90은 실제 입력의 컨텍스트 길이와 GPU 메모리 요구에 적합한가?