
딥러닝 모델을 학습시키거나 대규모 수치 연산을 수행할 때, 분명 메모리 총량은 충분함에도 불구하고 Out of Memory(OOM) 에러가 발생하는 기현상을 겪어보셨을 겁니다. 이는 대개 메모리 누수가 아닌 '메모리 파편화(Fragmentation)' 때문입니다. 텐서(Tensor) 객체들이 메모리 곳곳에 불연속적으로 배치되면서, 새로운 거대 텐서를 할당할 '연속된 공간'이 부족해지는 현상입니다. 본 포스팅에서는 memory_profiler와 Pytorch 내부 도구를 활용해 이 실체를 추적하고 해결하는 전문적인 엔지니어링 기법을 다룹니다.
1. 메모리 누수(Leak)와 파편화(Fragmentation)의 결정적 차이
많은 개발자가 이 두 개념을 혼동하지만, 해결 방법은 완전히 다릅니다. 누수는 사용하지 않는 객체가 해제되지 않는 것이고, 파편화는 해제는 되었으나 남은 공간이 쪼개져 있는 상태입니다.
| 비교 항목 | 메모리 누수 (Memory Leak) | 메모리 파편화 (Fragmentation) |
|---|---|---|
| 핵심 증상 | 시간이 지날수록 가용 메모리가 선형적으로 감소 | 전체 가용 메모리는 충분하나 큰 객체 할당 실패 |
| 주요 원인 | 순환 참조, 전역 변수에 객체 누적 | 서로 다른 크기의 텐서 빈번한 할당 및 해제 |
| 추적 도구 | objgraph, tracemalloc | Memory Profiler, PyTorch Caching Allocator |
| 해결 방법 | 참조 제거, weakref 사용 | 메모리 풀링, 텐서 재사용, 할당 전략 수정 |
| 시스템 영향 | 결국 시스템 전체 다운 유발 | 특정 대규모 연산 시점에서만 간헐적 발생 |
2. 실무 고도화 Example: 텐서 파편화 추적 및 해결 솔루션 7가지
단순한 설치법을 넘어 실무 파이프라인에서 즉시 사용 가능한 코드 예제입니다.
Example 01. `@profile` 데코레이터를 이용한 라인별 메모리 추적
가장 직관적으로 어떤 연산에서 메모리 스파이크가 발생하는지 확인하는 방법입니다.
from memory_profiler import profile
import torch
@profile
def train_step():
# 파편화를 유발할 수 있는 가변 크기 텐서 생성 루프
tensors = []
for i in range(100):
tensors.append(torch.randn(i * 100, i * 100))
del tensors
return
if __name__ == "__main__":
train_step()
Example 02. PyTorch Caching Allocator 스냅샷 분석
내부 캐싱 할당자의 상태를 확인하여 실제 파편화된 블록의 개수를 파악합니다.
import torch
def check_tensor_fragmentation():
stats = torch.cuda.memory_stats()
allocated = stats["allocated_bytes.all.current"]
reserved = stats["reserved_bytes.all.current"]
# 예약된 메모리와 실제 할당된 메모리의 차이가 파편화 영역임
fragmentation = (reserved - allocated) / reserved * 100
print(f"현재 메모리 파편화 율: {fragmentation:.2f}%")
check_tensor_fragmentation()
Example 03. `time_based` 프로파일링으로 가비지 컬렉션 주기 확인
시간 흐름에 따른 메모리 사용량을 그래프로 그려 GC가 적절히 개입하는지 확인합니다.
# 터미널 실행 명령어
# mprof run --python my_script.py
# mprof plot
Example 04. 텐서 재사용(In-place Operation)을 통한 파편화 방지
새로운 메모리 블록을 할당받지 않고 기존 메모리를 덮어쓰는 해결 방법입니다.
import torch
def optimized_op(x):
# 새로운 텐서를 생성하는 대신 x = x + 1 대신 x.add_(1) 사용
x.add_(1)
x.relu_() # In-place 연산
return x
Example 05. `PYTORCH_CUDA_ALLOC_CONF` 환경변수 최적화
파편화가 심할 경우 할당 전략을 수정하여 작은 블록들을 합치도록 유도합니다.
import os
# 가용 블록이 부족할 때 할당자가 더 공격적으로 메모리를 정리하도록 설정
os.environ['PYTORCH_CUDA_ALLOC_CONF'] = "max_split_size_mb:128"
Example 06. 대규모 텐서 할당 전 `empty_cache()` 명시적 호출
파편화된 공간을 정리하여 연속된 큰 블록을 확보하는 임시 방편입니다.
import torch
def heavy_allocation():
# 중요한 거대 텐서 생성 직전 실행
torch.cuda.empty_cache()
large_tensor = torch.randn(10000, 10000, device='cuda')
return large_tensor
Example 07. `tracemalloc`을 병행한 파이썬 객체-텐서 매핑
파이썬 레벨의 객체 할당과 텐서 메모리 사이의 상관관계를 추적합니다.
import tracemalloc
tracemalloc.start()
# 연산 수행...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:5]:
print(stat)
3. 전문가 가이드: 지속 가능한 메모리 관리 전략
프로파일링 결과 파편화가 20% 이상 지속된다면, 아키텍처 수정을 고려해야 합니다. 특히 가변 길이 시퀀스를 다루는 RNN이나 Transformer 모델에서 Dynamic Batching을 사용할 때 파편화가 극심해집니다. 이때는 데이터를 비슷한 길이끼리 묶는 Bucketing 전략을 사용하는 것이 근본적인 해결책입니다.
'Artificial Intelligence > 60. Python' 카테고리의 다른 글
| [PYTHON] 가비지 컬렉션(GC) 수동 제어로 딥러닝 메모리 누수 해결하는 7가지 방법 (0) | 2026.04.14 |
|---|---|
| [PYTHON] __slots__를 활용한 메모리 최적화 방법과 수백만 객체 처리 성능 차이 분석 (0) | 2026.04.14 |
| [PYTHON] Decorator를 활용한 모델 추론 레이턴시(Latency) 로깅 시스템 설계 : 성능 최적화를 위한 7가지 해결 방법 (0) | 2026.04.14 |
| [PYTHON] 효율적인 GPU 관리: Context Manager를 이용한 리소스 자동 할당 및 해제 방법 7가지 (0) | 2026.04.14 |
| [PYTHON] RAG 파이프라인 최적화를 위한 벡터 DB 선택 기준 5가지와 성능 해결 방법 (0) | 2026.04.13 |