Uso de intervalos para expresar relleno y disposición de memoria

Los tensores de DirectML, que respaldan los búferes de Direct3D 12, se describen mediante propiedades conocidas como sizes y los strides del tensor. La propiedad sizes (tamaños) del tensor describe las dimensiones lógicas del tensor. Por ejemplo, un tensor 2D puede tener un alto de 2 y un ancho de 3. Lógicamente, el tensor tiene 6 elementos distintos, aunque los tamaños no especifican cómo se almacenan esos elementos en la memoria. La propiedad strides (intervalos) del sensor describe el diseño de memoria física de los elementos del tensor.

Dos matrices bidimensionales (2D)

Piense en un tensor 2D que tiene un alto de 2 y un ancho de 3; los datos constan de caracteres textuales. En C/C++, esto puede expresarse mediante una matriz multidimensional.

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';

La vista lógica del tensor anterior se visualiza a continuación.

A B C
D E F

En C/C++, una matriz multidimensional se almacena según el orden principal de fila. En otras palabras, los elementos consecutivos a lo largo de la dimensión de ancho se almacenan de forma contigua en el espacio de memoria lineal.

Desplazamiento: 0 1 2 3 4 5
Valor: A B C D E F

El intervalo de una dimensión es el número de elementos que se van a omitir para tener acceso al siguiente elemento de esa dimensión. Los intervalos expresan el diseño del tensor en la memoria. Con un orden principal de fila, el intervalo de la dimensión de ancho siempre es 1, ya que los elementos adyacentes a lo largo de la dimensión se almacenan de forma contigua. El intervalo de la dimensión de alto depende del tamaño de la dimensión de ancho; en el ejemplo anterior, la distancia entre los elementos consecutivos a lo largo de la dimensión de alto (por ejemplo, de A a D) es igual al ancho del tensor (que es 3 en este ejemplo).

Para ilustrar un diseño diferente, considere el orden principal de columna. En otras palabras, los elementos consecutivos a lo largo de la dimensión de altura se almacenan de forma contigua en el espacio de memoria lineal. En este caso, el intervalo de altura siempre es 1 y el intervalo de ancho es 2 (el tamaño de la dimensión de alto).

Desplazamiento: 0 1 2 3 4 5
Valor: A D B E C V

Dimensiones más altas

Cuando se trata de más de dos dimensiones, no se puede hacer referencia a un diseño como principal de fila o principal de columna. Por lo tanto, en el resto de este tema se usan términos y etiquetas como estos.

  • 2D: "HW": el alto es la dimensión de orden más alto (principal de fila).
  • 2D: "WH": el ancho es la dimensión de orden más alto (principal de columna).
  • 3D: "DHW": la profundidad es la dimensión de orden más alto, seguida de la altura y, a continuación, el ancho.
  • 3D: "WHD": el ancho es la dimensión de orden más alto, seguida de la altura y, a continuación, la profundidad.
  • 4D: "NCHW": el número de imágenes (tamaño del lote), el número de canales, el alto y el ancho.

En general, el intervalo total de una dimensión es igual al producto de los tamaños de las dimensiones de orden inferior. Por ejemplo, con un diseño "DHW", el intervalo D es igual a H * W; el intervalo H es igual a W; y el intervalo W es igual a 1. Se dice que los intervalos se empaquetan cuando el tamaño físico total del tensor es igual al tamaño lógico total del tensor; en otras palabras, no hay espacio adicional ni elementos superpuestos.

Vamos a ampliar el ejemplo 2D a tres dimensiones, de modo que tengamos un tensor con profundidad 2, alto 2 y ancho 3 (para un total de 12 elementos lógicos).

A B C
D E F

G H I
J K L

Con un diseño "DHW", este tensor se almacena de la siguiente manera.

Desplazamiento: 0 1 2 3 4 5 6 7 8 9 10 11
Valor: A B C D E F G H I J K L
  • Intervalo D = alto (2) * ancho (3) = 6 (por ejemplo, la distancia entre "A" y "G").
  • Intervalo H = ancho (3) = 3 (por ejemplo, la distancia entre "A" y "D").
  • Intervalo W = 1 (por ejemplo, la distancia entre "A" y "B").

El producto de punto de los índices o coordenadas de un elemento y los intervalos proporcionan el desplazamiento a ese elemento en el búfer. Por ejemplo, el desplazamiento del elemento H (d=1, h=0, w=1) es 7.

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

Tensores empaquetados

Los ejemplos anteriores ilustran tensores empaquetados. Se dice que un tensor se empaqueta cuando el tamaño lógico del tensor (en los elementos) es igual al tamaño físico del búfer (en los elementos) y cada elemento tiene una dirección/desplazamiento única. Por ejemplo, se empaqueta un tensor 2x2x3 si el búfer tiene 12 elementos de longitud y ningún par de elementos comparten el mismo desplazamiento en el búfer. Los tensores empaquetados son el caso más común; pero los intervalos permiten diseños de memoria más complejos.

Difusión con intervalos

