안녕하세요! 지난 몇 달 동안 저희는 TorchTitan 및 PyTorch 유지보수자들과 함께 작업하며 GH200(JUPITER 슈퍼컴퓨터 내부의 GPU)을 얼마나 끌어올릴 수 있는지 알아보고 있었습니다. 간단히 말하자면, 384개의 GPU를 사용하여 약 1T 파라미터 규모의 MoE(Mixture-of-Experts) 모델을 GPU당 약 1,300 토큰/초로 학습할 수 있을 만큼 충분히 끌어올릴 수 있다는 것입니다. 이는 저희가 시작했던 지점보다 14배 높은 수치이며, 저희가 시작했던 기준점은 솔직히 말해 겸손한 수준이었습니다. 이 과정에서 몇 가지 교훈을 얻었습니다. 만약 큰 모델을 학습시키거나 단순히 CUDA out of memory 오류를 본 적이 있다면, 내용 중 일부가 익숙하게 느껴지실 겁니다. 여기 과정을 소개합니다. 모든 그래프와 함께 전체 내용을 보고 싶다면 Domyn 블로그에 올라와 있으며, 커피가 부족하다면 PyTorch 블로그에서 요약본을 볼 수 있습니다. 마음에 드셨으면 좋겠습니다!
설정: FSDP + EP, 그리고 제가 왜 PP를 선호하지 않는지
1T MoE는 하나의 GPU에 들어가지 않습니다(충격적이죠, 저도 압니다). 따라서 첫 번째 질문은 어떻게 샤딩(sharding)할 것인가입니다. 이 선택이 이후의 거의 모든 최적화를 결정하게 되므로, 이에 대해 깊이 생각하는 것이 중요합니다.
일반적으로 고려되는 방식들은 다음과 같습니다: 파이프라인 병렬 처리(Pipeline Parallelism, PP) (각 GPU가 레이어 청크를 담당), FSDP (Fully Sharded Data Parallel, 각 GPU가 모든 가중치의 일부 조각을 보유하고 필요할 때 나머지 부분을 모음), 그리고 전문가 병렬 처리(Expert Parallelism, EP) (각 GPU가 전문가의 부분집합을 소유하고 토큰이 그곳으로 이동함).
저희는 PP를 건너뛰었습니다. PP의 버블(bubbles)을 숨기려면 많은 마이크로 배치(microbatches)가 필요한데, 많은 마이크로 배치는 긴 스텝(steps)을 의미합니다. 긴 스텝은 규모에서 무언가가 고장 났을 때—그리고 규모에서는 항상 뭔가가 고장 나죠—영원히 기다려야 한다는 것을 의미합니다. 저는 빠르게 반복하는 것이 더 좋습니다. 그래서 다음과 같이 설정했습니다: 노드 내부에서 EP를 엄격하게 유지하면서, 384-way FSDP와 4-way EP를 사용했습니다. 전문가에 대한 토큰 all-to-all 통신은 숨기기 어려운 통신이므로 빠르게 일어나야 합니다. 반면에 FSDP에서의 통신은 이미 숨겨져 있습니다: 레이어 i를 계산하는 동안, 이미 i+1을 모으고 있기 때문입니다.
이것이 왜 그렇게 중요할까요? JUPITER에서는 InfiniBand가 GPU당 약 25 GB/s의 대역폭을 제공합니다. 이는 NVLink보다 6배 느리고 Grace-Hopper 링크보다 36배 느립니다. 저희 기준 스텝은 84초가 걸렸고, 그중 68초는 네트워크를 기다리는 데 사용되었습니다.
GPU들은 대부분 유휴 상태로 있게 하는 데 돈을 받고 있었습니다. 이 게시물의 나머지는 기본적으로 저희가 그들을 다시 일하게 하려는 시도입니다.
교훈 1: GPU가 유휴 상태라면, 메모리를 활용하도록 만드세요. FSDP는 가중치(weights)를 이동시키며, 레이어당 수집된 가중치는 얼마나 많은 토큰을 통과시키는지에 신경 쓰지 않습니다. 시퀀스당 더 많은 토큰은 전송선상의 바이트당 컴퓨팅 능력을 높인다는 의미이며, 이는 우리가 정확히 원했던 것입니다. 문제는 더 긴 시퀀스가 더 많은 메모리를 필요로 한다는 것이므로, 우리는 그것을 찾았습니다: 전체 활성화 체크포인팅(Full activation checkpointing). 역방향 전파(backward)에서 모든 레이어를 재계산하는 것은 낭비처럼 들리지만, GPU가 어차피 유휴 상태였다는 것을 기억하기 전까지는 그렇습니다. 무료 컴퓨팅은 공짜입니다. 이것만으로도 저희에게 4배의 성능 향상을 가져다주었습니다.
체크포인트 활성화(checkpoint activations)를 900 GB/s의 C2C 링크를 통해 Grace로 오프로드하고, 재계산하기 한 레이어 전에 미리 가져오는(prefetching) 방식을 사용했습니다. Grace가 바로 옆에 있었기 때문에 저희는 그것을 활용했습니다. Adam 대신 Muon을 사용했습니다. 매개변수당 두 개가 아닌 하나의 옵티마이저 상태를 사용했고, 이것이 마침내 64k 토큰 시퀀스까지 도달하게 했습니다.
교훈 2: 샤딩(shard)하지 않으면, 모을 필요가 없습니다. 간단히 복습하자면: Muon은 몇 번의 Newton-Schulz 반복을 통해 각 업데이트를 직교화(orthogonalizes)하며, Newton-Schulz는 전체 행렬이 필요합니다. 한편 FSDP는 모든 행렬을 384개의 슬라이스로 신중하게 잘라 작업 전반에 걸쳐 분산시켰습니다(scattered). 순진한 해결책은 각 행렬을 하나의 랭크(rank)에서 모으고(gather), Newton-Schulz를 실행한 다음 결과를 다시 분산시키는 것인데, 이는 더 많은 통신(communication)을 의미하며, 우리는 절대 필요하지 않은 것이 바로 이 통신이었습니다.
요령은 더 스마트하게 샤딩하는 것입니다. FlexShard(이미 TorchTitan에 포함됨)를 사용하면 어떤 랭크가 어떤 매개변수를 소유할지 선택할 수 있으므로, 저희는 전문가별로 전체 전문가(whole expert) 단위로 샤딩했습니다. 모든 GPU가 완전한 전문가 행렬을 소유하고 통신 없이 로컬에서 이를 직교화할 수 있습니다. 밀집 행렬(dense matrices) (어텐션 투영 및 관련 요소들)은 여전히 모을 필요가 있지만, 각 랭크가 자체 전문가를 처리하느라 바쁜 동안 전송 중이므로 트래픽이 컴퓨팅 능력 뒤에 숨겨지게 됩니다. 한 가지 걸림돌이 있었습니다. 저희는 옵티마이저를 Grace로 오프로드했고, 이는 Newton-Schulz가 CPU에서도 실행됨을 의미했습니다.
Grace는 멋진 CPU이지만, 매트멀(matmuls)을 처리하도록 요청하는 것은 사서에게 가구를 옮기라고 하는 것과 같습니다: 기술적으로는 가능하지만, 매우 비효율적입니다. 최적화 단계에 42초가 걸렸습니다. 그래서 우리는 파라미터가 호스트(host)에 있더라도 GPU에서 Newton-Schulz를 실행하는 TorchTitan의 수정 사항을 기여했습니다. 최적화 단계는 약 2초로 떨어졌고, 처리량은 35% 증가했습니다.
레슨 3: 당신의 속도는 가장 느린 랭크(rank)에 의해 결정됩니다. 384 GPU를 잠금 단계(lockstep)로 사용하는 것은 그룹 하이킹과 약간 같습니다. 모두가 뒤처지는 사람을 기다립니다. 우리는 두 가지 지연 요인(stragglers)을 발견했습니다: 어텐션 비용(Attention cost). 단일 60k 토큰 문서를 받는 랭크는 같은 수의 토큰을 보더라도, 백 개의 짧은 문서를 받는 랭크보다 훨씬 많은 어텐션 작업을 수행합니다. 우리는 행(rows)을 미리 패킹하고, 어텐션 비용별로 버킷팅(bucket)하여 각 단계를 유사한 비용의 행들로 구성합니다. 가장 나쁜 랭크는 평균 대비 7.9배에서 1.15배로 줄어들어 처리량이 +13% 증가했습니다.
라우터(The router). 그룹 제한 라우팅(Group-limited routing) (각 노드 그룹에서 두 명의 전문가 선택)은 전문가 부하를 완벽하게 균등하게 만들어 처리량을 +7% 증가시킵니다. 문제는 라우터에게 토큰을 어디로 보낼 수 있는지 알려준다는 것인데, 초기 실행 결과에 따르면 이것이 훈련에 방해가 될 수 있습니다. 이 부분은 아직 의견이 분분합니다.
레슨 4: fp32로 누적하고, bf16으로 전송합니다. 그래디언트(Gradients)는 역전파(backward) 과정에서 bf16으로 나옵니다. 하지만 수백 개의 그래디언트를 bf16으로 합산하는 것은 정밀도를 서서히 잃어버리는 좋은 방법이므로, 표준 FSDP 혼합 정밀도(mixed-precision) 설정은 fp32로 축소합니다. 이것이 올바른 결정입니다. 문제는 그곳에 도달하는 방식입니다. NCCL의 reduce-scatter는 링(ring) 형태로 실행됩니다. 각 랭크는 이웃으로부터 부분 합계(partial sum)를 받고, 자체 기여분을 더한 다음, 결과를 전달합니다. 링을 따라 이동하는 것은 누적 합계이므로, 축적이 fp32로 일어나려면 전송되는 데이터도 fp32여야 합니다. 따라서 FSDP는 집합 연산(collective) 이전에 bf16 그래디언트를 fp32로 업캐스트(upcasts)하며, 모든 바이트의 그래디언트가 필요한 크기의 두 배로 InfiniBand를 통과합니다. 이미 InfiniBand 자체가 병목 현상인 클러스터에서는 이것이 손해입니다.
하지만 합산(summation)에만 fp32가 필요할 뿐 전송 과정 전체에 필요한 것은 아닙니다. 그래서 저희는 전문가 기울기(expert gradients)에 대해 리듀스-스캐터(reduce-scatter)를 올-투-올(all-to-all) 방식으로 변경했습니다. 각 랭크는 자신이 소유한 원본 bf16 슬라이스를 다른 모든 랭크에게 전송하며, 부분 합산은 관여하지 않습니다. 수신자는 모든 슬라이스를 받고, 이를 업캐스트(upcast)하여 로컬에서 fp32로 합산합니다. 수학적 계산과 중요도가 높은 정밀도는 동일하지만, 통신선상의 바이트는 절반으로 줄어듭니다. 그 결과는 합산 순서만 다를 뿐 fp32 리듀스-스캐터와 일치합니다. 교훈 5: 의심스러울 때는 더 적은 바이트를 전송하세요.
결국, 더 긴 시퀀스는 도움이 되지 않았습니다(이유는 잠시 후에 설명). 그래서 저희는 통신 볼륨 자체에 집중했습니다: 블록 단위 스케일링을 적용한 FP8 전문가 올-개더(expert all-gather) 방식입니다. 마스터 가중치(Master weights)는 전체 정밀도(full precision)를 유지하고, 전송 과정만 압축합니다: +18% 절감. 순방향(forward) 이후에 밀집 가중치(dense weights)를 노드 로컬로 재분할(resharding)하여, 역방향(backward) 시의 리-개더가 인피니밴드(InfiniBand)에서 기울기들과 경쟁하는 대신 NVLink를 통해 실행되도록 했습니다. 그리고 이 모든 과정은 비교 테스트에서 손실값(loss)을 움직이지 않았는데, 이는 안도감을 주었습니다. 교훈 6: OOM 오류는 당신에게 거짓말을 할 수 있습니다.
이것이 제가 가장 좋아하는 부분입니다. 소프트웨어 업그레이드 후, 모든 랭크가 약 70% HBM 사용률에서 할당자 재시도(allocator retries)에 도달하기 시작했는데, 이때 여유 공간으로 캐싱된 메모리가 25~30 GiB나 남아 있었습니다. 이것은 메모리 부족이 아니라, 일종의 '메모리 인질극'이었습니다. 원인은 PyTorch 캐싱 할당자(PyTorch caching allocator)가 CUDA 스트림마다 별도의 풀을 유지하고 있기 때문이며, FSDP는 자체 스트림에 버퍼를 할당합니다. 저희가 사용한 멋진 FP8 및 bf16 콜렉티브 연산도 여기에 임시 메모리를 할당했기 때문에, 약 21 GiB가 컴퓨트 스트림(compute stream)이 접근할 수 없는 별도의 스트림에 주차되어 있었습니다. 해결책은 이러한 할당을 컴퓨트 스트림으로 이동시키는 것(FSDP2의 private unshard_async_op 플래그를 이용, 현재 공개 API가 될 예정임)과 기울기를 조금 더 일찍 해제하는 것이었습니다. 재시도는 384개 랭크에서 0으로 줄었고, 최대 예약 메모리는 13.5 GiB 감소했으며, 전송하는 바이트는 정확히 동일한 상태를 유지하면서 처리량(throughput)은 21% 증가했습니다.
결론적으로, 공유되고 공공 자금으로 지원되는 Hopper 세대 머신은 일반 사용자 레벨 접근 권한과 root 권한 없이도 최첨단 규모(frontier-scale)의 훈련을 호스팅할 수 있습니다. 사용자는 단지 두 가지 실제 한계점인 노드 간 대역폭(inter-node bandwidth)과 호스트 메모리(host memory)를 존중하고 모든 것을 이를 중심으로 설계해야 합니다. 우리가 발견한 모든 수정 사항은 PyTorch와 TorchTitan에 상위 스트림(upstream)으로 전달되었으므로, 다음 사용자는 우리가 겪었던 것보다 적은 벽에 부딪히기를 바랍니다. 모델과 훈련 레시피는 공개적으로 배포될 예정입니다. 아직 할 일 목록에는 그룹 제한 라우팅(group-limited routing)의 장기적인 품질 비용과 FP8 전문가 GEMMs가 남아 있습니다. 저희가 다시 보고드리겠습니다. 리뷰, 인내심, 그리고 빠른 병합(merges)을 제공해 준 JSC와 PyTorch 및 TorchTitan 유지 관리자들에게 진심으로 감사드립니다! 제출: /u/f14-bertolotti [링크] [댓글]