최근 프로덕션 환경에서 대규모 AI 모델을 서빙하는 과정에서 예측 불가능한 Out-Of-Memory(OOM) 오류가 빈번하게 발생하여 서비스 안정성에 심각한 영향을 미쳤다. 특히, GPU 자원 사용률은 겉보기에 여유가 있는 것처럼 보였음에도 불구하고 새로운 추론 요청이 들어올 때마다 OOM이 발생하거나, 기존에 잘 동작하던 모델이 갑자기 메모리 할당에 실패하는 현상이 관찰되었다. 초기에는 모델 자체의 메모리 누수나 잘못된 배치 사이즈 설정 등을 의심했지만, 심층적인 분석 결과 GPU 메모리 단편화(Fragmentation)가 주된 원인으로 밝혀졌다.
이 문제는 특히 동적으로 여러 모델을 로드/언로드하거나, 다양한 크기의 텐서 연산이 반복적으로 수행되는 AI 서빙 시스템에서 두드러진다. GPU 메모리가 물리적으로는 충분히 남아있어도, 연속된 큰 블록의 메모리를 할당할 수 없는 상태가 되어 OOM을 유발하는 것이다. 이는 마치 디스크 단편화와 유사하게, 사용 가능한 공간이 조각조각 나뉘어 있어 큰 파일을 저장할 수 없는 상황과 같다. 본 가이드에서는 AI 모델 서빙 환경에서 GPU 메모리 단편화로 인한 OOM 오류의 발생 원리를 깊이 있게 파고들고, 이를 진단하고 해결하기 위한 실무적인 접근 방식과 최적화 전략을 상세히 다룬다.
GPU 메모리 단편화의 메커니즘과 OOM 발생 원리
GPU 메모리 단편화는 주로 동적인 메모리 할당 및 해제 패턴에서 발생한다. AI 모델 추론 과정에서는 모델 가중치, 중간 활성화 값, 옵티마이저 상태 등 다양한 크기의 텐서들이 GPU 메모리에 할당되고 해제된다. 예를 들어, 크고 작은 텐서들이 불규칙하게 할당되었다가 해제되면, 메모리 공간에 빈틈이 생기게 된다. 이러한 빈틈들은 개별적으로는 사용 가능한 공간이지만, 연속된 하나의 큰 블록으로 합쳐지지 않으면 특정 크기 이상의 텐서를 할당할 때 실패하게 된다.
특히, 딥러닝 프레임워크(TensorFlow, PyTorch 등)는 내부적으로 GPU 메모리 풀(Memory Pool)을 관리하며 메모리 할당/해제를 최적화하려고 시도한다. 그러나 이 풀 관리 전략만으로는 모든 단편화 문제를 해결하기 어렵다. 예를 들어, PyTorch의 경우 torch.cuda.empty_cache() 함수를 통해 캐시된 메모리를 해제할 수 있지만, 이는 단편화된 메모리를 완전히 재정렬하는 것은 아니다. 결국, 물리적으로는 전체 GPU 메모리 사용량이 낮아 보이더라도, 가장 큰 연속된 가용 블록(Largest Free Block)의 크기가 필요한 텐서 크기보다 작아지면 OOM이 발생한다. 이 문제는 특히 배치 사이즈가 크거나, 모델 구조가 복잡하여 메모리 요구량이 높은 모델을 로드할 때 더욱 심화된다.
GPU 메모리 사용량 및 단편화 현상 진단
GPU 메모리 단편화 문제를 진단하기 위해서는 단순히 nvidia-smi 명령어로 확인하는 총 사용량 외에, 실제 가용 메모리 블록의 크기 분포를 확인하는 것이 중요하다. nvidia-smi는 전체 GPU 메모리 중 현재 사용 중인 양만 보여줄 뿐, 내부적인 단편화 상태는 알려주지 않는다.
실제 시스템에서는 다음과 같은 증상으로 단편화를 의심할 수 있다.
nvidia-smi상으로는 메모리 여유가 충분한데도 특정 모델 로딩 시 OOM 발생.- 장시간 서비스 운영 후, 초기에는 잘 동작하던 모델이 OOM을 겪기 시작.
- 동일한 모델을 재시작하면 일시적으로 문제가 해결되지만, 시간이 지나면 다시 발생.
이러러한 상황을 진단하기 위해 PyTorch 환경에서는 torch.cuda.memory_summary()와 같은 함수를 활용하여 메모리 할당 상태를 좀 더 자세히 볼 수 있다.
import torch
# GPU가 사용 가능한지 확인
if torch.cuda.is_available():
# 현재 GPU의 메모리 할당 상태를 자세히 출력
# 'full_history=True'를 통해 더 상세한 할당/해제 이력을 포함합니다.
# 이 정보는 단편화 패턴을 이해하는 데 도움이 됩니다.
print(torch.cuda.memory_summary(device=None, abbreviated=False, full_history=True))
else:
print("CUDA is not available. Please check your GPU setup.")
위 코드는 PyTorch가 관리하는 GPU 메모리 풀의 현재 상태를 요약하여 보여준다. 특히 allocated, reserved, active_bytes, inactive_bytes 등의 지표를 통해 메모리 사용 패턴을 유추할 수 있다. inactive_bytes가 높다면 사용되지 않는 메모리가 캐시되어 있을 가능성이 있지만, 이것이 곧 단편화된 메모리를 의미하는 것은 아니다. 가장 중요한 것은 largest_block_bytes와 같은 지표로, 현재 할당 가능한 가장 큰 연속 메모리 블록의 크기를 파악하는 것이다. 이 값이 필요한 텐서 크기보다 작으면 OOM이 발생한다.
GPU 메모리 단편화 완화 및 OOM 방지 전략
GPU 메모리 단편화를 완화하고 OOM 오류를 방지하기 위한 몇 가지 실무적인 전략이 있다.
1. 모델 로드/언로드 전략 최적화
동적으로 모델을 로드하고 언로드하는 경우, 불필요한 모델은 즉시 메모리에서 해제하여 단편화를 최소화해야 한다. del model과 torch.cuda.empty_cache()를 함께 사용하여 명시적으로 메모리를 비우는 것이 중요하다.
import torch
# 모델 로드 및 추론 예시
def run_inference_with_model(model_path, data):
model = torch.load(model_path)
model.cuda() # 모델을 GPU로 이동
# 추론 수행
with torch.no_grad():
output = model(data.cuda())
# 모델 사용 후 명시적 메모리 해제
# 1. 모델 객체 삭제 (파이썬 가비지 컬렉터가 참조를 해제하도록 유도)
del model
# 2. PyTorch CUDA 캐시 비우기 (단편화된 작은 블록들을 정리)
torch.cuda.empty_cache()
print("Model unloaded and cache emptied.")
return output
# 실제 환경에서는 모델 로드/언로드 시점을 신중하게 관리해야 합니다.
# 예를 들어, 특정 모델이 자주 사용된다면 메모리에 상주시키는 것을 고려할 수 있습니다.
2. 메모리 할당 전략 조정 (PyTorch)
PyTorch는 기본적으로 메모리 할당자를 사용하지만, 특정 시나리오에서는 PYTORCH_CUDA_ALLOC_CONF 환경 변수를 통해 할당 동작을 조정할 수 있다. 예를 들어, max_split_size_mb를 조절하여 할당자가 메모리 블록을 분할하는 최대 크기를 제한할 수 있다. 이는 작은 블록으로의 과도한 분할을 막아 단편화를 줄이는 데 도움이 될 수 있다.
# PyTorch 메모리 할당 설정 예시
# 최대 분할 크기를 128MB로 제한하여 과도한 작은 블록 생성을 방지합니다.
# 이 설정은 애플리케이션 시작 전에 환경 변수로 설정해야 합니다.
export PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:128
# PyTorch 애플리케이션 실행
python your_ai_serving_app.py
주의: 이 설정은 시스템 전반의 메모리 할당 전략에 영향을 미치므로, 충분한 테스트를 거쳐야 한다. 잘못된 설정은 오히려 성능 저하나 다른 OOM을 유발할 수 있다.
3. 배치 사이즈 최적화 및 동적 배치 처리
고정된 큰 배치 사이즈는 메모리 단편화에 취약할 수 있다. 동적 배치(Dynamic Batching)를 구현하여 GPU 메모리 상황에 따라 배치 사이즈를 유연하게 조절하는 것이 효과적이다. 예를 들어, NVIDIA Triton Inference Server와 같은 솔루션은 이러한 동적 배치를 기본적으로 지원한다.
4. GPU 프로세스 격리 및 재시작 전략
가장 확실한 방법 중 하나는 GPU 메모리를 주기적으로 초기화하는 것이다. 이는 AI 모델 서빙 프로세스를 주기적으로 재시작하거나, 컨테이너 환경에서 각 모델 서빙 인스턴스를 격리하여 운영하는 방식으로 구현할 수 있다. Kubernetes 환경에서는 Pod의 restartPolicy를 활용하거나, 특정 주기마다 Pod를 재생성하는 CronJob을 활용할 수 있다.
# Kubernetes Deployment 예시: AI 모델 서빙 Pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-model-server
spec:
replicas: 3
selector:
matchLabels:
app: ai-model-server
template:
metadata:
labels:
app: ai-model-server
spec:
containers:
- name: model-container
image: your-model-serving-image:latest
resources:
limits:
nvidia.com/gpu: 1 # 각 Pod는 1개의 GPU를 사용
memory: "16Gi" # Pod 메모리 제한
requests:
nvidia.com/gpu: 1
memory: "8Gi"
env:
- name: PYTORCH_CUDA_ALLOC_CONF
value: "max_split_size_mb:128" # 환경 변수 설정
# livenessProbe와 readinessProbe를 통해 비정상적인 상태(OOM 등) 감지 시 재시작 유도
livenessProbe:
httpGet:
path: /healthz
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 1
# nodeSelector 또는 affinity를 사용하여 특정 GPU 노드에 배포할 수 있습니다.
# tolerations 등을 통해 테인트된 노드에도 배포 가능합니다.
각 Pod가 독립적인 GPU 프로세스를 가지므로, 한 Pod에서 발생하는 단편화가 다른 Pod에 미치는 영향을 최소화할 수 있다. 주기적인 재시작은 단편화된 메모리를 완전히 초기화하여 깨끗한 상태로 복구하는 가장 확실한 방법이다.
OOM 발생 시 로그 분석 및 사후 조치
OOM 오류가 발생했을 때는 단순히 애플리케이션을 재시작하는 것을 넘어, 발생 원인을 정확히 파악하기 위한 로그 분석이 필수적이다.
1. 애플리케이션 로그 확인
대부분의 딥러닝 프레임워크는 OOM 발생 시 관련 정보를 로그로 남긴다. PyTorch의 경우 다음과 같은 메시지를 볼 수 있다.
RuntimeError: CUDA out of memory. Tried to allocate X MiB (GPU Y; Z MiB total capacity; A MiB already allocated; B MiB free; C MiB cached)
이 메시지에서 B MiB free가 충분히 커 보임에도 OOM이 발생했다면, 이는 단편화를 강력하게 시사한다. X MiB는 할당하려던 텐서의 크기이며, 이 크기가 B MiB free보다 작다면 단편화로 인해 연속된 공간이 부족하다는 의미다.
2. 시스템 로그 및 dmesg 확인
드물지만, GPU 드라이버 수준에서 문제가 발생하거나 커널 OOM Killer가 작동하는 경우도 있다. dmesg 명령어를 통해 커널 로그를 확인하여 관련 메시지가 있는지 살펴보는 것이 좋다.
# 커널 로그에서 OOM 관련 메시지 검색
dmesg | grep -i oom
dmesg | grep -i nvidia
3. 모니터링 시스템 활용
Prometheus, Grafana와 같은 모니터링 스택을 구축하여 GPU 사용률, 메모리 사용량, 그리고 가장 중요한 'Largest Free Block' 크기를 지속적으로 모니터링하는 것이 중요하다. 이를 통해 OOM 발생 전 징후를 미리 감지하고 선제적으로 대응할 수 있다. 예를 들어, nvidia-smi의 XML 출력(nvidia-smi -q -x)을 파싱하여 더 상세한 메모리 정보를 수집할 수 있다.
실무 엔지니어 FAQ
Q. torch.cuda.empty_cache()를 호출했는데도 OOM이 계속 발생합니다. 왜 그런가요?
A. torch.cuda.empty_cache()는 PyTorch가 내부적으로 캐시하고 있는 메모리 블록들을 운영체제/드라이버로 반환하여 재사용 가능하게 만들지만, 이미 단편화된 메모리 공간을 재정렬하거나 압축하지는 않습니다. 즉, 작은 조각들이 많아서 큰 텐서를 할당할 수 없는 상황은 해결되지 않을 수 있습니다. 근본적인 해결을 위해서는 프로세스 재시작을 고려해야 합니다.
Q. GPU 메모리 사용률이 50%인데도 OOM이 발생합니다. 버그인가요?
A. 버그가 아닐 가능성이 높습니다. 이는 전형적인 GPU 메모리 단편화의 증상입니다. 전체 GPU 메모리 중 절반이 사용 가능하다고 해도, 그 절반이 여러 개의 작은 조각으로 나뉘어 있다면 필요한 크기의 연속된 메모리 블록을 할당할 수 없어 OOM이 발생할 수 있습니다. torch.cuda.memory_summary()를 통해 largest_block_bytes 값을 확인하여 현재 할당 가능한 가장 큰 연속 블록의 크기를 파악하는 것이 중요합니다.
Q. NVIDIA Triton Inference Server를 사용하는데도 단편화 문제가 발생할 수 있나요?
A. 네, 발생할 수 있습니다. Triton은 효율적인 GPU 활용을 위해 많은 최적화를 제공하지만, 모델 로드/언로드 패턴, 동적 배치 설정, 그리고 백엔드 프레임워크(TensorFlow, PyTorch 등)의 메모리 관리 방식에 따라 단편화가 완전히 해소되지 않을 수 있습니다. 특히, 서로 다른 크기의 모델들이 자주 로드/언로드되거나, 커스텀 백엔드에서 메모리를 비효율적으로 사용하는 경우 단편화가 발생할 수 있습니다. Triton의 model-control-mode를 explicit으로 설정하고 unload 명령어를 통해 불필요한 모델을 명시적으로 해제하는 것이 도움이 될 수 있습니다.
Q. GPU 메모리 단편화를 완벽하게 없앨 수 있는 방법은 없나요?
A. 완벽하게 없애는 것은 사실상 불가능합니다. GPU 메모리 할당/해제는 동적으로 이루어지므로 단편화는 필연적으로 발생합니다. 목표는 단편화를 최소화하고, OOM이 발생하기 전에 이를 감지하고 복구하는 전략을 마련하는 것입니다. 위에서 언급한 모델 로드/언로드 최적화, 프로세스 격리 및 주기적 재시작, 그리고 정교한 모니터링 시스템 구축이 가장 현실적인 접근 방식입니다.