Si el tamaño de búfer de un tensor (en los elementos) es menor que el producto de sus dimensiones lógicas, debe haber cierta superposición de elementos. El caso más habitual se conoce como difusión; donde los elementos de una dimensión son duplicados de otra dimensión. Por ejemplo, vamos a revisar el ejemplo 2D. Supongamos que queremos un tensor que lógicamente es 2x3, pero la segunda fila es idéntica a la primera fila. Este es su aspecto.

A B C
A B C

Esto se puede almacenar como un tensor HW/principal de fila empaquetado. Pero un almacenamiento más compacto solo contendría 3 elementos (A, B y C) y usaría un intervalo de altura de 0 en lugar de 3. En este caso, el tamaño físico del tensor es de 3 elementos, pero el tamaño lógico es de 6 elementos.

En general, si el intervalo de una dimensión es 0, todos los elementos de las dimensiones de orden inferior se repiten a lo largo de la dimensión retransmitida. Por ejemplo, si el tensor es NCHW y el paso de C es 0, cada canal tiene los mismos valores a lo largo de H y W.

Relleno con intervalos

Se dice que un tensor se rellena si su tamaño físico es mayor que el tamaño mínimo necesario para ajustarse a sus elementos. Cuando no hay elementos de difusión ni superposición, el tamaño mínimo del tensor (en los elementos) es simplemente el producto de sus dimensiones. Puede usar la función DMLCalcBufferTensorSize auxiliar (consulte Funciones auxiliares de DirectML para obtener una lista de esa función) para calcular el tamaño mínimo del búfer para los tensores de DirectML.

Supongamos que un búfer contiene los valores siguientes (los elementos "x" indican valores de relleno).

0 1 2 3 4 5 6 7 8 9
A B C x x D E F x x

El tensor de relleno se puede describir mediante un intervalo de altura de 5 en lugar de 3. En lugar de recorrer paso intervalo a intervalo 3 elementos para llegar a la siguiente fila, el intervalo es de 5 elementos (3 elementos reales más 2 elementos de relleno). El relleno es común en los gráficos informáticos, por ejemplo, para asegurarse de que una imagen tiene una alineación de potencia de dos.

A B C
D E F

Descripciones de tensor del búfer de DirectML

DirectML puede trabajar con una variedad de diseños de tensor físicos, ya que la estructura DML_BUFFER_TENSOR_DESC tiene los dos miembros Sizes y Strides . Algunas implementaciones de operador pueden ser más eficaces con un diseño específico, por lo que no es raro cambiar cómo se almacenan los datos del tensor para mejorar el rendimiento.

La mayoría de los operadores DirectML requieren tensores 4D o 5D, y el orden de los tamaños y los valores de intervalos es fijo. Al corregir el orden de los tamaños y los valores de intervalos en una descripción de tensor, es posible que DirectML infiera diferentes diseños físicos.

4D

5D

  • DML_BUFFER_TENSOR_DESC::Sizes = { N-size, C-size, H-size, W-size }
  • DML_BUFFER_TENSOR_DESC::Strides = { N-stride, C-stride, D-stride, H-stride, W-stride }

Si un operador DirectML requiere un tensor 4D o 5D, pero los datos reales tienen una clasificación más pequeña (por ejemplo, 2D), las dimensiones iniciales deben rellenarse con 1s. Por ejemplo, un tensor "HW" se establece mediante DML_BUFFER_TENSOR_DESC::Sizes = { 1, 1, H, W }.

Si los datos del tensor se almacenan en NCHW/NCDHW, no es necesario establecer DML_BUFFER_TENSOR_DESC::Strides, a menos que desee difundir o rellenar. Puede establecer el campo de intervalos en nullptr. Sin embargo, si los datos del tensor se almacenan en otro diseño, como NHWC, necesita intervalos para expresar la transformación de NCHW a ese diseño.

Para ver un ejemplo sencillo, piense la descripción de un tensor 2D con alto 3 y ancho 5.

NCHW empaquetado (intervalos implícitos)

  • DML_BUFFER_TENSOR_DESC::Sizes = { 1, 1, 3, 5 }
  • DML_BUFFER_TENSOR_DESC::Strides = nullptr

NCHW empaquetado (intervalos explícitos)

  • N-stride = C-size * H-size * W-size = 1 * 3 * 5 = 15
  • C-stride = H-size * W-size = 3 * 5 = 15
  • Intervalo H = tamaño W = 5
  • Intervalo de W = 1
  • DML_BUFFER_TENSOR_DESC::Sizes = { 1, 1, 3, 5 }
  • DML_BUFFER_TENSOR_DESC::Strides = { 15, 15, 5, 1 }

NHWC empaquetado

  • N-stride = H-size * W-size * C-size = 3 * 5 * 1 = 15
  • H-stride = W-size * C-size = 5 * 1 = 5
  • Intervalo de W = tamaño C = 1
  • Intervalo de C = 1
  • DML_BUFFER_TENSOR_DESC::Sizes = { 1, 1, 3, 5 }
  • DML_BUFFER_TENSOR_DESC::Strides = { 15, 1, 5, 1 }

Consulte también