Verwenden von Schritten zum Ausdrücken von Auffüllung, Arbeitsspeicherlayout

DirectML-Tensoren, die von Direct3D 12-Puffern unterstützt werden, werden durch Eigenschaften beschrieben, die als Größen und Fortschritte des Tensors bezeichnet werden. Die Größen des Tensors beschreiben die logischen Dimensionen des Tensors. Beispielsweise kann ein 2D-Tensor eine Höhe von 2 und eine Breite von 3 aufweisen. Logisch weist der Tensor 6 unterschiedliche Elemente auf, obwohl die Größen nicht angeben, wie diese Elemente im Speicher gespeichert werden. Die Fortschritte des Tensors beschreiben das Layout des physischen Speicher der Tensorelemente.

Zweidimensionale (2D) Arrays

Betrachten Sie einen 2D-Tensor mit einer Höhe von 2 und einer Breite von 3; die Daten bestehen aus Textzeichen. In C/C++ kann dies mithilfe eines mehrdimensionalen Arrays ausgedrückt werden.

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

Die logische Ansicht des obigen Tensors wird unten dargestellt.

A B C
D E F

In C/C++ wird ein mehrdimensionales Array in der Zeilen-Hauptreihenfolge gespeichert. Mit anderen Worten, die aufeinander folgenden Elemente entlang der Breitendimension werden zusammenhängend im linearen Speicherbereich gespeichert.

Offset: 0 1 2 3 4 5
Wert: H B C D E F

Der Schritt einer Dimension ist die Anzahl der zu überspringenden Elemente, um auf das nächste Element in dieser Dimension zuzugreifen. Fortschritte drücken das Layout des Tensors im Arbeitsspeicher aus. Bei einer Zeilenmastreihenfolge ist die Breitedimension immer 1, da angrenzende Elemente entlang der Dimension zusammenhängend gespeichert werden. Der Abstand der Höhe hängt von der Größe der Breite ab; im obigen Beispiel entspricht der Abstand zwischen aufeinander folgenden Elementen entlang der Höhendimension (z. B. A bis D) der Breite des Tensors (in diesem Beispiel 3).

Wenn Sie ein anderes Layout veranschaulichen möchten, sollten Sie die Spalten-Hauptreihenfolge in Betracht ziehen. Mit anderen Worten, die aufeinander folgenden Elemente entlang der Höhendimension werden zusammenhängend im linearen Arbeitsspeicher gespeichert. In diesem Fall ist der Höhe-Fortschritt immer 1, und der Breite-Fortschritt ist 2 (die Größe der Höhe-Dimension).

Offset: 0 1 2 3 4 5
Wert: Ein D b E C F

Höhere Dimensionen

Wenn es um mehr als zwei Dimensionen geht, ist es unübersichtlich, auf ein Layout als Zeilen-Haupt- oder Spalten-Hauptformat zu verweisen. Der Rest dieses Themas verwendet also Begriffe und Bezeichnungen wie diese.

  • 2D: „HW“ – Höhe ist die Dimension der höchsten Reihenfolge (Zeilenmathematik).
  • 2D: „WH“: Breite ist die Dimension der höchsten Reihenfolge (Spaltenmathematik).
  • 3D: „DHW“ – Tiefe ist die höchste Dimension, gefolgt von Höhe und dann Breite.
  • 3D: „WHD“ – Breite ist die höchste Dimension, gefolgt von Höhe und dann Tiefe.
  • 4D: „NCHW“ – die Anzahl der Bilder (Batchgröße), dann die Anzahl der Kanäle, dann Höhe und Breite.

Im Allgemeinen entspricht der gepackte Fortschritt einer Dimension dem Produkt der Größen der Abmessungen der niedrigeren Reihenfolge. Beispielsweise ist der D-Fortschritt mit einem „DHW“-Layout gleich H * W; der H-Fortschritt ist gleich W; und der W-Fortschritt ist gleich 1. Fortschritte sollen gepackt werden, wenn die gesamt physische Größe des Tensors gleich der gesamt logischen Größe des Tensors ist. Mit anderen Worten, es gibt keinen zusätzlichen Raum oder überlappende Elemente.

