soobook
AI

Parallelism Strategies

모델이 GPU 하나에 안 들어가면 쪼개야 하는데, 어느 방향으로 자를까?

Why Split

모델이 크면 GPU 하나에 다 안들어간다.

GPU 하나에 안들어간다는 말이 정확히 뭐냐면, 어느 한 순간에 HBM 위에 동시에 올라와 있어야 하는 바이트가 HBM 용량보다 크다는 뜻이다.

“동시에”, “한 순간”, “바이트” 를 기억하자.

학습 중에는 파라미터 1개당 약 16바이트가 필요하다.

항목정밀도크기
weightBF162B
gradientBF162B
master weightFP324B
Adam momentumFP324B
Adam varianceFP324B

BF 는 Brain Float, FP 는 Floating Point 의 약자다. 부동소수점 형식 이름이고, 뒤에 숫자가 비트 수.

Adam momentum 등에 대해서는 다루지 않음!

70B 모델이면 이것만 1.12TB다. H100 80GB로는 14장이 있어야 계산을 시작이라도 할 수 있다.

1T 파라미터면 16TB다. GB300 한 장의 HBM이 288GB이므로 최소 56장에 나눠 담아야 한다.

추론과 결정적으로 갈리는 지점이다. 추론은 weight만 있으면 되니 1T 모델이 2TB지만 학습은 같은 모델에 8배를 쓴다.

메모리 압력은 모델 하나를 여러 GPU에 나눠 담는 문제를 만들고, 처리량 압력은 같은 모델을 여러 벌 두고 데이터를 나누는 문제를 만든다. 앞의 것이 TP, EP, PP이고 뒤의 것이 DP다.

Four Axes

예로부터 이런 경우에는 분산 처리가 답이다. 어떻게 분산할까?

Transformer block 구조 안에 이미 힌트가 있다.

block 하나는 Attention과 FFN으로 되어 있고, 그 block이 N개 쌓여 있고, 그 전체에 batch가 들어간다.

batch는 이 구조를 통과하는 데이터의 개수를 말한다.

  • batch 방향으로 자르면 DP(data parallelism)
  • 쌓인 방향, 즉 깊이로 자르면 PP(pipeline parallelism)
  • block 하나 내부의 weight 행렬을 폭 방향으로 자르면 TP(tensor parallelism)
  • FFN 자리가 MoE라면 expert 단위로 자르면 EP(expert parallelism)

같은 transformer stack을 네 방향으로 자른 그림. DP는 batch를 나눠 모델 복제본 둘에 태우고 gradient 통신이 backward 동안 흐르지만, TP는 block 안의 weight 행렬을 갈라 block마다 activation all-reduce가 두 번 돌고, EP는 MoE의 expert만 흩어 attention은 그대로 두며, PP는 깊이를 stage로 잘라 경계에서만 activation을 넘긴다

앞의 DP, PP, TP는 서로 직교해서 그대로 곱해진다.

 

world size=dp×pp×tp\text{world size} = dp \times pp \times tp

 

World size 는 분산학습 Job에 참여하는 전체 프로세스 개수이다.

GPU 512장을 tp=8, pp=8, dp=8로 쓴다는 건 각 GPU가 어느 복제본의, 어느 stage의, 어느 조각이냐는 세 좌표를 하나씩 갖는다는 뜻이다. 이 좌표계를 mesh라고 부른다.

EP는 여기에 곱해지지 않는다. 새 GPU를 더 쓰는 게 아니라 이미 있는 GPU들을 FFN 구간에서만 다시 나눠 쓰는 방식이기 때문이다.

DP: Data Parallelism

모델 전체를 여러 벌 복제하고 batch를 나눠 각 복제본이 자기 몫을 처리한다.

Software Engineering 에서도 자주 등장하던 방식이다!

backward가 끝나면 gradient를 모아 평균 내고, 모든 복제본이 같은 값으로 weight를 갱신한다.

이전 작업이 끝나면 gradient(기울기)라는 것을 모아서 정산하고, all-reduce를 수행해서 모든 복제본이 동일한 값으로 갱신한다.


깨알상식

학습은 forward -> loss -> backward -> optimizer step 이라는 단계를 거친다.

  • forward: 입력을 block N개에 차례로 통과시켜서 예측을 뽑는다
  • loss: 예측과 정답의 차이를 숫자 하나로 잰다
  • backward: 그 차이를 거꾸로 흘려서 weight마다 gradient를 만든다
  • optimizer step: gradient를 보고 weight를 고친다.

