본문 바로가기
Artificial Intelligence/21. PyTorch

[PYTORCH] 텐서 슬라이싱 메모리 공유 문제 해결 및 효율적인 복사 방법 3가지

by Papa Martino V 2026. 4. 5.
728x90

텐서 슬라이싱 메모리 공유
텐서 슬라이싱 메모리 공유

 

파이토치(PyTorch)를 사용하는 딥러닝 엔지니어라면 반드시 직면하게 되는 '뷰(View)'와 '카피(Copy)'의 메커니즘을 심층 분석하고, 예기치 않은 데이터 변형을 막는 실무적인 해결 방법을 제시합니다.


1. 서론: 왜 슬라이싱은 위험할 수 있는가?

파이토치에서 텐서 조작은 모델의 성능과 직결됩니다. 특히 대용량 데이터셋을 다룰 때 메모리 효율성을 극대화하기 위해 파이토치는 '메모리 공유(Memory Sharing)' 방식을 채택합니다. 슬라이싱(Slicing)을 통해 생성된 새로운 텐서는 원본과 동일한 메모리 저장 공간(Storage)을 참조하는 경우가 많으며, 이를 '뷰(View)'라고 부릅니다. 이러한 설계는 속도 면에서 큰 이점을 주지만, 슬라이싱된 텐서의 값을 수정했을 때 원본 데이터까지 함께 변해버리는 Side Effect(부작용)를 초래합니다. 본 아티클에서는 이러한 문제를 완벽히 제어하고 실무에서 버그를 방지하는 7가지 이상의 구체적인 사례를 다룹니다.

2. 뷰(View) vs 카피(Copy) 상세 비교

가장 먼저 파악해야 할 핵심은 특정 연산이 메모리를 공유하는 '뷰'를 반환하는지, 아니면 완전히 새로운 메모리를 할당하는 '카피'를 수행하는지 구분하는 것입니다.

구분 슬라이싱 (Slicing/View) 고급 인덱싱 (Advanced Indexing) Clone & Detach
메모리 공유 여부 공유함 (Shared) 공유하지 않음 (New Allocation) 완전 독립 (Independent)
데이터 수정 영향 원본 데이터도 변경됨 원본에 영향 없음 원본에 영향 없음
주요 함수/연산 tensor[0:5], .view() tensor[[0, 2, 4]], tensor[mask] .clone(), .copy_()
연산 속도 매우 빠름 (O(1)) 상대적으로 느림 (O(N)) 메모리 복사 비용 발생

3. 실무 적용을 위한 7가지 핵심 코드 예제 (Sample Examples)

개발자가 현업에서 즉시 활용할 수 있는 시나리오별 예제입니다.

Example 1: 슬라이싱 후 값 변경 시 발생하는 전이 현상 방지

가장 흔한 실수인 단순 슬라이싱 후 .clone()을 사용하지 않았을 때의 문제입니다.


import torch

# 1. 원본 텐서 생성
original = torch.tensor([1.0, 2.0, 3.0, 4.0])

# 2. 슬라이싱 (메모리 공유 발생)
slice_tensor = original[0:2]
slice_tensor[0] = 99.0

# 결과: original도 [99.0, 2.0, 3.0, 4.0]으로 변함
# 해결책: .clone() 명시적 사용
safe_slice = original[0:2].clone()
safe_slice[1] = 77.0  # original은 변하지 않음
        

Example 2: 논리 마스킹(Boolean Masking)을 이용한 안전한 데이터 추출

마스킹 인덱싱은 기본적으로 카피를 생성하므로 메모리 공유 문제에서 자유롭습니다.


data = torch.randn(5, 5)
mask = data > 0

# 마스킹 인덱싱은 새로운 메모리를 할당함
positive_values = data[mask]
positive_values.fill_(0) # data 원본은 그대로 유지됨
        

Example 3: Gradient 전파를 끊는 Detach와의 조합

추론(Inference) 단계에서 데이터를 시각화하거나 로그를 남길 때 메모리 공유와 연산 그래프 문제를 동시에 해결합니다.


# 연산 그래프가 포함된 텐서
output = model(input_data)

# 시각화를 위해 넘파이로 변환 시 복사본 생성
# .detach()는 그래프 분리, .cpu()는 장치 이동, .numpy()는 변환
viz_data = output[0].detach().cpu().clone().numpy()
        

Example 4: 겹치지 않는 영역의 In-place 연산 최적화

메모리 부족 상황에서는 오히려 공유를 활용하여 효율을 높여야 합니다.


# 큰 텐서의 특정 부분만 0으로 초기화하고 싶을 때
full_tensor = torch.ones(1000, 1000)
full_tensor[500:].zero_() # 메모리 추가 할당 없이 원본 수정
        

Example 5: 비연속적(Non-contiguous) 텐서의 문제와 해결

슬라이싱 후 view()를 호출할 때 발생하는 에러를 reshape()으로 해결하는 방법입니다.


base = torch.randn(10, 10)
sub = base[:, :5] # 비연속적 텐서 생성

try:
    sub.view(-1)
except RuntimeError:
    # 해결: reshape은 필요 시 자동으로 복사본(copy)을 만듦
    reshaped_sub = sub.reshape(-1)
        

Example 6: 다차원 텐서의 특정 채널 독립적 처리

이미지 처리 시 특정 채널(RGB)만 추출하여 전처리할 때 유용합니다.


img = torch.randn(3, 224, 224) # CHW
red_channel = img[0, :, :].clone() # 빨간색 채널만 독립적으로 연산

# 독립적 연산 수행
red_channel = red_channel * 0.5
        

Example 7: 정수 리스트 인덱싱을 이용한 강제 복사

슬라이싱 기호(:) 대신 리스트를 전달하여 명시적으로 새로운 텐서를 생성합니다.


x = torch.arange(10)
# x[0:3]은 뷰를 반환하지만, x[[0, 1, 2]]는 카피를 반환함
y = x[[0, 1, 2]]
y[0] = 100 # x[0]은 여전히 0임
        

4. 결론 및 심층 권고 사항

파이토치의 메모리 공유 메커니즘은 성능 최적화를 위한 강력한 도구이지만, '예상치 못한 값의 변경'이라는 위험 요소를 내포하고 있습니다. 데이터 무결성이 중요한 학습 루프 내에서는 다음과 같은 원칙을 지키는 것이 좋습니다.

  • 원본 데이터를 보존해야 한다면 반드시 .clone()을 사용하십시오.
  • 메모리 사용량을 줄여야 하는 대규모 연산에서는 슬라이싱과 In-place 연산(add_, sub_ 등)을 적절히 혼합하십시오.
  • 텐서의 주소값이 같은지 확인하려면 tensor1.data_ptr() == tensor2.data_ptr()을 사용하여 검증할 수 있습니다.
728x90