Skip to main content

기울기 누적 (Gradient Accumulation)

GPU 메모리가 부족하여 큰 배치 크기를 사용할 수 없을 때, 여러 미니배치의 기울기를 누적한 후 한 번에 업데이트하여 **유효 배치 크기(Effective Batch Size)**를 키우는 기법입니다.

원리

일반 학습에서는 매 배치마다 기울기를 계산하고 즉시 업데이트합니다. 기울기 누적에서는 NN번의 순전파/역전파 후에 한 번 업데이트합니다. 유효 배치 크기=미니배치 크기×누적 스텝 수\text{유효 배치 크기} = \text{미니배치 크기} \times \text{누적 스텝 수}
1

기본 구현

import torch
import torch.nn as nn

accumulation_steps = 4   # 4번 누적 → 유효 배치 크기 4배
batch_size = 8           # 실제 미니배치 크기
# 유효 배치 크기 = 8 × 4 = 32

model = nn.Linear(784, 10).to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()

optimizer.zero_grad()    # 루프 밖에서 한 번 초기화

for step, (data, labels) in enumerate(train_loader):
    data, labels = data.to(device), labels.to(device)

    # 순전파 + 역전파 (기울기 누적)
    outputs = model(data)
    loss = criterion(outputs, labels)
    loss = loss / accumulation_steps  # 손실 스케일링
    loss.backward()

    # N 스텝마다 가중치 업데이트
    if (step + 1) % accumulation_steps == 0:
        optimizer.step()
        optimizer.zero_grad()
2

Mixed Precision과 결합

from torch.amp import autocast, GradScaler

scaler = GradScaler('cuda')
accumulation_steps = 4

optimizer.zero_grad()

for step, (data, labels) in enumerate(train_loader):
    data, labels = data.to(device), labels.to(device)

    with autocast('cuda'):
        outputs = model(data)
        loss = criterion(outputs, labels) / accumulation_steps

    scaler.scale(loss).backward()

    if (step + 1) % accumulation_steps == 0:
        scaler.step(optimizer)
        scaler.update()
        optimizer.zero_grad()

주의사항

  • 손실 나누기: loss / accumulation_steps로 스케일링하여 평균 기울기를 유지합니다
  • BatchNorm: 각 미니배치의 통계량이 다르므로, 기울기 누적 시 배치 통계가 부정확할 수 있습니다. SyncBatchNorm이나 GroupNorm 사용을 고려하세요
  • 학습률: 유효 배치 크기에 맞게 학습률을 조정할 수 있습니다 (선형 스케일링 규칙)

체크리스트

  • 기울기 누적의 원리와 유효 배치 크기 계산을 이해한다
  • 손실 스케일링(loss / accumulation_steps)의 이유를 안다
  • Mixed Precision과 기울기 누적을 결합할 수 있다

다음 문서