MLOps - # 10 Model to GPU

jaewonnow_·2026년 4월 3일

MLOps

목록 보기
10/11

GPU에서의 딥러닝 , 메모리 구조

딥러닝 모델을 GPU에서 훈련시킨다는 것은 단순히 코드를 실행하는 것이 아니다.

GPU의 제한된 VRAM 안에서 모델 파라미터, 그래디언트, 활성화 값, 옵티마이저 상태를 모두 올려두고 연산해야 한다. 메모리가 부족하면 OOM(Out of Memory) 에러로 훈련이 중단되고, 메모리를 낭비하면 처리할 수 있는 배치 크기가 줄어 훈련이 느려진다.

GPU 메모리를 이해하는 것은 딥러닝 실무의 핵심이다.


1. 딥러닝 훈련의 전체 흐름

GPU에서 딥러닝 훈련이 어떻게 이루어지는지 전체 흐름을 먼저 파악해야 한다.

[데이터 로드]
    ↓
[순전파 (Forward Pass)]
    입력 데이터 → 각 레이어 통과 → 예측값 출력
    이 과정에서 각 레이어의 활성화 값이 메모리에 쌓인다
    ↓
[손실 계산 (Loss Calculation)]
    예측값과 정답 비교 → 손실값 계산
    ↓
[역전파 (Backward Pass)]
    손실값을 기반으로 각 파라미터의 그래디언트 계산
    순전파에서 저장해둔 활성화 값을 이 시점에 사용한다
    ↓
[가중치 업데이트 (Optimizer Step)]
    그래디언트를 이용해 파라미터 갱신
    ↓
[반복]

이 한 사이클(순전파 → 역전파 → 업데이트)을 1 iteration이라 한다.
이 과정에서 GPU 메모리에는 여러 종류의 데이터가 동시에 올라간다.


2. GPU RAM을 점유하는 요소들

훈련 중 GPU VRAM은 다음 여섯 가지 요소가 나눠서 차지한다. 각 요소가 어느 시점에 메모리를 사용하는지 이해하는 것이 중요하다.

모델 파라미터

신경망의 가중치(Weight)와 편향(Bias)이다. 모델이 존재하는 한 훈련 내내 메모리를 점유한다.

파라미터 메모리 = 파라미터 수 × 데이터 타입 크기

예) 7B 파라미터 모델, FP16 사용 시
= 7,000,000,000 × 2바이트 = 약 14GB

가장 기본적인 메모리 점유 요소이며, 모델을 GPU에 올리는 순간부터 자리를 차지한다.

입력 데이터 (배치)

CPU의 데이터셋에서 GPU로 로드되는 훈련 배치다. 배치 크기와 입력 차원(시퀀스 길이, 이미지 해상도 등)에 따라 크기가 달라진다. 배칭이 얼마나 잘 작동하는지는 요청 스트림에 따라 크게 달라진다. 하지만 균일한 요청으로 정적 배칭을 벤치마킹하여 성능의 상한선을 파악할 수 있다.

배치 메모리 = Batch Size × 시퀀스 길이 × 차원 수 × 데이터 타입 크기

배치 크기를 두 배로 늘리면 이 항목의 메모리가 두 배가 된다.

정적 배치

클라이언트가 여러 프롬프트를 요청으로 묶고 배치 내의 모든 시퀀스가 완료된 후 응답이 반환된다. 당사의 추론 서버는 이를 지원하지만 필수는 아니다.

동적 배치

프롬프트는 서버 내부에서 즉석에서 함께 배치된다. 일반적으로 이 방법은 정적 배치보다 성능이 떨어지지만, 응답이 짧거나 길이가 균일한 경우 최적에 가까워질 수 있다. 요청에 다른 매개변수가 있을 때는 잘 작동하지 않는다.

연속 배치

요청을 도착하는 대로 함께 배치하는 아이디어는 현재 SOTA(State-of-the-Art) 방법이다. 배치 내의 모든 시퀀스가 완료될 때까지 기다리는 대신, 반복 수준에서 시퀀스를 함께 그룹화한다. 동적 배치보다 10배-20배 더 나은 처리량을 달성할 수 있다.

높은 처리량과 비싼 GPU를 효과적으로 활용하려면 여러 요청을 동시에 처리하는 것이 중요히다. 공유 온라인 서비스의 경우 연속 배치가 필수적이지만, 오프라인 일괄 추론 워크로드는 더 간단한 배치 기술로 높은 처리량을 달성할 수 있다.


그래디언트

역전파 과정에서 계산되는 각 파라미터에 대한 기울기 값이다. 크기는 모델 파라미터와 동일하다.

그래디언트 메모리 ≈ 파라미터 메모리와 동일

예) 7B 모델, FP16 시 → 그래디언트도 약 14GB 추가 점유

역전파가 시작되는 순간 생성되며, 가중치 업데이트 후 초기화(zero_grad())되기 전까지 메모리를 유지한다.


활성화 값 (Activations)

