다음을 통해 공유


스트라이드를 사용하여 패딩 및 메모리 레이아웃 표현

Direct3D 12 버퍼에서 지원되는 DirectML 텐서는 텐서의 크기보폭 이라고 하는 속성에 설명되어 있습니다. 텐서의 크기는 텐서의 논리적 차원을 설명합니다. 예를 들어 2D 텐서의 높이가 2이고 너비가 3일 수 있습니다. 논리적으로 텐서에는 6개의 고유 요소가 있지만 크기는 해당 요소가 메모리에 저장되는 방법을 지정하지 않습니다. 텐서의 스트라이드는 텐서 요소의 물리적 메모리 레이아웃을 설명합니다.

2D(2차원) 배열

높이가 2이고 너비가 3인 2D 텐서를 고려합니다. 데이터는 텍스트 문자로 구성됩니다. C/C++에서는 다차원 배열을 사용하여 표현할 수 있습니다.

constexpr int rows = 2;
constexpr int columns = 3;
char tensor[rows][columns];
tensor[0][0] = 'A';
tensor[0][1] = 'B';
tensor[0][2] = 'C';
tensor[1][0] = 'D';
tensor[1][1] = 'E';
tensor[1][2] = 'F';

위의 텐서의 논리적 보기는 아래에서 시각화됩니다.

A B C
D E F

C/C++에서는 다차원 배열이 행 주 순서로 저장됩니다. 즉, 너비 차원을 따라 연속된 요소는 선형 메모리 공간에 연속적으로 저장됩니다.

오프셋: 0 1 2 3 4 5
값: A b C D E F

차원의 보폭 은 해당 차원의 다음 요소에 액세스하기 위해 건너뛸 요소의 수입니다. Strides는 메모리에서 텐서의 레이아웃을 표현합니다. 행 주 순서를 사용하면, 차원을 따라 인접한 요소가 연속적으로 저장되기 때문에 너비 차원의 간격은 항상 1입니다. 높이 차원의 보폭은 너비 차원의 크기에 따라 달라집니다. 위의 예제에서 높이 차원(예: A에서 D)을 따라 연속된 요소 사이의 거리는 텐서의 너비(이 예제에서는 3)와 같습니다.

다른 레이아웃을 설명하려면 열 우선 순서를 고려합니다. 즉, 높이 차원을 따라 연속된 요소는 선형 메모리 공간에 연속적으로 저장됩니다. 이 경우 높이-보폭은 항상 1이고 너비-보폭은 2(높이 차원의 크기)입니다.

오프셋: 0 1 2 3 4 5
값: A D b E C F

상위 차원

2차원보다 큰 경우 레이아웃을 행 우선 또는 열 우선으로 참조하는 것은 다루기 어려울 수 있습니다. 따라서 이 항목의 나머지 부분에는 다음과 같은 용어 및 레이블이 사용됩니다.

  • 2D: "HW"- 높이가 가장 높은 차원(행 주)입니다.
  • 2D: "WH"- 너비가 가장 높은 차원(열 주)입니다.
  • 3D: "DHW"- 깊이가 가장 높은 차원이며 높이와 너비가 뒤따릅니다.
  • 3D: "WHD"- 너비가 가장 높은 차원이며 높이와 깊이가 뒤따릅니다.
  • 4D: "NCHW"- 이미지 수(일괄 처리 크기), 채널 수, 높이, 너비입니다.

일반적으로 차원의 압축된 스트라이드는 하위 차원 크기의 곱과 같습니다. 예를 들어 "DHW" 레이아웃의 D-stride는 H * W와 같습니다. H-stride는 W와 같습니다. W-stride는 1과 같습니다. 스트라이드는 텐서의 총 물리적 크기가 텐서의 총 논리적 크기와 같을 때 압축 되었다고 합니다. 즉, 추가 공간도 없고 겹치는 요소도 없어야 한다.

2D 예제를 3차원으로 확장하여 깊이 2, 높이 2 및 너비 3이 있는 텐서(총 12개의 논리 요소)를 갖도록 하겠습니다.

A B C
D E F

G H I
J K L

"DHW" 레이아웃을 사용하면 이 텐서가 다음과 같이 저장됩니다.

오프셋: 0 1 2 3 4 5 6 7 8 (여덟) 9 10 11
값: A b C D E F G H J 케이
  • D-스트라이드 = 높이 (2) * 너비 (3) = 6 (예: 'A'와 'G' 사이의 거리).
  • H-stride = width (3) = 3(예: 'A'와 'D' 사이의 거리).
  • W-stride = 1(예: 'A'와 'B' 사이의 거리).

요소의 인덱스/좌표와 보폭의 점 곱은 그 요소가 버퍼 내에서 차지하는 위치를 결정하는 데 사용됩니다. 예를 들어 H 요소의 오프셋(d=1, h=0, w=1)은 7입니다.

{1, 0, 1} ⋅ {6, 3, 1} = 1 * 6 + 0 * 3 + 1 * 1 = 7

정렬된 텐서

위의 예제에서는 압축된 텐서를 보여 줍니다. 텐서의 논리적 크기(요소)가 버퍼의 실제 크기(요소)와 같고 각 요소에 고유한 주소/오프셋이 있는 경우 텐서가 압축 되었다고 합니다. 예를 들어 버퍼의 길이가 12개이고 한 쌍의 요소가 버퍼에서 동일한 오프셋을 공유하지 않는 경우 2x2x3 텐서가 압축됩니다. 패킹된 텐서가 가장 일반적인 경우입니다. 하지만 스트라이드는 더 복잡한 메모리 레이아웃을 허용합니다.

발전하는 방송

