PyTorch 하드웨어 의존성 제거하기: Hugging Face Accelerate로 갈아타야 하는 이유

2026. 1. 28. 14:51·AI · ML/Computer Vision
728x90
반응형

"로컬에서 잘 돌던 코드가 GPU 서버에 올리니 터진다"는 경험, 한 번쯤 있지 않은가?

들어가며

PyTorch로 딥러닝 모델을 개발하다 보면, 모델 아키텍처 자체보다 '학습 환경 설정(Boilerplate Code)' 때문에 스트레스를 받는 순간이 반드시 온다.

"로컬(CPU)에서 짤 때는 잘 돌아갔는데, 서버(GPU)에 올리니 에러가 나네?"
"단일 GPU 코드를 멀티 GPU(DDP)로 바꾸려니 코드를 다 뜯어고쳐야 하네?"

이런 하드웨어 의존적인 코드를 획기적으로 줄여주는 Hugging Face Accelerate 라이브러리를 소개한다. 기존 PyTorch 코드와 비교하여 얼마나 생산성이 높아지는지 살펴보자.

The "Before": 순수 PyTorch의 고통

PyTorch만 사용하여 멀티 GPU 환경과 Mixed Precision(FP16) 학습을 구현하려면, 우리는 비즈니스 로직(모델 학습)과 상관없는 코드를 덕지덕지 붙여야 한다.

문제점

Device 관리의 귀찮음
모든 텐서와 모델에 .to(device)를 명시해야 한다.

복잡한 DDP 설정
DistributedDataParallel 래핑, Sampler 설정, 프로세스 그룹 초기화 등 설정이 복잡하다.

AMP(Mixed Precision) 코드
GradScaler, autocast 등을 직접 관리해야 한다.

😣 기존 코드 예시 (Boilerplate의 늪)

import torch
import torch.nn as nn
import torch.optim as optim
from torch.nn.parallel import DistributedDataParallel as DDP
from torch.utils.data.distributed import DistributedSampler

def train():
    # 1. 하드웨어 설정 (복잡함)
    local_rank = int(os.environ["LOCAL_RANK"])
    torch.cuda.set_device(local_rank)
    dist.init_process_group(backend='nccl')
    device = torch.device("cuda", local_rank)

    # 2. 모델을 GPU로 이동 후 DDP 래핑
    model = MyModel().to(device)
    model = DDP(model, device_ids=[local_rank])

    # 3. 데이터 로더에 Sampler 필수
    sampler = DistributedSampler(dataset)
    dataloader = DataLoader(dataset, sampler=sampler)

    optimizer = optim.Adam(model.parameters())

    # 4. Mixed Precision을 위한 Scaler 준비
    scaler = torch.cuda.amp.GradScaler()

    for batch in dataloader:
        optimizer.zero_grad()

        # 5. 데이터도 일일이 device로 이동
        inputs, targets = batch
        inputs, targets = inputs.to(device), targets.to(device)

        # 6. Autocast 컨텍스트 매니저 사용
        with torch.cuda.amp.autocast():
            outputs = model(inputs)
            loss = criterion(outputs, targets)

        # 7. Scaler를 통한 역전파 및 스텝
        scaler.scale(loss).backward()
        scaler.step(optimizer)
        scaler.update()

보시다시피 학습 로직보다 환경 설정 코드가 더 길다. 만약 이 코드를 다시 CPU 환경에서 테스트하려면 if torch.cuda.is_available(): 같은 분기문을 수없이 넣어야 한다.

The "After": Accelerate의 우아함

Accelerate는 하드웨어 설정을 추상화한다. 개발자는 "어디서 실행될지" 고민하지 않고 "무엇을 실행할지"에만 집중하면 된다.

✨ Accelerate 코드 예시

from accelerate import Accelerator

def train():
    # 1. Accelerator 객체 생성
    # fp16, cpu, multi-gpu 등 환경을 알아서 감지
    accelerator = Accelerator(mixed_precision="fp16")

    device = accelerator.device  # device 할당도 알아서

    model = MyModel()
    optimizer = optim.Adam(model.parameters())
    dataloader = DataLoader(dataset)  # Sampler 안 넣어도 됨!

    # 2. Prepare: 마법의 메서드
    # 모델, 옵티마이저, 데이터로더를 현재 하드웨어에 맞게 자동 변환
    model, optimizer, dataloader = accelerator.prepare(
        model, optimizer, dataloader
    )

    for batch in dataloader:
        optimizer.zero_grad()

        # 3. .to(device) 불필요 (자동 처리됨)
        inputs, targets = batch

        outputs = model(inputs)
        loss = criterion(outputs, targets)

        # 4. 역전파: 문법 통일
        accelerator.backward(loss)

        optimizer.step()

