1 문제: 모델 가중치는 왜 별도 관리가 필요한가
딥러닝 모델을 학습하면 가중치(weight) 파일이 생성된다. .pt, .pth, .h5, .onnx 등의 형식이며, 용량은 수백 MB에서 수 GB에 이른다. 이 파일은 일반 코드와 근본적으로 성격이 다르다.
| 속성 | 소스 코드 | 모델 가중치 |
|---|---|---|
| 용량 | 수 KB ~ 수 MB | 수백 MB ~ 수 GB |
| 변경 빈도 | 커밋 단위 | 학습 완료 시 |
| 버전 관리 | Git (diff 가능) | 바이너리 (diff 불가) |
| 배포 대상 | 모든 개발 환경 | 서빙/추론 환경만 |
Git 저장소에 가중치를 직접 넣으면 clone 속도가 급격히 느려지고, .git 히스토리에 대용량 바이너리가 누적되어 저장소 크기가 비정상적으로 커진다. 따라서 코드와 모델 가중치는 반드시 분리하여 관리한다.
2 가중치 파일 전송 방법
모델 가중치를 서버에 올리는 방법은 크게 4가지이다. 각 방법의 특성과 적합한 상황이 다르다.
2.1 Git LFS (Git Large File Storage)
Git 저장소에 대용량 파일을 포함시키되, 실제 파일은 별도 스토리지에 저장하는 방식이다.
# 설치 후 대용량 파일 트래킹
git lfs install
git lfs track "*.pt" "*.pth" "*.h5" "*.onnx"
git add .gitattributes
git commit -m "track model weights with LFS"- Git 워크플로를 그대로 유지할 수 있다는 장점이 있다
- GitHub 무료 용량 제한이 1GB이므로, 대형 모델에는 부적합하다
- 팀 규모가 작고 모델 크기가 수백 MB 이하일 때 적합하다
2.2 클라우드 오브젝트 스토리지
실무에서 가장 많이 사용하는 방식이다. Azure Blob Storage, AWS S3, GCS 등 클라우드 오브젝트 스토리지에 모델을 업로드하고, 서버에서 필요할 때 다운로드한다.
# GCS 예시
gsutil cp model_weights.pt gs://your-bucket/models/
# VM에서 다운로드
gsutil cp gs://your-bucket/models/model_weights.pt ./models/
# AWS S3 예시
aws s3 cp model_weights.pt s3://your-bucket/models/- 용량 제한이 사실상 없다
- 팀 공유가 쉽고, 버전 관리(versioning) 기능을 제공한다
- CI/CD 파이프라인과의 통합이 자연스럽다
2.3 SCP / rsync (직접 전송)
온프레미스 환경에서 빠르게 파일을 전송할 때 사용한다.
# SCP: 단순 복사
scp model_weights.pt user@vm-ip:/path/to/models/
# rsync: 중단 시 이어받기 가능, 변경분만 전송
rsync -avz --progress model_weights.pt user@vm-ip:/path/to/models/- 별도 인프라 없이 바로 사용할 수 있다
- 수동 작업이라 자동화에 한계가 있다
- 프로토타이핑이나 일회성 전송에 적합하다
2.4 DVC (Data Version Control)
모델과 데이터의 버전 관리가 함께 필요할 때 사용한다. Git과 유사한 인터페이스로 대용량 파일을 관리한다.
pip install dvc
dvc init
dvc add models/weights.pt
dvc remote add -d storage s3://your-bucket/dvc
dvc push- Git 커밋과 모델 버전을 1:1로 매핑할 수 있다
- 실험 재현성(reproducibility)이 중요한 연구 환경에 적합하다
- 초기 설정이 다소 복잡하다
2.5 방법 선택 기준
| 상황 | 추천 방법 |
|---|---|
| 소규모 팀, 모델 < 500MB | Git LFS |
| 실무 서비스, 팀 공유 필요 | 클라우드 스토리지 (S3, Blob, GCS) |
| 온프레미스, 일회성 전송 | SCP / rsync |
| 실험 재현성, 모델+데이터 버전 관리 | DVC |
일반적으로 클라우드 오브젝트 스토리지가 가장 범용적인 선택이다. .gitignore에 가중치 파일을 추가하고, 코드에서 스토리지로부터 자동 다운로드하는 패턴이 표준이다.
3 Azure Blob Storage 기반 모델 관리
주기적으로 업데이트되는 모델일수록 클라우드 오브젝트 스토리지가 적합하다. Azure 환경을 예시로 설명하지만, S3나 GCS에서도 동일한 패턴이 적용된다.
3.1 왜 Blob Storage인가
- 버전 관리: Blob Storage의 versioning을 활성화하면 모델 업데이트 이력이 자동 보존된다
- 자동화 용이: 학습 파이프라인 끝에 업로드 스크립트 한 줄이면 된다
- pull 방식: VM이 시작 시 또는 스케줄에 맞춰 최신 모델을 가져온다
3.2 전체 흐름
[학습 서버/파이프라인]
| 학습 완료 -> 가중치 업로드
v
[Azure Blob Storage] <- 버전 관리 ON
|
v
[서빙 VM] <- 최신 모델 pull (스케줄 or 이벤트 트리거)
3.3 구현 예시
from azure.storage.blob import BlobServiceClient
blob_client = BlobServiceClient.from_connection_string(conn_str)
container = blob_client.get_container_client("models")
# 학습 후 업로드
with open("model_v2.pt", "rb") as f:
container.upload_blob("latest/model.pt", f, overwrite=True)
# 서빙 VM에서 다운로드
blob = container.get_blob_client("latest/model.pt")
with open("model.pt", "wb") as f:
f.write(blob.download_blob().readall())3.4 고도화 옵션
모델 관리 요구사항이 복잡해지면 아래와 같이 확장한다.
| 요구사항 | 방법 |
|---|---|
| 업데이트 시 VM에 자동 알림 | Event Grid + Azure Functions |
| A/B 테스트, 롤백 | latest/, stable/, canary/ 폴더 분리 |
| 모델 메타데이터 관리 | Azure ML Model Registry |
| CI/CD 통합 | Azure DevOps Pipeline에서 업로드/배포 자동화 |
하지만 이런 고도화는 트래픽과 요구사항이 생긴 뒤에 고민할 문제이다. 처음부터 이렇게 설계할 필요는 없다.
4 코드와 모델의 역할 분리: Poetry + Blob Storage
“Poetry로 모델을 설치할 수 있지 않나?”라는 의문이 들 수 있다. Poetry는 Python 패키지(코드) 의존성 관리 도구이지, 대용량 바이너리 파일 배포용이 아니다.
| 역할 | 도구 | 대상 |
|---|---|---|
| 코드 배포 | Poetry (pip, conda 등) | Python 라이브러리, 소스 코드 |
| 모델 배포 | Blob Storage (S3, GCS 등) | 가중치 파일 (.pt, .h5 등) |
PyPI 패키지 최대 용량은 약 60MB이므로, 수백 MB 이상인 모델 가중치는 올릴 수 없다.
앞서 코드와 모델의 역할이 다름을 확인했으므로, 이제 이 분리를 프로젝트 구조에 반영하는 방법을 살펴본다.
4.1 프로젝트 디렉토리 구조
your_project/
├── pyproject.toml # Poetry 프로젝트 설정
├── scripts/
│ └── upload_model.py # 로컬에서 학습 후 Blob에 업로드
├── your_package/
│ └── model_loader.py # 서버에서 Blob에서 모델 다운로드
└── models/ # .gitignore에 추가 (가중치 파일)
별도 프로젝트 두 개를 만드는 것이 아니라, 같은 프로젝트에 업로드/다운로드 스크립트를 함께 두는 구조이다.
4.2 실행 흐름
[로컬 개발 PC] [서버 VM]
1. 모델 학습 1. poetry install
2. python scripts/upload_model.py 2. 앱 실행 -> model_loader.py가
-> Blob Storage에 업로드 Blob에서 자동 다운로드
4.3 첫 실행 시 자동 다운로드 패턴
HuggingFace의 transformers 라이브러리도 이 방식이다. pip/poetry로 코드만 설치하고, 모델 가중치는 첫 실행 시 Hub에서 다운로드한다.
import os
from azure.storage.blob import BlobServiceClient
MODEL_PATH = "models/weights.pt"
def ensure_model():
"""모델이 로컬에 없으면 Blob Storage에서 다운로드한다."""
if not os.path.exists(MODEL_PATH):
print("Downloading model weights...")
blob = container.get_blob_client("latest/weights.pt")
with open(MODEL_PATH, "wb") as f:
f.write(blob.download_blob().readall())
return MODEL_PATHpoetry install 후 코드를 실행하면 모델이 없을 때 자동으로 받아오므로, 사용자 입장에서는 한 번에 세팅되는 느낌이다.
5 서비스 규모별 모델 서빙 아키텍처
앞서 모델을 저장하고 가져오는 방법을 다루었다. 이제 실제 서비스에서 이 패턴을 어떻게 확장하는지 규모별로 살펴본다.
5.1 소규모 / 초기 서비스
앞서 설명한 패턴을 그대로 사용한다.
VM 시작 -> Blob에서 모델 다운로드 -> FastAPI 등으로 서빙
대부분의 서비스가 이 구조에서 시작하며, 이 단계에서 충분히 동작한다.
5.2 규모가 커질 때
| 문제 | 해결 |
|---|---|
| 모델 업데이트 시 서버 재시작이 번거로움 | 모델 버전 관리 + 핫 리로드 |
| 트래픽 증가 | 여러 VM에 로드밸런싱 |
| 모델이 여러 개 | Azure ML, MLflow 같은 모델 레지스트리 |
| GPU 서빙 필요 | Triton Inference Server, TorchServe |
핵심은 처음부터 복잡한 아키텍처를 설계하지 않는 것이다. Blob Storage + 다운로드 스크립트 패턴으로 시작하고, 병목이 발생하는 지점부터 개선한다.
6 온프레미스 환경에서의 적용
클라우드 VM이 아닌 사내 GPU 서버에서도 동일한 패턴이 적용된다. 실제로 온프레미스 서버에 Azure Blob Storage를 마운트하여 로컬 디렉토리처럼 사용하는 경우도 있다.
# blobfuse2로 Blob Storage를 로컬 디렉토리에 마운트하는 예시
# 마운트된 상태에서는 일반 파일 시스템처럼 접근 가능
ls /BiO/home/user/az_storage/models/이 경우 별도의 다운로드 스크립트 없이, 마운트된 경로에서 직접 모델을 로드할 수 있다. 다만 네트워크 I/O가 발생하므로, 추론 성능이 중요하면 로컬 디스크에 복사한 뒤 사용하는 것이 좋다.
7 요약
[코드] [모델 가중치]
| |
v v
Poetry / pip install Blob Storage / S3 / GCS
| |
v v
소스 코드 + 라이브러리 .pt / .h5 / .onnx 파일
| |
└─────────── 서버에서 합류 ────────┘
|
v
모델 서빙 (FastAPI 등)
- 코드와 모델 가중치는 역할이 다르므로 배포 경로를 분리한다
- 가중치 전송은 클라우드 오브젝트 스토리지가 가장 범용적이다
- 프로젝트 내에 업로드/다운로드 스크립트를 함께 두고, 첫 실행 시 자동 다운로드 패턴을 사용한다
- 소규모로 시작하고 병목이 생기면 확장한다