Gradient Accumulation
그래디언트 누적은 한 번의 가중치 업데이트 전에 여러 마이크로 배치의 그래디언트를 합산하여, 제한된 GPU 메모리로 더 큰 배치와 같은 효과를 냅니다.
그래디언트 누적은 마이크로배치라고 하는 여러 개의 작은 배치에 순전파와 역전파를 수행하고 그래디언트를 합산한 뒤에만 모델 가중치를 업데이트하는 학습 기법입니다. 이 업데이트는 모든 마이크로배치를 합친 크기의 배치로 한 번 학습하는 것을 근사하며, GPU에는 한 번에 하나의 마이크로배치에 대한 활성화만 저장됩니다. 이를 통해 메모리가 제한된 장치에서도 이미지 해상도를 낮추거나 모델 크기를 줄이지 않고 레시피에 지정된 배치 크기로 학습할 수 있습니다. 유효 배치 크기는 마이크로배치 크기에 누적 단계를 곱하고 장치 수를 곱한 값입니다. 따라서 이미지 4개로 구성된 마이크로배치를 4개의 GPU에서 8단계 누적하면 배치 크기 128처럼 동작합니다.
그래디언트 누적의 작동 방식#
PyTorch와 대부분의 다른 프레임워크에서 backward()을 호출하면 파라미터에 이미 저장된 그래디언트를 교체하는 대신 새 그래디언트를 더합니다. 누적은 이 동작을 직접 활용합니다. 각 마이크로배치에서 순전파와 역전파를 실행하고, 정해진 수의 마이크로배치가 모이면 옵티마이저를 한 번 호출한 다음 그래디언트를 지우고 다음 윈도우를 시작합니다.
대부분의 손실 함수는 배치 내 샘플에 대해 평균을 구하므로, 합산된 그래디언트를 평균으로 변환하려면 각 마이크로배치의 손실을 단계 수로 나눕니다. 이러한 스케일링을 적용하면 샘플 8개씩으로 구성된 마이크로배치 4개는 샘플 32개로 구성된 배치 하나와 같은 그래디언트를 생성합니다.
import torch
from torch import nn
torch.manual_seed(0)
model = nn.Linear(16, 1)
x, y = torch.randn(32, 16), torch.randn(32, 1)
loss_fn = nn.MSELoss()
# Reference: one batch of 32 samples
loss_fn(model(x), y).backward()
batch_grad = model.weight.grad.clone()
model.zero_grad()
# Four micro-batches of 8: backward() adds into .grad, and dividing each loss by 4 turns the sum into a mean
steps = 4
for x_micro, y_micro in zip(x.chunk(steps), y.chunk(steps)):
(loss_fn(model(x_micro), y_micro) / steps).backward()
print("Matches the full-batch gradient:", torch.allclose(model.weight.grad, batch_grad, atol=1e-6))
print(f"Largest difference: {(model.weight.grad - batch_grad).abs().max():.1e}")최대 차이가 1.6e-07인 Matches the full-batch gradient: True을 출력합니다. 이는 같은 숫자를 다른 순서로 더할 때 발생하는 부동소수점 반올림 차이입니다.
큰 배치와 누적 방식이 다른 점#
이 방식은 독립 샘플의 평균을 구하는 손실에서 동일한 결과를 내지만, 흔히 다음 세 가지 경우에는 차이가 발생합니다.
- 배치 정규화: 각 마이크로배치에서 평균과 분산을 계산하므로 이미지 4개의 통계는 이미지 128개의 통계보다 잡음이 큽니다.
- 가변 길이 시퀀스: 언어 모델이 각 마이크로배치의 토큰 전체에 대해 손실 평균을 구하면 토큰이 적은 마이크로배치도 긴 마이크로배치와 같은 가중치를 갖습니다.
- 그래디언트 클리핑: 그래디언트를 누적한 후 옵티마이저 단계 직전에 한 번 수행해야 합니다. 각 마이크로배치마다 클리핑하면 결과가 달라집니다.
Hugging Face와 Unsloth는 2024년 10월에 토큰 수 불일치 문제를 해결하여 대규모 언어 모델(LLM) 파인튜닝에서 전체 누적 윈도우의 총 토큰 수로 합산 손실을 나누도록 했습니다. 누적은 속도 향상도 제공하지 않습니다. 동일한 수의 샘플이 여전히 마이크로배치 단위로 네트워크를 통과하며, 작은 마이크로배치는 GPU를 덜 효율적으로 사용합니다. 분산 학습에서 PyTorch의 DistributedDataParallel은 마지막 마이크로배치를 제외한 각 마이크로배치에서 그래디언트 전체 합산을 건너뛰는 no_sync() 컨텍스트를 제공하여 통신량을 줄입니다. 유효 배치가 커지면 일반적으로 더 높은 학습률이 필요합니다. Facebook 연구진은 배치 크기에 맞춰 학습률을 선형적으로 높이고 점진적으로 워밍업하여 GPU 256개에서 이미지 8,192개 배치로 ImageNet의 ResNet-50을 1시간 만에 학습했습니다.
관련 기법#
- 그래디언트 체크포인팅: 활성화를 저장하는 대신 역전파 중에 다시 계산합니다. 추가 연산을 사용하는 대가로 각 마이크로배치의 메모리를 절약합니다.
- 혼합 정밀도: 활성화를 16비트 형식으로 저장하여 더 큰 마이크로배치를 사용할 공간을 확보합니다. 손실 스케일러는 마지막 마이크로배치 이후 그래디언트의 스케일을 한 번 되돌립니다.
- 파이프라인 병렬 처리: 각 배치를 마이크로배치로 분할해 서로 다른 GPU의 모델 단계에 통과시키고, 옵티마이저 단계를 한 번 수행하기 전에 그래디언트를 누적합니다.
Ultralytics YOLO26 학습은 기본적으로 그래디언트를 누적합니다. 학습 모드에서는 트레이너가 round(nbs / batch) 배치마다 옵티마이저 단계를 수행하므로, 기본 batch=16과 명목 배치 크기 nbs=64를 사용하면 업데이트당 배치 4개가 포함되고 batch=8를 사용하면 8개가 포함됩니다. 워밍업 중에는 누적 수가 1부터 점진적으로 증가하며, 가중치 감쇠는 batch × accumulation / nbs로 스케일링됩니다. 따라서 이미지 64개 배치에 맞춰 설정된 기본 하이퍼파라미터는 한 번에 몇 개의 이미지만 처리할 수 있는 GPU에서도 그대로 적용됩니다.










