브로드캐스팅

브로드캐스팅 (Broadcasting semantics)

두 텐서를 더할 때 모양(shape)이 서로 달라도 오류가 나지 않아 당황한 적 있죠? PyTorch가 넘파이의 브로드캐스팅(broadcasting) 규칙에 따라 작은 텐서를 자동으로 확장해 주기 때문이에요. 데이터를 실제로 복사하지 않고 논리적으로만 크기를 맞춰서, 더하기 같은 연산이 훨씬 유연해져요.

출처: PyTorch 공식 문서 — Broadcasting semantics

브로드캐스트 가능한 조건

두 텐서가 다음 규칙을 모두 만족하면 "브로드캐스트 가능(broadcastable)"해요.

  • 각 텐서는 최소 한 개 이상의 차원을 가져야 해요.
  • 뒷자리(trailing) 차원부터 크기를 비교했을 때, 두 크기가 같거나, 둘 중 하나가 1이거나, 한쪽이 존재하지 않아야 해요.
>>> x = torch.empty(5, 7, 3)
>>> y = torch.empty(5, 7, 3)
# 같은 모양이면 항상 브로드캐스트 가능

>>> x = torch.empty((0,))
>>> y = torch.empty(2, 2)
# x가 차원을 하나도 갖지 않아 브로드캐스트 불가

# 뒷자리 차원부터 맞출 수 있는 경우
>>> x = torch.empty(5, 3, 4, 1)
>>> y = torch.empty(3, 1, 1)
# 1번째 뒷자리: 둘 다 크기 1
# 2번째 뒷자리: y가 크기 1
# 3번째 뒷자리: x 크기 == y 크기
# 4번째 뒷자리: y 차원이 존재하지 않음

결과 모양 계산

브로드캐스트 가능하면 결과 텐서의 크기는 이렇게 계산돼요. 차원 수가 다르면 차원이 적은 쪽 앞에 1을 붙여 길이를 맞추고, 그다음 각 차원에서 두 크기의 최댓값이 결과 크기가 돼요.

>>> x = torch.empty(5, 1, 4, 1)
>>> y = torch.empty(3, 1, 1)
>>> (x + y).size()
torch.Size([5, 3, 4, 1])

>>> x = torch.empty(1)
>>> y = torch.empty(3, 1, 7)
>>> (x + y).size()
torch.Size([3, 1, 7])

>>> x = torch.empty(5, 2, 4, 1)
>>> y = torch.empty(3, 1, 1)
>>> (x + y).size()
RuntimeError: The size of tensor a (2) must match the size of tensor b (3) at non-singleton dimension 1

in-place 연산의 제약

in-place 연산은 결과로 자기 자신의 모양이 바뀌는 것을 허용하지 않아요. 따라서 원본이 바뀌는 add_() 같은 연산은 브로드캐스트 결과가 원래 크기를 넘어설 수 없어요.

>>> x = torch.empty(5, 3, 4, 1)
>>> y = torch.empty(3, 1, 1)
>>> (x.add_(y)).size()
torch.Size([5, 3, 4, 1])

# 원래 크기를 넘어서는 브로드캐스트는 오류
>>> x = torch.empty(1, 3, 1)
>>> y = torch.empty(3, 1, 7)
>>> (x.add_(y)).size()
RuntimeError: The expanded size of the tensor (1) must match the existing size (7) at non-singleton dimension 2.

이전 버전과의 호환성

옛 PyTorch는 원소 개수만 같으면 모양이 달라도 특정 pointwise 함수를 실행했어요. 지금도 브로드캐스팅을 지원하지만, "1차원으로 보고 처리하는" 옛 방식은 deprecated 라서 모양이 다르면서 원소 개수가 같은 경우 Python 경고가 나와요. torch.utils.backcompat.broadcast_warning.enabledTrue 로 두면 이런 비호환 지점을 경고로 찾아낼 수 있어요.

더 알아보기