LLM 시스템을 위한 RDMA 지점 간 통신

새로운 LLM 통신 패턴을 해결하기 위한 우아한 도구

작성자Perplexity Research

LLM 시스템은 전통적으로 학습 및 추론 시 구조화된 병렬 패턴을 위해 집합 통신에 의존해 왔습니다. 그러나 분산 추론, 전문가 혼합(MoE) 라우팅, 비동기 강화 학습 미세 조정과 같은 새로운 워크로드는 지점 간(point-to-point) 통신의 유연성을 요구합니다.

최신 연구 논문에서는 NVIDIA ConnectX-7 및 AWS Elastic Fabric Adapter(EFA)를 포함하여 서로 다른 네트워크 인터페이스 컨트롤러(NIC)에서 작동하는 휴대용 RDMA 기반 통신 라이브러리인 TransferEngine을 소개합니다. Perplexity AI의 세 가지 프로덕션 시스템에서 해당 라이브러리의 효과를 입증했습니다.

분산 Prefill 및 Decode: 페이지 쓰기 및 UVM 감시자를 사용하는 계층별 KvCache 전송을 통해 Prefill 및 Decode 인스턴스가 독립적으로 확장될 수 있도록 합니다.

2초 이내의 RL 사후 학습을 위한 가중치 전송: 학습 노드와 추론 노드 간의 초고속 가중치 동기화.

AWS EFA에서 1조 개의 매개변수 모델 구현: 스캐터 및 배리어 기본 요소를 사용하는 저지연 MoE 발송 및 결합 작업으로, ConnectX-7에서 최첨단 지연 시간을 달성하고 AWS EFA와 호환되는 최초의 실행 가능한 구현입니다.

본 문서는 이러한 시스템을 구동하는 통신 라이브러리인 TransferEngine 자체에 초점을 맞춥니다. 전체 연구 논문은 arXiv에서 읽고 코드는 GitHub에서 살펴보십시오.

동기

집합 통신은 텐서 병렬 처리 및 데이터 병렬 처리와 같은 구조화된 병렬 패턴에서 탁월하지만, 새로운 워크로드에 대한 적용 가능성을 제한하는 제약 조건을 부과합니다.

고정된 멤버십: 집합 통신은 초기화 시 모든 참가자 간의 동기화된 설정과 함께 전역 통신 세계를 구축해야 합니다. 분산 추론의 경우 Prefill 및 Decode 인스턴스 수가 트래픽 패턴에 따라 동적으로 확장되어야 하므로 이는 문제가 됩니다.

균일한 텐서 모양: 집합 통신은 모든 참가자가 동일한 모양과 데이터 유형의 텐서를 교환한다고 가정합니다. MoE 라우팅에서 각 전문가에게 전송되는 토큰 수는 데이터에 따라 달라지며 런타임 시 확인되므로 밀집형 올투올(all-to-all) 집합 통신은 최악의 버퍼 크기로 패딩해야 합니다.

작업 순서 지정: 집합 통신 API는 애플리케이션이 일련의 작업이 완료되었음을 알아야 할 때에도 동기화된 의미 체계를 강제합니다. 예를 들어 KvCache 전송에서 Prefill 인스턴스에서 Decode 인스턴스로의 모든 페이지는 어떤 순서로든 도착할 수 있습니다. 마찬가지로 RL 가중치 업데이트에서도 모든 가중치 텐서는 순서 지정 제약 조건 없이 독립적으로 전송될 수 있습니다.

RDMA 기반 지점 간 통신은 이러한 한계를 자연스럽게 해결하지만 세 가지 장벽이 도입을 가로막고 있습니다.

라이브러리 제한 사항: torch.distributed 및 NCCL은 Send/Recv와 같은 지점 간 API를 제공하지만, 이러한 작업은 여전히 집합 통신의 의미 체계와 제한 사항을 계승합니다.

