DirectML 張量由 Direct3D 12 緩衝區支援,並以稱為張量的 大小 和 步幅 的屬性來描述。 張量 的大小 描述張量邏輯維度。 例如,2D 張量的高度可能為2,寬度為3。 邏輯上,張量有 6 個不同的元素,不過大小不會指定這些元素儲存在記憶體中的方式。 張量的 步幅 描述張量元素的物理記憶體配置。
二維 (2D) 陣列
請考慮一個具有高度 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 |
|---|---|---|---|---|---|---|
| 值: | 一個 | B | C | D | E | F |
維度的 步幅 是要跳過的元素數目,以便存取該維度中的下一個元素。 Strides 表示記憶體中張量的配置。 以列優先的順序,寬度維度的步幅一律為 1,因為沿著維度的相鄰元素會連續儲存。 高度維度的步幅取決於寬度維度的大小;在上述範例中,沿著高度維度的連續元素之間的距離(例如,A 到 D)等於張量的寬度(在此範例中為 3)。
若要說明不同的排列方式,請考慮以列優先順序。 換句話說,沿著高度維度的連續元素會連續儲存在線性記憶體空間中。 在此情況下,高度步幅一律為1,而寬度步幅為2(高度維度的大小)。
| 抵消: | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 值: | 一個 | D | B | E | C | F |
更高維度
當涉及到大於兩個維度時,將佈局稱為列優先或行優先會變得不再合適。 因此,本主題的其餘部分會使用這類詞彙和標籤。
- 2D:「HW」—高度是最高的維度(行優先)。
- 2D:「WH」—寬度是最高階維度(列優先)。
- 3D:“DHW”-深度是最高階維度,後面接著高度,然後寬度。
- 3D:「WHD」—寬度是最高階維度,後面接著高度,然後是深度。
- 4D:“NCHW”—影像數目(批次大小)、通道數目、高度、寬度。
一般而言,維度 的封裝 步幅等於較低順序維度大小的乘積。 例如,使用「DHW」配置時,D 步長等於 H * W。H 步長等於 W。W 步長等於 1。 當張量的總物理大小等於張量的總邏輯大小時,據說步幅是 緊密 的。換句話說,沒有多餘的空間,也沒有重疊的元素。
讓我們將 2D 範例延伸至三個維度,因此我們有深度 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 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 值: | 一個 | B | C | D | E | F | G | H | 我 | J | K | L |
- D-stride 等於高度(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張量會封裝起來。 緊湊的張量是最常見的情況,但步幅設定允許更複雜的記憶體配置。
與時俱進的廣播
如果 tensor 的緩衝區大小(以元素計)小於其邏輯維度的乘積,那麼必定會有一些元素重疊。 這種常見的情況稱為廣播,其中一個維度的元素是另一個維度的重複。 例如,讓我們重新流覽 2D 範例。 假設我們想要一個在邏輯上是 2x3 的張量,但是第二行與第一行相同。 以下是它的外觀。
A B C
A B C
這可以儲存為打包的 HW/行優先張量。 但是,更精簡的儲存空間只會包含3個元素(A、B和 C),並使用0的高度步幅,而不是3個。 在此情況下,張量的實際大小為3個元素,但邏輯大小為6個元素。
一般而言,如果維度的步幅為 0,則較低順序維度中的所有元素都會沿著廣播維度重複;例如,如果張量是 NCHW,而 C-stride 為 0,則每個通道在 H 和 W 上都有相同的值。
填補步幅
如果張量的物理大小大於其元素所需的最小大小,則該張量稱為填充。 當沒有廣播或重疊的元素時,張量的最小大小(以元素計算)只是其維度的乘積。 您可以使用輔助函式
假設緩衝區包含下列值(『x』 元素表示填補值)。
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|
| 一個 | B | C | x | x | D | E | F | x | x |
使用步幅為 5 而不是 3 的高度,即可描述填充的張量。 步驟不是每次跨越 3 個元素來到達下一行,而是跨越 5 個元素(3 個實際元素加上 2 個填充元素)。 例如,在計算機圖形中,填充很常見,用於確保影像具有兩的冪次對齊。
A B C
D E F
DirectML 緩衝區張量描述
DirectML 可以使用各種不同的實體張量配置,因為 DML_BUFFER_TENSOR_DESC 結構 同時具有 Sizes 和 Strides 成員。 某些算子實作在特定配置下可能更有效率,因此改變張量數據的存儲方式以提升效能並不罕見。
大部分 DirectML 運算元都需要 4D 或 5D 張量,且大小和步幅值的順序是固定的。 藉由調整張量描述中大小和步幅值的順序,DirectML 可以推斷不同的實體版面配置。
4D
- DML_BUFFER_TENSOR_DESC::Size = { N-size, C-size, H-size, W-size }
- DML_BUFFER_TENSOR_DESC::Strides = { N-stride, C-stride, H-stride, W-stride }
5D
- DML_BUFFER_TENSOR_DESC::Size = { 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 }
如果 DirectML 運算子要求 4D 或 5D 張量,但實際數據的維度較小(例如 2D),則最前面的維度應填入 1。 例如,將 "HW" 張量設置為 DML_BUFFER_TENSOR_DESC::Sizes = { 1, 1, H, W }。
如果 tensor 資料儲存在 NCHW/NCDHW 中,則除非您想要廣播或填充,否則不需要設定 DML_BUFFER_TENSOR_DESC::Strides。 您可以將 [步幅] 欄位設定為 nullptr。 不過,如果張量數據儲存在另一個配置中,例如 NHWC,則您需要大步,才能表達從 NCHW 到該版面配置的轉換。
以簡單的例子為例,考慮描述一個高度為 3、寬度為 5 的 2D 張量。
封裝的 NCHW (隱含步幅)
- DML_BUFFER_TENSOR_DESC::Sizes = { 1, 1, 3, 5 }
-
DML_BUFFER_TENSOR_DESC::Strides =
nullptr
包裝的 NCHW (明確步幅)
- N 步幅 = C 碼 * H 碼 * W 碼 = 1 * 3 * 5 = 15
- C-stride = 高度(H-size)* 寬度(W-size) = 3 * 5 = 15
- H-步幅 = W-大小 = 5
- W 步幅 = 1
- DML_BUFFER_TENSOR_DESC::Sizes = { 1, 1, 3, 5 }
- DML_BUFFER_TENSOR_DESC::Strides = { 15, 15, 5, 1 }
填充的 NHWC
- N 步幅 = H 大小 * W 大小 * C 大小 = 3 * 5 * 1 = 15
- H 步幅 = W 大小 * C 大小 = 5 * 1 = 5
- W 步幅 = C 大小 = 1
- C 步幅 = 1
- DML_BUFFER_TENSOR_DESC::Sizes = { 1, 1, 3, 5 }
- DML_BUFFER_TENSOR_DESC::Strides = { 15, 1, 5, 1 }