DP가 늘리는 건 처리량이다. step당 소화하는 토큰 수가 복제본 수만큼 늘어난다.

복제본 수 = 병렬로 처리중인 수, “데이터”를 병렬로 처리한다는 점에 집중하자.

대신 메모리 문제는 하나도 풀어주지 않는다. 모델이 이미 GPU에 들어간다는 전제 위에서만 성립한다.

통신은 step마다 한 차례다. gradient가 다 합쳐져 있어야 하는 시점은 optimizer 직전 한 번이고, 실제 all-reduce 호출은 bucket 단위로 backward가 도는 동안 여러 번 나간다.

통신량은 파라미터 수에 비례하고, batch 크기나 sequence 길이와는 무관하다.

위에서 본 forward->loss->backward->optimizer step 이 하나의 step이다.

TP: Tensor Parallelism

TP는 행렬 곱 하나를 여러 GPU가 나눠 계산한다. block을 통째로 옮기는 게 아니라 block 안의 weight 행렬을 갈라 각자 일부만 갖는다.

FFN에서 어떻게 자르는지부터 보자. FFN은 펼쳤다가 걸러내고 다시 압축하는 구조다.

  • up projection(4096 → 14336)을 열 방향으로 N등분한다. GPU 하나가 14336칸 중 자기 몫만 계산한다.
  • 활성화 함수는 칸마다 독립적으로 적용되므로 통신 없이 그냥 통과한다.
  • down projection(14336 → 4096)을 행 방향으로 N등분한다. 각 GPU가 자기 조각으로 4096칸짜리 부분합을 만든다.
  • 부분합을 all-reduce로 더하면 정답이 나온다.

projection은 선형변환, 쉽게 말해서 행렬 하나를 곱하는 연산이다. up-proj는 차원을 올리는거고 down-proj는 차원을 내린다.

자르는 방향을 열, 행 순서로 고른 덕분에 중간에 주고받을 게 없고 끝에서 딱 한 번만 통신하면 된다. 순서를 뒤집으면 중간에도 통신이 생긴다.

FFN을 TP로 자를 때 up projection은 열 방향으로, down projection은 행 방향으로 나눈다. GPU 두 장이 각자 자기 열만 계산하고 활성화 함수는 칸마다 독립이라 통신 없이 통과하며, down projection에서 나온 부분합을 마지막에 all-reduce 한 번으로 더한다

Attention은 더 쉽다. head끼리 이미 독립이라 head를 GPU에 나눠 주면 각자 자기 head의 Q, K, V를 만들고 attention을 끝까지 계산한다. output projection에서 합쳐야 하므로 여기서도 all-reduce 1회다.

FFN과 Attention을 합치면 block 하나당 forward에서 all-reduce 2회, backward에서 2회다.

TP는 메모리와 계산을 동시에 나눈다. weight가 N등분되고 행렬 곱도 N등분된다. block 하나가 GPU 하나보다 클 때 쓸 수 있는 유일한 축이기도 하다.

문제는 통신이다. 80-block 모델이면 forward만 160회의 all-reduce가 돈다. 게다가 오가는 데이터가 activation이라 batch와 sequence 길이에 비례해 커진다.

EP: Expert Parallelism

MoE에서는 FFN 자리가 expert 여러 벌로 복제되어 있고, router가 토큰마다 그중 소수만 고른다. DeepSeek-V3라면 routed expert 256개 중 토큰당 8개다.

EP는 이 expert들을 GPU에 나눠 배치한다. expert 256개를 GPU 64장에 흩으면 장당 4개다.

문제는 토큰이 자기가 갈 expert와 같은 GPU에 있으리라는 보장이 전혀 없다는 것이다. 목적지는 router가 그때그때 정한다. 토큰을 목적지 GPU로 보내고(dispatch), 거기서 expert 계산을 하고, 결과를 원래 자리로 돌려받는다(combine). 이 두 번이 all-to-all이다.

MoE의 파라미터는 대부분 expert에 있고, EP는 그 부분을 GPU 수만큼 갈라 담아 메모리 부담을 던다. 계산량은 어차피 활성 expert 몫만 도니까 늘지 않는다.

all-to-all은 all-reduce보다 까다롭다.

우선 목적지가 런타임에 정해진다. router 출력을 보기 전에는 누가 누구에게 얼마나 보낼지 몰라서 미리 schedule을 짜둘 수 없다.

부하도 고르지 않다. 인기 있는 expert에 토큰이 몰리면 그 GPU가 병목이 되고 나머지는 기다린다.

