Share via


Utilisation de strides pour exprimer le remplissage et la disposition de la mémoire

Les tenseurs DirectML, qui sont soutenus par des mémoires tampons Direct3D 12, sont décrits par les propriétés appelées sizes et strides du tenseur. Les sizes du tenseur décrivent les dimensions logiques du tenseur. Par exemple, un tenseur 2D peut avoir une hauteur de 2 et une largeur de 3. Logiquement, le tenseur a 6 éléments distincts, bien que les tailles ne spécifient pas la façon dont ces éléments sont stockés en mémoire. Les strides du tenseur décrivent la disposition de la mémoire physique des éléments du tenseur.

Tableau à deux dimensions (2D)

Considérez un tenseur 2D qui a une hauteur de 2 et une largeur de 3 dont les données comprennent des caractères textuels. En C/C++, cela peut être exprimé à l’aide d’un tableau multidimensionnel.

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 vue logique du tenseur ci-dessus est visualisée ci-dessous.

A B C
D E F

En C/C++, un tableau multidimensionnel est stocké dans l’ordre en ligne. En d'autres termes, les éléments consécutifs le long de la dimension de la largeur sont stockés de manière contiguë dans l'espace mémoire linéaire.

Décalage : 0 1 2 3 4 5
Valeur : A B C D E F

Le stride d’une dimension est le nombre d’éléments à ignorer pour accéder à l’élément suivant dans cette dimension. Strides exprime la disposition du tenseur en mémoire. Avec dans l’ordre en ligne, le stride de la dimension de largeur est toujours 1, car les éléments adjacents le long de la dimension sont stockés de manière contiguë. Le stride de la dimension de hauteur dépend de la taille de la dimension de largeur. Dans l’exemple ci-dessus, la distance entre les éléments consécutifs le long de la dimension de hauteur (par exemple, de A à D) est égale à la largeur du tenseur (qui est 3 dans cet exemple).

Pour illustrer une autre disposition, envisagez l’ordre en colonnes. En d'autres termes, les éléments consécutifs le long de la dimension de la hauteur sont stockés de manière contiguë dans l'espace mémoire linéaire. Dans ce cas, la hauteur est toujours égale à 1 et la largeur est de 2 (la taille de la dimension de hauteur).

Décalage : 0 1 2 3 4 5
Valeur : A D G E C F

Dimension supérieure

Lorsqu’il y a plus de deux dimensions, il n’est pas très pratique de faire référence à une disposition en ligne ou en colonne. Par conséquent, le reste de cette rubrique utilise des termes et des étiquettes comme ceux-ci.

  • 2D : « HW » : la hauteur est la dimension de classement le plus élevé (en ligne).
  • 2D : « WH » : la largeur est la dimension de classement le plus élevé (en colonne).
  • 3D : « DHW » : la profondeur est la dimension de classement la plus élevée, suivie de la hauteur, puis de la largeur.
  • 3D : « WHD » : la largeur est la dimension de classement le plus élevé, suivie de la hauteur, puis de la profondeur.
  • 4D : « NCHW » : nombre d’images (taille du lot), puis nombre de canaux, puis hauteur, puis largeur.

En règle générale, le stride emballé d’une dimension est égal au produit des tailles des dimensions inférieures. Par exemple, avec une disposition « DHW », le stride D est égale à H * W ; le stride H est égal à W ; et le stride W est égal à 1. Les strides sont dits emballés lorsque la taille physique totale du tenseur est égale à la taille logique totale du tenseur. En d’autres termes, il n’y a pas d’espace supplémentaire ni de chevauchement d’éléments.

Étendons l’exemple 2D à un exemple 3D de manière à disposer d’un tenseur de profondeur 2, de hauteur 2 et de largeur 3 (pour un total de 12 éléments logiques).

A B C
D E F

G H I
J K L

Avec une disposition « DHW », ce tenseur est stocké comme suit.

Décalage : 0 1 2 3 4 5 6 7 8 9 10 11
Valeur : A B C D E F G H I J K L
  • Stride D = hauteur (2) * largeur (3) = 6 (par exemple, la distance entre « A » et « G »).
  • Stride H = largeur (3) = 3 (par exemple, la distance entre « A » et « D »).
  • Stride W = 1 (par exemple, la distance entre « A » et « B »).

Le produit de points des index/coordonnées d’un élément et les strides fournissent le décalage à cet élément dans la mémoire tampon. Par exemple, le décalage de l’élément H (d = 1, h = 0, w = 1) est 7.

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

Tenseurs emballés

