Xenova HF Staff commited on
Commit
8148465
·
verified ·
1 Parent(s): f5ec031

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,15 +18,15 @@ See the [ONNX `Transpose` spec](https://onnx.ai/onnx/operators/onnx__Transpose.h
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
- | `data` | `x` | `T` | — | — | The input tensor to transpose. | required |
24
 
25
  ## Outputs
26
 
27
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
28
  | --- | --- | --- | --- | --- | --- | --- |
29
- | `transposed` | `y` | `T` | same as `data` | — | The transposed output tensor with permuted axes. | required |
30
 
31
  ## Attributes
32
 
@@ -42,9 +42,16 @@ Attributes and default values (overridable per request):
42
  | --- | --- |
43
  | `T` | `float32`, `float16`, `int32`, `int16`, `uint32`, `uint8`, `int8`, `bool` |
44
 
 
 
 
 
 
 
 
45
  ## Files
46
 
47
- - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
48
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
49
  - [`test.json`](build/webgpu/test.json) — correctness cases
50
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
@@ -56,15 +63,18 @@ Attributes and default values (overridable per request):
56
 
57
  ## Use with `@huggingface/kernels`
58
 
59
- The loader automatically allocates outputs whose metadata it can derive from the manifest contract and this call.
 
 
60
 
61
- The explicit `outputs` entries provide shape and logical dtype metadata for the results listed below:
62
 
63
- - `y`
64
 
65
- Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
66
 
67
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
68
 
69
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
70
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
+ | `x` | `data` | `T` | — | — | The input tensor to transpose. | required |
24
 
25
  ## Outputs
26
 
27
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
28
  | --- | --- | --- | --- | --- | --- | --- |
29
+ | `y` | `transposed` | `T` | same as `x` | — | The transposed output tensor with permuted axes. | required |
30
 
31
  ## Attributes
32
 
 
42
  | --- | --- |
43
  | `T` | `float32`, `float16`, `int32`, `int16`, `uint32`, `uint8`, `int8`, `bool` |
44
 
45
+ ## Implementation variants
46
+
47
+ One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
48
+
49
+ - `tiled_scalar` — Stages ragged two-dimensional transpose planes through a padded workgroup tile so reads and writes remain coalesced when vec4 alignment is unavailable.
50
+ - `inner_vec4` — Vectorizes the contiguous innermost dimension for permutations that leave that axis in place. It assigns multiple vectors per invocation only when enough invocations remain to keep the dispatch populated.
51
+
52
  ## Files
53
 
54
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
55
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
56
  - [`test.json`](build/webgpu/test.json) — correctness cases
57
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
63
 
64
  ## Use with `@huggingface/kernels`
65
 
66
+ ```sh
67
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
68
+ ```
69
 
70
+ Outputs with inferable metadata are allocated automatically. Explicit `outputs` entries request optional results or provide metadata that cannot be inferred from the supplied inputs and attributes.
71
 
72
+ This example supplies explicit metadata for:
73
 
74
+ - `y`
75
 
76
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
77
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
78
 
79
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
80
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.Transpose",
3
  "tunableSpace": {
4
  "WORKGROUP_SIZE": [64, 128, 256],
5
  "INNER_VEC4_ITEMS_PER_THREAD": [1, 2, 4, 8],
@@ -216,6 +215,56 @@
216
  "inputs": { "x": { "shape": [1812, 384], "dtype": "float32", "dist": "normal", "seed": 9451, "scale": 0.2 } },
217
  "outputs": { "y": { "shape": [384, 1812], "dtype": "float32" } },
218
  "bench": { "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 2" }] }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  }
220
  ]
221
  }
 
1
  {
 
2
  "tunableSpace": {
3
  "WORKGROUP_SIZE": [64, 128, 256],
4
  "INNER_VEC4_ITEMS_PER_THREAD": [1, 2, 4, 8],
 
215
  "inputs": { "x": { "shape": [1812, 384], "dtype": "float32", "dist": "normal", "seed": 9451, "scale": 0.2 } },
216
  "outputs": { "y": { "shape": [384, 1812], "dtype": "float32" } },
217
  "bench": { "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 2" }] }
218
+ },
219
+ {
220
+ "name": "transpose-f32-rank6-swin-window-partition-perm013245-pathology",
221
+ "preset": "edge",
222
+ "provenance": {
223
+ "notes": "Rank-6 window partition (swap of the two window-grid axes with a contiguous 96-wide last axis) at a batch-8 Swin 56x56 stage. Rank 6 is outside every tiled and inner-vec4 pattern, so the generic strided kernel is the selected route; the permutation is its own inverse, so one case covers both directions."
224
+ },
225
+ "vars": { "count": 2408448, "dtype": "float32" },
226
+ "attrs": { "perm": [0, 1, 3, 2, 4, 5] },
227
+ "inputs": {
228
+ "x": { "shape": [8, 8, 7, 8, 7, 96], "dtype": "float32", "dist": "normal", "seed": 154, "scale": 0.2 }
229
+ },
230
+ "outputs": { "y": { "shape": [8, 8, 8, 7, 7, 96], "dtype": "float32" } },
231
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 2" }] }
232
+ },
233
+ {
234
+ "name": "transpose-f32-rank6-swin-window-partition-rank4-tiled-control",
235
+ "preset": "edge",
236
+ "provenance": {
237
+ "notes": "Equal-byte tiled control for the rank-6 window-partition case: the same 8x56x56x96 elements as an NHWC-to-NCHW permutation, which takes the vec4 staged transpose tile."
238
+ },
239
+ "vars": { "count": 2408448, "dtype": "float32" },
240
+ "attrs": { "perm": [0, 3, 1, 2] },
241
+ "inputs": { "x": { "shape": [8, 56, 56, 96], "dtype": "float32", "dist": "normal", "seed": 155, "scale": 0.2 } },
242
+ "outputs": { "y": { "shape": [8, 96, 56, 56], "dtype": "float32" } },
243
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 2" }] }
244
+ },
245
+ {
246
+ "name": "transpose-f32-rank4-perm0321-8x64x197x64-pathology",
247
+ "preset": "edge",
248
+ "provenance": {
249
+ "notes": "Rank-4 permutation [0,3,2,1] at a 197-token, 64-head-dim attention shape: axis 3 leaves the last position and the permutation is none of the three tiled rank-4 patterns, so the generic strided kernel is the selected route."
250
+ },
251
+ "vars": { "count": 6455296, "dtype": "float32" },
252
+ "attrs": { "perm": [0, 3, 2, 1] },
253
+ "inputs": { "x": { "shape": [8, 64, 197, 64], "dtype": "float32", "dist": "normal", "seed": 156, "scale": 0.2 } },
254
+ "outputs": { "y": { "shape": [8, 64, 197, 64], "dtype": "float32" } },
255
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 2" }] }
256
+ },
257
+ {
258
+ "name": "transpose-f32-rank4-perm0213-8x64x197x64-control",
259
+ "preset": "edge",
260
+ "provenance": {
261
+ "notes": "Control for the [0,3,2,1] case: the same tensor under the attention-layout permutation [0,2,1,3] keeps the innermost axis in place and takes the inner-vec4 route."
262
+ },
263
+ "vars": { "count": 6455296, "dtype": "float32" },
264
+ "attrs": { "perm": [0, 2, 1, 3] },
265
+ "inputs": { "x": { "shape": [8, 64, 197, 64], "dtype": "float32", "dist": "normal", "seed": 157, "scale": 0.2 } },
266
+ "outputs": { "y": { "shape": [8, 197, 64, 64], "dtype": "float32" } },
267
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 2" }] }
268
  }
269
  ]
270
  }
