IPC 엔진
IPC 엔진 (IPC Engine)
IPC 가중치 전송 엔진은 CUDA IPC(Inter-Process Communication) 핸들을 사용해 같은 GPU의 트레이너와 추론 워커 간 GPU 메모리를 직접 공유합니다. 데이터 복사가 전혀 없어, 트레이닝과 추론을 같은 GPU에 배치할 때 가장 효율적인 옵션입니다. 멀티 GPU 설정도 지원됩니다 — 가중치가 각 GPU에 모두 모아지고, 올바른 동일 위치(colocated) 프로세스가 이를 추출합니다.
출처: 문서
본문
IPC를 언제 사용하나 (When to Use IPC)
- 트레이닝과 추론이 같은 GPU를 공유(colocated)할 때
동작 방식 (How It Works)
- 트레이너가 각 가중치에 대한 CUDA 텐서를 만들고
torch.multiprocessing.reductions.reduce_tensor로 IPC 핸들을 생성합니다. 멀티 GPU 설정(예: FSDP)에서는 각 트레이너 랭크가 핸들을 생성하기 전에 각 파라미터의 전체 텐서를 자신의 GPU에 물질화합니다 — 이는ModuleSource가 대신 처리해 줍니다. - 각 트레이너 랭크가 자신의 핸들을 all-gather에 기여합니다. 송신자가 이들을 병합해 각 페이로드가 모든 GPU UUID를 자신의 인자에 매핑하게 하고, 병합된 핸들을 클라이언트를 통해 추론 엔진으로 보냅니다. 각 워커는 자신의 GPU에 대한 핸들만 읽습니다.
- 추론 워커가
rebuild_cuda_tensor로 핸들에서 텐서를 재구성하며, 트레이너의 GPU 메모리에서 직접 읽습니다.
NCCL과 달리 IPC 전송은 직선형입니다: update_weights가 곧 전송이며, 이는 클라이언트를 타고 가므로 겹칠(overlap) 동시 브로드캐스트가 없습니다.
참고
2단계의 핸들 all-gather는 기본 프로세스 그룹에서 실행됩니다. 즉 그 그룹이 정확히 colocated 트레이너 랭크들의 집합이고 송신자가 그 멤버라고 가정합니다. 분산 그룹이 없으면 no-op입니다.
경고
IPC 핸들은 직렬화된 Python 객체 전송을 수반합니다. HTTP 전송을 사용할 때 서버와 클라이언트 모두에 VLLM_ALLOW_INSECURE_SERIALIZATION=1을 설정해야 합니다. IPC 핸들은 HTTP 전송을 위해 피클링되고 base64 인코딩되기 때문입니다.
추론 측 (Inference Side)
from vllm import LLM
from vllm.config import WeightTransferConfig
llm = LLM(model="my-model", weight_transfer_config=WeightTransferConfig(backend="ipc"))
vllm serve my-model --weight-transfer-config '{"backend": "ipc"}'
IPC는 데이터 플레인 rendezvous가 필요 없으므로 init_transfer_engine은 채널을 열지 않습니다 — 트레이너가 handshake에서 보내는 packed 플래그만 기록하며, receive_weights가 이를 읽습니다. 즉 전송이 packed인지 여부는 추론 측에서 결코 구성하지 않습니다.
트레이너 측 (Trainer Side)
from vllm.distributed.weight_transfer import (
ModuleSource,
HTTPVLLMWeightSyncClient,
WeightTransferTrainerFactory,
)
from vllm.distributed.weight_transfer.ipc_engine import IPCTrainerInitInfo
engine = WeightTransferTrainerFactory.trainer_init(
init_info=IPCTrainerInitInfo(rank=0, packed=False), # rank 0 is the sender
client=HTTPVLLMWeightSyncClient("http://localhost:8000"),
source=ModuleSource(model),
)
engine.send_weights() # once per sync
send_weights()는 start_weight_update, 전송 자체, finish_weight_update를 구동하며, post-send 배리어 이후까지 IPC 공유 복사본에 대한 강한 참조를 유지합니다 — 그렇지 않으면 소비자의 뷰가 dangling이 됩니다.
여기서 VLLMWeightSyncClient는 무엇이든 동작합니다 — 내장 HTTP·Ray 클라이언트, 또는 자체 스택용 어댑터. 엔진은 어느 쪽이든 동일합니다.
IPCTrainerInitInfo
| 필드 | 기본값 | 설명 |
|---|---|---|
rank |
— | 키워드 전용. 이 트레이너 프로세스의 랭크; 0이 송신자 |
packed |
False |
청크형, 한정 메모리 전송(아래 참고) |
packed_buffer_size_bytes |
1 GiB | packed=True일 때 청크 크기 |
packed는 반드시 양측이 동의해야 하는 wire 파라미터입니다: trainer_init이 이를 워커에 보내면 워커가 기록하고 그에 따라 디코딩합니다. 이는 WeightTransferConfig 필드도, per-round update_weights 필드도 아닙니다. packed_buffer_size_bytes는 프로듀서 전용입니다 — 소비자는 IPC 핸들 + per-chunk tensor_sizes에서 재구성하므로 버퍼 크기가 필요 없습니다.
Packed (청크형) 전송 (Packed (Chunked) Transfer)
기본적으로 모든 가중치가 단일 update_weights 호출로 전송됩니다. 큰 모델에서는 이로 인해 전체 모델이 양쪽 GPU 메모리에 동시에 상주해야 합니다. packed=True를 설정하면 청크형 전송을 한정된 GPU 메모리로 활성화합니다:
- 가중치가 고정 크기 packed 버퍼(
packed_buffer_size_bytes)로 연결됩니다. - 각 청크는 단일
start_weight_update/finish_weight_update괄호 안에서 별도의update_weights호출로 전송되므로, 레이어별 재로드 패스는 청크 수와 무관하게 시작에서 한 번 초기화되고 끝에서 한 번 완료됩니다. - 각 청크가 소비된 후 해당 청크의 GPU 메모리는 회수될 수 있습니다.
engine = WeightTransferTrainerFactory.trainer_init(
init_info=IPCTrainerInitInfo(
rank=0,
packed=True,
packed_buffer_size_bytes=256 * 1024 * 1024, # 256 MB chunks
),
client=client,
source=ModuleSource(model),
)
멀티 랭크 트레이너에서 프로듀서는 청크 간 하나의 버퍼를 재사용하므로, packed 모드는 랭크 간 per-chunk 배리어를 수반합니다. 배리어가 없으면 colocated 워커가 현재 청크를 읽는 동안 한 랭크가 자기를 덮어쓸 수 있기 때문입니다. 이는 send_weights() 내부에서 처리됩니다.
랭크 로컬 업데이트 (Rank-Local Updates)
워커마다 다른 파라미터 하위 집합을 위해 update_info는 워커 랭크로 인덱싱된 리스트일 수 있습니다. 이 형태는 collective 백엔드가 모든 워커의 참여를 요구하므로 IPC 전용입니다.
예시 (Examples)
- RLHF with IPC weight syncing (
vllm serve, HTTP) — 여기서 시작하세요. 서버와 트레이닝 모델이 단일 GPU를 공유합니다. HTTP 컨트롤 플레인, CUDA IPC 데이터 플레인. 자체 서버를 띄우고 종료합니다 - RLHF with IPC + FSDP2 and expert parallelism — 같은 4개 GPU에서
--data-parallel-size 4서버와 colocated된 멀티 랭크 트레이너: 모든 FSDP 랭크가 엔진을 만들고 핸들 all-gather에 참여하며, packed 청킹과 전송 주위에 sleep/wake를 사용
더 알아보기 (Learn more)
- 가중치 전송 개요 — 플러그형 백엔드 시스템
- 가중치 전송: NCCL — 멀티 GPU 엔진
- 비동기 RL — pause/resume API