무엇보다 모든 GPU 쌍 사이에 실제 트래픽이 생긴다. all-reduce는 ring으로 돌리면 이웃끼리만 주고받는 형태로 바꿀 수 있지만 all-to-all은 그런게 안 된다.

PP: Pipeline Parallelism

block 1~20은 GPU A, 21~40은 GPU B. 모델을 깊이 방향으로 잘라 stage로 나눈다.

통신은 stage 경계에서 activation tensor 하나를 다음 stage로 넘기는 것뿐이다. 80 block을 4 stage로 자르면 경계는 3곳이다. collective가 아니라 P2P 전송이라 대역폭 요구 자체가 낮다.

각 GPU는 자기 stage의 weight만 들고 있으면 되니 메모리가 그만큼 준다.

대신 bubble이 생긴다. stage 1이 계산하는 동안 stage 2, 3, 4는 할 일이 없다. 그대로 두면 GPU의 1/4만 일한다.

해법은 batch를 microbatch로 잘게 쪼개 pipeline에 연속으로 흘리는 것이다. stage 1이 두 번째 microbatch를 잡을 때 stage 2는 첫 번째를 처리한다. bubble 비율은 대략 이렇게 줄어든다.

 

bubbleP1M+P1\text{bubble} \approx \frac{P-1}{M+P-1}

 

PP는 stage 수, MM은 microbatch 수다. stage 4개에 microbatch 32개면 bubble은 8% 남짓이다.

Communication Patterns

step 하나는 weight를 한 번 갱신하는 단위다. 그 안에서 microbatch가 차례로 흐르고, microbatch 하나는 block을 forward로 끝까지 지나갔다가 backward로 되짚어 온다.

step 하나 안에서 microbatch 32개가 차례로 흐르고, microbatch 하나는 block 1번부터 80번까지 forward로 지나갔다가 backward로 되짚어 온다. TP all-reduce는 그 block마다 두 번씩 돌아 step당 1만 번을 넘는 반면, DP는 gradient를 다 맞춰야 하는 시점이 step 끝 한 곳뿐이고 그 통신은 bucket으로 쪼개져 backward와 겹친다

TP가 block마다 통신한다는 건 이 반복에 들어간다는 뜻이다.

DP는 gradient를 다 맞춰 놔야 하는 시점이 step 끝에 한 번뿐이다. 통신 자체는 그 자리에 몰리지 않고 bucket으로 쪼개져 backward 내내 흩어진다.

반복 안에 있느냐 밖에 있느냐, 클라스가 다르다.

방식분산 대상통신 연산오가는 데이터빈도overlap
TPblock 내부 weight 행렬all-reduceactivationblock마다 2회거의 불가
EPMoE expertall-to-alltokenMoE block마다 2회어려움
PPblock 구간P2Pactivationstage 경계 × microbatch가능
DP복제본 batchall-reducegradientstep당 bucket 수만큼가능

TP/EP는 모델 하나를 “안에서” 쪼갠 것이고, PP/DP는 구간이나 복제본 “사이”를 가른 것이다.

안을 쪼개면 통신이 계산 한복판에서 결과를 기다리게 만들고, 사이를 가르면 통신이 계산과 나란히 흐를 수 있다.

이건 꽤 중요한데, 특히 TP와 EP는 안에서 쪼갠다는 것을 기억하자.

Why TP and EP Stay in One Rack

GB300 NVL72 기준으로 rack 안 NVLink는 GPU당 1.8TB/s, rack 사이 InfiniBand는 GPU당 수십 GB/s다. 수십 배 차이가 난다.

이 격차 앞에서 네 방식이 갈린다.

TP부터 세어 보자. 80-block 모델에 microbatch가 32개면 all-reduce가 step당 1만 번을 넘는다.

DP도 호출 수만 보면 적지 않다. gradient를 bucket으로 쪼개 backward가 도는 동안 내보내므로 호출은 bucket 수만큼 나가고, 각 bucket 통신도 그 자체로는 collective라 GPU들이 맞춰야 한다. 호출 횟수로는 두 축이 갈리지 않는다.

옮기는 양도 단위가 다르다. TP는 한 번에 activation 한 장이라 batch와 sequence에 비례하고, DP가 step 내내 보내는 총량은 gradient 전체라 파라미터 수에 비례한다.