하드웨어 다양성: RC(Reliable Connection) 전송을 사용하는 NVIDIA ConnectX NIC가 가장 일반적인 RDMA 설정으로 순서 보장 배송을 제공합니다. AWS와 같은 클라우드 공급자는 다른 하드웨어를 사용하며, SRD(Scalable Reliable Datagram) 전송을 사용하는 EFA는 메시지를 순서 없이 배송합니다. 또한 EFA는 400Gbps 대역폭을 달성하기 위해 4개의 100Gbps NIC(또는 2개의 200Gbps NIC)를 집계해야 합니다.

소프트웨어 이식성: 기존 RDMA 라이브러리는 공급업체 종속성으로 인해 어려움을 겪습니다. DeepEP는 ConnectX에서만 사용할 수 있는 GPU 시작 RDMA(IBGDA)가 필요합니다. NVSHMEM은 EFA에서 심각한 성능 저하를 보입니다. Mooncake 및 NIXL과 같은 라이브러리는 EFA 지원이 부족하거나 초기 개발 단계에 머물러 있습니다. 결과적으로 LLM 추론을 위한 실행 가능한 공급자 간 솔루션은 존재하지 않습니다.

TransferEngine

TransferEngine은 네 가지 핵심 기능을 통해 이러한 장벽을 해결합니다.

공통 전송 가정: TransferEngine은 특정 NIC를 대상으로 하는 대신 기본 전송이 안정적이지만 순서가 지정되지 않았다고 가정합니다. 즉, RC 및 SRD 전송 모두에서 제공되는 보증의 교집합입니다.

ImmCounter 동기화: 메시지 순서에 의존하지 않고도 작동하는 완료 알림을 위한 새로운 기본 요소로, 동일한 코드가 순서 지정(RC) 및 비순서 지정(SRD) 전송 모두에서 실행될 수 있도록 합니다.

다중 NIC 지원: GPU당 여러 NIC를 투명하게 관리하여 대역폭을 최대화하기 위해 전송을 자동으로 샤딩합니다.

최소 API 표면: 복잡한 통신 패턴을 구축하는 데 충분한 소규모 작업 세트입니다.

TransferEngine 시스템 설계로, 두 개의 GPU/NUMA 도메인에 걸쳐 요청 및 콜백 경로, CPU 워커, UVM 버퍼, GPU, NIC를 보여줍니다.

TransferEngine은 GPU당 하나의 작업 스레드를 생성합니다. 각 작업자는 하드웨어 플랫폼에 따라 1~4개의 RDMA NIC를 조정하는 DomainGroup을 관리합니다(단일 ConnectX-7은 400Gbps를 제공하며 EFA는 4개의 100Gbps 어댑터 또는 2개의 200Gbps 어댑터가 필요함). DomainGroup 내에서 각 Domain은 하드웨어별로 다르며 단일 NIC를 관리하여 큐 쌍 관리, 작업 제출 및 완료 폴링을 처리합니다.

각 TransferEngine 인스턴스는 피어 식별 및 검색을 위해 단일 기본 주소를 노출합니다. 원격 피어는 네트워크 주소를 인코딩하고 대역 외 통신을 위해 직렬화할 수 있는 NetAddr 구조체를 교환합니다. 시스템의 모든 피어는 GPU당 동일한 수의 NIC를 사용해야 합니다. 이러한 균일한 구성을 통해 TransferEngine은 전송을 지능적으로 샤딩할 수 있습니다. 전송을 시작할 때 소스와 대상 모두 사용 가능한 NIC를 정확히 파악하여 모든 어댑터에 걸쳐 자동 부하 분산을 수행할 수 있습니다.

API 설계

TransferEngine은 기본 RDMA 인터페이스의 복잡성을 추상화하는 최소한의 API를 노출합니다.