Lassen Sie uns das 2D-Beispiel auf drei Dimensionen erweitern, sodass wir einen Tensor mit Tiefe 2, Höhe 2 und Breite 3 haben (für insgesamt 12 logische Elemente).

A B C
D E F

G H I
J K L

Mit einem „DHW“-Layout wird dieser Tensor wie folgt gespeichert.

Offset: 0 1 2 3 4 5 6 7 8 9 10 11
Wert: H B C D E F G H I J K L
  • D-Fortschritt = Höhe (2) * Breite (3) = 6 (z. B. der Abstand zwischen 'A' und 'G').
  • H-Fortschritt = Breite (3) = 3 (z. B. der Abstand zwischen 'A' und 'D').
  • W-Fortschritt = 1 (z. B. der Abstand zwischen 'A' und 'B').

Das Punktprodukt der Indizes/Koordinaten eines Elements und die Fortschritte stellen den Offset für dieses Element im Puffer bereit. Beispielsweise ist der Offset des H-Elements (d=1, h=0, w=1) 7.

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

Verpackte Tensoren

Die obigen Beispiele veranschaulichen gepackte Tensoren. Ein Tensor soll gepackt werden, wenn die logische Größe des Tensors (in Elementen) der physischen Größe des Puffers (in Elementen) entspricht, und jedes Element eine eindeutige Adresse/einen Offset aufweist. Beispielsweise wird ein 2x2x3-Tensor gepackt, wenn der Puffer 12 Elemente lang ist und kein Elementpaar den gleichen Offset im Puffer aufweist. Verpackte Tensoren sind der häufigste Fall; ermöglichen jedoch komplexere Speicherlayouts.

Sendung mit Fortschritten

Wenn die Puffergröße eines Tensors (in Elementen) kleiner als das Produkt seiner logischen Dimensionen ist, muss es eine Überschneidung von Elementen geben. Der übliche Fall dafür wird als Sendung bezeichnet. Dabei handelt es sich bei den Elementen einer Dimension um ein Duplikat einer anderen Dimension. Beispiel: Schauen wir uns das 2D-Beispiel erneut an. Angenommen, wir möchten einen Tensor, der logisch 2x3 ist, aber die zweite Zeile ist identisch mit der ersten Zeile. Dann sieht es so aus:

A B C
A B C

Dies könnte als verpackter HW/Row-Major-Tensor gespeichert werden. Ein kompakterer Speicher würde jedoch nur 3 Elemente (A, B und C) enthalten und ein Höhen-Fortschritt von 0 anstelle von 3 verwenden. In diesem Fall beträgt die physische Größe des Tensors 3 Elemente, die logische Größe beträgt jedoch 6 Elemente.

Wenn der Schritt einer Dimension 0 ist, werden im Allgemeinen alle Elemente in den Dimensionen der unteren Reihenfolge entlang der übertragenen Dimension wiederholt; Wenn der Tensor z. B. NCHW ist und der C-Fortschritt 0 ist, weist jeder Kanal die gleichen Werte entlang von H und W auf.

Auffüllung mit Fortschritten

Ein Tensor wird als aufgefüllt bezeichnet, wenn seine physische Größe größer ist als die Mindestgröße, die erforderlich ist, um seine Elemente unterzubringen. Wenn keine Sendungs- oder Überschneidungselemente vorhanden sind, ist die Mindestgröße des Tensors (in Elementen) einfach das Produkt seiner Dimensionen. Sie können die Hilfsprogrammfunktion DMLCalcBufferTensorSize (siehe DirectML-Hilfsprogrammfunktionen für eine Auflistung dieser Funktion) verwenden, um die minimale Puffergröße für Ihre DirectML-Tensoren zu berechnen.

