본문 바로가기
C.W.K.
Stream
Lesson 07 of 11 · published

제자리 연산과 끝의 밑줄

~10 min · inplace, memory, autograd

Level 0텐서 탐구자
0 XP0/62 lessons0/13 achievements
0/120 XP to next level120 XP to go0% complete

이름 끝의 밑줄은 제자리 변경을 뜻해

이름 끝에 밑줄이 붙은 PyTorch 연산은 원래 텐서를 제자리에서 바꿔. add_, mul_, zero_, fill_, uniform_, normal_, clamp_가 모두 같은 관례를 따라. 밑줄이 없는 버전은 입력을 그대로 두고 새 텐서를 반환해.

이 차이가 중요한 이유는 두 가지야:

  1. 메모리. 제자리 연산은 새 텐서 할당을 피할 수 있어. 매개변수가 10억 개인 모델에서는 의미 있는 차이가 나.
  2. Autograd 안전성. 제자리 연산은 autograd가 역전파에 쓰려고 보관한 값을 훼손할 수 있어. PyTorch가 이를 감지하면 명확한 오류를 내지만 원칙은 간단해. 역전파에 필요한 텐서는 바꾸지 마.

제자리 연산을 일상적으로 쓰는 곳

  • optimizer.zero_grad()는 각 매개변수에서 p.grad.zero_()를 호출해. set_to_none=True를 넘기면 대신 p.grad = None으로 설정하며, 이 방식이 현대적인 PyTorch의 기본값이고 조금 더 빨라.
  • 사용자 정의 가중치 초기화에서는 보통 torch.no_grad() 블록 안에서 .uniform_()이나 .normal_()을 써.
  • 교사 모델이나 모멘텀 신경망의 EMA(지수 이동 평균) 갱신도 제자리 연산이 잘 맞는 대표 사례야.

이런 경우가 아니라면 제자리 연산이 아닌 버전을 우선해. 일반적인 모델 코드에서 얻는 작은 메모리 절약은 autograd를 망가뜨릴 위험에 비해 거의 가치가 없어.

Code

새 텐서를 만드는 연산과 제자리 연산·python
import torch

t = torch.tensor([1.0, 2.0, 3.0])

# Out-of-place: returns new tensor, original untouched
t2 = t.add(5)
print(t)   # tensor([1., 2., 3.])
print(t2)  # tensor([6., 7., 8.])

# In-place: mutates t, returns t for chaining
t.add_(5)
print(t)   # tensor([6., 7., 8.])

# Chaining
t.mul_(2).clamp_(0, 100)
print(t)   # tensor([12., 14., 16.])
no_grad 안의 제자리: 가중치 초기화·python
import torch
import torch.nn as nn

linear = nn.Linear(10, 4)

# Custom Xavier init — must be inside no_grad to avoid autograd-tracking
with torch.no_grad():
    bound = (6.0 / (linear.in_features + linear.out_features)) ** 0.5
    linear.weight.uniform_(-bound, bound)
    linear.bias.zero_()

print(linear.weight.std())  # roughly Xavier-shaped
autograd가 제자리 연산을 꺼리는 이유: 작은 예제·python
import torch

x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = x ** 2          # y depends on x's values for the backward
y.sum().backward()  # works
print(x.grad)       # tensor([2., 4., 6.])

# Now mutate x AFTER the forward but BEFORE backward — invalid
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = x ** 2
x.add_(100)         # corrupts the value autograd needs
try:
    y.sum().backward()
except RuntimeError as e:
    print(type(e).__name__, str(e)[:80])
# RuntimeError: a leaf Variable that requires grad is being used in an in-place operation.

External links

Exercise

수동 EMA 갱신을 구현해. 같은 모양의 두 텐서 onlinetarget이 주어졌을 때 target = 0.99 * target + 0.01 * online을 새 텐서 할당 없이 제자리 연산으로 계산해. 갱신 전후 target.data_ptr()가 같은지 확인해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.