rust
#[serde] struct NetAddr(Bytes); #[serde] struct MrDesc{ ptr: u64, rkeys: Vec<(NetAddr, u64)> } struct MrHandle(NonNull<c_void>); type Offset = u64; struct Pages{ indices: Vec<u32>, stride: u64, offset: Offset } struct PeerGroupHandle(u64); struct ScatterDst{ len: u64, src: Offset, dst: (MrDesc,Offset)} enum OnDone { Callback(fn () -> ()), Flag(Atomic<bool>) } trait TransferEngine { fn main_address() -> NetAddr; // Memory Region Management fn reg_mr(ptr, len, device) -> (MrHandle, MrDesc); // Two-sided Send/Recv fn submit_send(addr: NetAddr, msg: &[u8], cb: fn () -> ()); fn submit_recvs(len: u64, cnt: u64, cb: fn (&[u8]) -> ()); // One-sided Write fn expect_imm_count(imm: u32, count: u32, cb: fn () -> ()); fn submit_single_write(len: u64, imm: Option<u32> src: (MrHandle, Offset), dst: (MrDesc, Offset), OnDone); fn submit_paged_writes(page_len: u64, imm: Option<u32>, src: (MrHandle, Pages), dst: (MrDesc, Pages), OnDone); // One-sided Write to a group of peers fn add_peer_group(addrs: Vec<NetAddr>) -> PeerGroupHandle; fn submit_scatter(h: Option<PeerGroupHandle>, OnDone, imm: Option<u32>, src: MrHandle, dst: Vec<ScatterDst>); fn submit_barrier(h: Option<PeerGroupHandle>, OnDone, imm: u32, dst: Vec<MrDesc>); // Watcher for CPU-GPU synchronization fn alloc_uvm_watcher(cb: fn(u64,u64) -> ()) -> NonNull<u64>; }

API는 여러 핵심 구성 요소로 구성됩니다.

메모리 등록

RDMA 작업은 데이터 전송 전에 NIC에 메모리 영역을 등록해야 합니다. reg_mr 함수는 포인터, 길이 및 장치를 입력받아 두 개의 핸들을 반환합니다.

MrHandle: 전송 소스로 사용되는 로컬 핸들

MrDesc: 원격 피어와 교환하여 등록된 메모리에 쓸 수 있도록 하는 직렬화 가능한 설명자

MrDesc 구조체는 메모리 영역에 대한 원격 액세스 권한을 부여하는 원격 네트워크 주소 및 원격 키(NIC당 하나) 목록을 포함합니다. 이 설계는 다중 NIC 설정을 자연스럽게 처리합니다.

양방향 Send/Recv

소규모 메시지 및 RPC 스타일 제어 평면 메시지의 경우 TransferEngine은 submit_send 및 submit_recvs를 제공합니다.

submit_send: NetAddr로 식별되는 원격 피어에 메시지를 전송합니다. 메시지는 내부적으로 복사되므로 호출자는 버퍼를 즉시 재사용하거나 해제할 수 있습니다.

submit_recvs: 수신 버퍼 풀을 게시합니다. 메시지가 도착하면 수신된 데이터와 함께 콜백이 호출됩니다. 콜백이 완료된 후 버퍼는 자동으로 풀로 반환됩니다.

단방향 Write

대량 데이터 전송을 위해 TransferEngine은 제로 카피 단방향 쓰기 작업을 제공합니다.

submit_single_write: 소스에서 대상으로 연속적인 영역을 전송합니다.

submit_paged_writes: 인덱스, 스트라이드 및 오프셋으로 지정된 비연속 페이지를 전송합니다.

두 작업 모두 완료 시 수신자에게 원자적으로 전달되는 선택적 32비트 즉시 값을 허용합니다.

엔진은 대역폭을 최대화하기 위해 여러 NIC에 걸쳐 대규모 전송을 자동으로 샤딩합니다.

피어 그룹 및 대량 작업

여러 피어를 대상으로 하는 작업(MoE 라우팅에서 일반적)의 경우 TransferEngine은 최적화된 기본 요소를 제공합니다.

add_peer_group: 빠른 조회를 위해 피어 주소 세트를 미리 등록합니다.

submit_scatter: 그룹의 각 피어에 서로 다른 데이터 조각을 전송합니다.

submit_barrier: 모든 피어에 즉시 알림만 전송합니다(0바이트 전송).

이러한 작업은 내부 데이터 구조를 캐싱하고 RDMA 작업 요청을 템플릿화하여 반복적인 설정 오버헤드를 방지함으로써 낮은 지연 시간을 위해 최적화됩니다.