순전파 중 각 레이어가 출력하는 중간 결과값이다. 역전파 시 그래디언트 계산에 필요하기 때문에 순전파가 끝난 뒤에도 메모리에 유지해야 한다.

[Layer 1] → 활성화 값 저장 → [Layer 2] → 활성화 값 저장 → ... → [출력]
                                                                      ↓
                                                                  역전파 시작
                                                         저장된 활성화 값 역순으로 사용

네트워크가 깊어질수록, 배치 크기가 커질수록 활성화 값이 차지하는 메모리가 기하급수적으로 늘어난다. 대형 모델에서 OOM이 발생하는 주원인 중 하나다.


옵티마이저 상태

옵티마이저가 가중치 업데이트를 위해 별도로 관리하는 상태값이다. 옵티마이저 종류에 따라 차이가 크다.

옵티마이저추가 메모리저장하는 상태
SGD파라미터의 1배모멘텀
Adam / AdamW파라미터의 2배1차 모멘트(m), 2차 모멘트(v)
Adafactor파라미터보다 적음인수분해된 상태

Adam을 사용하는 7B 모델을 FP32 상태로 훈련할 경우, 옵티마이저 상태만으로 파라미터 크기의 2배에 해당하는 메모리가 추가로 필요하다.

Adam 옵티마이저 총 메모리
= 파라미터(1배) + 그래디언트(1배) + m(1배) + v(1배)
= 파라미터의 4배

임시 버퍼

순전파와 역전파 중 중간 연산에 사용되는 임시 메모리다. 연산이 끝나면 해제되지만, 연산 도중에는 추가 메모리를 점유한다. 어텐션 연산의 Attention Map이 대표적인 예다.

Attention Map 크기 = Batch Size × Head 수 × 시퀀스 길이²

시퀀스 길이가 길어질수록 제곱으로 증가한다.

3. GPU 메모리 효율 전략

위 여섯 가지 요소를 어떻게 줄이거나 분산시키느냐가 GPU 메모리 최적화의 핵심이다.

혼합 정밀도 훈련 (Mixed Precision)

FP32 대신 FP16 또는 BF16을 사용하면 파라미터, 그래디언트, 활성화 값의 메모리를 절반으로 줄일 수 있다. 다만 수치 안정성을 위해 옵티마이저 상태는 FP32로 유지하는 것이 일반적이다.

FP32 전체 훈련:   파라미터 + 그래디언트 + 옵티마이저 상태 모두 FP32
혼합 정밀도 훈련: 파라미터 + 그래디언트 → FP16
                  옵티마이저 상태        → FP32 (정밀도 보존)

속도와 메모리 효율을 동시에 얻을 수 있어 현재 대부분의 대형 모델 훈련에서 기본으로 사용된다.

그래디언트 체크포인팅 (Gradient Checkpointing)

활성화 값을 전부 저장하지 않고, 일부만 저장한 뒤 역전파 시점에 나머지를 재계산하는 방법이다.

일반 훈련:
  순전파 중 모든 레이어의 활성화 값 저장 → 메모리 높음, 역전파 빠름

그래디언트 체크포인팅:
  체크포인트 레이어의 활성화 값만 저장
  나머지는 역전파 시 재계산 → 메모리 낮음, 역전파 느림 (약 30~40% 추가 연산)

메모리와 연산 시간을 맞바꾸는 전략이다. VRAM이 부족할 때 배치 크기를 줄이는 대신 체크포인팅을 적용하면 같은 메모리로 더 큰 모델을 훈련할 수 있다.

모델 병렬화

단일 GPU에 모델 전체가 올라가지 않을 때 여러 GPU에 분산하는 방법이다.

텐서 병렬화: 하나의 레이어를 여러 GPU에 분산
파이프라인 병렬화: 레이어 그룹을 순서대로 GPU에 배치
데이터 병렬화: 동일 모델을 복제하여 각 GPU가 다른 배치 처리

메모리 효율적 옵티마이저

AdamW는 Adam의 가중치 감쇠 방식을 개선한 버전으로, 수치 안정성이 높다. 더 나아가 Adafactor는 옵티마이저 상태를 인수분해하여 Adam 대비 메모리를 대폭 줄인다.

효율적 데이터 로딩

데이터를 CPU에서 GPU로 전송하는 과정에서 병목이 생기지 않도록, DataLoader의 num_workersprefetch_factor를 조정하여 다음 배치를 미리 준비해두는 것이 중요하다.


4. Gradient Accumulation: 제한된 메모리에서 큰 배치 효과 내기

왜 필요한가

딥러닝 훈련에서 배치 크기는 훈련 안정성과 수렴 속도에 직접적인 영향을 준다. 배치가 클수록 그래디언트 추정의 분산이 줄어들어 훈련이 안정적이다. 그러나 배치 크기를 늘리면 입력 데이터와 활성화 값의 메모리가 함께 증가한다.

VRAM이 8GB인 GPU에서 배치 크기 64로 훈련하고 싶지만, 실제로 올릴 수 있는 배치는 4밖에 안 된다면 어떻게 할까?.

Gradient Accumulation이 이 문제를 해결한다.

동작 원리

