본문 바로가기

자연어 처리 과정

Contiguous는 도대체 뭘까?

개요

torch에 존재하는 contiguous()가 도대체 어떤 method인지 알아보자.

 

1. contiguous란 무엇인가?

2. 마무리

 

 

 

Contiguous란 무엇인가?

Contiguous의 의미

무작정 contiguous라는 영단어의 의미부터 살펴보자면 "인접하고 있는", "근접한"이라는 의미가 나타난다.

어떤 메시지를 전달하려는지는 파악이 되지 않더라도 contiguous가 뭔가가 붙어있어야 한다는 뉘앙스를 풍긴다는 것만 인지하자!

 

 

Contiguous의 이해

t = torch.tensor([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]])

위와 같은 tensor가 하나 있다고 생각해보자.

t라는 tensor는 현재 아래와 같은 모양으로 저장되어 있다.

그렇다면 메모리에는?

아래와 같이 저장되어 있다.

tensor가 저장된 shape에서 인접하는 element들과

tensor의 값이 메모리에 저장되었을 때 인접하는 element들이 같음을 확인할 수 있다.

그러므로 우리가 가지고 있는 현재 tensor t는 contiguous 하다는 것을 알 수 있다.

코드로는 아래와 같이 확인할 수 있다.

print(t.is_contiguous())
# True

 

여기서 잠깐 생각해보자면, contiguous 하지 않다는 것은 메모리에 값을 할당했을 때

붙어있어야 하는 값들이 아니라, 다른 값들과 인접하게 된다는 것이다.

즉, 이런 상황이 발생하려면?

tensor가 기존 shape에서 reshape이 될 때 contiguous 하지 않은 tensor가 만들어질 수 있다는 것이다.

메모리에 할당되는 값의 위치도 변해야 하기 때문이다.

 

이해를 위해 contiguous 하지 않은 상황을 살펴보자.

 

Contiguous 하지 않은 상황

t_trans = t.T
print(t_trans.is_contiguous())
# False

단순하게 우리가 가지고 있던 tensor t를 transpose 시켜봤다.

그렇다면 우리의 t는 아래와 같이 shape이 변할 것이다.

그럼 이때 메모리에 이 tensor의 값을 할당하게 된다면, 어떤 모습으로 값들이 할당될까?

정답은 아래와 같다.

즉, 메모리에 할당된 값들은 tensor의 shape이 transpose 됐다는 것을 반영하지 않았다.

이때, 우리의 transpost된 tensor에서 볼 수 있듯 값 0 옆에는 4가 있어야 하지만,

메모리 상에서는 0 옆에는 1이 저장되어 있는 것을 확인할 수 있다.

 

따라서, t_trans라는 이름의 tensor는 contiguous하지 않다.

 

그래서 이를 어떻게 contiguous하게 바꿔줄 수 있을까?

 

Contiguous의 사용

t_trans = t_trans.contiguous()
print(t_trans.is_contiguous())
# True

이때 사용되는 것이 바로 contiguous()이다!

 

간단하게 contiguous만 적어준다면, 우리의 메모리는 아래와 같이 값을 저장한다.

 

*추가: 우리의 tensor 값이 메모리에 어떤 방식으로 저장되어 있는지 어떻게 알 수 있을까?

그것은 바로 stride()를 사용하면 확인할 수 있다!

stride란 "얼마나 건너뛰어야"하는지 알려주는 메소드인데 아래에서 추가적으로 설명하려고 한다!

위 사진은 tensor t의 값을 메모리에 할당했을 때, 값들이 메모리에 저장되는 모습이다.

이때 tensor t의 stride를 확인해보자.

print(t.stride())
# (4, 1)

이렇게 return된 (4, 1)은 무엇을 의미할까?

4: 다음 row(tensor t의 shape(3, 4) 기준)로 가려면 4칸 가면 된다.

1: 다음 element로 가려면 1칸 가면 된다.

 

stride가 알려준 값은 우리의 tensor t의 shape과 비교해봤을 때도 알맞은 값이다.

다음 row로 가려면 column에는 4개의 값이 있기에 4칸을 뛰어야 하고

같은 줄의 다음 element를 찾으려면 1칸만 가면 된다.(contiguous 하니까)

 

 

하지만 contiguous 하지 않을 때, stride는 어떤 값을 내뱉을까?

t_trans = t.T
print(t_trans.stride())
# (1, 4)

t를 transpose했을 때 stride를 찍어보니, 반환된 값은 (1,4) 이다.

이를 해석해보면,

1: 다음 row로 가려면 1칸만 가면 된다.

4: 같은 line의 다음 element로 가려면 4칸을 가면 된다.

하지만, t_trans의 shape은 4 x 3으로 3칸을 가야 다음 row로 향할 수 있다.

그리고 당연하게도 다음 element는 1칸만 가면 만날 수 있다.

stride값이 위와 같이 나온 이유

tensor의 값들이 메모리에 아래와 같이 저장되어 있기 때문이다.

메모리 상에서는 1칸을 가면 바로 다음 row로 갈 수 있다.

그리고 4칸을 가야 0옆에 붙어있는 4를 만날 수 있다.

 

* 이렇게 우리는 stride를 통해 우리의 값이 어떻게 메모리에 저장되어 있는지 확인할 수 있다.

나의 생각이지만, stride를 확인했을 때 shape의 column과 stride의 첫 번째 값이 일치한다면

그리고 stride의 두 번째 값이 1이라면 contiguous하다고 말할 수 있을 것이다.

 

 

마무리

오늘은 이렇게 contiguous와 추가적으로 stride 메소드를 알아봤다.

tensor 연산에서 tensor가 contiguous 하지 않게끔 메모리에 할당이 되어 있다면 에러가 날 가능성이 존재하니 꼭 확인해보자.

 


Reference

https://titania7777.tistory.com/m/3

https://aigong.tistory.com/430

https://stackoverflow.com/a/69599806