LLM은 GPU 위에서 어떻게 동작할까 | How LLMs Run on GPUs (2)
🔗 LLM 분산 학습을 위한 GPU 병렬화 전략과 통신 방식을 정리합니다.
KEYWORDS
분산 학습, LLM 병렬화, ZeRO 뜻, FSDP, Tensor Parallelism, Pipeline Parallelism, Sequence Parallelism, NVLink, Collective Communication, All-reduce, ND Parallelism, LLaMA 3 학습 구조
Introduction
- (1)편의 결론은 명확합니다. 70B 모델을 Single GPU로 파인튜닝하는 것은 사실상 불가능하고, 추론도 Long Context와 High Concurrency 상황에서는 한 장의 GPU로 감당하기 어렵습니다.
- 그래서 LLM 학습과 서빙은 여러 GPU를 묶어 함께 쓰는 것이 표준이 됩니다.
- 본 글은 Multi-GPU 통신부터 시작해 Collective Communication, Data/Model/Activation Parallelism, ND Parallelism 조합 전략까지 다루겠습니다.
Single GPU의 한계
- (1)편의 핵심 표를 다시 떠올려 봅시다.
- LLaMA 70B 추론 FP16: 156 GB → 80 GB GPU 2장 필요
- LLaMA 70B 파인튜닝 FP16: 554 GB → 80 GB GPU 7장 필요
이건 단순히 모델이 클수록 GPU를 더 많이 써야 한다는 의미를 넘어, 단일 GPU의 메모리 용량 자체가 절대 부족하다는 뜻입니다.
- 게다가 메모리뿐 아니라 연산량(FLOPs) 도 문제입니다.
- 학습 한 번에 수십, 수백 ExaFLOPs를 처리해야 하는 모델은, 한 GPU의 Throughput으로는 몇 년이 걸립니다.
그래서 LLM 학습과 서빙의 무대는 자연스럽게 Datacenter 단위로 바뀝니다. 우리는 GPU 한 장이 아니라, 수십에서 수천 장에 이르는 GPU 클러스터를 다루게 됩니다.
- 이때 자연스럽게 떠오르는 질문은 두 가지입니다.
- GPU들끼리는 어떻게 통신하는가?
- 모델, 데이터, 연산을 GPU들 사이에 어떻게 나눠 줘야 효율적인가?
- 1번이 Interconnect와 Collective Communication이고, 2번이 본 글의 주제인 병렬화 전략입니다.
Multi-GPU 통신 Interconnect
- GPU 사이를 데이터가 오갈 때, 그 데이터가 지나가는 길이 무엇이냐가 통신 비용을 결정합니다.
- 이 길의 종류를 알면 왜 어떤 병렬화는 한 노드 안에서만 권장되고, 어떤 병렬화는 노드 간에 어울리는지가 자연스럽게 이해됩니다.
Interconnect 종류
- PCIe (PCI Express) ㅣ 가장 일반적인 GPU-CPU/메모리 연결 통로
- x16 Lanes 기준 양방향 약 242 GB/s 대역폭을 제공합니다.
- 데이터센터 인터커넥트로서는 상대적으로 느린 편입니다.
- Ethernet ㅣ 노드 간 일반 네트워크
- 데이터센터에서도 사용되지만, 대역폭이 NVLink와 NVSwitch 대비 훨씬 낮습니다.
- 대규모 분산 학습에서는 GPU 간 통신 경로로 잘 쓰이지 않습니다.
- NVLink ㅣ NVIDIA가 만든 GPU 전용 고속 통신 링크
- GPU 끼리 CPU를 거치지 않고 직접 통신합니다.
- 한 노드 안 (Intra-node) GPU 간에 사용됩니다.
- 예를 들어 H100의 경우 18개의 NVLink를 합쳐서 약 900 GB/s 대역폭을 제공합니다.
- NVSwitch ㅣ NVLink를 노드 간으로 확장한 스위치
- 여러 노드의 GPU들을 직접 연결해서 Ethernet 경로를 우회합니다.
- 노드 간 (Inter-node) 고속 통신이 가능합니다.
GPU 간 통신 계층: PCIe → NVLink (Intra-node) → NVSwitch (Inter-node)
통신 계층의 큰 그림
- GPU 위에 도는 데이터의 통신 비용은 작고 빠를수록 가깝고, 크고 느릴수록 멉니다.
| 계층 | 위치 | 속도 (대략) |
|---|---|---|
| L1 Cache / Shared Memory | SM 내부 | 가장 빠름 (TB/s) |
| HBM (Global Memory) | GPU 칩 옆 | ~3 TB/s (H100) |
| NVLink | 노드 내 GPU 간 | ~900 GB/s |
| NVSwitch | 노드 간 GPU 간 | 수백 GB/s |
| Ethernet | 노드 간 일반 망 | 상대적으로 매우 느림 |
- 모든 분산 학습 설계의 기본 원리: 빠른 인터커넥트(NVLink) 안에서 잦은 통신을 하고, 느린 인터커넥트(Ethernet)에서는 통신을 최소화한다.
Collective Communication 기초
- 인터커넥트가 도로라면, Collective Communication은 그 위에서 자주 쓰는 표준 운송 패턴입니다.
- GPU들이 모여 데이터를 주고받을 때 자주 쓰는 정형 패턴을 뜻합니다.
- 모든 분산 학습 알고리즘이 이 패턴들의 조합으로 표현되기 때문에, 먼저 패턴 자체를 알아두면 뒤가 훨씬 쉽습니다.
용어 정의
- Rank ㅣ 클러스터 내 각 GPU(혹은 Worker)의 ID. 0, 1, 2, …
- World Size ㅣ 전체 Rank의 수 (= 전체 GPU 수)
- 이 절의 예시는 모두 World Size = 4, Rank 0~3 기준입니다.
먼저, 기본 요소 4가지 (한 줄씩)
- Broadcast ㅣ Root Rank 한 곳의 데이터를 모든 Rank에 똑같이 복사합니다.
- Scatter ㅣ Root의 큰 데이터를 잘라서 각 Rank에 나눠줍니다.
- Gather ㅣ 각 Rank의 데이터를 Root 한 곳에 모읍니다 (Scatter의 반대).
Reduce ㅣ 각 Rank의 데이터에 연산(sum, min, max 등)을 적용해 Root 한 곳에 모읍니다.
- 위 네 가지가 뒤이어 등장하는 병렬화 알고리즘의 원자적 요소입니다. 이후 상세히 설명하는 세 패턴은 이 요소들의 조합과 확장이라고 보면 됩니다.
자주 쓰이는 3가지 패턴 (상세)
- All-reduce ㅣ Reduce 한 결과를 다시 모든 Rank가 가짐 (Reduce 후 Broadcast에 해당)
- 각 Rank가 자기 데이터에 대해 Reduce 연산(예: 덧셈)을 함께 수행한 뒤, 그 최종 결과를 모든 Rank의 Receive Buffer에 동일하게 배포합니다.
- Data Parallel에서 각 GPU가 계산한 Gradient를 하나로 합쳐 모두가 갖게 하는 데 사용됩니다.
- All-gather ㅣ 각 Rank의 데이터를 모아서 모두가 전체를 가짐 (Gather 후 Broadcast에 해당)
- 각 Rank가 자기 몫의 데이터를 유지한 상태에서, 다른 Rank들의 데이터도 전부 모아 모두가 같은 전체 버퍼를 가지게 됩니다.
- ZeRO-3에서 파라미터 분할을 다시 합칠 때 사용됩니다.
- Reduce-scatter ㅣ Reduce를 하되, 결과를 한 곳에 모으지 않고 Rank별로 동일 크기 블록으로 흩어 보냄 (Reduce + Scatter의 결합)
- 각 Rank는 최종 Reduce 결과의 자기 몫만 받게 되며, 어떤 Rank도 전체 결과를 갖지 않습니다.
- ZeRO 시리즈에서 Gradient를 GPU별로 분할할 때 핵심 역할을 합니다.
Collective Communication 기본 패턴: Broadcast / Scatter / Gather / Reduce / All-reduce / All-gather / Reduce-scatter 1
All-reduce의 위상
- 위 패턴 중 All-reduce가 압도적으로 자주 등장합니다. 이유는 Gradient를 모든 GPU가 똑같이 가져야 모델 파라미터가 동기화되기 때문입니다.
- 다음 절에서 All-reduce가 어떻게 더 싸게 분해될 수 있는지 봅니다.
All-reduce = Reduce-scatter + All-gather
- All-reduce는 분산 학습에서 가장 중요하기 때문에, 통신 비용을 정확히 따져야 합니다.
- All-reduce는 Reduce-scatter + All-gather로 분해할 수 있고, 이 분해가 단순하게 짠 All-reduce보다 P배 더 쌉니다.
All-reduce의 비용
- 각 GPU가 자기 데이터를 모든 다른 GPU에 보내고, 받아서 합친다고 할때,
변수 약속 ㅣ \(D\) = 데이터 크기 (Bytes), \(P\) = GPU 수
- 한 GPU 입장에서 Send와 Receive를 합쳐 \(2 \times D \times (P-1) \approx 2DP\) 만큼의 통신 비용이 발생합니다.
- \(P\) 가 크면 통신이 GPU 수에 비례해 폭증합니다.
분해의 트릭
같은 All-reduce를 다음과 같이 두 단계로 나누면 비용을 크게 낮출 수 있습니다.
- Step 1. Reduce-scatter
- 전체 데이터 \(D\) 를 \(P\) 개의 조각으로 나눕니다. 각 조각의 크기는 \(D/P\) 입니다.
- \(P-1\) 번의 링(ring) 스텝을 돌면서, 각 GPU는 매 스텝마다 자기 조각(\(D/P\))씩만 이웃 GPU로 보냅니다.
- 한 GPU가 총 보내는 양은 \((P-1) \times D/P\) 입니다.
- 종료 시점에는 각 GPU가 최종 Reduce 결과의 자기 몫 조각만 갖게 됩니다.
- Step 2. All-gather
- 이번에는 각 GPU가 방금 얻은 자기 조각을 다른 GPU들에게 순차적으로 전달합니다.
- 마찬가지로 \(P-1\) 번의 링 스텝을 돌며, 매 스텝마다 \(D/P\) 크기의 조각을 하나씩 보냅니다.
- 한 GPU가 총 보내는 양은 \((P-1) \times D/P\) 입니다.
- 종료 시점에는 모든 GPU가 전체 Reduce 결과(\(D\))를 갖게 됩니다.
- 두 스텝의 통신량을 합치면 한 GPU당 \(2 \times D \times (P-1)/P \approx 2D\). \(P\) 가 사실상 사라지고, GPU 수와 무관하게 상수적 통신량으로 줄었습니다.
All-reduce 분해 방식: Reduce-scatter 후 All-gather. 비용 \(2DP \to 2D\)
- 다시 말해 대역폭 제한 환경에서는 이게 이론적 최선입니다 1.
- 이 분해 방식이 이후의 ZeRO에 대한 핵심 아이디어가 됩니다.
- 메모리를 절감하면서도 통신 비용이 그대로 유지되는 이유입니다.
LLM 병렬화: 3대 축
이제 본격적인 병렬화 전략으로 들어갑니다. LLM 학습을 GPU에 나누는 방법은 크게 세 갈래로 분류됩니다 1:
- (1) Data Parallelism ㅣ 데이터(배치)를 나눔. 각 GPU는 모델 전체를 가지고 서로 다른 Batch Slice를 처리.
- (2) Model Parallelism ㅣ 모델 파라미터를 나눔. 두 종류:
- Pipeline Parallel: 모델을 깊이(Depth) 방향으로 잘라 GPU에 분배
- Tensor Parallel: 한 레이어를 너비(Width) 방향으로 잘라 GPU에 분배
- (3) Activation Parallelism ㅣ Activation(중간 계산값)을 나눔. Sequence Parallel이 대표 사례입니다.
| 축 | 무엇을 나누나 | 대표 기법 | 통신 패턴 |
|---|---|---|---|
| Data | Batch 데이터 | DDP, ZeRO 1/2/3 (FSDP) | All-reduce / Reduce-scatter + All-gather |
| Model (Depth) | 레이어 | Pipeline Parallel (GPipe, 1F1B) | P2P (Point-to-point) |
| Model (Width) | 한 레이어 내부 행렬 | Tensor Parallel (Megatron) | All-reduce per Layer |
| Activation | seq 차원 활성값 | Sequence Parallel | All-gather / Reduce-scatter |
- 실제 대형 모델 학습은 이 셋을 조합(ND parallelism) 해서 씁니다. 먼저 각각 따로 본 다음 마지막에 합치는 흐름으로 갑니다.
Data Parallelism (Naive DDP)
- 가장 단순하면서 가장 오래된 병렬화. 각 GPU가 모델 전체의 복사본을 가지고, 다른 데이터 슬라이스를 처리합니다.
Naive DDP의 동작
- 절차 (PyTorch의 DDP(Distributed Data Parallel)가 정확히 이 방식 1):
- Batch \(B\) 를 \(M\) 개의 GPU에 나눕니다. 각 GPU는 \(B/M\) 개의 샘플을 담당합니다.
- 각 GPU는 로컬 Forward/Backward로 자기 Gradient를 계산합니다.
- 모든 GPU 간 All-reduce로 Gradient를 동기화합니다.
- 모두 같은 Gradient로 같은 Optimizer Step을 수행하며, 그 결과 파라미터가 모든 GPU에서 동일하게 유지됩니다.
- 평가
- Compute Scaling: 각 GPU가 \(B/M\) 샘플 처리.
- Communication Overhead: Batch마다 파라미터의 2배 통신. 큰 Batch에서는 좋습니다.
- Memory Scaling: 전혀 없습니다. 모든 GPU가 모델 전체를 들고 있어야 하기 때문입니다.
Naive DDP: 모든 GPU가 모델 전체를 복제하고, 데이터 슬라이스만 다름
Naive DDP의 메모리 문제
메모리 측면에서 단순 모델 복제가 얼마나 비싼지 봅시다. FP16 Mixed Precision 기준 한 파라미터당 필요한 Byte를 풀어 보면 1:
- 2 Bytes: FP/BF16 모델 파라미터
- 2 Bytes: FP/BF16 Gradient
- 4 Bytes: FP32 Master Weights (Mixed Precision 학습에서 정확도 보존용)
- 4 Bytes (또는 2): FP32/BF16 Adam First Moment (Momentum)
- 4 Bytes (또는 2): FP32/BF16 Adam Second Moment (Variance)
- 즉 모델 한 벌의 5배가 메모리에 항상 올라가 있어야 합니다. 70B 모델이면 GPU마다 약 1.1 TB 가 필요합니다.
- 그래서 모델이 커질수록 Naive DDP는 빠르게 막힙니다. 해법이 다음 절의 ZeRO입니다.
(1)편의 <공식과 LLaMA 70B 예시> 섹션을 참고하세요.
ZeRO: Zero Redundancy Optimizer
- ZeRO(Zero Redundancy Optimizer) ㅣ DeepSpeed 팀이 제안한 메모리 최적화 기법입니다 2.
- 핵심 아이디어는 모든 GPU가 같은 것을 들고 있는 중복(Redundancy)을 제거하자로 요약됩니다.
- ZeRO는 메모리를 단계별로 Partition합니다. 단계마다 더 공격적이며, 그만큼 메모리 절감 폭이 큽니다 (대신 통신이 조금 더 늘어납니다).
ZeRO-1: Optimizer States 분할
- 핵심 아이디어 ㅣ Optimizer State(Adam의 1st/2nd Moment 등) 만 GPU별로 나눕니다.
- 파라미터와 Gradient는 모든 GPU가 그대로 보유합니다.
- 각 GPU는 자기 몫의 파라미터에 대해서만 Optimizer Step을 수행합니다.
- 동작
- 모든 GPU가 자기 Batch Slice로 Full Gradient를 계산합니다.
- Reduce-scatter로 Gradient를 Partition해서 각 GPU가 자기 Slice를 담당합니다.
- 각 GPU가 자기 Slice의 파라미터를 자기 Optimizer State로 업데이트합니다.
- All-gather로 업데이트된 파라미터를 모두에게 Broadcast합니다.
- 비용 비교:
| Comm. Primitive | Comm. Cost | Memory (per param) | |
|---|---|---|---|
| Naive DDP | All-reduce (Grads) | \(2 \times \text{#params}\) | \((4+K) \times \text{#params}\) |
| ZeRO-1 | Reduce-scatter + All-gather | \(2 \times \text{#params}\) | \((4 + K/N_{\text{gpu}}) \times \text{#params}\) |
- where \(K\) ㅣ Optimizer State의 Byte/param
- 통신은 그대로 유지되면서 메모리는 \(K\) 항을 GPU 수만큼 나눠 가집니다. 공짜에 가까운 효율입니다.
ZeRO-2: Gradient도 분할
- 핵심 아이디어 ㅣ Optimizer State에 더해 Gradient도 GPU별로 Sharded 상태로 유지합니다.
- 전체 Gradient 벡터를 한 번에 가지지 않습니다.
- 각 GPU가 자기 Slice의 Gradient만 보유합니다.
- 동작
- 각 GPU가 Computation Graph를 Backward로 점진적으로 진행합니다.
- 한 레이어의 Gradient가 계산되는 즉시 Reduce해서 담당 GPU로 전송합니다.
- 그 Gradient가 더 이상 필요 없으면 즉시 해제합니다.
- 각 GPU가 자기 Slice의 파라미터를 업데이트합니다.
- All-gather로 파라미터를 Broadcast합니다.
- 통신 비용은 ZeRO-1과 같은 \(2 \times \text{#params}\)이며, 메모리가 추가로 절감됩니다.
ZeRO-3 (FSDP): 파라미터까지 분할
- 핵심 아이디어 ㅣ 모든 것을 Sharded. 파라미터, Gradient, Optimizer State 전부 GPU별 분할.
- 이를 PyTorch에서는 FSDP(Fully Sharded Data Parallel) 라고 부릅니다.
- 동작 (점진적 통신과 계산)
- Forward Pass
- Step 1. 레이어 \(i\) 의 Forward 직전에 All-gather로 Layer \(i\) 의 파라미터를 모두에게 Materialize합니다.
- Step 2. Layer \(i\) 의 Forward가 끝나면 즉시 Layer \(i\) 의 파라미터를 해제합니다.
- Backward Pass
- Step 3. Layer \(i\) 의 Backward 직전에 All-gather로 다시 파라미터를 Materialize합니다.
- Step 4. Gradient를 계산한 후 즉시 Reduce-scatter를 수행합니다.
- Step 5. 각 GPU가 자기 Slice의 파라미터를 업데이트합니다.
- Step 6. 업데이트가 끝나면 즉시 Gradient를 해제합니다.
- Forward Pass
- 통신 비용: \(3 \times \text{#params}\) (Forward 1 All-gather + Backward 1 All-gather + 1 Reduce-scatter).
- 즉 Naive 대비 약 1.5배 통신 Cost 가 들지만, 메모리 선형 스케일 을 얻습니다.
ZeRO 1/2/3 비교 정리
- 통신 비용 한 줄 정리 1:
| Comm. Cost (per param) | 메모리 효과 | |
|---|---|---|
| DDP | \(2 \times \text{\#params}\) | 모델/Grad/Opt State 전부 복제 |
| ZeRO-1 | \(2 \times \text{\#params}\) (공짜) | Optimizer State 분할 |
| ZeRO-2 | \(2 \times \text{\#params}\) (거의 공짜) | + Gradient 분할 |
| ZeRO-3 | \(3 \times \text{\#params}\) (1.5배) | + 파라미터 분할 (선형 스케일) |
- 각 값의 유도는 다음과 같습니다.
- DDP: 매 스텝마다 Gradient 전체를 All-reduce합니다.
- 앞 절에서 본 대로 All-reduce = Reduce-scatter + All-gather 이므로 파라미터 하나당 통신량은 \(2 \times \text{#params}\).
- ZeRO-1, ZeRO-2: Gradient 통신을 Reduce-scatter로 하고, 업데이트된 파라미터를 다시 All-gather로 배포합니다.
- 두 스텝의 통신량은 각각 \(1 \times \text{#params}\) 이므로 합계는 여전히 \(2 \times \text{#params}\).
- ZeRO-3 (FSDP): 파라미터까지 분할된 상태이므로, Forward 시 파라미터 All-gather (1×), Backward 시 파라미터 All-gather (1×), Gradient Reduce-scatter (1×)로 총 \(3 \times \text{#params}\).
- Naive 대비 1.5배 통신 비용이지만 메모리는 GPU 수에 비례해 선형으로 줄어듭니다.
- DDP: 매 스텝마다 Gradient 전체를 All-reduce합니다.
- ZeRO-3에서는 All-gather와 Forward 계산이 Overlap 되어 통신 비용을 가릴 수 있다는 점도 큰 장점입니다 1.
ZeRO의 스케일링 효과
- 정리하면 ZeRO는 통신 비용을 크게 늘리지 않으면서 한 GPU당 필요한 메모리를 단계적으로 줄여주는 기법입니다.
- 특히 ZeRO-3 (FSDP)는 파라미터, Gradient, Optimizer State 모두를 GPU 수만큼 분할하므로, 이론상 같은 클러스터에서 GPU 수에 비례해 더 큰 모델을 담을 수 있습니다.
- 예를 들어 GPU 8장을 활용하면 Naive DDP 대비 약 8배 큰 모델을 올릴 수 있는 방향으로 스케일이 확장됩니다.
ZeRO-1/2/3 단계별 Partition: Optimizer State → +Grad → +Params 2
Data Parallelism의 한계
ZeRO 시리즈로 메모리를 GPU 수만큼 나눌 수 있게 됐지만, 그래도 한계는 남습니다 1.
- Batch Size에 의존 ㅣ Data Parallel은 결국 Batch를 나누는 방식입니다.
- GPU를 늘리면 Batch가 비례해서 커지는데, Batch 크기에는 학습 효율 측면의 Diminishing Returns가 존재합니다.
- ZeRO-1과 2는 메모리 스케일 한계 ㅣ ZeRO-1, ZeRO-2는 메모리를 일부만 분할하므로 모델이 더 커지면 결국 한계에 부딪힙니다.
- ZeRO-3도 Activation은 줄여주지 않음 ㅣ ZeRO-3는 파라미터, Gradient, Optimizer State를 줄이지만, Activation 메모리는 여전히 GPU에 그대로 남습니다.
통신 부담 ㅣ ZeRO-3는 통신이 많아 느린 인터커넥트에서는 비효율적입니다.
- 이 한계들이 Model Parallelism 으로 이어집니다. Batch를 그대로 두고 모델 자체를 GPU에 나누자는 발상입니다.
Pipeline Parallelism: 깊이로 자르기
- Pipeline Parallel ㅣ 모델의 레이어를 GPU별로 나눠 맡김. 모델 깊이(Depth) 방향 분할.
- 가장 직관적인 형태부터 시작해, 무엇이 안 되고 어떻게 개선해왔는지를 단계적으로 봅니다.
Layer-Wise Parallel
- 가장 단순한 형태: 모델 80개 레이어를 8개 GPU에 10개씩 나눔.
Forward는 1 → 2 → 3 → … → 8 GPU로 흐르고, Backward는 반대 방향으로 흐릅니다.
- 단점: 이용률(Utilization)이 낮음
- GPU 0이 Forward를 끝내고 GPU 1로 넘기면, GPU 0은 Backward가 돌아올 때까지 아무 일도 하지 않고 대기합니다.
- \(N\) 개의 GPU 환경에서 각 GPU는 약 \(1/N\) 시간만 실제로 활동합니다.
- 이래서는 GPU를 늘려도 Throughput이 안 늘어납니다.
GPipe: Micro-batch로 채우기
- GPipe 3 ㅣ Batch를 더 작은 Micro-batch로 쪼갠 뒤, 첫 Micro-batch가 다음 GPU로 넘어가는 동안 두 번째 Micro-batch를 시작하는 방식입니다.
- 변수 약속 ㅣ \(P\) = GPU 수, \(M\) = Micro-batch 수
- 단 여전히 파이프라인의 시작과 끝 부분에서 GPU가 비는 구간이 생깁니다. 이 구간을 Bubble 이라 부르며, Bubble 비율은:
- 즉 \(M\) 이 크면 Bubble이 줄어듭니다. 그래서 GPipe는 큰 Batch Size가 필요합니다.
Pipeline Parallel을 쓰는 이유
- Bubble, 큰 Batch 요구 등 단점이 많은데 왜 쓸까요?
- 메모리 절감: 모델을 깊이로 잘랐기 때문에 한 GPU에는 일부 레이어만 올라갑니다. 그래서 DDP보다 메모리 효율이 높습니다.
- 통신 패턴이 좋음: 통신은 \(B \times S \times H\) 크기의 Activation 1번(Point-to-point)만 발생하며, FSDP보다 통신량이 적습니다.
- 느린 인터커넥트에 적합: 그래서 보통 노드 간(Inter-node) 분할에 Pipeline을 사용합니다. 이는 메모리 측면의 스케일을 위한 선택입니다.
1F1B (PipeDream): 메모리도 잡기
- GPipe에는 또 다른 문제가 있습니다.
- 첫 GPU는 모든 Micro-batch가 끝날 때까지 모든 Activation을 저장해 두어야 합니다 (Backward에서 필요).
- \(M\) 을 크게 하면 Bubble은 줄지만, Activation 메모리가 폭발적으로 증가합니다.
- 해법은 1F1B (One Forward, One Backward, PipeDream) 4 입니다.
- 각 GPU가 파이프라인 끝까지 Forward를 한 후, 가장 오래된 Micro-batch의 Backward를 즉시 수행합니다.
- Backward가 끝난 Micro-batch의 Activation은 즉시 해제합니다.
- 결과적으로 Bubble은 비슷하지만, Activation 메모리가 크게 절감됩니다.
Pipeline Parallel의 진화: GPipe (Bubble + Activation 폭발) vs 1F1B (Activation 즉시 해제) 34
Interleaved 1F1B
1F1B는 여전히 Pipeline Flush에서 Bubble을 가집니다. 이를 더 줄이는 대표적 변형이 Interleaved 1F1B입니다.
Interleaved 1F1B ㅣ Megatron-LM v2 5.
- 각 GPU에 연속된 레이어 한 덩어리 대신 여러 작은 Chunk(예: Layer 0,1,4,5)를 배정합니다. Bubble을 줄이는 대신 통신 횟수는 증가합니다.
- Chunk 수 \(V\) 가 커지면 분모가 커지므로 Bubble이 GPipe 대비 \(1/V\) 배로 줄어드는 구조입니다.
Tensor Parallelism: 너비로 자르기
- Pipeline이 모델을 깊이로 잘랐다면, Tensor Parallel은 너비로 자릅니다. 한 레이어 안의 거대한 행렬을 Submatrix로 쪼개서 여러 GPU에 분배.
기본 아이디어
- 거대한 행렬 곱 \(Y = X \cdot A \cdot B\) 를 생각해 봅시다.
- 행렬 \(A\) 를 column-wise, \(B\) 를 row-wise로 잘라 GPU 0과 GPU 1에 배치:
- GPU 0: \(A_1\) (Column 절반), \(B_1\) (Row 절반)
- GPU 1: \(A_2\) , \(B_2\)
각 GPU가 자기 부분 곱을 계산하고, 마지막에 Partial Sum을 합치면 \(Y\) 와 같습니다.
- Forward와 Backward에서 필요한 통신 6:
- Forward: \(f\) 는 identity, \(g\) 는 All-reduce
- Backward: \(f\) 는 All-reduce, \(g\) 는 identity
Tensor Parallelism: 한 레이어의 행렬을 Column/Row로 자르고 Partial Sum을 All-reduce 6
TP vs PP
- 같은 Model Parallel이지만 성격이 다릅니다 6:
| Tensor Parallel | Pipeline Parallel | |
|---|---|---|
| 자르는 방향 | 너비 (Width) | 깊이 (Depth) |
| Bubble | 없음 | 있음 (1F1B로 완화) |
| 추가 인프라 | 적음 (모델만 wrapping) | 큰 변경 (스케줄링) |
| Batch Size 요구 | 작아도 OK | 큰 Batch 필요 |
| 통신량 | 매우 큼 (per Layer All-reduce) | 작음 (per Micro-batch p2p) |
| 통신 데이터 | \(8 \times B \times S \times H \times (P-1)/P\) per Layer | \(B \times S \times H\) per Micro-batch |
- 상수 8은 FFN과 Attention에서 Forward와 Backward를 합쳐 2 All-reduce × 2 component × 2 로 인한 인자입니다.
Tensor Parallel 사용 조건
- 저지연/고대역폭 Interconnect가 보장될 때만 사용합니다.
- GPU 위에서는 노드 내 (Intra-node), 보통 8 GPU까지 사용됩니다 6.
- 노드 간 (Inter-node) 통신이 끼면 TP는 급격히 느려집니다.
- 그래서 보통 TP는 노드 안, PP는 노드 간이 황금률로 통합니다.
Sequence Parallelism: Activation을 자르기
- 지금까지 우리는 파라미터 메모리를 줄이는 방법만 봤습니다. 그런데 (1)편에서 본 것처럼 LLM 학습에는 Activation 메모리도 큽니다.
- TP와 PP는 파라미터 메모리는 잘 줄여주지만, Activation 메모리는 다 안 줄여줍니다.
Transformer 한 레이어의 Activation 메모리
Transformer 한 레이어가 보유하는 Activation 메모리는 다음 항들의 합 7:
- Matmul 관련 (\(s\)=seq_len, \(b\)=Batch, \(h\)=hidden):
- QKV Input: \(3 \times 2sbh\)
- Attention Output: \(2sbh\)
- FFN Input/Output: \(2 \times 4 \times 2sbh\)
- pointwise 관련 (TP로 안 줄어드는 부분):
- LayerNorm × 2: \(2 \times 2sbh\)
- Residual × 2: \(2 \times 2sbh\)
- Dropout × 2: \(2 \times 1sbh\) (boolean)
- Attention 계산 관련:
- Attention Logits \(QK^T\): \(2abs^2\)
- Softmax: \(2abs^2\)
- Attention Dropout: \(1abs^2\) (이 항은 Recomputation으로 제거 가능)
- TP는 Matmul 부분의 Activation을 잘라줍니다. 그러나 LayerNorm, Residual, Dropout 같은 pointwise 연산은 Matmul이 없기 때문에 TP로 자를 수 없습니다.
- 이 남은 항이 무시 못 할 크기(\(\approx 10sbh\))에 해당합니다.
Sequence Parallel의 아이디어
- Sequence Parallel 7 ㅣ pointwise 연산을 시퀀스 축(sequence axis) 으로 쪼개 GPU에 분배합니다.
- pointwise 연산은 시퀀스 차원에 의존성이 없으므로 자유롭게 잘라도 안전합니다.
- TP 위에 SP를 얹으면 매우 깔끔하게 맞물립니다.
- TP의 \(\bar{f}\) (Forward에서 All-reduce) 를 Reduce-scatter + All-gather로 분해합니다.
- SP의 \(g\) (All-gather) 와 합쳐지면서 추가 통신 비용 없이 결합됩니다.
- TP + SP + Recomputation 을 결합하면 메모리에 대해 전체 선형 스케일링을 얻을 수 있습니다 7.
병렬화 5종 요약표
- 지금까지 본 기법들을 한 장에 정리하면 1:
| 방식 | 동기화 오버헤드 | 메모리 효과 | 대역폭 요구 | Batch Size 효과 |
|---|---|---|---|---|
| DDP / ZeRO-1 | Per-batch | 스케일 없음 | \(2 \times\) #params | 선형 |
| FSDP (ZeRO-3) | per-FSDP Block 3× | 선형 | \(3 \times\) #params | 선형 |
| PP | Per-pipeline | 선형 | \(bsh\) | 선형 |
| TP + SP | 2× per Transformer Block | 선형 | \(8 \cdot bsh\) per Layer (All-reduce) | 영향 없음 |
- 어느 하나가 모든 면에서 우월하지 않습니다. 메모리, 대역폭, Batch Size 사이의 trade-off 가 항상 존재합니다.
추가 병렬화: EP, CP
- 최근 모델에서 자주 등장하는 두 가지 변종입니다. 1:
- Expert Parallelism (EP) ㅣ MoE 모델에서 expert들을 GPU에 분배.
- Context Parallelism (CP) ㅣ 매우 긴 시퀀스를 여러 GPU에 분할.
ND Parallelism: 조합의 원칙
- 이제 마지막 큰 그림. 실제 대형 모델 학습은 위 기법들을 여러 축으로 동시에 결합합니다.
- 이를 ND Parallelism 이라 부릅니다 (Nd-dimensional parallelism).
- 2D, 3D, 4D, 5D parallelism 이라는 표현도 같은 의미를 가집니다.
경험 법칙 (rule of thumb)
- 문헌에서 통용되는 단순한 가이드 1:
- 모델이 메모리에 들어갈 때까지:
- TP 는 노드 안 GPU 수까지 (보통 최대 8)
- PP 는 노드 간 (느린 인터커넥트 활용)
- (또는 인터커넥트가 좋으면 ZeRO-3 (FSDP) 사용)
- 그 뒤 남은 GPU: DP 로 채움
- Batch가 너무 작으면 Gradient Accumulation 으로 통신 효율을 사면서 Batch Size를 키움
- 모델이 메모리에 들어갈 때까지:
- Megatron-LM 의 실제 사용 사례 (PTD-P: Pipeline + tensor + Data Parallel) 5:
- TP는 8까지 캡, 그 이상은 PP로
- DP는 모델이 클수록 줄어듦 (가장 큰 모델에서 DP=6)
Case Study
- 마지막으로, 최근 공개된 대형 모델들이 실제로 어떤 조합을 사용했는지 살펴봅니다 1.
LLaMA 3-405B
- 4D parallelism: TP + CP + PP + FSDP
- 3-stage training: small Batch Size → large Batch Size → Long Context
- H100 클러스터에서 학습.
DeepSeek-V3
- 3D parallelism: PP (DualPipe) + EP + ZeRO-1 DP
- PP = 16, EP = 64
2048 × H800.
- 두 모델의 공통 패턴에서 확인할 수 있는 실무 원칙은 다음과 같습니다.
- 2D 이상의 조합은 거의 보편입니다.
- PP는 거의 모든 대규모 학습에서 사용됩니다.
- MoE 모델은 반드시 EP를 병행합니다.
- Long Context 모델은 CP를 도입합니다.
Conclusion
- 한 GPU로 LLM을 학습할 수 없는 이유는 두 가지, 즉 메모리와 연산이 모두 부족하기 때문입니다.
- 그래서 Multi-GPU와 Multi-node 클러스터로 확장합니다.
- 그 무대 위에서 인터커넥트 위계(L1 → HBM → NVLink → NVSwitch → Ethernet)와 Collective Communication 패턴(All-reduce, All-gather, Reduce-scatter 등)이 모든 병렬화의 공통 언어입니다.
- 병렬화는 크게 3축, 즉 Data(DP, ZeRO 1/2/3), Model(TP, PP), Activation(SP)으로 나뉘며, 각각 메모리, 통신, Batch에 대해 다른 trade-off를 가집니다.
- 실제 학습은 이들을 조합한 ND parallelism입니다. LLaMA 3는 4D, DeepSeek-V3는 3D + MoE 조합을 사용합니다.
- 빠른 인터커넥트에는 무거운 통신을, 느린 인터커넥트에는 가벼운 통신을 배치합니다.
- 이것이 모든 LLM 분산 학습 설계의 시작이자 끝입니다.
References
Namhyuk Ahn, *Parallelism* (ECE7115 Multimodal VLM LLM, Lecture 9), Generative Computing Lab, Inha University, Spring 2026. ↩︎ ↩︎2 ↩︎3 ↩︎4 ↩︎5 ↩︎6 ↩︎7 ↩︎8 ↩︎9 ↩︎10 ↩︎11 ↩︎12
Rajbhandari et al., “ZeRO: Memory Optimizations Toward Training Trillion Parameter Models,” 2019. ↩︎ ↩︎2
Huang et al., “GPipe: Efficient Training of Giant Neural Networks using Pipeline Parallelism,” 2018. ↩︎ ↩︎2
Harlap et al., “PipeDream: Fast and Efficient Pipeline Parallel DNN Training,” 2018. ↩︎ ↩︎2
Narayanan et al., “Efficient Large-Scale Language Model Training on GPU Clusters Using Megatron-LM,” 2021. ↩︎ ↩︎2
Shoeybi et al., “Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism,” 2019. ↩︎ ↩︎2 ↩︎3 ↩︎4
Korthikanti et al., “Reducing Activation Recomputation in Large Transformer Models,” 2022. ↩︎ ↩︎2 ↩︎3