Les exemples ci-dessus illustrent les tenseurs emballés. Un tenseur est dit emballé lorsque la taille logique du tenseur (dans les éléments) est égale à la taille physique de la mémoire tampon (dans les éléments) et que chaque élément a une adresse/décalage unique. Par exemple, un tenseur 2 x 2 x 3 est emballé si la mémoire tampon est de 12 éléments de longueur et qu’aucune paire d’éléments ne partage le même décalage dans la mémoire tampon. Les tenseurs emballés sont le cas le plus courant, mais les strides permettent des dispositions de mémoire plus complexes.

Diffusion avec des strides

Si la taille de la mémoire tampon d'un tenseur (en éléments) est inférieure au produit de ses dimensions logiques, il en résulte que certains éléments se chevauchent. Le cas le plus courant est appelé diffusion, c'est-à-dire que les éléments d'une dimension sont la copie d'une autre dimension. Par exemple, examinons l’exemple 2D à nouveau. Supposons que nous voulons logiquement un tenseur 2 x 3, mais que la deuxième ligne est identique à la première ligne. Voici à quoi cela ressemble.

A B C
A B C

Cela peut être stocké sous la forme d’un tenseur emballé en HW/en ligne. Mais un stockage plus compact ne contiendrait que 3 éléments (A, B et C) et utiliserait une hauteur de 0 au lieu de 3. Dans ce cas, la taille physique du tenseur est de 3 éléments, mais la taille logique est de 6 éléments.

En général, si le stride d’une dimension est 0, tous les éléments dans les dimensions de l’ordre inférieur sont alors répétés le long de la dimension diffusée. Par exemple, si le tenseur est NCHW et que le stride C est 0, chaque canal a alors les mêmes valeurs le long de H et W.

Remplissage avec des strides

Un tenseur est dit être rempli si sa taille physique est supérieure à la taille minimale nécessaire pour ajuster ses éléments. Lorsqu’il n’y a pas de diffusion ni de chevauchement d’éléments, la taille minimale du tenseur (dans les éléments) est simplement le produit de ses dimensions. Vous pouvez utiliser la fonction d’assistance DMLCalcBufferTensorSize (voir Fonctions d’assistance DirectML pour obtenir une liste de cette fonction) pour calculer la taille minimale de la mémoire tampon pour vos tenseurs DirectML.

Supposons qu’une mémoire tampon contient les valeurs suivantes (les éléments « x » indiquent des valeurs de remplissage).

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

Le tenseur rempli peut être décrit à l’aide d’une hauteur de stride de 5 au lieu de 3. Au lieu de passer par 3 éléments pour atteindre la rangée suivante, le passage se fait par 5 éléments (3 éléments réels plus 2 éléments de remplissage). Le remplissage est courant dans les représentations graphiques sur ordinateur pour s’assurer, par exemple, qu’une image a un alignement à la puissance de deux.

A B C
D E F

Descriptions des tenseurs de mémoire tampon DirectML

DirectML peut fonctionner avec une variété de dispositions de tenseur physique, car la structure DML_BUFFER_TENSOR_DESC possède à la fois des membres Sizes et Strides. Certaines implémentations d’opérateur peuvent être plus efficaces avec une disposition spécifique. Il n’est donc pas rare de modifier la façon dont les données de tenseur sont stockées pour obtenir de meilleures performances.

La plupart des opérateurs DirectML nécessitent des tenseurs 4D ou 5D, et l’ordre des tailles et des strides est fixe. En corrigeant l’ordre des valeurs sizes et strides dans une description de tenseur, il est possible pour DirectML de déduire différentes dispositions physiques.

4D

5D

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

Si un opérateur DirectML nécessite un tenseur 4D ou 5D, mais que les données réelles ont un rang plus petit (par exemple, 2D), les dimensions de début doivent être remplies avec des 1. Par exemple, un tenseur « HW » est défini à l’aide de DML_BUFFER_TENSOR_DESC::Sizes = { 1, 1, H, W }.

Si les données de tenseur sont stockées dans l’ordre NCHW/NCDHW, il n’est pas nécessaire de définir DML_BUFFER_TENSOR_DESC::Strides, sauf si vous souhaitez diffuser ou remplir. Vous pouvez définir le champ strides sur nullptr. Toutefois, si les données de tenseur sont stockées dans une autre disposition, telle que NHWC, vous avez alors besoin des strides pour exprimer la transformation de NCHW vers cette disposition.

Pour obtenir un exemple simple, considérez la description d’un tenseur 2D avec une hauteur 3 et une largeur 5.

Packed NCHW (strides implicites)

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

Packed NCHW (strides explicites)

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

Packed NHWC

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

Voir aussi