Angenommen, ein Puffer enthält die folgenden Werte (die „x“-Elemente geben Auffüllungswerte an).

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

Der aufgefüllte Tensor kann mithilfe eines Höhen-Fortschritts von 5 statt 3 beschrieben werden. Anstatt um drei Elemente zu wechseln, um zur nächsten Zeile zu gelangen, ist der Schritt 5 Elemente (3 reale Elemente plus 2 Auffüllungselemente). Der Abstand ist in Computergrafiken üblich, um beispielsweise sicherzustellen, dass ein Bild eine Power-of-Two-Ausrichtung aufweist.

A B C
D E F

Beschreibungen des DirectML-Puffer-Tensors

DirectML kann mit einer Vielzahl physischer Tensorlayouts arbeiten, da die DML_BUFFER_TENSOR_DESC-Struktur sowohl Sizes als auch StridesElemente aufweist. Einige Operator-Implementierungen können mit einem bestimmten Layout effizienter sein, daher ist es nicht ungewöhnlich, zu ändern, wie Tensordaten für eine bessere Leistung gespeichert werden.

Die meisten DirectML-Operatoren erfordern entweder 4D- oder 5D-Tensoren, und die Reihenfolge der Größen und Fortschritt-Werte ist fest. Durch Korrigieren der Reihenfolge der Größen und Fortschritts-Werte in einer Tensorbeschreibung ist es möglich, dass DirectML verschiedene physische Layouts ableiten kann.

4D

5D

  • DML_BUFFER_TENSOR_DESC::Sizes = { N-Größe, C-Größe, D-Größe, H-Größe, W-Größe}
  • DML_BUFFER_TENSOR_DESC::Strides = { N-Fortschritt, C-Fortschritt, H-Fortschritt, W-Fortschritt}

Wenn ein DirectML-Operator einen 4D- oder 5D-Tensor erfordert, aber die tatsächlichen Daten einen kleineren Rang haben (z. B. 2D), sollten die führenden Dimensionen mit 1 gefüllt werden. Beispielsweise wird ein „HW“-Tensor mit DML_BUFFER_TENSOR_DESC::Sizes = { 1, 1, H, W } festgelegt.

Wenn Tensordaten in NCHW/NCDHW gespeichert werden, ist es nicht erforderlich, DML_BUFFER_TENSOR_DESC::Strides festzulegen, es sei denn, Sie möchten versenden oder auffüllen. Sie können das Feld Fortschritte auf nullptr festlegen. Wenn die Tensordaten jedoch in einem anderen Layout gespeichert werden, z. B. NHWC, müssen Sie Schritt für Schritt ausführen, um die Transformation von NCHW zu diesem Layout auszudrücken.

Betrachten Sie für ein einfaches Beispiel die Beschreibung eines 2D-Tensors mit Höhe 3 und Breite 5.

Gepackter NCHW (implizite Fortschritte)

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

Gepackter NCHW (explizite Fortschritte)

  • N-Fortschritt = C-Größe * H-Größe * W-Größe = 1 * 3 * 5 = 15
  • C-Fortschritt = H-Größe * W-Größe = 3 * 5 = 15
  • H-Fortschritt = W-Größe = 5
  • W-Fortschritt = 1
  • DML_BUFFER_TENSOR_DESC::Sizes = { 1, 1, 3, 5 }
  • DML_BUFFER_TENSOR_DESC::Strides = { 15, 15, 5, 1 }

Gepackter NHWC

  • N-Fortschritt = H-Größe * W-Größe * C-Größe = 3 * 5 * 1 = 15
  • H-Fortschritt = W-Größe * C-Größe = 5 * 1 = 5
  • W-Fortschritt = C-Größe = 1
  • C-Fortschritt = 1
  • DML_BUFFER_TENSOR_DESC::Sizes = { 1, 1, 3, 5 }
  • DML_BUFFER_TENSOR_DESC::Strides = { 15, 1, 5, 1 }

Weitere Informationen