Xenova HF Staff commited on
Commit
410baf2
·
verified ·
1 Parent(s): 77b755a

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,96 @@
1
  ---
 
2
  license: apache-2.0
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ library_name: kernels
3
  license: apache-2.0
4
+ tags:
5
+ - kernel
6
+ - webgpu
7
+ - wgsl
8
  ---
9
+ # ai.onnx.ConvTranspose
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 11
12
+
13
+ ## Description
14
+
15
+ Computes the transpose of a convolution, also known as a fractionally strided convolution or deconvolution, from input tensor `X`, filter weights `W`, and an optional bias `B`. Output spatial dimensions follow the stride, dilation, padding, and optional `output_padding` attributes. Supports grouped convolution through `group`.
16
+
17
+ See the [ONNX `ConvTranspose` spec](https://onnx.ai/onnx/operators/onnx__ConvTranspose.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `X` | `x` | `T` | — | — | Input data tensor of shape `(N x C x D1 x ... x Dn)`, where `N` is batch size and `C` is the number of input channels. | required |
24
+ | `W` | `w` | `T` | — | — | Filter weight tensor of shape `(C x M/group x k1 x ... x kn)`, where `M` is the number of output feature maps. | required |
25
+ | `B` | `bias` | `T` | `1` | — | Optional 1-D bias of length M added to each output channel. | optional |
26
+
27
+ ## Outputs
28
+
29
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
30
+ | --- | --- | --- | --- | --- | --- | --- |
31
+ | `Y` | `y` | `T` | same as `X` | derived; see description | Output tensor whose spatial dimensions are computed from the input size, kernel shape, strides, dilations, and padding. | required |
32
+
33
+ ## Attributes
34
+
35
+ Attributes and default values (overridable per request):
36
+
37
+ | Attribute | Default | Description |
38
+ | --- | --- | --- |
39
+ | `auto_pad` | `"NOTSET"` | Padding mode: `NOTSET` uses explicit pads; `SAME_UPPER` and `SAME_LOWER` make output spatial size equal input size times stride, with any odd extra padding added at the end or beginning respectively; `VALID` applies no padding. |
40
+ | `group` | `1` | Number of groups that input and output channels are divided into for grouped (depthwise) convolution. |
41
+ | `dilations` | — | Dilation factors for each spatial axis; defaults to one on every axis. |
42
+ | `kernel_shape` | — | Kernel dimensions for each spatial axis. When omitted, they are inferred from the spatial dimensions of `W`. |
43
+ | `output_padding` | — | Additional size on the high-index end of each output spatial axis; each value must be smaller than the corresponding stride or dilation. |
44
+ | `output_shape` | — | Requested output spatial dimensions. When present, it must match the declared spatial shape of `Y`. |
45
+ | `pads` | — | Padding at the beginning of every spatial axis followed by padding at the end of every spatial axis; defaults to zeros. |
46
+ | `strides` | — | Stride factors for each spatial axis; defaults to one on every axis. |
47
+
48
+ ## Type constraints
49
+
50
+ | Variable | Allowed dtypes |
51
+ | --- | --- |
52
+ | `T` | `float32`, `float16` |
53
+
54
+ ## Device requirements
55
+
56
+ Some implementation variants require `subgroup-matrix`, `shader-f16`, and `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
57
+
58
+ ## Files
59
+
60
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
61
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
62
+ - [`test.json`](build/webgpu/test.json) — correctness cases
63
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
64
+ - [`conv-1x1-gemm-tiled-reg.wgsl.jinja`](build/webgpu/conv-1x1-gemm-tiled-reg.wgsl.jinja)
65
+ - [`conv-1x1-gemm-tiled.wgsl.jinja`](build/webgpu/conv-1x1-gemm-tiled.wgsl.jinja)
66
+ - [`conv-1x1-subgroup-matrix.wgsl.jinja`](build/webgpu/conv-1x1-subgroup-matrix.wgsl.jinja)
67
+ - [`conv-transpose-empty-input.wgsl.jinja`](build/webgpu/conv-transpose-empty-input.wgsl.jinja)
68
+ - [`conv-transpose1d-ncl.wgsl.jinja`](build/webgpu/conv-transpose1d-ncl.wgsl.jinja)
69
+ - [`conv-transpose1d-phase-tiled.wgsl.jinja`](build/webgpu/conv-transpose1d-phase-tiled.wgsl.jinja)
70
+ - [`conv-transpose2d-col2im.wgsl.jinja`](build/webgpu/conv-transpose2d-col2im.wgsl.jinja)
71
+ - [`conv-transpose2d-grouped-stride-phase.wgsl.jinja`](build/webgpu/conv-transpose2d-grouped-stride-phase.wgsl.jinja)
72
+ - [`conv-transpose2d-nchw.wgsl.jinja`](build/webgpu/conv-transpose2d-nchw.wgsl.jinja)
73
+ - [`conv-transpose2d-phase-gemm-tiled.wgsl.jinja`](build/webgpu/conv-transpose2d-phase-gemm-tiled.wgsl.jinja)
74
+ - [`conv-transpose2d-weight-reorder.wgsl.jinja`](build/webgpu/conv-transpose2d-weight-reorder.wgsl.jinja)
75
+ - [`conv-transpose3d-col2im.wgsl.jinja`](build/webgpu/conv-transpose3d-col2im.wgsl.jinja)
76
+ - [`conv-transpose3d-ncdhw.wgsl.jinja`](build/webgpu/conv-transpose3d-ncdhw.wgsl.jinja)
77
+ - [`conv-transpose3d-weight-reorder.wgsl.jinja`](build/webgpu/conv-transpose3d-weight-reorder.wgsl.jinja)
78
+
79
+ ## Use with `@huggingface/kernels`
80
+
81
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
82
+ It then allocates the result tensors automatically.
83
+
84
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
85
+
86
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
87
+
88
+ ```js
89
+ import { getKernel } from "@huggingface/kernels";
90
+
91
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.ConvTranspose", { version: 1 });
92
+ const { y } = await kernel({
93
+ x: { data: xData, shape: [1, 1, 3] },
94
+ w: { data: wData, shape: [1, 2, 2] },
95
+ });
96
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,452 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.ConvTranspose",
3
+ "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
4
+ "cases": [
5
+ {
6
+ "name": "conv-transpose-f32-b1c16m16-16x16-k3s2",
7
+ "preset": "smoke",
8
+ "vars": {
9
+ "batch": 1,
10
+ "inChannels": 16,
11
+ "outChannels": 16,
12
+ "inH": 16,
13
+ "inW": 16,
14
+ "kernelH": 3,
15
+ "kernelW": 3,
16
+ "strideH": 2,
17
+ "strideW": 2,
18
+ "padH": 1,
19
+ "padW": 1
20
+ },
21
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
22
+ "inputs": {
23
+ "x": { "shape": [1, 16, 16, 16], "dtype": "float32", "dist": "normal", "seed": 114, "scale": 0.2 },
24
+ "w": { "shape": [16, 16, 3, 3], "dtype": "float32", "dist": "normal", "seed": 115, "scale": 0.1 }
25
+ },
26
+ "outputs": { "y": { "shape": [1, 16, 31, 31], "dtype": "float32" } },
27
+ "bench": {
28
+ "primary": true,
29
+ "metrics": [
30
+ {
31
+ "type": "gflops",
32
+ "value": "2 * args.batch * args.outChannels * args.inH * args.inW * args.inChannels * args.kernelH * args.kernelW"
33
+ }
34
+ ]
35
+ }
36
+ },
37
+ {
38
+ "name": "conv-transpose-f32-non-vector-bias-b1c3m5-32x32-k3s2",
39
+ "preset": "edge",
40
+ "vars": {
41
+ "batch": 1,
42
+ "inChannels": 3,
43
+ "outChannels": 5,
44
+ "inH": 32,
45
+ "inW": 32,
46
+ "kernelH": 3,
47
+ "kernelW": 3,
48
+ "strideH": 2,
49
+ "strideW": 2,
50
+ "padH": 1,
51
+ "padW": 1
52
+ },
53
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
54
+ "inputs": {
55
+ "x": { "shape": [1, 3, 32, 32], "dtype": "float32", "dist": "normal", "seed": 116, "scale": 0.2 },
56
+ "w": { "shape": [3, 5, 3, 3], "dtype": "float32", "dist": "normal", "seed": 117, "scale": 0.1 },
57
+ "bias": { "shape": [5], "dtype": "float32", "dist": "normal", "seed": 118, "scale": 0.1 }
58
+ },
59
+ "outputs": { "y": { "shape": [1, 5, 63, 63], "dtype": "float32" } },
60
+ "bench": {
61
+ "metrics": [
62
+ {
63
+ "type": "gflops",
64
+ "value": "2 * args.batch * args.outChannels * args.inH * args.inW * args.inChannels * args.kernelH * args.kernelW"
65
+ }
66
+ ]
67
+ }
68
+ },
69
+ {
70
+ "name": "conv-transpose-f16-b1c64m64-32x32-k3s2",
71
+ "preset": "smoke",
72
+ "vars": {
73
+ "batch": 1,
74
+ "inChannels": 64,
75
+ "outChannels": 64,
76
+ "inH": 32,
77
+ "inW": 32,
78
+ "kernelH": 3,
79
+ "kernelW": 3,
80
+ "strideH": 2,
81
+ "strideW": 2,
82
+ "padH": 1,
83
+ "padW": 1
84
+ },
85
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
86
+ "inputs": {
87
+ "x": { "shape": [1, 64, 32, 32], "dtype": "float16", "dist": "normal", "seed": 211, "scale": 0.2 },
88
+ "w": { "shape": [64, 64, 3, 3], "dtype": "float16", "dist": "normal", "seed": 212, "scale": 0.1 }
89
+ },
90
+ "outputs": { "y": { "shape": [1, 64, 63, 63], "dtype": "float16" } },
91
+ "bench": {
92
+ "metrics": [
93
+ {
94
+ "type": "gflops",
95
+ "value": "2 * args.batch * args.outChannels * args.inH * args.inW * args.inChannels * args.kernelH * args.kernelW"
96
+ }
97
+ ]
98
+ }
99
+ },
100
+ {
101
+ "name": "conv-transpose-f32-b1c64m64-32x32-k3s2",
102
+ "preset": "smoke",
103
+ "vars": {
104
+ "batch": 1,
105
+ "inChannels": 64,
106
+ "outChannels": 64,
107
+ "inH": 32,
108
+ "inW": 32,
109
+ "kernelH": 3,
110
+ "kernelW": 3,
111
+ "strideH": 2,
112
+ "strideW": 2,
113
+ "padH": 1,
114
+ "padW": 1
115
+ },
116
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
117
+ "inputs": {
118
+ "x": { "shape": [1, 64, 32, 32], "dtype": "float32", "dist": "normal", "seed": 211, "scale": 0.2 },
119
+ "w": { "shape": [64, 64, 3, 3], "dtype": "float32", "dist": "normal", "seed": 212, "scale": 0.1 }
120
+ },
121
+ "outputs": { "y": { "shape": [1, 64, 63, 63], "dtype": "float32" } },
122
+ "bench": {
123
+ "metrics": [
124
+ {
125
+ "type": "gflops",
126
+ "value": "2 * args.batch * args.outChannels * args.inH * args.inW * args.inChannels * args.kernelH * args.kernelW"
127
+ }
128
+ ]
129
+ }
130
+ },
131
+ {
132
+ "name": "conv-transpose-f16-large-upsampler-b1c128m128-64x64-k4s2",
133
+ "preset": "smoke",
134
+ "vars": {
135
+ "batch": 1,
136
+ "inChannels": 128,
137
+ "outChannels": 128,
138
+ "inH": 64,
139
+ "inW": 64,
140
+ "kernelH": 4,
141
+ "kernelW": 4,
142
+ "strideH": 2,
143
+ "strideW": 2,
144
+ "padH": 1,
145
+ "padW": 1
146
+ },
147
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
148
+ "inputs": {
149
+ "x": { "shape": [1, 128, 64, 64], "dtype": "float16", "dist": "normal", "seed": 213, "scale": 0.2 },
150
+ "w": { "shape": [128, 128, 4, 4], "dtype": "float16", "dist": "normal", "seed": 214, "scale": 0.05 }
151
+ },
152
+ "outputs": { "y": { "shape": [1, 128, 128, 128], "dtype": "float16" } },
153
+ "bench": {
154
+ "metrics": [
155
+ {
156
+ "type": "gflops",
157
+ "value": "2 * args.batch * args.outChannels * args.inH * args.inW * args.inChannels * args.kernelH * args.kernelW"
158
+ }
159
+ ]
160
+ }
161
+ },
162
+ {
163
+ "name": "conv-transpose-f32-depthwise-g64-32x32-k4s2",
164
+ "preset": "smoke",
165
+ "vars": {
166
+ "batch": 1,
167
+ "inChannels": 64,
168
+ "outChannels": 64,
169
+ "inChannelsPerGroup": 1,
170
+ "inH": 32,
171
+ "inW": 32,
172
+ "kernelH": 4,
173
+ "kernelW": 4,
174
+ "strideH": 2,
175
+ "strideW": 2,
176
+ "padH": 1,
177
+ "padW": 1,
178
+ "group": 64
179
+ },
180
+ "attrs": { "group": 64, "strides": [2, 2], "pads": [1, 1, 1, 1] },
181
+ "inputs": {
182
+ "x": { "shape": [1, 64, 32, 32], "dtype": "float32", "dist": "normal", "seed": 215, "scale": 0.2 },
183
+ "w": { "shape": [64, 1, 4, 4], "dtype": "float32", "dist": "normal", "seed": 216, "scale": 0.1 }
184
+ },
185
+ "outputs": { "y": { "shape": [1, 64, 64, 64], "dtype": "float32" } },
186
+ "bench": {
187
+ "metrics": [
188
+ {
189
+ "type": "gflops",
190
+ "value": "2 * args.batch * args.outChannels * args.inH * args.inW * args.inChannelsPerGroup * args.kernelH * args.kernelW"
191
+ }
192
+ ]
193
+ }
194
+ },
195
+ {
196
+ "name": "conv-transpose-f32-depthwise-pointwise-g64-512x512-dispatch-fold",
197
+ "preset": "edge",
198
+ "provenance": {
199
+ "notes": "Realistic depthwise pointwise projection at 16,777,216 outputs. Exercises the two-dimensional dispatch fold while exposing scalar coordinate/division overhead in the generic grouped kernel."
200
+ },
201
+ "vars": { "count": 16777216 },
202
+ "attrs": { "group": 64 },
203
+ "inputs": {
204
+ "x": { "shape": [1, 64, 512, 512], "dtype": "float32", "dist": "normal", "seed": 221, "scale": 0.2 },
205
+ "w": { "shape": [64, 1, 1, 1], "dtype": "float32", "dist": "normal", "seed": 222, "scale": 0.1 }
206
+ },
207
+ "outputs": { "y": { "shape": [1, 64, 512, 512], "dtype": "float32" } },
208
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "(2 * args.count + 64) * 4" }] }
209
+ },
210
+ {
211
+ "name": "conv-transpose-f32-1d-b1c128m128-256-k4s2",
212
+ "preset": "smoke",
213
+ "vars": { "batch": 1, "inChannels": 128, "outChannels": 128, "inW": 256, "kernelW": 4, "strideW": 2, "padW": 1 },
214
+ "attrs": { "strides": [2], "pads": [1, 1] },
215
+ "inputs": {
216
+ "x": { "shape": [1, 128, 256], "dtype": "float32", "dist": "normal", "seed": 217, "scale": 0.2 },
217
+ "w": { "shape": [128, 128, 4], "dtype": "float32", "dist": "normal", "seed": 218, "scale": 0.1 }
218
+ },
219
+ "outputs": { "y": { "shape": [1, 128, 512], "dtype": "float32" } },
220
+ "bench": {
221
+ "metrics": [
222
+ { "type": "gflops", "value": "2 * args.batch * args.outChannels * args.inW * args.inChannels * args.kernelW" }
223
+ ]
224
+ }
225
+ },
226
+ {
227
+ "name": "neural-audio-1d-f32-b1c256m256-w1024-k4s2-pathology",
228
+ "preset": "stress",
229
+ "provenance": {
230
+ "source": "authored for performance coverage",
231
+ "notes": "Realistic neural-audio upsampling stage for the direct NCW fallback. Every output sample serially scans 256 input channels and four kernel taps."
232
+ },
233
+ "vars": { "batch": 1, "inChannels": 256, "outChannels": 256, "inW": 1024, "kernelW": 4, "strideW": 2, "padW": 1 },
234
+ "attrs": { "strides": [2], "pads": [1, 1] },
235
+ "inputs": {
236
+ "x": { "shape": [1, 256, 1024], "dtype": "float32", "dist": "normal", "seed": 219, "scale": 0.2 },
237
+ "w": { "shape": [256, 256, 4], "dtype": "float32", "dist": "normal", "seed": 220, "scale": 0.05 }
238
+ },
239
+ "outputs": { "y": { "shape": [1, 256, 2048], "dtype": "float32" } },
240
+ "bench": {
241
+ "metrics": [
242
+ { "type": "gflops", "value": "2 * args.batch * args.outChannels * args.inW * args.inChannels * args.kernelW" }
243
+ ]
244
+ }
245
+ },
246
+ {
247
+ "name": "conv-transpose-f32-3d-naive-b1c16m16-16x16x16-k3",
248
+ "preset": "stress",
249
+ "vars": {
250
+ "batch": 1,
251
+ "inChannels": 16,
252
+ "outChannels": 16,
253
+ "inD": 16,
254
+ "inH": 16,
255
+ "inW": 16,
256
+ "kernelD": 3,
257
+ "kernelH": 3,
258
+ "kernelW": 3
259
+ },
260
+ "attrs": {},
261
+ "inputs": {
262
+ "x": { "shape": [1, 16, 16, 16, 16], "dtype": "float32", "dist": "normal", "seed": 331, "scale": 0.2 },
263
+ "w": { "shape": [16, 16, 3, 3, 3], "dtype": "float32", "dist": "normal", "seed": 332, "scale": 0.05 }
264
+ },
265
+ "outputs": { "y": { "shape": [1, 16, 18, 18, 18], "dtype": "float32" } },
266
+ "bench": {
267
+ "metrics": [
268
+ {
269
+ "type": "gflops",
270
+ "value": "2 * args.batch * args.outChannels * args.inD * args.inH * args.inW * args.inChannels * args.kernelD * args.kernelH * args.kernelW"
271
+ }
272
+ ]
273
+ }
274
+ },
275
+ {
276
+ "name": "medical-decoder-3d-f32-b1c32m32-32x32x32-k3-pathology",
277
+ "preset": "stress",
278
+ "provenance": {
279
+ "source": "authored for performance coverage",
280
+ "notes": "Realistic 3D decoder stage for the direct NCDHW fallback. Each output voxel serially scans all 32 input channels and the 3x3x3 kernel."
281
+ },
282
+ "vars": {
283
+ "batch": 1,
284
+ "inChannels": 32,
285
+ "outChannels": 32,
286
+ "inD": 32,
287
+ "inH": 32,
288
+ "inW": 32,
289
+ "kernelD": 3,
290
+ "kernelH": 3,
291
+ "kernelW": 3
292
+ },
293
+ "attrs": { "pads": [1, 1, 1, 1, 1, 1] },
294
+ "inputs": {
295
+ "x": { "shape": [1, 32, 32, 32, 32], "dtype": "float32", "dist": "normal", "seed": 333, "scale": 0.2 },
296
+ "w": { "shape": [32, 32, 3, 3, 3], "dtype": "float32", "dist": "normal", "seed": 334, "scale": 0.05 }
297
+ },
298
+ "outputs": { "y": { "shape": [1, 32, 32, 32, 32], "dtype": "float32" } },
299
+ "bench": {
300
+ "metrics": [
301
+ {
302
+ "type": "gflops",
303
+ "value": "2 * args.batch * args.outChannels * args.inD * args.inH * args.inW * args.inChannels * args.kernelD * args.kernelH * args.kernelW"
304
+ }
305
+ ]
306
+ }
307
+ },
308
+ {
309
+ "name": "conv-transpose-f32-grouped-naive-g4-c32m64-48x48-k4s2",
310
+ "preset": "stress",
311
+ "vars": {
312
+ "batch": 1,
313
+ "inChannels": 32,
314
+ "outChannels": 64,
315
+ "inChannelsPerGroup": 8,
316
+ "inH": 48,
317
+ "inW": 48,
318
+ "kernelH": 4,
319
+ "kernelW": 4,
320
+ "strideH": 2,
321
+ "strideW": 2,
322
+ "padH": 1,
323
+ "padW": 1,
324
+ "group": 4
325
+ },
326
+ "attrs": { "group": 4, "strides": [2, 2], "pads": [1, 1, 1, 1] },
327
+ "inputs": {
328
+ "x": { "shape": [1, 32, 48, 48], "dtype": "float32", "dist": "normal", "seed": 341, "scale": 0.2 },
329
+ "w": { "shape": [32, 16, 4, 4], "dtype": "float32", "dist": "normal", "seed": 342, "scale": 0.05 }
330
+ },
331
+ "outputs": { "y": { "shape": [1, 64, 96, 96], "dtype": "float32" } },
332
+ "bench": {
333
+ "metrics": [
334
+ {
335
+ "type": "gflops",
336
+ "value": "2 * args.batch * args.outChannels * args.inH * args.inW * args.inChannelsPerGroup * args.kernelH * args.kernelW"
337
+ }
338
+ ]
339
+ }
340
+ },
341
+ {
342
+ "name": "grouped-bias-g4-c32m64-48x48-k4s2-generic-pathology",
343
+ "preset": "stress",
344
+ "provenance": {
345
+ "source": "authored for variant coverage",
346
+ "notes": "Adding channel bias excludes nchw2d_grouped_stride_phase and selects nchw2d_grouped_bias; the paired no-bias case isolates this optional-input route boundary."
347
+ },
348
+ "vars": {
349
+ "batch": 1,
350
+ "inChannels": 32,
351
+ "outChannels": 64,
352
+ "inChannelsPerGroup": 8,
353
+ "inH": 48,
354
+ "inW": 48,
355
+ "kernelH": 4,
356
+ "kernelW": 4,
357
+ "strideH": 2,
358
+ "strideW": 2,
359
+ "padH": 1,
360
+ "padW": 1,
361
+ "group": 4
362
+ },
363
+ "attrs": { "group": 4, "strides": [2, 2], "pads": [1, 1, 1, 1] },
364
+ "inputs": {
365
+ "x": { "shape": [1, 32, 48, 48], "dtype": "float32", "dist": "normal", "seed": 5104, "scale": 0.2 },
366
+ "w": { "shape": [32, 16, 4, 4], "dtype": "float32", "dist": "normal", "seed": 5105, "scale": 0.05 },
367
+ "bias": { "shape": [64], "dtype": "float32", "dist": "normal", "seed": 5106, "scale": 0.05 }
368
+ },
369
+ "outputs": { "y": { "shape": [1, 64, 96, 96], "dtype": "float32" } },
370
+ "bench": {
371
+ "metrics": [
372
+ {
373
+ "type": "gflops",
374
+ "value": "2 * args.batch * args.outChannels * args.inH * args.inW * args.inChannelsPerGroup * args.kernelH * args.kernelW"
375
+ }
376
+ ]
377
+ }
378
+ },
379
+ {
380
+ "name": "conv-transpose-f32-dpt-reassemble-48ch-37x49-k4s4",
381
+ "preset": "all",
382
+ "provenance": {
383
+ "source": "Depth Anything V2 Small neck reassemble",
384
+ "notes": "Depth Anything V2 Small's factor-4 reassemble at 518x686. ConvTranspose contracts the projected neck width (48), not the backbone width."
385
+ },
386
+ "vars": {
387
+ "batch": 1,
388
+ "inChannels": 48,
389
+ "outChannels": 48,
390
+ "inH": 37,
391
+ "inW": 49,
392
+ "kernelH": 4,
393
+ "kernelW": 4,
394
+ "strideH": 4,
395
+ "strideW": 4,
396
+ "padH": 0,
397
+ "padW": 0
398
+ },
399
+ "attrs": { "strides": [4, 4] },
400
+ "inputs": {
401
+ "x": { "shape": [1, 48, 37, 49], "dtype": "float32", "dist": "normal", "seed": 9421, "scale": 0.2 },
402
+ "w": { "shape": [48, 48, 4, 4], "dtype": "float32", "dist": "normal", "seed": 9422, "scale": 0.1 },
403
+ "bias": { "shape": [48], "dtype": "float32", "dist": "normal", "seed": 9423, "scale": 0.1 }
404
+ },
405
+ "outputs": { "y": { "shape": [1, 48, 148, 196], "dtype": "float32" } },
406
+ "bench": {
407
+ "metrics": [
408
+ {
409
+ "type": "gflops",
410
+ "value": "2 * args.batch * args.outChannels * args.inH * args.inW * args.inChannels * args.kernelH * args.kernelW"
411
+ }
412
+ ]
413
+ }
414
+ },
415
+ {
416
+ "name": "conv-transpose-f32-dpt-reassemble-96ch-37x49-k2s2",
417
+ "preset": "all",
418
+ "provenance": {
419
+ "source": "Depth Anything V2 Small neck reassemble",
420
+ "notes": "The factor-2 reassemble of the same model, likewise contracting over the neck width (96). Four taps per output element, one of them real."
421
+ },
422
+ "vars": {
423
+ "batch": 1,
424
+ "inChannels": 96,
425
+ "outChannels": 96,
426
+ "inH": 37,
427
+ "inW": 49,
428
+ "kernelH": 2,
429
+ "kernelW": 2,
430
+ "strideH": 2,
431
+ "strideW": 2,
432
+ "padH": 0,
433
+ "padW": 0
434
+ },
435
+ "attrs": { "strides": [2, 2] },
436
+ "inputs": {
437
+ "x": { "shape": [1, 96, 37, 49], "dtype": "float32", "dist": "normal", "seed": 9431, "scale": 0.2 },
438
+ "w": { "shape": [96, 96, 2, 2], "dtype": "float32", "dist": "normal", "seed": 9432, "scale": 0.1 },
439
+ "bias": { "shape": [96], "dtype": "float32", "dist": "normal", "seed": 9433, "scale": 0.1 }
440
+ },
441
+ "outputs": { "y": { "shape": [1, 96, 74, 98], "dtype": "float32" } },
442
+ "bench": {
443
+ "metrics": [
444
+ {
445
+ "type": "gflops",
446
+ "value": "2 * args.batch * args.outChannels * args.inH * args.inW * args.inChannels * args.kernelH * args.kernelW"
447
+ }
448
+ ]
449
+ }
450
+ }
451
+ ]
452
+ }
build/webgpu/conv-1x1-gemm-tiled-reg.wgsl.jinja ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // 1x1 / im2col Conv routed as a batched GEMM:
2
+ // Y[b] (M x N) = W (M x K) * X[b] (K x N), with M = outChannels,
3
+ // K = inChannels * kh * kw after im2col, and N = H * W. Register-blocked
4
+ // workgroup lanes compute TM x TN micro-tiles. The defaults use 16x16 lanes with
5
+ // an 8x4 micro-tile over a 64x64 output tile. Weight A is shared across the batch;
6
+ // input B and the output use the dispatch.z batch offset. Accumulation is f32;
7
+ // the optional epilogue applies bias, residual Z, and activation in the
8
+ // accumulator domain on store. Every dimension is bounds-checked.
9
+ // f16 operands remain packed in workgroup memory and widen only at the FMA site;
10
+ // the f32 accumulation order is retained while shared traffic is halved.
11
+ //
12
+ // Both shared tiles are stored as vec4 groups along the axis each lane consumes
13
+ // contiguously: tileA groups four K values for one output row, tileB groups four
14
+ // N columns for one K. One micro-tile step therefore reads TM + 4 vector words
15
+ // instead of 4 * (TM + TN) scalars, so a shared word feeds four times as many
16
+ // FMAs and the K loop runs four accumulation steps per iteration.
17
+ {% if usesF16 %}
18
+ enable f16;
19
+ {% endif %}
20
+ {{ env.wgsl.resourceDeclarations }}
21
+ {% set hasActivation = hasActivation is defined and hasActivation %}
22
+ {% if hasActivation %}
23
+ // Apply the fused activation in the f32 accumulator before the single output
24
+ // cast, avoiding an intermediate convolution tensor.
25
+ fn fused_act(v: f32) -> f32 {
26
+ return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
27
+ }
28
+ {% endif %}
29
+
30
+ {% set tileT = "f16" if usesF16 else "f32" -%}
31
+ {%- set GEMM_BK = gemmKTile if gemmKTile is defined else 16 %}
32
+ {% set GEMM_TM = gemmThreadRows if gemmThreadRows is defined else 8 %}
33
+ {% set GEMM_WG_X = gemmWorkgroupX if gemmWorkgroupX is defined else 16 %}
34
+ {% set GEMM_BM = gemmMTile if gemmMTile is defined else 64 %}
35
+ {% set GEMM_BN = gemmNTile if gemmNTile is defined else GEMM_WG_X * 4 %}
36
+ {% set GEMM_TN = gemmThreadColumns if gemmThreadColumns is defined else (GEMM_BN / GEMM_WG_X)|int %}
37
+ {% set GEMM_WG_Y = gemmWorkgroupY if gemmWorkgroupY is defined else (GEMM_BM / GEMM_TM)|int %}
38
+ {% set components = ["x", "y", "z", "w"] %}
39
+ const BK: u32 = {{ GEMM_BK }}u;
40
+ const BM: u32 = {{ GEMM_BM }}u;
41
+ const BN: u32 = {{ GEMM_BN }}u;
42
+ const TM: u32 = {{ GEMM_TM }}u;
43
+ const TN: u32 = {{ GEMM_TN }}u;
44
+ const WG_X: u32 = {{ GEMM_WG_X }}u;
45
+ const WG_SIZE: u32 = {{ GEMM_WG_X * GEMM_WG_Y }}u;
46
+ // Vector words per tile row: A groups K, B groups N.
47
+ const AK_VECS: u32 = BK / 4u;
48
+ const BN_VECS: u32 = BN / 4u;
49
+
50
+ // implicitIm2col leaves B unmaterialized. While staging a tile, the kernel
51
+ // decodes each (k, n) column-matrix coordinate into a raw NCHW input address.
52
+ // Overlapping windows may reread input values, trading address arithmetic and
53
+ // cache traffic for the storage and bandwidth of the expanded column matrix.
54
+ {% set implicitIm2col = implicitIm2col is defined and implicitIm2col %}
55
+ {% set fusedNarrowProjection = fusedNarrowProjection is defined and fusedNarrowProjection %}
56
+ {% if fusedNarrowProjection %}
57
+ {% set projectionChannels = projectionOutChannels %}
58
+ {% set projectionInputAct = inputActivation %}
59
+ {% set projectionOutputAct = outputActivation %}
60
+ {% set projectionScaled = hasOutputScale %}
61
+ {% set projectionBiased = hasProjectionBias %}
62
+ {% endif %}
63
+ {% set narrowProjectionTile = "tileB" if GEMM_BK >= GEMM_BM else "projectionTile" %}
64
+ {% set splitKValue = splitK if splitK is defined else 1 %}
65
+ {% set splitKPartial = splitKValue > 1 %}
66
+ {% set gemmKLimit = "kEnd" if splitKPartial else "K" %}
67
+ {% set implicitGatherMode = tunables.IMPLICIT_TILED_GATHER_MODE if tunables.IMPLICIT_TILED_GATHER_MODE is defined else 0 %}
68
+ {% set implicitGatherWgSize = GEMM_WG_X * GEMM_WG_Y %}
69
+ {% set implicitGatherColumnVecs = (GEMM_BN / 4)|int %}
70
+ {% set implicitGatherKChunks = (implicitGatherWgSize / implicitGatherColumnVecs)|int %}
71
+ {% set implicitGatherDivides = implicitGatherWgSize % implicitGatherColumnVecs == 0 and implicitGatherKChunks > 0 and GEMM_BK % implicitGatherKChunks == 0 %}
72
+ {% set useCarriedImplicitGather = implicitIm2col and implicitGatherMode == 1 and implicitGatherDivides %}
73
+ {% if useCarriedImplicitGather %}
74
+ {% set implicitGatherLoadWidth = (GEMM_BK / implicitGatherKChunks)|int %}
75
+ {% endif %}
76
+ {% if implicitIm2col %}
77
+ const CONV_KERNEL_H: u32 = {{ convKernelH }}u;
78
+ const CONV_KERNEL_W: u32 = {{ convKernelW }}u;
79
+ const CONV_KSIZE: u32 = CONV_KERNEL_H * CONV_KERNEL_W;
80
+ const CONV_STRIDE_H: u32 = {{ convStrideH }}u;
81
+ const CONV_STRIDE_W: u32 = {{ convStrideW }}u;
82
+ const CONV_DILATION_H: u32 = {{ convDilationH }}u;
83
+ const CONV_DILATION_W: u32 = {{ convDilationW }}u;
84
+ const CONV_PAD_TOP: i32 = {{ convPadTop }};
85
+ const CONV_PAD_LEFT: i32 = {{ convPadLeft }};
86
+ const CONV_IN_H: u32 = {{ convInH }}u;
87
+ const CONV_IN_W: u32 = {{ convInW }}u;
88
+ const CONV_OUT_W: u32 = {{ convOutW }}u;
89
+ const CONV_IN_CHANNELS: u32 = {{ convInChannels }}u;
90
+ {% endif %}
91
+
92
+ {% if fusedNarrowProjection %}
93
+
94
+ const PROJECTION_OUT_C: u32 = {{ projectionChannels }}u;
95
+ {% else %}{% set emitConvStoreOut = not splitKPartial %}{% if emitConvStoreOut | default(true) %}fn store_out(m: u32, n: u32, yBase: u32, raw: f32) {
96
+ if (m >= params.M || n >= params.N) {
97
+ return;
98
+ }
99
+ var v = raw;
100
+ {% if hasBias %}
101
+ v = v + f32(bias[m]);
102
+ {% endif %}
103
+ {% if hasActivation %}
104
+ v = fused_act(v);
105
+ {% endif %}
106
+ y[yBase + m * params.N + n] = {{ T }}(v);
107
+ }
108
+ {% endif %}
109
+ {% endif %}
110
+
111
+ var<workgroup> tileA: array<array<vec4<{{ tileT }}>, AK_VECS>, BM>;
112
+ var<workgroup> tileB: array<array<vec4<{{ tileT }}>, BN_VECS>, BK>;
113
+ {% if fusedNarrowProjection and GEMM_BK < GEMM_BM %}
114
+ // BK16 cannot reuse the 16-row input tile to publish a BM32 intermediate.
115
+ // The extra 32x64 f32 tile keeps the total at 14 KiB, below WebGPU's
116
+ // guaranteed 16 KiB workgroup-storage floor.
117
+ var<workgroup> projectionTile: array<array<vec4<f32>, BN_VECS>, BM>;
118
+ {% endif %}
119
+ {% macro publish_projection_input(localRow, localVec, component, globalRow, globalColumn, raw) %}
120
+ if ({{ globalRow }} < M && {{ globalColumn }} < N) {
121
+ var projectionInput = {{ raw }};
122
+ {% if hasBias %}
123
+ projectionInput = projectionInput + f32(bias[{{ globalRow }}]);
124
+ {% endif %}
125
+ {% if projectionInputAct == "relu" %}
126
+ projectionInput = projection_relu(projectionInput);
127
+ {% endif %}
128
+ {{ narrowProjectionTile }}[{{ localRow }}][{{ localVec }}].{{ component }} = projectionInput;
129
+ } else {
130
+ {{ narrowProjectionTile }}[{{ localRow }}][{{ localVec }}].{{ component }} = 0.0;
131
+ }
132
+ {%- endmacro %}
133
+ {% macro load_a_vec4(rowBase, columnBase, rowLimit, columnLimit) %}
134
+ for (var linear = li; linear < BM * AK_VECS; linear += WG_SIZE) {
135
+ let ar = linear / AK_VECS;
136
+ let ac4 = linear % AK_VECS;
137
+ let am = {{ rowBase }} + ar;
138
+ let ak = {{ columnBase }} + ac4 * 4u;
139
+ var av = vec4<{{ tileT }}>({{ tileT }}(0.0));
140
+ if (am < {{ rowLimit }}) {
141
+ let aBase = am * K + ak;
142
+ if (ak + 3u < {{ columnLimit }}) {
143
+ av = vec4<{{ tileT }}>({{ tileT }}(w[aBase]), {{ tileT }}(w[aBase + 1u]), {{ tileT }}(w[aBase + 2u]), {{ tileT }}(w[aBase + 3u]));
144
+ } else {
145
+ for (var t = 0u; t < 4u; t = t + 1u) {
146
+ if (ak + t < {{ columnLimit }}) {
147
+ av[t] = {{ tileT }}(w[aBase + t]);
148
+ }
149
+ }
150
+ }
151
+ }
152
+ tileA[ar][ac4] = av;
153
+ }
154
+ {%- endmacro %}
155
+ {% macro load_b_vec4(rowBase, columnBase, rowLimit, columnLimit) %}
156
+ for (var linear = li; linear < BK * BN_VECS; linear += WG_SIZE) {
157
+ let br = linear / BN_VECS;
158
+ let bc4 = linear % BN_VECS;
159
+ let bk = {{ rowBase }} + br;
160
+ let bn = {{ columnBase }} + bc4 * 4u;
161
+ var bvec = vec4<{{ tileT }}>({{ tileT }}(0.0));
162
+ if (bk < {{ rowLimit }}) {
163
+ let bBase = xBatchBase + bk * N + bn;
164
+ if (bn + 3u < {{ columnLimit }}) {
165
+ bvec = vec4<{{ tileT }}>({{ tileT }}(xm[bBase]), {{ tileT }}(xm[bBase + 1u]), {{ tileT }}(xm[bBase + 2u]), {{ tileT }}(xm[bBase + 3u]));
166
+ } else {
167
+ for (var t = 0u; t < 4u; t = t + 1u) {
168
+ if (bn + t < {{ columnLimit }}) {
169
+ bvec[t] = {{ tileT }}(xm[bBase + t]);
170
+ }
171
+ }
172
+ }
173
+ }
174
+ tileB[br][bc4] = bvec;
175
+ }
176
+ {%- endmacro %}
177
+ {% macro load_b_implicit_vec4(rowBase, columnBase, rowLimit, columnLimit) %}
178
+ for (var linear = li; linear < BK * BN_VECS; linear += WG_SIZE) {
179
+ let br = linear / BN_VECS;
180
+ let bc4 = linear % BN_VECS;
181
+ let bk = {{ rowBase }} + br;
182
+ var bvec = vec4<{{ tileT }}>({{ tileT }}(0.0));
183
+ if (bk < {{ rowLimit }}) {
184
+ // k is the OIHW weight flattening (ic * KH + kh) * KW + kw; n is the output position.
185
+ let ic = bk / CONV_KSIZE;
186
+ let kq = bk % CONV_KSIZE;
187
+ let kh = kq / CONV_KERNEL_W;
188
+ let kw = kq % CONV_KERNEL_W;
189
+ let planeBase = (batch * CONV_IN_CHANNELS + ic) * CONV_IN_H;
190
+ for (var t = 0u; t < 4u; t = t + 1u) {
191
+ let bn = {{ columnBase }} + bc4 * 4u + t;
192
+ if (bn < {{ columnLimit }}) {
193
+ let oh = bn / CONV_OUT_W;
194
+ let ow = bn % CONV_OUT_W;
195
+ let ih = i32(oh * CONV_STRIDE_H + kh * CONV_DILATION_H) - CONV_PAD_TOP;
196
+ let iw = i32(ow * CONV_STRIDE_W + kw * CONV_DILATION_W) - CONV_PAD_LEFT;
197
+ if (ih >= 0 && ih < i32(CONV_IN_H) && iw >= 0 && iw < i32(CONV_IN_W)) {
198
+ bvec[t] = {{ tileT }}(xm[(planeBase + u32(ih)) * CONV_IN_W + u32(iw)]);
199
+ }
200
+ }
201
+ }
202
+ }
203
+ tileB[br][bc4] = bvec;
204
+ }
205
+ {%- endmacro %}
206
+ {% macro load_b_implicit_carried(rowBase, columnBase, rowLimit, columnLimit) %}
207
+ let bColVec = li / {{ implicitGatherKChunks }}u;
208
+ let bChunk = li % {{ implicitGatherKChunks }}u;
209
+ let bLocalK = bChunk * {{ implicitGatherLoadWidth }}u;
210
+ let bStartK = {{ rowBase }} + bLocalK;
211
+ let bCol0 = {{ columnBase }} + bColVec * 4u;
212
+ let bCols = vec4<u32>(bCol0, bCol0 + 1u, bCol0 + 2u, bCol0 + 3u);
213
+ let inColumn = bCols < vec4<u32>({{ columnLimit }});
214
+ let ih0 = vec4<i32>((bCols / vec4<u32>(CONV_OUT_W)) * CONV_STRIDE_H) - vec4<i32>(CONV_PAD_TOP);
215
+ let iw0 = vec4<i32>((bCols % vec4<u32>(CONV_OUT_W)) * CONV_STRIDE_W) - vec4<i32>(CONV_PAD_LEFT);
216
+ var carriedK = bStartK;
217
+ var carriedIc = carriedK / CONV_KSIZE;
218
+ let carriedKq = carriedK % CONV_KSIZE;
219
+ var carriedKh = carriedKq / CONV_KERNEL_W;
220
+ var carriedKw = carriedKq % CONV_KERNEL_W;
221
+ var carriedIh = ih0 + vec4<i32>(i32(carriedKh * CONV_DILATION_H));
222
+ var carriedIw = iw0 + vec4<i32>(i32(carriedKw * CONV_DILATION_W));
223
+
224
+ // Interior words can carry four addresses. Padding, the N tail, and the
225
+ // final K tile retain the coordinate-checked loop below and publish exact
226
+ // zeros.
227
+ let interior = all(inColumn)
228
+ && bStartK + {{ implicitGatherLoadWidth }}u <= {{ rowLimit }}
229
+ && all(ih0 >= vec4<i32>(0))
230
+ && all(ih0 + vec4<i32>(i32((CONV_KERNEL_H - 1u) * CONV_DILATION_H)) < vec4<i32>(i32(CONV_IN_H)))
231
+ && all(iw0 >= vec4<i32>(0))
232
+ && all(iw0 + vec4<i32>(i32((CONV_KERNEL_W - 1u) * CONV_DILATION_W)) < vec4<i32>(i32(CONV_IN_W)));
233
+ if (interior) {
234
+ let batchBase = batch * CONV_IN_CHANNELS * CONV_IN_H * CONV_IN_W;
235
+ let colStep = i32(CONV_DILATION_W);
236
+ let rowStep = i32(CONV_DILATION_H * CONV_IN_W);
237
+ let planeStep = i32(CONV_IN_H * CONV_IN_W);
238
+ let kwWrap = colStep * i32(CONV_KERNEL_W) - rowStep;
239
+ let khWrap = rowStep * i32(CONV_KERNEL_H) - planeStep;
240
+ var carriedAddress = vec4<i32>(i32(carriedIc) * planeStep) + carriedIh * vec4<i32>(i32(CONV_IN_W)) + carriedIw;
241
+ for (var i = 0u; i < {{ implicitGatherLoadWidth }}u; i = i + 1u) {
242
+ tileB[bLocalK + i][bColVec] = vec4<{{ tileT }}>(
243
+ {% for component in components %}
244
+ {{ tileT }}(xm[batchBase + u32(carriedAddress.{{ component }})]){% if not loop.last %},{% endif %}
245
+ {% endfor %}
246
+ );
247
+ carriedAddress = carriedAddress + vec4<i32>(colStep);
248
+ carriedKw = carriedKw + 1u;
249
+ if (carriedKw == CONV_KERNEL_W) {
250
+ carriedKw = 0u;
251
+ carriedAddress = carriedAddress - vec4<i32>(kwWrap);
252
+ carriedKh = carriedKh + 1u;
253
+ if (carriedKh == CONV_KERNEL_H) {
254
+ carriedKh = 0u;
255
+ carriedAddress = carriedAddress - vec4<i32>(khWrap);
256
+ }
257
+ }
258
+ }
259
+ } else {
260
+ for (var i = 0u; i < {{ implicitGatherLoadWidth }}u; i = i + 1u) {
261
+ var carriedValue = vec4<{{ tileT }}>({{ tileT }}(0.0));
262
+ let carriedRow = (batch * CONV_IN_CHANNELS + carriedIc) * CONV_IN_H;
263
+ let carriedOk = inColumn
264
+ & (carriedIh >= vec4<i32>(0)) & (carriedIh < vec4<i32>(i32(CONV_IN_H)))
265
+ & (carriedIw >= vec4<i32>(0)) & (carriedIw < vec4<i32>(i32(CONV_IN_W)));
266
+ if (carriedK < {{ rowLimit }}) {
267
+ {% for component in components %}
268
+ if (carriedOk.{{ component }}) {
269
+ carriedValue.{{ component }} = {{ tileT }}(xm[(carriedRow + u32(carriedIh.{{ component }})) * CONV_IN_W + u32(carriedIw.{{ component }})]);
270
+ }
271
+ {% endfor %}
272
+ }
273
+ tileB[bLocalK + i][bColVec] = carriedValue;
274
+ carriedK = carriedK + 1u;
275
+ carriedKw = carriedKw + 1u;
276
+ carriedIw = carriedIw + vec4<i32>(i32(CONV_DILATION_W));
277
+ if (carriedKw == CONV_KERNEL_W) {
278
+ carriedKw = 0u;
279
+ carriedIw = iw0;
280
+ carriedKh = carriedKh + 1u;
281
+ carriedIh = carriedIh + vec4<i32>(i32(CONV_DILATION_H));
282
+ if (carriedKh == CONV_KERNEL_H) {
283
+ carriedKh = 0u;
284
+ carriedIh = ih0;
285
+ carriedIc = carriedIc + 1u;
286
+ }
287
+ }
288
+ }
289
+ }
290
+ {%- endmacro %}
291
+
292
+ @compute @workgroup_size({{ GEMM_WG_X }}, {{ GEMM_WG_Y }}, 1)
293
+ fn main(
294
+ @builtin(workgroup_id) wg: vec3<u32>,
295
+ @builtin(local_invocation_id) lid: vec3<u32>
296
+ ) {
297
+ let M = params.M;
298
+ let K = params.K;
299
+ let N = params.N;
300
+ let mBase = wg.y * BM;
301
+ let nBase = wg.x * BN;
302
+ let batch = wg.z;
303
+ {% if not implicitIm2col %}
304
+ let xBatchBase = batch * K * N;
305
+ {% endif %}
306
+ let li = lid.y * WG_X + lid.x;
307
+
308
+ // Explicit vectors give the backend fixed register indices and expose TM
309
+ // independent FMA chains.
310
+ {% for row in range(GEMM_TM) %}
311
+ var acc{{ row }} = vec{{ GEMM_TN }}<f32>(0.0);
312
+ {% endfor %}
313
+
314
+ let numTiles = (K + BK - 1u) / BK;
315
+ for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
316
+ let kBase = kt * BK;
317
+ {{ load_a_vec4("mBase", "kBase", "M", gemmKLimit) }}
318
+ {% if useCarriedImplicitGather %}
319
+ {{ load_b_implicit_carried("kBase", "nBase", gemmKLimit, "N") }}
320
+ {% elif implicitIm2col %}
321
+ {{ load_b_implicit_vec4("kBase", "nBase", gemmKLimit, "N") }}
322
+ {% else %}
323
+ {{ load_b_vec4("kBase", "nBase", gemmKLimit, "N") }}
324
+ {% endif %}
325
+ workgroupBarrier();
326
+ // One iteration consumes four K steps: TM vector words of A and four of B
327
+ // cover TM x TN x 4 FMAs.
328
+ let aRow = lid.y * TM;
329
+ for (var kv: u32 = 0u; kv < AK_VECS; kv = kv + 1u) {
330
+ {% for row in range(GEMM_TM) %}
331
+ let av{{ row }} = vec4<f32>(tileA[aRow + {{ row }}u][kv]);
332
+ {% endfor %}
333
+ {% for sub in range(4) %}
334
+ let bv{{ sub }} = vec{{ GEMM_TN }}<f32>(tileB[kv * 4u + {{ sub }}u][lid.x]);
335
+ {% for row in range(GEMM_TM) %}
336
+ acc{{ row }} += vec{{ GEMM_TN }}<f32>(av{{ row }}.{{ components[sub] }}) * bv{{ sub }};
337
+ {% endfor %}
338
+ {% endfor %}
339
+ }
340
+ workgroupBarrier();
341
+ }
342
+
343
+ {% if not fusedNarrowProjection %}
344
+ let yBatchBase = batch * M * N;
345
+ {% endif %}
346
+ let m0 = mBase + lid.y * TM;
347
+ let n0 = nBase + lid.x * TN;
348
+ {% for row in range(GEMM_TM) %}
349
+ {% for column in range(GEMM_TN) %}
350
+ {% if fusedNarrowProjection %}
351
+ {{ publish_projection_input("lid.y * TM + " ~ row ~ "u", "lid.x * " ~ ((GEMM_TN / 4)|int) ~ "u + " ~ ((column / 4)|int) ~ "u", components[column % 4], "m0 + " ~ row ~ "u", "n0 + " ~ column ~ "u", "acc" ~ row ~ "." ~ components[column]) }}
352
+ {% else %}
353
+ store_out(m0 + {{ row }}u, n0 + {{ column }}u, yBatchBase, acc{{ row }}.{{ components[column] }});
354
+ {% endif %}
355
+ {% endfor %}
356
+ {% endfor %}{% if fusedNarrowProjection %}
357
+
358
+ // Conv's final K-tile barrier makes tileB dead before it becomes the
359
+ // intermediate tile. Every lane publishes its unique micro-tile, then one
360
+ // lane per spatial vector word consumes all logical M rows in increasing
361
+ // order, four adjacent columns at a time. Invalid M/N-tail cells are
362
+ // initialized above, so the barrier is uniform and no lane can observe a
363
+ // stale input-tile value.
364
+ workgroupBarrier();
365
+ if (li < BN_VECS) {
366
+ let projectionNBase = nBase + li * 4u;
367
+ {% for oc in range(projectionChannels) %}
368
+ var projectionAcc{{ oc }} = vec4<f32>(0.0);
369
+ {% endfor %}
370
+ for (var channel = 0u; channel < M; channel = channel + 1u) {
371
+ let projectionValues = vec4<f32>({{ narrowProjectionTile }}[channel][li]);
372
+ {% for oc in range(projectionChannels) %}
373
+ projectionAcc{{ oc }} = projectionAcc{{ oc }} + projectionValues * f32(projectionW[{{ oc }}u * M + channel]);
374
+ {% endfor %}
375
+ }
376
+
377
+ let projectionYBase = batch * PROJECTION_OUT_C * N;
378
+ {% for oc in range(projectionChannels) %}
379
+ let projected{{ oc }} = projectionAcc{{ oc }}{% if projectionBiased %} + vec4<f32>(f32(projectionBias[{{ oc }}u])){% endif %};
380
+ {% endfor %}
381
+ {% for column in range(4) %}
382
+ let projectionN{{ column }} = projectionNBase + {{ column }}u;
383
+ if (projectionN{{ column }} < N) {
384
+ {% for oc in range(projectionChannels) %}
385
+ {% if projectionOutputAct == "relu" %}
386
+ let activated{{ oc }}_{{ column }} = projection_relu(projected{{ oc }}.{{ components[column] }});
387
+ {% elif projectionOutputAct == "sigmoid" %}
388
+ let activated{{ oc }}_{{ column }} = sigmoid_safe(projected{{ oc }}.{{ components[column] }});
389
+ {% else %}
390
+ let activated{{ oc }}_{{ column }} = projected{{ oc }}.{{ components[column] }};
391
+ {% endif %}
392
+ {% if projectionScaled %}
393
+ y[projectionYBase + {{ oc }}u * N + projectionN{{ column }}] = activated{{ oc }}_{{ column }} * params.outputScale;
394
+ {% else %}
395
+ y[projectionYBase + {{ oc }}u * N + projectionN{{ column }}] = activated{{ oc }}_{{ column }};
396
+ {% endif %}
397
+ {% endfor %}
398
+ }
399
+ {% endfor %}
400
+ }
401
+ {% endif %}
402
+ }
build/webgpu/conv-1x1-gemm-tiled.wgsl.jinja ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // 1x1 Conv routed as a batched GEMM: Y[b] (M x N) = W (M x K) * X[b] (K x N)
2
+ // with M = outChannels, K = inChannels, N = H*W. For a 1x1 kernel with
3
+ // group == 1, stride 1, no padding, and dilation 1, the NCHW input is already
4
+ // the [K, N] matrix and the OIHW weight is already the [M, K] matrix — no
5
+ // data movement, only index arithmetic.
6
+ //
7
+ // Register-blocked 32x32 shared-memory tile (16x16 threads, each computes a 2x2
8
+ // micro-tile, with a selectable K tile (16 by default), the same structure as
9
+ // the general tiled matmul path — each shared element feeds 2 FMAs
10
+ // and a 32-wide column tile is staged once instead of re-reading W per output
11
+ // column. The weight matrix (A) is shared across the batch (no batch stride);
12
+ // the input (B) and output carry the batch offset via dispatch.z. f32
13
+ // accumulation, cast to the output element type on store; the optional bias is
14
+ // per output channel (M row), added after the full K accumulation per ONNX Conv
15
+ // semantics. f16 operands stay packed in workgroup memory and widen only when
16
+ // consumed. Fully bounds-checked (any M/N/K).
17
+ {% if usesF16 %}
18
+ enable f16;
19
+ {% endif %}
20
+ {{ env.wgsl.resourceDeclarations }}
21
+ {% set hasActivation = hasActivation is defined and hasActivation %}
22
+ {% if hasActivation %}
23
+ // Apply the fused activation in the f32 accumulator before the single output
24
+ // cast, avoiding an intermediate convolution tensor.
25
+ fn fused_act(v: f32) -> f32 {
26
+ return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
27
+ }
28
+ {% endif %}
29
+
30
+ {% set tileT = "f16" if usesF16 else "f32" -%}
31
+ const BK: u32 = 16u;
32
+ const BM: u32 = 32u;
33
+ const BN: u32 = 32u;
34
+
35
+ // Store one output element with the optional bias/residual/activation epilogue in the
36
+ // f32 accumulator domain: Y = activation(conv + bias + Z).
37
+ fn store_out(m: u32, n: u32, yBase: u32, raw: f32) {
38
+ if (m >= params.M || n >= params.N) {
39
+ return;
40
+ }
41
+ var v = raw;
42
+ {% if hasBias %}
43
+ v = v + f32(bias[m]);
44
+ {% endif %}
45
+ {% if hasActivation %}
46
+ v = fused_act(v);
47
+ {% endif %}
48
+ y[yBase + m * params.N + n] = {{ T }}(v);
49
+ }
50
+
51
+
52
+ var<workgroup> tileA: array<array<{{ tileT }}, BK>, BM>;
53
+ var<workgroup> tileB: array<array<{{ tileT }}, BN>, BK>;
54
+
55
+ @compute @workgroup_size(16, 16, 1)
56
+ fn main(
57
+ @builtin(workgroup_id) wg: vec3<u32>,
58
+ @builtin(local_invocation_id) lid: vec3<u32>
59
+ ) {
60
+ let M = params.M;
61
+ let K = params.K;
62
+ let N = params.N;
63
+ let mBase = wg.y * BM;
64
+ let nBase = wg.x * BN;
65
+ let batch = wg.z;
66
+ let xBatchBase = batch * K * N;
67
+ let li = lid.y * 16u + lid.x;
68
+
69
+ var acc00: f32 = 0.0;
70
+ var acc01: f32 = 0.0;
71
+ var acc10: f32 = 0.0;
72
+ var acc11: f32 = 0.0;
73
+ let numTiles = (K + BK - 1u) / BK;
74
+ for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
75
+ let kBase = kt * BK;
76
+ // Cooperative load: 32x16 A(=W) tile + 16x32 B(=X[batch]) tile, 256 threads x 2 each.
77
+ for (var e: u32 = 0u; e < (BM * BK) / 256u; e = e + 1u) {
78
+ let idx = li + e * 256u;
79
+ let ar = idx / BK;
80
+ let ac = idx % BK;
81
+ let am = mBase + ar;
82
+ let ak = kBase + ac;
83
+ if (am < M && ak < K) {
84
+ tileA[ar][ac] = {{ tileT }}(w[am * K + ak]);
85
+ } else {
86
+ tileA[ar][ac] = {{ tileT }}(0.0);
87
+ }
88
+ let br = idx / BN;
89
+ let bc = idx % BN;
90
+ let bk = kBase + br;
91
+ let bn = nBase + bc;
92
+ if (bk < K && bn < N) {
93
+ tileB[br][bc] = {{ tileT }}(xm[xBatchBase + bk * N + bn]);
94
+ } else {
95
+ tileB[br][bc] = {{ tileT }}(0.0);
96
+ }
97
+ }
98
+ workgroupBarrier();
99
+ for (var kk: u32 = 0u; kk < BK; kk = kk + 1u) {
100
+ let a0 = f32(tileA[lid.y * 2u][kk]);
101
+ let a1 = f32(tileA[lid.y * 2u + 1u][kk]);
102
+ let b0 = f32(tileB[kk][lid.x * 2u]);
103
+ let b1 = f32(tileB[kk][lid.x * 2u + 1u]);
104
+ acc00 = acc00 + a0 * b0;
105
+ acc01 = acc01 + a0 * b1;
106
+ acc10 = acc10 + a1 * b0;
107
+ acc11 = acc11 + a1 * b1;
108
+ }
109
+ workgroupBarrier();
110
+ }
111
+
112
+ let m0 = mBase + lid.y * 2u;
113
+ let m1 = m0 + 1u;
114
+ let n0 = nBase + lid.x * 2u;
115
+ let n1 = n0 + 1u;
116
+ let yBatchBase = batch * M * N;
117
+ store_out(m0, n0, yBatchBase, acc00);
118
+ store_out(m0, n1, yBatchBase, acc01);
119
+ store_out(m1, n0, yBatchBase, acc10);
120
+ store_out(m1, n1, yBatchBase, acc11);
121
+ }
build/webgpu/conv-1x1-subgroup-matrix.wgsl.jinja ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Convolution as subgroup-matrix GEMM. OIHW weights form row-major [M,K]; `xm`
2
+ // is either an NCHW 1x1 view [K,N] or a materialized im2col matrix. Bias is
3
+ // applied per output channel after the full f32 accumulation.
4
+ //
5
+ // Full 8x8 matrix loads require complete K/N tiles. The aligned path enforces
6
+ // that geometry; `padded` zero-fills tails, and `implicitIm2col` gathers and
7
+ // zero-fills directly from NCHW without materializing the column matrix.
8
+ //
9
+ // `splitK` assigns disjoint K ranges to workgroup_id.z. Each slice writes raw
10
+ // sums to tile-padded [splitK,batch,M_PAD,N_PAD] scratch; a later pass combines
11
+ // the slices and applies bias or an epilogue. Padded tail cells remain zero and
12
+ // are never copied to the logical output.
13
+ {% set fusedNarrowProjection = fusedNarrowProjection if fusedNarrowProjection is defined else false %}
14
+ {% set polyphase = polyphaseConvTranspose is defined and polyphaseConvTranspose %}
15
+ {% set splitKValue = splitK if splitK is defined else 1 %}
16
+ {% set splitKPartial = splitKValue > 1 %}
17
+ {% set kLoopVar = "K_LOOP" if padded else "K" %}
18
+ {% set nColsVar = "N_COLS" if padded else "N" %}
19
+ {% if usesF16 %}
20
+ enable f16;
21
+ {% endif %}
22
+ enable subgroups;
23
+ {% if pinSubgroupSize32 %}
24
+ enable subgroup_size_control;
25
+ {% endif %}
26
+ enable chromium_experimental_subgroup_matrix;
27
+ diagnostic(off, chromium.subgroup_matrix_uniformity);
28
+
29
+ {{ env.wgsl.resourceDeclarations }}
30
+
31
+ {% set operandScalar = fScalar %}
32
+ {% set accScalar = "f32" %}
33
+ {% set useDirectMatrixStore = directMatrixStore is defined and directMatrixStore %}
34
+ {% set tileRowsValue = tileRows if tileRows is defined else 32 %}
35
+ {% set tileColsValue = tileCols if tileCols is defined else 64 %}
36
+ {% set workgroupThreadsValue = workgroupThreads if workgroupThreads is defined else 128 %}
37
+ {% set subgroupRowsValue = subgroupRows if subgroupRows is defined else 2 %}
38
+ {% set subgroupColsValue = subgroupCols if subgroupCols is defined else 2 %}
39
+ {% set subRowsValue = (tileRowsValue / subgroupRowsValue)|int %}
40
+ {% set subColsValue = (tileColsValue / subgroupColsValue)|int %}
41
+ {% set bLoadWidth = (tileColsValue * 32 / workgroupThreadsValue)|int %}
42
+ {% set bKChunks = (32 / bLoadWidth)|int %}
43
+
44
+ const M: u32 = {{ M }}u;
45
+ const K: u32 = {{ K }}u;
46
+ const N: u32 = {{ N }}u;
47
+ const B_BATCH_STRIDE: u32 = {{ kLoopVar }} * {{ nColsVar }};
48
+ {% if not splitKPartial and not polyphase and not fusedNarrowProjection %}
49
+ const C_BATCH_STRIDE: u32 = M * N;
50
+ {% endif %}
51
+ const TILE_COLS: u32 = {{ tileColsValue }}u;
52
+ const TILE_ROWS: u32 = {{ tileRowsValue }}u;
53
+ const TILE_K: u32 = 32u;
54
+ const SUB_COLS: u32 = {{ subColsValue }}u;
55
+ const SUB_ROWS: u32 = {{ subRowsValue }}u;
56
+
57
+ var<workgroup> tile_A: array<{{ operandScalar }}, {{ tileRowsValue }} * 32>;
58
+ var<workgroup> tile_B: array<{{ operandScalar }}, {{ tileColsValue }} * 32>;
59
+ {% if (not useDirectMatrixStore or padded) and not splitKPartial and not fusedNarrowProjection %}
60
+ var<workgroup> scratch: array<array<array<{{ accScalar }}, 64>, 4>, {{ (workgroupThreadsValue / 32)|int }}>;
61
+
62
+ {% endif %}
63
+ fn loadSHMA(tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
64
+ let a_global = tile_base + row;
65
+ let col = c_idx * 8u;
66
+ for (var col_offset = 0u; col_offset < 8u; col_offset = col_offset + 1u) {
67
+ let k = k_idx + col + col_offset;
68
+ if (a_global < M) {
69
+ {% set A_PHASE = "phase_base + " if polyphase else "" %}
70
+ {% if operandScalar == "f16" %}
71
+ tile_A[row * TILE_K + col + col_offset] = f16(w[{{ A_PHASE }}a_global * K + k]);
72
+ {% else %}
73
+ tile_A[row * TILE_K + col + col_offset] = f32(w[{{ A_PHASE }}a_global * K + k]);
74
+ {% endif %}
75
+ } else {
76
+ {% if operandScalar == "f16" %}
77
+ tile_A[row * TILE_K + col + col_offset] = 0.0h;
78
+ {% else %}
79
+ tile_A[row * TILE_K + col + col_offset] = 0.0;
80
+ {% endif %}
81
+ }
82
+ }
83
+ }
84
+
85
+ fn loadSHMB(b_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
86
+ let b_col = tile_base + row;
87
+ let col = c_idx * {{ bLoadWidth }}u;
88
+ for (var i = 0u; i < {{ bLoadWidth }}u; i = i + 1u) {
89
+ let k = k_idx + col + i;
90
+ {% if operandScalar == "f16" %}
91
+ tile_B[row * TILE_K + col + i] = f16(xm[b_base + k * {{ nColsVar }} + b_col]);
92
+ {% else %}
93
+ tile_B[row * TILE_K + col + i] = f32(xm[b_base + k * {{ nColsVar }} + b_col]);
94
+ {% endif %}
95
+ }
96
+ }
97
+
98
+ {% set hasActivation = hasActivation is defined and hasActivation %}
99
+ {% if hasActivation %}
100
+ // Apply the fused activation in the f32 accumulator before the single output
101
+ // cast, avoiding an intermediate convolution tensor.
102
+ fn fused_act(v: f32) -> f32 {
103
+ return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
104
+ }
105
+ {% endif %}
106
+
107
+ {% set biasAdd = " + bv" if hasBias else "" %}
108
+ {% if hasActivation or hasZ %}
109
+ // Fused epilogue: Y = activation(conv + bias + Z), applied at the output store.
110
+ // `raw` is the f32 convolution-plus-bias accumulator; Z shares Y's NCHW layout.
111
+ fn epi(raw: f32) -> {{ T }} {
112
+ var r = raw;
113
+ {% if hasActivation %}
114
+ r = fused_act(r);
115
+ {% endif %}
116
+ return {{ T }}(r);
117
+ }
118
+ {% endif %}
119
+ {% macro store_val(valExpr, idxExpr) %}
120
+ {% if hasActivation or hasZ %}epi({{ valExpr }})
121
+ {%- else %}{{ T }}({{ valExpr }})
122
+ {%- endif %}
123
+ {% endmacro %}
124
+
125
+ {% if (not useDirectMatrixStore or padded) and not splitKPartial and not fusedNarrowProjection %}
126
+ fn storeOutput(offset: u32, {% if hasBias or polyphase %}row_base: u32, {% endif %}row: u32, col: u32, src_slot: u32, row_limit: i32) {
127
+ if (row_limit > 0 && row < u32(row_limit)) {
128
+ let col2 = col + 1u;
129
+ {% if hasBias %}
130
+ let bv = {{ accScalar }}(bias[row_base + row]);
131
+ {% endif %}
132
+ y[offset + row * N + col] = {{ store_val("scratch[src_slot][0][row * 8u + col]" ~ biasAdd, "offset + row * N + col") }};
133
+ y[offset + row * N + col + 8u] = {{ store_val("scratch[src_slot][1][row * 8u + col]" ~ biasAdd, "offset + row * N + col + 8u") }};
134
+ y[offset + row * N + col + 16u] = {{ store_val("scratch[src_slot][2][row * 8u + col]" ~ biasAdd, "offset + row * N + col + 16u") }};
135
+ y[offset + row * N + col + 24u] = {{ store_val("scratch[src_slot][3][row * 8u + col]" ~ biasAdd, "offset + row * N + col + 24u") }};
136
+
137
+ y[offset + row * N + col2] = {{ store_val("scratch[src_slot][0][row * 8u + col2]" ~ biasAdd, "offset + row * N + col2") }};
138
+ y[offset + row * N + col2 + 8u] = {{ store_val("scratch[src_slot][1][row * 8u + col2]" ~ biasAdd, "offset + row * N + col2 + 8u") }};
139
+ y[offset + row * N + col2 + 16u] = {{ store_val("scratch[src_slot][2][row * 8u + col2]" ~ biasAdd, "offset + row * N + col2 + 16u") }};
140
+ y[offset + row * N + col2 + 24u] = {{ store_val("scratch[src_slot][3][row * 8u + col2]" ~ biasAdd, "offset + row * N + col2 + 24u") }};
141
+ }
142
+ }
143
+
144
+ {% endif %}
145
+ @compute @workgroup_size({{ workgroupThreadsValue }}, 1, 1){{ " @subgroup_size(32)" if pinSubgroupSize32 else "" }}
146
+ fn main(
147
+ @builtin(workgroup_id) workgroup_id: vec3<u32>,
148
+ @builtin(local_invocation_index) local_idx: u32,
149
+ {% if (not useDirectMatrixStore or padded) and not splitKPartial and not fusedNarrowProjection %}
150
+ @builtin(subgroup_invocation_id) sg_id: u32,
151
+ {% endif %}
152
+ @builtin(subgroup_size) sg_size: u32
153
+ ) {
154
+ let batch = workgroup_id.z;
155
+ let b_base = batch * B_BATCH_STRIDE;
156
+ {% if not fusedNarrowProjection %}
157
+ let c_base = batch * C_BATCH_STRIDE;
158
+ {% endif %}
159
+ let a_global_base = workgroup_id.y * TILE_ROWS;
160
+ let b_global_base = workgroup_id.x * TILE_COLS;
161
+ let subtile_id = local_idx / sg_size;
162
+ let subtile_idx = subtile_id / {{ subgroupRowsValue }}u;
163
+ let subtile_idy = subtile_id % {{ subgroupRowsValue }}u;
164
+ let base_A = subtile_idy * SUB_ROWS;
165
+ let base_B = subtile_idx * SUB_COLS;
166
+
167
+ var matC00: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
168
+ var matC01: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
169
+ var matC02: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
170
+ var matC03: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
171
+ var matC10: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
172
+ var matC11: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
173
+ var matC12: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
174
+ var matC13: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
175
+
176
+ for (var kidx = 0u; kidx < {{ kLoopVar }}; kidx = kidx + TILE_K) {
177
+ loadSHMA(a_global_base, kidx, local_idx / 4u, local_idx % 4u);
178
+ loadSHMB(b_base, b_global_base, kidx, local_idx / {{ bKChunks }}u, local_idx % {{ bKChunks }}u);
179
+ workgroupBarrier();
180
+
181
+ for (var step = 0u; step < TILE_K; step = step + 8u) {
182
+ let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
183
+ var matA0: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset, false, TILE_K);
184
+ var matA1: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset + 8u * TILE_K, false, TILE_K);
185
+
186
+ let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
187
+ var matB0: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset, true, TILE_K);
188
+ var matB1: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 8u * TILE_K, true, TILE_K);
189
+ var matB2: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 16u * TILE_K, true, TILE_K);
190
+ var matB3: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 24u * TILE_K, true, TILE_K);
191
+
192
+ matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
193
+ matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
194
+ matC02 = subgroupMatrixMultiplyAccumulate(matA0, matB2, matC02);
195
+ matC03 = subgroupMatrixMultiplyAccumulate(matA0, matB3, matC03);
196
+ matC10 = subgroupMatrixMultiplyAccumulate(matA1, matB0, matC10);
197
+ matC11 = subgroupMatrixMultiplyAccumulate(matA1, matB1, matC11);
198
+ matC12 = subgroupMatrixMultiplyAccumulate(matA1, matB2, matC12);
199
+ matC13 = subgroupMatrixMultiplyAccumulate(matA1, matB3, matC13);
200
+ }
201
+ workgroupBarrier();
202
+ }
203
+
204
+ {% if fusedNarrowProjection %}
205
+ // The Conv producer owns exactly one 32-row tile. Its input tile is dead after
206
+ // the final K iteration, so reuse those 32x64 f32 cells as the only
207
+ // cross-subgroup handoff into the narrow projection. All collective stores
208
+ // remain subgroup-uniform, and every fragment has a disjoint destination.
209
+ let fused_tile_offset = base_A * TILE_COLS + base_B;
210
+ subgroupMatrixStore(&tile_B, fused_tile_offset + 0u * TILE_COLS + 0u, matC00, false, TILE_COLS);
211
+ subgroupMatrixStore(&tile_B, fused_tile_offset + 0u * TILE_COLS + 8u, matC01, false, TILE_COLS);
212
+ subgroupMatrixStore(&tile_B, fused_tile_offset + 0u * TILE_COLS + 16u, matC02, false, TILE_COLS);
213
+ subgroupMatrixStore(&tile_B, fused_tile_offset + 0u * TILE_COLS + 24u, matC03, false, TILE_COLS);
214
+ subgroupMatrixStore(&tile_B, fused_tile_offset + 8u * TILE_COLS + 0u, matC10, false, TILE_COLS);
215
+ subgroupMatrixStore(&tile_B, fused_tile_offset + 8u * TILE_COLS + 8u, matC11, false, TILE_COLS);
216
+ subgroupMatrixStore(&tile_B, fused_tile_offset + 8u * TILE_COLS + 16u, matC12, false, TILE_COLS);
217
+ subgroupMatrixStore(&tile_B, fused_tile_offset + 8u * TILE_COLS + 24u, matC13, false, TILE_COLS);
218
+ workgroupBarrier();
219
+
220
+ // One invocation owns one spatial column and walks the producer channels in
221
+ // increasing order to preserve the scalar projection's accumulation order.
222
+ // The inactive half of the workgroup has no remaining collective operation
223
+ // to reach.
224
+ let fused_global_col = b_global_base + local_idx;
225
+ if (local_idx < TILE_COLS && fused_global_col < N) {
226
+ {% for oc in range(projectionOutChannels) %}
227
+ var projected_acc{{ oc }} = f32(0.0);
228
+ {% endfor %}
229
+ for (var channel = 0u; channel < M; channel = channel + 1u) {
230
+ let producer_value = tile_B[channel * TILE_COLS + local_idx]{% if hasBias %} + f32(bias[channel]){% endif %};
231
+ let activated_value = producer_value;
232
+ {% for oc in range(projectionOutChannels) %}
233
+ projected_acc{{ oc }} = projected_acc{{ oc }} + activated_value * f32(projectionW[{{ oc }}u * M + channel]);
234
+ {% endfor %}
235
+ }
236
+
237
+ let fused_y_base = batch * {{ projectionOutChannels }}u * N + fused_global_col;
238
+ {% for oc in range(projectionOutChannels) %}
239
+ let projected{{ oc }} = projected_acc{{ oc }};
240
+ let activated{{ oc }} = projected{{ oc }};
241
+ y[fused_y_base + {{ oc }}u * N] = activated{{ oc }};
242
+ {% endfor %}
243
+ }
244
+ {% elif useDirectMatrixStore %}
245
+ // Full f32 output tiles can be published by the subgroup-matrix collectives
246
+ // directly. With no epilogue this removes scratch traffic and three barriers.
247
+ // When an epilogue is present, publish the raw accumulators first, then apply
248
+ // bias, residual Z, and activation cooperatively after one storage barrier.
249
+ let matrix_c_offset = c_base + (a_global_base + base_A) * N + b_global_base + base_B;
250
+ subgroupMatrixStore(&y, matrix_c_offset + 0u * N + 0u, matC00, false, N);
251
+ subgroupMatrixStore(&y, matrix_c_offset + 0u * N + 8u, matC01, false, N);
252
+ subgroupMatrixStore(&y, matrix_c_offset + 0u * N + 16u, matC02, false, N);
253
+ subgroupMatrixStore(&y, matrix_c_offset + 0u * N + 24u, matC03, false, N);
254
+ subgroupMatrixStore(&y, matrix_c_offset + 8u * N + 0u, matC10, false, N);
255
+ subgroupMatrixStore(&y, matrix_c_offset + 8u * N + 8u, matC11, false, N);
256
+ subgroupMatrixStore(&y, matrix_c_offset + 8u * N + 16u, matC12, false, N);
257
+ subgroupMatrixStore(&y, matrix_c_offset + 8u * N + 24u, matC13, false, N);
258
+ {% if hasBias or hasActivation or hasZ %}
259
+ storageBarrier();
260
+ for (var tile_idx = local_idx; tile_idx < TILE_ROWS * TILE_COLS; tile_idx += {{ workgroupThreadsValue }}u) {
261
+ let local_row = tile_idx / TILE_COLS;
262
+ let local_col = tile_idx % TILE_COLS;
263
+ let global_row = a_global_base + local_row;
264
+ let global_col = b_global_base + local_col;
265
+ let y_index = c_base + global_row * N + global_col;
266
+ var raw = f32(y[y_index]);
267
+ {% if hasBias %}
268
+ raw += f32(bias[global_row]);
269
+ {% endif %}
270
+ {% if hasActivation or hasZ %}
271
+ y[y_index] = epi(raw);
272
+ {% else %}
273
+ y[y_index] = {{ T }}(raw);
274
+ {% endif %}
275
+ }
276
+ {% endif %}
277
+ {% endif %}
278
+ {% if (not useDirectMatrixStore or padded) and not splitKPartial and not fusedNarrowProjection %}
279
+ // The four scratch banks are reused across the two row-groups, and each is written
280
+ // by a collective subgroupMatrixStore then read CROSS-LANE by storeOutput. Barriers
281
+ // give the reads visibility of the store AND stop the second row-group's store from
282
+ // clobbering the first's still-in-flight readback when a partial final M-tile
283
+ // diverges storeOutput's guard. Without both barriers the last valid row can be corrupted.
284
+ subgroupMatrixStore(&scratch[subtile_id][0], 0u, matC00, false, 8u);
285
+ subgroupMatrixStore(&scratch[subtile_id][1], 0u, matC01, false, 8u);
286
+ subgroupMatrixStore(&scratch[subtile_id][2], 0u, matC02, false, 8u);
287
+ subgroupMatrixStore(&scratch[subtile_id][3], 0u, matC03, false, 8u);
288
+ workgroupBarrier();
289
+ let row = sg_id / 4u;
290
+ let col = (sg_id % 4u) * 2u;
291
+ let row_base = a_global_base + base_A;
292
+ var matrix_c_offset = c_base + row_base * N + b_global_base + base_B;
293
+ var row_limit = i32(M) - i32(row_base);
294
+ storeOutput(matrix_c_offset, {% if hasBias or polyphase %}row_base, {% endif %}row, col, subtile_id, row_limit);
295
+ workgroupBarrier();
296
+
297
+ subgroupMatrixStore(&scratch[subtile_id][0], 0u, matC10, false, 8u);
298
+ subgroupMatrixStore(&scratch[subtile_id][1], 0u, matC11, false, 8u);
299
+ subgroupMatrixStore(&scratch[subtile_id][2], 0u, matC12, false, 8u);
300
+ subgroupMatrixStore(&scratch[subtile_id][3], 0u, matC13, false, 8u);
301
+ workgroupBarrier();
302
+ matrix_c_offset = matrix_c_offset + 8u * N;
303
+ row_limit = i32(M) - i32(row_base + 8u);
304
+ storeOutput(matrix_c_offset, {% if hasBias or polyphase %}row_base + 8u, {% endif %}row, col, subtile_id, row_limit);
305
+ {% endif %}
306
+ }
build/webgpu/conv-transpose-empty-input.wgsl.jinja ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ {% if usesF16 %}
35
+ enable f16;
36
+ {% endif %}
37
+ {{ env.wgsl.resourceDeclarations }}
38
+
39
+ const COUNT: u32 = {{ source.count }}u;
40
+ {% if hasBias %}
41
+ const OUT_CHANNELS: u32 = {{ source.outChannels }}u;
42
+ const OUTPUT_SPATIAL: u32 = {{ source.outputSpatial }}u;
43
+
44
+ {% endif %}
45
+ // An empty contraction contributes the additive identity at every output
46
+ // coordinate. The optional ConvTranspose bias is applied after that reduction.
47
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
48
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
49
+ @builtin(num_workgroups) nwg: vec3<u32>) {
50
+ {{ flat_index_2d("index", "COUNT", note="device-axis") }}
51
+ {% if hasBias %}
52
+ let output_channel = (index / OUTPUT_SPATIAL) % OUT_CHANNELS;
53
+ y[index] = bias[output_channel];
54
+ {% else %}
55
+ y[index] = {{ T }}(0.0);
56
+ {% endif %}
57
+ }
build/webgpu/conv-transpose1d-ncl.wgsl.jinja ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ {% macro tap(raw, coord, out, kernel, axis, indent) %}
35
+ {{ indent }}let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
36
+ {{ indent }}if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
37
+ {{ indent }}let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
38
+ {{ indent }}if ({{ coord }} < 0 || {{ coord }} >= i32(params.in{{ axis }})) { continue; }
39
+ {%- endmacro -%}
40
+ {% if usesF16 %}
41
+ // Preserve f32 accumulation for the deep channel-by-kernel sum: widen each
42
+ // f16 product and narrow only once at the output store.
43
+ enable f16;
44
+ {% endif %}
45
+ {{ env.wgsl.resourceDeclarations }}
46
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
47
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
48
+ {{ flat_index_2d("index", note="device-axis") }}
49
+
50
+ let ow = index % params.outW;
51
+ var t = index / params.outW;
52
+ let oc = t % params.outChannels;
53
+ let batch = t / params.outChannels;
54
+ {% if grouped %}
55
+
56
+ let groupIndex = oc / params.outChannelsPerGroup;
57
+ let localOc = oc - groupIndex * params.outChannelsPerGroup;
58
+ let inputStart = groupIndex * params.inChannelsPerGroup;
59
+
60
+ {% endif %}
61
+ var acc = 0.0;
62
+ {% if hasBias %}
63
+ {% if usesF16 %}
64
+ acc = f32(bias[oc]);
65
+ {% else %}
66
+ acc = bias[oc];
67
+ {% endif %}
68
+ {% endif %}
69
+ {% if grouped %}
70
+ for (var localIc = 0u; localIc < params.inChannelsPerGroup; localIc = localIc + 1u) {
71
+ let ic = inputStart + localIc;
72
+ for (var kw = 0u; kw < params.kernelW; kw = kw + 1u) {
73
+ {{ tap("rawW", "iw", "ow", "kw", "W", " ") }}
74
+ let xIndex = (batch * params.inChannels + ic) * params.inW + u32(iw);
75
+ let wIndex = (ic * params.outChannelsPerGroup + localOc) * params.kernelW + kw;
76
+ {% if usesF16 %}
77
+ acc = acc + f32(x[xIndex]) * f32(w[wIndex]);
78
+ {% else %}
79
+ acc = acc + x[xIndex] * w[wIndex];
80
+ {% endif %}
81
+ }
82
+ }
83
+ {% else %}
84
+ for (var ic = 0u; ic < params.inChannels; ic = ic + 1u) {
85
+ for (var kw = 0u; kw < params.kernelW; kw = kw + 1u) {
86
+ {{ tap("rawW", "iw", "ow", "kw", "W", " ") }}
87
+ let xIndex = (batch * params.inChannels + ic) * params.inW + u32(iw);
88
+ let wIndex = (ic * params.outChannels + oc) * params.kernelW + kw;
89
+ {% if usesF16 %}
90
+ acc = acc + f32(x[xIndex]) * f32(w[wIndex]);
91
+ {% else %}
92
+ acc = acc + x[xIndex] * w[wIndex];
93
+ {% endif %}
94
+ }
95
+ }
96
+ {% endif %}
97
+ {% if usesF16 %}
98
+ y[index] = f16(acc);
99
+ {% else %}
100
+ y[index] = acc;
101
+ {% endif %}
102
+ }
build/webgpu/conv-transpose1d-phase-tiled.wgsl.jinja ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+
3
+ // Stride-2, k=4 transposed convolution decomposes into two dense phases per
4
+ // input position:
5
+ // y[2j] = x[j] W1 + x[j-1] W3
6
+ // y[2j+1] = x[j+1] W0 + x[j] W2
7
+ // A workgroup computes both phases for a tile of positions and output channels.
8
+ // Input samples and four-tap weight vectors are staged once per input-channel
9
+ // tile, so every staged value is reused across the tile's dot products.
10
+ const IN_CHANNELS: u32 = {{ source.inChannels }}u;
11
+ const IN_WIDTH: u32 = {{ source.inWidth }}u;
12
+ const OUT_CHANNELS: u32 = {{ source.outChannels }}u;
13
+ const POSITION_TILE: u32 = {{ source.positionTile }}u;
14
+ const OUTPUT_TILE: u32 = {{ source.outputTile }}u;
15
+ const OUTPUTS_PER_THREAD: u32 = {{ source.outputsPerThread }}u;
16
+ const OUTPUT_GROUPS: u32 = OUTPUT_TILE / OUTPUTS_PER_THREAD;
17
+ const K_TILE: u32 = {{ source.kTile }}u;
18
+ const WORKGROUP_SIZE: u32 = {{ source.workgroupSize }}u;
19
+ const INPUT_TILE_STRIDE: u32 = POSITION_TILE + 2u;
20
+
21
+ var<workgroup> tile_x: array<f32, {{ source.xTileElements }}>;
22
+ var<workgroup> tile_w: array<vec4<f32>, {{ source.weightTileElements }}>;
23
+
24
+ @compute @workgroup_size({{ source.workgroupSize }})
25
+ fn main(
26
+ @builtin(local_invocation_index) lane: u32,
27
+ @builtin(workgroup_id) wid: vec3<u32>
28
+ ) {
29
+ let position_base = wid.x * POSITION_TILE;
30
+ let output_base = wid.y * OUTPUT_TILE;
31
+ let batch = wid.z;
32
+ let local_position = lane % POSITION_TILE;
33
+ let local_output_group = lane / POSITION_TILE;
34
+ let position = position_base + local_position;
35
+ {% for slot in range(source.outputsPerThread) %}
36
+ let output_channel_{{ slot }} = output_base + local_output_group + {{ slot }}u * OUTPUT_GROUPS;
37
+ let output_valid_{{ slot }} = position < IN_WIDTH && output_channel_{{ slot }} < OUT_CHANNELS;
38
+ var even_{{ slot }} = 0.0;
39
+ var odd_{{ slot }} = 0.0;
40
+ {% if hasBias %}
41
+ if (output_channel_{{ slot }} < OUT_CHANNELS) {
42
+ even_{{ slot }} = bias[output_channel_{{ slot }}];
43
+ odd_{{ slot }} = even_{{ slot }};
44
+ }
45
+ {% endif %}
46
+ {% endfor %}
47
+
48
+ for (var k_base = 0u; k_base < IN_CHANNELS; k_base += K_TILE) {
49
+ for (var load_index = lane; load_index < {{ source.xTileElements }}u; load_index += WORKGROUP_SIZE) {
50
+ let local_k = load_index / INPUT_TILE_STRIDE;
51
+ let halo_position = load_index - local_k * INPUT_TILE_STRIDE;
52
+ let input_channel = k_base + local_k;
53
+ let input_position = i32(position_base + halo_position) - 1;
54
+ var value = 0.0;
55
+ if (input_channel < IN_CHANNELS && input_position >= 0 && input_position < i32(IN_WIDTH)) {
56
+ let input_index = (batch * IN_CHANNELS + input_channel) * IN_WIDTH + u32(input_position);
57
+ value = x[input_index];
58
+ }
59
+ tile_x[load_index] = value;
60
+ }
61
+
62
+ for (var load_index = lane; load_index < {{ source.weightTileElements }}u; load_index += WORKGROUP_SIZE) {
63
+ let local_k = load_index / OUTPUT_TILE;
64
+ let local_oc = load_index - local_k * OUTPUT_TILE;
65
+ let input_channel = k_base + local_k;
66
+ let oc = output_base + local_oc;
67
+ var value = vec4<f32>(0.0);
68
+ if (input_channel < IN_CHANNELS && oc < OUT_CHANNELS) {
69
+ value = w[input_channel * OUT_CHANNELS + oc];
70
+ }
71
+ tile_w[load_index] = value;
72
+ }
73
+
74
+ workgroupBarrier();
75
+ if (position < IN_WIDTH) {
76
+ for (var local_k = 0u; local_k < K_TILE; local_k += 1u) {
77
+ let x_base = local_k * INPUT_TILE_STRIDE + local_position;
78
+ let previous = tile_x[x_base];
79
+ let center = tile_x[x_base + 1u];
80
+ let next = tile_x[x_base + 2u];
81
+ {% for slot in range(source.outputsPerThread) %}
82
+ if (output_channel_{{ slot }} < OUT_CHANNELS) {
83
+ let weights_{{ slot }} = tile_w[local_k * OUTPUT_TILE + local_output_group + {{ slot }}u * OUTPUT_GROUPS];
84
+ even_{{ slot }} = fma(center, weights_{{ slot }}.y, even_{{ slot }});
85
+ even_{{ slot }} = fma(previous, weights_{{ slot }}.w, even_{{ slot }});
86
+ odd_{{ slot }} = fma(next, weights_{{ slot }}.x, odd_{{ slot }});
87
+ odd_{{ slot }} = fma(center, weights_{{ slot }}.z, odd_{{ slot }});
88
+ }
89
+ {% endfor %}
90
+ }
91
+ }
92
+ workgroupBarrier();
93
+ }
94
+
95
+ {% for slot in range(source.outputsPerThread) %}
96
+ if (output_valid_{{ slot }}) {
97
+ // The output width is exactly 2*IN_WIDTH, so one vec2 owns both phases.
98
+ let output_index_{{ slot }} = (batch * OUT_CHANNELS + output_channel_{{ slot }}) * IN_WIDTH + position;
99
+ y[output_index_{{ slot }}] = vec2<f32>(even_{{ slot }}, odd_{{ slot }});
100
+ }
101
+ {% endfor %}
102
+ }
build/webgpu/conv-transpose2d-col2im.wgsl.jinja ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ {% macro tap(raw, coord, out, kernel, axis, indent) %}
35
+ {{ indent }}let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
36
+ {{ indent }}if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
37
+ {{ indent }}let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
38
+ {{ indent }}if ({{ coord }} < 0 || {{ coord }} >= i32(params.in{{ axis }})) { continue; }
39
+ {%- endmacro -%}
40
+ // ConvTranspose col2im scatter (output-stationary) for the im2col-GEMM path.
41
+ //
42
+ // The GEMM pass produced cols[m = (oc*KH+kh)*KW+kw, n = ih*IW+iw] = sum_ic
43
+ // Wt[m, ic] * X[ic, n], i.e. the per-tap partial products with the IC contraction
44
+ // already folded in. This pass reconstructs each output element by gathering the
45
+ // KH*KW taps that land on it — exactly the (oh,ow) -> (ih,iw) mapping the direct
46
+ // kernel uses, but reading the precomputed cols instead of re-running the IC sum.
47
+ // One thread per output element, f32 accumulation, deterministic (no atomics).
48
+ {% if usesF16 %}
49
+ enable f16;
50
+
51
+ {% endif %}
52
+ {{ env.wgsl.resourceDeclarations }}
53
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
54
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
55
+ {{ flat_index_2d("index", note="") }}
56
+
57
+ let ow = index % params.outW;
58
+ var t = index / params.outW;
59
+ let oh = t % params.outH;
60
+ t = t / params.outH;
61
+ let oc = t % params.outChannels;
62
+ let batch = t / params.outChannels;
63
+
64
+ // cols is [M = OC*KH*KW, N = IH*IW] per batch image.
65
+ let cols_row_stride = params.inH * params.inW;
66
+ let cols_batch_stride = params.outChannels * params.kernelH * params.kernelW * cols_row_stride;
67
+ let b_base = batch * cols_batch_stride;
68
+
69
+ var acc = 0.0;
70
+ {% if hasBias %}
71
+ acc = f32(bias[oc]);
72
+ {% endif %}
73
+ {% if exactTiling is defined and exactTiling %}
74
+ // When kernel equals stride with no padding or dilation, the taps tile the
75
+ // output and exactly one (kh, kw) contributes to each element. Derive that
76
+ // tap directly; the output-shape contract keeps ih and iw in range.
77
+ let kh = oh % params.kernelH;
78
+ let ih = oh / params.kernelH;
79
+ let kw = ow % params.kernelW;
80
+ let iw = ow / params.kernelW;
81
+ let m = (oc * params.kernelH + kh) * params.kernelW + kw;
82
+ let n = ih * params.inW + iw;
83
+ acc = acc + f32(cols[b_base + m * cols_row_stride + n]);
84
+ {% else %}
85
+ for (var kh = 0u; kh < params.kernelH; kh = kh + 1u) {
86
+ {{ tap("raw_h", "ih", "oh", "kh", "H", " ") }}
87
+ for (var kw = 0u; kw < params.kernelW; kw = kw + 1u) {
88
+ {{ tap("raw_w", "iw", "ow", "kw", "W", " ") }}
89
+ let m = (oc * params.kernelH + kh) * params.kernelW + kw;
90
+ let n = u32(ih) * params.inW + u32(iw);
91
+ acc = acc + f32(cols[b_base + m * cols_row_stride + n]);
92
+ }
93
+ }
94
+ {% endif %}
95
+ y[index] = {{ T }}(acc);
96
+ }
build/webgpu/conv-transpose2d-grouped-stride-phase.wgsl.jinja ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ // With stride > 1 only one kernel phase contributes to a given output pixel.
7
+ // Start each kernel loop at that phase and advance by stride so it visits only
8
+ // contributing taps and needs no per-tap modulo or divisibility test.
9
+ const COUNT: u32 = {{ source.count }}u;
10
+ const OUT_C: u32 = {{ source.outC }}u;
11
+ const OUT_CPG: u32 = {{ source.outCPerGroup }}u;
12
+ const OUT_H: u32 = {{ source.outH }}u;
13
+ const OUT_W: u32 = {{ source.outW }}u;
14
+ const IN_C: u32 = {{ source.inC }}u;
15
+ const IN_CPG: u32 = {{ source.inCPerGroup }}u;
16
+ const IN_H: i32 = {{ source.inH }};
17
+ const IN_W: i32 = {{ source.inW }};
18
+ const IN_H_U: u32 = {{ source.inH }}u;
19
+ const IN_W_U: u32 = {{ source.inW }}u;
20
+ const KH: i32 = {{ source.kernelH }};
21
+ const KW: i32 = {{ source.kernelW }};
22
+ const STRIDE_H: i32 = {{ source.strideH }};
23
+ const STRIDE_W: i32 = {{ source.strideW }};
24
+ const PAD_H: i32 = {{ source.padH }};
25
+ const PAD_W: i32 = {{ source.padW }};
26
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
27
+
28
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
29
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
30
+ let index = gid.x + gid.y * nwg.x * WG;
31
+ if (index >= COUNT) { return; }
32
+ let ow = index % OUT_W;
33
+ var t = index / OUT_W;
34
+ let oh = t % OUT_H;
35
+ t /= OUT_H;
36
+ let oc = t % OUT_C;
37
+ let batch = t / OUT_C;
38
+ let group = oc / OUT_CPG;
39
+ let localOc = oc - group * OUT_CPG;
40
+ let inputStart = group * IN_CPG;
41
+ let rawHBase = i32(oh) + PAD_H;
42
+ let rawWBase = i32(ow) + PAD_W;
43
+ let khPhase = rawHBase % STRIDE_H;
44
+ let kwPhase = rawWBase % STRIDE_W;
45
+ {% if source.hasBias %}
46
+ var acc = f32(bias[oc]);
47
+ {% else %}
48
+ var acc = 0.0;
49
+ {% endif %}
50
+
51
+ for (var localIc = 0u; localIc < IN_CPG; localIc++) {
52
+ let ic = inputStart + localIc;
53
+ for (var kh = khPhase; kh < KH; kh += STRIDE_H) {
54
+ let rawH = rawHBase - kh;
55
+ if (rawH < 0) { continue; }
56
+ let ih = rawH / STRIDE_H;
57
+ if (ih >= IN_H) { continue; }
58
+ for (var kw = kwPhase; kw < KW; kw += STRIDE_W) {
59
+ let rawW = rawWBase - kw;
60
+ if (rawW < 0) { continue; }
61
+ let iw = rawW / STRIDE_W;
62
+ if (iw >= IN_W) { continue; }
63
+ let xIndex = ((batch * IN_C + ic) * IN_H_U + u32(ih)) * IN_W_U + u32(iw);
64
+ let wIndex = ((ic * OUT_CPG + localOc) * u32(KH) + u32(kh)) * u32(KW) + u32(kw);
65
+ acc += f32(x[xIndex]) * f32(w[wIndex]);
66
+ }
67
+ }
68
+ }
69
+ y[index] = {{ scalar }}(acc);
70
+ }
build/webgpu/conv-transpose2d-nchw.wgsl.jinja ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ {% macro tap(raw, coord, out, kernel, axis, indent) %}
35
+ {{ indent }}let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
36
+ {{ indent }}if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
37
+ {{ indent }}let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
38
+ {{ indent }}if ({{ coord }} < 0 || {{ coord }} >= i32(params.in{{ axis }})) { continue; }
39
+ {%- endmacro -%}
40
+ {% if usesF16 %}
41
+ // Preserve f32 accumulation for the deep channel-by-kernel sum: widen each
42
+ // f16 product and narrow only once at the output store.
43
+ enable f16;
44
+ {% endif %}
45
+ {{ env.wgsl.resourceDeclarations }}
46
+ {% if depthwisePointwiseVec4 %}
47
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
48
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
49
+ {{ flat_index_2d("vector_index", "", note="") }}
50
+ let base = vector_index * 4u;
51
+ if (base >= params.count) {
52
+ return;
53
+ }
54
+
55
+ // The contract guarantees a whole number of vec4s per NCHW channel plane,
56
+ // so these four adjacent values share one depthwise weight and never cross
57
+ // a channel boundary. This removes four copies of the generic coordinate,
58
+ // stride, padding, and kernel-loop arithmetic from the pointwise case.
59
+ let plane = params.outH * params.outW;
60
+ let oc = (base / plane) % params.outChannels;
61
+ let xv = vec4<f32>(x[base], x[base + 1u], x[base + 2u], x[base + 3u]);
62
+ let result = vec4<f32>(0.0) + xv * vec4<f32>(w[oc]);
63
+ y[base] = result.x;
64
+ y[base + 1u] = result.y;
65
+ y[base + 2u] = result.z;
66
+ y[base + 3u] = result.w;
67
+ }
68
+ {% else %}
69
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
70
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
71
+ {{ flat_index_2d("index", note="device-axis") }}
72
+
73
+ let ow = index % params.outW;
74
+ var t = index / params.outW;
75
+ let oh = t % params.outH;
76
+ t = t / params.outH;
77
+ let oc = t % params.outChannels;
78
+ let batch = t / params.outChannels;
79
+ {% if grouped %}
80
+
81
+ let group_index = oc / params.outChannelsPerGroup;
82
+ let local_oc = oc - group_index * params.outChannelsPerGroup;
83
+ let input_start = group_index * params.inChannelsPerGroup;
84
+
85
+ {% endif %}
86
+ var acc = 0.0;
87
+ {% if hasBias %}
88
+ {% if usesF16 %}
89
+ acc = f32(bias[oc]);
90
+ {% else %}
91
+ acc = bias[oc];
92
+ {% endif %}
93
+ {% endif %}
94
+ {% if grouped %}
95
+ for (var local_ic = 0u; local_ic < params.inChannelsPerGroup; local_ic = local_ic + 1u) {
96
+ let ic = input_start + local_ic;
97
+ {% else %}
98
+ for (var ic = 0u; ic < params.inChannels; ic = ic + 1u) {
99
+ {% endif %}
100
+ for (var kh = 0u; kh < params.kernelH; kh = kh + 1u) {
101
+ {{ tap("raw_h", "ih", "oh", "kh", "H", " ") }}
102
+ for (var kw = 0u; kw < params.kernelW; kw = kw + 1u) {
103
+ {{ tap("raw_w", "iw", "ow", "kw", "W", " ") }}
104
+ let x_index = ((batch * params.inChannels + ic) * params.inH + u32(ih)) * params.inW + u32(iw);
105
+ {% if grouped %}
106
+ let w_index = ((ic * params.outChannelsPerGroup + local_oc) * params.kernelH + kh) * params.kernelW + kw;
107
+ {% else %}
108
+ let w_index = ((ic * params.outChannels + oc) * params.kernelH + kh) * params.kernelW + kw;
109
+ {% endif %}
110
+ {% if usesF16 %}
111
+ acc = acc + f32(x[x_index]) * f32(w[w_index]);
112
+ {% else %}
113
+ acc = acc + x[x_index] * w[w_index];
114
+ {% endif %}
115
+ }
116
+ }
117
+ }
118
+ {% if usesF16 %}
119
+ y[index] = f16(acc);
120
+ {% else %}
121
+ y[index] = acc;
122
+ {% endif %}
123
+ }
124
+ {% endif %}
build/webgpu/conv-transpose2d-phase-gemm-tiled.wgsl.jinja ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Fused k4s2 ConvTranspose for the common exact-2x upsampling geometry.
2
+ //
3
+ // A stride-two output has four independent parity phases. Within one phase,
4
+ // each output pixel receives exactly four kernel taps per input channel, so the
5
+ // operation is an implicit GEMM:
6
+ //
7
+ // Y_phase[OC, IH*IW] = W_phase[OC, IC*4] * X_neighborhood[IC*4, IH*IW]
8
+ //
9
+ // Materializing the conventional [OC*16, IH*IW] columns tensor costs one large
10
+ // scratch write, one large scratch read, and a second col2im dispatch. This
11
+ // kernel gathers the four shifted input samples while cooperatively loading the
12
+ // GEMM B tile, then stores each register tile directly to its interleaved output
13
+ // phase. Boundary samples are represented by zero in shared memory.
14
+ enable f16;
15
+
16
+ {{ env.wgsl.resourceDeclarations }}
17
+
18
+ const IN_C: u32 = {{ source.inChannels }}u;
19
+ const IN_H: u32 = {{ source.inHeight }}u;
20
+ const IN_W: u32 = {{ source.inWidth }}u;
21
+ const OUT_C: u32 = {{ source.outChannels }}u;
22
+ const OUT_H: u32 = {{ source.outHeight }}u;
23
+ const OUT_W: u32 = {{ source.outWidth }}u;
24
+ const BATCH: u32 = {{ source.batch }}u;
25
+ const PAD_H: u32 = {{ source.padH }}u;
26
+ const PAD_W: u32 = {{ source.padW }}u;
27
+
28
+ const PHASES: u32 = 4u;
29
+ const TAPS_PER_PHASE: u32 = 4u;
30
+ const PHASE_EDGE: u32 = 2u;
31
+ const N: u32 = IN_H * IN_W;
32
+ const K: u32 = IN_C * TAPS_PER_PHASE;
33
+
34
+ const M_TILE: u32 = {{ source.mTile }}u;
35
+ const N_TILE: u32 = {{ source.nTile }}u;
36
+ const BK: u32 = {{ source.kTile }}u;
37
+ const THREAD_ROWS: u32 = {{ source.threadRows }}u;
38
+ const THREAD_COLUMNS: u32 = {{ source.threadColumns }}u;
39
+ const WG_EDGE: u32 = {{ source.workgroupEdge }}u;
40
+ const WG_SIZE: u32 = WG_EDGE * WG_EDGE;
41
+
42
+ var<workgroup> tileA: array<array<f16, BK>, M_TILE>;
43
+ var<workgroup> tileB: array<array<f16, N_TILE>, BK>;
44
+
45
+ fn store_output(batch: u32, phase: u32, oc: u32, n: u32, value: f32) {
46
+ if (oc >= OUT_C || n >= N) {
47
+ return;
48
+ }
49
+ let phaseH = phase / PHASE_EDGE;
50
+ let phaseW = phase % PHASE_EDGE;
51
+ let oh = (n / IN_W) * PHASE_EDGE + phaseH;
52
+ let ow = (n % IN_W) * PHASE_EDGE + phaseW;
53
+ {% if source.hasBias %}
54
+ let result = value + f32(bias[oc]);
55
+ {% else %}
56
+ let result = value;
57
+ {% endif %}
58
+ y[((batch * OUT_C + oc) * OUT_H + oh) * OUT_W + ow] = f16(result);
59
+ }
60
+
61
+ @compute @workgroup_size({{ source.workgroupEdge }}, {{ source.workgroupEdge }}, 1)
62
+ fn main(
63
+ @builtin(workgroup_id) wg: vec3<u32>,
64
+ @builtin(local_invocation_id) lid: vec3<u32>
65
+ ) {
66
+ let phase = wg.z % PHASES;
67
+ let batch = wg.z / PHASES;
68
+ if (batch >= BATCH) {
69
+ return;
70
+ }
71
+
72
+ let phaseH = phase / PHASE_EDGE;
73
+ let phaseW = phase % PHASE_EDGE;
74
+ let kernelPhaseH = (phaseH + PAD_H) % PHASE_EDGE;
75
+ let kernelPhaseW = (phaseW + PAD_W) % PHASE_EDGE;
76
+ let inputShiftH = (phaseH + PAD_H - kernelPhaseH) / PHASE_EDGE;
77
+ let inputShiftW = (phaseW + PAD_W - kernelPhaseW) / PHASE_EDGE;
78
+
79
+ let mBase = wg.y * M_TILE;
80
+ let nBase = wg.x * N_TILE;
81
+ let lane = lid.y * WG_EDGE + lid.x;
82
+ let aRow = lid.y * THREAD_ROWS;
83
+ let bCol = lid.x * THREAD_COLUMNS;
84
+
85
+ {% for r in range(source.threadRows) %}
86
+ var acc{{ r }} = vec4<f32>(0.0);
87
+ {% endfor %}
88
+
89
+ for (var kBase = 0u; kBase < K; kBase += BK) {
90
+ // The square A/B tiles contain the same number of values, so one loop
91
+ // publishes a coalesced share of both operands per lane.
92
+ for (var linear = lane; linear < M_TILE * BK; linear += WG_SIZE) {
93
+ let ar = linear / BK;
94
+ let ac = linear % BK;
95
+ let oc = mBase + ar;
96
+ let k = kBase + ac;
97
+ if (oc < OUT_C && k < K) {
98
+ let ic = k / TAPS_PER_PHASE;
99
+ let tap = k % TAPS_PER_PHASE;
100
+ let tapH = tap / PHASE_EDGE;
101
+ let tapW = tap % PHASE_EDGE;
102
+ let kh = kernelPhaseH + tapH * PHASE_EDGE;
103
+ let kw = kernelPhaseW + tapW * PHASE_EDGE;
104
+ let wIndex = ((ic * OUT_C + oc) * 4u + kh) * 4u + kw;
105
+ tileA[ar][ac] = w[wIndex];
106
+ } else {
107
+ tileA[ar][ac] = f16(0.0);
108
+ }
109
+
110
+ let br = linear / N_TILE;
111
+ let bc = linear % N_TILE;
112
+ let bk = kBase + br;
113
+ let n = nBase + bc;
114
+ if (bk < K && n < N) {
115
+ let ic = bk / TAPS_PER_PHASE;
116
+ let tap = bk % TAPS_PER_PHASE;
117
+ let tapH = tap / PHASE_EDGE;
118
+ let tapW = tap % PHASE_EDGE;
119
+ let ih = i32(n / IN_W) + i32(inputShiftH) - i32(tapH);
120
+ let iw = i32(n % IN_W) + i32(inputShiftW) - i32(tapW);
121
+ if (ih >= 0 && ih < i32(IN_H) && iw >= 0 && iw < i32(IN_W)) {
122
+ let xIndex = ((batch * IN_C + ic) * IN_H + u32(ih)) * IN_W + u32(iw);
123
+ tileB[br][bc] = x[xIndex];
124
+ } else {
125
+ tileB[br][bc] = f16(0.0);
126
+ }
127
+ } else {
128
+ tileB[br][bc] = f16(0.0);
129
+ }
130
+ }
131
+ workgroupBarrier();
132
+
133
+ for (var kk = 0u; kk < BK; kk++) {
134
+ let bv = vec4<f32>(
135
+ f32(tileB[kk][bCol]),
136
+ f32(tileB[kk][bCol + 1u]),
137
+ f32(tileB[kk][bCol + 2u]),
138
+ f32(tileB[kk][bCol + 3u])
139
+ );
140
+ {% for r in range(source.threadRows) %}
141
+ acc{{ r }} += vec4<f32>(f32(tileA[aRow + {{ r }}u][kk])) * bv;
142
+ {% endfor %}
143
+ }
144
+ workgroupBarrier();
145
+ }
146
+
147
+ let oc0 = mBase + aRow;
148
+ let n0 = nBase + bCol;
149
+ {% for r in range(source.threadRows) %}
150
+ store_output(batch, phase, oc0 + {{ r }}u, n0, acc{{ r }}.x);
151
+ store_output(batch, phase, oc0 + {{ r }}u, n0 + 1u, acc{{ r }}.y);
152
+ store_output(batch, phase, oc0 + {{ r }}u, n0 + 2u, acc{{ r }}.z);
153
+ store_output(batch, phase, oc0 + {{ r }}u, n0 + 3u, acc{{ r }}.w);
154
+ {% endfor %}
155
+ }
build/webgpu/conv-transpose2d-weight-reorder.wgsl.jinja ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ // ConvTranspose weight reorder for the im2col-GEMM path.
35
+ //
36
+ // The ONNX ConvTranspose weight is laid out [IC, OC, KH, KW]. The GEMM that
37
+ // produces the cols matrix (cols[OC*KH*KW, IH*IW] = Wt @ X) needs its A operand
38
+ // as a row-major [M = OC*KH*KW, K = IC] matrix, i.e. the IC dimension contracted
39
+ // to the inner (column) axis. This pass gathers W into that transposed layout.
40
+ {% if usesF16 %}
41
+ enable f16;
42
+
43
+ {% endif %}
44
+ {{ env.wgsl.resourceDeclarations }}
45
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
46
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
47
+ {{ flat_index_2d("index", note="") }}
48
+ // Wt[m, ic] with m = (oc * KH + kh) * KW + kw, contracted dim ic.
49
+ let ic = index % params.inChannels;
50
+ var m = index / params.inChannels;
51
+ let kw = m % params.kernelW;
52
+ m = m / params.kernelW;
53
+ let kh = m % params.kernelH;
54
+ let oc = m / params.kernelH;
55
+ let w_index = ((ic * params.outChannels + oc) * params.kernelH + kh) * params.kernelW + kw;
56
+ wt[index] = wsrc[w_index];
57
+ }
build/webgpu/conv-transpose3d-col2im.wgsl.jinja ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ {% macro tap(raw, coord, out, kernel, axis, indent) %}
35
+ {{ indent }}let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
36
+ {{ indent }}if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
37
+ {{ indent }}let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
38
+ {{ indent }}if ({{ coord }} < 0 || {{ coord }} >= i32(params.in{{ axis }})) { continue; }
39
+ {%- endmacro -%}
40
+ // Output-stationary rank-5 col2im gather for ConvTranspose-as-GEMM.
41
+ //
42
+ // GEMM has already contracted input channels into
43
+ // cols[oc,kd,kh,kw,id,ih,iw]. This pass gathers only the spatial taps that land
44
+ // on each output coordinate, retaining deterministic f32 accumulation and the
45
+ // full stride/dilation/padding contract without atomics.
46
+ {{ env.wgsl.resourceDeclarations }}
47
+
48
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
49
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
50
+ @builtin(num_workgroups) nwg: vec3<u32>) {
51
+ {{ flat_index_2d("index", note="device-axis") }}
52
+
53
+ let ow = index % params.outW;
54
+ var t = index / params.outW;
55
+ let oh = t % params.outH;
56
+ t /= params.outH;
57
+ let od = t % params.outD;
58
+ t /= params.outD;
59
+ let oc = t % params.outChannels;
60
+ let batch = t / params.outChannels;
61
+
62
+ let inputPlane = params.inH * params.inW;
63
+ let inputVolume = params.inD * inputPlane;
64
+ let kernelPlane = params.kernelH * params.kernelW;
65
+ let kernelVolume = params.kernelD * kernelPlane;
66
+ let colsBatchStride = params.outChannels * kernelVolume * inputVolume;
67
+ let batchBase = batch * colsBatchStride;
68
+
69
+ var acc = 0.0;
70
+ {% if hasBias %}
71
+ acc = f32(bias[oc]);
72
+ {% endif %}
73
+ for (var kd = 0u; kd < params.kernelD; kd += 1u) {
74
+ {{ tap("rawD", "id", "od", "kd", "D", " ") }}
75
+ for (var kh = 0u; kh < params.kernelH; kh += 1u) {
76
+ {{ tap("rawH", "ih", "oh", "kh", "H", " ") }}
77
+ for (var kw = 0u; kw < params.kernelW; kw += 1u) {
78
+ {{ tap("rawW", "iw", "ow", "kw", "W", " ") }}
79
+ let matrixRow = ((oc * params.kernelD + kd) * params.kernelH + kh) *
80
+ params.kernelW + kw;
81
+ let matrixColumn = (u32(id) * params.inH + u32(ih)) * params.inW +
82
+ u32(iw);
83
+ acc += f32(cols[batchBase + matrixRow * inputVolume + matrixColumn]);
84
+ }
85
+ }
86
+ }
87
+ y[index] = {{ T }}(acc);
88
+ }
build/webgpu/conv-transpose3d-ncdhw.wgsl.jinja ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ {% macro tap(raw, coord, out, kernel, axis, indent) %}
35
+ {{ indent }}let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
36
+ {{ indent }}if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
37
+ {{ indent }}let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
38
+ {{ indent }}if ({{ coord }} < 0 || {{ coord }} >= i32(params.in{{ axis }})) { continue; }
39
+ {%- endmacro -%}
40
+ {{ env.wgsl.resourceDeclarations }}
41
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
42
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
43
+ {{ flat_index_2d("index", note="device-axis") }}
44
+
45
+ let ow = index % params.outW;
46
+ var t = index / params.outW;
47
+ let oh = t % params.outH;
48
+ t = t / params.outH;
49
+ let od = t % params.outD;
50
+ t = t / params.outD;
51
+ let oc = t % params.outChannels;
52
+ let batch = t / params.outChannels;
53
+
54
+ var acc = 0.0;
55
+ {% if hasBias %}
56
+ acc = bias[oc];
57
+ {% endif %}
58
+ for (var ic = 0u; ic < params.inChannels; ic = ic + 1u) {
59
+ for (var kd = 0u; kd < params.kernelD; kd = kd + 1u) {
60
+ {{ tap("raw_d", "id", "od", "kd", "D", " ") }}
61
+ for (var kh = 0u; kh < params.kernelH; kh = kh + 1u) {
62
+ {{ tap("raw_h", "ih", "oh", "kh", "H", " ") }}
63
+ for (var kw = 0u; kw < params.kernelW; kw = kw + 1u) {
64
+ {{ tap("raw_w", "iw", "ow", "kw", "W", " ") }}
65
+ let x_index = (((batch * params.inChannels + ic) * params.inD + u32(id)) * params.inH + u32(ih)) * params.inW + u32(iw);
66
+ let w_index = (((ic * params.outChannels + oc) * params.kernelD + kd) * params.kernelH + kh) * params.kernelW + kw;
67
+ acc = acc + x[x_index] * w[w_index];
68
+ }
69
+ }
70
+ }
71
+ }
72
+ y[index] = acc;
73
+ }
build/webgpu/conv-transpose3d-weight-reorder.wgsl.jinja ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ // Reorder ONNX ConvTranspose weights from [IC, OC, KD, KH, KW] into the
35
+ // row-major GEMM operand [OC*KD*KH*KW, IC]. All extents come from the rank-5
36
+ // tensor contract through the uniform parameters.
37
+ {{ env.wgsl.resourceDeclarations }}
38
+
39
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
40
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
41
+ @builtin(num_workgroups) nwg: vec3<u32>) {
42
+ {{ flat_index_2d("index", note="device-axis") }}
43
+
44
+ let ic = index % params.inChannels;
45
+ var m = index / params.inChannels;
46
+ let kw = m % params.kernelW;
47
+ m /= params.kernelW;
48
+ let kh = m % params.kernelH;
49
+ m /= params.kernelH;
50
+ let kd = m % params.kernelD;
51
+ let oc = m / params.kernelD;
52
+
53
+ let sourceIndex = (((ic * params.outChannels + oc) * params.kernelD + kd) *
54
+ params.kernelH + kh) * params.kernelW + kw;
55
+ wt[index] = wsrc[sourceIndex];
56
+ }
build/webgpu/manifest.json ADDED
The diff for this file is too large to render. See raw diff
 
