티스토리 뷰

728x90
반응형

 

PyTorch와 같은 딥러닝 프레임워크를 공부하다 보면, 자주 등장하지만 처음에는 헷갈릴 수 있는 개념 중 하나가 바로 "In-place 연산"입니다. 이는 새로운 값을 담을 공간을 만들지 않고, 기존 메모리 공간에서 직접 값을 수정하는 연산을 뜻합니다. 개념을 이해하고 나면 optimizer.zero_grad()나 optimizer.step()과 같은 연산에서 왜 = 할당 없이도 값이 바뀌는지 자연스럽게 이해할 수 있습니다.


예제로 보는 In-place 연산

import torch

# 일반 연산
x = torch.tensor([1.0, 2.0, 3.0])
y = x + 1
print(x)  # tensor([1., 2., 3.])
print(y)  # tensor([2., 3., 4.])

# In-place 연산
x.add_(1)
print(x)  # tensor([2., 3., 4.])

add_()처럼 언더스코어(_)가 붙은 PyTorch의 연산 함수들은 In-place 연산을 수행합니다. 이 연산은 기존 텐서 x의 값을 직접 바꾸며, 새로운 텐서를 생성하지 않습니다.


Python의 객체, 참조, 그리고 변경 가능성

  • Python에서는 모든 데이터가 '객체'입니다.
  • 변수는 객체를 가리키는 '참조(reference)'에 불과합니다.
  • 객체는 두 가지로 나뉩니다:
    • 변경 불가능 (Immutable): int, float, str, tuple
    • 변경 가능 (Mutable): list, dict, torch.Tensor

예제 1: 변경 불가능한 객체

x = 10
y = x
x = x + 1
print(x, y)  # 11 10

x = x + 1은 새로운 객체 11을 만들고 x가 그것을 가리키게 됩니다.

예제 2: 변경 가능한 객체

x = [1, 2, 3]
x.append(4)
print(x)  # [1, 2, 3, 4]

append()는 기존 객체 x를 그대로 수정합니다. 이게 바로 In-place 연산입니다.


PyTorch Tensor와 In-place 연산

PyTorch의 Tensor는 변경 가능한 객체입니다. 텐서는 실제 데이터를 메모리에 연속적으로 저장하고 있으며, 다음과 같은 속성을 함께 가지고 있습니다:

  • 데이터 값 (data)
  • 모양 (shape)
  • 자료형 (dtype)
  • 장치 정보 (device)
  • 기울기 저장 공간 (.grad)
x = torch.tensor([1., 2., 3.], requires_grad=True)
print(x.grad)  # None (backward 전)
  • .grad는 backward가 수행된 이후에 gradient 값이 저장되는 텐서입니다.
  • 이 .grad 역시 Tensor이기 때문에 In-place 연산이 가능합니다.

Optimizer가 In-place 연산을 사용하는 방식

optimizer.zero_grad()의 역할

for p in model.parameters():
    if p.grad is not None:
        p.grad.zero_()

모델의 모든 파라미터에 대해 .grad가 존재한다면, 해당 gradient를 In-place 연산으로 0으로 초기화합니다.

optimizer.step()의 역할

for p in model.parameters():
    p.data.add_(-lr * p.grad)

학습률(lr)과 gradient를 곱한 값을 현재 파라미터에 In-place로 더해줍니다. 즉, 새로운 파라미터 객체를 만들지 않고 기존 값을 직접 수정합니다.

이러한 방식이 가능한 이유는 optimizer가 모델 파라미터 객체의 '참조'를 가지고 있기 때문입니다.


In-place 연산의 장점

이유 설명

메모리 효율 새 텐서를 생성하지 않기 때문에 메모리 사용량을 줄일 수 있습니다.
속도 메모리 재할당이 없으므로 연산 속도가 더 빠릅니다.
간결한 코드 값의 복사 없이 모델 파라미터를 바로 수정할 수 있습니다.

주의할 점: Autograd와의 충돌

In-place 연산은 편리하지만, 자동 미분(autograd)과 함께 사용할 때는 주의해야 합니다. 이미 autograd 연산 그래프에 연결된 텐서에 대해 In-place 연산을 수행하면 오류가 발생할 수 있습니다.

a = torch.tensor([2.], requires_grad=True)
b = a * 3
b.backward()
a.add_(1)  # RuntimeError 발생

이 경우, a.clone()이나 a.detach()로 복사본을 만든 후 연산을 수행하는 것이 안전합니다.


마무리 정리

In-place 연산은 PyTorch 텐서가 갖는 변경 가능성(Mutability)을 적극 활용하는 중요한 기술입니다. 새로운 객체를 만들지 않고 기존 객체를 직접 수정함으로써 메모리 효율성과 속도를 높이며, 학습 과정에서 필요한 파라미터 업데이트 등을 더 간단하게 처리할 수 있습니다.

특히 optimizer의 zero_grad()와 step() 내부에서 어떻게 In-place 연산이 사용되는지를 이해하면, PyTorch의 작동 방식과 연산 흐름을 훨씬 깊이 있게 파악할 수 있습니다.

728x90
반응형