ZeRO: Memory Optimizations Toward Training Trillion Parameter Models


읽게 된 이유

  • N모사에서 NVIDIA Superpod으로 175B짜리 한국어 모델을 굽는 중 (GPT-3)
  • 하지만 저거는 1024대 이상의 GPU(A100)을 쓰는데.. MS에서 1대 만으로도 10B 모델을 구울 수 있다고?
  • 이건 해봐야 해...!

TL;DR

ZeRO1, 2와 CPU OffLoad까지 사용하면 10B 모델을 1대의 V100 GPU(32G vram)에서 학습할 수 있다.
이때 Model Parallel, Pipeline Parallel 등 모델 파티셔닝을 극한으로 쪼개고, 여기에 CPU 메모리까지 추가로 사용해서 Vram의 사용 효율을 극대화 한다.
  • v1은 ZeRO1, Oct 2019
  • v2는 ZeRO2와 ZeRO-Offload, May 2020
(주의: 1GPU 사용시에는 MP/DP 없이 CPU Offload만 사용함)
DeepSpeed & ZeRO2 설명 영상

공홈 홍보글

BERT-Large 학습에 한시간!
notion image

Data Parallel & Model Parallel & Pipeline Parallel

딥러닝 뉴럴넷을 쪼개서 Parallel하게 학습하는 3가지 방법
notion image
1) Data Parallel
  • PyTorch의 ddp 쓰면 곧바로 사용 가능
  • Batch를 나눠서 할당
  • 모델은 한 GPU에 다 올라가는 셈
2) Model Parallel
  • Layer 한 층을 여러개로 쪼개서 여러 GPU로 계산
  • GPU간에 통신이 자주 필요함
MS의 PipeDream에서의 예시
MS의 PipeDream에서의 예시
3) Pipeline Parallel (제일 위 그림에서는 workload partitioning이라고 표현)
  • Layer를 층별로 나눔
  • 앞선 Layer의 계산이 끝날때까지 기다렸다가 결과를 받아 다음 GPU로 넘겨줌
  • 위로 인한 공백 시간이 발생하는 것을 막기 위해, Micro batch in Mini Batch 기법을 사용함
    • Batch Size가 128이라면 이걸 4로 나눠서 32씩 계산하고 다음 GPU로 넘기고 하는 방법
    • GPU Utilization 높이기 (= 연산 전체 속도 상승)
    • MS의 PipeDream 페이퍼의 예시
      MS의 PipeDream 페이퍼의 예시

Abstract

현존하는 거대한 Language Model들은 학습하는 것에 문제가 있음.

  • 학습을 빠르게 하고싶다 = 많은 GPU + 큰 Batch size → Data Parallel
    • 모델을 복사해야 해서 Redundency 발생
    • 한 GPU 보다 큰 모델은 학습 못함
  • 엄청 큰 모델을 학습하고 싶다 → 모델을 쪼개자 → Model Parallel
    • Transformer의 MultiHead를 각 head별로 쪼개서 GPU에서 각각 계산하자!
    • 커뮤니케이션이 결국 필요함
    • 개발도 어려움

우리는 vram 효율도 좋고 속도도 빠르고 엄청 큰 모델도 올릴 수 있는 것을 만들었다!

→ "ZeRO: Zero Redundancy Optimizer"
  • DP/MP에서 발생하는 Memory Redundancy를 제거
  • Communication cost를 최소화
  • Computational Power를 최대한 유지(각 GPU별로)
이 방법으로 최대 1Trillion Params를 가진 모델을 현존하는 장비(아마도 V100 1024대)로 학습할 수 있다.
그리고 이거 오픈소스(DeepSpeed)로 공개함 → 100B params GPT를 400GPU에서 학습 가능했음
Model Parallel 없이도 13B 모델을 단일 GPU에 올릴 수 있음

Extended Introduction

기존에 ZeRO1에서 버전 올리면서 한 페이퍼에 분량이 늘어남. 사실상 이 부분만 봐도 논문 다 보는 셈

LM이 너무 커지고 있다.

기존의 큰 Language Model에는
  • BERT-Large 0.3B
    • 분명히 KcBERT 학습할 때는 엄청 커보였는데 이제는 너무 작고 귀여워 보인다..
  • GPT-2 1.3B
  • Meagtron-LM 8.3B
  • T5 11B
  • GPT-3 175B
하지만 MultiGPU를 사용하기 위해 가장 쉽게 사용하는 Data Parallel(DP, DDP) 방식은 한 GPU에 모든 Model param이 올라가야 한다는 점으로 인해, 32G V100 기준 1.4B params 이상은 올릴 수가 없다.
다른 솔루션으로는 Pipeline Parallel 방식으로, 각 layer(층)별로 모델을 쪼개거나 혹은 Model Parallel로 Multihead attention에서 head를 쪼개는 등의 방식을 사용할 수도 있다.
또다른 방식으로는 CPU Off loading(CPU Ram 활용) 등을 쓸 수도 있다.
하지만 여전히 이러한 방식을 써도 속도 이슈 등이 발생한다.