build/webgpu/metadata.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.ConvTranspose",
3
+ "id": "_ai_onnx_convtranspose_webgpu_a7764a8",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "K/3QCWt4Y0n8hE7RNLjJqIdqSDzkpSnjvOzP4cYpsgU=",
11
+ "conv-1x1-gemm-tiled-reg.wgsl.jinja": "hwyCsCvuGx3JXDj/OC0mVEhBqvrqpR5KqTWdUUdInZY=",
12
+ "conv-1x1-gemm-tiled.wgsl.jinja": "VeRKrzHV+9CvWm0Lrsgpt5C98Gkz5MubGVEnYApQhdk=",
13
+ "conv-1x1-subgroup-matrix.wgsl.jinja": "unfOK0d/deJ/p0Mzv12Z49OZHoZ89yHE8tQvQGTpOhI=",
14
+ "conv-transpose-empty-input.wgsl.jinja": "242WjbMQKXP51v/XyR8R/FORSaUohUJy0qMTCgcyd7Y=",
15
+ "conv-transpose1d-ncl.wgsl.jinja": "QV+rvtffoKy5nFid4T0vKcLbiRGeCVef87otPiRZs3Q=",
16
+ "conv-transpose1d-phase-tiled.wgsl.jinja": "nN+V8lCFxP3Omu1vLhUqZMpCt7Gb/LLQAVhPGntzcxk=",
17
+ "conv-transpose2d-col2im.wgsl.jinja": "TlUk+O29OeiRgVi+sCzFtm/pvJjQ4Pxs727+pS7Fg4c=",
18
+ "conv-transpose2d-grouped-stride-phase.wgsl.jinja": "VW/769KFvrA4NywQuNw9uawTGodB/iKUJdAE9vszJx8=",
19
+ "conv-transpose2d-nchw.wgsl.jinja": "VB/VsdNoFh6eVJJ9x1rHjIJYG1INTVMGO145elCU7ow=",
20
+ "conv-transpose2d-phase-gemm-tiled.wgsl.jinja": "uF07bIUt+NQdzc5Jq6IDLUDn51h6GASE++9HYbPGZms=",
21
+ "conv-transpose2d-weight-reorder.wgsl.jinja": "Gt8Qw4oUFXATVT33pGW7hvEIZ+VaYPrN70sFkFPNfGs=",
22
+ "conv-transpose3d-col2im.wgsl.jinja": "hKL2pQTqi81DFVEVuUZd5KNoikhK5Fcq+e6xPSBpM60=",
23
+ "conv-transpose3d-ncdhw.wgsl.jinja": "F9SYKafHOobKRTCa9yE/dUD4SCU/Fzgo+EdQdiAssCg=",
24
+ "conv-transpose3d-weight-reorder.wgsl.jinja": "CghFqX1j85v2ZY6DjTGhO2B+Es/lERkUB+QR+KoTzNA=",
25
+ "manifest.json": "X8AIY7imVWPKlvjr6NpBMaBGsONv+0kH68l2z0fwYZM=",
26
+ "test.json": "xRSBVV55KeohZ8pTZbAS5n3B0wwxBev9MxJbaE6/wF4="
27
+ }
28
+ },
29
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
30
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.ConvTranspose" }
31
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,1856 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.ConvTranspose",
3
+ "fixtureArrays": {
4
+ "grouped_two_groups_no_bias_input_x": [1, 2, 3, 4, -1, -2, 0.5, 1.5, 2, -3, 4, -5, 0.25, -0.5, 0.75, -1],
5
+ "grouped_two_groups_no_bias_input_w": [1, 0, 0.5, -1, -0.25, 0.75, 1.5, 0, 0.5, -0.5, 1, 0.25, 1.25, 0, -0.75, 0.5, -1, 0.5, 0.25, 1, 0.75, -1.25, 0, 0.5, 0.25, 1.5, -0.5, 0, -0.75, 0.5, 1, -1],
6
+ "ort_default_strides_dilations_multi_channel_input_x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
7
+ "onnx_backend_convtranspose_input_w": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
8
+ "f16_1d_nobias_input_x": [0.1, 0.5, -0.25, 0.75, 1, -0.5, 0.25, -1, 0.5, -0.75, 1.25, -0.25, 0, 0.5, -1, 0.75],
9
+ "f16_1d_nobias_input_w": [0.5, -0.25, 1, 0.75, -0.5, 0.25, -1, 0.5, 0.125, 1, -0.5, 0.25, 0.5, 0.75, -0.25, -0.75, 0.5, 1]
10
+ },
11
+ "cases": [
12
+ {
13
+ "name": "f32_subnormal_times_large_scale_1d_gpu_gap",
14
+ "provenance": {
15
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
16
+ "test": "ConvTransposeTest",
17
+ "notes": "A 1D transposed convolution with a pointwise large finite weight should preserve subnormal input signal as normal finite output."
18
+ },
19
+ "attrs": {},
20
+ "inputs": {
21
+ "x": {
22
+ "dtype": "float32",
23
+ "shape": [1, 1, 4],
24
+ "data": { "kind": "values", "values": [1e-39, -1e-39, 1e-38, -1e-38] }
25
+ },
26
+ "w": { "dtype": "float32", "shape": [1, 1, 1], "data": { "kind": "constant", "value": 1e+38 } }
27
+ },
28
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 4], "tolerance": 0.000001 } },
29
+ "skipGpu": {
30
+ "category": "permanent",
31
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: Metal flushes f32 subnormals to zero (FTZ); subnormal-magnitude values cannot survive the GPU arithmetic path."
32
+ }
33
+ },
34
+ {
35
+ "name": "f32_subnormal_times_large_scale_2d_gpu_gap",
36
+ "provenance": {
37
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
38
+ "test": "ConvTransposeTest",
39
+ "notes": "A 2D transposed convolution with a pointwise large finite weight should preserve subnormal input signal as normal finite output."
40
+ },
41
+ "attrs": {},
42
+ "inputs": {
43
+ "x": {
44
+ "dtype": "float32",
45
+ "shape": [1, 1, 2, 2],
46
+ "data": { "kind": "values", "values": [1e-39, -1e-39, 1e-38, -1e-38] }
47
+ },
48
+ "w": { "dtype": "float32", "shape": [1, 1, 1, 1], "data": { "kind": "constant", "value": 1e+38 } }
49
+ },
50
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 2, 2], "tolerance": 0.000001 } },
51
+ "skipGpu": {
52
+ "category": "permanent",
53
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: Metal flushes f32 subnormals to zero (FTZ); subnormal-magnitude values cannot survive the GPU arithmetic path."
54
+ }
55
+ },
56
+ {
57
+ "name": "f32_subnormal_times_large_scale_3d_gpu_gap",
58
+ "provenance": {
59
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
60
+ "test": "ConvTransposeTest",
61
+ "notes": "A 3D transposed convolution with a pointwise large finite weight should preserve subnormal input signal as normal finite output."
62
+ },
63
+ "attrs": {},
64
+ "inputs": {
65
+ "x": {
66
+ "dtype": "float32",
67
+ "shape": [1, 1, 2, 2, 2],
68
+ "data": { "kind": "values", "values": [1e-39, -1e-39, 2e-39, -2e-39, 5e-39, -5e-39, 1e-38, -1e-38] }
69
+ },
70
+ "w": { "dtype": "float32", "shape": [1, 1, 1, 1, 1], "data": { "kind": "constant", "value": 1e+38 } }
71
+ },
72
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 2, 2, 2], "tolerance": 0.000001 } },
73
+ "skipGpu": {
74
+ "category": "permanent",
75
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: Metal flushes f32 subnormals to zero (FTZ); subnormal-magnitude values cannot survive the GPU arithmetic path."
76
+ }
77
+ },
78
+ {
79
+ "name": "dispatch_cliff_ncl1d",
80
+ "attrs": {},
81
+ "inputs": {
82
+ "x": {
83
+ "dtype": "float32",
84
+ "shape": [1, 1, 16776961],
85
+ "data": { "kind": "cycle", "values": [0.5, -0.25, 1.0, -0.75] }
86
+ },
87
+ "w": { "dtype": "float32", "shape": [1, 1, 1], "data": { "kind": "constant", "value": 2.0 } }
88
+ },
89
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 16776961], "tolerance": 0.00001 } }
90
+ },
91
+ {
92
+ "name": "phase_tiled_1d_k4s2_batch_channel_position_tails",
93
+ "provenance": {
94
+ "notes": "Exercises the neural-audio stride-2 phase decomposition with non-tile-aligned input channels, output channels, positions, and batch > 1."
95
+ },
96
+ "attrs": { "strides": [2], "pads": [1, 1] },
97
+ "inputs": {
98
+ "x": {
99
+ "dtype": "float32",
100
+ "shape": [2, 37, 35],
101
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.17, "scale": 0.2 }
102
+ },
103
+ "w": {
104
+ "dtype": "float32",
105
+ "shape": [37, 13, 4],
106
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.13, "scale": 0.1 }
107
+ }
108
+ },
109
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 13, 70], "tolerance": 0.00002 } }
110
+ },
111
+ {
112
+ "name": "phase_tiled_1d_k4s2_bias_tails",
113
+ "provenance": { "notes": "Bias companion for the phase-tiled neural-audio path with channel and position tails." },
114
+ "attrs": { "strides": [2], "pads": [1, 1] },
115
+ "inputs": {
116
+ "x": {
117
+ "dtype": "float32",
118
+ "shape": [1, 17, 33],
119
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.23, "scale": 0.2 }
120
+ },
121
+ "w": {
122
+ "dtype": "float32",
123
+ "shape": [17, 19, 4],
124
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.29, "scale": 0.1 }
125
+ },
126
+ "bias": {
127
+ "dtype": "float32",
128
+ "shape": [19],
129
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.31, "scale": 0.05 }
130
+ }
131
+ },
132
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 19, 66], "tolerance": 0.00002 } }
133
+ },
134
+ {
135
+ "name": "phase_tiled_1d_register_tile_channel_position_tails",
136
+ "provenance": {
137
+ "notes": "Crosses the occupancy-derived two-output register-tile threshold while retaining input-channel, output-channel, and position tails."
138
+ },
139
+ "attrs": { "strides": [2], "pads": [1, 1] },
140
+ "inputs": {
141
+ "x": {
142
+ "dtype": "float32",
143
+ "shape": [1, 17, 1025],
144
+ "data": { "kind": "fillFloat32", "sinStep": 0.09, "cosStep": 0.21, "scale": 0.2 }
145
+ },
146
+ "w": {
147
+ "dtype": "float32",
148
+ "shape": [17, 65, 4],
149
+ "data": { "kind": "fillFloat32", "sinStep": 0.15, "cosStep": 0.27, "scale": 0.1 }
150
+ }
151
+ },
152
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 65, 2050], "tolerance": 0.00002 } }
153
+ },
154
+ {
155
+ "name": "nchw_no_bias",
156
+ "attrs": { "strides": [2, 1], "pads": [1, 0, 1, 0] },
157
+ "inputs": {
158
+ "x": {
159
+ "dtype": "float32",
160
+ "shape": [1, 2, 3, 4],
161
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.17 }
162
+ },
163
+ "w": {
164
+ "dtype": "float32",
165
+ "shape": [2, 2, 3, 2],
166
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.13 }
167
+ }
168
+ },
169
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 5, 5], "tolerance": 0.00001 } }
170
+ },
171
+ {
172
+ "name": "nchw_bias",
173
+ "attrs": { "strides": [2, 1], "pads": [1, 0, 1, 0] },
174
+ "inputs": {
175
+ "x": {
176
+ "dtype": "float32",
177
+ "shape": [1, 2, 3, 4],
178
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.17 }
179
+ },
180
+ "w": {
181
+ "dtype": "float32",
182
+ "shape": [2, 2, 3, 2],
183
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.13 }
184
+ },
185
+ "bias": {
186
+ "dtype": "float32",
187
+ "shape": [2],
188
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.29 }
189
+ }
190
+ },
191
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 5, 5], "tolerance": 0.00001 } }
192
+ },
193
+ {
194
+ "name": "non_vector_channels_no_bias",
195
+ "attrs": {},
196
+ "inputs": {
197
+ "x": {
198
+ "dtype": "float32",
199
+ "shape": [1, 3, 2, 2],
200
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.17 }
201
+ },
202
+ "w": {
203
+ "dtype": "float32",
204
+ "shape": [3, 5, 2, 2],
205
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23 }
206
+ }
207
+ },
208
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 5, 3, 3], "tolerance": 0.00001 } }
209
+ },
210
+ {
211
+ "name": "non_vector_channels_bias",
212
+ "attrs": {},
213
+ "inputs": {
214
+ "x": {
215
+ "dtype": "float32",
216
+ "shape": [1, 3, 2, 2],
217
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.17 }
218
+ },
219
+ "w": {
220
+ "dtype": "float32",
221
+ "shape": [3, 5, 2, 2],
222
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23 }
223
+ },
224
+ "bias": {
225
+ "dtype": "float32",
226
+ "shape": [5],
227
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.29 }
228
+ }
229
+ },
230
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 5, 3, 3], "tolerance": 0.00001 } }
231
+ },
232
+ {
233
+ "name": "non_vector_channels_bias_stride2_compact",
234
+ "provenance": {
235
+ "notes": "Compact companion for bench case conv-transpose-f32-non-vector-bias-b1c3m5-32x32-k3s2, preserving non-vec channel counts, bias, stride=2, and padded k3 output geometry."
236
+ },
237
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
238
+ "inputs": {
239
+ "x": {
240
+ "dtype": "float32",
241
+ "shape": [1, 3, 8, 8],
242
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.17, "scale": 0.2 }
243
+ },
244
+ "w": {
245
+ "dtype": "float32",
246
+ "shape": [3, 5, 3, 3],
247
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 0.12 }
248
+ },
249
+ "bias": {
250
+ "dtype": "float32",
251
+ "shape": [5],
252
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.29, "scale": 0.1 }
253
+ }
254
+ },
255
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 5, 15, 15], "tolerance": 0.00001 } }
256
+ },
257
+ {
258
+ "name": "output_padding_extends_shape",
259
+ "attrs": { "strides": [2, 2], "output_padding": [1, 1] },
260
+ "inputs": {
261
+ "x": { "dtype": "float32", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0] } },
262
+ "w": {
263
+ "dtype": "float32",
264
+ "shape": [1, 1, 2, 2],
265
+ "data": { "kind": "values", "values": [1.0, -1.0, 2.0, 0.5] }
266
+ }
267
+ },
268
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 5, 5], "tolerance": 0.000001 } }
269
+ },
270
+ {
271
+ "name": "dilated_kernel_spreads_taps",
272
+ "attrs": { "dilations": [2, 2] },
273
+ "inputs": {
274
+ "x": {
275
+ "dtype": "float32",
276
+ "shape": [1, 1, 3, 3],
277
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] }
278
+ },
279
+ "w": {
280
+ "dtype": "float32",
281
+ "shape": [1, 1, 2, 2],
282
+ "data": { "kind": "values", "values": [1.0, -2.0, 0.5, 3.0] }
283
+ }
284
+ },
285
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 5, 5], "tolerance": 0.000001 } }
286
+ },
287
+ {
288
+ "name": "grouped_two_groups_no_bias",
289
+ "attrs": { "group": 2 },
290
+ "inputs": {
291
+ "x": {
292
+ "dtype": "float32",
293
+ "shape": [1, 4, 2, 2],
294
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/grouped_two_groups_no_bias_input_x" } }
295
+ },
296
+ "w": {
297
+ "dtype": "float32",
298
+ "shape": [4, 2, 2, 2],
299
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/grouped_two_groups_no_bias_input_w" } }
300
+ }
301
+ },
302
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 3, 3], "tolerance": 0.000001 } }
303
+ },
304
+ {
305
+ "name": "f32_grouped2d_stride2_phase",
306
+ "provenance": {
307
+ "notes": "Compact grouped stride-2 fixture that exercises the phase-decomposed nchw2d_grouped_stride_phase fallback on every feature tier."
308
+ },
309
+ "attrs": { "group": 2, "strides": [2, 2], "pads": [1, 1, 1, 1] },
310
+ "inputs": {
311
+ "x": {
312
+ "dtype": "float32",
313
+ "shape": [1, 4, 3, 4],
314
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.17, "scale": 0.25 }
315
+ },
316
+ "w": {
317
+ "dtype": "float32",
318
+ "shape": [4, 3, 3, 3],
319
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.13, "scale": 0.2 }
320
+ }
321
+ },
322
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 6, 5, 7], "tolerance": 0.00001 } }
323
+ },
324
+ {
325
+ "name": "grouped_two_groups_bias",
326
+ "attrs": { "group": 2 },
327
+ "inputs": {
328
+ "x": {
329
+ "dtype": "float32",
330
+ "shape": [1, 4, 2, 2],
331
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/grouped_two_groups_no_bias_input_x" } }
332
+ },
333
+ "w": {
334
+ "dtype": "float32",
335
+ "shape": [4, 2, 2, 2],
336
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/grouped_two_groups_no_bias_input_w" } }
337
+ },
338
+ "bias": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.5, -1.0, 1.5, -0.25] } }
339
+ },
340
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 3, 3], "tolerance": 0.000001 } }
341
+ },
342
+ {
343
+ "name": "ort_zero_batch_dim",
344
+ "provenance": {
345
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
346
+ "test": "ConvTransposeTest.DimWithZero"
347
+ },
348
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1], "output_padding": [1, 1] },
349
+ "inputs": {
350
+ "x": { "dtype": "float32", "shape": [0, 1, 3, 3], "data": { "kind": "values", "values": [] } },
351
+ "w": {
352
+ "dtype": "float32",
353
+ "shape": [1, 1, 3, 3],
354
+ "data": {
355
+ "kind": "values",
356
+ "values": [-0.06230065, 0.37932432, -0.25388849, 0.33878803, 0.43709868, -0.22477469, 0.04118127, -0.44696793, 0.06373066]
357
+ }
358
+ }
359
+ },
360
+ "outputs": { "y": { "dtype": "float32", "shape": [0, 1, 6, 6], "tolerance": 0 } }
361
+ },
362
+ {
363
+ "name": "ort_outputpadding_strides2",
364
+ "provenance": {
365
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
366
+ "test": "ConvTransposeTest.ConvTranspose_2D_outputpadding_strides2"
367
+ },
368
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1], "output_padding": [1, 1] },
369
+ "inputs": {
370
+ "x": {
371
+ "dtype": "float32",
372
+ "shape": [1, 1, 3, 3],
373
+ "data": {
374
+ "kind": "values",
375
+ "values": [0.16857791, -0.15161794, 0.08540368, 0.1820628, -0.21746576, 0.08245695, 0.1431433, -0.43156421, 0.30591947]
376
+ }
377
+ },
378
+ "w": {
379
+ "dtype": "float32",
380
+ "shape": [1, 1, 3, 3],
381
+ "data": {
382
+ "kind": "values",
383
+ "values": [-0.06230065, 0.37932432, -0.25388849, 0.33878803, 0.43709868, -0.22477469, 0.04118127, -0.44696793, 0.06373066]
384
+ }
385
+ }
386
+ },
387
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 6, 6], "tolerance": 0.000001 } }
388
+ },
389
+ {
390
+ "name": "ort_outputpadding_strides2_f16",
391
+ "provenance": {
392
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
393
+ "test": "ConvTransposeTest.ConvTranspose_2D_outputpadding_strides2"
394
+ },
395
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1], "output_padding": [1, 1] },
396
+ "inputs": {
397
+ "x": {
398
+ "dtype": "float16",
399
+ "shape": [1, 1, 3, 3],
400
+ "data": {
401
+ "kind": "values",
402
+ "values": [0.16857791, -0.15161794, 0.08540368, 0.1820628, -0.21746576, 0.08245695, 0.1431433, -0.43156421, 0.30591947]
403
+ }
404
+ },
405
+ "w": {
406
+ "dtype": "float16",
407
+ "shape": [1, 1, 3, 3],
408
+ "data": {
409
+ "kind": "values",
410
+ "values": [-0.06230065, 0.37932432, -0.25388849, 0.33878803, 0.43709868, -0.22477469, 0.04118127, -0.44696793, 0.06373066]
411
+ }
412
+ }
413
+ },
414
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 1, 6, 6], "tolerance": 0.001 } }
415
+ },
416
+ {
417
+ "name": "ort_2d_c2_single_output_channel",
418
+ "provenance": {
419
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
420
+ "test": "ConvTransposeTest.ConvTranspose_2D_C2"
421
+ },
422
+ "attrs": {},
423
+ "inputs": {
424
+ "x": {
425
+ "dtype": "float32",
426
+ "shape": [1, 2, 3, 3],
427
+ "data": {
428
+ "kind": "values",
429
+ "values": [0.43, 0.42871707, 0.29552766, 0.17258859, 0.68087016, 0.7090254, 0.60937387, 0.58646585, 0.84525721, 0.47011843, 0.95854213, 0.3972888, 0.0585452, 0.1206734, 0.76727852, 0.46040912, 0.83495316, 0.02409773]
430
+ }
431
+ },
432
+ "w": {
433
+ "dtype": "float32",
434
+ "shape": [2, 1, 2, 2],
435
+ "data": {
436
+ "kind": "values",
437
+ "values": [0.25616416, 0.10246604, 0.08771133, 0.30770606, 0.84369617, 0.3010619, 0.44524362, 0.6056068]
438
+ }
439
+ }
440
+ },
441
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 4, 4], "tolerance": 0.000001 } }
442
+ },
443
+ {
444
+ "name": "ort_dilation2_kernel2_simple",
445
+ "provenance": {
446
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
447
+ "test": "ConvTransposeTest.ConvTranspose_2D_Dilation_1"
448
+ },
449
+ "attrs": { "dilations": [2, 2] },
450
+ "inputs": {
451
+ "x": {
452
+ "dtype": "float32",
453
+ "shape": [1, 1, 2, 2],
454
+ "data": { "kind": "values", "values": [11.0, 12.0, 21.0, 22.0] }
455
+ },
456
+ "w": { "dtype": "float32", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [1.0, 1.0, 1.0, 1.0] } }
457
+ },
458
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 4, 4], "tolerance": 0.000001 } }
459
+ },
460
+ {
461
+ "name": "ort_dilation3_kernel2_mixed_values",
462
+ "provenance": {
463
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
464
+ "test": "ConvTransposeTest.ConvTranspose_2D_Dilation_4"
465
+ },
466
+ "attrs": { "dilations": [3, 3] },
467
+ "inputs": {
468
+ "x": {
469
+ "dtype": "float32",
470
+ "shape": [1, 1, 3, 3],
471
+ "data": { "kind": "values", "values": [3.0, 8.0, 1.0, 9.0, 5.0, 7.0, 3.0, 2.0, 6.0] }
472
+ },
473
+ "w": { "dtype": "float32", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [7.0, 2.0, 1.0, 9.0] } }
474
+ },
475
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 6, 6], "tolerance": 0.000001 } }
476
+ },
477
+ {
478
+ "name": "ort_dilation3_asymmetric_pads_top_left",
479
+ "provenance": {
480
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
481
+ "test": "ConvTransposeTest.ConvTranspose_2D_Dilation_AsymmetricPads_1"
482
+ },
483
+ "attrs": { "pads": [2, 2, 1, 1], "dilations": [3, 3] },
484
+ "inputs": {
485
+ "x": {
486
+ "dtype": "float32",
487
+ "shape": [1, 1, 3, 3],
488
+ "data": { "kind": "values", "values": [3.0, 8.0, 1.0, 9.0, 5.0, 7.0, 3.0, 2.0, 6.0] }
489
+ },
490
+ "w": { "dtype": "float32", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [7.0, 2.0, 1.0, 9.0] } }
491
+ },
492
+ "outputs": {
493
+ "y": {
494
+ "dtype": "float32",
495
+ "shape": [1, 1, 3, 3],
496
+ "tolerance": 0.000001,
497
+ "data": { "kind": "values", "values": [42.0, 6.0, 4.0, 1.0, 27.0, 72.0, 7.0, 81.0, 45.0] }
498
+ }
499
+ }
500
+ },
501
+ {
502
+ "name": "ort_dilation3_asymmetric_pads_bottom_right",
503
+ "provenance": {
504
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
505
+ "test": "ConvTransposeTest.ConvTranspose_2D_Dilation_AsymmetricPads_2"
506
+ },
507
+ "attrs": { "pads": [1, 1, 2, 2], "dilations": [3, 3] },
508
+ "inputs": {
509
+ "x": {
510
+ "dtype": "float32",
511
+ "shape": [1, 1, 3, 3],
512
+ "data": { "kind": "values", "values": [3.0, 8.0, 1.0, 9.0, 5.0, 7.0, 3.0, 2.0, 6.0] }
513
+ },
514
+ "w": { "dtype": "float32", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [7.0, 2.0, 1.0, 9.0] } }
515
+ },
516
+ "outputs": {
517
+ "y": {
518
+ "dtype": "float32",
519
+ "shape": [1, 1, 3, 3],
520
+ "tolerance": 0.000001,
521
+ "data": { "kind": "values", "values": [35.0, 49.0, 18.0, 14.0, 42.0, 6.0, 8.0, 1.0, 27.0] }
522
+ }
523
+ }
524
+ },
525
+ {
526
+ "name": "ort_dilation3_asymmetric_pads_crop_top_left",
527
+ "provenance": {
528
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
529
+ "test": "ConvTransposeTest.ConvTranspose_2D_Dilation_AsymmetricPads_3"
530
+ },
531
+ "attrs": { "pads": [2, 2, 0, 0], "dilations": [3, 3] },
532
+ "inputs": {
533
+ "x": {
534
+ "dtype": "float32",
535
+ "shape": [1, 1, 3, 3],
536
+ "data": { "kind": "values", "values": [3.0, 8.0, 1.0, 9.0, 5.0, 7.0, 3.0, 2.0, 6.0] }
537
+ },
538
+ "w": { "dtype": "float32", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [7.0, 2.0, 1.0, 9.0] } }
539
+ },
540
+ "outputs": {
541
+ "y": {
542
+ "dtype": "float32",
543
+ "shape": [1, 1, 4, 4],
544
+ "tolerance": 0.000001,
545
+ "data": {
546
+ "kind": "values",
547
+ "values": [42.0, 6.0, 4.0, 12.0, 1.0, 27.0, 72.0, 9.0, 7.0, 81.0, 45.0, 63.0, 6.0, 27.0, 18.0, 54.0]
548
+ }
549
+ }
550
+ }
551
+ },
552
+ {
553
+ "name": "ort_dilation3_asymmetric_pads_crop_bottom_right",
554
+ "provenance": {
555
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
556
+ "test": "ConvTransposeTest.ConvTranspose_2D_Dilation_AsymmetricPads_4"
557
+ },
558
+ "attrs": { "pads": [0, 0, 2, 2], "dilations": [3, 3] },
559
+ "inputs": {
560
+ "x": {
561
+ "dtype": "float32",
562
+ "shape": [1, 1, 3, 3],
563
+ "data": { "kind": "values", "values": [3.0, 8.0, 1.0, 9.0, 5.0, 7.0, 3.0, 2.0, 6.0] }
564
+ },
565
+ "w": { "dtype": "float32", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [7.0, 2.0, 1.0, 9.0] } }
566
+ },
567
+ "outputs": {
568
+ "y": {
569
+ "dtype": "float32",
570
+ "shape": [1, 1, 4, 4],
571
+ "tolerance": 0.000001,
572
+ "data": {
573
+ "kind": "values",
574
+ "values": [21.0, 56.0, 7.0, 6.0, 63.0, 35.0, 49.0, 18.0, 21.0, 14.0, 42.0, 6.0, 3.0, 8.0, 1.0, 27.0]
575
+ }
576
+ }
577
+ }
578
+ },
579
+ {
580
+ "name": "ort_default_strides_dilations_multi_channel",
581
+ "provenance": {
582
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
583
+ "test": "ConvTransposeTest.ConvTranspose_DefaultStridesAndDilations"
584
+ },
585
+ "attrs": {},
586
+ "inputs": {
587
+ "x": {
588
+ "dtype": "float32",
589
+ "shape": [1, 2, 3, 3],
590
+ "data": {
591
+ "kind": "values",
592
+ "values": { "$ref": "#/fixtureArrays/ort_default_strides_dilations_multi_channel_input_x" }
593
+ }
594
+ },
595
+ "w": {
596
+ "dtype": "float32",
597
+ "shape": [2, 3, 2, 2],
598
+ "data": {
599
+ "kind": "values",
600
+ "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0]
601
+ }
602
+ }
603
+ },
604
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 3, 4, 4], "tolerance": 0.000001 } }
605
+ },
606
+ {
607
+ "name": "ort_stride_width2_dilation_width3",
608
+ "provenance": {
609
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
610
+ "test": "ConvTransposeTest.ConvTranspose_2D_NonDefaultStridesAndDilations"
611
+ },
612
+ "attrs": { "strides": [1, 2], "dilations": [1, 3] },
613
+ "inputs": {
614
+ "x": { "dtype": "float32", "shape": [1, 1, 1, 2], "data": { "kind": "values", "values": [1.0, 2.0] } },
615
+ "w": { "dtype": "float32", "shape": [1, 1, 1, 4], "data": { "kind": "values", "values": [1.0, 1.0, 1.0, 1.0] } }
616
+ },
617
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1, 12], "tolerance": 0.000001 } }
618
+ },
619
+ {
620
+ "name": "ort_stride_height2_dilation_height3",
621
+ "provenance": {
622
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
623
+ "test": "ConvTransposeTest.ConvTranspose_2D_NonDefaultStridesAndDilations_T"
624
+ },
625
+ "attrs": { "strides": [2, 1], "dilations": [3, 1] },
626
+ "inputs": {
627
+ "x": { "dtype": "float32", "shape": [1, 1, 2, 1], "data": { "kind": "values", "values": [1.0, 2.0] } },
628
+ "w": { "dtype": "float32", "shape": [1, 1, 4, 1], "data": { "kind": "values", "values": [1.0, 1.0, 1.0, 1.0] } }
629
+ },
630
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 12, 1], "tolerance": 0.000001 } }
631
+ },
632
+ {
633
+ "name": "ort_2d_bias_3x3_padded",
634
+ "provenance": {
635
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
636
+ "test": "ConvTransposeTest.ConvTranspose_2D_Bias_1"
637
+ },
638
+ "attrs": { "pads": [1, 1, 1, 1] },
639
+ "inputs": {
640
+ "x": {
641
+ "dtype": "float32",
642
+ "shape": [1, 1, 5, 5],
643
+ "data": {
644
+ "kind": "values",
645
+ "values": [0.22572887, -0.07105902, -0.40399021, -0.14461157, 0.05367219, -0.08353302, 0.41023391, 0.42745841, -0.3769345, -0.42057109, -0.1372498, 0.05485916, 0.34602994, -0.06402895, -0.06000063, 0.07891446, -0.09410021, 0.26251942, -0.11043271, 0.47966552, 0.34682763, -0.04511502, 0.22414422, 0.24618894, -0.21480265]
646
+ }
647
+ },
648
+ "w": {
649
+ "dtype": "float32",
650
+ "shape": [1, 1, 3, 3],
651
+ "data": {
652
+ "kind": "values",
653
+ "values": [-0.0962126, 0.19827795, 0.03667754, 0.36756599, -0.01076147, -0.11781135, -0.11574665, -0.38404959, 0.44403327]
654
+ }
655
+ },
656
+ "bias": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.04676145] } }
657
+ },
658
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 5, 5], "tolerance": 0.000001 } }
659
+ },
660
+ {
661
+ "name": "ort_2d_bias_2x2_no_pad",
662
+ "provenance": {
663
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
664
+ "test": "ConvTransposeTest.ConvTranspose_2D_Bias_2"
665
+ },
666
+ "attrs": {},
667
+ "inputs": {
668
+ "x": {
669
+ "dtype": "float32",
670
+ "shape": [1, 1, 7, 7],
671
+ "data": {
672
+ "kind": "values",
673
+ "values": [0.01270282, 0.09657472, -0.36909008, -0.08085269, 0.0242992, 0.40873009, -0.46927932, 0.34412372, -0.39574206, 0.26234281, 0.27352369, -0.22265741, 0.43270493, -0.24710381, -0.03418651, -0.04413456, -0.16414353, 0.3158558, 0.1087395, -0.38577938, -0.38986659, -0.09614426, 0.17591673, 0.40140027, -0.0869683, -0.47193506, -0.05010766, 0.29325962, 0.22680271, -0.0793834, -0.36764491, 0.20451134, 0.46361887, -0.12190259, 0.03413916, 0.12307656, 0.28569579, -0.392129, 0.17179191, 0.27161086, -0.12766263, 0.1371125, 0.28137422, -0.39899838, 0.23824286, -0.19693244, 0.32956779, 0.46209556, -0.46913007]
674
+ }
675
+ },
676
+ "w": {
677
+ "dtype": "float32",
678
+ "shape": [1, 1, 2, 2],
679
+ "data": { "kind": "values", "values": [-0.34922412, 0.1114341, -0.01778314, 0.46861196] }
680
+ },
681
+ "bias": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.17402864] } }
682
+ },
683
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 8, 8], "tolerance": 0.00001 } }
684
+ },
685
+ {
686
+ "name": "ort_1d_mixed_channels",
687
+ "provenance": {
688
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
689
+ "test": "ConvTransposeTest.ConvTranspose_1D"
690
+ },
691
+ "attrs": {},
692
+ "inputs": {
693
+ "x": {
694
+ "dtype": "float32",
695
+ "shape": [1, 2, 3],
696
+ "data": { "kind": "values", "values": [0.1, 1.0, 2.0, 3.0, 4.0, 5.0] }
697
+ },
698
+ "w": {
699
+ "dtype": "float32",
700
+ "shape": [2, 2, 3],
701
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0] }
702
+ }
703
+ },
704
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 5], "tolerance": 0.000001 } }
705
+ },
706
+ {
707
+ "name": "ort_1d_asymmetric_padding",
708
+ "provenance": {
709
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
710
+ "test": "ConvTransposeTest.ConvTranspose_1D_AsymmetricPads"
711
+ },
712
+ "attrs": { "pads": [1, 0] },
713
+ "inputs": {
714
+ "x": { "dtype": "float32", "shape": [1, 1, 4], "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0] } },
715
+ "w": { "dtype": "float32", "shape": [1, 2, 2], "data": { "kind": "values", "values": [1.0, 1.0, 1.0, 1.0] } }
716
+ },
717
+ "outputs": {
718
+ "y": {
719
+ "dtype": "float32",
720
+ "shape": [1, 2, 4],
721
+ "tolerance": 0.000001,
722
+ "data": { "kind": "values", "values": [3.0, 5.0, 7.0, 4.0, 3.0, 5.0, 7.0, 4.0] }
723
+ }
724
+ }
725
+ },
726
+ {
727
+ "name": "ort_output_shape_2_bias_single_row",
728
+ "provenance": {
729
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
730
+ "test": "ConvTransposeTest.ConvTranspose_2D_OutputShape_2",
731
+ "notes": "ORT uses the exact output_shape attribute; this fixture has the same natural output size."
732
+ },
733
+ "attrs": { "output_shape": [1, 14] },
734
+ "inputs": {
735
+ "x": { "dtype": "float32", "shape": [1, 1, 1, 10], "data": { "kind": "linspace", "start": 0.0, "end": 9.0 } },
736
+ "w": {
737
+ "dtype": "float32",
738
+ "shape": [1, 1, 1, 5],
739
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 2.0, 1.0] }
740
+ },
741
+ "bias": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } }
742
+ },
743
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1, 14], "tolerance": 0.000001 } }
744
+ },
745
+ {
746
+ "name": "ort_output_shape_with_batch_size_bias_single_row",
747
+ "provenance": {
748
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
749
+ "test": "ConvTransposeTest.ConvTranspose_2D_OutputShapeWithBatchSize",
750
+ "notes": "ORT uses the exact output_shape attribute; this fixture has the same natural output size."
751
+ },
752
+ "attrs": { "output_shape": [1, 14] },
753
+ "inputs": {
754
+ "x": { "dtype": "float32", "shape": [2, 1, 1, 10], "data": { "kind": "linspace", "start": 0.0, "end": 19.0 } },
755
+ "w": {
756
+ "dtype": "float32",
757
+ "shape": [1, 1, 1, 5],
758
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 2.0, 1.0] }
759
+ },
760
+ "bias": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } }
761
+ },
762
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 1, 1, 14], "tolerance": 0.000001 } }
763
+ },
764
+ {
765
+ "name": "ort_output_shape_1_equivalent_symmetric_crop",
766
+ "provenance": {
767
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
768
+ "test": "ConvTransposeTest.ConvTranspose_2D_OutputShape_1",
769
+ "notes": "Covers the exact spatial output_shape [4, 4]; ConvTranspose derives the required symmetric crop."
770
+ },
771
+ "attrs": { "output_shape": [4, 4] },
772
+ "inputs": {
773
+ "x": { "dtype": "float32", "shape": [1, 3, 4, 4], "data": { "kind": "constant", "value": 1.0 } },
774
+ "w": { "dtype": "float32", "shape": [3, 3, 3, 3], "data": { "kind": "constant", "value": 1.0 } }
775
+ },
776
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 3, 4, 4], "tolerance": 0 } }
777
+ },
778
+ {
779
+ "name": "ort_dilation3_kernel2_repeated_tiles",
780
+ "provenance": {
781
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
782
+ "test": "ConvTransposeTest.ConvTranspose_2D_Dilation_2"
783
+ },
784
+ "attrs": { "dilations": [3, 3] },
785
+ "inputs": {
786
+ "x": {
787
+ "dtype": "float32",
788
+ "shape": [1, 1, 2, 2],
789
+ "data": { "kind": "values", "values": [11.0, 12.0, 21.0, 22.0] }
790
+ },
791
+ "w": { "dtype": "float32", "shape": [1, 1, 2, 2], "data": { "kind": "constant", "value": 1.0 } }
792
+ },
793
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 5, 5], "tolerance": 0 } }
794
+ },
795
+ {
796
+ "name": "ort_dilation2_kernel2_mixed_values",
797
+ "provenance": {
798
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
799
+ "test": "ConvTransposeTest.ConvTranspose_2D_Dilation_3"
800
+ },
801
+ "attrs": { "dilations": [2, 2] },
802
+ "inputs": {
803
+ "x": {
804
+ "dtype": "float32",
805
+ "shape": [1, 1, 3, 3],
806
+ "data": { "kind": "values", "values": [3.0, 8.0, 1.0, 9.0, 5.0, 7.0, 3.0, 2.0, 6.0] }
807
+ },
808
+ "w": { "dtype": "float32", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [7.0, 2.0, 1.0, 9.0] } }
809
+ },
810
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 5, 5], "tolerance": 0 } }
811
+ },
812
+ {
813
+ "name": "onnx_backend_convtranspose",
814
+ "inputs": {
815
+ "x": {
816
+ "dtype": "float32",
817
+ "shape": [1, 1, 3, 3],
818
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
819
+ },
820
+ "w": {
821
+ "dtype": "float32",
822
+ "shape": [1, 2, 3, 3],
823
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_convtranspose_input_w" } }
824
+ }
825
+ },
826
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 5, 5] } },
827
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_convtranspose" }
828
+ },
829
+ {
830
+ "name": "onnx_backend_convtranspose_dilations",
831
+ "attrs": { "dilations": [2, 2] },
832
+ "inputs": {
833
+ "x": {
834
+ "dtype": "float32",
835
+ "shape": [1, 1, 3, 3],
836
+ "data": { "kind": "values", "values": [3.0, 8.0, 1.0, 9.0, 5.0, 7.0, 3.0, 2.0, 6.0] }
837
+ },
838
+ "w": { "dtype": "float32", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [7.0, 2.0, 1.0, 9.0] } }
839
+ },
840
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 5, 5] } },
841
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_convtranspose_dilations" }
842
+ },
843
+ {
844
+ "name": "onnx_backend_convtranspose_group_2",
845
+ "attrs": { "group": 2 },
846
+ "inputs": {
847
+ "x": {
848
+ "dtype": "float32",
849
+ "shape": [1, 2, 3, 3],
850
+ "data": {
851
+ "kind": "values",
852
+ "values": { "$ref": "#/fixtureArrays/ort_default_strides_dilations_multi_channel_input_x" }
853
+ }
854
+ },
855
+ "w": {
856
+ "dtype": "float32",
857
+ "shape": [2, 1, 3, 3],
858
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_convtranspose_input_w" } }
859
+ }
860
+ },
861
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 5, 5] } },
862
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_convtranspose_group_2" }
863
+ },
864
+ {
865
+ "name": "onnx_backend_convtranspose_group_2_image_3",
866
+ "attrs": { "group": 2 },
867
+ "inputs": {
868
+ "x": {
869
+ "dtype": "float32",
870
+ "shape": [3, 2, 3, 3],
871
+ "data": {
872
+ "kind": "values",
873
+ "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0]
874
+ }
875
+ },
876
+ "w": {
877
+ "dtype": "float32",
878
+ "shape": [2, 1, 3, 3],
879
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_convtranspose_input_w" } }
880
+ }
881
+ },
882
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 2, 5, 5] } },
883
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_convtranspose_group_2_image_3" }
884
+ },
885
+ {
886
+ "name": "onnx_backend_convtranspose_pad",
887
+ "attrs": { "strides": [3, 2], "output_padding": [1, 1] },
888
+ "inputs": {
889
+ "x": {
890
+ "dtype": "float32",
891
+ "shape": [1, 1, 3, 3],
892
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
893
+ },
894
+ "w": {
895
+ "dtype": "float32",
896
+ "shape": [1, 2, 3, 3],
897
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_convtranspose_input_w" } }
898
+ }
899
+ },
900
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 10, 8] } },
901
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_convtranspose_pad" }
902
+ },
903
+ {
904
+ "name": "onnx_backend_convtranspose_pads",
905
+ "attrs": { "strides": [3, 2], "pads": [1, 2, 1, 2] },
906
+ "inputs": {
907
+ "x": {
908
+ "dtype": "float32",
909
+ "shape": [1, 1, 3, 3],
910
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
911
+ },
912
+ "w": {
913
+ "dtype": "float32",
914
+ "shape": [1, 2, 3, 3],
915
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_convtranspose_input_w" } }
916
+ }
917
+ },
918
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 7, 3] } },
919
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_convtranspose_pads" }
920
+ },
921
+ {
922
+ "name": "onnx_backend_convtranspose_autopad_same_explicit",
923
+ "attrs": { "strides": [2, 2], "auto_pad": "SAME_UPPER" },
924
+ "inputs": {
925
+ "x": {
926
+ "dtype": "float32",
927
+ "shape": [1, 1, 3, 3],
928
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
929
+ },
930
+ "w": {
931
+ "dtype": "float32",
932
+ "shape": [1, 2, 3, 3],
933
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_convtranspose_input_w" } }
934
+ }
935
+ },
936
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 6, 6], "tolerance": 0 } },
937
+ "provenance": {
938
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_convtranspose_autopad_same",
939
+ "notes": "SAME_UPPER is represented by equivalent explicit symmetric pads plus output padding for this fixture."
940
+ }
941
+ },
942
+ {
943
+ "name": "onnx_backend_convtranspose_output_shape_as_output_padding",
944
+ "attrs": { "strides": [3, 2], "output_shape": [10, 8] },
945
+ "inputs": {
946
+ "x": {
947
+ "dtype": "float32",
948
+ "shape": [1, 1, 3, 3],
949
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
950
+ },
951
+ "w": {
952
+ "dtype": "float32",
953
+ "shape": [1, 2, 3, 3],
954
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_convtranspose_input_w" } }
955
+ }
956
+ },
957
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 10, 8], "tolerance": 0 } },
958
+ "provenance": {
959
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_convtranspose_output_shape",
960
+ "notes": "Uses the official spatial output_shape attribute from the ONNX backend fixture."
961
+ }
962
+ },
963
+ {
964
+ "name": "onnx_backend_convtranspose_kernel_shape_output_padding",
965
+ "attrs": { "strides": [3, 2], "output_padding": [1, 1], "output_shape": [10, 8], "kernel_shape": [3, 3] },
966
+ "inputs": {
967
+ "x": {
968
+ "dtype": "float32",
969
+ "shape": [1, 1, 3, 3],
970
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
971
+ },
972
+ "w": {
973
+ "dtype": "float32",
974
+ "shape": [1, 2, 3, 3],
975
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/onnx_backend_convtranspose_input_w" } }
976
+ }
977
+ },
978
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 10, 8], "tolerance": 0 } },
979
+ "provenance": {
980
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_convtranspose_kernel_shape",
981
+ "notes": "Uses the official kernel_shape, output_shape, and output_padding attributes from the ONNX backend fixture."
982
+ }
983
+ },
984
+ {
985
+ "name": "onnx_backend_convtranspose_1d",
986
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_convtranspose_1d" },
987
+ "inputs": {
988
+ "x": { "dtype": "float32", "shape": [1, 1, 3], "data": { "kind": "linspace", "start": 0.0, "end": 2.0 } },
989
+ "w": { "dtype": "float32", "shape": [1, 2, 3], "data": { "kind": "constant", "value": 1.0 } }
990
+ },
991
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 5], "tolerance": 0 } }
992
+ },
993
+ {
994
+ "name": "ort_convtranspose_1d_autopad_same_upper",
995
+ "provenance": {
996
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
997
+ "test": "ConvTransposeTest.ConvTranspose_1D_AutoPad_SameUpper",
998
+ "notes": "Covers one-dimensional ConvTranspose with auto_pad=SAME_UPPER."
999
+ },
1000
+ "attrs": { "auto_pad": "SAME_UPPER" },
1001
+ "inputs": {
1002
+ "x": { "dtype": "float32", "shape": [1, 1, 4], "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0] } },
1003
+ "w": { "dtype": "float32", "shape": [1, 2, 2], "data": { "kind": "constant", "value": 1.0 } }
1004
+ },
1005
+ "outputs": {
1006
+ "y": {
1007
+ "dtype": "float32",
1008
+ "shape": [1, 2, 4],
1009
+ "tolerance": 0,
1010
+ "data": { "kind": "values", "values": [1.0, 3.0, 5.0, 7.0, 1.0, 3.0, 5.0, 7.0] }
1011
+ }
1012
+ }
1013
+ },
1014
+ {
1015
+ "name": "ort_convtranspose_1d_autopad_same_lower",
1016
+ "provenance": {
1017
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
1018
+ "test": "ConvTransposeTest.ConvTranspose_1D_AutoPad_SameLower",
1019
+ "notes": "Covers one-dimensional ConvTranspose with auto_pad=SAME_LOWER."
1020
+ },
1021
+ "attrs": { "auto_pad": "SAME_LOWER" },
1022
+ "inputs": {
1023
+ "x": { "dtype": "float32", "shape": [1, 1, 4], "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0] } },
1024
+ "w": { "dtype": "float32", "shape": [1, 2, 2], "data": { "kind": "constant", "value": 1.0 } }
1025
+ },
1026
+ "outputs": {
1027
+ "y": {
1028
+ "dtype": "float32",
1029
+ "shape": [1, 2, 4],
1030
+ "tolerance": 0,
1031
+ "data": { "kind": "values", "values": [3.0, 5.0, 7.0, 4.0, 3.0, 5.0, 7.0, 4.0] }
1032
+ }
1033
+ }
1034
+ },
1035
+ {
1036
+ "name": "ort_convtranspose_2d_autopad_same_upper_stride2",
1037
+ "provenance": {
1038
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
1039
+ "test": "ConvTransposeTest.ConvTranspose_AutoPad_with_non_default_strides",
1040
+ "notes": "Covers two-dimensional stride-2 ConvTranspose with auto_pad=SAME_UPPER."
1041
+ },
1042
+ "attrs": { "auto_pad": "SAME_UPPER", "strides": [2, 2] },
1043
+ "inputs": {
1044
+ "x": {
1045
+ "dtype": "float32",
1046
+ "shape": [1, 1, 3, 3],
1047
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] }
1048
+ },
1049
+ "w": { "dtype": "float32", "shape": [1, 2, 3, 3], "data": { "kind": "constant", "value": 1.0 } }
1050
+ },
1051
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 6, 6], "tolerance": 0 } }
1052
+ },
1053
+ {
1054
+ "name": "ort_convtranspose_1d_grouped",
1055
+ "provenance": {
1056
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
1057
+ "test": "ConvTransposeTest.ConvTranspose_1D_OutputShape_1_group_2_for_transpose_path",
1058
+ "notes": "Covers grouped one-dimensional ConvTranspose."
1059
+ },
1060
+ "attrs": { "group": 2 },
1061
+ "inputs": {
1062
+ "x": { "dtype": "float32", "shape": [1, 2, 2], "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0] } },
1063
+ "w": { "dtype": "float32", "shape": [2, 1, 1], "data": { "kind": "values", "values": [2.0, 3.0] } }
1064
+ },
1065
+ "outputs": {
1066
+ "y": {
1067
+ "dtype": "float32",
1068
+ "shape": [1, 2, 2],
1069
+ "tolerance": 0.000001,
1070
+ "data": { "kind": "values", "values": [2.0, 4.0, 9.0, 12.0] }
1071
+ }
1072
+ }
1073
+ },
1074
+ {
1075
+ "name": "ncl1d_grouped_bias",
1076
+ "provenance": {
1077
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
1078
+ "test": "ConvTransposeTest.ConvTranspose_1D_OutputShape_1_group_2_for_transpose_path",
1079
+ "notes": "Grouped (group=2) 1D ConvTranspose WITH bias. Exercises the ncl1d_grouped_bias variant."
1080
+ },
1081
+ "attrs": { "group": 2 },
1082
+ "inputs": {
1083
+ "x": { "dtype": "float32", "shape": [1, 2, 2], "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0] } },
1084
+ "w": { "dtype": "float32", "shape": [2, 1, 1], "data": { "kind": "values", "values": [2.0, 3.0] } },
1085
+ "bias": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.25, -0.5] } }
1086
+ },
1087
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 2], "tolerance": 0.000001 } }
1088
+ },
1089
+ {
1090
+ "name": "ort_convtranspose_1d_bias",
1091
+ "provenance": {
1092
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
1093
+ "test": "ConvTransposeTest.ConvTranspose_2D_Bias_2",
1094
+ "notes": "Covers one-dimensional ConvTranspose with the optional bias input."
1095
+ },
1096
+ "inputs": {
1097
+ "x": { "dtype": "float32", "shape": [1, 1, 3], "data": { "kind": "values", "values": [1.0, 2.0, 3.0] } },
1098
+ "w": { "dtype": "float32", "shape": [1, 2, 2], "data": { "kind": "values", "values": [1.0, 2.0, -1.0, 0.5] } },
1099
+ "bias": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.25, -0.5] } }
1100
+ },
1101
+ "outputs": {
1102
+ "y": {
1103
+ "dtype": "float32",
1104
+ "shape": [1, 2, 4],
1105
+ "tolerance": 0.000001,
1106
+ "data": { "kind": "values", "values": [1.25, 4.25, 7.25, 6.25, -1.5, -2.0, -2.5, 1.0] }
1107
+ }
1108
+ }
1109
+ },
1110
+ {
1111
+ "name": "onnx_backend_convtranspose_3d",
1112
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_convtranspose_3d" },
1113
+ "inputs": {
1114
+ "x": { "dtype": "float32", "shape": [1, 1, 3, 4, 5], "data": { "kind": "linspace", "start": 0.0, "end": 59.0 } },
1115
+ "w": { "dtype": "float32", "shape": [1, 2, 3, 3, 3], "data": { "kind": "constant", "value": 1.0 } }
1116
+ },
1117
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 5, 6, 7], "tolerance": 0 } }
1118
+ },
1119
+ {
1120
+ "name": "ort_convtranspose_3d_bias",
1121
+ "provenance": {
1122
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
1123
+ "test": "ConvTransposeTest.ConvTranspose_3D",
1124
+ "notes": "Covers three-dimensional 1x1x1 ConvTranspose with the optional bias input."
1125
+ },
1126
+ "inputs": {
1127
+ "x": {
1128
+ "dtype": "float32",
1129
+ "shape": [1, 1, 2, 2, 2],
1130
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, -1.0, -2.0, -3.0, -4.0] }
1131
+ },
1132
+ "w": { "dtype": "float32", "shape": [1, 1, 1, 1, 1], "data": { "kind": "values", "values": [1.0] } },
1133
+ "bias": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } }
1134
+ },
1135
+ "outputs": {
1136
+ "y": {
1137
+ "dtype": "float32",
1138
+ "shape": [1, 1, 2, 2, 2],
1139
+ "tolerance": 0.000001,
1140
+ "data": { "kind": "values", "values": [1.25, 2.25, 3.25, 4.25, -0.75, -1.75, -2.75, -3.75] }
1141
+ }
1142
+ }
1143
+ },
1144
+ {
1145
+ "name": "nchw2d_bias_f16",
1146
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1], "output_padding": [1, 1] },
1147
+ "inputs": {
1148
+ "x": {
1149
+ "dtype": "float16",
1150
+ "shape": [1, 1, 3, 3],
1151
+ "data": {
1152
+ "kind": "values",
1153
+ "values": [0.16857791, -0.15161794, 0.08540368, 0.1820628, -0.21746576, 0.08245695, 0.1431433, -0.43156421, 0.30591947]
1154
+ }
1155
+ },
1156
+ "w": {
1157
+ "dtype": "float16",
1158
+ "shape": [1, 1, 3, 3],
1159
+ "data": {
1160
+ "kind": "values",
1161
+ "values": [-0.06230065, 0.37932432, -0.25388849, 0.33878803, 0.43709868, -0.22477469, 0.04118127, -0.44696793, 0.06373066]
1162
+ }
1163
+ },
1164
+ "bias": { "dtype": "float16", "shape": [1], "data": { "kind": "values", "values": [0.25] } }
1165
+ },
1166
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 1, 6, 6], "tolerance": 0.001 } }
1167
+ },
1168
+ {
1169
+ "name": "f16_1d_nobias",
1170
+ "attrs": {},
1171
+ "inputs": {
1172
+ "x": {
1173
+ "dtype": "float16",
1174
+ "shape": [1, 2, 8],
1175
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/f16_1d_nobias_input_x" } }
1176
+ },
1177
+ "w": {
1178
+ "dtype": "float16",
1179
+ "shape": [2, 3, 3],
1180
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/f16_1d_nobias_input_w" } }
1181
+ }
1182
+ },
1183
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 3, 10], "tolerance": 0.02 } }
1184
+ },
1185
+ {
1186
+ "name": "f16_grouped2d_nobias",
1187
+ "attrs": { "group": 2, "strides": [2, 2], "pads": [1, 1, 1, 1] },
1188
+ "inputs": {
1189
+ "x": {
1190
+ "dtype": "float16",
1191
+ "shape": [1, 4, 8, 8],
1192
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.17 }
1193
+ },
1194
+ "w": {
1195
+ "dtype": "float16",
1196
+ "shape": [4, 3, 3, 3],
1197
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.13 }
1198
+ }
1199
+ },
1200
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 6, 15, 15], "tolerance": 0.03 } }
1201
+ },
1202
+ {
1203
+ "name": "f16_autopad_same_upper_stride2",
1204
+ "attrs": { "auto_pad": "SAME_UPPER", "strides": [2, 2] },
1205
+ "inputs": {
1206
+ "x": {
1207
+ "dtype": "float16",
1208
+ "shape": [1, 2, 4, 4],
1209
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.17 }
1210
+ },
1211
+ "w": {
1212
+ "dtype": "float16",
1213
+ "shape": [2, 3, 3, 3],
1214
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23 }
1215
+ }
1216
+ },
1217
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 3, 8, 8], "tolerance": 0.03 } }
1218
+ },
1219
+ {
1220
+ "name": "nchw2d_f16_nobias",
1221
+ "attrs": {},
1222
+ "inputs": {
1223
+ "x": {
1224
+ "dtype": "float16",
1225
+ "shape": [1, 2, 4, 4],
1226
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.17 }
1227
+ },
1228
+ "w": {
1229
+ "dtype": "float16",
1230
+ "shape": [2, 3, 3, 3],
1231
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.13 }
1232
+ }
1233
+ },
1234
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 3, 6, 6], "tolerance": 0.02 } }
1235
+ },
1236
+ {
1237
+ "name": "f16_phase_gemm_k4s2_channel_and_spatial_tails",
1238
+ "provenance": {
1239
+ "notes": "Compact route lock for the fused four-phase k4s2 GEMM path. Odd channel and spatial sizes exercise K, M, and N tile tails plus all padded boundaries."
1240
+ },
1241
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
1242
+ "inputs": {
1243
+ "x": {
1244
+ "dtype": "float16",
1245
+ "shape": [1, 5, 7, 9],
1246
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.17, "scale": 0.2 }
1247
+ },
1248
+ "w": {
1249
+ "dtype": "float16",
1250
+ "shape": [5, 11, 4, 4],
1251
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.13, "scale": 0.1 }
1252
+ }
1253
+ },
1254
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 11, 14, 18], "tolerance": 0.03 } }
1255
+ },
1256
+ {
1257
+ "name": "f16_phase_gemm_k4s2_bias_tails",
1258
+ "provenance": {
1259
+ "notes": "Bias companion for the fused four-phase k4s2 path with non-tile-aligned channels and phase-grid dimensions."
1260
+ },
1261
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
1262
+ "inputs": {
1263
+ "x": {
1264
+ "dtype": "float16",
1265
+ "shape": [1, 3, 5, 6],
1266
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.23, "scale": 0.2 }
1267
+ },
1268
+ "w": {
1269
+ "dtype": "float16",
1270
+ "shape": [3, 7, 4, 4],
1271
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.29, "scale": 0.1 }
1272
+ },
1273
+ "bias": {
1274
+ "dtype": "float16",
1275
+ "shape": [7],
1276
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.31, "scale": 0.05 }
1277
+ }
1278
+ },
1279
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 7, 10, 12], "tolerance": 0.03 } }
1280
+ },
1281
+ {
1282
+ "name": "f16_gemm_2d_no_bias",
1283
+ "provenance": {
1284
+ "notes": "f16 2D ConvTranspose, group 1, IC%32==0 and IH*IW%64==0 -> exercises the im2col-GEMM path nchw2d_gemm (subgroup-matrix) AND the direct nchw2d_f16 kernel."
1285
+ },
1286
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
1287
+ "inputs": {
1288
+ "x": {
1289
+ "dtype": "float16",
1290
+ "shape": [1, 32, 8, 8],
1291
+ "data": { "kind": "fillFloat32", "sinStep": 0.03, "cosStep": 0.07, "scale": 0.4 }
1292
+ },
1293
+ "w": {
1294
+ "dtype": "float16",
1295
+ "shape": [32, 16, 3, 3],
1296
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.11, "scale": 0.4 }
1297
+ }
1298
+ },
1299
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 16, 15, 15], "tolerance": 0.03 } }
1300
+ },
1301
+ {
1302
+ "name": "f16_gemm_2d_bias",
1303
+ "provenance": {
1304
+ "notes": "f16 2D ConvTranspose, group 1, IC%32==0 and IH*IW%64==0 -> exercises the im2col-GEMM path nchw2d_gemm_bias (subgroup-matrix) AND the direct nchw2d_f16 kernel."
1305
+ },
1306
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
1307
+ "inputs": {
1308
+ "x": {
1309
+ "dtype": "float16",
1310
+ "shape": [1, 32, 8, 8],
1311
+ "data": { "kind": "fillFloat32", "sinStep": 0.03, "cosStep": 0.07, "scale": 0.4 }
1312
+ },
1313
+ "w": {
1314
+ "dtype": "float16",
1315
+ "shape": [32, 16, 3, 3],
1316
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.11, "scale": 0.4 }
1317
+ },
1318
+ "bias": {
1319
+ "dtype": "float16",
1320
+ "shape": [16],
1321
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.19, "scale": 0.3 }
1322
+ }
1323
+ },
1324
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 16, 15, 15], "tolerance": 0.03 } }
1325
+ },
1326
+ {
1327
+ "name": "f16_gemm_2d_batch2",
1328
+ "provenance": {
1329
+ "notes": "f16 2D ConvTranspose GEMM path with batch=2 — exercises the GEMM workgroup_id.z batching and col2im batch decode."
1330
+ },
1331
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
1332
+ "inputs": {
1333
+ "x": {
1334
+ "dtype": "float16",
1335
+ "shape": [2, 32, 8, 8],
1336
+ "data": { "kind": "fillFloat32", "sinStep": 0.023, "cosStep": 0.061, "scale": 0.4 }
1337
+ },
1338
+ "w": {
1339
+ "dtype": "float16",
1340
+ "shape": [32, 16, 3, 3],
1341
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.11, "scale": 0.4 }
1342
+ }
1343
+ },
1344
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 16, 15, 15], "tolerance": 0.03 } }
1345
+ },
1346
+ {
1347
+ "name": "nchw2d_gemm_f32_reg_m512_n1024",
1348
+ "attrs": {},
1349
+ "inputs": {
1350
+ "x": {
1351
+ "dtype": "float32",
1352
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.17 },
1353
+ "shape": [1, 8, 32, 32]
1354
+ },
1355
+ "w": {
1356
+ "dtype": "float32",
1357
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.13 },
1358
+ "shape": [8, 128, 2, 2]
1359
+ }
1360
+ },
1361
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 128, 33, 33], "tolerance": 0.0001 } }
1362
+ },
1363
+ {
1364
+ "name": "nchw2d_gemm_f32_bias_reg_m512_n1024",
1365
+ "attrs": {},
1366
+ "inputs": {
1367
+ "x": {
1368
+ "dtype": "float32",
1369
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.17 },
1370
+ "shape": [1, 8, 32, 32]
1371
+ },
1372
+ "w": {
1373
+ "dtype": "float32",
1374
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.13 },
1375
+ "shape": [8, 128, 2, 2]
1376
+ },
1377
+ "bias": {
1378
+ "dtype": "float32",
1379
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.23 },
1380
+ "shape": [128]
1381
+ }
1382
+ },
1383
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 128, 33, 33], "tolerance": 0.0001 } }
1384
+ },
1385
+ {
1386
+ "name": "empty_input_spatial_dim_3d_nonempty_output",
1387
+ "provenance": {
1388
+ "notes": "A zero-depth input leaves a nonempty rank-5 output because the kernel is wider than the empty spatial extent. The contraction is empty, so every output is the additive identity."
1389
+ },
1390
+ "attrs": {},
1391
+ "inputs": {
1392
+ "x": { "dtype": "float32", "shape": [1, 1, 0, 3, 3] },
1393
+ "w": { "dtype": "float32", "shape": [1, 1, 3, 3, 3], "data": { "kind": "constant", "value": 1.0 } }
1394
+ },
1395
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 2, 5, 5], "data": { "kind": "constant", "value": 0.0 } } }
1396
+ },
1397
+ {
1398
+ "name": "empty_input_spatial_dim_3d_nonempty_output_bias",
1399
+ "provenance": {
1400
+ "notes": "Bias is applied after an empty rank-5 contraction, so each nonempty output channel is exactly its bias value without reading X or W."
1401
+ },
1402
+ "attrs": {},
1403
+ "inputs": {
1404
+ "x": { "dtype": "float32", "shape": [1, 1, 0, 1, 1] },
1405
+ "w": { "dtype": "float32", "shape": [1, 2, 2, 1, 1], "data": { "kind": "constant", "value": 1.0 } },
1406
+ "bias": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.25, -2.5] } }
1407
+ },
1408
+ "outputs": {
1409
+ "y": {
1410
+ "dtype": "float32",
1411
+ "shape": [1, 2, 1, 1, 1],
1412
+ "data": { "kind": "values", "values": [1.25, -2.5] },
1413
+ "tolerance": 0
1414
+ }
1415
+ }
1416
+ },
1417
+ {
1418
+ "name": "empty_input_spatial_dim_2d_grouped_f16_nonempty_output",
1419
+ "provenance": {
1420
+ "notes": "The empty-reduction fill route also covers grouped rank-4 float16 without allocating or reading an im2col scratch buffer."
1421
+ },
1422
+ "attrs": { "group": 2 },
1423
+ "inputs": {
1424
+ "x": { "dtype": "float16", "shape": [1, 2, 0, 1] },
1425
+ "w": { "dtype": "float16", "shape": [2, 1, 2, 1], "data": { "kind": "constant", "value": 1.0 } }
1426
+ },
1427
+ "outputs": {
1428
+ "y": { "dtype": "float16", "shape": [1, 2, 1, 1], "data": { "kind": "constant", "value": 0.0 }, "tolerance": 0 }
1429
+ }
1430
+ },
1431
+ {
1432
+ "name": "empty_input_spatial_dim_1d_f16_nonempty_output_bias",
1433
+ "provenance": {
1434
+ "notes": "The float16 bias-broadcast source is rendered explicitly: a zero-width rank-3 reduction returns one output position per channel containing only bias."
1435
+ },
1436
+ "attrs": {},
1437
+ "inputs": {
1438
+ "x": { "dtype": "float16", "shape": [1, 1, 0] },
1439
+ "w": { "dtype": "float16", "shape": [1, 2, 2], "data": { "kind": "constant", "value": 1.0 } },
1440
+ "bias": { "dtype": "float16", "shape": [2], "data": { "kind": "values", "values": [0.5, -0.75] } }
1441
+ },
1442
+ "outputs": {
1443
+ "y": {
1444
+ "dtype": "float16",
1445
+ "shape": [1, 2, 1],
1446
+ "data": { "kind": "values", "values": [0.5, -0.75] },
1447
+ "tolerance": 0
1448
+ }
1449
+ }
1450
+ },
1451
+ {
1452
+ "name": "zero_batch_dim_3d_empty_output",
1453
+ "attrs": {},
1454
+ "inputs": {
1455
+ "x": { "dtype": "float32", "shape": [0, 1, 3, 3, 3] },
1456
+ "w": { "dtype": "float32", "shape": [1, 2, 3, 3, 3] }
1457
+ },
1458
+ "outputs": { "y": { "dtype": "float32", "shape": [0, 2, 5, 5, 5] } }
1459
+ },
1460
+ {
1461
+ "name": "zero_batch_dim_grouped_empty_output",
1462
+ "attrs": { "group": 2 },
1463
+ "inputs": {
1464
+ "x": { "dtype": "float32", "shape": [0, 4, 4, 4] },
1465
+ "w": { "dtype": "float32", "shape": [4, 2, 2, 2] }
1466
+ },
1467
+ "outputs": { "y": { "dtype": "float32", "shape": [0, 4, 5, 5] } }
1468
+ },
1469
+ {
1470
+ "name": "grouped_dispatch_fold_depthwise_1x1_over_16m",
1471
+ "attrs": { "group": 64 },
1472
+ "inputs": {
1473
+ "x": { "dtype": "float32", "shape": [1, 64, 512, 512] },
1474
+ "w": { "dtype": "float32", "shape": [64, 1, 1, 1], "data": { "kind": "constant", "value": 1.0 } }
1475
+ },
1476
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 64, 512, 512] } }
1477
+ },
1478
+ {
1479
+ "name": "grouped_2d_f16_bias_routing_gap",
1480
+ "attrs": { "group": 2, "strides": [2, 2], "pads": [1, 1, 1, 1] },
1481
+ "inputs": {
1482
+ "x": { "dtype": "float16", "shape": [1, 4, 8, 8] },
1483
+ "w": { "dtype": "float16", "shape": [4, 3, 3, 3] },
1484
+ "bias": { "dtype": "float16", "shape": [6] }
1485
+ },
1486
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 6, 15, 15], "tolerance": 0.03 } }
1487
+ },
1488
+ {
1489
+ "name": "convtranspose3d_depth_output_padding1_compact",
1490
+ "provenance": {
1491
+ "source": "ONNX ConvTranspose-22 volumetric output_padding semantics",
1492
+ "notes": "Covers a non-default depth entry in the exact output_padding list attribute."
1493
+ },
1494
+ "attrs": { "strides": [2, 1, 1], "output_padding": [1, 0, 0] },
1495
+ "inputs": {
1496
+ "x": { "dtype": "float32", "shape": [1, 1, 2, 1, 1], "data": { "kind": "values", "values": [1.0, 2.0] } },
1497
+ "w": { "dtype": "float32", "shape": [1, 1, 2, 1, 1], "data": { "kind": "values", "values": [3.0, 4.0] } }
1498
+ },
1499
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 5, 1, 1], "tolerance": 0.000001 } }
1500
+ },
1501
+ {
1502
+ "name": "dispatch_cliff_batch_over_65535_bias_forces_nchw_scalar",
1503
+ "provenance": {
1504
+ "notes": "Batch 65536 exceeds maxComputeWorkgroupsPerDimension, so gemmDispatchFits is false and every col2im GEMM variant drops out; the scalar NCHW kernel with its bias epilogue is the only remaining f32 dense path."
1505
+ },
1506
+ "attrs": {},
1507
+ "inputs": {
1508
+ "x": {
1509
+ "dtype": "float32",
1510
+ "shape": [65536, 1, 1, 1],
1511
+ "data": { "kind": "fillFloat32", "sinStep": 0.0009, "cosStep": 0.0007 }
1512
+ },
1513
+ "w": { "dtype": "float32", "shape": [1, 1, 1, 1], "data": { "kind": "constant", "value": 1.25 } },
1514
+ "bias": { "dtype": "float32", "shape": [1], "data": { "kind": "constant", "value": 0.5 } }
1515
+ },
1516
+ "outputs": { "y": { "dtype": "float32", "shape": [65536, 1, 1, 1], "tolerance": 0.00001 } }
1517
+ },
1518
+ {
1519
+ "name": "dispatch_cliff_batch_over_65535_bias_f16_forces_nchw_scalar",
1520
+ "provenance": {
1521
+ "notes": "f16 companion of the batch dispatch cliff: with gemmDispatchFits false the tiled and subgroup-matrix col2im GEMMs are ineligible, and the stride-2 phase GEMM geometry does not apply, leaving the scalar f16 NCHW bias kernel."
1522
+ },
1523
+ "attrs": {},
1524
+ "inputs": {
1525
+ "x": {
1526
+ "dtype": "float16",
1527
+ "shape": [65536, 1, 1, 1],
1528
+ "data": { "kind": "fillFloat32", "sinStep": 0.0009, "cosStep": 0.0007 }
1529
+ },
1530
+ "w": { "dtype": "float16", "shape": [1, 1, 1, 1], "data": { "kind": "constant", "value": 1.25 } },
1531
+ "bias": { "dtype": "float16", "shape": [1], "data": { "kind": "constant", "value": 0.5 } }
1532
+ },
1533
+ "outputs": { "y": { "dtype": "float16", "shape": [65536, 1, 1, 1], "tolerance": 0.002, "relTolerance": 0.01 } }
1534
+ },
1535
+ {
1536
+ "name": "f16_grouped2d_stride1_no_bias",
1537
+ "provenance": {
1538
+ "notes": "Grouped f16 ConvTranspose at stride 1: the grouped stride-phase kernel requires strideH/strideW >= 2, so unit stride is what routes a grouped f16 request to the scalar grouped NCHW kernel."
1539
+ },
1540
+ "attrs": { "group": 2 },
1541
+ "inputs": {
1542
+ "x": {
1543
+ "dtype": "float16",
1544
+ "shape": [1, 2, 2, 2],
1545
+ "data": { "kind": "fillFloat32", "sinStep": 0.11, "cosStep": 0.17 }
1546
+ },
1547
+ "w": {
1548
+ "dtype": "float16",
1549
+ "shape": [2, 1, 2, 2],
1550
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.13 }
1551
+ }
1552
+ },
1553
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 2, 3, 3], "tolerance": 0.01 } }
1554
+ },
1555
+ {
1556
+ "name": "f16_grouped2d_stride1_bias",
1557
+ "provenance": {
1558
+ "notes": "Bias companion of the unit-stride grouped f16 case: the grouped stride-phase bias kernel is gated on stride >= 2, so the bias epilogue of the scalar grouped f16 NCHW kernel is only reachable at stride 1."
1559
+ },
1560
+ "attrs": { "group": 2 },
1561
+ "inputs": {
1562
+ "x": {
1563
+ "dtype": "float16",
1564
+ "shape": [1, 2, 2, 2],
1565
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.19 }
1566
+ },
1567
+ "w": {
1568
+ "dtype": "float16",
1569
+ "shape": [2, 1, 2, 2],
1570
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.11 }
1571
+ },
1572
+ "bias": {
1573
+ "dtype": "float16",
1574
+ "shape": [2],
1575
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.29 }
1576
+ }
1577
+ },
1578
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 2, 3, 3], "tolerance": 0.01 } }
1579
+ },
1580
+ {
1581
+ "name": "convtranspose3d_subgroup_matrix_col2im_overlap_f32",
1582
+ "provenance": {
1583
+ "notes": "General rank-5 matrix-path coverage with K=IC=32, N=ID*IH*IW=64, and M=OC*KD*KH*KW=32. Constant operands make the complete 2x2x2 overlap multiplicity explicit while exercising weight reorder, subgroup-matrix contraction, and 3D col2im."
1584
+ },
1585
+ "attrs": {},
1586
+ "inputs": {
1587
+ "x": { "dtype": "float32", "shape": [1, 32, 4, 4, 4], "data": { "kind": "constant", "value": 1.0 } },
1588
+ "w": { "dtype": "float32", "shape": [32, 4, 2, 2, 2], "data": { "kind": "constant", "value": 0.125 } }
1589
+ },
1590
+ "outputs": {
1591
+ "y": {
1592
+ "dtype": "float32",
1593
+ "shape": [1, 4, 5, 5, 5],
1594
+ "tolerance": 0.0002,
1595
+ "relTolerance": 0.0002,
1596
+ "data": {
1597
+ "kind": "cycle",
1598
+ "values": [4.0, 8.0, 8.0, 8.0, 4.0, 8.0, 16.0, 16.0, 16.0, 8.0, 8.0, 16.0, 16.0, 16.0, 8.0, 8.0, 16.0, 16.0, 16.0, 8.0, 4.0, 8.0, 8.0, 8.0, 4.0, 8.0, 16.0, 16.0, 16.0, 8.0, 16.0, 32.0, 32.0, 32.0, 16.0, 16.0, 32.0, 32.0, 32.0, 16.0, 16.0, 32.0, 32.0, 32.0, 16.0, 8.0, 16.0, 16.0, 16.0, 8.0, 8.0, 16.0, 16.0, 16.0, 8.0, 16.0, 32.0, 32.0, 32.0, 16.0, 16.0, 32.0, 32.0, 32.0, 16.0, 16.0, 32.0, 32.0, 32.0, 16.0, 8.0, 16.0, 16.0, 16.0, 8.0, 8.0, 16.0, 16.0, 16.0, 8.0, 16.0, 32.0, 32.0, 32.0, 16.0, 16.0, 32.0, 32.0, 32.0, 16.0, 16.0, 32.0, 32.0, 32.0, 16.0, 8.0, 16.0, 16.0, 16.0, 8.0, 4.0, 8.0, 8.0, 8.0, 4.0, 8.0, 16.0, 16.0, 16.0, 8.0, 8.0, 16.0, 16.0, 16.0, 8.0, 8.0, 16.0, 16.0, 16.0, 8.0, 4.0, 8.0, 8.0, 8.0, 4.0]
1599
+ }
1600
+ }
1601
+ }
1602
+ },
1603
+ {
1604
+ "name": "convtranspose3d_subgroup_matrix_bias_f32",
1605
+ "provenance": {
1606
+ "notes": "Bias companion for the aligned rank-5 matrix path. A pointwise volumetric kernel isolates the bias epilogue while preserving context-derived M/K/N eligibility."
1607
+ },
1608
+ "attrs": {},
1609
+ "inputs": {
1610
+ "x": { "dtype": "float32", "shape": [1, 32, 4, 4, 4], "data": { "kind": "constant", "value": 1.0 } },
1611
+ "w": { "dtype": "float32", "shape": [32, 32, 1, 1, 1], "data": { "kind": "constant", "value": 0.125 } },
1612
+ "bias": { "dtype": "float32", "shape": [32], "data": { "kind": "constant", "value": 0.5 } }
1613
+ },
1614
+ "outputs": {
1615
+ "y": {
1616
+ "dtype": "float32",
1617
+ "shape": [1, 32, 4, 4, 4],
1618
+ "tolerance": 0.0002,
1619
+ "relTolerance": 0.0002,
1620
+ "data": { "kind": "constant", "value": 4.5 }
1621
+ }
1622
+ }
1623
+ },
1624
+ {
1625
+ "name": "convtranspose3d_tiled_reg_large_spatial_f32",
1626
+ "provenance": {
1627
+ "notes": "Portable register-tiled rank-5 coverage with N=8*32*32 and M=32. Pointwise weights isolate the large matrix path and 3D tensor plumbing while tile eligibility remains manifest-derived."
1628
+ },
1629
+ "attrs": {},
1630
+ "inputs": {
1631
+ "x": { "dtype": "float32", "shape": [1, 32, 8, 32, 32], "data": { "kind": "constant", "value": 1.0 } },
1632
+ "w": { "dtype": "float32", "shape": [32, 32, 1, 1, 1], "data": { "kind": "constant", "value": 0.03125 } }
1633
+ },
1634
+ "outputs": {
1635
+ "y": {
1636
+ "dtype": "float32",
1637
+ "shape": [1, 32, 8, 32, 32],
1638
+ "tolerance": 0.0002,
1639
+ "relTolerance": 0.0002,
1640
+ "data": { "kind": "constant", "value": 1.0 }
1641
+ }
1642
+ }
1643
+ },
1644
+ {
1645
+ "name": "convtranspose3d_tiled_bias_reg_large_spatial_f32",
1646
+ "provenance": {
1647
+ "notes": "Bias companion for the portable register-tiled rank-5 path, validating that bias stays in the output-stationary col2im epilogue rather than the intermediate GEMM."
1648
+ },
1649
+ "attrs": {},
1650
+ "inputs": {
1651
+ "x": { "dtype": "float32", "shape": [1, 32, 8, 32, 32], "data": { "kind": "constant", "value": 1.0 } },
1652
+ "w": { "dtype": "float32", "shape": [32, 32, 1, 1, 1], "data": { "kind": "constant", "value": 0.03125 } },
1653
+ "bias": { "dtype": "float32", "shape": [32], "data": { "kind": "constant", "value": 0.5 } }
1654
+ },
1655
+ "outputs": {
1656
+ "y": {
1657
+ "dtype": "float32",
1658
+ "shape": [1, 32, 8, 32, 32],
1659
+ "tolerance": 0.0002,
1660
+ "relTolerance": 0.0002,
1661
+ "data": { "kind": "constant", "value": 1.5 }
1662
+ }
1663
+ }
1664
+ },
1665
+ {
1666
+ "name": "exact_tiling_stride4_kernel4_gemm_bias",
1667
+ "provenance": {
1668
+ "notes": "Kernel == stride with no padding, no dilation and no output padding: the taps TILE the output rather than overlapping it, so col2im takes its collapsed single-tap path instead of the KH*KW gather. Shaped after Depth Anything V2's factor-4 reassemble (x [1,384,37,49], w [384,48,4,4]). Without a case in this quadrant the collapsed branch had zero coverage: poisoning it left all 163 ConvTranspose tests green."
1669
+ },
1670
+ "attrs": { "strides": [4, 4] },
1671
+ "inputs": {
1672
+ "x": {
1673
+ "dtype": "float32",
1674
+ "shape": [1, 16, 5, 7],
1675
+ "data": { "kind": "fillFloat32", "sinStep": 0.03, "cosStep": 0.07, "scale": 0.4 }
1676
+ },
1677
+ "w": {
1678
+ "dtype": "float32",
1679
+ "shape": [16, 8, 4, 4],
1680
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.11, "scale": 0.4 }
1681
+ },
1682
+ "bias": {
1683
+ "dtype": "float32",
1684
+ "shape": [8],
1685
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.19, "scale": 0.3 }
1686
+ }
1687
+ },
1688
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 8, 20, 28], "tolerance": 0.0005 } }
1689
+ },
1690
+ {
1691
+ "name": "exact_tiling_nonsquare_stride2x3_no_bias",
1692
+ "provenance": {
1693
+ "notes": "Kernel == stride on BOTH axes but with different strides per axis (2 x 3), so a kh/kw or inW/outW swap in the collapsed col2im tap cannot cancel out the way it would on a square kernel."
1694
+ },
1695
+ "attrs": { "strides": [2, 3] },
1696
+ "inputs": {
1697
+ "x": {
1698
+ "dtype": "float32",
1699
+ "shape": [1, 8, 4, 5],
1700
+ "data": { "kind": "fillFloat32", "sinStep": 0.023, "cosStep": 0.061, "scale": 0.4 }
1701
+ },
1702
+ "w": {
1703
+ "dtype": "float32",
1704
+ "shape": [8, 6, 2, 3],
1705
+ "data": { "kind": "fillFloat32", "sinStep": 0.047, "cosStep": 0.089, "scale": 0.4 }
1706
+ }
1707
+ },
1708
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 6, 8, 15], "tolerance": 0.0005 } }
1709
+ },
1710
+ {
1711
+ "name": "exact_tiling_stride2_kernel2_f16_bias",
1712
+ "provenance": {
1713
+ "notes": "The f16 im2col-GEMM family shares the same col2im shader, so the collapsed tap needs a case on that side of the dtype split too. Shaped after Depth Anything V2's factor-2 reassemble."
1714
+ },
1715
+ "attrs": { "strides": [2, 2] },
1716
+ "inputs": {
1717
+ "x": {
1718
+ "dtype": "float16",
1719
+ "shape": [1, 32, 8, 8],
1720
+ "data": { "kind": "fillFloat32", "sinStep": 0.03, "cosStep": 0.07, "scale": 0.4 }
1721
+ },
1722
+ "w": {
1723
+ "dtype": "float16",
1724
+ "shape": [32, 16, 2, 2],
1725
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.11, "scale": 0.4 }
1726
+ },
1727
+ "bias": {
1728
+ "dtype": "float16",
1729
+ "shape": [16],
1730
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.19, "scale": 0.3 }
1731
+ }
1732
+ },
1733
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 16, 16, 16], "tolerance": 0.03 } }
1734
+ },
1735
+ {
1736
+ "name": "cols_gemm_2d_bias_m_and_n_tail",
1737
+ "provenance": {
1738
+ "notes": "An f32 rank-4 im2col-GEMM whose channel count is a multiple of 32 and whose BOTH gemm axes are ragged: gemmM = 12*3*3 = 108 tails a 32-row tile and gemmN = 9*11 = 99 tails a 64-column tile. Written to cover a subgroup-matrix 2D path that was then reverted, and kept because it is the shape that CAUGHT it: the shared sgmat kernel's materialized B load has no N guard, so this case read maxAbs 6.6 wrong while the same shape at N = 64 was exact."
1739
+ },
1740
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
1741
+ "inputs": {
1742
+ "x": {
1743
+ "dtype": "float32",
1744
+ "shape": [1, 32, 9, 11],
1745
+ "data": { "kind": "fillFloat32", "sinStep": 0.031, "cosStep": 0.067, "scale": 0.4 }
1746
+ },
1747
+ "w": {
1748
+ "dtype": "float32",
1749
+ "shape": [32, 12, 3, 3],
1750
+ "data": { "kind": "fillFloat32", "sinStep": 0.053, "cosStep": 0.089, "scale": 0.4 }
1751
+ },
1752
+ "bias": {
1753
+ "dtype": "float32",
1754
+ "shape": [12],
1755
+ "data": { "kind": "fillFloat32", "sinStep": 0.131, "cosStep": 0.197, "scale": 0.3 }
1756
+ }
1757
+ },
1758
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 12, 17, 21], "tolerance": 0.0005 } }
1759
+ },
1760
+ {
1761
+ "name": "cols_gemm_2d_no_bias_m_aligned_n_below_tile",
1762
+ "provenance": {
1763
+ "notes": "The companion: gemmM = 16*4*4 = 256 is tile-aligned and gemmN = 5*6 = 30 is BELOW one column tile, the narrowest an N tail gets. Same provenance — it is half of the pair that showed a ragged N is not safe on the shared subgroup-matrix kernel."
1764
+ },
1765
+ "attrs": { "strides": [4, 4] },
1766
+ "inputs": {
1767
+ "x": {
1768
+ "dtype": "float32",
1769
+ "shape": [1, 64, 5, 6],
1770
+ "data": { "kind": "fillFloat32", "sinStep": 0.023, "cosStep": 0.061, "scale": 0.4 }
1771
+ },
1772
+ "w": {
1773
+ "dtype": "float32",
1774
+ "shape": [64, 16, 4, 4],
1775
+ "data": { "kind": "fillFloat32", "sinStep": 0.047, "cosStep": 0.083, "scale": 0.4 }
1776
+ }
1777
+ },
1778
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 16, 20, 24], "tolerance": 0.0005 } }
1779
+ },
1780
+ {
1781
+ "name": "f16_1d_bias",
1782
+ "provenance": {
1783
+ "notes": "Covers rank-3 f16 ConvTranspose with bias. Exact half-valued inputs keep bias-indexing errors observable without f16 rounding ambiguity."
1784
+ },
1785
+ "attrs": {},
1786
+ "inputs": {
1787
+ "x": {
1788
+ "dtype": "float16",
1789
+ "shape": [1, 2, 8],
1790
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/f16_1d_nobias_input_x" } }
1791
+ },
1792
+ "w": {
1793
+ "dtype": "float16",
1794
+ "shape": [2, 3, 3],
1795
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/f16_1d_nobias_input_w" } }
1796
+ },
1797
+ "bias": { "dtype": "float16", "shape": [3], "data": { "kind": "values", "values": [0.25, -0.5, 1.5] } }
1798
+ },
1799
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 3, 10], "tolerance": 0.02 } }
1800
+ },
1801
+ {
1802
+ "name": "f16_1d_grouped_nobias",
1803
+ "provenance": {
1804
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
1805
+ "test": "ConvTransposeTest.ConvTranspose_1D_OutputShape_1_group_2_for_transpose_path",
1806
+ "notes": "Covers grouped rank-3 f16 ConvTranspose without bias. Distinct per-group weights 2 and 3 make group-indexing errors observable."
1807
+ },
1808
+ "attrs": { "group": 2 },
1809
+ "inputs": {
1810
+ "x": { "dtype": "float16", "shape": [1, 2, 2], "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0] } },
1811
+ "w": { "dtype": "float16", "shape": [2, 1, 1], "data": { "kind": "values", "values": [2.0, 3.0] } }
1812
+ },
1813
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 2, 2], "tolerance": 0.01 } }
1814
+ },
1815
+ {
1816
+ "name": "f16_1d_grouped_bias",
1817
+ "provenance": {
1818
+ "source": "onnxruntime/test/providers/cpu/nn/conv_transpose_op_test.cc",
1819
+ "test": "ConvTransposeTest.ConvTranspose_1D_OutputShape_1_group_2_for_transpose_path",
1820
+ "notes": "Covers grouped rank-3 f16 ConvTranspose with per-output-channel bias. The two bias entries differ in sign and magnitude, making a wrong channel index observable."
1821
+ },
1822
+ "attrs": { "group": 2 },
1823
+ "inputs": {
1824
+ "x": { "dtype": "float16", "shape": [1, 2, 2], "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0] } },
1825
+ "w": { "dtype": "float16", "shape": [2, 1, 1], "data": { "kind": "values", "values": [2.0, 3.0] } },
1826
+ "bias": { "dtype": "float16", "shape": [2], "data": { "kind": "values", "values": [0.25, -0.5] } }
1827
+ },
1828
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 2, 2], "tolerance": 0.01 } }
1829
+ },
1830
+ {
1831
+ "name": "zero_input_spatial_dim_3d_empty_output",
1832
+ "provenance": {
1833
+ "notes": "A zero input-depth dimension with a unit kernel produces a zero output-depth dimension. This exercises the direct rank-5 fallback when the GEMM column dimension is zero and the nonempty-output reduction does not apply."
1834
+ },
1835
+ "attrs": {},
1836
+ "inputs": {
1837
+ "x": { "dtype": "float32", "shape": [1, 1, 0, 2, 2], "data": { "kind": "values", "values": [] } },
1838
+ "w": { "dtype": "float32", "shape": [1, 1, 1, 1, 1], "data": { "kind": "values", "values": [2.0] } }
1839
+ },
1840
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 0, 2, 2], "tolerance": 0 } }
1841
+ },
1842
+ {
1843
+ "name": "zero_input_spatial_dim_3d_empty_output_bias",
1844
+ "provenance": {
1845
+ "notes": "Bias does not make an output tensor with a zero spatial dimension nonempty. This is the bias-bearing sibling for the direct rank-5 zero-column fallback."
1846
+ },
1847
+ "attrs": {},
1848
+ "inputs": {
1849
+ "x": { "dtype": "float32", "shape": [1, 1, 0, 2, 2], "data": { "kind": "values", "values": [] } },
1850
+ "w": { "dtype": "float32", "shape": [1, 1, 1, 1, 1], "data": { "kind": "values", "values": [2.0] } },
1851
+ "bias": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.75] } }
1852
+ },
1853
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 0, 2, 2], "tolerance": 0 } }
1854
+ }
1855
+ ]
1856
+ }