NCCL and NVSHMEM — two GPU communication models, side by side
multi-GPU 시대의 communication 라이브러리 두 갈래 — NCCL 의 collective 추상과 NVSHMEM 의 PGAS put/get 추상. Jeff Hammond 가 깐 이 강의는 두 모델이 같은 하드웨어 위에서 무엇을 다르게 약속하는지를 정리한다. NCCL 의 allreduce 한 줄과 NVSHMEM 의 shmem_put 한 줄이 같은 NVLink 를 어떻게 다르게 사용하는지 — 그 차이가 곧 학습/추론 코드의 형태를 결정한다.
§ 01강의가 풀려는 문제· Two communication models, one hardware
“NCCL 만 있으면 되는 줄 알았는데, 왜 NVSHMEM 이 따로 있는가”
PyTorch 에서 dist.all_reduce 한 줄을 부르면 그게 NCCL 로 들어간다. 학습 코드의 99% 가 그 한 줄로 끝난다. 그런데 frontier inference / training 의 최근 코드 (DeepSeek, FlashInfer, NVSHMEM-based MoE) 는 NVSHMEM 을 직접 쓴다. 같은 NVLink, 같은 RDMA 위에서 — 왜 다른 인터페이스인가?
강의의 출발 질문 셋.
NCCL 의 collective 추상이 왜 부족할 때가 있는가 — host-launched, batched, bulk 동기 모델 자체의 한계.
NVSHMEM 의 PGAS 모델은 어떤 패턴을 추가로 표현 가능하게 하는가 — fine-grained, GPU-initiated, asynchronous put/get.
두 모델은 같이 쓰는가, 둘 중 하나만 쓰는가 — 답: 같이 쓴다. layer 가 다르다.
Jeff 의 입장은 “두 모델은 경쟁이 아니라 보완”이다. NCCL 은 큰 단위의 collective를 가장 빠르게, NVSHMEM 은 작은 단위의 비대칭 통신을 가장 자연스럽게. 이게 강의 전체를 끌고 가는 framing.
강의의 인지적 frame
두 라이브러리를 같은 추상 레이어로 보지 말 것 — NCCL 은 “모든 GPU 가 같은 데이터에 같은 연산을 한다” 의 collective 모델, NVSHMEM 은 “특정 GPU 의 특정 메모리 위치에 직접 쓴다” 의 one-sided 모델. 같은 trip 을 짤 때 “단체 관광버스” 와 “개별 우버” 의 차이.
“NCCL 은 batched, bulk synchronous. NVSHMEM 은 fine-grained, asynchronous. 둘 다 NVLink 위에서 돌지만 추상이 정반대다.”강의 §1 재구성 · Jeff Hammond
§ 02NCCL collective 의 추상· allreduce · broadcast · all-gather
“모두가 같이 부른다, 그리고 같이 끝난다” — bulk synchronous 의 약속
NCCL 의 핵심 단어는 collective. 모든 rank 가 같은 함수를 같은 인자로 부르고, 모두가 끝나야 다음 코드가 실행된다. ncclAllReduce, ncclBroadcast, ncclAllGather, ncclReduceScatter — 이 4-5개가 학습 코드의 99%.
collective 의 본질적 제약 두 가지.
모든 rank 가 참여 — 한 rank 만 빠지면 deadlock. communicator 의 멤버십이 고정이라는 의미.
모든 rank 가 같은 인자 — buffer 사이즈, dtype, op 가 일치해야 함. 한 rank 만 다른 사이즈를 부르면 정의되지 않은 동작.
이 제약이 곧 NCCL 의 강점이기도 하다 — 미리 알고 있으면 topology-aware 한 최적 알고리즘을 한 번 선택해서 같은 모양으로 반복할 수 있다. ring, tree, double binary tree — §05 에서 자세히.
PyTorch 위치
torch.distributed.all_reduce(tensor, op=ReduceOp.SUM) 한 줄이 들어가면 NCCL 의 ncclAllReduce 가 호출된다. backend='nccl' 으로 init 한 ProcessGroup 이 그대로 NCCL communicator. PyTorch 의 distributed API 의 추상이 곧 collective 모델 그 자체.
// NCCL 의 표준 사용 패턴 — host code
ncclComm_t comm;
ncclCommInitRank(&comm, nranks, id, rank);
// 각 rank 가 자기 GPU 에 sendbuff/recvbuff 준비
cudaMalloc(&sendbuff, size);
cudaMalloc(&recvbuff, size);
// 모든 rank 가 같이 부른다
ncclAllReduce(sendbuff, recvbuff,
count, ncclFloat,
ncclSum, comm, stream);
// stream 위에서 비동기로 실행됨 — 이후 sync 필요
cudaStreamSynchronize(stream);
FIG · 4 개 collective 의 데이터 흐름4 ranks 예시
AllReduce = ReduceScatter + AllGather 의 합성. 큰 메시지일 때 ring 알고리즘이 이 합성을 직접 활용해 latency 를 절반으로 줄인다 — §05 의 핵심.
§ 03NVSHMEM 의 PGAS 모델· Put · get · symmetric heap
“내가 너의 메모리에 직접 쓴다” — partitioned global address space
PGAS (Partitioned Global Address Space) 의 약속 — 각 GPU 의 메모리가 모두 같은 가상 주소 공간 안에 있다. 단, 물리적으로는 분산. 각 메모리 영역은 한 GPU 에 속한다 (partitioned). 다른 GPU 에 있는 변수에 접근하려면 shmem_get, 다른 GPU 에 쓰려면 shmem_put. 한쪽만 부르면 끝난다 — one-sided.
NVSHMEM 의 핵심 개념 셋.
symmetric heap — 모든 GPU 가 같은 사이즈의 heap 을 가지고, 같은 변수가 같은 offset 에 위치. nvshmem_malloc(size) 의 결과가 모두 같은 주소.
PE (Processing Element) — rank 의 NVSHMEM 명칭. nvshmem_my_pe() 가 자기 번호를, nvshmem_n_pes() 가 전체 수를 돌려줌.
one-sided primitives — nvshmem_put(dst, src, size, pe), nvshmem_get(dst, src, size, pe). 상대 PE 는 함수를 부르지 않아도 자기 메모리가 바뀐다.
왜 “symmetric”
같은 변수가 모든 GPU 의 같은 offset 에 있다는 약속이 있어야, “PE 3 의 변수 X 에 쓴다” 가 단순한 주소 계산으로 풀린다. 이게 PGAS 의 효율성의 출발점. MPI one-sided 와 같은 가족이지만 GPU-aware.
// NVSHMEM 의 표준 host code
nvshmem_init();
int me = nvshmem_my_pe();
int npes = nvshmem_n_pes();
// 모든 PE 가 같은 사이즈, 같은 offsetfloat* buf = (float*)
nvshmem_malloc(N * sizeof(float));
// PE 0 이 PE 1 의 buf[0..N) 를 자기 src 로 덮어씀if (me == 0) {
nvshmem_float_put(buf, src, N, 1);
}
// PE 1 은 아무것도 안 부른다 — 자기 buf 가 바뀐다
nvshmem_barrier_all();
nvshmem_free(buf);
nvshmem_finalize();
// Device 에서 직접 — kernel 안에서 통신__global__void exchange(float* buf) {
int tid = threadIdx.x;
int me = nvshmem_my_pe();
int peer = (me + 1) % nvshmem_n_pes();
// thread 단위 fine-grained put
nvshmem_float_p(&buf[tid], src[tid], peer);
}
왜 GPU-initiated 가 결정적인가
NVSHMEM 의 진짜 강점은 device-side API. 위 두 번째 코드처럼 kernel 안에서 직접 nvshmem_float_p 를 부른다 — kernel 을 끝내고 host 로 나갔다가 NCCL collective 를 부르는 게 아니라, 같은 kernel 안에서 통신과 계산이 섞인다.
“NCCL 은 host 가 ‘이제 통신해라’ 라고 시킨다. NVSHMEM 은 GPU 가 자기 코드 흐름 안에서 직접 통신한다 — 이 차이가 latency 를 한 자릿수 바꾼다.”강의 §3 재구성
§ 04두 모델의 비교· When to choose what
같은 NVLink, 다른 추상 — 무엇이 무엇을 약속하는가
두 라이브러리를 한 표 위에 펼쳐놓는다. 같은 hardware 위에서 다른 약속을 한다는 점이 명확해야 한다.
FIG · NCCL vs NVSHMEM 의 12개 차원same hardware, different promises
차원NCCLNVSHMEM의미
communication 모델collectivePGAS / one-sided바라보는 시점
참여 방식모든 rank한 쪽만non-symmetric 가능
동기화자동명시적 fencememory ordering
호출 위치host (mostly)host + devicein-kernel comm
메시지 단위큰 bufferword/elementfine-grained
topology 인지자동 최적화사용자 책임algorithm 선택
latency (small msg)~10–30 µs~1–5 µsdevice-side 효과
bandwidth (large)peak 가까움peak 가까움큰 메시지는 둘 다
programming difficulty낮음높음trade-off
PyTorch 통합기본실험적DeepEP 같은 용도
대표 use caseDDP, ZeRO, TPMoE dispatch, EPcollective vs sparse
fault tolerance제한적제한적L070 PCCL 참고
"latency vs bandwidth" 의 측면이 §08 에서 본격적으로 다뤄진다. 작은 메시지에서는 NVSHMEM, 큰 batched 메시지에서는 NCCL 이 압도. 같은 GPU 시스템 위에서 두 도구를 같이 쓰는 게 자연스러운 결론.
NCCL 이 같은 collective 를 두 가지 방식으로 — 메시지 사이즈에 따라 다르다
NCCL 의 black box 안에 들어가면 같은 allreduce 가 ring 알고리즘과 tree 알고리즘 두 형태로 다르게 구현돼 있다. 작은 메시지에서는 tree (latency 우위), 큰 메시지에서는 ring (bandwidth 우위). NCCL 이 자동으로 고른다.
ring algorithm — bandwidth optimal
모든 GPU 가 ring 으로 연결됐다고 가정. 메시지를 N개 chunk 로 나눠 ring 위에서 N-1 step 을 반복. 각 step 에서 한 chunk 가 한 hop 이동. 결국 2(N-1) step으로 allreduce 가 끝남 (reduce-scatter + all-gather 합성).
강점 — 각 link 위 데이터 양이 (N-1)/N 으로 거의 1, 즉 bandwidth 거의 100% 사용. 약점 — step 수가 N 에 비례해 latency 누적.
tree algorithm — latency optimal
이진 트리 (또는 double binary tree) 형태로 GPU 들을 묶음. up-phase 에서 child 가 parent 로 reduce, root 에서 결과 집계 후 down-phase 에서 broadcast. step 수가 log N.
강점 — 작은 메시지에서 latency dominant 한 영역에서 ring 보다 빠름. 약점 — bandwidth 가 나무의 윗부분에서 병목 (root 가 단일 point).
FIG · ring vs tree 의 메시지 사이즈별 시간simple model
crossover 는 ~1MB 부근 (확인 필요, GPU/NVLink 세대마다 다름). NCCL 은 NCCL_ALGO=Ring|Tree|Auto 환경변수로 강제 선택 가능 — 디버깅용.
double binary tree
현대 NCCL 의 기본 — 두 개의 보완적 binary tree 를 동시에 돌려 tree 의 bandwidth 약점을 보강한다. 각 tree 가 서로 다른 link 만 사용하도록 짜여 있어서 NVLink 의 양방향 대역폭을 모두 활용. 이게 NCCL 2.x 가 “fastest collective library” 의 자리에 머물게 한 핵심 알고리즘.
§ 06GPU-initiated 통신· Device-side calls
kernel 이 끝나기 전에 통신을 시작한다 — overlap 의 진짜 의미
NCCL 은 host 가 launch 하는 collective. 그 결과 — kernel 이 끝나야 통신이 시작된다 (또는 별도 stream 위에 띄워야 함). NVSHMEM 은 kernel 안에서 직접 put/get. 한 thread block 이 작업을 끝내자마자 그 결과를 다른 GPU 로 send 시작.
FIG · NCCL host-launch vs NVSHMEM device-initiated같은 일, 다른 timeline
위 timeline 은 단순화. NCCL 도 별도 stream 으로 overlap 시킬 수 있지만, 같은 kernel 안에서의 fine-grained interleaving 은 NVSHMEM 의 device API 만 가능.
실전 예 — DeepEP / DeepSeek 의 expert parallelism dispatch. token 마다 어느 expert 로 갈지가 다르다. 이걸 NCCL 의 모든 token batched all-to-all 로 보내면 padding 과 sync 비용이 크다. NVSHMEM 의 per-token put 으로 보내면 정확히 필요한 만큼만, kernel 안에서 직접.
하드웨어 전제
device-initiated 통신은 GPUDirect RDMA (NIC ↔ GPU 메모리 직접 access) 와 IB Verbs / NVLink 의 GPU-side 트리거가 필요. 모든 cluster 에서 되는 건 아니다 — H100 + ConnectX-7 + IB switch 같은 환경에서 검증됨.
§ 07fault tolerance 와 retry· Resilience patterns
두 라이브러리 모두 “하나 죽으면 모두 죽는다” — 그 전제가 흔들리고 있다
NCCL 과 NVSHMEM 모두 tightly coupled 모델. communicator 안의 한 GPU/노드가 죽으면 collective 전체가 멈추고, 보통 process group 을 다시 만들어야 한다. 큰 학습 클러스터 (10k GPU) 에서는 이게 사실상 항상 일어난다.
현재의 핸들링 패턴 — checkpoint + restart. 정기적으로 체크포인트를 저장하고, NCCL communicator 가 깨지면 모든 rank 가 재시작해서 마지막 체크포인트부터 다시.
장점 — 단순하다. 추가 인프라 안 필요.
단점 — 재시작 비용이 크다. 10k GPU 클러스터에서 한 GPU 가 죽으면 모두 재시작.
이걸 깬 새 라이브러리가 L070 PCCL 의 시도 — collective 안에서 일부 rank 의 dropout 을 견디는 fault-tolerant collective. NCCL 자체는 NCCL 3.x 에서 partial recovery 를 검토중 (확인 필요).
FIG · failure modes현재 NCCL/NVSHMEM
실패 종류NCCLNVSHMEM
단일 GPU 죽음전체 hang전체 hangrestart 필요
노드 죽음restartrestart동일
network flaptimeouttimeoutenv timeout
stragglers대기대기slow rank 가 느림
elastic shrink미지원미지원PCCL 가 시도
“NCCL 은 ‘모두가 살아있다’ 를 가정한다. 1만 GPU 클러스터에서 그 가정은 매시간 깨진다 — fault tolerance 는 더 이상 옵션이 아니다.”학습 노트 · §7
§ 08latency vs bandwidth· Message-size regimes
같은 라이브러리 안에서도 메시지 사이즈에 따라 다른 알고리즘을 선택해야 하는 이유
communication 의 cost 모델 — T(n) = α + β·n. α 는 latency floor, β 는 reciprocal bandwidth, n 은 메시지 사이즈. 이 단순한 모델 위에서 알고리즘 선택의 모든 기준이 나온다.
FIG · 메시지 사이즈 regimeα-β model
regime크기 (대략)지배 비용최선 도구
tiny (control flag)≤ 16 Bα dominantNVSHMEM put
small (token feature)~ 4 KBα dominantNVSHMEM
medium (layer slice)~ 1 MBcrossoverNCCL tree
large (gradient bucket)~ 100 MBβ dominantNCCL ring
xlarge (whole model)~ 10 GBβ dominantNCCL ring + chunking
PyTorch DDP 의 gradient bucketing 이 정확히 이 regime 위에서 설계됐다 — 작은 gradient 들을 합쳐 ~25MB 단위로 묶어 NCCL ring allreduce 의 sweet spot 을 만든다.
그리고 — “latency 와 bandwidth 는 같은 hardware 위에서도 다른 측정치”. NVLink 의 peer bandwidth 는 ~600 GB/s 인데 latency 는 ~1 µs 영역. 같은 NVLink 가 어떤 메시지에 대해서는 빠르고, 어떤 메시지에 대해서는 어쩔 수 없이 늦다 — 사이즈가 작으면 α 가 지배.
측정 시점
NCCL bench (nccl-tests) 를 돌릴 때 꼭 메시지 사이즈를 sweep 한다. ./all_reduce_perf -b 8 -e 1G -f 2 처럼. 8B 부터 1GB 까지 두 배씩 — α 와 β 가 그래프에서 직접 분리된다.
NCCL 3.x 의 정확한 fault tolerance design — 본문에서 “검토중” 으로 적은 부분. 공식 NCCL 릴리스 노트와 NVIDIA GTC 자료 확인 필요.
ring vs tree crossover 의 정확한 사이즈 — H100 NVLink, A100 NVLink, B200 NVLink 별로 다름. nccl-tests 자체 측정 필요.
NVSHMEM 와 IBGDA 관계 — IB GPUDirect Async 의 상태. NVSHMEM 이 사용하는 transport. 강의에서 잠깐 언급됐는지 영상 재확인 필요.
PyTorch SymmetricMemory 의 실 사용 사례 — 어떤 production 코드가 이미 채택했는지. 본 노트에서는 일반화된 표현으로만.
α-β 모델의 한계 — 단순 모델은 contention, congestion, NUMA 효과를 못 잡음. 실제 측정에서는 변동 큼.
검증 메모
본문의 latency 수치 (NCCL ~10–30 µs, NVSHMEM ~1–5 µs) 는 일반적 도메인 지식 범위에서 가져온 추정치. 정확한 값은 특정 GPU 세대 + 특정 NIC 조합에서의 직접 측정 필요. 강의 영상이 다시 transcript 가능해지면 Jeff 가 인용한 정확한 숫자로 교체 권장.