이미지 로딩 중...
AI Generated
2025. 11. 17. · 4 Views
GPU 환경 설정 완벽 가이드
Colab, Kaggle, 로컬 서버에서 GPU를 활용한 딥러닝 환경을 구축하는 방법을 초급자도 쉽게 따라할 수 있도록 설명합니다. 각 플랫폼의 특징과 실무 활용법을 배워보세요.
목차
- Google Colab GPU 기본 설정 - 무료로 GPU 시작하기
- Colab에서 Google Drive 연동하기 - 데이터 영구 보관
- Kaggle GPU 환경 설정하기 - 주 30시간 무료 GPU
- 로컬 서버 CUDA 설치하기 - 본격적인 GPU 환경 구축
- CUDA 버전 관리 및 호환성 - 버전 충돌 해결하기
- GPU 메모리 관리 및 최적화 - Out of Memory 에러 해결
- 멀티 GPU 활용하기 - DataParallel과 DistributedDataParallel
- GPU 모니터링 및 벤치마킹 - 성능 최적화
- 클라우드 GPU 비용 최적화 - Spot 인스턴스와 자동 종료
- Docker로 GPU 환경 패키징하기 - 재현 가능한 환경
1. Google Colab GPU 기본 설정 - 무료로 GPU 시작하기
시작하며
여러분이 딥러닝을 처음 시작할 때 가장 큰 장벽은 무엇일까요? 바로 '내 컴퓨터에는 GPU가 없는데 어떻게 하지?'라는 고민입니다.
수백만원짜리 GPU를 구매할 수도 없고, 복잡한 드라이버 설치는 더욱 막막합니다. 많은 입문자들이 이 단계에서 포기하곤 합니다.
GPU 없이는 간단한 모델 훈련조차 몇 시간씩 걸리고, 실습은커녕 기다리다가 지쳐버리죠. 바로 이럴 때 필요한 것이 Google Colab입니다.
클릭 몇 번으로 무료 GPU를 사용할 수 있고, 브라우저만 있으면 어디서든 딥러닝 실습이 가능합니다.
개요
간단히 말해서, Google Colab은 구글이 무료로 제공하는 클라우드 기반 Jupyter 노트북 환경입니다. 실제 개발 현장에서 프로토타입을 빠르게 만들거나, 학습 자료를 공유하거나, 간단한 모델을 훈련할 때 매우 유용합니다.
예를 들어, 팀원들과 코드를 공유하면서 동시에 실행 결과까지 보여줄 수 있습니다. 기존에는 로컬 환경에 CUDA, cuDNN을 설치하고 드라이버를 맞춰야 했다면, 이제는 런타임 유형만 변경하면 즉시 GPU를 사용할 수 있습니다.
핵심 특징은 첫째, 완전 무료로 Tesla T4 GPU를 사용할 수 있고, 둘째, Google Drive와 연동되어 데이터 관리가 편리하며, 셋째, 대부분의 딥러닝 라이브러리가 사전 설치되어 있다는 점입니다. 이러한 특징들이 초급자의 진입 장벽을 크게 낮춰줍니다.
코드 예제
# GPU 사용 가능 여부 확인
import torch
# CUDA(GPU) 사용 가능 확인
if torch.cuda.is_available():
device = torch.device("cuda")
print(f"GPU 사용 가능: {torch.cuda.get_device_name(0)}")
print(f"GPU 메모리: {torch.cuda.get_device_properties(0).total_memory / 1e9:.2f} GB")
else:
device = torch.device("cpu")
print("GPU를 사용할 수 없습니다. CPU 모드로 실행됩니다.")
# 간단한 텐서를 GPU로 이동
x = torch.rand(3, 3).to(device)
print(f"텐서가 실행되는 장치: {x.device}")
설명
이것이 하는 일: 여러분의 코드가 실제로 GPU에서 실행되고 있는지 확인하고, GPU의 사양을 파악하는 과정입니다. 첫 번째로, torch.cuda.is_available()은 현재 환경에서 CUDA를 지원하는 GPU가 있는지 확인합니다.
만약 Colab에서 런타임 유형을 GPU로 변경하지 않았다면 False를 반환하고 CPU 모드로 실행됩니다. 이 확인을 하지 않으면 나중에 "왜 훈련이 이렇게 느리지?"라고 고민하게 됩니다.
그 다음으로, torch.cuda.get_device_name(0)과 get_device_properties()가 실행되면서 실제 GPU의 이름과 메모리 용량을 알려줍니다. Colab 무료 버전은 보통 Tesla T4(16GB) 또는 K80(12GB)를 제공하는데, 이 정보를 알아야 배치 사이즈를 적절히 조절할 수 있습니다.
마지막으로, .to(device)가 텐서를 GPU 메모리로 이동시켜 최종적으로 GPU에서 연산이 가능한 상태를 만들어냅니다. x.device를 출력하면 'cuda:0'이라고 표시되는데, 이는 첫 번째 GPU에 텐서가 올라갔다는 의미입니다.
여러분이 이 코드를 사용하면 GPU가 제대로 활성화되었는지 즉시 확인할 수 있고, 사용 가능한 메모리를 파악하여 Out of Memory 에러를 예방할 수 있습니다. 실무에서는 모델 크기와 배치 사이즈를 결정할 때 이 정보가 필수적입니다.
실전 팁
💡 Colab 무료 버전은 최대 12시간 연속 사용 제한이 있으니, 긴 훈련은 중간 체크포인트를 저장하세요
💡 GPU 메모리 부족 에러가 나면 배치 사이즈를 절반으로 줄여보세요 (예: 32 → 16)
💡 torch.cuda.empty_cache()로 사용하지 않는 GPU 메모리를 정리할 수 있습니다
💡 런타임 > 모두 실행 전에 런타임 유형이 GPU인지 꼭 확인하세요
💡 Google Drive 마운트 시 '/content/drive/MyDrive' 경로를 사용하면 데이터를 영구 저장할 수 있습니다
2. Colab에서 Google Drive 연동하기 - 데이터 영구 보관
시작하며
여러분이 Colab에서 몇 시간 동안 모델을 훈련시켰는데, 브라우저를 껐다가 다시 켜니 모든 데이터가 사라진 경험 있나요? 마치 모래성을 쌓다가 파도에 쓸려가는 느낌입니다.
이런 문제는 Colab의 세션이 종료되면 /content 폴더의 모든 파일이 삭제되기 때문에 발생합니다. 훈련된 모델, 다운로드한 데이터셋, 실험 결과가 모두 날아가버리죠.
바로 이럴 때 필요한 것이 Google Drive 마운트입니다. 여러분의 Drive를 Colab에 연결하면 파일이 영구적으로 저장되고, 어떤 세션에서든 접근할 수 있습니다.
개요
간단히 말해서, Drive 마운트는 여러분의 Google Drive를 Colab의 파일 시스템에 연결하는 작업입니다. 실무에서 데이터셋을 재사용하거나, 모델 체크포인트를 저장하거나, 팀원과 결과를 공유할 때 매우 유용합니다.
예를 들어, 오늘 훈련한 모델을 내일 다시 불러와서 이어서 훈련하는 경우에 필수적입니다. 기존에는 매번 데이터를 다시 업로드하거나 wget으로 다운로드해야 했다면, 이제는 Drive에 한 번 저장해두고 계속 재사용할 수 있습니다.
핵심 특징은 첫째, 15GB 무료 저장공간을 사용할 수 있고, 둘째, 여러 노트북에서 동일한 데이터에 접근할 수 있으며, 셋째, 파일 변경사항이 자동으로 동기화된다는 점입니다. 이러한 특징들이 반복 실험과 협업을 훨씬 쉽게 만들어줍니다.
코드 예제
# Google Drive 마운트
from google.colab import drive
# Drive 연결 (인증 필요)
drive.mount('/content/drive')
# 작업 디렉토리 설정
import os
work_dir = '/content/drive/MyDrive/DeepLearning_Projects'
# 디렉토리가 없으면 생성
os.makedirs(work_dir, exist_ok=True)
os.chdir(work_dir)
print(f"현재 작업 디렉토리: {os.getcwd()}")
print(f"디렉토리 내용: {os.listdir('.')}")
설명
이것이 하는 일: Google Drive를 Colab의 파일 시스템에 마운트하여 영구 저장소로 사용할 수 있게 만드는 과정입니다. 첫 번째로, drive.mount('/content/drive')를 실행하면 구글 계정 인증 창이 나타납니다.
여기서 권한을 허용하면 여러분의 Drive 전체가 /content/drive 경로에 연결됩니다. 이 과정은 세션당 한 번만 수행하면 됩니다.
그 다음으로, 작업 디렉토리 경로를 설정합니다. /content/drive/MyDrive는 여러분이 웹에서 보는 "내 드라이브"와 정확히 같은 공간입니다.
여기에 프로젝트별 폴더를 만들어서 정리하면 나중에 찾기 쉽습니다. 마지막으로, os.makedirs()가 폴더를 생성하고 os.chdir()이 작업 디렉토리를 변경하여 최종적으로 모든 파일 작업이 Drive에 저장되도록 만들어냅니다.
exist_ok=True 옵션은 폴더가 이미 있어도 에러가 나지 않게 합니다. 여러분이 이 코드를 사용하면 훈련 중간에 세션이 끊겨도 체크포인트가 보존되고, 대용량 데이터셋을 한 번만 업로드하면 계속 재사용할 수 있습니다.
실무에서는 실험 로그, 모델 가중치, 전처리된 데이터를 모두 이 경로에 저장합니다.
실전 팁
💡 첫 실행 시 나오는 인증 링크를 클릭하고 코드를 복사해서 붙여넣으세요
💡 대용량 파일은 Drive에 업로드하는 것보다 Colab에서 직접 다운로드하는 게 더 빠릅니다
💡 torch.save()로 모델을 저장할 때 Drive 경로를 지정하면 자동으로 보존됩니다
💡 Drive 용량이 부족하면 학습된 모델만 저장하고 중간 체크포인트는 삭제하세요
💡 shutil.copy()를 사용해 중요한 결과물만 선택적으로 Drive에 백업할 수 있습니다
3. Kaggle GPU 환경 설정하기 - 주 30시간 무료 GPU
시작하며
여러분이 Colab으로 작업하다가 "12시간 제한 때문에 훈련이 중단되었습니다"라는 메시지를 보신 적 있나요? 긴 훈련이 필요한 프로젝트는 어떻게 해야 할지 막막합니다.
이런 문제는 Colab의 시간 제한과 세션 불안정성 때문에 발생합니다. 특히 대회나 실전 프로젝트에서는 안정적인 GPU 환경이 필수입니다.
바로 이럴 때 필요한 것이 Kaggle Notebooks입니다. 주당 30시간의 GPU 할당량을 제공하고, 세션당 최대 12시간(GPU 사용 시 9시간)까지 안정적으로 사용할 수 있습니다.
개요
간단히 말해서, Kaggle은 데이터 과학 경진대회 플랫폼이면서 동시에 강력한 무료 GPU 환경을 제공하는 서비스입니다. 실무에서 대회 참가, 공개 데이터셋 활용, 커뮤니티 솔루션 학습 등에 매우 유용합니다.
예를 들어, ImageNet 같은 대용량 데이터셋이 이미 Kaggle에 업로드되어 있어서 즉시 사용할 수 있습니다. 기존에는 데이터셋을 직접 다운로드하고 전처리해야 했다면, 이제는 Kaggle Datasets를 클릭 한 번으로 노트북에 추가할 수 있습니다.
핵심 특징은 첫째, 주당 30시간의 GPU 할당량이 있어서 계획적으로 사용 가능하고, 둘째, P100 또는 T4 GPU를 선택할 수 있으며, 셋째, 수많은 공개 데이터셋과 노트북에 접근할 수 있다는 점입니다. 이러한 특징들이 체계적인 학습과 실전 경험을 가능하게 합니다.
코드 예제
# Kaggle에서 GPU 정보 확인
import torch
import subprocess
# GPU 사양 확인
if torch.cuda.is_available():
gpu_name = torch.cuda.get_device_name(0)
gpu_memory = torch.cuda.get_device_properties(0).total_memory / 1e9
print(f"GPU: {gpu_name}")
print(f"메모리: {gpu_memory:.2f} GB")
# nvidia-smi로 상세 정보 확인
result = subprocess.run(['nvidia-smi'], capture_output=True, text=True)
print("\n=== GPU 상세 정보 ===")
print(result.stdout)
else:
print("GPU 가속기를 활성화하세요: Settings > Accelerator > GPU")
설명
이것이 하는 일: Kaggle 환경에서 할당된 GPU의 종류와 현재 상태를 확인하여 최적의 설정을 파악하는 과정입니다. 첫 번째로, torch.cuda를 통해 GPU 기본 정보를 파악합니다.
Kaggle은 P100(Tesla P100, 16GB)과 T4(Tesla T4, 16GB) 중 하나를 제공하는데, P100이 훈련 속도가 약간 더 빠릅니다. 하지만 랜덤으로 할당되므로 선택할 수는 없습니다.
그 다음으로, subprocess를 사용해 nvidia-smi 명령어를 실행합니다. 이 명령어는 GPU 드라이버 버전, CUDA 버전, 현재 메모리 사용량, GPU 온도 등 상세한 정보를 보여줍니다.
특히 메모리 사용량을 확인하면 배치 사이즈를 얼마나 늘릴 수 있는지 판단할 수 있습니다. 마지막으로, 이 정보들을 종합하여 최종적으로 여러분의 모델 설정을 최적화할 수 있는 근거를 제공합니다.
예를 들어 P100이 할당되었다면 혼합 정밀도 훈련(mixed precision)을 사용해 속도를 더 높일 수 있습니다. 여러분이 이 코드를 사용하면 할당된 GPU를 최대한 활용할 수 있고, 메모리 부족 에러를 미리 예방할 수 있습니다.
실무에서는 대회 제출 전에 GPU 사양을 확인하여 최적의 하이퍼파라미터를 찾는 데 활용합니다.
실전 팁
💡 Kaggle은 인터넷 연결이 제한되어 있으니 외부 데이터는 Kaggle Datasets로 먼저 업로드하세요
💡 주당 30시간 할당량은 매주 토요일 오전(한국 시간)에 초기화됩니다
💡 Save Version 버튼으로 노트북을 저장하면 출력 결과도 함께 보존됩니다
💡 GPU 할당량이 부족하면 CPU로 전처리하고 GPU는 훈련에만 사용하세요
💡 Public 노트북으로 공유하면 커뮤니티 피드백을 받을 수 있어 학습에 도움됩니다
4. 로컬 서버 CUDA 설치하기 - 본격적인 GPU 환경 구축
시작하며
여러분이 클라우드 플랫폼의 시간 제한과 할당량에 지치셨나요? 대규모 프로젝트나 회사 업무에서는 제약 없는 환경이 필요합니다.
이런 문제는 무료 플랫폼의 공유 리소스 특성상 피할 수 없습니다. 중요한 실험이 중간에 중단되거나, 데이터 보안이 걱정되거나, 커스텀 라이브러리를 설치할 수 없는 경우도 있죠.
바로 이럴 때 필요한 것이 로컬 GPU 서버 구축입니다. 여러분의 컴퓨터나 서버에 직접 CUDA를 설치하면 시간 제한 없이, 완전한 제어권을 가지고 작업할 수 있습니다.
개요
간단히 말해서, CUDA는 NVIDIA GPU에서 병렬 연산을 수행할 수 있게 해주는 소프트웨어 플랫폼입니다. 실무에서 자체 서버 운영, 대규모 모델 훈련, 프로덕션 배포 환경 구축 등에 필수적입니다.
예를 들어, 회사에서 자체 AI 서비스를 개발할 때는 반드시 로컬 GPU 환경이 필요합니다. 기존에는 클라우드에서 제한적으로 작업했다면, 이제는 여러분의 하드웨어를 최대한 활용하여 24시간 중단 없이 훈련할 수 있습니다.
핵심 특징은 첫째, 시간 제한이 전혀 없고, 둘째, 모든 소프트웨어를 자유롭게 설치할 수 있으며, 셋째, 데이터가 외부로 나가지 않아 보안에 유리하다는 점입니다. 이러한 특징들이 전문적인 개발 환경을 만들어줍니다.
코드 예제
# CUDA 설치 확인 스크립트
import torch
import sys
def check_cuda_installation():
print("=== PyTorch CUDA 설치 확인 ===\n")
# PyTorch 버전
print(f"PyTorch 버전: {torch.__version__}")
# CUDA 사용 가능 여부
cuda_available = torch.cuda.is_available()
print(f"CUDA 사용 가능: {cuda_available}")
if cuda_available:
# CUDA 버전
print(f"CUDA 버전: {torch.version.cuda}")
# GPU 개수 및 정보
gpu_count = torch.cuda.device_count()
print(f"GPU 개수: {gpu_count}")
for i in range(gpu_count):
print(f"\n--- GPU {i} ---")
print(f"이름: {torch.cuda.get_device_name(i)}")
props = torch.cuda.get_device_properties(i)
print(f"메모리: {props.total_memory / 1e9:.2f} GB")
print(f"연산 능력: {props.major}.{props.minor}")
# cuDNN 버전
print(f"\ncuDNN 버전: {torch.backends.cudnn.version()}")
print(f"cuDNN 활성화: {torch.backends.cudnn.enabled}")
else:
print("\n⚠️ CUDA를 사용할 수 없습니다.")
print("1. NVIDIA GPU가 설치되어 있는지 확인하세요")
print("2. CUDA Toolkit이 설치되어 있는지 확인하세요")
print("3. PyTorch를 CUDA 버전으로 설치했는지 확인하세요")
if __name__ == "__main__":
check_cuda_installation()
설명
이것이 하는 일: 로컬 환경에서 CUDA가 올바르게 설치되었는지 체계적으로 확인하고, 문제가 있다면 해결 방향을 제시하는 종합 진단 스크립트입니다. 첫 번째로, torch.cuda.is_available()로 가장 중요한 질문에 답합니다.
"내 PyTorch가 GPU를 인식하는가?" 만약 False가 나온다면 CUDA Toolkit 설치, PyTorch 재설치, 드라이버 업데이트 중 하나가 필요합니다. 그 다음으로, CUDA와 cuDNN 버전을 확인합니다.
PyTorch는 특정 CUDA 버전과 함께 빌드되므로, 시스템에 설치된 CUDA 버전이 PyTorch와 호환되는지 확인해야 합니다. 예를 들어 PyTorch 2.0은 CUDA 11.8 또는 12.1을 지원합니다.
마지막으로, 여러 GPU가 있는 경우 각각의 사양을 출력하여 최종적으로 데이터 병렬 처리(DataParallel)를 사용할지 결정할 수 있게 합니다. 연산 능력(Compute Capability)이 7.0 이상이면 Tensor Core를 사용한 혼합 정밀도 훈련이 가능합니다.
여러분이 이 코드를 사용하면 환경 설정 문제를 빠르게 진단할 수 있고, GPU 하드웨어의 성능을 정확히 파악하여 최적의 설정을 선택할 수 있습니다. 실무에서는 새 서버 세팅 시 첫 번째로 실행하는 스크립트입니다.
실전 팁
💡 NVIDIA 드라이버는 nvidia.com에서, CUDA Toolkit은 developer.nvidia.com에서 다운로드하세요
💡 PyTorch 설치 시 pytorch.org에서 본인의 CUDA 버전에 맞는 명령어를 복사하세요
💡 pip install torch torchvision torchaudio --index-url로 CUDA 버전을 명시하면 실수를 방지할 수 있습니다
💡 conda 환경을 사용하면 CUDA 라이브러리가 자동으로 관리되어 충돌이 적습니다
💡 nvidia-smi 명령어로 드라이버가 정상적으로 설치되었는지 먼저 확인하세요
5. CUDA 버전 관리 및 호환성 - 버전 충돌 해결하기
시작하며
여러분이 새로운 딥러닝 라이브러리를 설치했는데 "CUDA version mismatch" 에러가 나타난 경험 있나요? 어떤 버전을 설치해야 할지 혼란스럽습니다.
이런 문제는 PyTorch, TensorFlow, CUDA Toolkit, GPU 드라이버가 각각 다른 버전 요구사항을 가지기 때문에 발생합니다. 특히 여러 프로젝트를 동시에 진행하면 버전 충돌이 빈번합니다.
바로 이럴 때 필요한 것이 체계적인 CUDA 버전 관리입니다. 가상 환경을 활용하고 호환성 테이블을 확인하면 안정적인 개발 환경을 유지할 수 있습니다.
개요
간단히 말해서, CUDA 버전 관리는 프로젝트별로 필요한 CUDA와 딥러닝 프레임워크 버전을 격리하여 관리하는 기법입니다. 실무에서 레거시 프로젝트 유지보수, 새 프레임워크 실험, 다양한 라이브러리 동시 사용 등에 필수적입니다.
예를 들어, 한 프로젝트는 TensorFlow 1.15(CUDA 10.0), 다른 프로젝트는 PyTorch 2.0(CUDA 11.8)을 사용하는 경우가 많습니다. 기존에는 시스템 전체에 하나의 CUDA 버전만 설치했다면, 이제는 conda나 docker를 사용해서 프로젝트마다 독립적인 환경을 만들 수 있습니다.
핵심 특징은 첫째, 환경 간 충돌을 완전히 방지하고, 둘째, 프로젝트 이동 시 빠르게 환경을 전환할 수 있으며, 셋째, 팀원과 동일한 환경을 공유할 수 있다는 점입니다. 이러한 특징들이 협업과 재현성을 보장합니다.
코드 예제
# CUDA 버전 호환성 확인 및 환경 설정
import subprocess
import sys
def get_cuda_version():
"""시스템 CUDA 버전 확인"""
try:
result = subprocess.run(['nvcc', '--version'],
capture_output=True, text=True)
output = result.stdout
# CUDA 버전 파싱
for line in output.split('\n'):
if 'release' in line:
version = line.split('release')[1].split(',')[0].strip()
return version
except FileNotFoundError:
return "CUDA Toolkit이 설치되지 않음"
def get_pytorch_cuda_version():
"""PyTorch가 빌드된 CUDA 버전 확인"""
import torch
if torch.cuda.is_available():
return torch.version.cuda
else:
return "CUDA 지원 없음"
def check_compatibility():
"""버전 호환성 체크"""
print("=== CUDA 버전 호환성 확인 ===\n")
system_cuda = get_cuda_version()
pytorch_cuda = get_pytorch_cuda_version()
print(f"시스템 CUDA Toolkit: {system_cuda}")
print(f"PyTorch CUDA 버전: {pytorch_cuda}")
if pytorch_cuda != "CUDA 지원 없음":
# 메이저 버전 비교
if system_cuda.split('.')[0] == pytorch_cuda.split('.')[0]:
print("✅ 호환성 양호: 메이저 버전이 일치합니다")
else:
print("⚠️ 경고: CUDA 버전 불일치로 문제가 발생할 수 있습니다")
print(f"해결 방법: pytorch.org에서 CUDA {system_cuda}용 PyTorch를 재설치하세요")
# 권장 설치 명령어 출력
print(f"\n권장 설치 명령어 (CUDA {system_cuda}):")
major_version = system_cuda.split('.')[0] if system_cuda != "CUDA Toolkit이 설치되지 않음" else "11.8"
print(f"pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu{major_version.replace('.', '')}")
if __name__ == "__main__":
check_compatibility()
설명
이것이 하는 일: 시스템에 설치된 CUDA 버전과 PyTorch가 기대하는 CUDA 버전을 비교하여 호환성 문제를 사전에 발견하고 해결 방법을 제시합니다. 첫 번째로, nvcc --version 명령어를 실행하여 실제 시스템에 설치된 CUDA Toolkit 버전을 확인합니다.
nvcc는 NVIDIA CUDA Compiler로, CUDA Toolkit과 함께 설치됩니다. 만약 "command not found" 에러가 나면 CUDA Toolkit이 설치되지 않았거나 PATH 설정이 잘못된 것입니다.
그 다음으로, torch.version.cuda를 통해 현재 설치된 PyTorch가 어느 CUDA 버전과 함께 빌드되었는지 확인합니다. 예를 들어 "11.8"이 나오면 이 PyTorch는 CUDA 11.8 라이브러리를 기대합니다.
중요한 것은 마이너 버전이 정확히 일치할 필요는 없고, 메이저 버전(11, 12 등)만 맞으면 대부분 작동한다는 점입니다. 마지막으로, 두 버전을 비교하여 불일치가 발견되면 올바른 PyTorch 설치 명령어를 생성해줍니다.
최종적으로 여러분은 복사-붙여넣기만 하면 호환되는 버전을 설치할 수 있습니다. 여러분이 이 코드를 사용하면 "왜 GPU가 인식되지 않지?" 같은 막연한 고민 대신 구체적인 해결책을 얻을 수 있고, 새 환경 구축 시 시행착오를 크게 줄일 수 있습니다.
실무에서는 CI/CD 파이프라인에 이런 체크를 포함시켜 배포 전 호환성을 검증합니다.
실전 팁
💡 conda create -n myenv python=3.9 cudatoolkit=11.8로 환경별로 CUDA 버전을 지정할 수 있습니다
💡 PyTorch는 CUDA 11.8과 12.1을 주로 지원하므로 이 버전들을 사용하는 것이 안전합니다
💡 Docker를 사용하면 CUDA 버전을 완전히 격리할 수 있어 가장 확실합니다
💡 export LD_LIBRARY_PATH로 특정 CUDA 라이브러리 경로를 우선순위로 지정할 수 있습니다
💡 nvidia/cuda 공식 Docker 이미지를 베이스로 사용하면 환경 설정이 간편합니다
6. GPU 메모리 관리 및 최적화 - Out of Memory 에러 해결
시작하며
여러분이 배치 사이즈를 늘리거나 큰 모델을 로드하면 "CUDA out of memory" 에러를 자주 만나시죠? 16GB GPU인데도 작은 모델조차 훈련이 안 될 때가 있습니다.
이런 문제는 GPU 메모리가 모델 가중치, 중간 활성화 값, 그래디언트, 옵티마이저 상태 등 여러 요소를 동시에 저장해야 하기 때문에 발생합니다. 특히 역전파 과정에서 메모리 사용량이 급증합니다.
바로 이럴 때 필요한 것이 GPU 메모리 관리 기법입니다. 그래디언트 체크포인팅, 혼합 정밀도 훈련, 배치 크기 조절 등을 통해 제한된 메모리를 효율적으로 사용할 수 있습니다.
개요
간단히 말해서, GPU 메모리 관리는 제한된 GPU 메모리 내에서 최대한 큰 모델을 훈련하거나 배치 크기를 늘리는 최적화 기법들입니다. 실무에서 대규모 언어 모델 파인튜닝, 고해상도 이미지 처리, 배치 크기 증가를 통한 성능 향상 등에 필수적입니다.
예를 들어, BERT 모델을 파인튜닝할 때 16GB GPU로는 배치 4도 어려운데, 최적화하면 배치 16까지 가능합니다. 기존에는 메모리 부족 시 더 작은 모델을 사용하거나 더 비싼 GPU를 구매해야 했다면, 이제는 다양한 기법으로 현재 하드웨어를 최대한 활용할 수 있습니다.
핵심 특징은 첫째, 메모리 사용량을 실시간으로 모니터링하고, 둘째, 혼합 정밀도로 메모리를 절반으로 줄이며, 셋째, 그래디언트 누적으로 작은 배치를 큰 배치처럼 사용할 수 있다는 점입니다. 이러한 특징들이 제한된 리소스로 최대 성능을 이끌어냅니다.
코드 예제
import torch
import torch.nn as nn
from torch.cuda.amp import autocast, GradScaler
class GPUMemoryMonitor:
"""GPU 메모리 사용량 모니터링"""
@staticmethod
def print_memory_stats():
if torch.cuda.is_available():
allocated = torch.cuda.memory_allocated() / 1e9
reserved = torch.cuda.memory_reserved() / 1e9
max_allocated = torch.cuda.max_memory_allocated() / 1e9
print(f"할당된 메모리: {allocated:.2f} GB")
print(f"예약된 메모리: {reserved:.2f} GB")
print(f"최대 할당 메모리: {max_allocated:.2f} GB")
@staticmethod
def clear_cache():
"""사용하지 않는 캐시 정리"""
torch.cuda.empty_cache()
print("GPU 캐시 정리 완료")
# 혼합 정밀도 훈련 예제
def train_with_amp(model, data_loader, optimizer, device):
"""혼합 정밀도(AMP)를 사용한 메모리 절약 훈련"""
scaler = GradScaler() # 그래디언트 스케일러
model.train()
for batch_idx, (data, target) in enumerate(data_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
# autocast 컨텍스트에서 FP16 연산
with autocast():
output = model(data)
loss = nn.functional.cross_entropy(output, target)
# 스케일된 그래디언트로 역전파
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
if batch_idx % 10 == 0:
print(f"Batch {batch_idx}, Loss: {loss.item():.4f}")
GPUMemoryMonitor.print_memory_stats()
# 그래디언트 누적 예제
def train_with_gradient_accumulation(model, data_loader, optimizer,
device, accumulation_steps=4):
"""작은 배치를 누적하여 큰 배치 효과"""
model.train()
optimizer.zero_grad()
for batch_idx, (data, target) in enumerate(data_loader):
data, target = data.to(device), target.to(device)
output = model(data)
loss = nn.functional.cross_entropy(output, target)
# 누적 스텝 수로 나누어 평균화
loss = loss / accumulation_steps
loss.backward()
# 누적이 완료되면 옵티마이저 스텝 실행
if (batch_idx + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()
print(f"Accumulated {accumulation_steps} batches, Loss: {loss.item() * accumulation_steps:.4f}")
설명
이것이 하는 일: GPU 메모리를 효율적으로 사용하기 위한 두 가지 핵심 기법(혼합 정밀도와 그래디언트 누적)을 실전에서 바로 활용할 수 있는 형태로 구현한 코드입니다. 첫 번째로, GPUMemoryMonitor 클래스는 현재 메모리 사용 현황을 실시간으로 추적합니다.
memory_allocated()는 실제로 텐서가 사용 중인 메모리, memory_reserved()는 PyTorch가 미리 확보해둔 메모리입니다. 이 차이를 이해하면 "왜 모델을 삭제했는데도 메모리가 안 줄어들지?"라는 의문이 해결됩니다.
그 다음으로, autocast 컨텍스트는 연산을 자동으로 FP16(16비트 부동소수점)으로 변환하여 메모리 사용량을 거의 절반으로 줄입니다. 동시에 GradScaler는 작은 그래디언트가 언더플로우되는 것을 방지합니다.
이 조합으로 메모리는 절약하면서 정확도 손실은 최소화합니다. 마지막으로, 그래디언트 누적 기법은 배치 4를 4번 실행하여 배치 16의 효과를 냅니다.
최종적으로 GPU 메모리가 부족해도 대용량 배치의 안정적인 훈련 효과를 얻을 수 있습니다. 손실을 accumulation_steps로 나누는 것이 핵심입니다.
여러분이 이 코드를 사용하면 동일한 GPU로 2배 큰 모델을 훈련하거나 배치 크기를 2배 늘릴 수 있고, Out of Memory 에러를 대부분 해결할 수 있습니다. 실무에서는 대규모 모델 파인튜닝 시 AMP와 그래디언트 누적을 기본으로 사용합니다.
실전 팁
💡 Volta 아키텍처 이후 GPU(V100, T4, A100 등)는 Tensor Core로 AMP 성능이 2-3배 빨라집니다
💡 배치 정규화(BatchNorm) 사용 시 그래디언트 누적은 통계 추정이 부정확해질 수 있으니 주의하세요
💡 torch.cuda.max_memory_allocated()를 주기적으로 리셋하면 메모리 피크를 정확히 측정할 수 있습니다
💡 PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128 환경변수로 메모리 단편화를 줄일 수 있습니다
💡 del 변수 후 torch.cuda.empty_cache()를 호출해야 메모리가 실제로 해제됩니다
7. 멀티 GPU 활용하기 - DataParallel과 DistributedDataParallel
시작하며
여러분이 GPU 2개 이상을 가지고 있는데 하나만 사용하고 있다면 엄청난 자원 낭비입니다. 훈련 시간을 절반으로 줄일 수 있는 기회를 놓치고 있는 것이죠.
이런 문제는 기본 PyTorch 코드가 단일 GPU만 사용하도록 설계되어 있기 때문에 발생합니다. 여러 GPU를 활용하려면 데이터 병렬 처리를 명시적으로 구현해야 합니다.
바로 이럴 때 필요한 것이 DataParallel 또는 DistributedDataParallel입니다. 모델을 여러 GPU에 복제하고 배치를 분산시켜 훈련 속도를 GPU 개수에 비례하여 향상시킬 수 있습니다.
개요
간단히 말해서, 멀티 GPU 훈련은 하나의 모델을 여러 GPU에 복제하고 데이터 배치를 나누어 동시에 처리하는 기법입니다. 실무에서 대규모 데이터셋 훈련, 하이퍼파라미터 튜닝 가속화, 프로덕션 환경 처리량 증대 등에 필수적입니다.
예를 들어, ImageNet 훈련을 단일 GPU로는 며칠 걸리지만, 8개 GPU로는 몇 시간으로 단축됩니다. 기존에는 하나의 GPU로 순차적으로 처리했다면, 이제는 배치를 GPU 개수로 나누어 병렬 처리하여 시간을 대폭 절약할 수 있습니다.
핵심 특징은 첫째, 코드 수정이 최소한으로 필요하고, 둘째, 거의 선형적으로 속도가 증가하며(2 GPU = 약 1.8배), 셋째, 모델 크기 제약은 여전히 단일 GPU 메모리에 의존한다는 점입니다. 이러한 특징들이 훈련 시간 단축의 핵심입니다.
코드 예제
import torch
import torch.nn as nn
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP
# 방법 1: DataParallel (간단하지만 느림)
def setup_dataparallel(model):
"""간단한 멀티 GPU 설정 (권장하지 않음)"""
if torch.cuda.device_count() > 1:
print(f"DataParallel 사용: {torch.cuda.device_count()}개 GPU")
model = nn.DataParallel(model)
return model.cuda()
# 방법 2: DistributedDataParallel (권장)
def setup_ddp(rank, world_size):
"""DDP 초기화 (더 빠르고 효율적)"""
import os
os.environ['MASTER_ADDR'] = 'localhost'
os.environ['MASTER_PORT'] = '12355'
# 프로세스 그룹 초기화
dist.init_process_group("nccl", rank=rank, world_size=world_size)
def cleanup_ddp():
"""DDP 종료"""
dist.destroy_process_group()
def train_ddp(rank, world_size, model, train_loader):
"""DDP를 사용한 훈련"""
print(f"GPU {rank} 시작")
# DDP 초기화
setup_ddp(rank, world_size)
# 모델을 해당 GPU로 이동
model = model.to(rank)
ddp_model = DDP(model, device_ids=[rank])
# 옵티마이저
optimizer = torch.optim.Adam(ddp_model.parameters(), lr=0.001)
# 훈련 루프
ddp_model.train()
for epoch in range(10):
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(rank), target.to(rank)
optimizer.zero_grad()
output = ddp_model(data)
loss = nn.functional.cross_entropy(output, target)
loss.backward()
optimizer.step()
if rank == 0 and batch_idx % 10 == 0: # 메인 프로세스만 출력
print(f"Epoch {epoch}, Batch {batch_idx}, Loss: {loss.item():.4f}")
cleanup_ddp()
# 멀티 프로세스 실행
def run_multi_gpu_training(model, train_loader):
"""멀티 GPU 훈련 실행"""
import torch.multiprocessing as mp
world_size = torch.cuda.device_count()
print(f"{world_size}개 GPU로 DDP 훈련 시작")
mp.spawn(train_ddp,
args=(world_size, model, train_loader),
nprocs=world_size,
join=True)
설명
이것이 하는 일: 여러 GPU를 효율적으로 활용하여 훈련 속도를 극대화하는 두 가지 방법을 비교하고, 실전에서 권장되는 DDP 구현 방법을 제시합니다. 첫 번째로, DataParallel은 nn.DataParallel로 모델을 감싸기만 하면 되는 가장 간단한 방법입니다.
하지만 Python GIL(Global Interpreter Lock) 때문에 멀티스레딩 오버헤드가 크고, GPU 0에 병목이 발생합니다. 따라서 2개 GPU로는 1.3배 정도만 빨라지는 비효율이 있습니다.
그 다음으로, DistributedDataParallel은 각 GPU마다 별도의 프로세스를 실행하여 GIL 문제를 완전히 회피합니다. dist.init_process_group()으로 프로세스 간 통신을 초기화하고, NCCL(NVIDIA Collective Communication Library) 백엔드로 GPU 간 데이터 교환을 최적화합니다.
이 방식은 거의 선형적인 속도 향상을 보입니다. 마지막으로, torch.multiprocessing.spawn이 GPU 개수만큼 프로세스를 생성하고 각각 train_ddp 함수를 실행하여 최종적으로 완전한 병렬 훈련을 구현합니다.
rank는 각 프로세스의 ID(0, 1, 2...)이고, world_size는 전체 GPU 개수입니다. 여러분이 이 코드를 사용하면 GPU 2개로 약 1.8배, 4개로 약 3.5배 훈련 속도를 향상시킬 수 있고, 대규모 실험을 훨씬 빠르게 수행할 수 있습니다.
실무에서는 클라우드 멀티 GPU 인스턴스나 회사 서버에서 DDP를 기본으로 사용합니다.
실전 팁
💡 DistributedSampler를 DataLoader와 함께 사용하여 각 GPU가 다른 데이터를 받도록 하세요
💡 배치 사이즈는 GPU 개수 × 단일 GPU 배치 사이즈로 설정하는 것이 일반적입니다
💡 학습률도 배치 크기에 비례하여 조정해야 합니다 (예: 배치 2배 → 학습률 2배)
💡 torchrun (구 torch.distributed.launch) 명령어로 더 간편하게 DDP를 실행할 수 있습니다
💡 AWS, GCP 등 클라우드에서는 멀티 노드 DDP로 수십 개 GPU를 사용할 수도 있습니다
8. GPU 모니터링 및 벤치마킹 - 성능 최적화
시작하며
여러분이 모델 훈련 중에 GPU가 실제로 얼마나 활용되고 있는지 확인해보신 적 있나요? 때로는 GPU 사용률이 30%밖에 안 되어서 시간을 낭비하는 경우가 있습니다.
이런 문제는 데이터 로딩 병목, 비효율적인 연산, CPU-GPU 간 전송 오버헤드 등 다양한 원인으로 발생합니다. GPU는 비싼 자원인데 제대로 활용하지 못하면 큰 손실입니다.
바로 이럴 때 필요한 것이 GPU 모니터링과 벤치마킹입니다. nvidia-smi, PyTorch Profiler 등을 활용하면 병목 구간을 찾아 최적화할 수 있습니다.
개요
간단히 말해서, GPU 모니터링은 실시간으로 GPU 사용률, 메모리, 온도 등을 추적하고, 벤치마킹은 코드의 각 부분이 얼마나 시간을 소비하는지 측정하는 작업입니다. 실무에서 성능 튜닝, 비용 최적화, 시스템 안정성 확보 등에 필수적입니다.
예를 들어, 클라우드 GPU를 시간당 요금으로 사용한다면 GPU 활용률을 높여 비용을 절감할 수 있습니다. 기존에는 막연히 "훈련이 느린 것 같다"라고 느꼈다면, 이제는 정확한 데이터로 "데이터 로딩에 40% 시간을 쓴다"라고 파악하고 개선할 수 있습니다.
핵심 특징은 첫째, 실시간 모니터링으로 문제를 즉시 발견하고, 둘째, 프로파일링으로 정확한 병목 구간을 식별하며, 셋째, 벤치마크로 최적화 전후를 정량적으로 비교할 수 있다는 점입니다. 이러한 특징들이 체계적인 성능 개선을 가능하게 합니다.
코드 예제
import torch
import time
from torch.profiler import profile, record_function, ProfilerActivity
class GPUBenchmark:
"""GPU 성능 벤치마킹 도구"""
@staticmethod
def measure_operation(func, *args, **kwargs):
"""특정 연산의 실행 시간 측정"""
# GPU 동기화 (비동기 실행 대기)
if torch.cuda.is_available():
torch.cuda.synchronize()
start_time = time.time()
result = func(*args, **kwargs)
if torch.cuda.is_available():
torch.cuda.synchronize()
end_time = time.time()
elapsed = end_time - start_time
print(f"{func.__name__} 실행 시간: {elapsed:.4f}초")
return result, elapsed
@staticmethod
def compare_cpu_gpu(model, input_tensor):
"""CPU vs GPU 성능 비교"""
# CPU 벤치마크
model_cpu = model.cpu()
input_cpu = input_tensor.cpu()
_, cpu_time = GPUBenchmark.measure_operation(
model_cpu, input_cpu
)
# GPU 벤치마크
if torch.cuda.is_available():
model_gpu = model.cuda()
input_gpu = input_tensor.cuda()
_, gpu_time = GPUBenchmark.measure_operation(
model_gpu, input_gpu
)
speedup = cpu_time / gpu_time
print(f"GPU 가속 배율: {speedup:.2f}x")
else:
print("GPU를 사용할 수 없습니다")
def profile_training_step(model, data, target, optimizer):
"""PyTorch Profiler로 훈련 스텝 분석"""
with profile(
activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
record_shapes=True,
profile_memory=True,
with_stack=True
) as prof:
with record_function("forward"):
output = model(data)
loss = torch.nn.functional.cross_entropy(output, target)
with record_function("backward"):
optimizer.zero_grad()
loss.backward()
with record_function("optimizer_step"):
optimizer.step()
# 결과 출력 (시간 기준 상위 10개)
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=10))
# Chrome Trace로 저장 (chrome://tracing에서 확인)
prof.export_chrome_trace("trace.json")
print("Trace 파일 저장: trace.json")
def monitor_gpu_continuously():
"""nvidia-smi를 통한 지속적 모니터링"""
import subprocess
# 1초마다 GPU 상태 출력
cmd = ['nvidia-smi', 'dmon', '-s', 'pumt', '-c', '60']
# p: 전력, u: 사용률, m: 메모리, t: 온도
print("60초 동안 GPU 모니터링 시작...")
subprocess.run(cmd)
설명
이것이 하는 일: GPU 연산의 실제 성능을 정확히 측정하고, 병목 구간을 찾아내며, CPU 대비 얼마나 빨라졌는지 정량적으로 평가하는 종합 벤치마킹 도구입니다. 첫 번째로, torch.cuda.synchronize()가 매우 중요합니다.
GPU 연산은 비동기로 실행되므로 이 함수 없이 시간을 재면 GPU가 실제로 작업을 끝내기 전에 측정이 종료됩니다. synchronize()는 모든 GPU 작업이 완료될 때까지 대기하여 정확한 시간을 측정합니다.
그 다음으로, PyTorch Profiler는 각 연산(forward, backward, optimizer step)이 CPU와 GPU에서 각각 얼마나 시간을 소비하는지 세밀하게 추적합니다. record_function으로 코드 블록을 구분하면 어느 부분이 느린지 한눈에 파악할 수 있습니다.
profile_memory=True로 메모리 사용 패턴도 분석 가능합니다. 마지막으로, 생성된 trace.json 파일을 Chrome 브라우저의 chrome://tracing에서 열면 시각적인 타임라인으로 확인할 수 있어 최종적으로 최적화 포인트를 명확히 식별할 수 있습니다.
여러분이 이 코드를 사용하면 "왜 GPU를 써도 별로 안 빠르지?"라는 의문을 데이터로 해결할 수 있고, 데이터 로딩 병목을 발견하여 num_workers를 늘리는 등 구체적 개선이 가능합니다. 실무에서는 최적화 전후로 벤치마크를 실행하여 개선 효과를 증명합니다.
실전 팁
💡 DataLoader의 num_workers를 4-8로 설정하면 데이터 로딩 병목을 줄일 수 있습니다
💡 pin_memory=True 옵션으로 CPU-GPU 간 데이터 전송을 가속화하세요
💡 nvidia-smi -l 1 명령어로 1초마다 GPU 상태를 실시간 모니터링할 수 있습니다
💡 TensorBoard에서도 Profiler 결과를 시각화할 수 있습니다 (tensorboard --logdir=./log)
💡 GPU 사용률이 100%가 아니라면 배치 사이즈를 늘려 GPU를 더 활용할 수 있습니다
9. 클라우드 GPU 비용 최적화 - Spot 인스턴스와 자동 종료
시작하며
여러분이 AWS나 GCP에서 GPU 인스턴스를 실행하다가 깜빡하고 종료를 안 해서 몇십만원 청구서를 받은 경험 있나요? 클라우드 GPU는 시간당 수천원씩 과금되므로 작은 실수가 큰 비용으로 이어집니다.
이런 문제는 클라우드 리소스를 수동으로 관리하다 보면 필연적으로 발생합니다. 특히 긴 훈련이 끝난 후 자동으로 인스턴스를 종료하지 않으면 밤새 돈이 나갑니다.
바로 이럴 때 필요한 것이 비용 최적화 전략입니다. Spot 인스턴스 활용, 자동 종료 스크립트, 체크포인트 저장 등으로 비용을 70% 이상 절감할 수 있습니다.
개요
간단히 말해서, 클라우드 GPU 비용 최적화는 동일한 성능을 더 저렴하게 얻거나, 불필요한 리소스 사용을 자동으로 차단하는 기법들입니다. 실무에서 연구 프로젝트 예산 절감, 스타트업의 실험 비용 관리, 대규모 하이퍼파라미터 튜닝 등에 필수적입니다.
예를 들어, Spot 인스턴스를 사용하면 On-Demand 대비 70% 저렴하게 GPU를 사용할 수 있습니다. 기존에는 온디맨드 인스턴스를 24시간 켜두어 불필요한 비용을 지불했다면, 이제는 훈련 완료 시 자동 종료하고 저렴한 Spot 인스턴스를 활용할 수 있습니다.
핵심 특징은 첫째, Spot 인스턴스로 70-90% 비용 절감이 가능하고, 둘째, 자동 종료로 유휴 시간 과금을 방지하며, 셋째, 체크포인트로 중단 시에도 재개할 수 있다는 점입니다. 이러한 특징들이 합리적인 클라우드 활용을 가능하게 합니다.
코드 예제
import torch
import os
import signal
import sys
from datetime import datetime
class AutoShutdownManager:
"""훈련 완료 시 자동 종료 관리자"""
def __init__(self, checkpoint_dir='./checkpoints'):
self.checkpoint_dir = checkpoint_dir
os.makedirs(checkpoint_dir, exist_ok=True)
# SIGTERM 시그널 핸들러 (Spot 인스턴스 중단 대비)
signal.signal(signal.SIGTERM, self.save_checkpoint_and_exit)
def save_checkpoint(self, model, optimizer, epoch, loss, filename=None):
"""체크포인트 저장"""
if filename is None:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"checkpoint_epoch{epoch}_{timestamp}.pt"
checkpoint_path = os.path.join(self.checkpoint_dir, filename)
checkpoint = {
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': loss,
'timestamp': datetime.now().isoformat()
}
torch.save(checkpoint, checkpoint_path)
print(f"체크포인트 저장: {checkpoint_path}")
return checkpoint_path
def load_checkpoint(self, model, optimizer, filename):
"""체크포인트 로드"""
checkpoint_path = os.path.join(self.checkpoint_dir, filename)
if os.path.exists(checkpoint_path):
checkpoint = torch.load(checkpoint_path)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
print(f"체크포인트 로드: epoch {checkpoint['epoch']}")
return checkpoint['epoch'], checkpoint['loss']
else:
print(f"체크포인트 파일 없음: {checkpoint_path}")
return 0, float('inf')
def save_checkpoint_and_exit(self, signum, frame):
"""Spot 중단 시 긴급 저장 후 종료"""
print("\n⚠️ Spot 인스턴스 중단 신호 감지!")
print("긴급 체크포인트 저장 중...")
# 여기서 현재 모델 저장 로직 실행
print("저장 완료. 프로그램 종료.")
sys.exit(0)
@staticmethod
def shutdown_instance_after_training():
"""훈련 완료 후 인스턴스 자동 종료 (AWS 예제)"""
import subprocess
print("훈련 완료! 60초 후 인스턴스를 종료합니다...")
print("취소하려면 Ctrl+C를 누르세요")
try:
import time
time.sleep(60)
# AWS EC2 인스턴스 종료
instance_id = subprocess.check_output(
['curl', '-s', 'http://169.254.169.254/latest/meta-data/instance-id'],
text=True
)
subprocess.run([
'aws', 'ec2', 'stop-instances',
'--instance-ids', instance_id
])
print(f"인스턴스 {instance_id} 종료 요청 완료")
except KeyboardInterrupt:
print("자동 종료가 취소되었습니다")
except Exception as e:
print(f"자동 종료 실패: {e}")
# 사용 예제
def train_with_autoshutdown(model, train_loader, optimizer, epochs=10):
"""자동 종료 기능이 포함된 훈련"""
manager = AutoShutdownManager()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 이전 체크포인트에서 재개
start_epoch, best_loss = manager.load_checkpoint(
model, optimizer, "latest_checkpoint.pt"
)
for epoch in range(start_epoch, epochs):
model.train()
total_loss = 0
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = torch.nn.functional.cross_entropy(output, target)
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(train_loader)
print(f"Epoch {epoch}, Loss: {avg_loss:.4f}")
# 매 에포크마다 체크포인트 저장
manager.save_checkpoint(model, optimizer, epoch, avg_loss,
"latest_checkpoint.pt")
print("훈련 완료!")
# 인스턴스 자동 종료 (선택사항)
# manager.shutdown_instance_after_training()
설명
이것이 하는 일: 클라우드 환경에서 예상치 못한 중단에 대비하고, 훈련 완료 후 자동으로 리소스를 정리하여 불필요한 비용을 방지하는 종합 관리 시스템입니다. 첫 번째로, signal.signal(signal.SIGTERM, ...)로 Spot 인스턴스가 회수되기 2분 전에 받는 종료 신호를 잡습니다.
AWS Spot이나 GCP Preemptible은 필요에 따라 인스턴스를 강제 종료하는데, 이 시그널을 받으면 긴급히 체크포인트를 저장하여 작업 내용을 보존합니다. 그 다음으로, 매 에포크마다 체크포인트를 저장하여 언제든 훈련을 재개할 수 있게 합니다.
model.state_dict()는 모델 가중치, optimizer.state_dict()는 학습률 스케줄러와 모멘텀 상태를 포함하므로 정확히 이어서 훈련할 수 있습니다. 마지막으로, 훈련 완료 후 AWS CLI를 사용해 인스턴스를 자동으로 중지합니다.
최종적으로 사용자가 깜빡해도 불필요한 과금이 발생하지 않습니다. 60초 대기 시간은 결과를 확인하거나 추가 작업을 할 여유를 줍니다.
여러분이 이 코드를 사용하면 Spot 인스턴스를 안심하고 사용할 수 있고(중단되어도 복구 가능), 실수로 인스턴스를 켜둬서 비용이 낭비되는 일을 방지할 수 있습니다. 실무에서는 대규모 실험 시 Spot Fleet을 사용하여 수십 개 인스턴스를 저렴하게 운영합니다.
실전 팁
💡 AWS Spot 인스턴스는 On-Demand 대비 70-90% 저렴하지만 언제든 회수될 수 있으니 체크포인트 필수입니다
💡 GCP Preemptible VM은 최대 24시간만 실행되므로 긴 훈련은 여러 번 재개 로직이 필요합니다
💡 S3나 Cloud Storage에 체크포인트를 저장하면 인스턴스가 사라져도 데이터가 보존됩니다
💡 AWS Lambda로 훈련 완료 알림을 보내면 모니터링이 편리합니다
💡 Spot Fleet Request로 여러 인스턴스 타입을 지정하면 가용성을 높일 수 있습니다
10. Docker로 GPU 환경 패키징하기 - 재현 가능한 환경
시작하며
여러분이 팀원에게 "내 컴퓨터에서는 잘 돌아가는데요?"라는 말을 들은 적 있나요? 다른 환경에서 똑같은 코드가 작동하지 않는 것은 개발자의 악몽입니다.
이런 문제는 CUDA 버전, Python 패키지, 시스템 라이브러리가 환경마다 다르기 때문에 발생합니다. 특히 딥러닝은 의존성이 복잡해서 환경 구축만 며칠 걸리는 경우도 있습니다.
바로 이럴 때 필요한 것이 Docker 컨테이너입니다. 전체 환경을 하나의 이미지로 패키징하면 어디서든 동일하게 실행되고, 팀원과 쉽게 공유할 수 있습니다.
개요
간단히 말해서, Docker는 애플리케이션과 모든 의존성을 하나의 컨테이너로 묶어서 어떤 환경에서든 동일하게 실행되도록 하는 기술입니다. 실무에서 프로덕션 배포, CI/CD 파이프라인, 팀 간 환경 공유, 실험 재현성 확보 등에 필수적입니다.
예를 들어, 연구 논문의 결과를 재현하려면 정확히 같은 환경이 필요한데, Docker 이미지를 공유하면 한 번에 해결됩니다. 기존에는 각자의 컴퓨터에 수동으로 환경을 구축했다면, 이제는 Dockerfile 하나로 모든 설정을 자동화하고 일관성을 보장할 수 있습니다.
핵심 특징은 첫째, 환경 차이로 인한 문제를 완전히 제거하고, 둘째, 버전 관리가 가능하며, 셋째, 클라우드 배포가 간단해진다는 점입니다. 이러한 특징들이 현대적인 MLOps의 기반이 됩니다.
코드 예제
# Dockerfile - GPU를 지원하는 PyTorch 환경
# 파일명: Dockerfile
FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04
# 기본 패키지 설치
RUN apt-get update && apt-get install -y \
python3.10 \
python3-pip \
git \
wget \
&& rm -rf /var/lib/apt/lists/*
# Python 패키지 설치
RUN pip3 install --no-cache-dir \
torch==2.0.1 \
torchvision==0.15.2 \
torchaudio==2.0.2 \
--index-url https://download.pytorch.org/whl/cu118
RUN pip3 install --no-cache-dir \
numpy \
pandas \
matplotlib \
jupyter \
tensorboard
# 작업 디렉토리 설정
WORKDIR /workspace
# Jupyter 노트북 포트 노출
EXPOSE 8888
# 기본 명령어
CMD ["jupyter", "notebook", "--ip=0.0.0.0", "--port=8888", "--no-browser", "--allow-root"]
# docker-compose.yml - 편리한 실행을 위한 설정
# 파일명: docker-compose.yml
version: '3.8'
services:
pytorch-gpu:
build: .
runtime: nvidia # NVIDIA Docker runtime
environment:
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
volumes:
- ./workspace:/workspace # 로컬 폴더 마운트
- ./data:/data
ports:
- "8888:8888" # Jupyter
- "6006:6006" # TensorBoard
shm_size: '8gb' # 공유 메모리 크기 (DataLoader에 필요)
stdin_open: true
tty: true
# 빌드 및 실행 스크립트 (build_and_run.sh)
#!/bin/bash
echo "Docker 이미지 빌드 중..."
docker-compose build
echo "컨테이너 실행 중..."
docker-compose up -d
echo "Jupyter 노트북 토큰 확인:"
sleep 3
docker-compose logs | grep token
echo ""
echo "접속 방법:"
echo "1. 브라우저에서 http://localhost:8888 열기"
echo "2. 위의 토큰 복사해서 로그인"
echo ""
echo "컨테이너 중지: docker-compose down"
echo "로그 확인: docker-compose logs -f"
설명
이것이 하는 일: NVIDIA GPU를 사용하는 완전한 딥러닝 환경을 Docker 컨테이너로 패키징하여 어디서든 동일하게 실행할 수 있도록 만드는 설정입니다. 첫 번째로, FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04는 NVIDIA가 공식 제공하는 이미지를 기반으로 시작합니다.
이 이미지에는 CUDA와 cuDNN이 이미 설치되어 있어서 복잡한 설정을 건너뛸 수 있습니다. runtime 버전은 훈련용으로 충분하며, 더 가벼운 이미지입니다.
그 다음으로, docker-compose.yml에서 runtime: nvidia 설정이 핵심입니다. 이것이 없으면 컨테이너가 GPU를 인식하지 못합니다.
NVIDIA Container Toolkit이 설치되어 있어야 하며, shm_size: '8gb'는 PyTorch DataLoader가 여러 워커를 사용할 때 필요한 공유 메모리입니다. 마지막으로, volumes 설정으로 로컬 디렉토리를 컨테이너에 마운트하면 최종적으로 코드와 데이터가 컨테이너 외부에 유지되어 컨테이너를 삭제해도 작업 내용이 보존됩니다.
여러분이 이 코드를 사용하면 팀원에게 Dockerfile 하나만 공유하면 똑같은 환경을 구축할 수 있고, Kubernetes나 클라우드 서비스에 바로 배포할 수 있습니다. 실무에서는 이런 Dockerfile을 Git에 포함시켜 버전 관리합니다.
실전 팁
💡 nvidia-docker2 또는 NVIDIA Container Toolkit을 먼저 설치해야 GPU를 사용할 수 있습니다
💡 멀티스테이지 빌드를 사용하면 이미지 크기를 줄일 수 있습니다 (빌드 도구 제거)
💡 .dockerignore 파일로 불필요한 파일을 제외하여 빌드 속도를 높이세요
💡 Docker Hub나 AWS ECR에 이미지를 푸시하면 팀원과 쉽게 공유할 수 있습니다
💡 VSCode의 Remote-Containers 확장으로 컨테이너 안에서 직접 개발할 수 있습니다