PyTorch 내부 구조 02 - Tensor는 storage, size, stride 위에서 동작한다
텐서를 다차원 배열로만 보면 view와 layout 문제를 잘못 이해하게 된다
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가 달라진다.