그래서 호출 수로도 바이트로도 나란히 놓기 어렵다. 실제로 갈리는 축은 마감이 어디 놓이느냐다. TP의 all-reduce는 전부 계산 한복판에 마감이 있다. 부분합이 안 모이면 다음 행렬 곱을 못 하므로, 링크가 느리면 그 지연이 step 시간에 그대로 더해진다. 1만 번이라는 건 그렇게 멈춰 서는 지점이 1만 개라는 뜻이다.

DP의 bucket 통신에는 그런 중간 마감이 없다. 유일한 마감은 step 끝이고, 그때까지 backward 계산 뒤에서 흐르기만 하면 된다. PP의 P2P도 microbatch 사이에서 같은 여유를 갖는다.

물론 공짜는 아니다. 겹칠 계산이 남아 있어야 가려지고, gradient accumulation을 길게 쌓으면 통신이 마지막 microbatch 쪽에 몰려 가릴 구간이 오히려 줄어든다.

통신 패턴의 모양도 다르다. EP의 all-to-all은 참여 GPU 전부가 서로에게 동시에 보낸다. rack 안 NVSwitch는 full crossbar라 이 요구를 정면으로 받아내지만, rack 사이 fabric은 상위 단으로 갈수록 얇아지는 경우가 흔하다. all-to-all이 rack 경계를 넘는 순간 가장 얇은 구간이 전체 속도를 정한다.

원칙 한 줄로 압축된다.

 

절단면은 DP와 PP 경계부터 쓴다. TP와 EP를 넘기는  마지막이다.\textbf{\text{절단면은 DP와 PP 경계부터 쓴다. TP와 EP를 넘기는 건 마지막이다.}}

 

같은 144 GPU 요청이라도 절단면을 어디에 두느냐로 학습 속도가 갈린다. 위쪽은 scheduler가 GPU 개수만 보고 채워 인스턴스 하나가 rack 경계에 8과 64로 찢어진 경우이고, 아래쪽은 DP 경계에서 잘라 rack 하나가 인스턴스 하나를 통째로 담은 경우다

rack 하나에 통째로 들어가야 하는 최소 단위는 모델 인스턴스가 아니라 (dp, pp) 좌표 하나가 가리키는 TP×EP 그룹이다. 인스턴스가 rack보다 커도 된다. PP로 rack 여러 개에 걸치는 게 오히려 대형 모델의 표준 배치다.

반대로 scheduler가 이 구조를 모르고 GPU를 개수로만 채우면 인스턴스 하나가 rack 경계에 걸쳐 찢어진다. 그러면 block마다 도는 TP all-reduce가 InfiniBand를 타고, 전체 학습이 가장 느린 링크 속도로 끌려간다. 같은 설정인데 어제는 빨랐다가 오늘은 느려지는 상황이 여기서 나온다.

흔한 함정은 EP 쪽이다. 프레임워크가 rank를 순서대로만 배정하면 EP 그룹이 조용히 rack을 넘어간다. 아무도 에러를 내지 않고 학습은 그냥 느려진다. rank 배정을 NVLink 도메인 기준으로 맞추는 게 대응이다.

다만 EP는 넘고 싶어서가 아니라 넘을 수밖에 없어서 넘는 경우가 있다. expert를 다 합친 크기가 도메인 하나에 안 들어가면 EP 그룹은 도메인 밖으로 나간다. 그때부터 원칙은 넘지 말라는 금지가 아니라, 넘은 값을 설계로 갚으라는 청구서가 된다.

Summary

 

안에서 자르면 도메인 안, 사이에서 자르면 도메인 \textbf{\text{안에서 자르면 도메인 안, 사이에서 자르면 도메인 밖}}

 

TP와 EP는 block 하나를 계산하는 도중에 통신이 끼어든다. 부분합을 모으거나 토큰을 흩는 동안 계산이 멈추므로, 링크 속도가 그대로 학습 속도가 된다. 그래서 되도록 NVLink 도메인 안에 가둔다.

PP와 DP는 stage와 복제본 사이에서만 통신하고, 그 통신은 쪼개서 계산과 겹칠 수 있다. 겹칠 계산이 남아 있는 한 도메인을 넘어도 된다.

EP만은 예외가 있다. expert가 도메인에 안 들어가면 넘을 수밖에 없다. DeepSeek-V3가 그랬고, 대신 routing 제한과 통신 overlap을 직접 만들어 그 값을 갚았다.

네 글자를 외우는 것보다, 자른 면에 통신이 얼마나 자주 그리고 얼마나 급하게 흐르는지를 보는 편이 낫다. 어느 rack에 무엇을 담을지는 거기서 정해진다.

References