무엇이 바뀌었나?

.to(device) 삭제
prepare() 메서드를 통과한 DataLoader는 배치 데이터를 자동으로 올바른 장치(GPU/TPU)에 올려준다.

scaler 삭제
accelerator.backward(loss)가 내부적으로 Mixed Precision scaling을 알아서 처리한다.

단일 코드베이스
위 코드는 수정 없이 CPU, 싱글 GPU, 멀티 GPU, 심지어 TPU에서도 그대로 돌아간다.

실행 방법 (CLI)

코드 내에서 하드웨어를 지정하지 않았기 때문에, 실행 시점에 CLI로 환경을 주입한다.

설정 마법사 실행 (최초 1회)

accelerate config
# 질문: GPU 몇 개 쓸 거야? FP16 쓸 거야? 
# → 답변하면 config 파일 생성됨

학습 실행

accelerate launch train.py

이제 파이썬 스크립트는 accelerate launch가 던져주는 환경 설정에 맞춰 유연하게 동작한다.

실전 사례: 팀 마이그레이션 경험

우리 팀에서 RT-DETR 학습 코드를 Accelerate로 마이그레이션한 경험을 공유한다.

Before: 하드웨어별 코드 분기

# 로컬 개발용
if torch.cuda.is_available():
    device = torch.device("cuda")
    model = model.to(device)
else:
    device = torch.device("cpu")

# 멀티 GPU용 (별도 파일)
model = DDP(model, device_ids=[local_rank])
sampler = DistributedSampler(train_dataset)

로컬에서 테스트하고, 서버에 배포할 때마다 코드를 바꿔야 했다.

After: 통합 코드베이스

from accelerate import Accelerator

accelerator = Accelerator(
    mixed_precision="fp16",
    gradient_accumulation_steps=4
)

model = RTDETRModel()
optimizer = AdamW(model.parameters(), lr=1e-4)
train_loader = DataLoader(train_dataset, batch_size=8)

# 마법의 prepare
model, optimizer, train_loader = accelerator.prepare(
    model, optimizer, train_loader
)

for epoch in range(num_epochs):
    for batch in train_loader:
        with accelerator.accumulate(model):
            outputs = model(batch['images'])
            loss = criterion(outputs, batch['targets'])
            accelerator.backward(loss)
            optimizer.step()
            optimizer.zero_grad()

결과:

  • 로컬(Mac M2): 그대로 실행 ✅
  • 서버(RTX 4060 1장): 그대로 실행 ✅
  • 서버(A100 4장): 그대로 실행 ✅

코드 한 줄도 안 바꿨다.

알아두면 좋은 기능들

Gradient Accumulation

배치 크기를 늘리지 않고 그래디언트만 누적할 수 있다.

accelerator = Accelerator(gradient_accumulation_steps=4)

for batch in train_loader:
    with accelerator.accumulate(model):  # 4번에 1번만 업데이트
        outputs = model(batch)
        loss = criterion(outputs, targets)
        accelerator.backward(loss)
        optimizer.step()
        optimizer.zero_grad()

GPU 메모리 부족할 때 유용하다.

체크포인트 저장/로드

# 저장
accelerator.save_state(output_dir="./checkpoints")

# 불러오기
accelerator.load_state(input_dir="./checkpoints")

DDP 상태까지 알아서 저장해준다.

로깅

# 메인 프로세스에서만 로그 출력
if accelerator.is_main_process:
    print(f"Epoch {epoch}, Loss: {loss.item()}")

# 또는
accelerator.print(f"Epoch {epoch}, Loss: {loss.item()}")
# 이건 자동으로 메인 프로세스에서만 출력됨

멀티 GPU 학습할 때 로그가 중복으로 찍히는 거 방지할 수 있다.

실제 성능 비교

