분산 서빙 (Distributed Serving)
분산 서빙 (Distributed Serving)
단일 모델 복제본에 대한 분산 추론 전략
단일 모델 복제본에 대한 분산 추론 전략을 고를 때는 다음 기준을 참고하면 돼요.
- 단일 GPU (분산 추론 불필요): 모델이 GPU 한 개에 들어간다면 분산 추론은 아마 필요 없어요. 그 GPU에서 바로 추론을 돌리면 됩니다.
- 단일 노드·다중 GPU (tensor parallel 추론): 모델이 GPU 한 개에는 너무 크지만, 여러 GPU가 달린 노드 한 대에는 들어간다면 *텐서 병렬화(tensor parallelism)*를 써요. 예를 들어 GPU 4개가 달린 노드라면
tensor_parallel_size=4로 설정하죠. - 다중 노드·다중 GPU (tensor parallel + pipeline parallel 추론): 모델이 노드 한 대로는 너무 크면 텐서 병렬화와 *파이프라인 병렬화(pipeline parallelism)*를 함께 사용해요.
tensor_parallel_size에는 노드별 GPU 수를,pipeline_parallel_size에는 노드 수를 설정하면 됩니다. 예를 들어 노드 2개에 노드당 GPU 8개라면tensor_parallel_size=8,pipeline_parallel_size=2로 설정하죠.
모델이 들어갈 GPU 메모리가 충분해질 때까지 GPU와 노드 수를 늘려요. tensor_parallel_size는 노드별 GPU 수, pipeline_parallel_size는 노드 수로 설정하면 됩니다.
모델에 맞는 자원을 충분히 확보한 뒤 vllm을 실행해 보세요. 로그에서 다음과 같은 메시지를 찾아보죠.
INFO 07-23 13:56:04 [kv_cache_utils.py:775] GPU KV cache size: 643,232 tokens
INFO 07-23 13:56:04 [kv_cache_utils.py:779] Maximum concurrency for 40,960 tokens per request: 15.70x
GPU KV cache size 줄은 GPU KV 캐시에 한 번에 저장할 수 있는 총 토큰 수를 보고해요. Maximum concurrency 줄은 요청 하나가 지정한 토큰 수(위 예시에서는 40,960)를 필요로 할 때 동시에 서빙할 수 있는 요청 수의 추정치를 알려주죠. 요청당 토큰 수는 모델 설정의 최대 시퀀스 길이인 ModelConfig.max_model_len에서 가져옵니다. 이 값들이 요구하는 처리량보다 낮다면 클러스터에 GPU나 노드를 더 추가하세요.
엣지 케이스: GPU 분할이 고르지 않을 때
모델이 노드 한 대에 들어가는데 GPU 수가 모델 크기를 균등하게 나누지 못한다면, 파이프라인 병렬화를 켜면 돼요. 파이프라인 병렬화는 모델을 레이어 단위로 나누고 균등하지 않은 분할도 지원하거든요. 이 경우 tensor_parallel_size=1로 두고 pipeline_parallel_size에는 GPU 수를 설정하세요. 게다가 노드의 GPU에 NVLINK 인터커넥트가 없다면(예: L40S) 텐서 병렬화 대신 파이프라인 병렬화를 쓰는 쪽이 처리량이 높고 통신 오버헤드가 낮아요.
Mixture of Experts (MoE) 모델의 분산 서빙
전문가(expert)의 고유한 병렬성을 활용하기 위해, 전문가 레이어에는 별도의 병렬화 전략을 쓰는 게 유리한 경우가 많아요. vLLM은 Data Parallel 어텐션과 Expert 또는 Tensor Parallel MoE 레이어를 조합한 대규모 배포를 지원합니다. 자세한 내용은 Data Parallel Deployment 문서를 참고하세요.
단일 노드 배포
vLLM은 분산 텐서 병렬 및 파이프라인 병렬 추론·서빙을 지원해요. 구현에는 Megatron-LM의 텐서 병렬 알고리즘이 포함됩니다.
기본 분산 런타임은 다중 노드 추론에 Ray, 단일 노드 추론에 Python 네이티브 multiprocessing이에요. 기본값은 LLM 클래스의 distributed_executor_backend 또는 API 서버의 --distributed-executor-backend로 바꿀 수 있습니다. multiprocessing은 mp, Ray는 ray로 설정하면 돼요.
다중 GPU 추론을 하려면 LLM 클래스의 tensor_parallel_size를 원하는 GPU 수로 설정하세요. 예를 들어 GPU 4개로 추론을 돌리려면:
from vllm import LLM
llm = LLM("facebook/opt-13b", tensor_parallel_size=4)
output = llm.generate("San Francisco is a")
다중 GPU 서빙을 하려면 서버를 시작할 때 --tensor-parallel-size를 포함하세요. 예를 들어 API 서버를 GPU 4개로 돌리려면:
vllm serve facebook/opt-13b \
--tensor-parallel-size 4
파이프라인 병렬화를 활성화하려면 --pipeline-parallel-size를 추가하면 돼요. 예를 들어 파이프라인 병렬화와 텐서 병렬화를 함께 써서 API 서버를 GPU 8개로 돌리려면:
# Eight GPUs total
vllm serve gpt2 \
--tensor-parallel-size 4 \
--pipeline-parallel-size 2
다중 노드 배포
노드 한 대에 모델을 담을 수 있는 GPU가 부족하다면 여러 노드에 걸쳐 vLLM을 배포하세요. 모든 노드가 모델 경로와 Python 패키지를 포함한 동일한 실행 환경을 제공해야 해요. 컨테이너 이미지를 쓰면 환경을 일관되게 유지하고 호스트 간 이질성을 숨기기 편리해서 권장됩니다.
Ray란 무엇인가?
Ray는 Python 프로그램을 확장하기 위한 분산 컴퓨팅 프레임워크예요. 다중 노드 vLLM 배포는 Ray를 런타임 엔진으로 사용할 수 있습니다.
vLLM은 Ray를 사용해 여러 노드에 걸친 태스크의 분산 실행을 관리하고, 실행이 일어날 위치를 제어해요.
Ray는 또한 vLLM을 엔진으로 활용하는 대규모 오프라인 배치 추론과 온라인 서빙을 위한 고수준 API도 제공합니다. 이 API들은 vLLM 워크로드에 프로덕션급 내결함성, 스케일링, 분산 관측성을 더해줘요.
Ray는 선택적(optional) 의존성입니다. Ray 기반 실행을 쓰기 전에 명시적으로 설치하세요. 예:
pip install "ray[cgraph]"
자세한 내용은 Ray 문서를 참고하세요.
컨테이너로 Ray 클러스터 구성하기
헬퍼 스크립트 examples/ray_serving/run_cluster.sh는 여러 노드에 컨테이너를 시작하고 Ray를 초기화해요. 기본적으로 이 스크립트는 관리자 권한 없이 Docker를 실행합니다. 그래서 프로파일링이나 트레이싱 시 GPU 성능 카운터에 접근하지 못하죠. 관리자 권한을 활성화하려면 Docker 명령에 --cap-add=CAP_SYS_ADMIN 플래그를 추가하세요.
한 노드를 헤드 노드로 골라서 다음을 실행해요:
bash run_cluster.sh \
vllm/vllm-openai \
<HEAD_NODE_IP> \
--head \
/path/to/the/huggingface/home/in/this/node \
-e VLLM_HOST_IP=<HEAD_NODE_IP>
각 워커 노드에서는 다음을 실행하세요:
bash run_cluster.sh \
vllm/vllm-openai \
<HEAD_NODE_IP> \
--worker \
/path/to/the/huggingface/home/in/this/node \
-e VLLM_HOST_IP=<WORKER_NODE_IP>
VLLM_HOST_IP는 워커마다 고유해야 해요. 이 명령들을 실행 중인 셸은 계속 열어 두세요. 셸을 닫으면 클러스터가 종료됩니다. 모든 노드가 서로의 IP 주소로 통신할 수 있는지도 확인하세요.
네트워크 보안
보안을 위해 VLLM_HOST_IP는 사설 네트워크 세그먼트의 주소로 설정하세요. 이 네트워크를 통해 보내는 트래픽은 암호화되지 않으며, 엔드포인트는 공격자가 네트워크에 접근하면 임의 코드를 실행할 수 있게 악용될 수 있는 형식으로 데이터를 교환해요. 신뢰할 수 없는 주체가 네트워크에 닿지 못하게 하세요.
어느 노드에서든 컨테이너에 들어가 ray status와 ray list nodes를 실행해서 Ray가 예상한 수의 노드와 GPU를 찾는지 확인하세요.
팁
또는 KubeRay로 Ray 클러스터를 구성할 수도 있어요. 자세한 내용은 KubeRay vLLM 문서를 참고하세요.
Ray 클러스터에서 vLLM 실행하기
팁
Ray가 컨테이너 안에서 실행 중이라면, 이 가이드의 나머지 명령들은 호스트가 아니라 컨테이너 안에서 실행하세요. 컨테이너 안 셸을 열려면 노드에 연결한 뒤 docker exec -it <container_name> /bin/bash를 사용하면 돼요.
Ray 클러스터가 실행되면, 단일 노드 설정에서 하듯이 vLLM을 사용하면 됩니다. Ray 클러스터 전체의 모든 자원이 vLLM에 보이므로, 한 노드에서 vllm 명령을 한 번만 실행하면 충분해요.
일반적인 관행은 텐서 병렬 크기를 노드별 GPU 수로, 파이프라인 병렬 크기를 노드 수로 설정하는 거예요. 예를 들어 노드 2개(노드당 GPU 8개)에 걸쳐 GPU 16개가 있다면 텐서 병렬 크기를 8, 파이프라인 병렬 크기를 2로 설정하세요:
vllm serve /path/to/the/model/in/the/container \
--tensor-parallel-size 8 \
--pipeline-parallel-size 2 \
--distributed-executor-backend ray
또는 tensor_parallel_size를 클러스터의 총 GPU 수로 설정할 수도 있어요:
vllm serve /path/to/the/model/in/the/container \
--tensor-parallel-size 16 \
--distributed-executor-backend ray
MultiProcessing으로 vLLM 실행하기
Ray 외에도, 다중 노드 vLLM 배포는 런타임 엔진으로 multiprocessing을 사용할 수 있어요. 노드 2개(노드당 GPU 8개)에 tp_size=8, pp_size=2로 모델을 배포하는 예시를 볼게요.
한 노드를 헤드 노드로 골라서 실행하세요:
vllm serve /path/to/the/model/in/the/container \
--tensor-parallel-size 8 --pipeline-parallel-size 2 \
--nnodes 2 --node-rank 0 \
--master-addr <HEAD_NODE_IP>
다른 워커 노드에서는 다음을 실행하세요:
vllm serve /path/to/the/model/in/the/container \
--tensor-parallel-size 8 --pipeline-parallel-size 2 \
--nnodes 2 --node-rank 1 \
--master-addr <HEAD_NODE_IP> --headless
텐서 병렬화를 위한 네트워크 통신 최적화
효율적인 텐서 병렬화는 빠른 노드 간 통신이 필요해요. 가급적 InfiniBand 같은 고속 네트워크 어댑터를 쓰는 게 좋죠. 클러스터가 InfiniBand를 사용하도록 구성하려면 examples/ray_serving/run_cluster.sh 헬퍼 스크립트에 --privileged -e NCCL_IB_HCA=mlx5 같은 추가 인자를 덧붙이세요. 필요한 플래그에 대한 자세한 내용은 시스템 관리자에게 문의하세요.
GPUDirect RDMA 활성화
GPUDirect RDMA(Remote Direct Memory Access)는 네트워크 어댑터가 CPU와 시스템 메모리를 거치지 않고 GPU 메모리에 직접 접근할 수 있게 해주는 NVIDIA 기술이에요. 이 직접 접근은 지연 시간과 CPU 오버헤드를 줄여줍니다. 노드 간 GPU 대 GPU 대용량 데이터 전송에 유리하죠.
vLLM으로 GPUDirect RDMA를 활성화하려면 다음 설정을 구성하세요:
IPC_LOCK보안 컨텍스트: 메모리 페이지를 잠그고 디스크로 스왑되지 않도록 컨테이너의 보안 컨텍스트에IPC_LOCK캐퍼빌리티를 추가하세요./dev/shm공유 메모리: 프로세스 간 통신(IPC)을 위한 공유 메모리를 제공하도록 pod 스펙에/dev/shm을 마운트하세요.
Docker를 쓴다면 컨테이너를 다음과 같이 구성하세요:
docker run --gpus all \
--ipc=host \
--shm-size=16G \
-v /dev/shm:/dev/shm \
vllm/vllm-openai
Kubernetes를 쓴다면 pod 스펙을 다음과 같이 구성하세요:
...
spec:
containers:
- name: vllm
image: vllm/vllm-openai
securityContext:
capabilities:
add: ["IPC_LOCK"]
volumeMounts:
- mountPath: /dev/shm
name: dshm
resources:
limits:
nvidia.com/gpu: 8
requests:
nvidia.com/gpu: 8
volumes:
- name: dshm
emptyDir:
medium: Memory
...
GPUDirect RDMA 동작 확인
InfiniBand 카드가 GPUDirect RDMA를 사용하는지 확인하려면 상세 NCCL 로그와 함께 vLLM을 실행하세요: NCCL_DEBUG=TRACE vllm serve ....
그다음 NCCL 버전과 사용된 네트워크를 확인해 보죠.
- 로그에서
[send] via NET/IB/GDRDMA를 발견하면 NCCL이 GPUDirect RDMA와 함께 InfiniBand를 사용하고 있다는 뜻이고, 효율적입니다. - 로그에서
[send] via NET/Socket을 발견하면 NCCL이 원시 TCP 소켓을 사용했다는 뜻이고, 노드 간 텐서 병렬화에는 효율적이지 않아요.
Hugging Face 모델 미리 다운로드
Hugging Face 모델을 쓴다면 vLLM을 시작하기 전에 모델을 내려받는 것을 권장해요. 모든 노드의 같은 경로에 모델을 내려받거나, 모든 노드가 접근할 수 있는 분산 파일 시스템에 모델을 저장하세요. 그리고 리포지토리 ID 대신 모델 경로를 전달하면 됩니다. 그렇지 않다면 run_cluster.sh에 -e HF_TOKEN=<TOKEN>을 추가해 Hugging Face 토큰을 제공하세요.
분산 배포 문제 해결
분산 디버깅에 대한 자세한 내용은 분산 배포 문제 해결 문서를 참고하세요.