텐서의 버퍼 크기(단위로)가 논리적 차원의 곱보다 작으면, 요소들 간에 겹침이 발생할 수 있습니다. 이에 대한 일반적인 경우는 브로드캐스팅이라고 합니다. 여기서 차원의 요소는 다른 차원의 중복 요소입니다. 예를 들어 2D 예제를 다시 살펴보겠습니다. 논리적으로 2x3인 텐서를 원하지만 두 번째 행은 첫 번째 행과 동일하다고 가정해 보겠습니다. 그 모습은 다음과 같습니다.

A B C
A B C

압축된 HW/행 주 텐서로 저장할 수 있습니다. 그러나 더 컴팩트한 스토리지는 3개 요소(A, B 및 C)만 포함하고 3이 아닌 0의 높이 보폭을 사용합니다. 이 경우 텐서의 물리적 크기는 3개 요소이지만 논리적 크기는 6개 요소입니다.

일반적으로 차원의 보폭이 0이면 하위 차원의 모든 요소가 브로드캐스트된 차원을 따라 반복됩니다. 예를 들어 텐서가 NCHW이고 C-stride가 0인 경우 각 채널의 값은 H 및 W와 동일합니다.

보폭을 사용하는 패딩

물리적 크기가 요소에 맞는 데 필요한 최소 크기보다 크면 텐서가 패딩이 되어있다고 합니다. 방송이나 중복되는 요소가 없을 때, 텐서의 최소 크기(요소)는 단순히 차원의 곱으로 계산할 수 있습니다. 도우미 함수(해당 함수 DMLCalcBufferTensorSize 목록에 대한 DirectML 도우미 함수 참조)를 사용하여 DirectML 텐서의 최소 버퍼 크기를 계산할 수 있습니다.

버퍼에 다음 값이 포함되어 있다고 가정해 보겠습니다('x' 요소는 패딩 값을 나타낸다).

0 1 2 3 4 5 6 7 8 (여덟) 9
A b C x x D E F x x

패딩된 텐서를 3이 아닌 5의 높이 보폭을 사용하여 설명할 수 있습니다. 다음 행으로 이동하려면 3개 요소를 단계별로 실행하는 대신 5개 요소( 실제 요소 3개와 패딩 요소 2개)가 있습니다. 패딩은 컴퓨터 그래픽에서 일반적입니다. 예를 들어, 이미지를 2의 제곱수 정렬로 맞추기 위해 사용됩니다.

A B C
D E F

DirectML 버퍼 텐서 설명

DirectML은 DML_BUFFER_TENSOR_DESC 구조체에 SizesStrides 멤버가 모두 있으므로 다양한 물리적 텐서 레이아웃에서 작동할 수 있습니다. 일부 연산자 구현은 특정 레이아웃에서 더 효율적일 수 있으므로 성능 향상을 위해 텐서 데이터가 저장되는 방식을 변경하는 것은 드문 일이 아닙니다.

대부분의 DirectML 연산자에는 4D 또는 5D 텐서가 필요하며 크기 및 보폭 값의 순서가 고정되어 있습니다. 텐서 설명에서 크기 및 스트라이드 값의 순서를 수정하면 DirectML에서 다양한 실제 레이아웃을 유추할 수 있습니다.

4차원

5D

  • DML_BUFFER_TENSOR_DESC::크기 = { N 크기, C 크기, D 크기, H 크기, W 크기 }
  • DML_BUFFER_TENSOR_DESC::보폭 = { N-보폭, C-보폭, D-보폭, H-보폭, W-보폭 }

DirectML 연산자에 4D 또는 5D 텐서가 필요하지만 실제 데이터에 더 작은 순위(예: 2D)가 있는 경우 선행 차원은 1로 채워져야 합니다. 예를 들어 "HW" 텐서가 DML_BUFFER_TENSOR_DESC::Sizes = { 1, 1, H, W }를 사용하여 설정됩니다.

텐서 데이터가 NCHW/NCDHW에 저장되는 경우 브로드캐스트 또는 패딩을 원하지 않는 한 DML_BUFFER_TENSOR_DESC::Strides를 설정할 필요가 없습니다. strides 필드를 nullptr로 설정할 수 있습니다. 그러나 텐서 데이터가 NHWC와 같은 다른 레이아웃에 저장되는 경우 NCHW에서 해당 레이아웃으로 변환을 표현하려면 보폭이 필요합니다.

간단한 예제에서는 높이가 3이고 너비가 5인 2D 텐서에 대한 설명을 고려해 보세요.

패킹된 NCHW(암시적 스트라이드)

  • DML_BUFFER_TENSOR_DESC::크기 = { 1, 1, 3, 5 }
  • DML_BUFFER_TENSOR_DESC::보폭 = nullptr

압축된 NCHW(명시적 스트라이드)

  • N-스트라이드 = C 크기 * H 크기 * W 크기 = 1 * 3 * 5 = 15
  • C-stride = H 크기 * W 크기 = 3 * 5 = 15
  • H-보폭 = W-크기 = 5
  • W-보폭 = 1
  • DML_BUFFER_TENSOR_DESC::크기 = { 1, 1, 3, 5 }
  • DML_BUFFER_TENSOR_DESC::보폭 = { 15, 15, 5, 1 }

압축된 NHWC

  • N-stride = H-크기 * W-크기 * C-크기 = 3 * 5 * 1 = 15
  • H-stride = W-크기 * C-크기 = 5 * 1 = 5
  • W-보폭 = C-크기 = 1
  • C-스트라이드 = 1
  • DML_BUFFER_TENSOR_DESC::크기 = { 1, 1, 3, 5 }
  • DML_BUFFER_TENSOR_DESC::보폭 = { 15, 1, 5, 1 }

참고하십시오