가중치를 업데이트하지 않고 그래디언트만 누적한 뒤, 지정한 횟수만큼 누적이 완료되면 한꺼번에 가중치를 업데이트한다.

gradient_accumulation_steps = 16, 실제 배치 크기 = 4

Step 1: 배치 4개 순전파/역전파 → 그래디언트 계산, 누적 (가중치 업데이트 X)
Step 2: 배치 4개 순전파/역전파 → 그래디언트 누적
...
Step 16: 배치 4개 순전파/역전파 → 그래디언트 누적
         → 누적된 그래디언트로 가중치 업데이트
         → 그래디언트 초기화

유효 배치 크기 = 4 × 16 = 64 (실제 메모리 점유는 배치 4 기준)

메모리 점유는 실제 배치 크기(4) 기준이지만, 가중치 업데이트는 유효 배치 크기(64)의 그래디언트를 반영한다.

Accelerator를 활용한 구현

HuggingFace의 Accelerate 라이브러리를 사용하면 Gradient Accumulation을 간단하게 적용할 수 있다.

from accelerate import Accelerator

accelerator = Accelerator(gradient_accumulation_steps=64)

model, optimizer, dataloader = accelerator.prepare(model, optimizer, dataloader)

for batch in dataloader:
    with accelerator.accumulate(model):  # 내부적으로 누적 로직 처리
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)
        optimizer.step()
        optimizer.zero_grad()

accelerator.accumulate(model) 컨텍스트 안에서는 64 step마다 한 번씩만 optimizer.step()이 실제로 실행된다. 나머지 63 step에서는 그래디언트만 누적된다.

메모리 관점에서의 의미

일반 배치 크기 64 훈련:
  입력 데이터: 64 × 시퀀스 길이 × 차원 수 → 높은 메모리
  활성화 값:  64 × 각 레이어 출력 크기   → 높은 메모리
  → OOM 발생 가능

Gradient Accumulation (steps=16, 실제 배치=4):
  입력 데이터: 4 × 시퀀스 길이 × 차원 수  → 낮은 메모리
  활성화 값:  4 × 각 레이어 출력 크기    → 낮은 메모리
  그래디언트: 누적되지만 파라미터 크기와 동일 (변하지 않음)
  → 유효 배치 64의 훈련 효과, 메모리는 배치 4 수준

단, 16번의 순전파/역전파를 수행해야 유효 배치 64와 동일한 효과를 내므로, 연산 횟수는 동일하다. 메모리를 아끼는 대신 시간적 오버헤드는 발생하지 않는 것이 Gradient Accumulation의 장점이다.


5. 전체 메모리 전략의 연결 구조

지금까지 설명한 개념들은 서로 독립적으로 존재하지 않는다.

GPU VRAM 한계
    │
    ├── 모델 파라미터가 너무 큼
    │       → 혼합 정밀도 (FP16/BF16) 적용
    │       → 모델 병렬화 (다중 GPU 분산)
    │
    ├── 활성화 값이 너무 큼
    │       → 그래디언트 체크포인팅 적용
    │       → 배치 크기 축소 + Gradient Accumulation으로 보완
    │
    ├── 옵티마이저 상태가 너무 큼
    │       → Adafactor 등 메모리 효율 옵티마이저 사용
    │
    └── 배치 크기를 늘리고 싶음
            → Gradient Accumulation으로 유효 배치 크기 확장

현실적인 대형 모델 훈련에서는 이 전략들을 조합해서 사용한다. 예를 들어 혼합 정밀도 + 그래디언트 체크포인팅 + Gradient Accumulation을 동시에 적용하면, 단일 GPU로도 수십억 파라미터 규모의 모델을 훈련하는 것이 가능해진다.


정리

GPU에서의 딥러닝 훈련은 VRAM이라는 제한된 자원 안에서 여섯 가지 메모리 요소를 동시에 관리하는 작업이다.

모델 파라미터와 그래디언트는 모델 크기에 고정적으로 비례하고, 활성화 값과 입력 데이터는 배치 크기에 따라 동적으로 변한다. 옵티마이저 상태는 선택에 따라 파라미터의 두 배까지 추가 점유할 수 있다.

이론을 이해하는 것이 중요하지만, 항상 종단 간 서버 성능을 측정하는 것이 좋다. 추론 배포가 예상보다 성능이 떨어지는 데에는 여러 가지 이유가 있다. 소프트웨어 비효율성으로 인해 MBU가 예상보다 낮을 수 있다. 또는 클라우드 제공업체 간의 하드웨어 차이로 인해 예상치 못한 결과가 발생할 수 있다.

Gradient Accumulation은 이 구조를 이해한 상태에서 활성화 값과 입력 데이터의 메모리 점유를 최소화하면서, 큰 유효 배치 크기의 훈련 효과를 유지하는 전략이다.

메모리 최적화 전략들은 각각 트레이드오프를 가지며, 모델 크기와 하드웨어 환경에 맞게 조합하는 것이 실무의 핵심이다.

profile
0 to 100 Data Engineer

0개의 댓글