우리 팀에서 RT-DETR 학습할 때 측정한 수치다.

개발 시간:

  • Before: 로컬/서버 코드 분기 때문에 2~3시간 소요
  • After: 통합 코드로 30분 단축

디버깅 시간:

  • Before: DDP 에러 디버깅에 하루 날림
  • After: 에러 없음 (Accelerate가 알아서 처리)

코드 라인 수:

  • Before: 하드웨어 설정 코드 ~100줄
  • After: Accelerator 설정 ~10줄

주의사항

커스텀 학습 루프에서만 사용

Trainer API(Hugging Face Transformers)를 쓰면 Accelerate가 이미 내장돼 있다. 커스텀 학습 루프를 짤 때만 직접 써야 한다.

디버깅 시

가끔 Accelerate 내부에서 에러가 나면 스택 트레이스가 복잡할 수 있다. 그럴 땐 ACCELERATE_DEBUG_MODE=1로 실행하면 자세한 로그를 볼 수 있다.

ACCELERATE_DEBUG_MODE=1 accelerate launch train.py

기존 코드 마이그레이션

한 번에 다 바꾸려고 하지 말고, 단계적으로 마이그레이션하라.

1단계: Accelerator 생성 + prepare만 적용
2단계: .to(device) 제거
3단계: scaler 제거
4단계: DDP 코드 제거

결론: 왜 도입해야 하는가?

개발 팀 리더로서 Accelerate 도입을 추천하는 이유는 단순한 '편리함' 때문만은 아니다.

유지보수성 향상
하드웨어 종속 코드가 사라져 비즈니스 로직(모델링)이 명확해진다.

실험 속도 가속화
로컬(Mac/CPU)에서 짠 코드를 배포(A100/Multi-GPU) 할 때 코드를 수정할 필요가 없다.

실용적인 접근
Hugging Face 생태계의 도구지만, PyTorch의 네이티브 기능을 해치지 않고 얇은 래퍼(Wrapper)로 동작하여 디버깅도 용이하다.

복잡한 torch.distributed 문서와 씨름하는 시간을 줄이고, 모델 성능 최적화에 그 시간을 투자하라.

728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'AI · ML > Computer Vision' 카테고리의 다른 글

YOLO만 쓰던 개발자가 RT-DETR을 선택한 이유  (1) 2026.01.21
YOLO26: 엣지 디바이스를 위한 차세대 객체 탐지 모델  (0) 2026.01.19
'AI · ML/Computer Vision' 카테고리의 다른 글
  • YOLO만 쓰던 개발자가 RT-DETR을 선택한 이유
  • YOLO26: 엣지 디바이스를 위한 차세대 객체 탐지 모델
Kun Woo Kim
Kun Woo Kim
안녕하세요, 김건우입니다! 웹과 앱 개발에 열정적인 전문가로, React, TypeScript, Next.js, Node.js, Express, Flutter 등을 활용한 프로젝트를 다룹니다. 제 블로그에서는 개발 여정, 기술 분석, 실용적 코딩 팁을 공유합니다. 창의적인 솔루션을 실제로 적용하는 과정의 통찰도 나눌 예정이니, 궁금한 점이나 상담은 언제든 환영합니다.
  • Kun Woo Kim
    WhiteMouseDev
    김건우
  • 깃허브
    포트폴리오
    velog
  • 전체
    오늘
    어제
  • 공지사항

    • [인사말] 이제 티스토리에서도 만나요! WhiteMouse⋯
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 분류 전체보기 (143)
      • Frontend Development (64)
      • Backend Development (27)
      • AI · ML (3)
        • Computer Vision (3)
      • Algorithm (35)
        • 백준 (11)
        • 프로그래머스 (18)
        • 알고리즘 (5)
      • Infra (1)
      • 자료구조 (4)
      • Language (6)
        • JavaScript (6)
  • 링크

    • Github
    • Portfolio
    • Velog
  • 인기 글

  • 태그

    Object Detection
    AI
    모델비교
    tailwindcss
    frontend development
    rt-detr
    YOLO
    딥러닝
    transformer
    CNN
    컴퓨터비전
    객체탐지
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Kun Woo Kim
PyTorch 하드웨어 의존성 제거하기: Hugging Face Accelerate로 갈아타야 하는 이유
상단으로

티스토리툴바