build/webgpu/datamove-flat-copy.wgsl.jinja CHANGED
@@ -1,9 +1,6 @@
1
- {% if usesF16 %}
2
- enable f16;
3
- {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
5
 
6
- const COUNT: u32 = {{ source.count }}u;
7
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
8
 
9
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
+ const COUNT: u32 = {{ count }}u;
4
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
5
 
6
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
build/webgpu/datamove-transpose-2d-tiled-scalar.wgsl.jinja CHANGED
@@ -1,24 +1,21 @@
1
  // Tiled, optionally batched 2-D transpose over scalar elements.
2
  //
3
- // A 32x32 tile enters workgroup memory through coalesced reads and leaves
4
- // through coalesced writes. Guarded edges make this valid for any extent,
5
- // including shapes that cannot use the aligned vec4 path and would otherwise
6
- // fall back to a generic copy whose reads and writes cannot both be coalesced.
7
  //
8
  // Layout contract:
9
  // - src is row-major [batch, ROWS, COLS]
10
  // - dst is row-major [batch, COLS, ROWS]
11
  // - dispatch is x = ceil(COLS / 32), y = ceil(ROWS / 32), z = batch
12
  //
13
- // The extra column avoids bank conflicts on 32-bank GPUs.
14
- {% if usesF16 %}
15
- enable f16;
16
- {% endif %}
17
  {{ env.wgsl.resourceDeclarations }}
18
 
19
- const ROWS: u32 = {{ source.rows }}u;
20
- const COLS: u32 = {{ source.cols }}u;
21
- const MAT: u32 = {{ source.rows * source.cols }}u;
22
  const TILE: u32 = 32u;
23
  const ROW_STEP: u32 = 8u;
24
 
@@ -46,7 +43,7 @@ fn main(
46
 
47
  // Write: lid.x runs along the destination row, which is the source column
48
  // direction — the transpose happens in the tile index, not in the memory
49
- // access, so this side is coalesced too.
50
  for (var i = 0u; i < TILE; i = i + ROW_STEP) {
51
  let out_row = col_base + lid.y + i;
52
  let out_col = row_base + lid.x;
 
1
  // Tiled, optionally batched 2-D transpose over scalar elements.
2
  //
3
+ // A 32x32 tile enters workgroup memory through consecutive row reads and leaves
4
+ // through consecutive transposed-row writes. Guarded edges make this valid for
5
+ // any extent.
 
6
  //
7
  // Layout contract:
8
  // - src is row-major [batch, ROWS, COLS]
9
  // - dst is row-major [batch, COLS, ROWS]
10
  // - dispatch is x = ceil(COLS / 32), y = ceil(ROWS / 32), z = batch
11
  //
12
+ // The extra column changes the transposed workgroup-memory stride and reduces
13
+ // bank-conflict risk on banked implementations.
 
 
14
  {{ env.wgsl.resourceDeclarations }}
15
 
16
+ const ROWS: u32 = {{ rowCount }}u;
17
+ const COLS: u32 = {{ cols }}u;
18
+ const MAT: u32 = {{ rowCount * cols }}u;
19
  const TILE: u32 = 32u;
20
  const ROW_STEP: u32 = 8u;
21
 
 
43
 
44
  // Write: lid.x runs along the destination row, which is the source column
45
  // direction — the transpose happens in the tile index, not in the memory
46
+ // access, so this side also traverses consecutive addresses.
47
  for (var i = 0u; i < TILE; i = i + ROW_STEP) {
48
  let out_row = col_base + lid.y + i;
49
  let out_col = row_base + lid.x;
build/webgpu/datamove-transpose-2d-tiled.wgsl.jinja CHANGED
@@ -5,19 +5,17 @@
5
  // - ROWS % 4 == 0 and COLS % 4 == 0, so both buffers bind as vec4<T>
6
  // - dispatch: x = ceil(COLS / 32), y = ceil(ROWS / 32), z = batch
7
  //
8
- // Each 8x32 workgroup moves one 32x32 scalar tile through workgroup memory, so
9
- // both global loads and global stores are coalesced vec4 accesses. The +1
10
- // column pad avoids bank conflicts on 32-bank GPUs.
11
- {% if usesF16 %}
12
- enable f16;
13
- {% endif %}
14
  {{ env.wgsl.resourceDeclarations }}
15
 
16
- const ROWS: u32 = {{ source.rows }}u;
17
- const COLS: u32 = {{ source.cols }}u;
18
- const ROWS_V: u32 = {{ ((source.rows / 4) | int) }}u;
19
- const COLS_V: u32 = {{ ((source.cols / 4) | int) }}u;
20
- const MAT_V: u32 = {{ (((source.rows * source.cols) / 4) | int) }}u;
21
  const TILE: u32 = 32u;
22
 
23
  var<workgroup> tile: array<array<{{ scalar }}, 33>, 32>;
 
5
  // - ROWS % 4 == 0 and COLS % 4 == 0, so both buffers bind as vec4<T>
6
  // - dispatch: x = ceil(COLS / 32), y = ceil(ROWS / 32), z = batch
7
  //
8
+ // Each 8x32 workgroup moves one 32x32 scalar tile through workgroup memory.
9
+ // Global loads and stores each traverse consecutive vec4 addresses. The extra
10
+ // workgroup-memory column changes the transposed stride and reduces
11
+ // bank-conflict risk on banked implementations.
 
 
12
  {{ env.wgsl.resourceDeclarations }}
13
 
14
+ const ROWS: u32 = {{ rowCount }}u;
15
+ const COLS: u32 = {{ cols }}u;
16
+ const ROWS_V: u32 = {{ ((rowCount / 4) | int) }}u;
17
+ const COLS_V: u32 = {{ ((cols / 4) | int) }}u;
18
+ const MAT_V: u32 = {{ (((rowCount * cols) / 4) | int) }}u;
19
  const TILE: u32 = 32u;
20
 
21
  var<workgroup> tile: array<array<{{ scalar }}, 33>, 32>;
build/webgpu/datamove-transpose-vec4.wgsl.jinja CHANGED
@@ -2,27 +2,24 @@
2
  // axis in place. Its dimension is a multiple of four, so both buffers bind as
3
  // vec4<T>. The index fold uses effective shapes whose last dimension is divided
4
  // by four, enabling 128-bit loads and stores.
5
- {% if usesF16 %}
6
- enable f16;
7
- {% endif %}
8
  {{ env.wgsl.resourceDeclarations }}
9
 
10
- const COUNT: u32 = {{ source.count }}u;
11
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
12
 
13
  fn input_offset(out_index: u32) -> u32 {
14
  var rem = out_index;
15
  var offset = 0u;
16
- {% for out_axis in range(source.rank) %}
17
  {% set out_stride = namespace(value=1) %}
18
- {% for j in range(out_axis + 1, source.rank) %}
19
- {% set dj = ((source.outputShape[j] / 4) | int) if j == source.rank - 1 else source.outputShape[j] %}
20
  {% set out_stride.value = out_stride.value * dj %}
21
  {% endfor %}
22
- {% set in_axis = source.perm[out_axis] %}
23
  {% set in_stride = namespace(value=1) %}
24
- {% for j in range(in_axis + 1, source.rank) %}
25
- {% set dj = ((source.inputShape[j] / 4) | int) if j == source.rank - 1 else source.inputShape[j] %}
26
  {% set in_stride.value = in_stride.value * dj %}
27
  {% endfor %}
28
  let coord{{ out_axis }} = rem / {{ out_stride.value }}u;
 
2
  // axis in place. Its dimension is a multiple of four, so both buffers bind as
3
  // vec4<T>. The index fold uses effective shapes whose last dimension is divided
4
  // by four, enabling 128-bit loads and stores.
 
 
 
5
  {{ env.wgsl.resourceDeclarations }}
6
 
7
+ const COUNT: u32 = {{ count }}u;
8
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
9
 
10
  fn input_offset(out_index: u32) -> u32 {
11
  var rem = out_index;
12
  var offset = 0u;
13
+ {% for out_axis in range(rank) %}
14
  {% set out_stride = namespace(value=1) %}
15
+ {% for j in range(out_axis + 1, rank) %}
16
+ {% set dj = ((outputShape[j] / 4) | int) if j == rank - 1 else outputShape[j] %}
17
  {% set out_stride.value = out_stride.value * dj %}
18
  {% endfor %}
19
+ {% set in_axis = perm[out_axis] %}
20
  {% set in_stride = namespace(value=1) %}
21
+ {% for j in range(in_axis + 1, rank) %}
22
+ {% set dj = ((inputShape[j] / 4) | int) if j == rank - 1 else inputShape[j] %}
23
  {% set in_stride.value = in_stride.value * dj %}
24
  {% endfor %}
25
  let coord{{ out_axis }} = rem / {{ out_stride.value }}u;
build/webgpu/manifest.json CHANGED
@@ -2,171 +2,96 @@
2
  "domain": "ai.onnx",
3
  "name": "Transpose",
4
  "sinceVersion": 13,
5
- "description": "Transposes the input tensor by permuting its axes according to the `perm` attribute. Axis `i` of the output corresponds to axis `perm[i]` of the input; if `perm` is omitted, the axes are reversed (`n-1, ..., 0`).",
6
- "inputs": [{ "role": "data", "dtype": "T", "description": "The input tensor to transpose." }],
7
- "outputs": [
8
- {
9
- "role": "transposed",
10
- "dtype": "T",
11
- "description": "The transposed output tensor with permuted axes.",
12
- "rank": "ranks.data"
13
- }
14
- ],
15
- "attributes": {},
16
- "attributeDescriptions": {
17
- "perm": "Optional permutation of the input axes. It must contain every axis from 0 through rank - 1 exactly once. When omitted, the axes are reversed."
18
- },
19
  "typeConstraints": { "T": ["float32", "float16", "int32", "int16", "uint32", "uint8", "int8", "bool"] },
20
- "args": {
21
- "x": { "kind": "tensor", "semantic": "data", "role": "input" },
22
- "y": { "kind": "tensor", "semantic": "transposed", "role": "output" }
23
- },
24
- "tunables": { "WORKGROUP_SIZE": 256, "INNER_VEC4_ITEMS_PER_THREAD": 4, "INNER_VEC4_MIN_THREADS": 65536 },
25
- "tunableDescriptions": {
26
- "INNER_VEC4_ITEMS_PER_THREAD": "Sets the target maximum number of vec4 items assigned to each invocation by the inner-axis vector route.",
27
- "INNER_VEC4_MIN_THREADS": "Sets the minimum logical thread count retained when shrinking the inner-axis vector dispatch."
28
  },
29
  "derive": {
30
  "dtypeSupported": "f16Ok(dtypes.T)",
31
- "rank0IO": "ranks.data == 0 and ranks.transposed == 0",
32
- "rank1IO": "ranks.data == 1 and ranks.transposed == 1",
33
- "rank2IO": "ranks.data == 2 and ranks.transposed == 2",
34
- "rank3IO": "ranks.data == 3 and ranks.transposed == 3",
35
- "rank4IO": "ranks.data == 4 and ranks.transposed == 4",
36
- "rank5IO": "ranks.data == 5 and ranks.transposed == 5",
37
- "rank6IO": "ranks.data == 6 and ranks.transposed == 6",
38
- "rank7IO": "ranks.data == 7 and ranks.transposed == 7",
39
  "permPresent": "has(attrs, \"perm\")",
40
- "permContract": "not permPresent or ((attrs.perm | length) == ranks.data and isUniqueIntList(attrs.perm, 0, ranks.data))",
41
- "rank0Permutation": "rank0IO and permContract",
42
- "rank1Permutation": "rank1IO and permContract and dim(shapes.transposed, 0) == dim(shapes.data, 0)",
43
- "explicitRank2": "rank2IO and permContract and permPresent and dim(shapes.transposed, 0) == dim(shapes.data, attrs.perm[0]) and dim(shapes.transposed, 1) == dim(shapes.data, attrs.perm[1])",
44
- "explicitRank3": "rank3IO and permContract and permPresent and dim(shapes.transposed, 0) == dim(shapes.data, attrs.perm[0]) and dim(shapes.transposed, 1) == dim(shapes.data, attrs.perm[1]) and dim(shapes.transposed, 2) == dim(shapes.data, attrs.perm[2])",
45
- "explicitRank4": "rank4IO and permContract and permPresent and dim(shapes.transposed, 0) == dim(shapes.data, attrs.perm[0]) and dim(shapes.transposed, 1) == dim(shapes.data, attrs.perm[1]) and dim(shapes.transposed, 2) == dim(shapes.data, attrs.perm[2]) and dim(shapes.transposed, 3) == dim(shapes.data, attrs.perm[3])",
46
- "explicitRank5": "rank5IO and permContract and permPresent and dim(shapes.transposed, 0) == dim(shapes.data, attrs.perm[0]) and dim(shapes.transposed, 1) == dim(shapes.data, attrs.perm[1]) and dim(shapes.transposed, 2) == dim(shapes.data, attrs.perm[2]) and dim(shapes.transposed, 3) == dim(shapes.data, attrs.perm[3]) and dim(shapes.transposed, 4) == dim(shapes.data, attrs.perm[4])",
47
- "explicitRank6": "rank6IO and permContract and permPresent and dim(shapes.transposed, 0) == dim(shapes.data, attrs.perm[0]) and dim(shapes.transposed, 1) == dim(shapes.data, attrs.perm[1]) and dim(shapes.transposed, 2) == dim(shapes.data, attrs.perm[2]) and dim(shapes.transposed, 3) == dim(shapes.data, attrs.perm[3]) and dim(shapes.transposed, 4) == dim(shapes.data, attrs.perm[4]) and dim(shapes.transposed, 5) == dim(shapes.data, attrs.perm[5])",
48
- "explicitRank7": "rank7IO and permContract and permPresent and dim(shapes.transposed, 0) == dim(shapes.data, attrs.perm[0]) and dim(shapes.transposed, 1) == dim(shapes.data, attrs.perm[1]) and dim(shapes.transposed, 2) == dim(shapes.data, attrs.perm[2]) and dim(shapes.transposed, 3) == dim(shapes.data, attrs.perm[3]) and dim(shapes.transposed, 4) == dim(shapes.data, attrs.perm[4]) and dim(shapes.transposed, 5) == dim(shapes.data, attrs.perm[5]) and dim(shapes.transposed, 6) == dim(shapes.data, attrs.perm[6])",
49
- "defaultRank2": "rank2IO and permContract and not permPresent and dim(shapes.transposed, 0) == dim(shapes.data, 1) and dim(shapes.transposed, 1) == dim(shapes.data, 0)",
50
- "defaultRank3": "rank3IO and permContract and not permPresent and dim(shapes.transposed, 0) == dim(shapes.data, 2) and dim(shapes.transposed, 1) == dim(shapes.data, 1) and dim(shapes.transposed, 2) == dim(shapes.data, 0)",
51
- "defaultRank4": "rank4IO and permContract and not permPresent and dim(shapes.transposed, 0) == dim(shapes.data, 3) and dim(shapes.transposed, 1) == dim(shapes.data, 2) and dim(shapes.transposed, 2) == dim(shapes.data, 1) and dim(shapes.transposed, 3) == dim(shapes.data, 0)",
52
- "defaultRank5": "rank5IO and permContract and not permPresent and dim(shapes.transposed, 0) == dim(shapes.data, 4) and dim(shapes.transposed, 1) == dim(shapes.data, 3) and dim(shapes.transposed, 2) == dim(shapes.data, 2) and dim(shapes.transposed, 3) == dim(shapes.data, 1) and dim(shapes.transposed, 4) == dim(shapes.data, 0)",
53
- "defaultRank6": "rank6IO and permContract and not permPresent and dim(shapes.transposed, 0) == dim(shapes.data, 5) and dim(shapes.transposed, 1) == dim(shapes.data, 4) and dim(shapes.transposed, 2) == dim(shapes.data, 3) and dim(shapes.transposed, 3) == dim(shapes.data, 2) and dim(shapes.transposed, 4) == dim(shapes.data, 1) and dim(shapes.transposed, 5) == dim(shapes.data, 0)",
54
- "defaultRank7": "rank7IO and permContract and not permPresent and dim(shapes.transposed, 0) == dim(shapes.data, 6) and dim(shapes.transposed, 1) == dim(shapes.data, 5) and dim(shapes.transposed, 2) == dim(shapes.data, 4) and dim(shapes.transposed, 3) == dim(shapes.data, 3) and dim(shapes.transposed, 4) == dim(shapes.data, 2) and dim(shapes.transposed, 5) == dim(shapes.data, 1) and dim(shapes.transposed, 6) == dim(shapes.data, 0)",
55
- "genericTransposeOk": "rank0Permutation or rank1Permutation or explicitRank2 or defaultRank2 or explicitRank3 or defaultRank3 or explicitRank4 or defaultRank4 or explicitRank5 or defaultRank5 or explicitRank6 or defaultRank6 or explicitRank7 or defaultRank7",
56
- "defaultPerm": "pick([[rank0IO, []], [rank1IO, [0]], [rank2IO, [1, 0]], [rank3IO, [2, 1, 0]], [rank4IO, [3, 2, 1, 0]], [rank5IO, [4, 3, 2, 1, 0]], [rank6IO, [5, 4, 3, 2, 1, 0]], [rank7IO, [6, 5, 4, 3, 2, 1, 0]]], [])",
57
  "genericPerm": "pick([[permPresent, attrs.perm]], defaultPerm)",
58
- "innerVec4Rank3Ok": "explicitRank3 and attrs.perm[2] == 2 and dim(shapes.data, 2) % 4 == 0 and numel(shapes.transposed) > 0 and dtypeSupported",
59
- "innerVec4Rank4Ok": "explicitRank4 and attrs.perm[3] == 3 and dim(shapes.data, 3) % 4 == 0 and numel(shapes.transposed) > 0 and dtypeSupported",
 
60
  "innerVec4Ok": "innerVec4Rank3Ok or innerVec4Rank4Ok",
61
  "innerVec4Perm": "attrs.perm",
62
- "tiled2dShapeOk": "(defaultRank2 or (explicitRank2 and attrs.perm[0] == 1 and attrs.perm[1] == 0)) and numel(shapes.data) > 0 and ceil(dim(shapes.data, 0) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and ceil(dim(shapes.data, 1) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and dtypeSupported",
63
- "tiled2dOk": "tiled2dShapeOk and dim(shapes.data, 0) % 4 == 0 and dim(shapes.data, 1) % 4 == 0",
64
- "tiled3dShapeOk": "explicitRank3 and attrs.perm[0] == 0 and attrs.perm[1] == 2 and attrs.perm[2] == 1 and numel(shapes.data) > 0 and dim(shapes.data, 0) <= device.limits.maxComputeWorkgroupsPerDimension and ceil(dim(shapes.data, 1) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and ceil(dim(shapes.data, 2) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and dtypeSupported",
65
- "tiled3dOk": "tiled3dShapeOk and dim(shapes.data, 1) % 4 == 0 and dim(shapes.data, 2) % 4 == 0",
66
- "tiled4dShapeOk": "explicitRank4 and attrs.perm[0] == 0 and attrs.perm[1] == 1 and attrs.perm[2] == 3 and attrs.perm[3] == 2 and numel(shapes.data) > 0 and dim(shapes.data, 0) * dim(shapes.data, 1) <= device.limits.maxComputeWorkgroupsPerDimension and ceil(dim(shapes.data, 2) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and ceil(dim(shapes.data, 3) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and dtypeSupported",
67
- "tiled4dOk": "tiled4dShapeOk and dim(shapes.data, 2) % 4 == 0 and dim(shapes.data, 3) % 4 == 0",
68
- "tiled5dNcdhwToNdhwcShapeOk": "explicitRank5 and attrs.perm[0] == 0 and attrs.perm[1] == 2 and attrs.perm[2] == 3 and attrs.perm[3] == 4 and attrs.perm[4] == 1 and numel(shapes.data) > 0 and dim(shapes.data, 0) <= device.limits.maxComputeWorkgroupsPerDimension and ceil(dim(shapes.data, 1) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and ceil((dim(shapes.data, 2) * dim(shapes.data, 3) * dim(shapes.data, 4)) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and dtypeSupported",
69
- "tiled5dNcdhwToNdhwcOk": "tiled5dNcdhwToNdhwcShapeOk and dim(shapes.data, 1) % 4 == 0 and (dim(shapes.data, 2) * dim(shapes.data, 3) * dim(shapes.data, 4)) % 4 == 0",
70
- "tiled5dNdhwcToNcdhwShapeOk": "explicitRank5 and attrs.perm[0] == 0 and attrs.perm[1] == 4 and attrs.perm[2] == 1 and attrs.perm[3] == 2 and attrs.perm[4] == 3 and numel(shapes.data) > 0 and dim(shapes.data, 0) <= device.limits.maxComputeWorkgroupsPerDimension and ceil(dim(shapes.data, 1) * dim(shapes.data, 2) * dim(shapes.data, 3) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and ceil(dim(shapes.data, 4) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and dtypeSupported",
71
- "tiled5dNdhwcToNcdhwOk": "tiled5dNdhwcToNcdhwShapeOk and dim(shapes.data, 1) * dim(shapes.data, 2) * dim(shapes.data, 3) % 4 == 0 and dim(shapes.data, 4) % 4 == 0",
72
- "memcpyPerm3": "explicitRank3 and (dim(shapes.data, attrs.perm[0]) == 1 or dim(shapes.data, attrs.perm[1]) == 1 or attrs.perm[0] < attrs.perm[1]) and (dim(shapes.data, attrs.perm[0]) == 1 or dim(shapes.data, attrs.perm[2]) == 1 or attrs.perm[0] < attrs.perm[2]) and (dim(shapes.data, attrs.perm[1]) == 1 or dim(shapes.data, attrs.perm[2]) == 1 or attrs.perm[1] < attrs.perm[2])",
73
- "memcpyPerm4": "explicitRank4 and (dim(shapes.data, attrs.perm[0]) == 1 or dim(shapes.data, attrs.perm[1]) == 1 or attrs.perm[0] < attrs.perm[1]) and (dim(shapes.data, attrs.perm[0]) == 1 or dim(shapes.data, attrs.perm[2]) == 1 or attrs.perm[0] < attrs.perm[2]) and (dim(shapes.data, attrs.perm[0]) == 1 or dim(shapes.data, attrs.perm[3]) == 1 or attrs.perm[0] < attrs.perm[3]) and (dim(shapes.data, attrs.perm[1]) == 1 or dim(shapes.data, attrs.perm[2]) == 1 or attrs.perm[1] < attrs.perm[2]) and (dim(shapes.data, attrs.perm[1]) == 1 or dim(shapes.data, attrs.perm[3]) == 1 or attrs.perm[1] < attrs.perm[3]) and (dim(shapes.data, attrs.perm[2]) == 1 or dim(shapes.data, attrs.perm[3]) == 1 or attrs.perm[2] < attrs.perm[3])",
74
- "memcpyPerm5": "explicitRank5 and (dim(shapes.data, attrs.perm[0]) == 1 or dim(shapes.data, attrs.perm[1]) == 1 or attrs.perm[0] < attrs.perm[1]) and (dim(shapes.data, attrs.perm[0]) == 1 or dim(shapes.data, attrs.perm[2]) == 1 or attrs.perm[0] < attrs.perm[2]) and (dim(shapes.data, attrs.perm[0]) == 1 or dim(shapes.data, attrs.perm[3]) == 1 or attrs.perm[0] < attrs.perm[3]) and (dim(shapes.data, attrs.perm[0]) == 1 or dim(shapes.data, attrs.perm[4]) == 1 or attrs.perm[0] < attrs.perm[4]) and (dim(shapes.data, attrs.perm[1]) == 1 or dim(shapes.data, attrs.perm[2]) == 1 or attrs.perm[1] < attrs.perm[2]) and (dim(shapes.data, attrs.perm[1]) == 1 or dim(shapes.data, attrs.perm[3]) == 1 or attrs.perm[1] < attrs.perm[3]) and (dim(shapes.data, attrs.perm[1]) == 1 or dim(shapes.data, attrs.perm[4]) == 1 or attrs.perm[1] < attrs.perm[4]) and (dim(shapes.data, attrs.perm[2]) == 1 or dim(shapes.data, attrs.perm[3]) == 1 or attrs.perm[2] < attrs.perm[3]) and (dim(shapes.data, attrs.perm[2]) == 1 or dim(shapes.data, attrs.perm[4]) == 1 or attrs.perm[2] < attrs.perm[4]) and (dim(shapes.data, attrs.perm[3]) == 1 or dim(shapes.data, attrs.perm[4]) == 1 or attrs.perm[3] < attrs.perm[4])",
75
  "memcpyPermOk": "memcpyPerm3 or memcpyPerm4 or memcpyPerm5",
76
- "identityPermutation": "rank1Permutation or (explicitRank2 and attrs.perm[0] == 0 and attrs.perm[1] == 1) or (explicitRank3 and attrs.perm[0] == 0 and attrs.perm[1] == 1 and attrs.perm[2] == 2) or (explicitRank4 and attrs.perm[0] == 0 and attrs.perm[1] == 1 and attrs.perm[2] == 2 and attrs.perm[3] == 3) or (explicitRank5 and attrs.perm[0] == 0 and attrs.perm[1] == 1 and attrs.perm[2] == 2 and attrs.perm[3] == 3 and attrs.perm[4] == 4) or (explicitRank6 and attrs.perm[0] == 0 and attrs.perm[1] == 1 and attrs.perm[2] == 2 and attrs.perm[3] == 3 and attrs.perm[4] == 4 and attrs.perm[5] == 5)",
77
- "identityCopyOk": "numel(shapes.transposed) > 0 and numel(shapes.transposed) % 4 == 0 and (identityPermutation or memcpyPermOk) and dtypeSupported",
78
- "tiled4dNchwToNhwcShapeOk": "explicitRank4 and attrs.perm[0] == 0 and attrs.perm[1] == 2 and attrs.perm[2] == 3 and attrs.perm[3] == 1 and numel(shapes.data) > 0 and dim(shapes.data, 0) <= device.limits.maxComputeWorkgroupsPerDimension and ceil(dim(shapes.data, 1) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and ceil((dim(shapes.data, 2) * dim(shapes.data, 3)) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and dtypeSupported",
79
- "tiled4dNchwToNhwcOk": "tiled4dNchwToNhwcShapeOk and dim(shapes.data, 1) % 4 == 0 and (dim(shapes.data, 2) * dim(shapes.data, 3)) % 4 == 0",
80
- "tiled4dNhwcToNchwShapeOk": "explicitRank4 and attrs.perm[0] == 0 and attrs.perm[1] == 3 and attrs.perm[2] == 1 and attrs.perm[3] == 2 and numel(shapes.data) > 0 and dim(shapes.data, 0) <= device.limits.maxComputeWorkgroupsPerDimension and ceil((dim(shapes.data, 1) * dim(shapes.data, 2)) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and ceil(dim(shapes.data, 3) / 32) <= device.limits.maxComputeWorkgroupsPerDimension and dtypeSupported",
81
- "tiled4dNhwcToNchwOk": "tiled4dNhwcToNchwShapeOk and (dim(shapes.data, 1) * dim(shapes.data, 2)) % 4 == 0 and dim(shapes.data, 3) % 4 == 0",
82
  "tiledTransposeOk": "tiled2dOk or tiled3dOk or tiled4dOk or tiled5dNcdhwToNdhwcOk or tiled5dNdhwcToNcdhwOk or tiled4dNchwToNhwcOk or tiled4dNhwcToNchwOk",
83
  "tiledScalarTransposeOk": "tiled2dShapeOk or tiled3dShapeOk or tiled4dShapeOk or tiled5dNcdhwToNdhwcShapeOk or tiled5dNdhwcToNcdhwShapeOk or tiled4dNchwToNhwcShapeOk or tiled4dNhwcToNchwShapeOk",
84
- "tiledRows": "dim(shapes.data, 0) if tiled2dShapeOk else (dim(shapes.data, 1) if tiled3dShapeOk else (dim(shapes.data, 2) if tiled4dShapeOk else (dim(shapes.data, 1) if tiled5dNcdhwToNdhwcShapeOk else (dim(shapes.data, 1) * dim(shapes.data, 2) * dim(shapes.data, 3) if tiled5dNdhwcToNcdhwShapeOk else (dim(shapes.data, 1) if tiled4dNchwToNhwcShapeOk else (dim(shapes.data, 1) * dim(shapes.data, 2) if tiled4dNhwcToNchwShapeOk else 1))))))",
85
- "tiledCols": "dim(shapes.data, 1) if tiled2dShapeOk else (dim(shapes.data, 2) if tiled3dShapeOk else (dim(shapes.data, 3) if tiled4dShapeOk else (dim(shapes.data, 2) * dim(shapes.data, 3) * dim(shapes.data, 4) if tiled5dNcdhwToNdhwcShapeOk else (dim(shapes.data, 4) if tiled5dNdhwcToNcdhwShapeOk else (dim(shapes.data, 2) * dim(shapes.data, 3) if tiled4dNchwToNhwcShapeOk else (dim(shapes.data, 3) if tiled4dNhwcToNchwShapeOk else 1))))))",
86
- "tiledBatch": "1 if tiled2dShapeOk else (dim(shapes.data, 0) if tiled3dShapeOk else (dim(shapes.data, 0) * dim(shapes.data, 1) if tiled4dShapeOk else (dim(shapes.data, 0) if tiled5dNcdhwToNdhwcShapeOk or tiled5dNdhwcToNcdhwShapeOk or tiled4dNchwToNhwcShapeOk or tiled4dNhwcToNchwShapeOk else 1)))"
87
  },
88
- "constants": { "usesF16": "dtypes.T == \"f16\"" },
89
- "bindingSets": {
90
- "vectorData": [
91
- {
92
- "name": "src",
93
- "arg": "x",
94
- "semantic": "data",
95
- "buffer": { "type": "read-only-storage" },
96
- "elementType": "$vectorScalar"
97
- },
98
- {
99
- "name": "dst",
100
- "arg": "y",
101
- "semantic": "transposed",
102
- "buffer": { "type": "storage" },
103
- "elementType": "$vectorScalar"
104
- }
105
- ],
106
- "scalarData": [
107
- {
108
- "name": "x",
109
- "arg": "x",
110
- "semantic": "data",
111
- "buffer": { "type": "read-only-storage" },
112
- "elementType": "$scalar"
113
- },
114
- { "name": "y", "arg": "y", "semantic": "transposed", "buffer": { "type": "storage" }, "elementType": "$scalar" },
115
- {
116
- "name": "params",
117
- "semantic": "kernel.params",
118
- "buffer": { "type": "uniform" },
119
- "struct": {
120
- "name": "Params",
121
- "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.transposed)" }]
122
- }
123
- }
124
- ],
125
- "scalarDataNoParams": [
126
- {
127
- "name": "x",
128
- "arg": "x",
129
- "semantic": "data",
130
- "buffer": { "type": "read-only-storage" },
131
- "elementType": "$scalar"
132
- },
133
- { "name": "y", "arg": "y", "semantic": "transposed", "buffer": { "type": "storage" }, "elementType": "$scalar" }
134
- ]
135
  },
136
  "variants": [
137
  {
138
  "id": "tiled_vec4",
139
  "priority": 22,
140
  "when": ["tiledTransposeOk"],
141
- "constants": { "scalar": "dtypes.T", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
142
  "passes": [
143
  {
144
  "id": "main",
145
  "name": "Transpose.tiled",
146
- "source": {
147
- "shader": "datamove-transpose-2d-tiled.wgsl.jinja",
148
- "inputs": { "rows": "tiledRows", "cols": "tiledCols" }
149
- },
150
- "bindings": "vectorData",
151
  "dispatch": { "x": "ceil(tiledCols / 32)", "y": "ceil(tiledRows / 32)", "z": "tiledBatch" }
152
  }
153
  ]
154
  },
155
  {
156
  "id": "tiled_scalar",
157
- "description": "Stages ragged two-dimensional transpose planes through a padded workgroup tile so reads and writes remain coalesced when vec4 alignment is unavailable.",
158
  "priority": 19,
159
  "when": ["tiledScalarTransposeOk", "dtypeSupported"],
160
- "constants": { "scalar": "dtypes.T" },
161
  "passes": [
162
  {
163
  "id": "main",
164
  "name": "Transpose.tiledScalar",
165
- "source": {
166
- "shader": "datamove-transpose-2d-tiled-scalar.wgsl.jinja",
167
- "inputs": { "rows": "tiledRows", "cols": "tiledCols" }
168
- },
169
- "bindings": "scalarDataNoParams",
170
  "dispatch": { "x": "ceil(tiledCols / 32)", "y": "ceil(tiledRows / 32)", "z": "tiledBatch" }
171
  }
172
  ]
@@ -175,41 +100,44 @@
175
  "id": "identity_vec4",
176
  "priority": 21,
177
  "when": ["identityCopyOk"],
178
- "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
179
  "passes": [
180
  {
181
  "id": "main",
182
  "name": "Transpose.identityVec4",
183
- "source": { "shader": "datamove-flat-copy.wgsl.jinja", "inputs": { "count": "numel(shapes.transposed) / 4" } },
184
- "bindings": "vectorData",
185
- "dispatch": { "gridStride": "numel(shapes.transposed) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
186
  }
187
  ]
188
  },
189
  {
190
  "id": "inner_vec4",
191
- "description": "Vectorizes the contiguous innermost dimension for permutations that leave that axis in place. It assigns multiple vectors per invocation only when enough invocations remain to keep the dispatch populated.",
192
  "priority": 20,
193
  "when": ["innerVec4Ok"],
194
- "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
195
  "passes": [
196
  {
197
  "id": "main",
198
  "name": "Transpose.innerVec4",
199
- "source": {
200
- "shader": "datamove-transpose-vec4.wgsl.jinja",
201
- "inputs": {
202
- "inputShape": "shapes.data",
203
- "outputShape": "shapes.transposed",
204
- "rank": "ranks.data",
205
- "perm": "innerVec4Perm",
206
- "count": "numel(shapes.transposed) / 4"
207
- }
208
  },
209
- "bindings": "vectorData",
210
  "dispatch": {
211
- "gridStride": "max(min(numel(shapes.transposed) / 4, tunables.INNER_VEC4_MIN_THREADS), ceilDiv(numel(shapes.transposed) / 4, tunables.INNER_VEC4_ITEMS_PER_THREAD))",
212
- "workgroupSize": "tunables.WORKGROUP_SIZE"
 
213
  }
214
  }
215
  ]
@@ -217,23 +145,29 @@
217
  {
218
  "id": "generic",
219
  "when": ["genericTransposeOk", "dtypeSupported"],
220
- "constants": { "scalar": "dtypes.T" },
221
  "passes": [
222
  {
223
  "id": "main",
224
  "name": "Transpose",
225
- "source": {
226
- "shader": "transpose.wgsl.jinja",
227
- "inputs": {
228
- "inputShape": "shapes.data",
229
- "outputShape": "shapes.transposed",
230
- "rank": "ranks.data",
231
- "perm": "genericPerm",
232
- "itemsPerInvocation": 4
233
- }
234
  },
235
- "bindings": "scalarData",
236
- "dispatch": { "threads": "ceilDiv(numel(shapes.transposed), 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
 
 
 
237
  }
238
  ]
239
  }
 
2
  "domain": "ai.onnx",
3
  "name": "Transpose",
4
  "sinceVersion": 13,
5
+ "inputs": { "x": { "onnx": "data", "dtype": "T" } },
6
+ "outputs": { "y": { "onnx": "transposed", "dtype": "T", "rank": "ranks.x" } },
7
+ "attributes": { "perm": {} },
 
 
 
 
 
 
 
 
 
 
 
8
  "typeConstraints": { "T": ["float32", "float16", "int32", "int16", "uint32", "uint8", "int8", "bool"] },
9
+ "tunables": {
10
+ "WORKGROUP_SIZE": { "default": 256 },
11
+ "INNER_VEC4_ITEMS_PER_THREAD": { "default": 4 },
12
+ "INNER_VEC4_MIN_THREADS": { "default": 65536 }
 
 
 
 
13
  },
14
  "derive": {
15
  "dtypeSupported": "f16Ok(dtypes.T)",
16
+ "rank2IO": "ranks.x == 2 and ranks.y == 2",
17
+ "rank3IO": "ranks.x == 3 and ranks.y == 3",
18
+ "rank4IO": "ranks.x == 4 and ranks.y == 4",
19
+ "rank5IO": "ranks.x == 5 and ranks.y == 5",
 
 
 
 
20
  "permPresent": "has(attrs, \"perm\")",
21
+ "permContract": "not permPresent or ((attrs.perm | length) == ranks.x and isUniqueIntList(attrs.perm, 0, ranks.x))",
22
+ "explicitRank2": "rank2IO and permContract and permPresent and dim(shapes.y, 0) == dim(shapes.x, attrs.perm[0]) and dim(shapes.y, 1) == dim(shapes.x, attrs.perm[1])",
23
+ "explicitRank3": "rank3IO and permContract and permPresent and dim(shapes.y, 0) == dim(shapes.x, attrs.perm[0]) and dim(shapes.y, 1) == dim(shapes.x, attrs.perm[1]) and dim(shapes.y, 2) == dim(shapes.x, attrs.perm[2])",
24
+ "explicitRank4": "rank4IO and permContract and permPresent and dim(shapes.y, 0) == dim(shapes.x, attrs.perm[0]) and dim(shapes.y, 1) == dim(shapes.x, attrs.perm[1]) and dim(shapes.y, 2) == dim(shapes.x, attrs.perm[2]) and dim(shapes.y, 3) == dim(shapes.x, attrs.perm[3])",
25
+ "explicitRank5": "rank5IO and permContract and permPresent and dim(shapes.y, 0) == dim(shapes.x, attrs.perm[0]) and dim(shapes.y, 1) == dim(shapes.x, attrs.perm[1]) and dim(shapes.y, 2) == dim(shapes.x, attrs.perm[2]) and dim(shapes.y, 3) == dim(shapes.x, attrs.perm[3]) and dim(shapes.y, 4) == dim(shapes.x, attrs.perm[4])",
26
+ "defaultRank2": "rank2IO and permContract and not permPresent and dim(shapes.y, 0) == dim(shapes.x, 1) and dim(shapes.y, 1) == dim(shapes.x, 0)",
27
+ "defaultPerm": "reversedAxes(ranks.x)",
 
 
 
 
 
 
 
 
 
 
28
  "genericPerm": "pick([[permPresent, attrs.perm]], defaultPerm)",
29
+ "genericTransposeOk": "ranks.x == ranks.y and permContract and sameShape(shapes.y, permuteShape(shapes.x, genericPerm))",
30
+ "innerVec4Rank3Ok": "explicitRank3 and attrs.perm[2] == 2 and dim(shapes.x, 2) % 4 == 0 and numel(shapes.y) > 0 and dtypeSupported",
31
+ "innerVec4Rank4Ok": "explicitRank4 and attrs.perm[3] == 3 and dim(shapes.x, 3) % 4 == 0 and numel(shapes.y) > 0 and dtypeSupported",
32
  "innerVec4Ok": "innerVec4Rank3Ok or innerVec4Rank4Ok",
33
  "innerVec4Perm": "attrs.perm",
34
+ "tiled2dShapeOk": "(defaultRank2 or (explicitRank2 and attrs.perm[0] == 1 and attrs.perm[1] == 0)) and numel(shapes.x) > 0 and ceil(dim(shapes.x, 0) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceil(dim(shapes.x, 1) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and dtypeSupported",
35
+ "tiled2dOk": "tiled2dShapeOk and dim(shapes.x, 0) % 4 == 0 and dim(shapes.x, 1) % 4 == 0",
36
+ "tiled3dShapeOk": "explicitRank3 and attrs.perm[0] == 0 and attrs.perm[1] == 2 and attrs.perm[2] == 1 and numel(shapes.x) > 0 and dim(shapes.x, 0) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceil(dim(shapes.x, 1) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceil(dim(shapes.x, 2) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and dtypeSupported",
37
+ "tiled3dOk": "tiled3dShapeOk and dim(shapes.x, 1) % 4 == 0 and dim(shapes.x, 2) % 4 == 0",
38
+ "tiled4dShapeOk": "explicitRank4 and attrs.perm[0] == 0 and attrs.perm[1] == 1 and attrs.perm[2] == 3 and attrs.perm[3] == 2 and numel(shapes.x) > 0 and dim(shapes.x, 0) * dim(shapes.x, 1) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceil(dim(shapes.x, 2) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceil(dim(shapes.x, 3) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and dtypeSupported",
39
+ "tiled4dOk": "tiled4dShapeOk and dim(shapes.x, 2) % 4 == 0 and dim(shapes.x, 3) % 4 == 0",
40
+ "tiled5dNcdhwToNdhwcShapeOk": "explicitRank5 and attrs.perm[0] == 0 and attrs.perm[1] == 2 and attrs.perm[2] == 3 and attrs.perm[3] == 4 and attrs.perm[4] == 1 and numel(shapes.x) > 0 and dim(shapes.x, 0) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceil(dim(shapes.x, 1) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceil((dim(shapes.x, 2) * dim(shapes.x, 3) * dim(shapes.x, 4)) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and dtypeSupported",
41
+ "tiled5dNcdhwToNdhwcOk": "tiled5dNcdhwToNdhwcShapeOk and dim(shapes.x, 1) % 4 == 0 and (dim(shapes.x, 2) * dim(shapes.x, 3) * dim(shapes.x, 4)) % 4 == 0",
42
+ "tiled5dNdhwcToNcdhwShapeOk": "explicitRank5 and attrs.perm[0] == 0 and attrs.perm[1] == 4 and attrs.perm[2] == 1 and attrs.perm[3] == 2 and attrs.perm[4] == 3 and numel(shapes.x) > 0 and dim(shapes.x, 0) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceil(dim(shapes.x, 1) * dim(shapes.x, 2) * dim(shapes.x, 3) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceil(dim(shapes.x, 4) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and dtypeSupported",
43
+ "tiled5dNdhwcToNcdhwOk": "tiled5dNdhwcToNcdhwShapeOk and dim(shapes.x, 1) * dim(shapes.x, 2) * dim(shapes.x, 3) % 4 == 0 and dim(shapes.x, 4) % 4 == 0",
44
+ "memcpyPerm3": "explicitRank3 and (dim(shapes.x, attrs.perm[0]) == 1 or dim(shapes.x, attrs.perm[1]) == 1 or attrs.perm[0] < attrs.perm[1]) and (dim(shapes.x, attrs.perm[0]) == 1 or dim(shapes.x, attrs.perm[2]) == 1 or attrs.perm[0] < attrs.perm[2]) and (dim(shapes.x, attrs.perm[1]) == 1 or dim(shapes.x, attrs.perm[2]) == 1 or attrs.perm[1] < attrs.perm[2])",
45
+ "memcpyPerm4": "explicitRank4 and (dim(shapes.x, attrs.perm[0]) == 1 or dim(shapes.x, attrs.perm[1]) == 1 or attrs.perm[0] < attrs.perm[1]) and (dim(shapes.x, attrs.perm[0]) == 1 or dim(shapes.x, attrs.perm[2]) == 1 or attrs.perm[0] < attrs.perm[2]) and (dim(shapes.x, attrs.perm[0]) == 1 or dim(shapes.x, attrs.perm[3]) == 1 or attrs.perm[0] < attrs.perm[3]) and (dim(shapes.x, attrs.perm[1]) == 1 or dim(shapes.x, attrs.perm[2]) == 1 or attrs.perm[1] < attrs.perm[2]) and (dim(shapes.x, attrs.perm[1]) == 1 or dim(shapes.x, attrs.perm[3]) == 1 or attrs.perm[1] < attrs.perm[3]) and (dim(shapes.x, attrs.perm[2]) == 1 or dim(shapes.x, attrs.perm[3]) == 1 or attrs.perm[2] < attrs.perm[3])",
46
+ "memcpyPerm5": "explicitRank5 and (dim(shapes.x, attrs.perm[0]) == 1 or dim(shapes.x, attrs.perm[1]) == 1 or attrs.perm[0] < attrs.perm[1]) and (dim(shapes.x, attrs.perm[0]) == 1 or dim(shapes.x, attrs.perm[2]) == 1 or attrs.perm[0] < attrs.perm[2]) and (dim(shapes.x, attrs.perm[0]) == 1 or dim(shapes.x, attrs.perm[3]) == 1 or attrs.perm[0] < attrs.perm[3]) and (dim(shapes.x, attrs.perm[0]) == 1 or dim(shapes.x, attrs.perm[4]) == 1 or attrs.perm[0] < attrs.perm[4]) and (dim(shapes.x, attrs.perm[1]) == 1 or dim(shapes.x, attrs.perm[2]) == 1 or attrs.perm[1] < attrs.perm[2]) and (dim(shapes.x, attrs.perm[1]) == 1 or dim(shapes.x, attrs.perm[3]) == 1 or attrs.perm[1] < attrs.perm[3]) and (dim(shapes.x, attrs.perm[1]) == 1 or dim(shapes.x, attrs.perm[4]) == 1 or attrs.perm[1] < attrs.perm[4]) and (dim(shapes.x, attrs.perm[2]) == 1 or dim(shapes.x, attrs.perm[3]) == 1 or attrs.perm[2] < attrs.perm[3]) and (dim(shapes.x, attrs.perm[2]) == 1 or dim(shapes.x, attrs.perm[4]) == 1 or attrs.perm[2] < attrs.perm[4]) and (dim(shapes.x, attrs.perm[3]) == 1 or dim(shapes.x, attrs.perm[4]) == 1 or attrs.perm[3] < attrs.perm[4])",
47
  "memcpyPermOk": "memcpyPerm3 or memcpyPerm4 or memcpyPerm5",
48
+ "identityPermutation": "genericTransposeOk and sameShape(genericPerm, reversedAxes(ranks.x) | reverse)",
49
+ "identityCopyOk": "numel(shapes.y) > 0 and numel(shapes.y) % 4 == 0 and (identityPermutation or memcpyPermOk) and dtypeSupported",
50
+ "tiled4dNchwToNhwcShapeOk": "explicitRank4 and attrs.perm[0] == 0 and attrs.perm[1] == 2 and attrs.perm[2] == 3 and attrs.perm[3] == 1 and numel(shapes.x) > 0 and dim(shapes.x, 0) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceil(dim(shapes.x, 1) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceil((dim(shapes.x, 2) * dim(shapes.x, 3)) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and dtypeSupported",
51
+ "tiled4dNchwToNhwcOk": "tiled4dNchwToNhwcShapeOk and dim(shapes.x, 1) % 4 == 0 and (dim(shapes.x, 2) * dim(shapes.x, 3)) % 4 == 0",
52
+ "tiled4dNhwcToNchwShapeOk": "explicitRank4 and attrs.perm[0] == 0 and attrs.perm[1] == 3 and attrs.perm[2] == 1 and attrs.perm[3] == 2 and numel(shapes.x) > 0 and dim(shapes.x, 0) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceil((dim(shapes.x, 1) * dim(shapes.x, 2)) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceil(dim(shapes.x, 3) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and dtypeSupported",
53
+ "tiled4dNhwcToNchwOk": "tiled4dNhwcToNchwShapeOk and (dim(shapes.x, 1) * dim(shapes.x, 2)) % 4 == 0 and dim(shapes.x, 3) % 4 == 0",
54
  "tiledTransposeOk": "tiled2dOk or tiled3dOk or tiled4dOk or tiled5dNcdhwToNdhwcOk or tiled5dNdhwcToNcdhwOk or tiled4dNchwToNhwcOk or tiled4dNhwcToNchwOk",
55
  "tiledScalarTransposeOk": "tiled2dShapeOk or tiled3dShapeOk or tiled4dShapeOk or tiled5dNcdhwToNdhwcShapeOk or tiled5dNdhwcToNcdhwShapeOk or tiled4dNchwToNhwcShapeOk or tiled4dNhwcToNchwShapeOk",
56
+ "tiledRows": "dim(shapes.x, 0) if tiled2dShapeOk else (dim(shapes.x, 1) if tiled3dShapeOk else (dim(shapes.x, 2) if tiled4dShapeOk else (dim(shapes.x, 1) if tiled5dNcdhwToNdhwcShapeOk else (dim(shapes.x, 1) * dim(shapes.x, 2) * dim(shapes.x, 3) if tiled5dNdhwcToNcdhwShapeOk else (dim(shapes.x, 1) if tiled4dNchwToNhwcShapeOk else (dim(shapes.x, 1) * dim(shapes.x, 2) if tiled4dNhwcToNchwShapeOk else 1))))))",
57
+ "tiledCols": "dim(shapes.x, 1) if tiled2dShapeOk else (dim(shapes.x, 2) if tiled3dShapeOk else (dim(shapes.x, 3) if tiled4dShapeOk else (dim(shapes.x, 2) * dim(shapes.x, 3) * dim(shapes.x, 4) if tiled5dNcdhwToNdhwcShapeOk else (dim(shapes.x, 4) if tiled5dNdhwcToNcdhwShapeOk else (dim(shapes.x, 2) * dim(shapes.x, 3) if tiled4dNchwToNhwcShapeOk else (dim(shapes.x, 3) if tiled4dNhwcToNchwShapeOk else 1))))))",
58
+ "tiledBatch": "1 if tiled2dShapeOk else (dim(shapes.x, 0) if tiled3dShapeOk else (dim(shapes.x, 0) * dim(shapes.x, 1) if tiled4dShapeOk else (dim(shapes.x, 0) if tiled5dNcdhwToNdhwcShapeOk or tiled5dNdhwcToNcdhwShapeOk or tiled4dNchwToNhwcShapeOk or tiled4dNhwcToNchwShapeOk else 1)))"
59
  },
60
+ "bindings": {
61
+ "src": { "arg": "x", "buffer": "read-only-storage", "elementType": "$vectorScalar" },
62
+ "dst": { "arg": "y", "buffer": "storage", "elementType": "$vectorScalar" },
63
+ "x": { "buffer": "read-only-storage", "elementType": "$scalar" },
64
+ "y": { "buffer": "storage", "elementType": "$scalar" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  },
66
  "variants": [
67
  {
68
  "id": "tiled_vec4",
69
  "priority": 22,
70
  "when": ["tiledTransposeOk"],
71
+ "derive": { "scalar": "dtypes.T", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
72
  "passes": [
73
  {
74
  "id": "main",
75
  "name": "Transpose.tiled",
76
+ "shader": "datamove-transpose-2d-tiled.wgsl.jinja",
77
+ "derive": { "rowCount": "tiledRows", "cols": "tiledCols" },
78
+ "bindings": ["src", "dst"],
 
 
79
  "dispatch": { "x": "ceil(tiledCols / 32)", "y": "ceil(tiledRows / 32)", "z": "tiledBatch" }
80
  }
81
  ]
82
  },
83
  {
84
  "id": "tiled_scalar",
 
85
  "priority": 19,
86
  "when": ["tiledScalarTransposeOk", "dtypeSupported"],
87
+ "derive": { "scalar": "dtypes.T" },
88
  "passes": [
89
  {
90
  "id": "main",
91
  "name": "Transpose.tiledScalar",
92
+ "shader": "datamove-transpose-2d-tiled-scalar.wgsl.jinja",
93
+ "derive": { "rowCount": "tiledRows", "cols": "tiledCols" },
94
+ "bindings": ["x", "y"],
 
 
95
  "dispatch": { "x": "ceil(tiledCols / 32)", "y": "ceil(tiledRows / 32)", "z": "tiledBatch" }
96
  }
97
  ]
 
100
  "id": "identity_vec4",
101
  "priority": 21,
102
  "when": ["identityCopyOk"],
103
+ "derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
104
  "passes": [
105
  {
106
  "id": "main",
107
  "name": "Transpose.identityVec4",
108
+ "shader": "datamove-flat-copy.wgsl.jinja",
109
+ "derive": { "count": "numel(shapes.y) / 4" },
110
+ "bindings": ["src", "dst"],
111
+ "dispatch": {
112
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
113
+ "y": 1,
114
+ "z": 1
115
+ }
116
  }
117
  ]
118
  },
119
  {
120
  "id": "inner_vec4",
 
121
  "priority": 20,
122
  "when": ["innerVec4Ok"],
123
+ "derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
124
  "passes": [
125
  {
126
  "id": "main",
127
  "name": "Transpose.innerVec4",
128
+ "shader": "datamove-transpose-vec4.wgsl.jinja",
129
+ "derive": {
130
+ "inputShape": "shapes.x",
131
+ "outputShape": "shapes.y",
132
+ "rank": "ranks.x",
133
+ "perm": "innerVec4Perm",
134
+ "count": "numel(shapes.y) / 4"
 
 
135
  },
136
+ "bindings": ["src", "dst"],
137
  "dispatch": {
138
+ "x": "min(ceilDiv((max(min(numel(shapes.y) / 4, tunables.INNER_VEC4_MIN_THREADS), ceilDiv(numel(shapes.y) / 4, tunables.INNER_VEC4_ITEMS_PER_THREAD))), (tunables.WORKGROUP_SIZE)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
139
+ "y": 1,
140
+ "z": 1
141
  }
142
  }
143
  ]
 
145
  {
146
  "id": "generic",
147
  "when": ["genericTransposeOk", "dtypeSupported"],
148
+ "derive": { "scalar": "dtypes.T" },
149
  "passes": [
150
  {
151
  "id": "main",
152
  "name": "Transpose",
153
+ "shader": "transpose.wgsl.jinja",
154
+ "derive": {
155
+ "inputShape": "shapes.x",
156
+ "outputShape": "shapes.y",
157
+ "rank": "ranks.x",
158
+ "perm": "genericPerm",
159
+ "itemsPerInvocation": 4
 
 
160
  },
161
+ "bindings": [
162
+ "x",
163
+ "y",
164
+ { "name": "params", "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] }
165
+ ],
166
+ "dispatch": {
167
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
168
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
169
+ "z": 1
170
+ }
171
  }
172
  ]
173
  }
build/webgpu/metadata.json CHANGED
@@ -1,22 +1,31 @@
1
  {
2
  "name": "ai.onnx.Transpose",
3
- "id": "_ai_onnx_transpose_webgpu_713616c",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "wT5OzWQiJ0OSKnc+JuSj0vOoFzY1Q/JzgJW1jpIbv5Q=",
11
- "datamove-flat-copy.wgsl.jinja": "rEpeYMvIZQuQnB2f3lt51jrrklKICsqUTzCJPRTknNk=",
12
- "datamove-transpose-2d-tiled-scalar.wgsl.jinja": "zy7rlN8Se+mn8RB9akbBTHq1RK8ot6NBboxG3vX1Xdg=",
13
- "datamove-transpose-2d-tiled.wgsl.jinja": "AtmY7NlBNCV1unIf2epvpBO3hfnLBmgGKGB7WiWMCQk=",
14
- "datamove-transpose-vec4.wgsl.jinja": "RgwjTbu7SHDVsUW7W2f5S81gv1UJBpT93udQX/MZdJ0=",
15
- "manifest.json": "Av+HRbQxG96oiNzrlouEobs2tS/VmfX0tCkNdy8rWL4=",
16
- "test.json": "acNlC/DcnpoXb3ni/Z4QMbpYD7C1YR16iHXX20mNp0Y=",
17
- "transpose.wgsl.jinja": "lYYw/Xqp/wTaYlWr8iB/vn1ZxacE3jOes9gYViMlhlo="
18
  }
19
  },
20
- "provenance": { "kernel": { "sha": "c928d21e6cc1310861cba3bafb75f5f679ecf5f3", "dirty": false } },
21
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.Transpose" }
 
 
 
 
 
 
 
 
 
22
  }
 
1
  {
2
  "name": "ai.onnx.Transpose",
3
+ "id": "_ai_onnx_transpose_webgpu_2a9c5f1",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "ClUFa7Y4uL69eUy46pTK4YciV/+jJxlDxSJFK+/JU6A=",
11
+ "datamove-flat-copy.wgsl.jinja": "g9d62mer2bmHfbScqX5CIT5Zq/Pdinac0W2dH0CD+5s=",
12
+ "datamove-transpose-2d-tiled-scalar.wgsl.jinja": "ycF9afS0CqHrLIfFi6hAqp+7GIcnHHAyN74hSF+/FrY=",
13
+ "datamove-transpose-2d-tiled.wgsl.jinja": "GNVRxhagDFVQ0DGjsUGD8BVzktDHsELwe0qw4xRHD60=",
14
+ "datamove-transpose-vec4.wgsl.jinja": "NARpwfOEcl8GRu17lnq62KXK/mgh61Jj3YHfi8Zofik=",
15
+ "manifest.json": "F4FH/KuY12tkUnIKlE3wBmydDgzgzAbfPva12DYYGBY=",
16
+ "test.json": "rQerBft2NMuE0I+S7GErvy8rPRoUQxOkI3NvNH5dcKc=",
17
+ "transpose.wgsl.jinja": "a9TDelnkVrpe1XBA8rTAurSGrTmIQ6BO16ZHRJWiQJw="
18
  }
19
  },
20
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
21
+ "webgpu": {
22
+ "manifestSpec": "2.0",
23
+ "variants": {
24
+ "tiled_vec4": ["datamove-transpose-2d-tiled.wgsl.jinja"],
25
+ "tiled_scalar": ["datamove-transpose-2d-tiled-scalar.wgsl.jinja"],
26
+ "identity_vec4": ["datamove-flat-copy.wgsl.jinja"],
27
+ "inner_vec4": ["datamove-transpose-vec4.wgsl.jinja"],
28
+ "generic": ["transpose.wgsl.jinja"]
29
+ }
30
+ }
31
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.Transpose",
3
  "fixtureArrays": {
4
  "onnx_backend_transpose_input_x": [0.54881352186203, 0.7151893377304077, 0.6027633547782898, 0.5448831915855408, 0.42365479469299316, 0.6458941102027893, 0.4375872015953064, 0.891772985458374, 0.9636627435684204, 0.3834415078163147, 0.7917250394821167, 0.5288949012756348, 0.5680445432662964, 0.9255966544151306, 0.07103605568408966, 0.08712930232286453, 0.020218396559357643, 0.832619845867157, 0.7781567573547363, 0.8700121641159058, 0.978618323802948, 0.7991585731506348, 0.4614793658256531, 0.7805292010307312],
5
  "ort_transpose021_f32_input_x": [1, 2, 3, 4, 5, 6, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 1.3, 2.3, 3.3, 4.3, 5.3, 6.3],
@@ -189,7 +188,7 @@
189
  "provenance": {
190
  "source": "onnxruntime/test/providers/cpu/tensor/transpose_test.cc",
191
  "test": "TransposeOpTest.TwoDimNoAttr",
192
- "notes": "Same two-dimensional data-movement fixture with an ONNX-valid bool payload."
193
  },
194
  "inputs": {
195
  "x": { "dtype": "bool", "shape": [2, 3], "data": { "kind": "values", "values": [1, 0, 1, 0, 1, 0] } }
@@ -249,7 +248,7 @@
249
  "provenance": {
250
  "source": "onnxruntime/test/providers/cpu/tensor/transpose_test.cc",
251
  "test": "TransposeOpTest.TwoDim_int32",
252
- "notes": "ORT covers int32; this framework covers the same 2D explicit transpose with supported uint32 data."
253
  },
254
  "attrs": { "perm": [1, 0] },
255
  "inputs": {
@@ -401,7 +400,7 @@
401
  "provenance": {
402
  "source": "onnxruntime/test/providers/cpu/tensor/transpose_test.cc",
403
  "test": "TransposeOpTest.SingleAxisMovingInwardsBlockCopy",
404
- "notes": "ORT uses uint64 payload values; this framework stores the representable test values as uint32."
405
  },
406
  "attrs": { "perm": [1, 2, 0, 3] },
407
  "inputs": {
@@ -807,7 +806,7 @@
807
  {
808
  "name": "rank2_scalar_odd_width_5x7",
809
  "provenance": {
810
- "notes": "Compact correctness sibling for the odd-width rank2 scalar transpose benchmark; the dimensions avoid vec4/tiled alignment while preserving the same rank2 permutation."
811
  },
812
  "attrs": { "perm": [1, 0] },
813
  "inputs": {
@@ -824,7 +823,7 @@
824
  "provenance": {
825
  "source": "onnxruntime/test/providers/cpu/tensor/transpose_test.cc",
826
  "test": "TransposeOpTest (generic N-dim)",
827
- "notes": "Rank-7 explicit-permutation coverage for the generic transpose path. The manifest supplies the rank and permutation as template inputs instead of maintaining a rank-specific variant."
828
  },
829
  "attrs": { "perm": [1, 0, 3, 2, 5, 4, 6] },
830
  "inputs": {
@@ -844,7 +843,7 @@
844
  "provenance": {
845
  "source": "onnxruntime/test/providers/cpu/tensor/transpose_test.cc",
846
  "test": "TransposeOpTest.TwoDim (bool payload)",
847
- "notes": "bool is in typeConstraints T and scalarType maps it to u32, so an aligned [4,4] bool transpose selects tiled_vec4 (gate is only f16Ok, true for u32). The pre-existing ort_two_dim_default_bool_gpu_gap fixture is stale-named; this NON-gpu-gapped case gives bool its first real GPU validation on the vec4 path."
848
  },
849
  "attrs": { "perm": [1, 0] },
850
  "inputs": {
@@ -861,7 +860,7 @@
861
  "provenance": {
862
  "source": "onnxruntime/test/providers/cpu/tensor/transpose_test.cc",
863
  "test": "TransposeOpTest.TransposeReshape (scrambled rank5)",
864
- "notes": "Genuinely scrambled rank-5 explicit perm [4,2,0,3,1] exercising the generic scalar-gather path with rank-specific template inputs; prior rank-5 coverage only exercised a default reversal and a near-reshape perm."
865
  },
866
  "attrs": { "perm": [4, 2, 0, 3, 1] },
867
  "inputs": {
@@ -893,7 +892,7 @@
893
  {
894
  "name": "rank1_2d_fold_boundary_last_element_guard",
895
  "provenance": {
896
- "notes": "numel = 2^24+1 = 16777217 forces ceilDiv(numel,256)=65536 > maxComputeWorkgroupsPerDimension(65535), engaging the scalar path's y-axis 2D fold (i = gid.x + gid.y*nwg.x*WG). rank1 numel%4 != 0 bypasses identity_vec4 and lands on the generic scalar path, isolating the fold last-row guard. Referenceless: TS reference supplies truth so a corrupted/dropped tail element in the second y-row fails the compare."
897
  },
898
  "inputs": {
899
  "x": {
@@ -1081,7 +1080,7 @@
1081
  "provenance": {
1082
  "source": "onnxruntime/core/providers/webgpu/tensor/transpose.cc",
1083
  "test": "unit-dim permutations lower to a flat copy",
1084
- "notes": "A permutation that only moves size-1 axes preserves the row-major layout, so the flat vec4 copy is exact."
1085
  },
1086
  "attrs": { "perm": [0, 2, 1, 3] },
1087
  "inputs": {
@@ -1092,6 +1091,38 @@
1092
  }
1093
  },
1094
  "outputs": { "y": { "dtype": "float32", "shape": [2, 4, 3, 4], "tolerance": 0 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1095
  }
1096
  ]
1097
  }
 
1
  {
 
2
  "fixtureArrays": {
3
  "onnx_backend_transpose_input_x": [0.54881352186203, 0.7151893377304077, 0.6027633547782898, 0.5448831915855408, 0.42365479469299316, 0.6458941102027893, 0.4375872015953064, 0.891772985458374, 0.9636627435684204, 0.3834415078163147, 0.7917250394821167, 0.5288949012756348, 0.5680445432662964, 0.9255966544151306, 0.07103605568408966, 0.08712930232286453, 0.020218396559357643, 0.832619845867157, 0.7781567573547363, 0.8700121641159058, 0.978618323802948, 0.7991585731506348, 0.4614793658256531, 0.7805292010307312],
4
  "ort_transpose021_f32_input_x": [1, 2, 3, 4, 5, 6, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 1.3, 2.3, 3.3, 4.3, 5.3, 6.3],
 
188
  "provenance": {
189
  "source": "onnxruntime/test/providers/cpu/tensor/transpose_test.cc",
190
  "test": "TransposeOpTest.TwoDimNoAttr",
191
+ "notes": "A two-dimensional transpose with an ONNX-valid bool payload verifies bit-preserving data movement."
192
  },
193
  "inputs": {
194
  "x": { "dtype": "bool", "shape": [2, 3], "data": { "kind": "values", "values": [1, 0, 1, 0, 1, 0] } }
 
248
  "provenance": {
249
  "source": "onnxruntime/test/providers/cpu/tensor/transpose_test.cc",
250
  "test": "TransposeOpTest.TwoDim_int32",
251
+ "notes": "A uint32 payload exercises an explicit two-dimensional transpose."
252
  },
253
  "attrs": { "perm": [1, 0] },
254
  "inputs": {
 
400
  "provenance": {
401
  "source": "onnxruntime/test/providers/cpu/tensor/transpose_test.cc",
402
  "test": "TransposeOpTest.SingleAxisMovingInwardsBlockCopy",
403
+ "notes": "Projects representable uint64 source values to uint32 storage."
404
  },
405
  "attrs": { "perm": [1, 2, 0, 3] },
406
  "inputs": {
 
806
  {
807
  "name": "rank2_scalar_odd_width_5x7",
808
  "provenance": {
809
+ "notes": "A 5-by-7 transpose avoids vector and tiled alignment, exercising scalar rank-2 address mapping."
810
  },
811
  "attrs": { "perm": [1, 0] },
812
  "inputs": {
 
823
  "provenance": {
824
  "source": "onnxruntime/test/providers/cpu/tensor/transpose_test.cc",
825
  "test": "TransposeOpTest (generic N-dim)",
826
+ "notes": "A rank-7 explicit permutation exercises generic high-rank index decomposition."
827
  },
828
  "attrs": { "perm": [1, 0, 3, 2, 5, 4, 6] },
829
  "inputs": {
 
843
  "provenance": {
844
  "source": "onnxruntime/test/providers/cpu/tensor/transpose_test.cc",
845
  "test": "TransposeOpTest.TwoDim (bool payload)",
846
+ "notes": "An aligned 4-by-4 bool tensor uses u32 storage and exercises vectorized tiled transpose without requiring subnormal preservation."
847
  },
848
  "attrs": { "perm": [1, 0] },
849
  "inputs": {
 
860
  "provenance": {
861
  "source": "onnxruntime/test/providers/cpu/tensor/transpose_test.cc",
862
  "test": "TransposeOpTest.TransposeReshape (scrambled rank5)",
863
+ "notes": "The rank-5 permutation [4,2,0,3,1] scrambles all nontrivial strides and exercises generic scalar gathering."
864
  },
865
  "attrs": { "perm": [4, 2, 0, 3, 1] },
866
  "inputs": {
 
892
  {
893
  "name": "rank1_2d_fold_boundary_last_element_guard",
894
  "provenance": {
895
+ "notes": "A rank-1 identity transpose with 16,777,217 elements requires 65,536 workgroups and therefore a two-row scalar dispatch. The odd element count exercises the final-row bound and makes a dropped tail element observable."
896
  },
897
  "inputs": {
898
  "x": {
 
1080
  "provenance": {
1081
  "source": "onnxruntime/core/providers/webgpu/tensor/transpose.cc",
1082
  "test": "unit-dim permutations lower to a flat copy",
1083
+ "notes": "Permutation [0,2,1,3] swaps non-unit extents 3 and 4, so it must perform a true transpose rather than a flat copy."
1084
  },
1085
  "attrs": { "perm": [0, 2, 1, 3] },
1086
  "inputs": {
 
1091
  }
1092
  },
1093
  "outputs": { "y": { "dtype": "float32", "shape": [2, 4, 3, 4], "tolerance": 0 } }
1094
+ },
1095
+ {
1096
+ "name": "rank8_explicit_perm_regroups_two_middle_axes",
1097
+ "provenance": {
1098
+ "source": "ONNX Transpose-13 specification",
1099
+ "notes": "A rank-8 staging permutation that moves one middle axis past two others. A distinct value per position makes any axis-stride mistake visible at the position it corrupts."
1100
+ },
1101
+ "attrs": { "perm": [0, 1, 2, 3, 5, 6, 4, 7] },
1102
+ "inputs": {
1103
+ "x": {
1104
+ "dtype": "float32",
1105
+ "shape": [2, 3, 2, 2, 4, 2, 2, 4],
1106
+ "data": { "kind": "linspace", "start": 0.0, "end": 1535.0 }
1107
+ }
1108
+ },
1109
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 2, 2, 2, 2, 4, 4], "tolerance": 0 } }
1110
+ },
1111
+ {
1112
+ "name": "rank8_default_perm_reverses_every_axis",
1113
+ "provenance": {
1114
+ "source": "ONNX Transpose-13 specification",
1115
+ "notes": "With `perm` omitted on a rank-8 tensor, the operator reverses every axis."
1116
+ },
1117
+ "attrs": {},
1118
+ "inputs": {
1119
+ "x": {
1120
+ "dtype": "float32",
1121
+ "shape": [2, 3, 2, 2, 4, 2, 2, 4],
1122
+ "data": { "kind": "linspace", "start": 0.0, "end": 1535.0 }
1123
+ }
1124
+ },
1125
+ "outputs": { "y": { "dtype": "float32", "shape": [4, 2, 2, 4, 2, 2, 3, 2], "tolerance": 0 } }
1126
  }
1127
  ]
1128
  }
build/webgpu/transpose.wgsl.jinja CHANGED
@@ -1,39 +1,37 @@
1
  {% macro flat_tail_open() %}
2
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
3
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
4
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
5
- // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills the rest into y).
6
- let invocation = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
7
  // Tail-safe scalar x4 keeps vector-like dispatch density without requiring
8
  // the logical tensor length (or its storage binding) to be vec4 aligned.
9
- let begin = invocation * {{ source.itemsPerInvocation }}u;
10
- let end = min(begin + {{ source.itemsPerInvocation }}u, params.count);
 
11
  for (var i = begin; i < end; i = i + 1u) {
12
  {%- endmacro %}
13
  {% macro flat_tail_close() %}
14
  }
15
  {% endmacro %}
16
 
17
- {% if usesF16 %}
18
- enable f16;
19
- {% endif %}
20
  {{ env.wgsl.resourceDeclarations }}
21
 
22
- fn input_offset({% if source.rank > 0 %}out_index: u32{% endif %}) -> u32 {
23
- {% if source.rank > 0 %}
24
  var rem = out_index;
25
  {% endif %}
26
  var offset = 0u;
27
- {% for out_axis in range(source.rank) %}
28
  {% set out_stride = namespace(value=1) %}
29
- {% for j in range(out_axis + 1, source.rank) %}
30
- {% set out_stride.value = out_stride.value * source.outputShape[j] %}
31
  {% endfor %}
32
  {% set out_stride_divisor = out_stride.value if out_stride.value != 0 else 1 %}
33
- {% set in_axis = source.perm[out_axis] %}
34
  {% set in_stride = namespace(value=1) %}
35
- {% for j in range(in_axis + 1, source.rank) %}
36
- {% set in_stride.value = in_stride.value * source.inputShape[j] %}
37
  {% endfor %}
38
  let coord{{ out_axis }} = rem / {{ out_stride_divisor }}u;
39
  rem = rem % {{ out_stride_divisor }}u;
@@ -43,6 +41,6 @@ fn input_offset({% if source.rank > 0 %}out_index: u32{% endif %}) -> u32 {
43
  }
44
 
45
  {{ flat_tail_open() }}
46
- y[i] = x[input_offset({% if source.rank > 0 %}i{% endif %})];
47
  {{ flat_tail_close() -}}
48
  }
 
1
  {% macro flat_tail_open() %}
2
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
3
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
4
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
5
+ // dispatch's per-axis workgroup fold width (the dispatch caps x and spills the rest into y).
6
+ let invocation = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
7
  // Tail-safe scalar x4 keeps vector-like dispatch density without requiring
8
  // the logical tensor length (or its storage binding) to be vec4 aligned.
9
+ {% set itemsPerInvocation = itemsPerInvocation if itemsPerInvocation is defined else 4 %}
10
+ let begin = invocation * {{ itemsPerInvocation }}u;
11
+ let end = min(begin + {{ itemsPerInvocation }}u, params.count);
12
  for (var i = begin; i < end; i = i + 1u) {
13
  {%- endmacro %}
14
  {% macro flat_tail_close() %}
15
  }
16
  {% endmacro %}
17
 
 
 
 
18
  {{ env.wgsl.resourceDeclarations }}
19
 
20
+ fn input_offset({% if rank > 0 %}out_index: u32{% endif %}) -> u32 {
21
+ {% if rank > 0 %}
22
  var rem = out_index;
23
  {% endif %}
24
  var offset = 0u;
25
+ {% for out_axis in range(rank) %}
26
  {% set out_stride = namespace(value=1) %}
27
+ {% for j in range(out_axis + 1, rank) %}
28
+ {% set out_stride.value = out_stride.value * outputShape[j] %}
29
  {% endfor %}
30
  {% set out_stride_divisor = out_stride.value if out_stride.value != 0 else 1 %}
31
+ {% set in_axis = perm[out_axis] %}
32
  {% set in_stride = namespace(value=1) %}
33
+ {% for j in range(in_axis + 1, rank) %}
34
+ {% set in_stride.value = in_stride.value * inputShape[j] %}
35
  {% endfor %}
36
  let coord{{ out_axis }} = rem / {{ out_stride_divisor }}u;
37
  rem = rem % {{ out_stride_divisor }}u;
 
41
  }
42
 
43
  {{ flat_tail_open() }}
44
+ y[i] = x[input_offset({% if rank > 0 %}i{% endif %})];
45
  {{ flat_tail_close() -}}
46
  }