UVM 감시자: GPU-to-CPU 신호 전달

LLM 시스템의 주요 과제는 RDMA 전송과 GPU 진행 상황을 조정하는 것입니다. 이상적으로는 GPU가 출력을 생성하자마자 데이터 전송이 시작되어야 하지만 CPU에서 GPU를 폴링하는 것은 비효율적입니다.

TransferEngine은 UVM 감시자를 통해 이를 해결합니다. 이는 GPU 커널(CUDA 그래프 내에서도)에 의해 업데이트되고 GDRCopy를 사용하여 CPU에 의해 효율적으로 폴링될 수 있는 통합 가상 메모리 위치입니다. UVM 감시자가 할당되면 GPU가 값을 업데이트할 때마다 호출되는 콜백이 등록됩니다.

이를 통해 계층별 KvCache 전송과 같은 패턴이 가능합니다. 각 트랜스포머 계층이 완료된 후 GPU는 감시자를 증분하여 후속 계층이 계속 계산되는 동안 해당 계층의 KvCache에 대한 RDMA 전송을 트리거합니다.

완료 알림

TransferEngine은 개시자 측(submit_write 호출자)과 수신자 측(쓰기 작업의 대상) 모두에 대한 완료 알림 메커니즘을 제공합니다.

개시자 측 알림: 쓰기 작업이 제출되면 호출자는 콜백 함수 또는 원자적 플래그를 통해 완료 시 알림을 받도록 선택할 수 있습니다. 이 알림은 데이터가 소스 버퍼에서 성공적으로 전송되었음을 확인합니다.

수신자 측 알림: 핵심 혁신은 32비트 즉시 숫자로 인덱싱된 카운터를 추적하는 구성 요소인 ImmCounter입니다. 쓰기 작업에 즉시 값이 포함되면 데이터 전달 시 수신자의 ImmCounter가 원자적으로 증분됩니다. expect_imm_count 함수를 사용하면 수신자가 특정 즉시 값이 목표 카운트에 도달할 때 실행되는 콜백을 등록할 수 있습니다. 카운터는 GDRCopy를 통해 GPU와 투명하게 동기화되거나 직접 폴링되거나 콜백을 통해 모니터링될 수 있습니다.

이 수신자 측 메커니즘은 메시지 순서 지정을 요구하지 않고도 안정적인 동기화를 제공합니다. 이 메커니즘은 ConnectX NIC(RC 전송을 통한 순서 보장 배송)와 EFA NIC(SRD 전송을 통해 순서 없이 메시지 배송) 모두에서 동일하게 잘 작동합니다.

구현

TransferEngine은 Rust로 구현되었으며 할당, 스레딩 및 동기화를 신중하게 최적화하여 지연 시간을 최소화하고 높은 처리량을 달성합니다. 엔진은 각 DomainGroup당 하나의 작업 스레드를 생성하며, 각각 장치가 연결된 NUMA 노드의 CPU 코어에 고정되어 스케줄링 및 메모리 액세스 지연 시간을 모두 최소화합니다. 도메인별 데이터 구조는 고정 후 할당되어 메모리가 올바른 NUMA 노드에 예약되도록 합니다. 하나의 작업 스레드는 최대 4개의 Domain(각각 단일 NIC 관리)을 처리하며, 다른 전용 스레드는 GPU를 폴링하여 UVM 감시자를 업데이트합니다. 스레드 간 통신은 잠금 없는 큐를 통해 수행됩니다.

API는 요청을 적절한 장치를 서비스하는 DomainGroup으로 전달하며, 첫 번째 그룹은 호스트도 서비스합니다. 밀접한 루프에서 도메인 작업자는 새 작업을 폴링하고 새 요청 제출을 우선시합니다. 하드웨어 및 구성에 따라 요청은 사용 가능한 Domain에 걸쳐 샤딩되고 부하 분산됩니다. 복합 요청의 첫 번째 쓰기는 NIC의 송신 큐에 즉시 게시됩니다. 새 요청이 소진되면 작업자는 보류 중인 작업을 진행하여 하드웨어 파이프라인을 채우기 위해 쓰기를 게시합니다. 그 후 완료 큐를 폴링하여 완료된 전송 및 즉시 카운터 증분을 쿼리합니다. 이벤트는 집계되어 전송별 알림을 전달하며, 모든 그룹이 공유하는 전용 콜백 스레드에 전송을 인계합니다.