Model Parallel vs Pipeline Parallel

위 3가지 방법 중 가장 쉽게 적용 가능한 것이 Model Parallel로, T5와 Megatron-LM은 MP를 적용해서 학습함.
하지만 MP는 모델 크기에 따라 최대로 늘릴 수 있는 것에 제약이 있다.
  • MultiHead att 등에서 HEAD를 쪼개는 것이기 때문에 최대로 늘릴 수 있는 경우가 Head 수로 제약이 됨
  • 좀더 General하게 이야기 하면, 각 layer에 있는 params를 쪼개서 여러 GPU로 나눠 계산하는 것.
  • 각 layer 단에서 계산된 값을 공유하는 것으로 인해 communication cost가 무척 높음
  • 따라서, 단일 Node(multiGPU인 한 장비)에서 쓰는 것은 괜찮지만, Multi Node로 가는 순간 속도 급락
  • 40B 모델로 테스트한 경우(2대 DGX-2) → V100 GPU당 겨우 5Tflops 나옴. (각 GPU 최대 성능의 5%)

ZeRO에서 제안하는 방향

  • "어디에서 Memory comsumption이 발생하는가?"
    • 대부분은 model states = optimizer states + gradients + params
    • 나머지는 Residual states = activation + tmp buffer + fragmented memory
  • ZeRO에서 제안하는 방향
    • Memory Efficiency
    • High compute & less communication

Optimize Model state Memory

model state는 학습 중에 가장 많은 GPU 메모리를 차지한다.
하지만 현존하는 DP와 MP는 좋은 솔루션이 아니다.
  • DP: Good compute & communication efficiency, Poor memory efficiency
    • 모델 전체를 copy 해서 모든 GPU에 할당
    • 각 Batch size를 N개(GPU)로 나눠서 계산
  • MP: Poor compute & comm efficiency.
위 두가지 방법 모두, 모든 Model state가 항상 필요한 것이 아님에도 GPU 메모리를 차지함

ZeRO-DP: ZeRO-powered Data Parallel

DP급의 computation & communication efficiency 유지하지만 MP급의 메모리 여유
  • Memory state redundancy 제거
    • 모델을 copy하는 대신 Partitioning
  • Dynamic communication schedule 통해서 communication volume을 DP급으로 줄임

ZeRO-DP의 3가지 단계

사실 이 Figure 하나가 논문 전체를 대변해 주는 것과 동일할 정도.
사실 이 Figure 하나가 논문 전체를 대변해 주는 것과 동일할 정도.
  1. Optimizer State Partitioning (P_os)
      • 1/4로 메모리 감소, communication cost 동일
  1. P_os + Gradient Partitioning (P_os+g)
      • 1/8로 메모리 감소, comm cost 동일
  1. P_os+g + Parameter Partitioning
      • N개의 GPU 쓸 경우 1/N으로 메모리 감소, comm cost는 50% 증가
위 방법 통해서 1T params 모델을 1024 V100 GPU로 학습시킬 수 있다.
  • 만약 Adam Optimizer + fp16으로 학습한다면 1GPU당 약 16G 메모리 사용함 (충분히 가능!)

Optimize Residual State Memory

model state뿐만 아니라, activation, temp buffers, unusable memory fragments 이슈가 있음

ZeRO-R: Residual 메모리 최적화

  • Activation 최적화
    • Checkpointing은 약간 도움 되지만 큰 모델에는 그닥 도움 x
    • MP에 있는 중복 Activation을 제거 (activation partitioning)
    • 만약 가능한 경우 CPU memory로 Activation을 Offload
      • notion image
        notion image
  • 적당한 Temp buffer 사이즈 책정
  • Tensor의 Lifetime 사용으로 fragmented memory 없도록 만들기

그렇다면 MP는 이제 필요가 없나?

NO, 여전히 쓸만할 경우가 있음
  • ZeRO-R을 사용할 경우 MP를 사용하면 Actvation memory footprint를 줄일 수 있음 (엄청 큰 모델의 경우)
  • DP 혼자 쓰는 것보다 MP 함께 쓰면 큰 Batch size로 aggreagate하는 효과를 누릴 수 있음
    • 좀더 좋은 Convergence 가능함

1-Bit Adam Optimizer

