tensor를 평면적으로 보면 놓치는 것

PyTorch tensor는 단순한 "2차원 행렬"이나 "N차원 배열"이 아니다. 내부적으로는 대략 다음 요소의 조합으로 이해하는 편이 좋다.

  • storage: 실제 데이터가 놓인 메모리 버퍼
  • size: 각 차원의 길이
  • stride: 다음 원소로 가기 위해 storage에서 얼마나 건너뛰는지
  • offset: storage 안에서 시작 위치

이 구조를 알아야 view, transpose, permute, narrow 같은 연산이 왜 어떤 때는 복사 없이 되고 어떤 때는 복사가 필요한지 이해할 수 있다.

stride가 중요한 이유

예를 들어 (B, T, H) 텐서에서 마지막 차원이 연속적이면 특정 연산은 매우 자연스럽게 접근할 수 있다. 반대로 permute로 차원 순서를 바꾸면 shape는 바뀌지 않아도 stride가 달라져 메모리 접근 패턴이 완전히 달라질 수 있다.

이때 중요한 것은 "값이 같아 보인다"가 아니라 "메모리에서 어떻게 걸어가느냐"다.

view는 왜 싸게 느껴질까

view류 연산은 가능할 때 기존 storage를 재해석한다. 즉 새 메모리를 만들지 않고 metadata만 바꾸는 셈이다. 그래서 싸다. 하지만 stride 조건이 맞지 않으면 원하는 shape 재해석이 불가능해지고, 이때는 contiguous() 같은 복사 경로가 개입할 수 있다.

실무에서 자주 만나는 문제

  • custom kernel이 contiguous만 가정하고 있어서 깨짐
  • hidden copy 때문에 메모리와 시간이 예상보다 많이 듦
  • 같은 shape인데 layout이 달라 성능 차이가 남

이 문제는 대부분 "tensor는 값을 담은 박스"라는 감각에서 온다. 실제로는 metadata와 storage 관계를 같이 봐야 한다.

다음 글에서는 contiguous와 memory format을 더 직접적으로 본다. 같은 tensor라도 layout이 다르면 kernel 성능과 hidden copy가 달라진다.