DomainGroup 내부의 샤딩은 유연합니다. 전송은 인덱스별로 특정 NIC를 대상으로 할 수 있습니다. 단일 쓰기는 분할될 수 있습니다. 모두 여러 쓰기로 변환되는 페이지 전송, 스캐터 및 배리어 작업은 모든 NIC에 걸쳐 샤딩될 수 있습니다.

하드웨어별 최적화

TransferEngine 내의 Domain은 제어 대상 하드웨어에 맞게 전문화되고 최적화되어 있습니다.

AWS EFA: EFA 구현은 libfabric을 사용하며 DomainGroup 내의 NIC당 패브릭 도메인을 관리합니다. 주요 구현 결정 및 최적화는 다음과 같습니다.

0바이트 쓰기: EFA는 0바이트 쓰기에 대해서도 유효한 대상 설명자를 요구함으로써 RDMA 사양에서 벗어납니다. 따라서 TransferEngine은 모든 전송에 대해 유효한 설명자를 강제합니다.

작업 요청 템플릿화: 게시하기 전에 libfabric 구조체의 공통 필드를 미리 채우고 유지하여 대량 전송 및 피어 그룹 작업에 대한 작업당 오버헤드를 줄입니다.

NVIDIA ConnectX-7: ConnectX-7 구현은 RC 전송과 함께 libibverbs를 사용합니다. 주요 구현 결정 및 최적화는 다음과 같습니다.

핸드셰이크를 위한 UD 큐 쌍: 각 Domain은 RC 연결 핸드셰이크를 교환하기 위해 신뢰할 수 없는 데이터그램(UD) 큐 쌍을 사용합니다.

별도의 RC 큐 쌍: 피어당 2개의 RC 큐 쌍을 생성하는데, 하나는 양방향 Send/Recv 작업용이고 다른 하나는 단방향 Write 및 WriteImm 작업용입니다. Recv와 WriteImm 완료가 모두 게시 순서대로 작업 요청을 소비하므로, 간섭 없이 WriteImm을 지원하면서 상위 수준의 Recv 의미 체계를 허용하기 위해 이러한 분리가 필요합니다.

작업 요청 템플릿화: 대량 전송 및 피어 그룹을 위한 작업 요청 구조체의 공통 필드를 미리 채웁니다.

작업 요청 체이닝: ibv_send_wr의 next 포인터를 통해 최대 4개의 작업 요청을 연결하여 NIC에 대한 도어벨 링 수를 줄입니다.

완화된 순서 지정: IBV_ACCESS_RELAXED_ORDERING을 활성화하여 NIC와 GPU 메모리 간의 순서 어긋난 PCIe 트랜잭션을 허용합니다.

결론

TransferEngine은 서로 다른 RDMA 전송 전반의 보증 교집합을 대상으로 하고 ImmCounter를 통한 순서 독립적 동기화를 도입함으로써 LLM 시스템의 지점 간 통신을 위한 휴대용 기반을 제공합니다. 이 라이브러리는 오픈 소스이며 GitHub에서 사용할 수 있습니다. 커뮤니티가 이를 탐색하고 대규모 머신 러닝 시스템의 새로운 통신 패턴을 구축하는 데 활용하기를 권장합니다.

인용

BibTeX
@online{perplexity2025RdmaPointToPointCommunicationForLlmSystems,
  author = {{Perplexity} and Nandor Licker and Kevin Hu and Vladimir Zaytsev and Lequn Chen},
  title = {{RDMA point-to-point communication for LLM systems}},
  date = {2025-11-05},
  url = {https://www.perplexity.ai/hub/blog/rdma-point-to-point-communication-for-llm-systems}
}