Adam Optimizer를 1bit로 전송함
  • Gradient Momentum 나온 값을 UP / DOWN 두가지 중 하나만을 선택해서 정해진 alpha값만큼 증가 혹은 감소하도록 처리
  • 모델이 워낙 크고 작은 lr을 사용하니 가능한 방식
  • Same Converge 보장, 1/5 정도의 communication cost 소모
  • 초반에는 Gradient의 Variance update가 굉장히 차이가 커서 정보량이 크지만, 일정 수준(Warmup) epochs 이후에는 Variance update 값이 매우 작아지기 때문에 고정된 값을 사용해도 된다. 대신 +인지 - 인지 방향만 전달해주면 OK.
  • 연구에서는 약 15-20%정도만 warmup해주면 OK.
    • notion image
      GPU 숫자에 따른 1bit adam의 성능 향상
      GPU 숫자에 따른 1bit adam의 성능 향상

ZeRO의 장점

  • Model Size
    • ZeRO-100B로 170B짜리 학습 가능
    • 현존하는 Megatron-LM 코드로는 40B 모델이 최대
  • Speed
    • 400대의 V100으로 학습할 경우 100B 모델 학습 시간 기준 기존 대비 10배 속도
    • 한 GPU당 학습 속도가 약 38TFlops, 총합 15PFlops
      • notion image
  • Scalability
    • 64→400GPU까지 올리는데 큰 지장이 없음. 그 이상도 가능
      • → MegatronLM vs ZeRO
        notion image
        notion image
  • ZeRO-100B 사용하면 PP/MP 없이도 한 GPU에 13B 모델 올릴 수 있음
    • 기존 PyTorch DDP 사용하면 1.4B 이상시 OOM 발생함
  • 이 외에도 Sparse transformer 지원을 통해 엄청 긴 Sequence를 학습할 수 있음 (Sparse attention)
위 모든 것 구현된 곳 → DeepSpeed

ZeRO-Offload

단일 GPU에서 10B 모델을!
위에서 봤던 것과 동일한 그림.
위에서 봤던 것과 동일한 그림.

ZeRO-Offload의 구성

ZeRO-Offload는 ZeRO-2 위에서 동작한다.
Offload의 핵심은 Compute efficiency loss를 최소화하는 방향이라서, GPU를 최대한 많이 쓰는 방향으로 동작한다.

ZeRO-Offload는 어떻게 10B처럼 큰 모델을 단일 GPU로 올리나?

가장 기본적으로, CPU Offload를 통해 Optimizer State와 Gradients를 CPU 메모리로 올린다.
ZeRO-2에서는 위 두 가지를 각각 다른 GPU에서 사용할 수 있도록 Partitioning을 하지만, 그 대신 CPU ram에 올리는 셈.
전체 학습 과정에서 Optimizer states는 항상 CPU memory에 올라가 있고, Gradients는 학습에 사용하는 GPU들에서 Backward 과정 중 Reduce-scatter 한 뒤, 각각의 GPU(DataParallel)에서 나온 Gradients들의 Avg로 다시 CPU로 Offload한다. (각각 GPU 학습에 사용하는 부분 말고는 버림)
Reduce-Scatter?
ReduceScatter는 기본적으로 Reduce와 동일하지만 결과물이 각각 다른 Partition 으로 나간다는 차이
notion image
위 과정으로 CPU에 Gradient가 올라오고 나면 CPU상에서 DataParallel 각각에서 Optimizer State partition이 업데이트(p update)되고, 업데이트 된 파라미터들은 All-gather 연산으로 합쳐진 뒤 각각의 GPU로 다시 내려간다.
이때, g offloadg swap 사이의 간격을 최소화 하기 위해 각 DataParallel별로 끝나는 시점에 곧장 CPU로 올려서 연산을 시작하고, 연산이 끝나는 즉시 GPU로 내리는 순서를 진행한다.
(이로 인해 각각 다른 CUDA stream을 사용한다고 함)

DeepSpeed Sparse Attention

10배 긴 Sequence에 6배 빠른 학습
Transformer 계열은 필연적으로 Attention 계산에서 문장의 길이 N에 따라 O(n^2)의 compute complexity가 발생한다.
DeepSpeed에서는 이러한 긴 Sequence 처리를 위한 Sparse Attention Kernel을 제공함.
아래 그림과 같이 Attention을 전체가 아닌 군데군데 하는 방식을 통해서 Attention 연산량을 줄인다.
Variable Sparsity structure
Variable Sparsity structure
실제 BERT에 가져다 사용한 모습
  • Dense
  • Dense + Activation checkpointing
  • Sparse + Activation checkpointing
notion image
  • 실제로 Sparse Pretraining하는게 성능도 더 잘나옴
    • LongFormer, BigBIRD 등 논문들에서 Dense att보다 sparse att이 더 성능 잘 나온다고 함
notion image

Reference