Xenova HF Staff commited on
Commit
2def0ac
·
verified ·
1 Parent(s): 900921f

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -59,6 +59,8 @@ One implementation is selected per call from the device capabilities, the reques
59
  - `ncdhw3d_implicit_sgmat_bias` — Stride-1 transposed convolution as a forward convolution of the unchanged input with tap-reversed weights and begin pads of kernel-1-pad: the implicit-gather GEMM reads input windows directly and stores output elements directly, so no column matrix is materialized and no scatter pass runs, while the reduction spans the full in-channel-by-tap depth.
60
  - `nchw2d_grouped_stride_phase_blocked` — Grouped stride-phase ConvTranspose where one invocation owns one output pixel and a block of that group's output channels: every gathered input sample feeds the whole block and the group's weight slab is staged once per workgroup. It serves grouped stride-two-or-more shapes whose per-group weights fit workgroup memory.
61
  - `nchw2d_grouped_stride_phase_blocked_bias` — Grouped stride-phase ConvTranspose where one invocation owns one output pixel and a block of that group's output channels: every gathered input sample feeds the whole block and the group's weight slab is staged once per workgroup. It serves grouped stride-two-or-more shapes whose per-group weights fit workgroup memory.
 
 
62
 
63
  ## Device requirements
64
 
@@ -69,7 +71,7 @@ Some implementation variants require `subgroup-matrix`, `shader-f16`, and `subgr
69
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
70
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
71
  - [`test.json`](build/webgpu/test.json) — correctness cases
72
- - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
73
  - [`conv-1x1-gemm-tiled-reg.wgsl.jinja`](build/webgpu/conv-1x1-gemm-tiled-reg.wgsl.jinja)
74
  - [`conv-1x1-gemm-tiled.wgsl.jinja`](build/webgpu/conv-1x1-gemm-tiled.wgsl.jinja)
75
  - [`conv-1x1-subgroup-matrix.wgsl.jinja`](build/webgpu/conv-1x1-subgroup-matrix.wgsl.jinja)
@@ -89,7 +91,7 @@ Some implementation variants require `subgroup-matrix`, `shader-f16`, and `subgr
89
  ## Use with `@huggingface/kernels`
90
 
91
  ```sh
92
- npm install --save-exact @huggingface/kernels@0.0.1-preview.2
93
  ```
94
 
95
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
 
59
  - `ncdhw3d_implicit_sgmat_bias` — Stride-1 transposed convolution as a forward convolution of the unchanged input with tap-reversed weights and begin pads of kernel-1-pad: the implicit-gather GEMM reads input windows directly and stores output elements directly, so no column matrix is materialized and no scatter pass runs, while the reduction spans the full in-channel-by-tap depth.
60
  - `nchw2d_grouped_stride_phase_blocked` — Grouped stride-phase ConvTranspose where one invocation owns one output pixel and a block of that group's output channels: every gathered input sample feeds the whole block and the group's weight slab is staged once per workgroup. It serves grouped stride-two-or-more shapes whose per-group weights fit workgroup memory.
61
  - `nchw2d_grouped_stride_phase_blocked_bias` — Grouped stride-phase ConvTranspose where one invocation owns one output pixel and a block of that group's output channels: every gathered input sample feeds the whole block and the group's weight slab is staged once per workgroup. It serves grouped stride-two-or-more shapes whose per-group weights fit workgroup memory.
62
+ - `nchw2d_exact_store_gemm_f32` — Exact-tiling f32 transposed convolution retains the register GEMM contraction and stores directly into output, removing the column intermediate and gather pass.
63
+ - `nchw2d_exact_store_gemm_f32_bias` — Exact-tiling f32 transposed convolution retains the register GEMM contraction and stores directly into output, removing the column intermediate and gather pass.
64
 
65
  ## Device requirements
66
 
 
71
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
72
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
73
  - [`test.json`](build/webgpu/test.json) — correctness cases
74
+ - [`bench.json`](build/webgpu/bench.json) — benchmark cases
75
  - [`conv-1x1-gemm-tiled-reg.wgsl.jinja`](build/webgpu/conv-1x1-gemm-tiled-reg.wgsl.jinja)
76
  - [`conv-1x1-gemm-tiled.wgsl.jinja`](build/webgpu/conv-1x1-gemm-tiled.wgsl.jinja)
77
  - [`conv-1x1-subgroup-matrix.wgsl.jinja`](build/webgpu/conv-1x1-subgroup-matrix.wgsl.jinja)
 
91
  ## Use with `@huggingface/kernels`
92
 
93
  ```sh
94
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
95
  ```
96
 
97
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
build/webgpu/bench.json CHANGED
@@ -342,7 +342,7 @@
342
  "preset": "stress",
343
  "provenance": {
344
  "source": "synthetic benchmark",
345
- "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."
346
  },
347
  "vars": {
348
  "batch": 1,
@@ -446,6 +446,45 @@
446
  }
447
  ]
448
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
449
  }
450
  ]
451
  }
 
342
  "preset": "stress",
343
  "provenance": {
344
  "source": "synthetic benchmark",
345
+ "notes": "Grouped stride-two transposed convolution with channel bias; the paired no-bias case isolates the effect of the optional input."
346
  },
347
  "vars": {
348
  "batch": 1,
 
446
  }
447
  ]
448
  }
449
+ },
450
+ {
451
+ "name": "conv-transpose-f32-exact-store-b2c65m80-29x53-k3x2-nobias",
452
+ "preset": "edge",
453
+ "provenance": { "notes": "Rectangular nonoverlapping taps with batch and channel tails." },
454
+ "attrs": { "strides": [3, 2] },
455
+ "inputs": {
456
+ "x": { "shape": [2, 65, 29, 53], "dtype": "float32", "dist": "normal", "seed": 9421, "scale": 0.2 },
457
+ "w": { "shape": [65, 80, 3, 2], "dtype": "float32", "dist": "normal", "seed": 9422, "scale": 0.1 }
458
+ },
459
+ "outputs": { "y": { "shape": [2, 80, 87, 106], "dtype": "float32" } },
460
+ "bench": {
461
+ "metrics": [
462
+ {
463
+ "type": "gflops",
464
+ "value": "2 * dim(shapes.x, 0) * dim(shapes.x, 1) * dim(shapes.x, 2) * dim(shapes.x, 3) * dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)"
465
+ }
466
+ ]
467
+ }
468
+ },
469
+ {
470
+ "name": "conv-transpose-f32-exact-store-b1c33m48-41x43-k2x3-bias",
471
+ "preset": "edge",
472
+ "provenance": { "notes": "Rectangular nonoverlapping taps with batch and channel tails." },
473
+ "attrs": { "strides": [2, 3] },
474
+ "inputs": {
475
+ "x": { "shape": [1, 33, 41, 43], "dtype": "float32", "dist": "normal", "seed": 9421, "scale": 0.2 },
476
+ "w": { "shape": [33, 48, 2, 3], "dtype": "float32", "dist": "normal", "seed": 9422, "scale": 0.1 },
477
+ "bias": { "shape": [48], "dtype": "float32", "dist": "normal", "seed": 9423, "scale": 0.1 }
478
+ },
479
+ "outputs": { "y": { "shape": [1, 48, 82, 129], "dtype": "float32" } },
480
+ "bench": {
481
+ "metrics": [
482
+ {
483
+ "type": "gflops",
484
+ "value": "2 * dim(shapes.x, 0) * dim(shapes.x, 1) * dim(shapes.x, 2) * dim(shapes.x, 3) * dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)"
485
+ }
486
+ ]
487
+ }
488
  }
489
  ]
490
  }
build/webgpu/conv-1x1-gemm-tiled-reg.wgsl.jinja CHANGED
@@ -14,45 +14,14 @@
14
  // instead of 4 * (TM + TN) scalars, so a shared word feeds four times as many
15
  // FMAs and the K loop runs four accumulation steps per iteration.
16
  {{ env.wgsl.resourceDeclarations }}
17
- {% set hasActivation = hasActivation is defined and hasActivation %}
18
- {% set activation = activation | default("") %}
19
- {% set actAlpha = actAlpha | default(0.0) %}
20
- {% set actBeta = actBeta | default(0.0) %}
21
- {% if hasActivation %}
22
- // Apply the fused activation in the f32 accumulator before the single output
23
- // cast, avoiding an intermediate convolution tensor.
24
- {% macro fused_act_return(mode, alpha, beta) -%}
25
- {% if mode == "Relu" %}
26
- return max(v, 0.0);
27
- {% elif mode == "Clip" %}
28
- return clamp(v, f32({{ alpha }}), f32({{ beta }}));
29
- {% elif mode == "LeakyRelu" %}
30
- return select(v * f32({{ alpha }}), v, v >= 0.0);
31
- {% elif mode == "Sigmoid" %}
32
- return 1.0 / (1.0 + exp(-v));
33
- {% elif mode == "Tanh" %}
34
- // tanh is already +/-1 to f32 precision at the clamp bounds. Clamping before
35
- // the builtin preserves that saturated result for larger accumulators.
36
- return tanh(clamp(v, -10.0, 10.0));
37
- {% elif mode == "HardSigmoid" %}
38
- return clamp(f32({{ alpha }}) * v + f32({{ beta }}), 0.0, 1.0);
39
- {% else %}
40
- return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
41
- {% endif %}
42
- {%- endmacro -%}
43
- fn fused_act(v: f32) -> f32 {
44
- {{ fused_act_return(activation, actAlpha, actBeta) -}}
45
- }
46
- {% endif %}
47
-
48
- {% set tileT = "f16" if usesF16 else "f32" -%}
49
- {%- set GEMM_BK = gemmKTile if gemmKTile is defined else 16 %}
50
- {% set GEMM_TM = gemmThreadRows if gemmThreadRows is defined else 8 %}
51
- {% set GEMM_WG_X = gemmWorkgroupX if gemmWorkgroupX is defined else 16 %}
52
- {% set GEMM_BM = gemmMTile if gemmMTile is defined else 64 %}
53
- {% set GEMM_BN = gemmNTile if gemmNTile is defined else GEMM_WG_X * 4 %}
54
- {% set GEMM_TN = gemmThreadColumns if gemmThreadColumns is defined else (GEMM_BN / GEMM_WG_X)|int %}
55
- {% set GEMM_WG_Y = gemmWorkgroupY if gemmWorkgroupY is defined else (GEMM_BM / GEMM_TM)|int %}
56
  {% set components = ["x", "y", "z", "w"] %}
57
  const BK: u32 = {{ GEMM_BK }}u;
58
  const BM: u32 = {{ GEMM_BM }}u;
@@ -70,18 +39,11 @@ const BN_VECS: u32 = BN / 4u;
70
  // Overlapping windows may reread input values, trading address arithmetic and
71
  // repeated input loads for writing and rereading an expanded column matrix.
72
  {% set implicitIm2col = implicitIm2col is defined and implicitIm2col %}
73
- {% set fusedNarrowProjection = fusedNarrowProjection is defined and fusedNarrowProjection %}
74
- {% set projectionChannels = projectionOutChannels | default(0) %}
75
- {% set projectionInputAct = inputActivation | default("none") %}
76
- {% set projectionOutputAct = outputActivation | default("none") %}
77
- {% set narrowProjectionTile = "tileB" if GEMM_BK >= GEMM_BM else "projectionTile" %}
78
- {% set splitKValue = splitK if splitK is defined else 1 %}
79
- {% set splitKPartial = splitKValue > 1 %}
80
- {% set gemmKLimit = "kEnd" if splitKPartial else "K" %}
81
  {% set implicitGatherMode = tunables.IMPLICIT_TILED_GATHER_MODE if tunables.IMPLICIT_TILED_GATHER_MODE is defined else 0 %}
82
- {% set implicitGatherWgSize = GEMM_WG_X * GEMM_WG_Y %}
83
- {% set implicitGatherColumnVecs = (GEMM_BN / 4)|int %}
84
- {% set implicitGatherKChunks = (implicitGatherWgSize / implicitGatherColumnVecs)|int %}
85
  {% set implicitGatherDivides = implicitGatherWgSize % implicitGatherColumnVecs == 0 and implicitGatherKChunks > 0 and GEMM_BK % implicitGatherKChunks == 0 %}
86
  {% set useCarriedImplicitGather = implicitIm2col and implicitGatherMode == 1 and implicitGatherDivides %}
87
  {% if useCarriedImplicitGather %}
@@ -102,72 +64,36 @@ const CONV_IN_W: u32 = {{ convInW }}u;
102
  const CONV_OUT_W: u32 = {{ convOutW }}u;
103
  const CONV_IN_CHANNELS: u32 = {{ convInChannels }}u;
104
  {% endif %}
105
-
106
- {% if fusedNarrowProjection %}
107
- {% set inputActivation = inputActivation | default("none") %}
108
- {% set outputActivation = outputActivation | default("none") %}
109
- {% if inputActivation == "relu" or outputActivation == "relu" %}
110
- fn is_nan_f32(value: f32) -> bool {
111
- let bits = bitcast<u32>(value);
112
- return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
113
- }
114
-
115
- fn projection_relu(value: f32) -> f32 {
116
- var out = max(value, 0.0);
117
- if (is_nan_f32(value)) {
118
- out = value;
119
- }
120
- return out;
121
- }
122
- {% endif %}
123
- {% if outputActivation == "sigmoid" %}
124
- fn sigmoid_safe(x: f32) -> f32 {
125
- if (x >= 0.0) {
126
- let z = exp(-x);
127
- return 1.0 / (1.0 + z);
128
- }
129
- let z = exp(x);
130
- return z / (1.0 + z);
131
- }
132
- {% endif %}
133
-
134
- const PROJECTION_OUT_C: u32 = {{ projectionChannels }}u;
135
- {% else %}{% set emitConvStoreOut = not splitKPartial %}{% set hasZ = hasZ is defined and hasZ %}
136
- {% if emitConvStoreOut | default(true) %}fn store_out(m: u32, n: u32, yBase: u32, raw: f32) {
137
  if (m >= params.M || n >= params.N) {
138
  return;
139
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  var v = raw;
141
- {% if hasZ %}
142
- v = v + f32(zResidual[yBase + m * params.N + n]);
143
- {% endif %}
144
- {% if hasActivation %}
145
- v = fused_act(v);
146
  {% endif %}
147
  y[yBase + m * params.N + n] = {{ T }}(v);
148
- }
149
- {% endif %}
150
  {% endif %}
 
151
 
152
  var<workgroup> tileA: array<array<vec4<{{ tileT }}>, AK_VECS>, BM>;
153
  var<workgroup> tileB: array<array<vec4<{{ tileT }}>, BN_VECS>, BK>;
154
- {% if fusedNarrowProjection and GEMM_BK < GEMM_BM %}
155
- // BK16 cannot reuse the 16-row input tile to publish a BM32 intermediate.
156
- // The extra 32x64 f32 tile keeps the total at 14 KiB, below WebGPU's
157
- // guaranteed 16 KiB workgroup-storage floor.
158
- var<workgroup> projectionTile: array<array<vec4<f32>, BN_VECS>, BM>;
159
- {% endif %}
160
- {% macro publish_projection_input(localRow, localVec, component, globalRow, globalColumn, raw) %}
161
- if ({{ globalRow }} < M && {{ globalColumn }} < N) {
162
- var projectionInput = {{ raw }};
163
- {% if projectionInputAct == "relu" %}
164
- projectionInput = projection_relu(projectionInput);
165
- {% endif %}
166
- {{ narrowProjectionTile }}[{{ localRow }}][{{ localVec }}].{{ component }} = projectionInput;
167
- } else {
168
- {{ narrowProjectionTile }}[{{ localRow }}][{{ localVec }}].{{ component }} = 0.0;
169
- }
170
- {%- endmacro %}
171
  {% macro load_a_vec4(rowBase, columnBase, rowLimit, columnLimit) %}
172
  for (var linear = li; linear < BM * AK_VECS; linear += WG_SIZE) {
173
  let ar = linear / AK_VECS;
@@ -188,8 +114,7 @@ var<workgroup> projectionTile: array<array<vec4<f32>, BN_VECS>, BM>;
188
  }
189
  }
190
  tileA[ar][ac4] = av;
191
- }
192
- {%- endmacro %}
193
  {% macro load_b_vec4(rowBase, columnBase, rowLimit, columnLimit) %}
194
  for (var linear = li; linear < BK * BN_VECS; linear += WG_SIZE) {
195
  let br = linear / BN_VECS;
@@ -210,8 +135,7 @@ var<workgroup> projectionTile: array<array<vec4<f32>, BN_VECS>, BM>;
210
  }
211
  }
212
  tileB[br][bc4] = bvec;
213
- }
214
- {%- endmacro %}
215
  {% macro load_b_implicit_vec4(rowBase, columnBase, rowLimit, columnLimit) %}
216
  for (var linear = li; linear < BK * BN_VECS; linear += WG_SIZE) {
217
  let br = linear / BN_VECS;
@@ -239,8 +163,7 @@ var<workgroup> projectionTile: array<array<vec4<f32>, BN_VECS>, BM>;
239
  }
240
  }
241
  tileB[br][bc4] = bvec;
242
- }
243
- {%- endmacro %}
244
  {% macro load_b_implicit_carried(rowBase, columnBase, rowLimit, columnLimit) %}
245
  let bColVec = li / {{ implicitGatherKChunks }}u;
246
  let bChunk = li % {{ implicitGatherKChunks }}u;
@@ -324,8 +247,7 @@ var<workgroup> projectionTile: array<array<vec4<f32>, BN_VECS>, BM>;
324
  }
325
  }
326
  }
327
- }
328
- {%- endmacro %}
329
 
330
  @compute @workgroup_size({{ GEMM_WG_X }}, {{ GEMM_WG_Y }}, 1)
331
  fn main(
@@ -377,59 +299,11 @@ fn main(
377
  workgroupBarrier();
378
  }
379
 
380
- {% if not fusedNarrowProjection %}
381
  let yBatchBase = batch * M * N;
382
- {% endif %}
383
  let m0 = mBase + lid.y * TM;
384
  let n0 = nBase + lid.x * TN;
385
  {% for row in range(GEMM_TM) %}
386
  {% for column in range(GEMM_TN) %}
387
- {% if fusedNarrowProjection %}
388
- {{ 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]) }}
389
- {% else %}
390
  store_out(m0 + {{ row }}u, n0 + {{ column }}u, yBatchBase, acc{{ row }}.{{ components[column] }});
391
- {% endif %}
392
- {% endfor %}
393
- {% endfor %}{% if fusedNarrowProjection %}
394
-
395
- // Conv's final K-tile barrier makes tileB dead before it becomes the
396
- // intermediate tile. Every lane publishes its unique micro-tile, then one
397
- // lane per spatial vector word consumes all logical M rows in increasing
398
- // order, four adjacent columns at a time. Invalid M/N-tail cells are
399
- // initialized above, so the barrier is uniform and no lane can observe a
400
- // stale input-tile value.
401
- workgroupBarrier();
402
- if (li < BN_VECS) {
403
- let projectionNBase = nBase + li * 4u;
404
- {% for oc in range(projectionChannels) %}
405
- var projectionAcc{{ oc }} = vec4<f32>(0.0);
406
- {% endfor %}
407
- for (var channel = 0u; channel < M; channel = channel + 1u) {
408
- let projectionValues = vec4<f32>({{ narrowProjectionTile }}[channel][li]);
409
- {% for oc in range(projectionChannels) %}
410
- projectionAcc{{ oc }} = projectionAcc{{ oc }} + projectionValues * f32(projectionW[{{ oc }}u * M + channel]);
411
  {% endfor %}
412
- }
413
-
414
- let projectionYBase = batch * PROJECTION_OUT_C * N;
415
- {% for oc in range(projectionChannels) %}
416
- let projected{{ oc }} = projectionAcc{{ oc }};
417
- {% endfor %}
418
- {% for column in range(4) %}
419
- let projectionN{{ column }} = projectionNBase + {{ column }}u;
420
- if (projectionN{{ column }} < N) {
421
- {% for oc in range(projectionChannels) %}
422
- {% if projectionOutputAct == "relu" %}
423
- let activated{{ oc }}_{{ column }} = projection_relu(projected{{ oc }}.{{ components[column] }});
424
- {% elif projectionOutputAct == "sigmoid" %}
425
- let activated{{ oc }}_{{ column }} = sigmoid_safe(projected{{ oc }}.{{ components[column] }});
426
- {% else %}
427
- let activated{{ oc }}_{{ column }} = projected{{ oc }}.{{ components[column] }};
428
- {% endif %}
429
- y[projectionYBase + {{ oc }}u * N + projectionN{{ column }}] = activated{{ oc }}_{{ column }};
430
- {% endfor %}
431
- }
432
- {% endfor %}
433
- }
434
- {% endif %}
435
- }
 
14
  // instead of 4 * (TM + TN) scalars, so a shared word feeds four times as many
15
  // FMAs and the K loop runs four accumulation steps per iteration.
16
  {{ env.wgsl.resourceDeclarations }}
17
+ {% set tileT = "f16" if usesF16 else "f32" %}
18
+ {% set GEMM_BK = gemmKTile if gemmKTile is defined else 16 %}
19
+ {% set GEMM_TM = 8 %}
20
+ {% set GEMM_WG_X = 16 %}
21
+ {% set GEMM_BM = 64 %}
22
+ {% set GEMM_BN = 64 %}
23
+ {% set GEMM_TN = 4 %}
24
+ {% set GEMM_WG_Y = 8 %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  {% set components = ["x", "y", "z", "w"] %}
26
  const BK: u32 = {{ GEMM_BK }}u;
27
  const BM: u32 = {{ GEMM_BM }}u;
 
39
  // Overlapping windows may reread input values, trading address arithmetic and
40
  // repeated input loads for writing and rereading an expanded column matrix.
41
  {% set implicitIm2col = implicitIm2col is defined and implicitIm2col %}
42
+ {% set gemmKLimit = "K" %}
 
 
 
 
 
 
 
43
  {% set implicitGatherMode = tunables.IMPLICIT_TILED_GATHER_MODE if tunables.IMPLICIT_TILED_GATHER_MODE is defined else 0 %}
44
+ {% set implicitGatherWgSize = 128 %}
45
+ {% set implicitGatherColumnVecs = 16 %}
46
+ {% set implicitGatherKChunks = 8 %}
47
  {% set implicitGatherDivides = implicitGatherWgSize % implicitGatherColumnVecs == 0 and implicitGatherKChunks > 0 and GEMM_BK % implicitGatherKChunks == 0 %}
48
  {% set useCarriedImplicitGather = implicitIm2col and implicitGatherMode == 1 and implicitGatherDivides %}
49
  {% if useCarriedImplicitGather %}
 
64
  const CONV_OUT_W: u32 = {{ convOutW }}u;
65
  const CONV_IN_CHANNELS: u32 = {{ convInChannels }}u;
66
  {% endif %}
67
+ fn store_out(m: u32, n: u32, yBase: u32, raw: f32) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  if (m >= params.M || n >= params.N) {
69
  return;
70
  }
71
+ {% if convTransposeExactStore | default(false) %}
72
+ {% set convTransposeKernelH = convTransposeKernelH | default(1) %}
73
+ {% set convTransposeKernelW = convTransposeKernelW | default(1) %}
74
+ {% set convTransposeInputW = convTransposeInputW | default(1) %}
75
+ {% set convTransposeOutputH = convTransposeOutputH | default(1) %}
76
+ {% set convTransposeOutputW = convTransposeOutputW | default(1) %}
77
+ // Nonoverlapping transposed-convolution taps map each matrix element to
78
+ // exactly one output. Keep the old col2im bias-plus-dot rounding order.
79
+ let kernelArea = {{ convTransposeKernelH }}u * {{ convTransposeKernelW }}u;
80
+ let oc = m / kernelArea;
81
+ let tap = m % kernelArea;
82
+ let oh = (n / {{ convTransposeInputW }}u) * {{ convTransposeKernelH }}u + tap / {{ convTransposeKernelW }}u;
83
+ let ow = (n % {{ convTransposeInputW }}u) * {{ convTransposeKernelW }}u + tap % {{ convTransposeKernelW }}u;
84
+ let outputIndex = yBase + (oc * {{ convTransposeOutputH }}u + oh) * {{ convTransposeOutputW }}u + ow;
85
+ y[outputIndex] = {% if hasBias %}f32(bias[oc]){% else %}0.0{% endif %} + raw;
86
+ {% else %}
87
  var v = raw;
88
+ {% if hasBias %}
89
+ v = v + f32(bias[m]);
 
 
 
90
  {% endif %}
91
  y[yBase + m * params.N + n] = {{ T }}(v);
 
 
92
  {% endif %}
93
+ }
94
 
95
  var<workgroup> tileA: array<array<vec4<{{ tileT }}>, AK_VECS>, BM>;
96
  var<workgroup> tileB: array<array<vec4<{{ tileT }}>, BN_VECS>, BK>;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  {% macro load_a_vec4(rowBase, columnBase, rowLimit, columnLimit) %}
98
  for (var linear = li; linear < BM * AK_VECS; linear += WG_SIZE) {
99
  let ar = linear / AK_VECS;
 
114
  }
115
  }
116
  tileA[ar][ac4] = av;
117
+ }{% endmacro %}
 
118
  {% macro load_b_vec4(rowBase, columnBase, rowLimit, columnLimit) %}
119
  for (var linear = li; linear < BK * BN_VECS; linear += WG_SIZE) {
120
  let br = linear / BN_VECS;
 
135
  }
136
  }
137
  tileB[br][bc4] = bvec;
138
+ }{% endmacro %}
 
139
  {% macro load_b_implicit_vec4(rowBase, columnBase, rowLimit, columnLimit) %}
140
  for (var linear = li; linear < BK * BN_VECS; linear += WG_SIZE) {
141
  let br = linear / BN_VECS;
 
163
  }
164
  }
165
  tileB[br][bc4] = bvec;
166
+ }{% endmacro %}
 
167
  {% macro load_b_implicit_carried(rowBase, columnBase, rowLimit, columnLimit) %}
168
  let bColVec = li / {{ implicitGatherKChunks }}u;
169
  let bChunk = li % {{ implicitGatherKChunks }}u;
 
247
  }
248
  }
249
  }
250
+ }{% endmacro %}
 
251
 
252
  @compute @workgroup_size({{ GEMM_WG_X }}, {{ GEMM_WG_Y }}, 1)
253
  fn main(
 
299
  workgroupBarrier();
300
  }
301
 
 
302
  let yBatchBase = batch * M * N;
 
303
  let m0 = mBase + lid.y * TM;
304
  let n0 = nBase + lid.x * TN;
305
  {% for row in range(GEMM_TM) %}
306
  {% for column in range(GEMM_TN) %}
 
 
 
307
  store_out(m0 + {{ row }}u, n0 + {{ column }}u, yBatchBase, acc{{ row }}.{{ components[column] }});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  {% endfor %}
309
+ {% endfor %}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
build/webgpu/conv-1x1-gemm-tiled.wgsl.jinja CHANGED
@@ -6,64 +6,45 @@
6
  // f16 operands remain packed in workgroup memory and widen when consumed. All
7
  // M, N, and K accesses are bounds-checked.
8
  {{ env.wgsl.resourceDeclarations }}
9
- {% set hasActivation = hasActivation is defined and hasActivation %}
10
- {% set activation = activation | default("") %}
11
- {% set actAlpha = actAlpha | default(0.0) %}
12
- {% set actBeta = actBeta | default(0.0) %}
13
- {% if hasActivation %}
14
- // Apply the fused activation in the f32 accumulator before the single output
15
- // cast, avoiding an intermediate convolution tensor.
16
- {% macro fused_act_return(mode, alpha, beta) -%}
17
- {% if mode == "Relu" %}
18
- return max(v, 0.0);
19
- {% elif mode == "Clip" %}
20
- return clamp(v, f32({{ alpha }}), f32({{ beta }}));
21
- {% elif mode == "LeakyRelu" %}
22
- return select(v * f32({{ alpha }}), v, v >= 0.0);
23
- {% elif mode == "Sigmoid" %}
24
- return 1.0 / (1.0 + exp(-v));
25
- {% elif mode == "Tanh" %}
26
- // tanh is already +/-1 to f32 precision at the clamp bounds. Clamping before
27
- // the builtin preserves that saturated result for larger accumulators.
28
- return tanh(clamp(v, -10.0, 10.0));
29
- {% elif mode == "HardSigmoid" %}
30
- return clamp(f32({{ alpha }}) * v + f32({{ beta }}), 0.0, 1.0);
31
- {% else %}
32
- return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
33
- {% endif %}
34
- {%- endmacro -%}
35
- fn fused_act(v: f32) -> f32 {
36
- {{ fused_act_return(activation, actAlpha, actBeta) -}}
37
- }
38
- {% endif %}
39
-
40
- {% set tileT = "f16" if usesF16 else "f32" -%}
41
  const BK: u32 = 16u;
42
  const BM: u32 = 32u;
43
  const BN: u32 = 32u;
44
 
45
  // Store one output element with the configured bias/residual/activation
46
  // epilogue in the f32 accumulator domain.
47
- {% set hasZ = hasZ is defined and hasZ %}
48
  fn store_out(m: u32, n: u32, yBase: u32, raw: f32) {
49
  if (m >= params.M || n >= params.N) {
50
  return;
51
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  var v = raw;
53
- {% if hasZ %}
54
- v = v + f32(zResidual[yBase + m * params.N + n]);
55
- {% endif %}
56
- {% if hasActivation %}
57
- v = fused_act(v);
58
- {% endif %}
59
  y[yBase + m * params.N + n] = {{ T }}(v);
 
60
  }
61
 
62
-
63
  var<workgroup> tileA: array<array<{{ tileT }}, BK>, BM>;
64
  var<workgroup> tileB: array<array<{{ tileT }}, BN>, BK>;
65
 
66
- @compute @workgroup_size(16, 16, 1)
67
  fn main(
68
  @builtin(workgroup_id) wg: vec3<u32>,
69
  @builtin(local_invocation_id) lid: vec3<u32>
@@ -75,7 +56,7 @@ fn main(
75
  let nBase = wg.x * BN;
76
  let batch = wg.z;
77
  let xBatchBase = batch * K * N;
78
- let li = lid.y * 16u + lid.x;
79
 
80
  var acc00: f32 = 0.0;
81
  var acc01: f32 = 0.0;
@@ -85,8 +66,8 @@ fn main(
85
  for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
86
  let kBase = kt * BK;
87
  // Cooperative load: 32x16 A(=W) tile + 16x32 B(=X[batch]) tile, 256 threads x 2 each.
88
- for (var e: u32 = 0u; e < (BM * BK) / 256u; e = e + 1u) {
89
- let idx = li + e * 256u;
90
  let ar = idx / BK;
91
  let ac = idx % BK;
92
  let am = mBase + ar;
@@ -112,10 +93,9 @@ fn main(
112
  let a1 = f32(tileA[lid.y * 2u + 1u][kk]);
113
  let b0 = f32(tileB[kk][lid.x * 2u]);
114
  let b1 = f32(tileB[kk][lid.x * 2u + 1u]);
115
- acc00 = acc00 + a0 * b0;
116
- acc01 = acc01 + a0 * b1;
117
- acc10 = acc10 + a1 * b0;
118
- acc11 = acc11 + a1 * b1;
119
  }
120
  workgroupBarrier();
121
  }
 
6
  // f16 operands remain packed in workgroup memory and widen when consumed. All
7
  // M, N, and K accesses are bounds-checked.
8
  {{ env.wgsl.resourceDeclarations }}
9
+ {% set accumulatorPrefix = "acc" %}
10
+ {% set tileT = "f32" %}
11
+ {% set wgX = 16 %}
12
+ {% set wgY = 16 %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  const BK: u32 = 16u;
14
  const BM: u32 = 32u;
15
  const BN: u32 = 32u;
16
 
17
  // Store one output element with the configured bias/residual/activation
18
  // epilogue in the f32 accumulator domain.
 
19
  fn store_out(m: u32, n: u32, yBase: u32, raw: f32) {
20
  if (m >= params.M || n >= params.N) {
21
  return;
22
  }
23
+ {% if convTransposeExactStore | default(false) %}
24
+ {% set convTransposeKernelH = convTransposeKernelH | default(1) %}
25
+ {% set convTransposeKernelW = convTransposeKernelW | default(1) %}
26
+ {% set convTransposeInputW = convTransposeInputW | default(1) %}
27
+ {% set convTransposeOutputH = convTransposeOutputH | default(1) %}
28
+ {% set convTransposeOutputW = convTransposeOutputW | default(1) %}
29
+ // Nonoverlapping transposed-convolution taps map each matrix element to
30
+ // exactly one output. Keep the old col2im bias-plus-dot rounding order.
31
+ let kernelArea = {{ convTransposeKernelH }}u * {{ convTransposeKernelW }}u;
32
+ let oc = m / kernelArea;
33
+ let tap = m % kernelArea;
34
+ let oh = (n / {{ convTransposeInputW }}u) * {{ convTransposeKernelH }}u + tap / {{ convTransposeKernelW }}u;
35
+ let ow = (n % {{ convTransposeInputW }}u) * {{ convTransposeKernelW }}u + tap % {{ convTransposeKernelW }}u;
36
+ let outputIndex = yBase + (oc * {{ convTransposeOutputH }}u + oh) * {{ convTransposeOutputW }}u + ow;
37
+ y[outputIndex] = 0.0 + raw;
38
+ {% else %}
39
  var v = raw;
 
 
 
 
 
 
40
  y[yBase + m * params.N + n] = {{ T }}(v);
41
+ {% endif %}
42
  }
43
 
 
44
  var<workgroup> tileA: array<array<{{ tileT }}, BK>, BM>;
45
  var<workgroup> tileB: array<array<{{ tileT }}, BN>, BK>;
46
 
47
+ @compute @workgroup_size({{ wgX }}, {{ wgY }}, 1)
48
  fn main(
49
  @builtin(workgroup_id) wg: vec3<u32>,
50
  @builtin(local_invocation_id) lid: vec3<u32>
 
56
  let nBase = wg.x * BN;
57
  let batch = wg.z;
58
  let xBatchBase = batch * K * N;
59
+ let li = lid.y * {{ wgX }}u + lid.x;
60
 
61
  var acc00: f32 = 0.0;
62
  var acc01: f32 = 0.0;
 
66
  for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
67
  let kBase = kt * BK;
68
  // Cooperative load: 32x16 A(=W) tile + 16x32 B(=X[batch]) tile, 256 threads x 2 each.
69
+ for (var e: u32 = 0u; e < (BM * BK) / {{ wgX * wgY }}u; e = e + 1u) {
70
+ let idx = li + e * {{ wgX * wgY }}u;
71
  let ar = idx / BK;
72
  let ac = idx % BK;
73
  let am = mBase + ar;
 
93
  let a1 = f32(tileA[lid.y * 2u + 1u][kk]);
94
  let b0 = f32(tileB[kk][lid.x * 2u]);
95
  let b1 = f32(tileB[kk][lid.x * 2u + 1u]);
96
+ {% for i in range(2) %}{% for j in range(2) %}
97
+ {{ accumulatorPrefix }}{{ i }}{{ j }} = {{ accumulatorPrefix }}{{ i }}{{ j }} + a{{ i }} * b{{ j }};
98
+ {% endfor %}{% endfor %}
 
99
  }
100
  workgroupBarrier();
101
  }
build/webgpu/conv-1x1-subgroup-matrix.wgsl.jinja CHANGED
@@ -11,19 +11,15 @@
11
  // sums to tile-padded [splitK,batch,M_PAD,N_PAD] scratch; a later pass combines
12
  // the slices and applies bias or an epilogue. Padded tail cells remain zero and
13
  // are never copied to the logical output.
14
- {% set directMatrixInputs = directMatrixInputs is defined and directMatrixInputs %}
15
- {% set fusedNarrowProjection = fusedNarrowProjection if fusedNarrowProjection is defined else false %}
16
- {% set polyphase = polyphaseConvTranspose is defined and polyphaseConvTranspose %}
17
  {% set implicit = implicitIm2col is defined and implicitIm2col %}
18
  {% set implicit3d = implicit and (convSpatialDims is defined and convSpatialDims == 3) %}
19
- {% set splitKValue = splitK if splitK is defined else 1 %}
20
- {% set splitKPartial = splitKValue > 1 %}
21
  {% set kLoopVar = "K_LOOP" if padded else "K" %}
22
  {% set nColsVar = "N_COLS" if padded else "N" %}
23
- {% set hasZ = hasZ is defined and hasZ %}
24
- {% set projectionOutChannels = projectionOutChannels | default(0) %}
25
- {% set hasProjectionBias = hasProjectionBias is defined and hasProjectionBias %}
26
- {% set hasOutputScale = hasOutputScale is defined and hasOutputScale %}
27
  {% set convKernelH = convKernelH | default(0) %}
28
  {% set convKernelW = convKernelW | default(0) %}
29
  {% set convStrideH = convStrideH | default(0) %}
@@ -42,7 +38,7 @@
42
  {% set convPadFront = convPadFront | default(0) %}
43
  {% set convInD = convInD | default(0) %}
44
  {% set convOutH = convOutH | default(0) %}
45
- {% set nPadded = nPadded | default(0) %}
46
  enable subgroups;
47
  {% if pinSubgroupSize32 %}
48
  enable subgroup_size_control;
@@ -50,36 +46,7 @@ enable subgroup_size_control;
50
  enable chromium_experimental_subgroup_matrix;
51
  diagnostic(off, chromium.subgroup_matrix_uniformity);
52
 
53
-
54
  {{ env.wgsl.resourceDeclarations }}
55
- {% if fusedNarrowProjection %}{% set inputActivation = inputActivation | default("none") %}
56
- {% set outputActivation = outputActivation | default("none") %}
57
- {% if inputActivation == "relu" or outputActivation == "relu" %}
58
- fn is_nan_f32(value: f32) -> bool {
59
- let bits = bitcast<u32>(value);
60
- return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
61
- }
62
-
63
- fn projection_relu(value: f32) -> f32 {
64
- var out = max(value, 0.0);
65
- if (is_nan_f32(value)) {
66
- out = value;
67
- }
68
- return out;
69
- }
70
- {% endif %}
71
- {% if outputActivation == "sigmoid" %}
72
- fn sigmoid_safe(x: f32) -> f32 {
73
- if (x >= 0.0) {
74
- let z = exp(-x);
75
- return 1.0 / (1.0 + z);
76
- }
77
- let z = exp(x);
78
- return z / (1.0 + z);
79
- }
80
- {% endif %}
81
- {% endif %}
82
-
83
  {% set operandScalar = fScalar %}
84
  {% set accScalar = "f32" %}
85
  {% set useDirectMatrixStore = directMatrixStore is defined and directMatrixStore %}
@@ -136,31 +103,26 @@ const N_COLS: u32 = {{ nPadded }}u;
136
  {% endif %}
137
  const B_BATCH_STRIDE: u32 = {{ kLoopVar }} * {{ nColsVar }};
138
  {% endif %}
139
- {% if not splitKPartial and not polyphase and not fusedNarrowProjection %}
140
  const C_BATCH_STRIDE: u32 = M * N;
141
- {% endif %}
142
  const TILE_COLS: u32 = {{ tileColsValue }}u;
143
  const TILE_ROWS: u32 = {{ tileRowsValue }}u;
144
  const TILE_K: u32 = 32u;
145
  const SUB_COLS: u32 = {{ subColsValue }}u;
146
  const SUB_ROWS: u32 = {{ subRowsValue }}u;
147
 
148
- {% if not directMatrixInputs %}
149
  var<workgroup> tile_A: array<{{ operandScalar }}, {{ tileRowsValue }} * 32>;
150
  var<workgroup> tile_B: array<{{ operandScalar }}, {{ tileColsValue }} * 32>;
151
- {% endif %}
152
  {% if (not useDirectMatrixStore or padded) and not splitKPartial and not fusedNarrowProjection %}
153
  var<workgroup> scratch: array<array<array<{{ accScalar }}, 64>, 4>, {{ (workgroupThreadsValue / 32)|int }}>;
154
 
155
  {% endif %}
156
- {% if not directMatrixInputs %}
157
  fn loadSHMA(tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
158
  let a_global = tile_base + row;
159
  let col = c_idx * 8u;
160
  for (var col_offset = 0u; col_offset < 8u; col_offset = col_offset + 1u) {
161
  let k = k_idx + col + col_offset;
162
  if (a_global < M{% if padded %} && k < K{% endif %}) {
163
- {% set A_PHASE = "phase_base + " if polyphase else "" %}
164
  {% if operandScalar == "f16" %}
165
  tile_A[row * TILE_K + col + col_offset] = f16(w[{{ A_PHASE }}a_global * K + k]);
166
  {% else %}
@@ -309,59 +271,12 @@ fn loadSHMB(b_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
309
  }
310
  {% endif %}
311
  }
312
-
313
- {% endif %}
314
- {% set hasActivation = hasActivation is defined and hasActivation %}
315
- {% set activation = activation | default("") %}
316
- {% set actAlpha = actAlpha | default(0.0) %}
317
- {% set actBeta = actBeta | default(0.0) %}
318
- {% if hasActivation %}
319
- // Apply the fused activation in the f32 accumulator before the single output
320
- // cast, avoiding an intermediate convolution tensor.
321
- {% macro fused_act_return(mode, alpha, beta) -%}
322
- {% if mode == "Relu" %}
323
- return max(v, 0.0);
324
- {% elif mode == "Clip" %}
325
- return clamp(v, f32({{ alpha }}), f32({{ beta }}));
326
- {% elif mode == "LeakyRelu" %}
327
- return select(v * f32({{ alpha }}), v, v >= 0.0);
328
- {% elif mode == "Sigmoid" %}
329
- return 1.0 / (1.0 + exp(-v));
330
- {% elif mode == "Tanh" %}
331
- // tanh is already +/-1 to f32 precision at the clamp bounds. Clamping before
332
- // the builtin preserves that saturated result for larger accumulators.
333
- return tanh(clamp(v, -10.0, 10.0));
334
- {% elif mode == "HardSigmoid" %}
335
- return clamp(f32({{ alpha }}) * v + f32({{ beta }}), 0.0, 1.0);
336
- {% else %}
337
- return v * clamp(v * 0.16666666666666666 + 0.5, 0.0, 1.0);
338
- {% endif %}
339
- {%- endmacro -%}
340
- fn fused_act(v: f32) -> f32 {
341
- {{ fused_act_return(activation, actAlpha, actBeta) -}}
342
- }
343
- {% endif %}
344
-
345
  {% set biasAdd = " + bv" if hasBias else "" %}
346
- {% if hasActivation or hasZ %}
347
- // Fused epilogue: Y = activation(conv + bias + Z), applied at the output store.
348
- // `raw` is the f32 convolution-plus-bias accumulator; Z shares Y's NCHW layout.
349
- fn epi(raw: f32{% if hasZ %}, yIndex: u32{% endif %}) -> {{ T }} {
350
- var r = raw;
351
- {% if hasZ %}
352
- r = r + f32(zResidual[yIndex]);
353
- {% endif %}
354
- {% if hasActivation %}
355
- r = fused_act(r);
356
- {% endif %}
357
- return {{ T }}(r);
358
- }
359
- {% endif %}
360
  {% macro store_val(valExpr, idxExpr) %}
361
- {% if hasActivation or hasZ %}epi({{ valExpr }}{% if hasZ %}, {{ idxExpr }}{% endif %})
362
- {%- else %}{{ T }}({{ valExpr }})
363
- {%- endif %}
364
- {% endmacro %}
365
 
366
  {% if (not useDirectMatrixStore or padded) and not splitKPartial and not fusedNarrowProjection %}
367
  fn storeOutput(offset: u32, {% if hasBias or polyphase %}row_base: u32, {% endif %}row: u32, col: u32, src_slot: u32, row_limit: i32{% if padded %}, col_anchor: u32{% endif %}) {
@@ -408,9 +323,7 @@ fn main(
408
  ) {
409
  let batch = workgroup_id.z;
410
  let b_base = batch * B_BATCH_STRIDE;
411
- {% if not fusedNarrowProjection %}
412
  let c_base = batch * C_BATCH_STRIDE;
413
- {% endif %}
414
  let a_global_base = workgroup_id.y * TILE_ROWS;
415
  let b_global_base = workgroup_id.x * TILE_COLS;
416
  let subtile_id = local_idx / sg_size;
@@ -429,22 +342,20 @@ fn main(
429
  var matC13: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
430
 
431
  for (var kidx = 0u; kidx < {{ kLoopVar }}; kidx = kidx + TILE_K) {
432
- {% if not directMatrixInputs %}
433
  loadSHMA(a_global_base, kidx, local_idx / 4u, local_idx % 4u);
434
  loadSHMB(b_base, b_global_base, kidx, local_idx / {{ bKChunks }}u, local_idx % {{ bKChunks }}u);
435
  workgroupBarrier();
436
- {% endif %}
437
 
438
  for (var step = 0u; step < TILE_K; step = step + 8u) {
439
- {% set directInputs = directMatrixInputs is defined and directMatrixInputs %}
440
- let matrix_a_offset = {% if directInputs %}(a_global_base + subtile_idy * SUB_ROWS) * K + kidx + step{% else %}subtile_idy * SUB_ROWS * TILE_K + step{% endif %};
441
  {% for r in range(2) %}
442
  var matA{{ r }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&{{ "w" if directInputs else "tile_A" }}, matrix_a_offset{% if r > 0 %} + 8u * {{ "K" if directInputs else "TILE_K" }}{% endif %}, {{ "K" if directInputs else "TILE_K" }});
443
  {% endfor %}
444
 
445
- let matrix_b_offset = {% if directInputs %}b_base + (kidx + step) * N + b_global_base + subtile_idx * SUB_COLS{% else %}subtile_idx * SUB_COLS * TILE_K + step{% endif %};
446
  {% for c in range(4) %}
447
- var matB{{ c }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, {% if directInputs %}row_major{% else %}col_major{% endif %}>(&{{ "xm" if directInputs else "tile_B" }}, matrix_b_offset{% if c > 0 %} + {{ c * 8 }}u{% if not directInputs %} * TILE_K{% endif %}{% endif %}, {{ "N" if directInputs else "TILE_K" }});
448
  {% endfor %}
449
 
450
  matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
@@ -456,62 +367,10 @@ fn main(
456
  matC12 = subgroupMatrixMultiplyAccumulate(matA1, matB2, matC12);
457
  matC13 = subgroupMatrixMultiplyAccumulate(matA1, matB3, matC13);
458
  }
459
- {% if not directMatrixInputs %}
460
  workgroupBarrier();
461
- {% endif %}
462
  }
463
 
464
- {% if fusedNarrowProjection %}
465
- // The Conv producer owns exactly one 32-row tile. Its input tile is dead after
466
- // the final K iteration, so reuse those 32x64 f32 cells as the only
467
- // cross-subgroup handoff into the narrow projection. All collective stores
468
- // remain subgroup-uniform, and every fragment has a disjoint destination.
469
- let fused_tile_offset = base_A * TILE_COLS + base_B;
470
- subgroupMatrixStore<row_major>(&tile_B, fused_tile_offset + 0u * TILE_COLS + 0u, matC00, TILE_COLS);
471
- subgroupMatrixStore<row_major>(&tile_B, fused_tile_offset + 0u * TILE_COLS + 8u, matC01, TILE_COLS);
472
- subgroupMatrixStore<row_major>(&tile_B, fused_tile_offset + 0u * TILE_COLS + 16u, matC02, TILE_COLS);
473
- subgroupMatrixStore<row_major>(&tile_B, fused_tile_offset + 0u * TILE_COLS + 24u, matC03, TILE_COLS);
474
- subgroupMatrixStore<row_major>(&tile_B, fused_tile_offset + 8u * TILE_COLS + 0u, matC10, TILE_COLS);
475
- subgroupMatrixStore<row_major>(&tile_B, fused_tile_offset + 8u * TILE_COLS + 8u, matC11, TILE_COLS);
476
- subgroupMatrixStore<row_major>(&tile_B, fused_tile_offset + 8u * TILE_COLS + 16u, matC12, TILE_COLS);
477
- subgroupMatrixStore<row_major>(&tile_B, fused_tile_offset + 8u * TILE_COLS + 24u, matC13, TILE_COLS);
478
- workgroupBarrier();
479
-
480
- // One invocation owns one spatial column and walks the producer channels in
481
- // increasing order to preserve the scalar projection's accumulation order.
482
- // The inactive half of the workgroup has no remaining collective operation
483
- // to reach.
484
- let fused_global_col = b_global_base + local_idx;
485
- if (local_idx < TILE_COLS && fused_global_col < N) {
486
- {% for oc in range(projectionOutChannels) %}
487
- var projected_acc{{ oc }} = f32(0.0);
488
- {% endfor %}
489
- for (var channel = 0u; channel < M; channel = channel + 1u) {
490
- let producer_value = tile_B[channel * TILE_COLS + local_idx]{% if hasBias %} + f32(bias[channel]){% endif %};
491
- {% if inputActivation == "relu" %}
492
- let activated_value = projection_relu(producer_value);
493
- {% else %}
494
- let activated_value = producer_value;
495
- {% endif %}
496
- {% for oc in range(projectionOutChannels) %}
497
- projected_acc{{ oc }} = projected_acc{{ oc }} + activated_value * f32(projectionW[{{ oc }}u * M + channel]);
498
- {% endfor %}
499
- }
500
-
501
- let fused_y_base = batch * {{ projectionOutChannels }}u * N + fused_global_col;
502
- {% for oc in range(projectionOutChannels) %}
503
- let projected{{ oc }} = projected_acc{{ oc }}{% if hasProjectionBias %} + f32(projectionBias[{{ oc }}u]){% endif %};
504
- {% if outputActivation == "relu" %}
505
- let activated{{ oc }} = projection_relu(projected{{ oc }});
506
- {% elif outputActivation == "sigmoid" %}
507
- let activated{{ oc }} = sigmoid_safe(projected{{ oc }});
508
- {% else %}
509
- let activated{{ oc }} = projected{{ oc }};
510
- {% endif %}
511
- y[fused_y_base + {{ oc }}u * N] = activated{{ oc }}{% if hasOutputScale %} * params.outputScale{% endif %};
512
- {% endfor %}
513
- }
514
- {% elif useDirectMatrixStore %}
515
  {% if padded %}
516
  // Padded N only needs guarded publication for the final partial-width tile.
517
  // Full tiles can use the same direct collective stores as the aligned path;
@@ -543,11 +402,7 @@ fn main(
543
  {% if hasBias %}
544
  raw += f32(bias[global_row]);
545
  {% endif %}
546
- {% if hasActivation or hasZ %}
547
- y[y_index] = epi(raw{% if hasZ %}, y_index{% endif %});
548
- {% else %}
549
  y[y_index] = {{ T }}(raw);
550
- {% endif %}
551
  }
552
  {% endif %}
553
  {% if padded %}
 
11
  // sums to tile-padded [splitK,batch,M_PAD,N_PAD] scratch; a later pass combines
12
  // the slices and applies bias or an epilogue. Padded tail cells remain zero and
13
  // are never copied to the logical output.
14
+ {% set fusedNarrowProjection = false %}
15
+ {% set polyphase = false %}
 
16
  {% set implicit = implicitIm2col is defined and implicitIm2col %}
17
  {% set implicit3d = implicit and (convSpatialDims is defined and convSpatialDims == 3) %}
18
+ {% set splitKPartial = false %}
 
19
  {% set kLoopVar = "K_LOOP" if padded else "K" %}
20
  {% set nColsVar = "N_COLS" if padded else "N" %}
21
+ {% set hasZ = false %}
22
+ {% set hasActivation = false %}
 
 
23
  {% set convKernelH = convKernelH | default(0) %}
24
  {% set convKernelW = convKernelW | default(0) %}
25
  {% set convStrideH = convStrideH | default(0) %}
 
38
  {% set convPadFront = convPadFront | default(0) %}
39
  {% set convInD = convInD | default(0) %}
40
  {% set convOutH = convOutH | default(0) %}
41
+ {% set nPadded = 0 %}
42
  enable subgroups;
43
  {% if pinSubgroupSize32 %}
44
  enable subgroup_size_control;
 
46
  enable chromium_experimental_subgroup_matrix;
47
  diagnostic(off, chromium.subgroup_matrix_uniformity);
48
 
 
49
  {{ env.wgsl.resourceDeclarations }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  {% set operandScalar = fScalar %}
51
  {% set accScalar = "f32" %}
52
  {% set useDirectMatrixStore = directMatrixStore is defined and directMatrixStore %}
 
103
  {% endif %}
104
  const B_BATCH_STRIDE: u32 = {{ kLoopVar }} * {{ nColsVar }};
105
  {% endif %}
 
106
  const C_BATCH_STRIDE: u32 = M * N;
 
107
  const TILE_COLS: u32 = {{ tileColsValue }}u;
108
  const TILE_ROWS: u32 = {{ tileRowsValue }}u;
109
  const TILE_K: u32 = 32u;
110
  const SUB_COLS: u32 = {{ subColsValue }}u;
111
  const SUB_ROWS: u32 = {{ subRowsValue }}u;
112
 
 
113
  var<workgroup> tile_A: array<{{ operandScalar }}, {{ tileRowsValue }} * 32>;
114
  var<workgroup> tile_B: array<{{ operandScalar }}, {{ tileColsValue }} * 32>;
 
115
  {% if (not useDirectMatrixStore or padded) and not splitKPartial and not fusedNarrowProjection %}
116
  var<workgroup> scratch: array<array<array<{{ accScalar }}, 64>, 4>, {{ (workgroupThreadsValue / 32)|int }}>;
117
 
118
  {% endif %}
 
119
  fn loadSHMA(tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
120
  let a_global = tile_base + row;
121
  let col = c_idx * 8u;
122
  for (var col_offset = 0u; col_offset < 8u; col_offset = col_offset + 1u) {
123
  let k = k_idx + col + col_offset;
124
  if (a_global < M{% if padded %} && k < K{% endif %}) {
125
+ {% set A_PHASE = "" %}
126
  {% if operandScalar == "f16" %}
127
  tile_A[row * TILE_K + col + col_offset] = f16(w[{{ A_PHASE }}a_global * K + k]);
128
  {% else %}
 
271
  }
272
  {% endif %}
273
  }
274
+ {% set hasActivation = false %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  {% set biasAdd = " + bv" if hasBias else "" %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  {% macro store_val(valExpr, idxExpr) %}
277
+ {% if hasActivation or hasZ %}
278
+ epi({{ valExpr }}){% else %}
279
+ {{ T }}({{ valExpr }}){% endif %}{% endmacro %}
 
280
 
281
  {% if (not useDirectMatrixStore or padded) and not splitKPartial and not fusedNarrowProjection %}
282
  fn storeOutput(offset: u32, {% if hasBias or polyphase %}row_base: u32, {% endif %}row: u32, col: u32, src_slot: u32, row_limit: i32{% if padded %}, col_anchor: u32{% endif %}) {
 
323
  ) {
324
  let batch = workgroup_id.z;
325
  let b_base = batch * B_BATCH_STRIDE;
 
326
  let c_base = batch * C_BATCH_STRIDE;
 
327
  let a_global_base = workgroup_id.y * TILE_ROWS;
328
  let b_global_base = workgroup_id.x * TILE_COLS;
329
  let subtile_id = local_idx / sg_size;
 
342
  var matC13: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
343
 
344
  for (var kidx = 0u; kidx < {{ kLoopVar }}; kidx = kidx + TILE_K) {
 
345
  loadSHMA(a_global_base, kidx, local_idx / 4u, local_idx % 4u);
346
  loadSHMB(b_base, b_global_base, kidx, local_idx / {{ bKChunks }}u, local_idx % {{ bKChunks }}u);
347
  workgroupBarrier();
 
348
 
349
  for (var step = 0u; step < TILE_K; step = step + 8u) {
350
+ {% set directInputs = false %}
351
+ let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
352
  {% for r in range(2) %}
353
  var matA{{ r }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&{{ "w" if directInputs else "tile_A" }}, matrix_a_offset{% if r > 0 %} + 8u * {{ "K" if directInputs else "TILE_K" }}{% endif %}, {{ "K" if directInputs else "TILE_K" }});
354
  {% endfor %}
355
 
356
+ let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
357
  {% for c in range(4) %}
358
+ var matB{{ c }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&{{ "xm" if directInputs else "tile_B" }}, matrix_b_offset{% if c > 0 %} + {{ c * 8 }}u * TILE_K{% endif %}, {{ "N" if directInputs else "TILE_K" }});
359
  {% endfor %}
360
 
361
  matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
 
367
  matC12 = subgroupMatrixMultiplyAccumulate(matA1, matB2, matC12);
368
  matC13 = subgroupMatrixMultiplyAccumulate(matA1, matB3, matC13);
369
  }
 
370
  workgroupBarrier();
 
371
  }
372
 
373
+ {% if useDirectMatrixStore %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  {% if padded %}
375
  // Padded N only needs guarded publication for the final partial-width tile.
376
  // Full tiles can use the same direct collective stores as the aligned path;
 
402
  {% if hasBias %}
403
  raw += f32(bias[global_row]);
404
  {% endif %}
 
 
 
405
  y[y_index] = {{ T }}(raw);
 
406
  }
407
  {% endif %}
408
  {% if padded %}
build/webgpu/conv-transpose-empty-input.wgsl.jinja CHANGED
@@ -1,36 +1,11 @@
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 dispatch's
4
- // per-axis workgroup fold width (outputs > 16.7M elements).
5
- {% elif note == "limit" %}
6
- // 2D-folded flat index: gid.y carries the high bits past the dispatch's
7
- // per-axis workgroup fold width.
8
- {% elif note == "device-axis" %}
9
- // The flat dispatch is folded across x/y at a fixed per-axis workgroup
10
- // width; 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 dispatch's
13
- // per-axis workgroup fold width (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
- // dispatch's per-axis workgroup fold width.
17
- {% elif note == "dispatch" %}
18
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
19
  // per-axis workgroup fold width.
20
- {% endif %}
21
- {% if bound == "" %}
22
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
23
- {%- elif guardInline %}
24
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
25
- if ({{ name }} >= {{ bound }}) { return; }
26
- {%- else %}
27
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
28
  if ({{ name }} >= {{ bound }}) {
29
  return;
30
- }
31
- {%- endif %}
32
- {% endmacro %}
33
-
34
  {{ env.wgsl.resourceDeclarations }}
35
 
36
  const COUNT: u32 = {{ count }}u;
@@ -43,7 +18,7 @@ const OUTPUT_SPATIAL: u32 = {{ outputSpatial }}u;
43
  // coordinate. The optional ConvTranspose bias is applied after that reduction.
44
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
45
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
46
- {{ flat_index_2d("index", "COUNT", note="device-axis") }}
47
  {% if hasBias %}
48
  let output_channel = (index / OUTPUT_SPATIAL) % OUT_CHANNELS;
49
  y[index] = bias[output_channel];
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
  // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
 
 
 
 
 
 
 
6
  if ({{ name }} >= {{ bound }}) {
7
  return;
8
+ }{% endmacro %}
 
 
 
9
  {{ env.wgsl.resourceDeclarations }}
10
 
11
  const COUNT: u32 = {{ count }}u;
 
18
  // coordinate. The optional ConvTranspose bias is applied after that reduction.
19
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
20
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
21
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "index", "COUNT") }}
22
  {% if hasBias %}
23
  let output_channel = (index / OUTPUT_SPATIAL) % OUT_CHANNELS;
24
  y[index] = bias[output_channel];
build/webgpu/conv-transpose1d-ncl.wgsl.jinja CHANGED
@@ -1,51 +1,22 @@
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 dispatch's
4
- // per-axis workgroup fold width (outputs > 16.7M elements).
5
- {% elif note == "limit" %}
6
- // 2D-folded flat index: gid.y carries the high bits past the dispatch's
7
- // per-axis workgroup fold width.
8
- {% elif note == "device-axis" %}
9
- // The flat dispatch is folded across x/y at a fixed per-axis workgroup
10
- // width; 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 dispatch's
13
- // per-axis workgroup fold width (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
- // dispatch's per-axis workgroup fold width.
17
- {% elif note == "dispatch" %}
18
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
19
  // per-axis workgroup fold width.
20
- {% endif %}
21
- {% if bound == "" %}
22
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
23
- {%- elif guardInline %}
24
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
25
- if ({{ name }} >= {{ bound }}) { return; }
26
- {%- else %}
27
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
28
  if ({{ name }} >= {{ bound }}) {
29
  return;
30
- }
31
- {%- endif %}
32
- {% endmacro %}
33
-
34
  {% macro tap(raw, coord, out, kernel, axis) %}
35
  let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
36
  if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
37
  let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
38
- 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>) {
48
- {{ flat_index_2d("index", note="device-axis") }}
49
 
50
  let ow = index % params.outW;
51
  var t = index / params.outW;
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
  // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
 
 
 
 
 
 
 
6
  if ({{ name }} >= {{ bound }}) {
7
  return;
8
+ }{% endmacro %}
 
 
 
9
  {% macro tap(raw, coord, out, kernel, axis) %}
10
  let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
11
  if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
12
  let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
13
+ if ({{ coord }} < 0 || {{ coord }} >= i32(params.in{{ axis }})) { continue; }{% endmacro %}
 
 
14
  // Preserve f32 accumulation for the deep channel-by-kernel sum: widen each
15
  // f16 product and narrow only once at the output store.
 
 
16
  {{ env.wgsl.resourceDeclarations }}
17
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
18
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
19
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "index") }}
20
 
21
  let ow = index % params.outW;
22
  var t = index / params.outW;
build/webgpu/conv-transpose2d-col2im.wgsl.jinja CHANGED
@@ -1,42 +1,16 @@
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 dispatch's
4
- // per-axis workgroup fold width (outputs > 16.7M elements).
5
- {% elif note == "limit" %}
6
- // 2D-folded flat index: gid.y carries the high bits past the dispatch's
7
- // per-axis workgroup fold width.
8
- {% elif note == "device-axis" %}
9
- // The flat dispatch is folded across x/y at a fixed per-axis workgroup
10
- // width; 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 dispatch's
13
- // per-axis workgroup fold width (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
- // dispatch's per-axis workgroup fold width.
17
- {% elif note == "dispatch" %}
18
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
19
  // per-axis workgroup fold width.
20
- {% endif %}
21
- {% if bound == "" %}
22
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
23
- {%- elif guardInline %}
24
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
25
- if ({{ name }} >= {{ bound }}) { return; }
26
- {%- else %}
27
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
28
  if ({{ name }} >= {{ bound }}) {
29
  return;
30
- }
31
- {%- endif %}
32
- {% endmacro %}
33
-
34
  {% macro tap(raw, coord, out, kernel, axis) %}
35
  let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
36
  if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
37
  let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
38
- 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
@@ -45,14 +19,10 @@ if ({{ coord }} < 0 || {{ coord }} >= i32(params.in{{ axis }})) { continue; }
45
  // KH*KW taps whose inverse stride-and-padding coordinates land on it, reading the
46
  // precomputed cols without repeating the IC contraction.
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>) {
55
- {{ flat_index_2d("index", note="") }}
56
 
57
  let ow = index % params.outW;
58
  var t = index / params.outW;
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
  // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
 
 
 
 
 
 
 
6
  if ({{ name }} >= {{ bound }}) {
7
  return;
8
+ }{% endmacro %}
 
 
 
9
  {% macro tap(raw, coord, out, kernel, axis) %}
10
  let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
11
  if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
12
  let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
13
+ if ({{ coord }} < 0 || {{ coord }} >= i32(params.in{{ axis }})) { continue; }{% endmacro %}
 
14
  // ConvTranspose col2im scatter (output-stationary) for the im2col-GEMM path.
15
  //
16
  // The GEMM pass produced cols[m = (oc*KH+kh)*KW+kw, n = ih*IW+iw] = sum_ic
 
19
  // KH*KW taps whose inverse stride-and-padding coordinates land on it, reading the
20
  // precomputed cols without repeating the IC contraction.
21
  // One thread per output element, f32 accumulation, deterministic (no atomics).
 
 
 
 
22
  {{ env.wgsl.resourceDeclarations }}
23
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
24
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
25
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "index") }}
26
 
27
  let ow = index % params.outW;
28
  var t = index / params.outW;
build/webgpu/conv-transpose2d-grouped-stride-phase.wgsl.jinja CHANGED
@@ -1,3 +1,9 @@
 
 
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  // With stride > 1 only one kernel phase contributes to a given output pixel.
@@ -24,8 +30,7 @@ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
24
 
25
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
26
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
27
- let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG;
28
- if (index >= COUNT) { return; }
29
  let ow = index % OUT_W;
30
  var t = index / OUT_W;
31
  let oh = t % OUT_H;
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
+ // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
6
+ if ({{ name }} >= {{ bound }}) { return; }{% endmacro %}
7
  {{ env.wgsl.resourceDeclarations }}
8
 
9
  // With stride > 1 only one kernel phase contributes to a given output pixel.
 
30
 
31
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
32
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
33
+ {{ flat_index_2d("WG", "index", "COUNT", guardInline=true) }}
 
34
  let ow = index % OUT_W;
35
  var t = index / OUT_W;
36
  let oh = t % OUT_H;
build/webgpu/conv-transpose2d-nchw.wgsl.jinja CHANGED
@@ -1,52 +1,25 @@
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 dispatch's
4
- // per-axis workgroup fold width (outputs > 16.7M elements).
5
- {% elif note == "limit" %}
6
- // 2D-folded flat index: gid.y carries the high bits past the dispatch's
7
- // per-axis workgroup fold width.
8
- {% elif note == "device-axis" %}
9
- // The flat dispatch is folded across x/y at a fixed per-axis workgroup
10
- // width; 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 dispatch's
13
- // per-axis workgroup fold width (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
- // dispatch's per-axis workgroup fold width.
17
- {% elif note == "dispatch" %}
18
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
19
  // per-axis workgroup fold width.
20
- {% endif %}
21
  {% if bound == "" %}
22
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
23
- {%- elif guardInline %}
24
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
25
- if ({{ name }} >= {{ bound }}) { return; }
26
- {%- else %}
27
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
28
  if ({{ name }} >= {{ bound }}) {
29
  return;
30
- }
31
- {%- endif %}
32
- {% endmacro %}
33
-
34
  {% macro tap(raw, coord, out, kernel, axis) %}
35
  let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
36
  if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
37
  let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
38
- 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>) {
49
- {{ flat_index_2d("vector_index", "", note="") }}
50
  let base = vector_index * 4u;
51
  if (base >= params.count) {
52
  return;
@@ -68,7 +41,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
68
  {% else %}
69
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
70
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
71
- {{ flat_index_2d("index", note="device-axis") }}
72
 
73
  let ow = index % params.outW;
74
  var t = index / params.outW;
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
  // per-axis workgroup fold width.
 
5
  {% if bound == "" %}
6
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};{% else %}
7
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
 
 
 
 
8
  if ({{ name }} >= {{ bound }}) {
9
  return;
10
+ }{% endif %}{% endmacro %}
 
 
 
11
  {% macro tap(raw, coord, out, kernel, axis) %}
12
  let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
13
  if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
14
  let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
15
+ if ({{ coord }} < 0 || {{ coord }} >= i32(params.in{{ axis }})) { continue; }{% endmacro %}
 
 
16
  // Preserve f32 accumulation for the deep channel-by-kernel sum: widen each
17
  // f16 product and narrow only once at the output store.
 
 
18
  {{ env.wgsl.resourceDeclarations }}
19
  {% if depthwisePointwiseVec4 %}
20
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
21
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
22
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "vector_index", "") }}
23
  let base = vector_index * 4u;
24
  if (base >= params.count) {
25
  return;
 
41
  {% else %}
42
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
43
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
44
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "index") }}
45
 
46
  let ow = index % params.outW;
47
  var t = index / params.outW;
build/webgpu/conv-transpose2d-phase-gemm-tiled.wgsl.jinja CHANGED
@@ -11,8 +11,6 @@
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 = {{ inChannels }}u;
@@ -42,18 +40,58 @@ const WG_SIZE: u32 = WG_EDGE * WG_EDGE;
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 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
  }
@@ -69,12 +107,7 @@ fn main(
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;
 
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
  {{ env.wgsl.resourceDeclarations }}
15
 
16
  const IN_C: u32 = {{ inChannels }}u;
 
40
  var<workgroup> tileA: array<array<f16, BK>, M_TILE>;
41
  var<workgroup> tileB: array<array<f16, N_TILE>, BK>;
42
 
43
+ {% macro phase_geometry() %}
44
+ let phaseH = phase / PHASE_EDGE;
45
+ let phaseW = phase % PHASE_EDGE;
46
+ let kernelPhaseH = (phaseH + PAD_H) % PHASE_EDGE;
47
+ let kernelPhaseW = (phaseW + PAD_W) % PHASE_EDGE;
48
+ let inputShiftH = (phaseH + PAD_H - kernelPhaseH) / PHASE_EDGE;
49
+ let inputShiftW = (phaseW + PAD_W - kernelPhaseW) / PHASE_EDGE;{% endmacro %}
50
+ // A synthetic zero input may multiply a NaN/Inf weight in the tiled dot.
51
+ // Reconstruct an affected perimeter output from its reachable taps only.
52
+ fn boundary_sum(batch: u32, phase: u32, oc: u32, n: u32) -> f32 {
53
+ {{ phase_geometry() }}
54
+ let row = i32(n / IN_W) + i32(inputShiftH);
55
+ let col = i32(n % IN_W) + i32(inputShiftW);
56
+ var sum = 0.0;
57
+ for (var ic = 0u; ic < IN_C; ic += 1u) {
58
+ for (var tap = 0u; tap < TAPS_PER_PHASE; tap += 1u) {
59
+ let tapH = tap / PHASE_EDGE;
60
+ let tapW = tap % PHASE_EDGE;
61
+ let ih = row - i32(tapH);
62
+ let iw = col - i32(tapW);
63
+ if (ih >= 0 && ih < i32(IN_H) && iw >= 0 && iw < i32(IN_W)) {
64
+ let kh = kernelPhaseH + tapH * PHASE_EDGE;
65
+ let kw = kernelPhaseW + tapW * PHASE_EDGE;
66
+ let xIndex = ((batch * IN_C + ic) * IN_H + u32(ih)) * IN_W + u32(iw);
67
+ let wIndex = ((ic * OUT_C + oc) * (2u * PHASE_EDGE) + kh) * (2u * PHASE_EDGE) + kw;
68
+ sum = fma(f32(x[xIndex]), f32(w[wIndex]), sum);
69
+ }
70
+ }
71
+ }
72
+ return sum;
73
+ }
74
+
75
  fn store_output(batch: u32, phase: u32, oc: u32, n: u32, value: f32) {
76
  if (oc >= OUT_C || n >= N) {
77
  return;
78
  }
79
  let phaseH = phase / PHASE_EDGE;
80
  let phaseW = phase % PHASE_EDGE;
81
+ var sum = value;
82
+ if ((bitcast<u32>(value) & 0x7fffffffu) > 0x7f800000u) {
83
+ let row = n / IN_W;
84
+ let col = n % IN_W;
85
+ if (row == 0u || row + 1u == IN_H || col == 0u || col + 1u == IN_W) {
86
+ sum = boundary_sum(batch, phase, oc, n);
87
+ }
88
+ }
89
  let oh = (n / IN_W) * PHASE_EDGE + phaseH;
90
  let ow = (n % IN_W) * PHASE_EDGE + phaseW;
91
  {% if hasBias %}
92
+ let result = sum + f32(bias[oc]);
93
  {% else %}
94
+ let result = sum;
95
  {% endif %}
96
  y[((batch * OUT_C + oc) * OUT_H + oh) * OUT_W + ow] = f16(result);
97
  }
 
107
  return;
108
  }
109
 
110
+ {{ phase_geometry() }}
 
 
 
 
 
111
 
112
  let mBase = wg.y * M_TILE;
113
  let nBase = wg.x * N_TILE;
build/webgpu/conv-transpose2d-weight-reorder.wgsl.jinja CHANGED
@@ -1,50 +1,21 @@
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 dispatch's
4
- // per-axis workgroup fold width (outputs > 16.7M elements).
5
- {% elif note == "limit" %}
6
- // 2D-folded flat index: gid.y carries the high bits past the dispatch's
7
- // per-axis workgroup fold width.
8
- {% elif note == "device-axis" %}
9
- // The flat dispatch is folded across x/y at a fixed per-axis workgroup
10
- // width; 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 dispatch's
13
- // per-axis workgroup fold width (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
- // dispatch's per-axis workgroup fold width.
17
- {% elif note == "dispatch" %}
18
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
19
  // per-axis workgroup fold width.
20
- {% endif %}
21
- {% if bound == "" %}
22
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
23
- {%- elif guardInline %}
24
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
25
- if ({{ name }} >= {{ bound }}) { return; }
26
- {%- else %}
27
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ 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>) {
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;
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
  // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
 
 
 
 
 
 
 
6
  if ({{ name }} >= {{ bound }}) {
7
  return;
8
+ }{% endmacro %}
 
 
 
9
  // ConvTranspose weight reorder for the im2col-GEMM path.
10
  //
11
  // The ONNX ConvTranspose weight is laid out [IC, OC, KH, KW]. The GEMM that
12
  // produces the cols matrix (cols[OC*KH*KW, IH*IW] = Wt @ X) needs its A operand
13
  // as a row-major [M = OC*KH*KW, K = IC] matrix, i.e. the IC dimension contracted
14
  // to the inner (column) axis. This pass gathers W into that transposed layout.
 
 
 
 
15
  {{ env.wgsl.resourceDeclarations }}
16
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
17
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
18
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "index") }}
19
  // Wt[m, ic] with m = (oc * KH + kh) * KW + kw, contracted dim ic.
20
  let ic = index % params.inChannels;
21
  var m = index / params.inChannels;
build/webgpu/conv-transpose3d-col2im.wgsl.jinja CHANGED
@@ -1,42 +1,16 @@
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 dispatch's
4
- // per-axis workgroup fold width (outputs > 16.7M elements).
5
- {% elif note == "limit" %}
6
- // 2D-folded flat index: gid.y carries the high bits past the dispatch's
7
- // per-axis workgroup fold width.
8
- {% elif note == "device-axis" %}
9
- // The flat dispatch is folded across x/y at a fixed per-axis workgroup
10
- // width; 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 dispatch's
13
- // per-axis workgroup fold width (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
- // dispatch's per-axis workgroup fold width.
17
- {% elif note == "dispatch" %}
18
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
19
  // per-axis workgroup fold width.
20
- {% endif %}
21
- {% if bound == "" %}
22
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
23
- {%- elif guardInline %}
24
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
25
- if ({{ name }} >= {{ bound }}) { return; }
26
- {%- else %}
27
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
28
  if ({{ name }} >= {{ bound }}) {
29
  return;
30
- }
31
- {%- endif %}
32
- {% endmacro %}
33
-
34
  {% macro tap(raw, coord, out, kernel, axis) %}
35
  let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
36
  if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
37
  let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
38
- 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
@@ -47,7 +21,7 @@ if ({{ coord }} < 0 || {{ coord }} >= i32(params.in{{ axis }})) { continue; }
47
 
48
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
49
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
50
- {{ flat_index_2d("index", note="device-axis") }}
51
 
52
  let ow = index % params.outW;
53
  var t = index / params.outW;
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
  // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
 
 
 
 
 
 
 
6
  if ({{ name }} >= {{ bound }}) {
7
  return;
8
+ }{% endmacro %}
 
 
 
9
  {% macro tap(raw, coord, out, kernel, axis) %}
10
  let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
11
  if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
12
  let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
13
+ if ({{ coord }} < 0 || {{ coord }} >= i32(params.in{{ axis }})) { continue; }{% endmacro %}
 
14
  // Output-stationary rank-5 col2im gather for ConvTranspose-as-GEMM.
15
  //
16
  // GEMM has already contracted input channels into
 
21
 
22
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
23
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
24
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "index") }}
25
 
26
  let ow = index % params.outW;
27
  var t = index / params.outW;
build/webgpu/conv-transpose3d-ncdhw.wgsl.jinja CHANGED
@@ -1,46 +1,20 @@
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 dispatch's
4
- // per-axis workgroup fold width (outputs > 16.7M elements).
5
- {% elif note == "limit" %}
6
- // 2D-folded flat index: gid.y carries the high bits past the dispatch's
7
- // per-axis workgroup fold width.
8
- {% elif note == "device-axis" %}
9
- // The flat dispatch is folded across x/y at a fixed per-axis workgroup
10
- // width; 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 dispatch's
13
- // per-axis workgroup fold width (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
- // dispatch's per-axis workgroup fold width.
17
- {% elif note == "dispatch" %}
18
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
19
  // per-axis workgroup fold width.
20
- {% endif %}
21
- {% if bound == "" %}
22
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
23
- {%- elif guardInline %}
24
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
25
- if ({{ name }} >= {{ bound }}) { return; }
26
- {%- else %}
27
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
28
  if ({{ name }} >= {{ bound }}) {
29
  return;
30
- }
31
- {%- endif %}
32
- {% endmacro %}
33
-
34
  {% macro tap(raw, coord, out, kernel, axis) %}
35
  let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
36
  if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
37
  let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
38
- 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>) {
43
- {{ flat_index_2d("index", note="device-axis") }}
44
 
45
  let ow = index % params.outW;
46
  var t = index / params.outW;
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
  // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
 
 
 
 
 
 
 
6
  if ({{ name }} >= {{ bound }}) {
7
  return;
8
+ }{% endmacro %}
 
 
 
9
  {% macro tap(raw, coord, out, kernel, axis) %}
10
  let {{ raw }} = i32({{ out }}) + params.pad{{ axis }} - i32({{ kernel }} * params.dilation{{ axis }});
11
  if ({{ raw }} < 0 || {{ raw }} % i32(params.stride{{ axis }}) != 0) { continue; }
12
  let {{ coord }} = {{ raw }} / i32(params.stride{{ axis }});
13
+ if ({{ coord }} < 0 || {{ coord }} >= i32(params.in{{ axis }})) { continue; }{% endmacro %}
 
14
  {{ env.wgsl.resourceDeclarations }}
15
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
16
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
17
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "index") }}
18
 
19
  let ow = index % params.outW;
20
  var t = index / params.outW;
build/webgpu/conv-transpose3d-weight-reorder.wgsl.jinja CHANGED
@@ -1,36 +1,11 @@
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 dispatch's
4
- // per-axis workgroup fold width (outputs > 16.7M elements).
5
- {% elif note == "limit" %}
6
- // 2D-folded flat index: gid.y carries the high bits past the dispatch's
7
- // per-axis workgroup fold width.
8
- {% elif note == "device-axis" %}
9
- // The flat dispatch is folded across x/y at a fixed per-axis workgroup
10
- // width; 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 dispatch's
13
- // per-axis workgroup fold width (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
- // dispatch's per-axis workgroup fold width.
17
- {% elif note == "dispatch" %}
18
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
19
  // per-axis workgroup fold width.
20
- {% endif %}
21
- {% if bound == "" %}
22
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
23
- {%- elif guardInline %}
24
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
25
- if ({{ name }} >= {{ bound }}) { return; }
26
- {%- else %}
27
- let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
28
  if ({{ name }} >= {{ bound }}) {
29
  return;
30
- }
31
- {%- endif %}
32
- {% endmacro %}
33
-
34
  {% set FORWARD_FLIP = forwardFlip is defined and forwardFlip %}
35
  {% if FORWARD_FLIP %}
36
  // Reorder ONNX ConvTranspose weights from [IC, OC, KD, KH, KW] into the
@@ -48,7 +23,7 @@
48
 
49
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
50
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
51
- {{ flat_index_2d("index", note="device-axis") }}
52
 
53
  {% if FORWARD_FLIP %}
54
  var m = index;
 
1
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
2
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  // 2D-folded flat index: gid.y carries the high bits past the dispatch's
4
  // per-axis workgroup fold width.
5
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
 
 
 
 
 
 
 
6
  if ({{ name }} >= {{ bound }}) {
7
  return;
8
+ }{% endmacro %}
 
 
 
9
  {% set FORWARD_FLIP = forwardFlip is defined and forwardFlip %}
10
  {% if FORWARD_FLIP %}
11
  // Reorder ONNX ConvTranspose weights from [IC, OC, KD, KH, KW] into the
 
23
 
24
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
25
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
26
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "index") }}
27
 
28
  {% if FORWARD_FLIP %}
29
  var m = index;
build/webgpu/manifest.json CHANGED
@@ -50,7 +50,6 @@
50
  "stridesLengthOk": "not has(attrs, \"strides\") or (attrs.strides | length) == spatialRank",
51
  "dilationsLengthOk": "not has(attrs, \"dilations\") or (attrs.dilations | length) == spatialRank",
52
  "padsLengthOk": "not has(attrs, \"pads\") or (attrs.pads | length) == 2 * spatialRank",
53
- "outputPaddingLengthOk": "not has(attrs, \"output_padding\") or (attrs.output_padding | length) == spatialRank",
54
  "kernelD": "attrs.kernel_shape[0] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank == 3 else 1",
55
  "kernelH": "attrs.kernel_shape[spatialRank - 2] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank >= 2 else 1",
56
  "kernelW": "attrs.kernel_shape[spatialRank - 1] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank >= 1 else 1",
@@ -66,6 +65,7 @@
66
  "padBack": "attrs.pads[spatialRank] if padsLengthOk and has(attrs, \"pads\") and spatialRank == 3 else 0",
67
  "padBottom": "attrs.pads[2 * spatialRank - 2] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 2 else 0",
68
  "padRight": "attrs.pads[2 * spatialRank - 1] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 1 else 0",
 
69
  "outputPaddingD": "attrs.output_padding[0] if outputPaddingLengthOk and has(attrs, \"output_padding\") and spatialRank == 3 else 0",
70
  "outputPaddingH": "attrs.output_padding[spatialRank - 2] if outputPaddingLengthOk and has(attrs, \"output_padding\") and spatialRank >= 2 else 0",
71
  "outputPaddingW": "attrs.output_padding[spatialRank - 1] if outputPaddingLengthOk and has(attrs, \"output_padding\") and spatialRank >= 1 else 0",
@@ -104,6 +104,7 @@
104
  "rank5DenseContract": "spatialAttributesOk and ranks.x == 5 and ranks.w == 5 and ranks.y == 5 and attrs.group == 1 and batchChannelContract and outputDispatchFits and dim(shapes.y, 1) == dim(shapes.w, 1)",
105
  "noBiasContract": "not present.bias",
106
  "biasContract": "present.bias and ranks.bias == 1 and dim(shapes.bias, 0) == dim(shapes.y, 1)",
 
107
  "f32Type": "dtypes.T == \"f32\"",
108
  "f16Type": "device.features.has(\"shader-f16\") and dtypes.T == \"f16\"",
109
  "gemmM": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
@@ -150,18 +151,31 @@
150
  "groupedBlockOc": "16 if groupedOutCPerGroup % 16 == 0 else (8 if groupedOutCPerGroup % 8 == 0 else (4 if groupedOutCPerGroup % 4 == 0 else (2 if groupedOutCPerGroup % 2 == 0 else 1)))",
151
  "groupedWeightBytes": "(dim(shapes.x, 1) / attrs.group) * groupedOutCPerGroup * dim(shapes.w, 2) * dim(shapes.w, 3) * 4",
152
  "groupedPlanes": "dim(shapes.x, 0) * attrs.group * (groupedOutCPerGroup / groupedBlockOc)",
153
- "groupedStridePhaseBlockedFits": "groupedWeightBytes <= device.limits.maxComputeWorkgroupStorageSize and groupedPlanes <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(dim(shapes.y, 2) * dim(shapes.y, 3), tunables.WORKGROUP_SIZE) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  },
155
  "bindings": {
156
- "y": { "buffer": "storage", "elementType": "$T" },
157
- "bias": { "buffer": "read-only-storage", "elementType": "$T" },
158
- "x": { "buffer": "read-only-storage", "elementType": "$T" },
159
- "w": { "buffer": "read-only-storage", "elementType": "$phaseWeightElement" },
160
- "y_2": { "name": "y", "buffer": "storage", "elementType": "$phaseOutputElement" },
161
- "wsrc": { "arg": "w", "buffer": "read-only-storage", "elementType": "$T" },
162
  "wt": { "buffer": "storage", "elementType": "$T" },
163
  "params": {
164
- "buffer": "uniform",
165
  "struct": [
166
  { "name": "count", "type": "u32", "value": "numel(shapes.w)" },
167
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.w, 0)" },
@@ -171,13 +185,12 @@
171
  { "name": "kernelW", "type": "u32", "value": "dim(shapes.w, 4)" }
172
  ]
173
  },
174
- "w_2": { "scratch": "wt", "name": "w", "buffer": "read-only-storage", "elementType": "$T" },
175
- "xm": { "arg": "x", "buffer": "read-only-storage", "elementType": "$T" },
176
- "y_3": { "scratch": "cols", "name": "y", "buffer": "storage", "elementType": "$T" },
177
  "cols": { "buffer": "read-only-storage", "elementType": "$T" },
178
- "params_2": {
179
  "name": "params",
180
- "buffer": "uniform",
181
  "struct": [
182
  { "name": "outChannels", "type": "u32", "value": "dim(shapes.w, 1)" },
183
  { "name": "kernelD", "type": "u32", "value": "dim(shapes.w, 2)" },
@@ -201,9 +214,8 @@
201
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
202
  ]
203
  },
204
- "params_4": {
205
  "name": "params",
206
- "buffer": "uniform",
207
  "struct": [
208
  { "name": "count", "type": "u32", "value": "numel(shapes.w)" },
209
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.w, 0)" },
@@ -212,9 +224,8 @@
212
  { "name": "kernelW", "type": "u32", "value": "dim(shapes.w, 3)" }
213
  ]
214
  },
215
- "params_5": {
216
  "name": "params",
217
- "buffer": "uniform",
218
  "struct": [
219
  { "name": "outChannels", "type": "u32", "value": "dim(shapes.w, 1)" },
220
  { "name": "kernelH", "type": "u32", "value": "dim(shapes.w, 2)" },
@@ -232,19 +243,17 @@
232
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
233
  ]
234
  },
235
- "params_6": {
236
  "name": "params",
237
- "buffer": "uniform",
238
  "struct": [
239
  { "name": "M", "type": "u32", "value": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)" },
240
  { "name": "K", "type": "u32", "value": "dim(shapes.x, 1)" },
241
  { "name": "N", "type": "u32", "value": "dim(shapes.x, 2) * dim(shapes.x, 3)" }
242
  ]
243
  },
244
- "w_4": { "name": "w", "buffer": "read-only-storage", "elementType": "$T" },
245
- "params_7": {
246
  "name": "params",
247
- "buffer": "uniform",
248
  "struct": [
249
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
250
  { "name": "inW", "type": "u32", "value": "dim(shapes.x, 2)" },
@@ -257,9 +266,8 @@
257
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
258
  ]
259
  },
260
- "params_8": {
261
  "name": "params",
262
- "buffer": "uniform",
263
  "struct": [
264
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
265
  { "name": "inChannelsPerGroup", "type": "u32", "value": "dim(shapes.x, 1) / attrs.group" },
@@ -274,9 +282,8 @@
274
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
275
  ]
276
  },
277
- "params_9": {
278
  "name": "params",
279
- "buffer": "uniform",
280
  "struct": [
281
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
282
  { "name": "inH", "type": "u32", "value": "dim(shapes.x, 2)" },
@@ -295,9 +302,8 @@
295
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
296
  ]
297
  },
298
- "params_10": {
299
  "name": "params",
300
- "buffer": "uniform",
301
  "struct": [
302
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
303
  { "name": "inD", "type": "u32", "value": "dim(shapes.x, 2)" },
@@ -322,9 +328,8 @@
322
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
323
  ]
324
  },
325
- "params_11": {
326
  "name": "params",
327
- "buffer": "uniform",
328
  "struct": [
329
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
330
  { "name": "inChannelsPerGroup", "type": "u32", "value": "dim(shapes.x, 1) / attrs.group" },
@@ -345,9 +350,8 @@
345
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
346
  ]
347
  },
348
- "params_12": {
349
  "name": "params",
350
- "buffer": "uniform",
351
  "struct": [
352
  { "name": "outChannels", "type": "u32", "value": "dim(shapes.y, 1)" },
353
  { "name": "outH", "type": "u32", "value": "dim(shapes.y, 2)" },
@@ -361,15 +365,12 @@
361
  "id": "empty_input_reduction",
362
  "priority": 100,
363
  "when": ["noBiasContract", "emptyInputReductionContract"],
364
- "derive": { "hasBias": false, "usesF16": "dtypes.T == \"f16\"" },
365
  "passes": [
366
  {
367
  "id": "main",
368
  "name": "ConvTranspose.EmptyInput",
369
  "shader": "conv-transpose-empty-input.wgsl.jinja",
370
  "derive": {
371
- "count": "numel(shapes.y)",
372
- "outChannels": "dim(shapes.y, 1)",
373
  "outputSpatial": "dim(shapes.y, 2) if ranks.y == 3 else (dim(shapes.y, 2) * dim(shapes.y, 3) if ranks.y == 4 else dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.y, 4))"
374
  },
375
  "bindings": ["y"],
@@ -385,15 +386,12 @@
385
  "id": "empty_input_reduction_bias",
386
  "priority": 100,
387
  "when": ["biasContract", "emptyInputReductionContract"],
388
- "derive": { "hasBias": true, "usesF16": "dtypes.T == \"f16\"" },
389
  "passes": [
390
  {
391
  "id": "main",
392
  "name": "ConvTranspose.EmptyInputBias",
393
  "shader": "conv-transpose-empty-input.wgsl.jinja",
394
  "derive": {
395
- "count": "numel(shapes.y)",
396
- "outChannels": "dim(shapes.y, 1)",
397
  "outputSpatial": "dim(shapes.y, 2) if ranks.y == 3 else (dim(shapes.y, 2) * dim(shapes.y, 3) if ranks.y == 4 else dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.y, 4))"
398
  },
399
  "bindings": ["bias", "y"],
@@ -410,7 +408,6 @@
410
  "priority": 40,
411
  "when": ["noBiasContract", "phase1dCovered"],
412
  "derive": {
413
- "hasBias": false,
414
  "phaseWeightElement": "\"vec4<\" ~ dtypes.T ~ \">\"",
415
  "phaseOutputElement": "\"vec2<\" ~ dtypes.T ~ \">\""
416
  },
@@ -420,9 +417,7 @@
420
  "name": "ConvTranspose1d.PhaseTiled",
421
  "shader": "conv-transpose1d-phase-tiled.wgsl.jinja",
422
  "derive": {
423
- "inChannels": "dim(shapes.x, 1)",
424
  "inWidth": "dim(shapes.x, 2)",
425
- "outChannels": "dim(shapes.y, 1)",
426
  "positionTile": "tunables.PHASE1D_POSITION_TILE",
427
  "outputTile": "phase1dOutputTile",
428
  "outputsPerThread": "phase1dOutputsPerThread",
@@ -431,7 +426,7 @@
431
  "xTileElements": "tunables.PHASE1D_K_TILE * (tunables.PHASE1D_POSITION_TILE + 2)",
432
  "weightTileElements": "tunables.PHASE1D_K_TILE * phase1dOutputTile"
433
  },
434
- "bindings": ["x", "w", "y_2"],
435
  "dispatch": {
436
  "x": "ceilDiv(dim(shapes.x, 2), tunables.PHASE1D_POSITION_TILE)",
437
  "y": "ceilDiv(dim(shapes.y, 1), phase1dOutputTile)",
@@ -445,7 +440,6 @@
445
  "priority": 45,
446
  "when": ["biasContract", "phase1dCovered"],
447
  "derive": {
448
- "hasBias": true,
449
  "phaseWeightElement": "\"vec4<\" ~ dtypes.T ~ \">\"",
450
  "phaseOutputElement": "\"vec2<\" ~ dtypes.T ~ \">\""
451
  },
@@ -455,9 +449,7 @@
455
  "name": "ConvTranspose1d.PhaseTiledBias",
456
  "shader": "conv-transpose1d-phase-tiled.wgsl.jinja",
457
  "derive": {
458
- "inChannels": "dim(shapes.x, 1)",
459
  "inWidth": "dim(shapes.x, 2)",
460
- "outChannels": "dim(shapes.y, 1)",
461
  "positionTile": "tunables.PHASE1D_POSITION_TILE",
462
  "outputTile": "phase1dOutputTile",
463
  "outputsPerThread": "phase1dOutputsPerThread",
@@ -466,7 +458,7 @@
466
  "xTileElements": "tunables.PHASE1D_K_TILE * (tunables.PHASE1D_POSITION_TILE + 2)",
467
  "weightTileElements": "tunables.PHASE1D_K_TILE * phase1dOutputTile"
468
  },
469
- "bindings": ["x", "w", "bias", "y_2"],
470
  "dispatch": {
471
  "x": "ceilDiv(dim(shapes.x, 2), tunables.PHASE1D_POSITION_TILE)",
472
  "y": "ceilDiv(dim(shapes.y, 1), phase1dOutputTile)",
@@ -484,9 +476,7 @@
484
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
485
  },
486
  "derive": {
487
- "usesF16": false,
488
  "fScalar": "\"f32\"",
489
- "hasBias": false,
490
  "directMatrixStore": "ctImplicitDirectStore",
491
  "tileRows": "tunables.SGMAT_TILE_ROWS",
492
  "subgroupRows": "tunables.SGMAT_SUBGROUP_ROWS",
@@ -536,7 +526,7 @@
536
  "id": "gemm",
537
  "name": "ConvTranspose3dImplicitSubgroupMatrix",
538
  "shader": "conv-1x1-subgroup-matrix.wgsl.jinja",
539
- "bindings": ["w_2", "xm", "y"],
540
  "dispatch": {
541
  "x": "ceilDiv(ctImplicitOutVolume, tunables.SGMAT_TILE_COLS)",
542
  "y": "ctImplicitYWorkgroups",
@@ -554,9 +544,7 @@
554
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
555
  },
556
  "derive": {
557
- "usesF16": false,
558
  "fScalar": "\"f32\"",
559
- "hasBias": true,
560
  "directMatrixStore": "ctImplicitDirectStore",
561
  "tileRows": "tunables.SGMAT_TILE_ROWS",
562
  "subgroupRows": "tunables.SGMAT_SUBGROUP_ROWS",
@@ -606,7 +594,7 @@
606
  "id": "gemm",
607
  "name": "ConvTranspose3dImplicitSubgroupMatrixBias",
608
  "shader": "conv-1x1-subgroup-matrix.wgsl.jinja",
609
- "bindings": ["w_2", "xm", "bias", "y"],
610
  "dispatch": {
611
  "x": "ceilDiv(ctImplicitOutVolume, tunables.SGMAT_TILE_COLS)",
612
  "y": "ctImplicitYWorkgroups",
@@ -624,9 +612,7 @@
624
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
625
  },
626
  "derive": {
627
- "usesF16": false,
628
  "fScalar": "\"f32\"",
629
- "hasBias": false,
630
  "directMatrixStore": "gemm3dM % tunables.SGMAT_TILE_ROWS == 0",
631
  "tileRows": "tunables.SGMAT_TILE_ROWS",
632
  "tileCols": "tunables.SGMAT_TILE_COLS",
@@ -658,7 +644,7 @@
658
  "name": "ConvTranspose3dColsGemm",
659
  "shader": "conv-1x1-subgroup-matrix.wgsl.jinja",
660
  "derive": { "hasBias": false },
661
- "bindings": ["w_2", "xm", "y_3"],
662
  "dispatch": {
663
  "x": "ceilDiv(gemm3dN, tunables.SGMAT_TILE_COLS)",
664
  "y": "ceilDiv(gemm3dM, tunables.SGMAT_TILE_ROWS)",
@@ -669,7 +655,7 @@
669
  "id": "col2im",
670
  "name": "ConvTranspose3dCol2Im",
671
  "shader": "conv-transpose3d-col2im.wgsl.jinja",
672
- "bindings": ["cols", "y", "params_2"],
673
  "dispatch": {
674
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
675
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -687,9 +673,7 @@
687
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
688
  },
689
  "derive": {
690
- "usesF16": false,
691
  "fScalar": "\"f32\"",
692
- "hasBias": true,
693
  "directMatrixStore": "gemm3dM % tunables.SGMAT_TILE_ROWS == 0",
694
  "tileRows": "tunables.SGMAT_TILE_ROWS",
695
  "tileCols": "tunables.SGMAT_TILE_COLS",
@@ -721,7 +705,7 @@
721
  "name": "ConvTranspose3dColsGemm",
722
  "shader": "conv-1x1-subgroup-matrix.wgsl.jinja",
723
  "derive": { "hasBias": false },
724
- "bindings": ["w_2", "xm", "y_3"],
725
  "dispatch": {
726
  "x": "ceilDiv(gemm3dN, tunables.SGMAT_TILE_COLS)",
727
  "y": "ceilDiv(gemm3dM, tunables.SGMAT_TILE_ROWS)",
@@ -732,7 +716,7 @@
732
  "id": "col2im",
733
  "name": "ConvTranspose3dCol2ImBias",
734
  "shader": "conv-transpose3d-col2im.wgsl.jinja",
735
- "bindings": ["cols", "bias", "y", "params_2"],
736
  "dispatch": {
737
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
738
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -745,7 +729,7 @@
745
  "id": "ncdhw3d_gemm_tiled_f32",
746
  "priority": 30,
747
  "when": ["noBiasContract", "f32Type", "rank5DenseContract", "rank5OutputShapeOk", "gemm3dN > 0", "gemm3dDispatchFits", "f32Gemm3dScratchFits"],
748
- "derive": { "usesF16": false, "hasBias": false, "M": "gemm3dM", "K": "dim(shapes.x, 1)", "N": "gemm3dN" },
749
  "intermediates": [
750
  { "id": "wt", "dtype": "float32", "shape": "[gemmWeightElements]" },
751
  { "id": "cols", "dtype": "float32", "shape": "[gemm3dColumnElements]" }
@@ -768,9 +752,9 @@
768
  "shader": "conv-1x1-gemm-tiled.wgsl.jinja",
769
  "derive": { "hasBias": false },
770
  "bindings": [
771
- "w_2",
772
  "xm",
773
- "y_3",
774
  {
775
  "name": "params",
776
  "struct": [
@@ -786,7 +770,7 @@
786
  "id": "col2im",
787
  "name": "ConvTranspose3dCol2Im",
788
  "shader": "conv-transpose3d-col2im.wgsl.jinja",
789
- "bindings": ["cols", "y", "params_2"],
790
  "dispatch": {
791
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
792
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -799,14 +783,7 @@
799
  "id": "ncdhw3d_gemm_tiled_f32_reg",
800
  "priority": 32,
801
  "when": ["noBiasContract", "f32Type", "rank5DenseContract", "rank5OutputShapeOk", "gemm3dN > 0", "gemm3dDispatchFits", "f32Gemm3dScratchFits", "ceilDiv(gemm3dM, 64) * ceilDiv(gemm3dN, 64) >= tunables.GEMM_REG_MIN_TILE_PRODUCT"],
802
- "derive": {
803
- "usesF16": false,
804
- "hasBias": false,
805
- "gemmKTile": "32 if dim(shapes.x, 1) % 32 == 0 else 16",
806
- "M": "gemm3dM",
807
- "K": "dim(shapes.x, 1)",
808
- "N": "gemm3dN"
809
- },
810
  "intermediates": [
811
  { "id": "wt", "dtype": "float32", "shape": "[gemmWeightElements]" },
812
  { "id": "cols", "dtype": "float32", "shape": "[gemm3dColumnElements]" }
@@ -829,9 +806,9 @@
829
  "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
830
  "derive": { "hasBias": false },
831
  "bindings": [
832
- "w_2",
833
  "xm",
834
- "y_3",
835
  {
836
  "name": "params",
837
  "struct": [
@@ -847,7 +824,7 @@
847
  "id": "col2im",
848
  "name": "ConvTranspose3dCol2Im",
849
  "shader": "conv-transpose3d-col2im.wgsl.jinja",
850
- "bindings": ["cols", "y", "params_2"],
851
  "dispatch": {
852
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
853
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -860,7 +837,7 @@
860
  "id": "ncdhw3d_gemm_tiled_f32_bias",
861
  "priority": 35,
862
  "when": ["biasContract", "f32Type", "rank5DenseContract", "rank5OutputShapeOk", "gemm3dN > 0", "gemm3dDispatchFits", "f32Gemm3dScratchFits"],
863
- "derive": { "usesF16": false, "hasBias": true, "M": "gemm3dM", "K": "dim(shapes.x, 1)", "N": "gemm3dN" },
864
  "intermediates": [
865
  { "id": "wt", "dtype": "float32", "shape": "[gemmWeightElements]" },
866
  { "id": "cols", "dtype": "float32", "shape": "[gemm3dColumnElements]" }
@@ -883,9 +860,9 @@
883
  "shader": "conv-1x1-gemm-tiled.wgsl.jinja",
884
  "derive": { "hasBias": false },
885
  "bindings": [
886
- "w_2",
887
  "xm",
888
- "y_3",
889
  {
890
  "name": "params",
891
  "struct": [
@@ -901,7 +878,7 @@
901
  "id": "col2im",
902
  "name": "ConvTranspose3dCol2ImBias",
903
  "shader": "conv-transpose3d-col2im.wgsl.jinja",
904
- "bindings": ["cols", "bias", "y", "params_2"],
905
  "dispatch": {
906
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
907
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -914,14 +891,7 @@
914
  "id": "ncdhw3d_gemm_tiled_f32_bias_reg",
915
  "priority": 37,
916
  "when": ["biasContract", "f32Type", "rank5DenseContract", "rank5OutputShapeOk", "gemm3dN > 0", "gemm3dDispatchFits", "f32Gemm3dScratchFits", "ceilDiv(gemm3dM, 64) * ceilDiv(gemm3dN, 64) >= tunables.GEMM_REG_MIN_TILE_PRODUCT"],
917
- "derive": {
918
- "usesF16": false,
919
- "hasBias": true,
920
- "gemmKTile": "32 if dim(shapes.x, 1) % 32 == 0 else 16",
921
- "M": "gemm3dM",
922
- "K": "dim(shapes.x, 1)",
923
- "N": "gemm3dN"
924
- },
925
  "intermediates": [
926
  { "id": "wt", "dtype": "float32", "shape": "[gemmWeightElements]" },
927
  { "id": "cols", "dtype": "float32", "shape": "[gemm3dColumnElements]" }
@@ -944,9 +914,9 @@
944
  "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
945
  "derive": { "hasBias": false },
946
  "bindings": [
947
- "w_2",
948
  "xm",
949
- "y_3",
950
  {
951
  "name": "params",
952
  "struct": [
@@ -962,7 +932,7 @@
962
  "id": "col2im",
963
  "name": "ConvTranspose3dCol2ImBias",
964
  "shader": "conv-transpose3d-col2im.wgsl.jinja",
965
- "bindings": ["cols", "bias", "y", "params_2"],
966
  "dispatch": {
967
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
968
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -980,13 +950,10 @@
980
  "subgroupMatrixConfigs": [{ "componentType": "f16", "M": 8, "N": 8, "K": 8 }]
981
  },
982
  "derive": {
983
- "usesF16": true,
984
  "fScalar": "\"f16\"",
985
- "hasBias": false,
986
  "M": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
987
  "K": "dim(shapes.x, 1)",
988
- "N": "dim(shapes.x, 2) * dim(shapes.x, 3)",
989
- "exactTiling": "exactTiling2d"
990
  },
991
  "intermediates": [
992
  {
@@ -1005,7 +972,7 @@
1005
  "id": "reorder",
1006
  "name": "ConvTransposeWeightReorder",
1007
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1008
- "bindings": ["wsrc", "wt", "params_4"],
1009
  "dispatch": {
1010
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1011
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1032,7 +999,7 @@
1032
  "id": "col2im",
1033
  "name": "ConvTransposeCol2Im",
1034
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1035
- "bindings": ["cols", "y", "params_5"],
1036
  "dispatch": {
1037
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1038
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1050,13 +1017,10 @@
1050
  "subgroupMatrixConfigs": [{ "componentType": "f16", "M": 8, "N": 8, "K": 8 }]
1051
  },
1052
  "derive": {
1053
- "usesF16": true,
1054
  "fScalar": "\"f16\"",
1055
- "hasBias": true,
1056
  "M": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
1057
  "K": "dim(shapes.x, 1)",
1058
- "N": "dim(shapes.x, 2) * dim(shapes.x, 3)",
1059
- "exactTiling": "exactTiling2d"
1060
  },
1061
  "intermediates": [
1062
  {
@@ -1075,7 +1039,7 @@
1075
  "id": "reorder",
1076
  "name": "ConvTransposeWeightReorder",
1077
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1078
- "bindings": ["wsrc", "wt", "params_4"],
1079
  "dispatch": {
1080
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1081
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1102,7 +1066,7 @@
1102
  "id": "col2im",
1103
  "name": "ConvTransposeCol2ImBias",
1104
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1105
- "bindings": ["cols", "bias", "y", "params_5"],
1106
  "dispatch": {
1107
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1108
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1115,14 +1079,7 @@
1115
  "id": "nchw2d_gemm_tiled",
1116
  "priority": 30,
1117
  "when": ["noBiasContract", "f16Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f16GemmScratchFits", "rank4OutputShapeOk"],
1118
- "derive": {
1119
- "usesF16": true,
1120
- "hasBias": false,
1121
- "M": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
1122
- "K": "dim(shapes.x, 1)",
1123
- "N": "dim(shapes.x, 2) * dim(shapes.x, 3)",
1124
- "exactTiling": "exactTiling2d"
1125
- },
1126
  "intermediates": [
1127
  {
1128
  "id": "wt",
@@ -1140,7 +1097,7 @@
1140
  "id": "reorder",
1141
  "name": "ConvTransposeWeightReorder",
1142
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1143
- "bindings": ["wsrc", "wt", "params_4"],
1144
  "dispatch": {
1145
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1146
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1152,7 +1109,7 @@
1152
  "name": "ConvTransposeColsGemmTiledReg",
1153
  "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
1154
  "derive": { "hasBias": false },
1155
- "bindings": ["w_2", "xm", "y_3", "params_6"],
1156
  "dispatch": {
1157
  "x": "ceil(dim(shapes.x, 2) * dim(shapes.x, 3) / 64)",
1158
  "y": "ceil(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) / 64)",
@@ -1163,7 +1120,7 @@
1163
  "id": "col2im",
1164
  "name": "ConvTransposeCol2Im",
1165
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1166
- "bindings": ["cols", "y", "params_5"],
1167
  "dispatch": {
1168
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1169
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1176,14 +1133,7 @@
1176
  "id": "nchw2d_gemm_tiled_bias",
1177
  "priority": 35,
1178
  "when": ["biasContract", "f16Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f16GemmScratchFits", "rank4OutputShapeOk"],
1179
- "derive": {
1180
- "usesF16": true,
1181
- "hasBias": true,
1182
- "M": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
1183
- "K": "dim(shapes.x, 1)",
1184
- "N": "dim(shapes.x, 2) * dim(shapes.x, 3)",
1185
- "exactTiling": "exactTiling2d"
1186
- },
1187
  "intermediates": [
1188
  {
1189
  "id": "wt",
@@ -1201,7 +1151,7 @@
1201
  "id": "reorder",
1202
  "name": "ConvTransposeWeightReorder",
1203
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1204
- "bindings": ["wsrc", "wt", "params_4"],
1205
  "dispatch": {
1206
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1207
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1213,7 +1163,7 @@
1213
  "name": "ConvTransposeColsGemmTiledReg",
1214
  "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
1215
  "derive": { "hasBias": false },
1216
- "bindings": ["w_2", "xm", "y_3", "params_6"],
1217
  "dispatch": {
1218
  "x": "ceil(dim(shapes.x, 2) * dim(shapes.x, 3) / 64)",
1219
  "y": "ceil(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) / 64)",
@@ -1224,7 +1174,7 @@
1224
  "id": "col2im",
1225
  "name": "ConvTransposeCol2ImBias",
1226
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1227
- "bindings": ["cols", "bias", "y", "params_5"],
1228
  "dispatch": {
1229
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1230
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1237,14 +1187,7 @@
1237
  "id": "nchw2d_gemm_tiled_f32",
1238
  "priority": 30,
1239
  "when": ["noBiasContract", "f32Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f32GemmScratchFits", "rank4OutputShapeOk"],
1240
- "derive": {
1241
- "usesF16": false,
1242
- "hasBias": false,
1243
- "M": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
1244
- "K": "dim(shapes.x, 1)",
1245
- "N": "dim(shapes.x, 2) * dim(shapes.x, 3)",
1246
- "exactTiling": "exactTiling2d"
1247
- },
1248
  "intermediates": [
1249
  {
1250
  "id": "wt",
@@ -1262,7 +1205,7 @@
1262
  "id": "reorder",
1263
  "name": "ConvTransposeWeightReorder",
1264
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1265
- "bindings": ["wsrc", "wt", "params_4"],
1266
  "dispatch": {
1267
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1268
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1274,7 +1217,7 @@
1274
  "name": "ConvTransposeColsGemmTiled",
1275
  "shader": "conv-1x1-gemm-tiled.wgsl.jinja",
1276
  "derive": { "hasBias": false },
1277
- "bindings": ["w_2", "xm", "y_3", "params_6"],
1278
  "dispatch": {
1279
  "x": "ceil(dim(shapes.x, 2) * dim(shapes.x, 3) / 32)",
1280
  "y": "ceil(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) / 32)",
@@ -1285,7 +1228,7 @@
1285
  "id": "col2im",
1286
  "name": "ConvTransposeCol2Im",
1287
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1288
- "bindings": ["cols", "y", "params_5"],
1289
  "dispatch": {
1290
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1291
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1298,14 +1241,7 @@
1298
  "id": "nchw2d_gemm_tiled_f32_reg",
1299
  "priority": 32,
1300
  "when": ["noBiasContract", "f32Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f32GemmScratchFits", "rank4OutputShapeOk", "ceilDiv(gemmM, 64) * ceilDiv(gemmN, 64) >= tunables.GEMM_REG_MIN_TILE_PRODUCT"],
1301
- "derive": {
1302
- "usesF16": false,
1303
- "hasBias": false,
1304
- "M": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
1305
- "K": "dim(shapes.x, 1)",
1306
- "N": "dim(shapes.x, 2) * dim(shapes.x, 3)",
1307
- "exactTiling": "exactTiling2d"
1308
- },
1309
  "intermediates": [
1310
  {
1311
  "id": "wt",
@@ -1323,7 +1259,7 @@
1323
  "id": "reorder",
1324
  "name": "ConvTransposeWeightReorder",
1325
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1326
- "bindings": ["wsrc", "wt", "params_4"],
1327
  "dispatch": {
1328
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1329
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1335,7 +1271,7 @@
1335
  "name": "ConvTransposeColsGemmRegTiled",
1336
  "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
1337
  "derive": { "hasBias": false },
1338
- "bindings": ["w_2", "xm", "y_3", "params_6"],
1339
  "dispatch": {
1340
  "x": "ceil(dim(shapes.x, 2) * dim(shapes.x, 3) / 64)",
1341
  "y": "ceil(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) / 64)",
@@ -1346,7 +1282,7 @@
1346
  "id": "col2im",
1347
  "name": "ConvTransposeCol2Im",
1348
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1349
- "bindings": ["cols", "y", "params_5"],
1350
  "dispatch": {
1351
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1352
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1359,14 +1295,7 @@
1359
  "id": "nchw2d_gemm_tiled_f32_bias",
1360
  "priority": 35,
1361
  "when": ["biasContract", "f32Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f32GemmScratchFits", "rank4OutputShapeOk"],
1362
- "derive": {
1363
- "usesF16": false,
1364
- "hasBias": true,
1365
- "M": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
1366
- "K": "dim(shapes.x, 1)",
1367
- "N": "dim(shapes.x, 2) * dim(shapes.x, 3)",
1368
- "exactTiling": "exactTiling2d"
1369
- },
1370
  "intermediates": [
1371
  {
1372
  "id": "wt",
@@ -1384,7 +1313,7 @@
1384
  "id": "reorder",
1385
  "name": "ConvTransposeWeightReorder",
1386
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1387
- "bindings": ["wsrc", "wt", "params_4"],
1388
  "dispatch": {
1389
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1390
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1396,7 +1325,7 @@
1396
  "name": "ConvTransposeColsGemmTiled",
1397
  "shader": "conv-1x1-gemm-tiled.wgsl.jinja",
1398
  "derive": { "hasBias": false },
1399
- "bindings": ["w_2", "xm", "y_3", "params_6"],
1400
  "dispatch": {
1401
  "x": "ceil(dim(shapes.x, 2) * dim(shapes.x, 3) / 32)",
1402
  "y": "ceil(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) / 32)",
@@ -1407,7 +1336,7 @@
1407
  "id": "col2im",
1408
  "name": "ConvTransposeCol2ImBias",
1409
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1410
- "bindings": ["cols", "bias", "y", "params_5"],
1411
  "dispatch": {
1412
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1413
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1420,14 +1349,7 @@
1420
  "id": "nchw2d_gemm_tiled_f32_bias_reg",
1421
  "priority": 37,
1422
  "when": ["biasContract", "f32Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f32GemmScratchFits", "rank4OutputShapeOk", "ceilDiv(gemmM, 64) * ceilDiv(gemmN, 64) >= tunables.GEMM_REG_MIN_TILE_PRODUCT"],
1423
- "derive": {
1424
- "usesF16": false,
1425
- "hasBias": true,
1426
- "M": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
1427
- "K": "dim(shapes.x, 1)",
1428
- "N": "dim(shapes.x, 2) * dim(shapes.x, 3)",
1429
- "exactTiling": "exactTiling2d"
1430
- },
1431
  "intermediates": [
1432
  {
1433
  "id": "wt",
@@ -1445,7 +1367,7 @@
1445
  "id": "reorder",
1446
  "name": "ConvTransposeWeightReorder",
1447
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1448
- "bindings": ["wsrc", "wt", "params_4"],
1449
  "dispatch": {
1450
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1451
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1457,7 +1379,7 @@
1457
  "name": "ConvTransposeColsGemmRegTiled",
1458
  "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
1459
  "derive": { "hasBias": false },
1460
- "bindings": ["w_2", "xm", "y_3", "params_6"],
1461
  "dispatch": {
1462
  "x": "ceil(dim(shapes.x, 2) * dim(shapes.x, 3) / 64)",
1463
  "y": "ceil(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) / 64)",
@@ -1468,7 +1390,7 @@
1468
  "id": "col2im",
1469
  "name": "ConvTransposeCol2ImBias",
1470
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1471
- "bindings": ["cols", "bias", "y", "params_5"],
1472
  "dispatch": {
1473
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1474
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1489,23 +1411,18 @@
1489
  "shader": "conv-transpose2d-phase-gemm-tiled.wgsl.jinja",
1490
  "derive": {
1491
  "batch": "dim(shapes.x, 0)",
1492
- "inChannels": "dim(shapes.x, 1)",
1493
  "inHeight": "dim(shapes.x, 2)",
1494
  "inWidth": "dim(shapes.x, 3)",
1495
- "outChannels": "dim(shapes.y, 1)",
1496
  "outHeight": "dim(shapes.y, 2)",
1497
  "outWidth": "dim(shapes.y, 3)",
1498
- "padH": "effectivePadTop",
1499
- "padW": "effectivePadLeft",
1500
  "mTile": "tunables.PHASE2D_GEMM_TILE",
1501
  "nTile": "tunables.PHASE2D_GEMM_TILE",
1502
  "kTile": "tunables.PHASE2D_GEMM_K_TILE",
1503
  "threadRows": "tunables.PHASE2D_GEMM_THREAD_TILE",
1504
  "threadColumns": "tunables.PHASE2D_GEMM_THREAD_TILE",
1505
- "workgroupEdge": "tunables.PHASE2D_GEMM_WORKGROUP_EDGE",
1506
- "hasBias": false
1507
  },
1508
- "bindings": ["x", "w", "y_2"],
1509
  "dispatch": {
1510
  "x": "ceilDiv(dim(shapes.x, 2) * dim(shapes.x, 3), tunables.PHASE2D_GEMM_TILE)",
1511
  "y": "ceilDiv(dim(shapes.y, 1), tunables.PHASE2D_GEMM_TILE)",
@@ -1526,23 +1443,18 @@
1526
  "shader": "conv-transpose2d-phase-gemm-tiled.wgsl.jinja",
1527
  "derive": {
1528
  "batch": "dim(shapes.x, 0)",
1529
- "inChannels": "dim(shapes.x, 1)",
1530
  "inHeight": "dim(shapes.x, 2)",
1531
  "inWidth": "dim(shapes.x, 3)",
1532
- "outChannels": "dim(shapes.y, 1)",
1533
  "outHeight": "dim(shapes.y, 2)",
1534
  "outWidth": "dim(shapes.y, 3)",
1535
- "padH": "effectivePadTop",
1536
- "padW": "effectivePadLeft",
1537
  "mTile": "tunables.PHASE2D_GEMM_TILE",
1538
  "nTile": "tunables.PHASE2D_GEMM_TILE",
1539
  "kTile": "tunables.PHASE2D_GEMM_K_TILE",
1540
  "threadRows": "tunables.PHASE2D_GEMM_THREAD_TILE",
1541
  "threadColumns": "tunables.PHASE2D_GEMM_THREAD_TILE",
1542
- "workgroupEdge": "tunables.PHASE2D_GEMM_WORKGROUP_EDGE",
1543
- "hasBias": true
1544
  },
1545
- "bindings": ["x", "w", "bias", "y_2"],
1546
  "dispatch": {
1547
  "x": "ceilDiv(dim(shapes.x, 2) * dim(shapes.x, 3), tunables.PHASE2D_GEMM_TILE)",
1548
  "y": "ceilDiv(dim(shapes.y, 1), tunables.PHASE2D_GEMM_TILE)",
@@ -1555,37 +1467,22 @@
1555
  "id": "nchw2d_grouped_stride_phase_blocked",
1556
  "priority": 25,
1557
  "when": ["noBiasContract", "rank4GroupedContract", "f16Ok(dtypes.T)", "strideH >= 2", "strideW >= 2", "dilationH == 1", "dilationW == 1", "dim(shapes.w, 2) >= 2", "dim(shapes.w, 2) <= 7", "dim(shapes.w, 3) >= 2", "dim(shapes.w, 3) <= 7", "dim(shapes.y, 2) == (dim(shapes.x, 2) - 1) * strideH - (padTop + padBottom) + dim(shapes.w, 2) + outputPaddingH", "dim(shapes.y, 3) == (dim(shapes.x, 3) - 1) * strideW - (padLeft + padRight) + dim(shapes.w, 3) + outputPaddingW", "groupedStridePhaseBlockedFits"],
1558
- "derive": {
1559
- "usesF16": "dtypes.T == \"f16\"",
1560
- "scalar": "dtypes.T",
1561
- "phaseWeightElement": "dtypes.T",
1562
- "phaseOutputElement": "dtypes.T"
1563
- },
1564
  "passes": [
1565
  {
1566
  "id": "main",
1567
  "name": "ConvTranspose.GroupedStridePhaseBlocked",
1568
  "shader": "conv-transpose2d-grouped-stride-phase-blocked.wgsl.jinja",
1569
  "derive": {
1570
- "outC": "dim(shapes.y, 1)",
1571
  "outCPerGroup": "groupedOutCPerGroup",
1572
- "outH": "dim(shapes.y, 2)",
1573
- "outW": "dim(shapes.y, 3)",
1574
- "inC": "dim(shapes.x, 1)",
1575
- "inCPerGroup": "dim(shapes.x, 1) / attrs.group",
1576
- "inH": "dim(shapes.x, 2)",
1577
- "inW": "dim(shapes.x, 3)",
1578
  "kernelHSpec": "dim(shapes.w, 2)",
1579
  "kernelWSpec": "dim(shapes.w, 3)",
1580
  "strideHSpec": "strideH",
1581
  "strideWSpec": "strideW",
1582
- "padH": "effectivePadTop",
1583
- "padW": "effectivePadLeft",
1584
  "groups": "attrs.group",
1585
- "ocBlock": "groupedBlockOc",
1586
- "hasBias": false
1587
  },
1588
- "bindings": ["x", "w", "y_2"],
1589
  "dispatch": {
1590
  "x": "ceilDiv(dim(shapes.y, 2) * dim(shapes.y, 3), tunables.WORKGROUP_SIZE)",
1591
  "y": "groupedPlanes"
@@ -1594,85 +1491,54 @@
1594
  ]
1595
  },
1596
  {
1597
- "id": "nchw2d_grouped_stride_phase_blocked_bias",
1598
- "priority": 25,
1599
- "when": ["biasContract", "rank4GroupedContract", "f16Ok(dtypes.T)", "strideH >= 2", "strideW >= 2", "dilationH == 1", "dilationW == 1", "dim(shapes.w, 2) >= 2", "dim(shapes.w, 2) <= 7", "dim(shapes.w, 3) >= 2", "dim(shapes.w, 3) <= 7", "dim(shapes.y, 2) == (dim(shapes.x, 2) - 1) * strideH - (padTop + padBottom) + dim(shapes.w, 2) + outputPaddingH", "dim(shapes.y, 3) == (dim(shapes.x, 3) - 1) * strideW - (padLeft + padRight) + dim(shapes.w, 3) + outputPaddingW", "groupedStridePhaseBlockedFits"],
1600
- "derive": {
1601
- "usesF16": "dtypes.T == \"f16\"",
1602
- "scalar": "dtypes.T",
1603
- "phaseWeightElement": "dtypes.T",
1604
- "phaseOutputElement": "dtypes.T"
1605
- },
1606
  "passes": [
1607
  {
1608
  "id": "main",
1609
- "name": "ConvTranspose.GroupedStridePhaseBlockedBias",
1610
- "shader": "conv-transpose2d-grouped-stride-phase-blocked.wgsl.jinja",
1611
  "derive": {
1612
- "outC": "dim(shapes.y, 1)",
1613
- "outCPerGroup": "groupedOutCPerGroup",
1614
- "outH": "dim(shapes.y, 2)",
1615
- "outW": "dim(shapes.y, 3)",
1616
- "inC": "dim(shapes.x, 1)",
1617
- "inCPerGroup": "dim(shapes.x, 1) / attrs.group",
1618
- "inH": "dim(shapes.x, 2)",
1619
- "inW": "dim(shapes.x, 3)",
1620
  "kernelHSpec": "dim(shapes.w, 2)",
1621
  "kernelWSpec": "dim(shapes.w, 3)",
1622
  "strideHSpec": "strideH",
1623
- "strideWSpec": "strideW",
1624
- "padH": "effectivePadTop",
1625
- "padW": "effectivePadLeft",
1626
- "groups": "attrs.group",
1627
- "ocBlock": "groupedBlockOc",
1628
- "hasBias": true
1629
  },
1630
- "bindings": ["x", "w", "bias", "y_2"],
1631
  "dispatch": {
1632
- "x": "ceilDiv(dim(shapes.y, 2) * dim(shapes.y, 3), tunables.WORKGROUP_SIZE)",
1633
- "y": "groupedPlanes"
 
1634
  }
1635
  }
1636
  ]
1637
  },
1638
  {
1639
- "id": "nchw2d_grouped_stride_phase",
1640
- "priority": 20,
1641
- "when": ["noBiasContract", "rank4GroupedContract", "f16Ok(dtypes.T)", "strideH >= 2", "strideW >= 2", "dilationH == 1", "dilationW == 1", "dim(shapes.w, 2) >= 2", "dim(shapes.w, 2) <= 7", "dim(shapes.w, 3) >= 2", "dim(shapes.w, 3) <= 7", "dim(shapes.y, 2) == (dim(shapes.x, 2) - 1) * strideH - (padTop + padBottom) + dim(shapes.w, 2) + outputPaddingH", "dim(shapes.y, 3) == (dim(shapes.x, 3) - 1) * strideW - (padLeft + padRight) + dim(shapes.w, 3) + outputPaddingW"],
1642
- "derive": {
1643
- "usesF16": "dtypes.T == \"f16\"",
1644
- "scalar": "dtypes.T",
1645
- "phaseWeightElement": "dtypes.T",
1646
- "phaseOutputElement": "dtypes.T"
1647
- },
1648
  "passes": [
1649
  {
1650
  "id": "main",
1651
- "name": "ConvTranspose.GroupedStridePhase",
1652
- "shader": "conv-transpose2d-grouped-stride-phase.wgsl.jinja",
1653
  "derive": {
1654
- "count": "numel(shapes.y)",
1655
- "outC": "dim(shapes.y, 1)",
1656
- "outCPerGroup": "dim(shapes.y, 1) / attrs.group",
1657
- "outH": "dim(shapes.y, 2)",
1658
- "outW": "dim(shapes.y, 3)",
1659
- "inC": "dim(shapes.x, 1)",
1660
- "inCPerGroup": "dim(shapes.x, 1) / attrs.group",
1661
- "inH": "dim(shapes.x, 2)",
1662
- "inW": "dim(shapes.x, 3)",
1663
  "kernelHSpec": "dim(shapes.w, 2)",
1664
  "kernelWSpec": "dim(shapes.w, 3)",
1665
  "strideHSpec": "strideH",
1666
  "strideWSpec": "strideW",
1667
- "padH": "effectivePadTop",
1668
- "padW": "effectivePadLeft",
1669
- "hasBias": false
1670
  },
1671
- "bindings": ["x", "w", "y_2"],
1672
  "dispatch": {
1673
- "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1674
- "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1675
- "z": 1
1676
  }
1677
  }
1678
  ]
@@ -1681,36 +1547,20 @@
1681
  "id": "nchw2d_grouped_stride_phase_bias",
1682
  "priority": 20,
1683
  "when": ["biasContract", "rank4GroupedContract", "f16Ok(dtypes.T)", "strideH >= 2", "strideW >= 2", "dilationH == 1", "dilationW == 1", "dim(shapes.w, 2) >= 2", "dim(shapes.w, 2) <= 7", "dim(shapes.w, 3) >= 2", "dim(shapes.w, 3) <= 7", "dim(shapes.y, 2) == (dim(shapes.x, 2) - 1) * strideH - (padTop + padBottom) + dim(shapes.w, 2) + outputPaddingH", "dim(shapes.y, 3) == (dim(shapes.x, 3) - 1) * strideW - (padLeft + padRight) + dim(shapes.w, 3) + outputPaddingW"],
1684
- "derive": {
1685
- "usesF16": "dtypes.T == \"f16\"",
1686
- "scalar": "dtypes.T",
1687
- "phaseWeightElement": "dtypes.T",
1688
- "phaseOutputElement": "dtypes.T"
1689
- },
1690
  "passes": [
1691
  {
1692
  "id": "main",
1693
  "name": "ConvTranspose.GroupedStridePhaseBias",
1694
  "shader": "conv-transpose2d-grouped-stride-phase.wgsl.jinja",
1695
  "derive": {
1696
- "count": "numel(shapes.y)",
1697
- "outC": "dim(shapes.y, 1)",
1698
  "outCPerGroup": "dim(shapes.y, 1) / attrs.group",
1699
- "outH": "dim(shapes.y, 2)",
1700
- "outW": "dim(shapes.y, 3)",
1701
- "inC": "dim(shapes.x, 1)",
1702
- "inCPerGroup": "dim(shapes.x, 1) / attrs.group",
1703
- "inH": "dim(shapes.x, 2)",
1704
- "inW": "dim(shapes.x, 3)",
1705
  "kernelHSpec": "dim(shapes.w, 2)",
1706
  "kernelWSpec": "dim(shapes.w, 3)",
1707
  "strideHSpec": "strideH",
1708
- "strideWSpec": "strideW",
1709
- "padH": "effectivePadTop",
1710
- "padW": "effectivePadLeft",
1711
- "hasBias": true
1712
  },
1713
- "bindings": ["x", "w", "bias", "y_2"],
1714
  "dispatch": {
1715
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1716
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1722,33 +1572,14 @@
1722
  {
1723
  "id": "ncl1d",
1724
  "priority": 0,
1725
- "when": ["noBiasContract", "f32Type", "rank3DenseContract", "(rank3OutputShapeOk or ((attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") and dim(shapes.y, 2) == dim(shapes.x, 2) * strideW))"],
1726
- "derive": {},
1727
- "passes": [
1728
- {
1729
- "id": "main",
1730
- "name": "ConvTranspose1d",
1731
- "shader": "conv-transpose1d-ncl.wgsl.jinja",
1732
- "bindings": ["x", "w_4", "y", "params_7"],
1733
- "dispatch": {
1734
- "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1735
- "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1736
- "z": 1
1737
- }
1738
- }
1739
- ]
1740
- },
1741
- {
1742
- "id": "ncl1d_f16",
1743
- "priority": 5,
1744
- "when": ["noBiasContract", "f16Type", "rank3DenseContract", "rank3OutputShapeOk"],
1745
- "derive": { "usesF16": true },
1746
  "passes": [
1747
  {
1748
  "id": "main",
1749
  "name": "ConvTranspose1d",
1750
  "shader": "conv-transpose1d-ncl.wgsl.jinja",
1751
- "bindings": ["x", "w_4", "y", "params_7"],
1752
  "dispatch": {
1753
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1754
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1760,33 +1591,14 @@
1760
  {
1761
  "id": "ncl1d_bias",
1762
  "priority": 10,
1763
- "when": ["biasContract", "f32Type", "rank3DenseContract", "rank3OutputShapeOk"],
1764
- "derive": { "hasBias": true },
1765
  "passes": [
1766
  {
1767
  "id": "main",
1768
  "name": "ConvTranspose1dBias",
1769
  "shader": "conv-transpose1d-ncl.wgsl.jinja",
1770
- "bindings": ["x", "w_4", "bias", "y", "params_7"],
1771
- "dispatch": {
1772
- "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1773
- "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1774
- "z": 1
1775
- }
1776
- }
1777
- ]
1778
- },
1779
- {
1780
- "id": "ncl1d_bias_f16",
1781
- "priority": 10,
1782
- "when": ["biasContract", "f16Type", "rank3DenseContract", "rank3OutputShapeOk"],
1783
- "derive": { "hasBias": true, "usesF16": true },
1784
- "passes": [
1785
- {
1786
- "id": "main",
1787
- "name": "ConvTranspose1dBias",
1788
- "shader": "conv-transpose1d-ncl.wgsl.jinja",
1789
- "bindings": ["x", "w_4", "bias", "y", "params_7"],
1790
  "dispatch": {
1791
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1792
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1798,33 +1610,14 @@
1798
  {
1799
  "id": "ncl1d_grouped",
1800
  "priority": 10,
1801
- "when": ["noBiasContract", "f32Type", "rank3GroupedContract", "rank3OutputShapeOk"],
1802
- "derive": { "hasBias": false, "grouped": true },
1803
  "passes": [
1804
  {
1805
  "id": "main",
1806
  "name": "ConvTranspose1dGrouped",
1807
  "shader": "conv-transpose1d-ncl.wgsl.jinja",
1808
- "bindings": ["x", "w_4", "y", "params_8"],
1809
- "dispatch": {
1810
- "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1811
- "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1812
- "z": 1
1813
- }
1814
- }
1815
- ]
1816
- },
1817
- {
1818
- "id": "ncl1d_grouped_f16",
1819
- "priority": 5,
1820
- "when": ["noBiasContract", "f16Type", "rank3GroupedContract", "rank3OutputShapeOk"],
1821
- "derive": { "hasBias": false, "usesF16": true, "grouped": true },
1822
- "passes": [
1823
- {
1824
- "id": "main",
1825
- "name": "ConvTranspose1dGrouped",
1826
- "shader": "conv-transpose1d-ncl.wgsl.jinja",
1827
- "bindings": ["x", "w_4", "y", "params_8"],
1828
  "dispatch": {
1829
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1830
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1836,33 +1629,14 @@
1836
  {
1837
  "id": "ncl1d_grouped_bias",
1838
  "priority": 15,
1839
- "when": ["biasContract", "f32Type", "rank3GroupedContract", "rank3OutputShapeOk"],
1840
- "derive": { "hasBias": true, "grouped": true },
1841
  "passes": [
1842
  {
1843
  "id": "main",
1844
  "name": "ConvTranspose1dGroupedBias",
1845
  "shader": "conv-transpose1d-ncl.wgsl.jinja",
1846
- "bindings": ["x", "w_4", "bias", "y", "params_8"],
1847
- "dispatch": {
1848
- "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1849
- "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1850
- "z": 1
1851
- }
1852
- }
1853
- ]
1854
- },
1855
- {
1856
- "id": "ncl1d_grouped_bias_f16",
1857
- "priority": 8,
1858
- "when": ["biasContract", "f16Type", "rank3GroupedContract", "rank3OutputShapeOk"],
1859
- "derive": { "hasBias": true, "usesF16": true, "grouped": true },
1860
- "passes": [
1861
- {
1862
- "id": "main",
1863
- "name": "ConvTranspose1dGroupedBias",
1864
- "shader": "conv-transpose1d-ncl.wgsl.jinja",
1865
- "bindings": ["x", "w_4", "bias", "y", "params_8"],
1866
  "dispatch": {
1867
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1868
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1874,14 +1648,14 @@
1874
  {
1875
  "id": "nchw2d",
1876
  "priority": 0,
1877
- "when": ["noBiasContract", "f32Type", "rank4DenseContract", "(rank4OutputShapeOk or ((attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") and dim(shapes.y, 2) == dim(shapes.x, 2) * strideH and dim(shapes.y, 3) == dim(shapes.x, 3) * strideW))"],
1878
- "derive": {},
1879
  "passes": [
1880
  {
1881
  "id": "main",
1882
  "name": "ConvTranspose",
1883
  "shader": "conv-transpose2d-nchw.wgsl.jinja",
1884
- "bindings": ["x", "w_4", "y", "params_9"],
1885
  "dispatch": {
1886
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1887
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1893,14 +1667,14 @@
1893
  {
1894
  "id": "nchw2d_bias",
1895
  "priority": 10,
1896
- "when": ["biasContract", "f32Type", "rank4DenseContract", "rank4OutputShapeOk"],
1897
- "derive": { "hasBias": true },
1898
  "passes": [
1899
  {
1900
  "id": "main",
1901
  "name": "ConvTranspose",
1902
  "shader": "conv-transpose2d-nchw.wgsl.jinja",
1903
- "bindings": ["x", "w_4", "bias", "y", "params_9"],
1904
  "dispatch": {
1905
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1906
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1919,7 +1693,7 @@
1919
  "id": "main",
1920
  "name": "ConvTranspose3d",
1921
  "shader": "conv-transpose3d-ncdhw.wgsl.jinja",
1922
- "bindings": ["x", "w_4", "y", "params_10"],
1923
  "dispatch": {
1924
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1925
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1932,13 +1706,13 @@
1932
  "id": "ncdhw3d_bias",
1933
  "priority": 10,
1934
  "when": ["biasContract", "f32Type", "rank5DenseContract", "rank5OutputShapeOk"],
1935
- "derive": { "hasBias": true },
1936
  "passes": [
1937
  {
1938
  "id": "main",
1939
  "name": "ConvTranspose3dBias",
1940
  "shader": "conv-transpose3d-ncdhw.wgsl.jinja",
1941
- "bindings": ["x", "w_4", "bias", "y", "params_10"],
1942
  "dispatch": {
1943
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1944
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1950,14 +1724,14 @@
1950
  {
1951
  "id": "nchw2d_grouped",
1952
  "priority": 10,
1953
- "when": ["noBiasContract", "f32Type", "rank4GroupedContract", "rank4OutputShapeOk"],
1954
- "derive": { "hasBias": false, "grouped": true },
1955
  "passes": [
1956
  {
1957
  "id": "main",
1958
  "name": "ConvTransposeGrouped",
1959
  "shader": "conv-transpose2d-nchw.wgsl.jinja",
1960
- "bindings": ["x", "w_4", "y", "params_11"],
1961
  "dispatch": {
1962
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1963
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1967,16 +1741,16 @@
1967
  ]
1968
  },
1969
  {
1970
- "id": "nchw2d_grouped_f16",
1971
- "priority": 5,
1972
- "when": ["noBiasContract", "f16Type", "rank4GroupedContract", "rank4OutputShapeOk"],
1973
- "derive": { "hasBias": false, "usesF16": true, "grouped": true },
1974
  "passes": [
1975
  {
1976
  "id": "main",
1977
  "name": "ConvTransposeGrouped",
1978
  "shader": "conv-transpose2d-nchw.wgsl.jinja",
1979
- "bindings": ["x", "w_4", "y", "params_11"],
1980
  "dispatch": {
1981
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1982
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -1986,97 +1760,103 @@
1986
  ]
1987
  },
1988
  {
1989
- "id": "nchw2d_grouped_bias_f16",
1990
- "priority": 8,
1991
- "when": ["biasContract", "f16Type", "rank4GroupedContract", "rank4OutputShapeOk"],
1992
- "derive": { "hasBias": true, "usesF16": true, "grouped": true },
1993
  "passes": [
1994
  {
1995
  "id": "main",
1996
- "name": "ConvTransposeGrouped",
1997
  "shader": "conv-transpose2d-nchw.wgsl.jinja",
1998
- "bindings": ["x", "w_4", "bias", "y", "params_11"],
1999
  "dispatch": {
2000
- "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
2001
- "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
2002
  "z": 1
2003
  }
2004
  }
2005
  ]
2006
  },
2007
  {
2008
- "id": "nchw2d_grouped_bias",
2009
- "priority": 15,
2010
- "when": ["biasContract", "f32Type", "rank4GroupedContract", "rank4OutputShapeOk"],
2011
- "derive": { "hasBias": true, "grouped": true },
2012
- "passes": [
 
 
 
 
 
 
 
 
2013
  {
2014
- "id": "main",
2015
- "name": "ConvTransposeGrouped",
2016
- "shader": "conv-transpose2d-nchw.wgsl.jinja",
2017
- "bindings": ["x", "w_4", "bias", "y", "params_11"],
2018
- "dispatch": {
2019
- "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
2020
- "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
2021
- "z": 1
2022
- }
2023
  }
2024
- ]
2025
- },
2026
- {
2027
- "id": "nchw2d_f16",
2028
- "priority": 5,
2029
- "when": ["noBiasContract", "f16Type", "rank4DenseContract", "(rank4OutputShapeOk or ((attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") and dim(shapes.y, 2) == dim(shapes.x, 2) * strideH and dim(shapes.y, 3) == dim(shapes.x, 3) * strideW))"],
2030
- "derive": { "usesF16": true },
2031
  "passes": [
2032
  {
2033
- "id": "main",
2034
- "name": "ConvTranspose2dF16",
2035
- "shader": "conv-transpose2d-nchw.wgsl.jinja",
2036
- "bindings": ["x", "w_4", "y", "params_9"],
2037
  "dispatch": {
2038
- "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
2039
- "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
2040
  "z": 1
2041
  }
 
 
 
 
 
 
 
2042
  }
2043
  ]
2044
  },
2045
  {
2046
- "id": "nchw2d_bias_f16",
2047
- "priority": 10,
2048
- "when": ["biasContract", "f16Type", "rank4DenseContract", "rank4OutputShapeOk"],
2049
- "derive": { "hasBias": true, "usesF16": true },
2050
- "passes": [
 
 
 
 
 
 
 
 
2051
  {
2052
- "id": "main",
2053
- "name": "ConvTranspose2dBiasF16",
2054
- "shader": "conv-transpose2d-nchw.wgsl.jinja",
2055
- "bindings": ["x", "w_4", "bias", "y", "params_9"],
2056
- "dispatch": {
2057
- "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
2058
- "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
2059
- "z": 1
2060
- }
2061
  }
2062
- ]
2063
- },
2064
- {
2065
- "id": "nchw2d_depthwise_pointwise_vec4",
2066
- "priority": 30,
2067
- "when": ["noBiasContract", "f32Type", "rank4DepthwisePointwiseVec4Contract"],
2068
- "derive": { "hasBias": false, "grouped": true, "depthwisePointwiseVec4": true },
2069
  "passes": [
2070
  {
2071
- "id": "main",
2072
- "name": "ConvTransposeDepthwisePointwiseVec4",
2073
- "shader": "conv-transpose2d-nchw.wgsl.jinja",
2074
- "bindings": ["x", "w_4", "y", "params_12"],
2075
  "dispatch": {
2076
- "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
2077
- "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
2078
  "z": 1
2079
  }
 
 
 
 
 
 
 
2080
  }
2081
  ]
2082
  }
 
50
  "stridesLengthOk": "not has(attrs, \"strides\") or (attrs.strides | length) == spatialRank",
51
  "dilationsLengthOk": "not has(attrs, \"dilations\") or (attrs.dilations | length) == spatialRank",
52
  "padsLengthOk": "not has(attrs, \"pads\") or (attrs.pads | length) == 2 * spatialRank",
 
53
  "kernelD": "attrs.kernel_shape[0] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank == 3 else 1",
54
  "kernelH": "attrs.kernel_shape[spatialRank - 2] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank >= 2 else 1",
55
  "kernelW": "attrs.kernel_shape[spatialRank - 1] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank >= 1 else 1",
 
65
  "padBack": "attrs.pads[spatialRank] if padsLengthOk and has(attrs, \"pads\") and spatialRank == 3 else 0",
66
  "padBottom": "attrs.pads[2 * spatialRank - 2] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 2 else 0",
67
  "padRight": "attrs.pads[2 * spatialRank - 1] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 1 else 0",
68
+ "outputPaddingLengthOk": "not has(attrs, \"output_padding\") or (attrs.output_padding | length) == spatialRank",
69
  "outputPaddingD": "attrs.output_padding[0] if outputPaddingLengthOk and has(attrs, \"output_padding\") and spatialRank == 3 else 0",
70
  "outputPaddingH": "attrs.output_padding[spatialRank - 2] if outputPaddingLengthOk and has(attrs, \"output_padding\") and spatialRank >= 2 else 0",
71
  "outputPaddingW": "attrs.output_padding[spatialRank - 1] if outputPaddingLengthOk and has(attrs, \"output_padding\") and spatialRank >= 1 else 0",
 
104
  "rank5DenseContract": "spatialAttributesOk and ranks.x == 5 and ranks.w == 5 and ranks.y == 5 and attrs.group == 1 and batchChannelContract and outputDispatchFits and dim(shapes.y, 1) == dim(shapes.w, 1)",
105
  "noBiasContract": "not present.bias",
106
  "biasContract": "present.bias and ranks.bias == 1 and dim(shapes.bias, 0) == dim(shapes.y, 1)",
107
+ "hasBias": "present.bias",
108
  "f32Type": "dtypes.T == \"f32\"",
109
  "f16Type": "device.features.has(\"shader-f16\") and dtypes.T == \"f16\"",
110
  "gemmM": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
 
151
  "groupedBlockOc": "16 if groupedOutCPerGroup % 16 == 0 else (8 if groupedOutCPerGroup % 8 == 0 else (4 if groupedOutCPerGroup % 4 == 0 else (2 if groupedOutCPerGroup % 2 == 0 else 1)))",
152
  "groupedWeightBytes": "(dim(shapes.x, 1) / attrs.group) * groupedOutCPerGroup * dim(shapes.w, 2) * dim(shapes.w, 3) * 4",
153
  "groupedPlanes": "dim(shapes.x, 0) * attrs.group * (groupedOutCPerGroup / groupedBlockOc)",
154
+ "groupedStridePhaseBlockedFits": "groupedWeightBytes <= device.limits.maxComputeWorkgroupStorageSize and groupedPlanes <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(dim(shapes.y, 2) * dim(shapes.y, 3), tunables.WORKGROUP_SIZE) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
155
+ "count": "numel(shapes.y)",
156
+ "exactTiling": "exactTiling2d",
157
+ "inC": "dim(shapes.x, 1)",
158
+ "inCPerGroup": "dim(shapes.x, 1) / attrs.group",
159
+ "inChannels": "dim(shapes.x, 1)",
160
+ "inH": "dim(shapes.x, 2)",
161
+ "inW": "dim(shapes.x, 3)",
162
+ "outC": "dim(shapes.y, 1)",
163
+ "outChannels": "dim(shapes.y, 1)",
164
+ "outH": "dim(shapes.y, 2)",
165
+ "outW": "dim(shapes.y, 3)",
166
+ "padH": "effectivePadTop",
167
+ "padW": "effectivePadLeft",
168
+ "scalar": "dtypes.T"
169
  },
170
  "bindings": {
171
+ "y": { "elementType": "$T" },
172
+ "bias": { "elementType": "$T" },
173
+ "x": { "elementType": "$T" },
174
+ "w": { "elementType": "$phaseWeightElement" },
175
+ "y_main": { "name": "y", "elementType": "$phaseOutputElement" },
176
+ "wsrc": { "arg": "w", "elementType": "$T" },
177
  "wt": { "buffer": "storage", "elementType": "$T" },
178
  "params": {
 
179
  "struct": [
180
  { "name": "count", "type": "u32", "value": "numel(shapes.w)" },
181
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.w, 0)" },
 
185
  { "name": "kernelW", "type": "u32", "value": "dim(shapes.w, 4)" }
186
  ]
187
  },
188
+ "w_t": { "scratch": "wt", "name": "w", "buffer": "read-only-storage", "elementType": "$T" },
189
+ "xm": { "arg": "x", "elementType": "$T" },
190
+ "y_t": { "scratch": "cols", "name": "y", "elementType": "$T" },
191
  "cols": { "buffer": "read-only-storage", "elementType": "$T" },
192
+ "params_col2im": {
193
  "name": "params",
 
194
  "struct": [
195
  { "name": "outChannels", "type": "u32", "value": "dim(shapes.w, 1)" },
196
  { "name": "kernelD", "type": "u32", "value": "dim(shapes.w, 2)" },
 
214
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
215
  ]
216
  },
217
+ "params_reorder": {
218
  "name": "params",
 
219
  "struct": [
220
  { "name": "count", "type": "u32", "value": "numel(shapes.w)" },
221
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.w, 0)" },
 
224
  { "name": "kernelW", "type": "u32", "value": "dim(shapes.w, 3)" }
225
  ]
226
  },
227
+ "params__uniform": {
228
  "name": "params",
 
229
  "struct": [
230
  { "name": "outChannels", "type": "u32", "value": "dim(shapes.w, 1)" },
231
  { "name": "kernelH", "type": "u32", "value": "dim(shapes.w, 2)" },
 
243
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
244
  ]
245
  },
246
+ "params_gemm": {
247
  "name": "params",
 
248
  "struct": [
249
  { "name": "M", "type": "u32", "value": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)" },
250
  { "name": "K", "type": "u32", "value": "dim(shapes.x, 1)" },
251
  { "name": "N", "type": "u32", "value": "dim(shapes.x, 2) * dim(shapes.x, 3)" }
252
  ]
253
  },
254
+ "w_main": { "name": "w", "elementType": "$T" },
255
+ "params_main": {
256
  "name": "params",
 
257
  "struct": [
258
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
259
  { "name": "inW", "type": "u32", "value": "dim(shapes.x, 2)" },
 
266
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
267
  ]
268
  },
269
+ "params_ncl1d_grouped": {
270
  "name": "params",
 
271
  "struct": [
272
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
273
  { "name": "inChannelsPerGroup", "type": "u32", "value": "dim(shapes.x, 1) / attrs.group" },
 
282
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
283
  ]
284
  },
285
+ "params_nchw2d": {
286
  "name": "params",
 
287
  "struct": [
288
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
289
  { "name": "inH", "type": "u32", "value": "dim(shapes.x, 2)" },
 
302
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
303
  ]
304
  },
305
+ "params_ncdhw3d": {
306
  "name": "params",
 
307
  "struct": [
308
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
309
  { "name": "inD", "type": "u32", "value": "dim(shapes.x, 2)" },
 
328
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
329
  ]
330
  },
331
+ "params_nchw2d_grouped": {
332
  "name": "params",
 
333
  "struct": [
334
  { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
335
  { "name": "inChannelsPerGroup", "type": "u32", "value": "dim(shapes.x, 1) / attrs.group" },
 
350
  { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
351
  ]
352
  },
353
+ "params_conv_transpose2d_nchw": {
354
  "name": "params",
 
355
  "struct": [
356
  { "name": "outChannels", "type": "u32", "value": "dim(shapes.y, 1)" },
357
  { "name": "outH", "type": "u32", "value": "dim(shapes.y, 2)" },
 
365
  "id": "empty_input_reduction",
366
  "priority": 100,
367
  "when": ["noBiasContract", "emptyInputReductionContract"],
 
368
  "passes": [
369
  {
370
  "id": "main",
371
  "name": "ConvTranspose.EmptyInput",
372
  "shader": "conv-transpose-empty-input.wgsl.jinja",
373
  "derive": {
 
 
374
  "outputSpatial": "dim(shapes.y, 2) if ranks.y == 3 else (dim(shapes.y, 2) * dim(shapes.y, 3) if ranks.y == 4 else dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.y, 4))"
375
  },
376
  "bindings": ["y"],
 
386
  "id": "empty_input_reduction_bias",
387
  "priority": 100,
388
  "when": ["biasContract", "emptyInputReductionContract"],
 
389
  "passes": [
390
  {
391
  "id": "main",
392
  "name": "ConvTranspose.EmptyInputBias",
393
  "shader": "conv-transpose-empty-input.wgsl.jinja",
394
  "derive": {
 
 
395
  "outputSpatial": "dim(shapes.y, 2) if ranks.y == 3 else (dim(shapes.y, 2) * dim(shapes.y, 3) if ranks.y == 4 else dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.y, 4))"
396
  },
397
  "bindings": ["bias", "y"],
 
408
  "priority": 40,
409
  "when": ["noBiasContract", "phase1dCovered"],
410
  "derive": {
 
411
  "phaseWeightElement": "\"vec4<\" ~ dtypes.T ~ \">\"",
412
  "phaseOutputElement": "\"vec2<\" ~ dtypes.T ~ \">\""
413
  },
 
417
  "name": "ConvTranspose1d.PhaseTiled",
418
  "shader": "conv-transpose1d-phase-tiled.wgsl.jinja",
419
  "derive": {
 
420
  "inWidth": "dim(shapes.x, 2)",
 
421
  "positionTile": "tunables.PHASE1D_POSITION_TILE",
422
  "outputTile": "phase1dOutputTile",
423
  "outputsPerThread": "phase1dOutputsPerThread",
 
426
  "xTileElements": "tunables.PHASE1D_K_TILE * (tunables.PHASE1D_POSITION_TILE + 2)",
427
  "weightTileElements": "tunables.PHASE1D_K_TILE * phase1dOutputTile"
428
  },
429
+ "bindings": ["x", "w", "y_main"],
430
  "dispatch": {
431
  "x": "ceilDiv(dim(shapes.x, 2), tunables.PHASE1D_POSITION_TILE)",
432
  "y": "ceilDiv(dim(shapes.y, 1), phase1dOutputTile)",
 
440
  "priority": 45,
441
  "when": ["biasContract", "phase1dCovered"],
442
  "derive": {
 
443
  "phaseWeightElement": "\"vec4<\" ~ dtypes.T ~ \">\"",
444
  "phaseOutputElement": "\"vec2<\" ~ dtypes.T ~ \">\""
445
  },
 
449
  "name": "ConvTranspose1d.PhaseTiledBias",
450
  "shader": "conv-transpose1d-phase-tiled.wgsl.jinja",
451
  "derive": {
 
452
  "inWidth": "dim(shapes.x, 2)",
 
453
  "positionTile": "tunables.PHASE1D_POSITION_TILE",
454
  "outputTile": "phase1dOutputTile",
455
  "outputsPerThread": "phase1dOutputsPerThread",
 
458
  "xTileElements": "tunables.PHASE1D_K_TILE * (tunables.PHASE1D_POSITION_TILE + 2)",
459
  "weightTileElements": "tunables.PHASE1D_K_TILE * phase1dOutputTile"
460
  },
461
+ "bindings": ["x", "w", "bias", "y_main"],
462
  "dispatch": {
463
  "x": "ceilDiv(dim(shapes.x, 2), tunables.PHASE1D_POSITION_TILE)",
464
  "y": "ceilDiv(dim(shapes.y, 1), phase1dOutputTile)",
 
476
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
477
  },
478
  "derive": {
 
479
  "fScalar": "\"f32\"",
 
480
  "directMatrixStore": "ctImplicitDirectStore",
481
  "tileRows": "tunables.SGMAT_TILE_ROWS",
482
  "subgroupRows": "tunables.SGMAT_SUBGROUP_ROWS",
 
526
  "id": "gemm",
527
  "name": "ConvTranspose3dImplicitSubgroupMatrix",
528
  "shader": "conv-1x1-subgroup-matrix.wgsl.jinja",
529
+ "bindings": ["w_t", "xm", "y"],
530
  "dispatch": {
531
  "x": "ceilDiv(ctImplicitOutVolume, tunables.SGMAT_TILE_COLS)",
532
  "y": "ctImplicitYWorkgroups",
 
544
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
545
  },
546
  "derive": {
 
547
  "fScalar": "\"f32\"",
 
548
  "directMatrixStore": "ctImplicitDirectStore",
549
  "tileRows": "tunables.SGMAT_TILE_ROWS",
550
  "subgroupRows": "tunables.SGMAT_SUBGROUP_ROWS",
 
594
  "id": "gemm",
595
  "name": "ConvTranspose3dImplicitSubgroupMatrixBias",
596
  "shader": "conv-1x1-subgroup-matrix.wgsl.jinja",
597
+ "bindings": ["w_t", "xm", "bias", "y"],
598
  "dispatch": {
599
  "x": "ceilDiv(ctImplicitOutVolume, tunables.SGMAT_TILE_COLS)",
600
  "y": "ctImplicitYWorkgroups",
 
612
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
613
  },
614
  "derive": {
 
615
  "fScalar": "\"f32\"",
 
616
  "directMatrixStore": "gemm3dM % tunables.SGMAT_TILE_ROWS == 0",
617
  "tileRows": "tunables.SGMAT_TILE_ROWS",
618
  "tileCols": "tunables.SGMAT_TILE_COLS",
 
644
  "name": "ConvTranspose3dColsGemm",
645
  "shader": "conv-1x1-subgroup-matrix.wgsl.jinja",
646
  "derive": { "hasBias": false },
647
+ "bindings": ["w_t", "xm", "y_t"],
648
  "dispatch": {
649
  "x": "ceilDiv(gemm3dN, tunables.SGMAT_TILE_COLS)",
650
  "y": "ceilDiv(gemm3dM, tunables.SGMAT_TILE_ROWS)",
 
655
  "id": "col2im",
656
  "name": "ConvTranspose3dCol2Im",
657
  "shader": "conv-transpose3d-col2im.wgsl.jinja",
658
+ "bindings": ["cols", "y", "params_col2im"],
659
  "dispatch": {
660
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
661
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
673
  "subgroupMatrixConfigs": [{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }]
674
  },
675
  "derive": {
 
676
  "fScalar": "\"f32\"",
 
677
  "directMatrixStore": "gemm3dM % tunables.SGMAT_TILE_ROWS == 0",
678
  "tileRows": "tunables.SGMAT_TILE_ROWS",
679
  "tileCols": "tunables.SGMAT_TILE_COLS",
 
705
  "name": "ConvTranspose3dColsGemm",
706
  "shader": "conv-1x1-subgroup-matrix.wgsl.jinja",
707
  "derive": { "hasBias": false },
708
+ "bindings": ["w_t", "xm", "y_t"],
709
  "dispatch": {
710
  "x": "ceilDiv(gemm3dN, tunables.SGMAT_TILE_COLS)",
711
  "y": "ceilDiv(gemm3dM, tunables.SGMAT_TILE_ROWS)",
 
716
  "id": "col2im",
717
  "name": "ConvTranspose3dCol2ImBias",
718
  "shader": "conv-transpose3d-col2im.wgsl.jinja",
719
+ "bindings": ["cols", "bias", "y", "params_col2im"],
720
  "dispatch": {
721
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
722
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
729
  "id": "ncdhw3d_gemm_tiled_f32",
730
  "priority": 30,
731
  "when": ["noBiasContract", "f32Type", "rank5DenseContract", "rank5OutputShapeOk", "gemm3dN > 0", "gemm3dDispatchFits", "f32Gemm3dScratchFits"],
732
+ "derive": { "usesF16": false },
733
  "intermediates": [
734
  { "id": "wt", "dtype": "float32", "shape": "[gemmWeightElements]" },
735
  { "id": "cols", "dtype": "float32", "shape": "[gemm3dColumnElements]" }
 
752
  "shader": "conv-1x1-gemm-tiled.wgsl.jinja",
753
  "derive": { "hasBias": false },
754
  "bindings": [
755
+ "w_t",
756
  "xm",
757
+ "y_t",
758
  {
759
  "name": "params",
760
  "struct": [
 
770
  "id": "col2im",
771
  "name": "ConvTranspose3dCol2Im",
772
  "shader": "conv-transpose3d-col2im.wgsl.jinja",
773
+ "bindings": ["cols", "y", "params_col2im"],
774
  "dispatch": {
775
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
776
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
783
  "id": "ncdhw3d_gemm_tiled_f32_reg",
784
  "priority": 32,
785
  "when": ["noBiasContract", "f32Type", "rank5DenseContract", "rank5OutputShapeOk", "gemm3dN > 0", "gemm3dDispatchFits", "f32Gemm3dScratchFits", "ceilDiv(gemm3dM, 64) * ceilDiv(gemm3dN, 64) >= tunables.GEMM_REG_MIN_TILE_PRODUCT"],
786
+ "derive": { "usesF16": false, "gemmKTile": "32 if dim(shapes.x, 1) % 32 == 0 else 16" },
 
 
 
 
 
 
 
787
  "intermediates": [
788
  { "id": "wt", "dtype": "float32", "shape": "[gemmWeightElements]" },
789
  { "id": "cols", "dtype": "float32", "shape": "[gemm3dColumnElements]" }
 
806
  "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
807
  "derive": { "hasBias": false },
808
  "bindings": [
809
+ "w_t",
810
  "xm",
811
+ "y_t",
812
  {
813
  "name": "params",
814
  "struct": [
 
824
  "id": "col2im",
825
  "name": "ConvTranspose3dCol2Im",
826
  "shader": "conv-transpose3d-col2im.wgsl.jinja",
827
+ "bindings": ["cols", "y", "params_col2im"],
828
  "dispatch": {
829
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
830
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
837
  "id": "ncdhw3d_gemm_tiled_f32_bias",
838
  "priority": 35,
839
  "when": ["biasContract", "f32Type", "rank5DenseContract", "rank5OutputShapeOk", "gemm3dN > 0", "gemm3dDispatchFits", "f32Gemm3dScratchFits"],
840
+ "derive": { "usesF16": false },
841
  "intermediates": [
842
  { "id": "wt", "dtype": "float32", "shape": "[gemmWeightElements]" },
843
  { "id": "cols", "dtype": "float32", "shape": "[gemm3dColumnElements]" }
 
860
  "shader": "conv-1x1-gemm-tiled.wgsl.jinja",
861
  "derive": { "hasBias": false },
862
  "bindings": [
863
+ "w_t",
864
  "xm",
865
+ "y_t",
866
  {
867
  "name": "params",
868
  "struct": [
 
878
  "id": "col2im",
879
  "name": "ConvTranspose3dCol2ImBias",
880
  "shader": "conv-transpose3d-col2im.wgsl.jinja",
881
+ "bindings": ["cols", "bias", "y", "params_col2im"],
882
  "dispatch": {
883
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
884
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
891
  "id": "ncdhw3d_gemm_tiled_f32_bias_reg",
892
  "priority": 37,
893
  "when": ["biasContract", "f32Type", "rank5DenseContract", "rank5OutputShapeOk", "gemm3dN > 0", "gemm3dDispatchFits", "f32Gemm3dScratchFits", "ceilDiv(gemm3dM, 64) * ceilDiv(gemm3dN, 64) >= tunables.GEMM_REG_MIN_TILE_PRODUCT"],
894
+ "derive": { "usesF16": false, "gemmKTile": "32 if dim(shapes.x, 1) % 32 == 0 else 16" },
 
 
 
 
 
 
 
895
  "intermediates": [
896
  { "id": "wt", "dtype": "float32", "shape": "[gemmWeightElements]" },
897
  { "id": "cols", "dtype": "float32", "shape": "[gemm3dColumnElements]" }
 
914
  "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
915
  "derive": { "hasBias": false },
916
  "bindings": [
917
+ "w_t",
918
  "xm",
919
+ "y_t",
920
  {
921
  "name": "params",
922
  "struct": [
 
932
  "id": "col2im",
933
  "name": "ConvTranspose3dCol2ImBias",
934
  "shader": "conv-transpose3d-col2im.wgsl.jinja",
935
+ "bindings": ["cols", "bias", "y", "params_col2im"],
936
  "dispatch": {
937
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
938
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
950
  "subgroupMatrixConfigs": [{ "componentType": "f16", "M": 8, "N": 8, "K": 8 }]
951
  },
952
  "derive": {
 
953
  "fScalar": "\"f16\"",
 
954
  "M": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
955
  "K": "dim(shapes.x, 1)",
956
+ "N": "dim(shapes.x, 2) * dim(shapes.x, 3)"
 
957
  },
958
  "intermediates": [
959
  {
 
972
  "id": "reorder",
973
  "name": "ConvTransposeWeightReorder",
974
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
975
+ "bindings": ["wsrc", "wt", "params_reorder"],
976
  "dispatch": {
977
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
978
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
 
999
  "id": "col2im",
1000
  "name": "ConvTransposeCol2Im",
1001
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1002
+ "bindings": ["cols", "y", "params__uniform"],
1003
  "dispatch": {
1004
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1005
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1017
  "subgroupMatrixConfigs": [{ "componentType": "f16", "M": 8, "N": 8, "K": 8 }]
1018
  },
1019
  "derive": {
 
1020
  "fScalar": "\"f16\"",
 
1021
  "M": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
1022
  "K": "dim(shapes.x, 1)",
1023
+ "N": "dim(shapes.x, 2) * dim(shapes.x, 3)"
 
1024
  },
1025
  "intermediates": [
1026
  {
 
1039
  "id": "reorder",
1040
  "name": "ConvTransposeWeightReorder",
1041
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1042
+ "bindings": ["wsrc", "wt", "params_reorder"],
1043
  "dispatch": {
1044
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1045
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1066
  "id": "col2im",
1067
  "name": "ConvTransposeCol2ImBias",
1068
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1069
+ "bindings": ["cols", "bias", "y", "params__uniform"],
1070
  "dispatch": {
1071
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1072
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1079
  "id": "nchw2d_gemm_tiled",
1080
  "priority": 30,
1081
  "when": ["noBiasContract", "f16Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f16GemmScratchFits", "rank4OutputShapeOk"],
1082
+ "derive": { "usesF16": true },
 
 
 
 
 
 
 
1083
  "intermediates": [
1084
  {
1085
  "id": "wt",
 
1097
  "id": "reorder",
1098
  "name": "ConvTransposeWeightReorder",
1099
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1100
+ "bindings": ["wsrc", "wt", "params_reorder"],
1101
  "dispatch": {
1102
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1103
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1109
  "name": "ConvTransposeColsGemmTiledReg",
1110
  "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
1111
  "derive": { "hasBias": false },
1112
+ "bindings": ["w_t", "xm", "y_t", "params_gemm"],
1113
  "dispatch": {
1114
  "x": "ceil(dim(shapes.x, 2) * dim(shapes.x, 3) / 64)",
1115
  "y": "ceil(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) / 64)",
 
1120
  "id": "col2im",
1121
  "name": "ConvTransposeCol2Im",
1122
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1123
+ "bindings": ["cols", "y", "params__uniform"],
1124
  "dispatch": {
1125
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1126
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1133
  "id": "nchw2d_gemm_tiled_bias",
1134
  "priority": 35,
1135
  "when": ["biasContract", "f16Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f16GemmScratchFits", "rank4OutputShapeOk"],
1136
+ "derive": { "usesF16": true },
 
 
 
 
 
 
 
1137
  "intermediates": [
1138
  {
1139
  "id": "wt",
 
1151
  "id": "reorder",
1152
  "name": "ConvTransposeWeightReorder",
1153
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1154
+ "bindings": ["wsrc", "wt", "params_reorder"],
1155
  "dispatch": {
1156
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1157
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1163
  "name": "ConvTransposeColsGemmTiledReg",
1164
  "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
1165
  "derive": { "hasBias": false },
1166
+ "bindings": ["w_t", "xm", "y_t", "params_gemm"],
1167
  "dispatch": {
1168
  "x": "ceil(dim(shapes.x, 2) * dim(shapes.x, 3) / 64)",
1169
  "y": "ceil(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) / 64)",
 
1174
  "id": "col2im",
1175
  "name": "ConvTransposeCol2ImBias",
1176
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1177
+ "bindings": ["cols", "bias", "y", "params__uniform"],
1178
  "dispatch": {
1179
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1180
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1187
  "id": "nchw2d_gemm_tiled_f32",
1188
  "priority": 30,
1189
  "when": ["noBiasContract", "f32Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f32GemmScratchFits", "rank4OutputShapeOk"],
1190
+ "derive": { "usesF16": false },
 
 
 
 
 
 
 
1191
  "intermediates": [
1192
  {
1193
  "id": "wt",
 
1205
  "id": "reorder",
1206
  "name": "ConvTransposeWeightReorder",
1207
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1208
+ "bindings": ["wsrc", "wt", "params_reorder"],
1209
  "dispatch": {
1210
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1211
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1217
  "name": "ConvTransposeColsGemmTiled",
1218
  "shader": "conv-1x1-gemm-tiled.wgsl.jinja",
1219
  "derive": { "hasBias": false },
1220
+ "bindings": ["w_t", "xm", "y_t", "params_gemm"],
1221
  "dispatch": {
1222
  "x": "ceil(dim(shapes.x, 2) * dim(shapes.x, 3) / 32)",
1223
  "y": "ceil(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) / 32)",
 
1228
  "id": "col2im",
1229
  "name": "ConvTransposeCol2Im",
1230
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1231
+ "bindings": ["cols", "y", "params__uniform"],
1232
  "dispatch": {
1233
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1234
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1241
  "id": "nchw2d_gemm_tiled_f32_reg",
1242
  "priority": 32,
1243
  "when": ["noBiasContract", "f32Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f32GemmScratchFits", "rank4OutputShapeOk", "ceilDiv(gemmM, 64) * ceilDiv(gemmN, 64) >= tunables.GEMM_REG_MIN_TILE_PRODUCT"],
1244
+ "derive": { "usesF16": false },
 
 
 
 
 
 
 
1245
  "intermediates": [
1246
  {
1247
  "id": "wt",
 
1259
  "id": "reorder",
1260
  "name": "ConvTransposeWeightReorder",
1261
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1262
+ "bindings": ["wsrc", "wt", "params_reorder"],
1263
  "dispatch": {
1264
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1265
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1271
  "name": "ConvTransposeColsGemmRegTiled",
1272
  "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
1273
  "derive": { "hasBias": false },
1274
+ "bindings": ["w_t", "xm", "y_t", "params_gemm"],
1275
  "dispatch": {
1276
  "x": "ceil(dim(shapes.x, 2) * dim(shapes.x, 3) / 64)",
1277
  "y": "ceil(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) / 64)",
 
1282
  "id": "col2im",
1283
  "name": "ConvTransposeCol2Im",
1284
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1285
+ "bindings": ["cols", "y", "params__uniform"],
1286
  "dispatch": {
1287
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1288
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1295
  "id": "nchw2d_gemm_tiled_f32_bias",
1296
  "priority": 35,
1297
  "when": ["biasContract", "f32Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f32GemmScratchFits", "rank4OutputShapeOk"],
1298
+ "derive": { "usesF16": false },
 
 
 
 
 
 
 
1299
  "intermediates": [
1300
  {
1301
  "id": "wt",
 
1313
  "id": "reorder",
1314
  "name": "ConvTransposeWeightReorder",
1315
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1316
+ "bindings": ["wsrc", "wt", "params_reorder"],
1317
  "dispatch": {
1318
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1319
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1325
  "name": "ConvTransposeColsGemmTiled",
1326
  "shader": "conv-1x1-gemm-tiled.wgsl.jinja",
1327
  "derive": { "hasBias": false },
1328
+ "bindings": ["w_t", "xm", "y_t", "params_gemm"],
1329
  "dispatch": {
1330
  "x": "ceil(dim(shapes.x, 2) * dim(shapes.x, 3) / 32)",
1331
  "y": "ceil(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) / 32)",
 
1336
  "id": "col2im",
1337
  "name": "ConvTransposeCol2ImBias",
1338
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1339
+ "bindings": ["cols", "bias", "y", "params__uniform"],
1340
  "dispatch": {
1341
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1342
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1349
  "id": "nchw2d_gemm_tiled_f32_bias_reg",
1350
  "priority": 37,
1351
  "when": ["biasContract", "f32Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f32GemmScratchFits", "rank4OutputShapeOk", "ceilDiv(gemmM, 64) * ceilDiv(gemmN, 64) >= tunables.GEMM_REG_MIN_TILE_PRODUCT"],
1352
+ "derive": { "usesF16": false },
 
 
 
 
 
 
 
1353
  "intermediates": [
1354
  {
1355
  "id": "wt",
 
1367
  "id": "reorder",
1368
  "name": "ConvTransposeWeightReorder",
1369
  "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1370
+ "bindings": ["wsrc", "wt", "params_reorder"],
1371
  "dispatch": {
1372
  "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1373
  "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1379
  "name": "ConvTransposeColsGemmRegTiled",
1380
  "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
1381
  "derive": { "hasBias": false },
1382
+ "bindings": ["w_t", "xm", "y_t", "params_gemm"],
1383
  "dispatch": {
1384
  "x": "ceil(dim(shapes.x, 2) * dim(shapes.x, 3) / 64)",
1385
  "y": "ceil(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) / 64)",
 
1390
  "id": "col2im",
1391
  "name": "ConvTransposeCol2ImBias",
1392
  "shader": "conv-transpose2d-col2im.wgsl.jinja",
1393
+ "bindings": ["cols", "bias", "y", "params__uniform"],
1394
  "dispatch": {
1395
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1396
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1411
  "shader": "conv-transpose2d-phase-gemm-tiled.wgsl.jinja",
1412
  "derive": {
1413
  "batch": "dim(shapes.x, 0)",
 
1414
  "inHeight": "dim(shapes.x, 2)",
1415
  "inWidth": "dim(shapes.x, 3)",
 
1416
  "outHeight": "dim(shapes.y, 2)",
1417
  "outWidth": "dim(shapes.y, 3)",
 
 
1418
  "mTile": "tunables.PHASE2D_GEMM_TILE",
1419
  "nTile": "tunables.PHASE2D_GEMM_TILE",
1420
  "kTile": "tunables.PHASE2D_GEMM_K_TILE",
1421
  "threadRows": "tunables.PHASE2D_GEMM_THREAD_TILE",
1422
  "threadColumns": "tunables.PHASE2D_GEMM_THREAD_TILE",
1423
+ "workgroupEdge": "tunables.PHASE2D_GEMM_WORKGROUP_EDGE"
 
1424
  },
1425
+ "bindings": ["x", "w", "y_main"],
1426
  "dispatch": {
1427
  "x": "ceilDiv(dim(shapes.x, 2) * dim(shapes.x, 3), tunables.PHASE2D_GEMM_TILE)",
1428
  "y": "ceilDiv(dim(shapes.y, 1), tunables.PHASE2D_GEMM_TILE)",
 
1443
  "shader": "conv-transpose2d-phase-gemm-tiled.wgsl.jinja",
1444
  "derive": {
1445
  "batch": "dim(shapes.x, 0)",
 
1446
  "inHeight": "dim(shapes.x, 2)",
1447
  "inWidth": "dim(shapes.x, 3)",
 
1448
  "outHeight": "dim(shapes.y, 2)",
1449
  "outWidth": "dim(shapes.y, 3)",
 
 
1450
  "mTile": "tunables.PHASE2D_GEMM_TILE",
1451
  "nTile": "tunables.PHASE2D_GEMM_TILE",
1452
  "kTile": "tunables.PHASE2D_GEMM_K_TILE",
1453
  "threadRows": "tunables.PHASE2D_GEMM_THREAD_TILE",
1454
  "threadColumns": "tunables.PHASE2D_GEMM_THREAD_TILE",
1455
+ "workgroupEdge": "tunables.PHASE2D_GEMM_WORKGROUP_EDGE"
 
1456
  },
1457
+ "bindings": ["x", "w", "bias", "y_main"],
1458
  "dispatch": {
1459
  "x": "ceilDiv(dim(shapes.x, 2) * dim(shapes.x, 3), tunables.PHASE2D_GEMM_TILE)",
1460
  "y": "ceilDiv(dim(shapes.y, 1), tunables.PHASE2D_GEMM_TILE)",
 
1467
  "id": "nchw2d_grouped_stride_phase_blocked",
1468
  "priority": 25,
1469
  "when": ["noBiasContract", "rank4GroupedContract", "f16Ok(dtypes.T)", "strideH >= 2", "strideW >= 2", "dilationH == 1", "dilationW == 1", "dim(shapes.w, 2) >= 2", "dim(shapes.w, 2) <= 7", "dim(shapes.w, 3) >= 2", "dim(shapes.w, 3) <= 7", "dim(shapes.y, 2) == (dim(shapes.x, 2) - 1) * strideH - (padTop + padBottom) + dim(shapes.w, 2) + outputPaddingH", "dim(shapes.y, 3) == (dim(shapes.x, 3) - 1) * strideW - (padLeft + padRight) + dim(shapes.w, 3) + outputPaddingW", "groupedStridePhaseBlockedFits"],
1470
+ "derive": { "phaseWeightElement": "dtypes.T", "phaseOutputElement": "dtypes.T" },
 
 
 
 
 
1471
  "passes": [
1472
  {
1473
  "id": "main",
1474
  "name": "ConvTranspose.GroupedStridePhaseBlocked",
1475
  "shader": "conv-transpose2d-grouped-stride-phase-blocked.wgsl.jinja",
1476
  "derive": {
 
1477
  "outCPerGroup": "groupedOutCPerGroup",
 
 
 
 
 
 
1478
  "kernelHSpec": "dim(shapes.w, 2)",
1479
  "kernelWSpec": "dim(shapes.w, 3)",
1480
  "strideHSpec": "strideH",
1481
  "strideWSpec": "strideW",
 
 
1482
  "groups": "attrs.group",
1483
+ "ocBlock": "groupedBlockOc"
 
1484
  },
1485
+ "bindings": ["x", "w", "y_main"],
1486
  "dispatch": {
1487
  "x": "ceilDiv(dim(shapes.y, 2) * dim(shapes.y, 3), tunables.WORKGROUP_SIZE)",
1488
  "y": "groupedPlanes"
 
1491
  ]
1492
  },
1493
  {
1494
+ "id": "nchw2d_grouped_stride_phase",
1495
+ "priority": 20,
1496
+ "when": ["noBiasContract", "rank4GroupedContract", "f16Ok(dtypes.T)", "strideH >= 2", "strideW >= 2", "dilationH == 1", "dilationW == 1", "dim(shapes.w, 2) >= 2", "dim(shapes.w, 2) <= 7", "dim(shapes.w, 3) >= 2", "dim(shapes.w, 3) <= 7", "dim(shapes.y, 2) == (dim(shapes.x, 2) - 1) * strideH - (padTop + padBottom) + dim(shapes.w, 2) + outputPaddingH", "dim(shapes.y, 3) == (dim(shapes.x, 3) - 1) * strideW - (padLeft + padRight) + dim(shapes.w, 3) + outputPaddingW"],
1497
+ "derive": { "phaseWeightElement": "dtypes.T", "phaseOutputElement": "dtypes.T" },
 
 
 
 
 
1498
  "passes": [
1499
  {
1500
  "id": "main",
1501
+ "name": "ConvTranspose.GroupedStridePhase",
1502
+ "shader": "conv-transpose2d-grouped-stride-phase.wgsl.jinja",
1503
  "derive": {
1504
+ "outCPerGroup": "dim(shapes.y, 1) / attrs.group",
 
 
 
 
 
 
 
1505
  "kernelHSpec": "dim(shapes.w, 2)",
1506
  "kernelWSpec": "dim(shapes.w, 3)",
1507
  "strideHSpec": "strideH",
1508
+ "strideWSpec": "strideW"
 
 
 
 
 
1509
  },
1510
+ "bindings": ["x", "w", "y_main"],
1511
  "dispatch": {
1512
+ "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1513
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1514
+ "z": 1
1515
  }
1516
  }
1517
  ]
1518
  },
1519
  {
1520
+ "id": "nchw2d_grouped_stride_phase_blocked_bias",
1521
+ "priority": 25,
1522
+ "when": ["biasContract", "rank4GroupedContract", "f16Ok(dtypes.T)", "strideH >= 2", "strideW >= 2", "dilationH == 1", "dilationW == 1", "dim(shapes.w, 2) >= 2", "dim(shapes.w, 2) <= 7", "dim(shapes.w, 3) >= 2", "dim(shapes.w, 3) <= 7", "dim(shapes.y, 2) == (dim(shapes.x, 2) - 1) * strideH - (padTop + padBottom) + dim(shapes.w, 2) + outputPaddingH", "dim(shapes.y, 3) == (dim(shapes.x, 3) - 1) * strideW - (padLeft + padRight) + dim(shapes.w, 3) + outputPaddingW", "groupedStridePhaseBlockedFits"],
1523
+ "derive": { "phaseWeightElement": "dtypes.T", "phaseOutputElement": "dtypes.T" },
 
 
 
 
 
1524
  "passes": [
1525
  {
1526
  "id": "main",
1527
+ "name": "ConvTranspose.GroupedStridePhaseBlockedBias",
1528
+ "shader": "conv-transpose2d-grouped-stride-phase-blocked.wgsl.jinja",
1529
  "derive": {
1530
+ "outCPerGroup": "groupedOutCPerGroup",
 
 
 
 
 
 
 
 
1531
  "kernelHSpec": "dim(shapes.w, 2)",
1532
  "kernelWSpec": "dim(shapes.w, 3)",
1533
  "strideHSpec": "strideH",
1534
  "strideWSpec": "strideW",
1535
+ "groups": "attrs.group",
1536
+ "ocBlock": "groupedBlockOc"
 
1537
  },
1538
+ "bindings": ["x", "w", "bias", "y_main"],
1539
  "dispatch": {
1540
+ "x": "ceilDiv(dim(shapes.y, 2) * dim(shapes.y, 3), tunables.WORKGROUP_SIZE)",
1541
+ "y": "groupedPlanes"
 
1542
  }
1543
  }
1544
  ]
 
1547
  "id": "nchw2d_grouped_stride_phase_bias",
1548
  "priority": 20,
1549
  "when": ["biasContract", "rank4GroupedContract", "f16Ok(dtypes.T)", "strideH >= 2", "strideW >= 2", "dilationH == 1", "dilationW == 1", "dim(shapes.w, 2) >= 2", "dim(shapes.w, 2) <= 7", "dim(shapes.w, 3) >= 2", "dim(shapes.w, 3) <= 7", "dim(shapes.y, 2) == (dim(shapes.x, 2) - 1) * strideH - (padTop + padBottom) + dim(shapes.w, 2) + outputPaddingH", "dim(shapes.y, 3) == (dim(shapes.x, 3) - 1) * strideW - (padLeft + padRight) + dim(shapes.w, 3) + outputPaddingW"],
1550
+ "derive": { "phaseWeightElement": "dtypes.T", "phaseOutputElement": "dtypes.T" },
 
 
 
 
 
1551
  "passes": [
1552
  {
1553
  "id": "main",
1554
  "name": "ConvTranspose.GroupedStridePhaseBias",
1555
  "shader": "conv-transpose2d-grouped-stride-phase.wgsl.jinja",
1556
  "derive": {
 
 
1557
  "outCPerGroup": "dim(shapes.y, 1) / attrs.group",
 
 
 
 
 
 
1558
  "kernelHSpec": "dim(shapes.w, 2)",
1559
  "kernelWSpec": "dim(shapes.w, 3)",
1560
  "strideHSpec": "strideH",
1561
+ "strideWSpec": "strideW"
 
 
 
1562
  },
1563
+ "bindings": ["x", "w", "bias", "y_main"],
1564
  "dispatch": {
1565
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1566
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1572
  {
1573
  "id": "ncl1d",
1574
  "priority": 0,
1575
+ "when": ["noBiasContract", "f32Type or f16Type", "rank3DenseContract", "rank3OutputShapeOk or (f32Type and ((attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") and dim(shapes.y, 2) == dim(shapes.x, 2) * strideW))"],
1576
+ "derive": { "usesF16": "dtypes.T == \"f16\"" },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1577
  "passes": [
1578
  {
1579
  "id": "main",
1580
  "name": "ConvTranspose1d",
1581
  "shader": "conv-transpose1d-ncl.wgsl.jinja",
1582
+ "bindings": ["x", "w_main", "y", "params_main"],
1583
  "dispatch": {
1584
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1585
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1591
  {
1592
  "id": "ncl1d_bias",
1593
  "priority": 10,
1594
+ "when": ["biasContract", "f32Type or f16Type", "rank3DenseContract", "rank3OutputShapeOk"],
1595
+ "derive": { "usesF16": "dtypes.T == \"f16\"" },
1596
  "passes": [
1597
  {
1598
  "id": "main",
1599
  "name": "ConvTranspose1dBias",
1600
  "shader": "conv-transpose1d-ncl.wgsl.jinja",
1601
+ "bindings": ["x", "w_main", "bias", "y", "params_main"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1602
  "dispatch": {
1603
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1604
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1610
  {
1611
  "id": "ncl1d_grouped",
1612
  "priority": 10,
1613
+ "when": ["noBiasContract", "f32Type or f16Type", "rank3GroupedContract", "rank3OutputShapeOk"],
1614
+ "derive": { "grouped": true, "usesF16": "dtypes.T == \"f16\"" },
1615
  "passes": [
1616
  {
1617
  "id": "main",
1618
  "name": "ConvTranspose1dGrouped",
1619
  "shader": "conv-transpose1d-ncl.wgsl.jinja",
1620
+ "bindings": ["x", "w_main", "y", "params_ncl1d_grouped"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1621
  "dispatch": {
1622
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1623
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1629
  {
1630
  "id": "ncl1d_grouped_bias",
1631
  "priority": 15,
1632
+ "when": ["biasContract", "f32Type or f16Type", "rank3GroupedContract", "rank3OutputShapeOk"],
1633
+ "derive": { "grouped": true, "usesF16": "dtypes.T == \"f16\"" },
1634
  "passes": [
1635
  {
1636
  "id": "main",
1637
  "name": "ConvTranspose1dGroupedBias",
1638
  "shader": "conv-transpose1d-ncl.wgsl.jinja",
1639
+ "bindings": ["x", "w_main", "bias", "y", "params_ncl1d_grouped"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1640
  "dispatch": {
1641
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1642
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1648
  {
1649
  "id": "nchw2d",
1650
  "priority": 0,
1651
+ "when": ["noBiasContract", "f32Type or f16Type", "rank4DenseContract", "(rank4OutputShapeOk or ((attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\") and dim(shapes.y, 2) == dim(shapes.x, 2) * strideH and dim(shapes.y, 3) == dim(shapes.x, 3) * strideW))"],
1652
+ "derive": { "usesF16": "dtypes.T == \"f16\"" },
1653
  "passes": [
1654
  {
1655
  "id": "main",
1656
  "name": "ConvTranspose",
1657
  "shader": "conv-transpose2d-nchw.wgsl.jinja",
1658
+ "bindings": ["x", "w_main", "y", "params_nchw2d"],
1659
  "dispatch": {
1660
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1661
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1667
  {
1668
  "id": "nchw2d_bias",
1669
  "priority": 10,
1670
+ "when": ["biasContract", "f32Type or f16Type", "rank4DenseContract", "rank4OutputShapeOk"],
1671
+ "derive": { "usesF16": "dtypes.T == \"f16\"" },
1672
  "passes": [
1673
  {
1674
  "id": "main",
1675
  "name": "ConvTranspose",
1676
  "shader": "conv-transpose2d-nchw.wgsl.jinja",
1677
+ "bindings": ["x", "w_main", "bias", "y", "params_nchw2d"],
1678
  "dispatch": {
1679
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1680
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1693
  "id": "main",
1694
  "name": "ConvTranspose3d",
1695
  "shader": "conv-transpose3d-ncdhw.wgsl.jinja",
1696
+ "bindings": ["x", "w_main", "y", "params_ncdhw3d"],
1697
  "dispatch": {
1698
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1699
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1706
  "id": "ncdhw3d_bias",
1707
  "priority": 10,
1708
  "when": ["biasContract", "f32Type", "rank5DenseContract", "rank5OutputShapeOk"],
1709
+ "derive": {},
1710
  "passes": [
1711
  {
1712
  "id": "main",
1713
  "name": "ConvTranspose3dBias",
1714
  "shader": "conv-transpose3d-ncdhw.wgsl.jinja",
1715
+ "bindings": ["x", "w_main", "bias", "y", "params_ncdhw3d"],
1716
  "dispatch": {
1717
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1718
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1724
  {
1725
  "id": "nchw2d_grouped",
1726
  "priority": 10,
1727
+ "when": ["noBiasContract", "f32Type or f16Type", "rank4GroupedContract", "rank4OutputShapeOk"],
1728
+ "derive": { "grouped": true, "usesF16": "dtypes.T == \"f16\"" },
1729
  "passes": [
1730
  {
1731
  "id": "main",
1732
  "name": "ConvTransposeGrouped",
1733
  "shader": "conv-transpose2d-nchw.wgsl.jinja",
1734
+ "bindings": ["x", "w_main", "y", "params_nchw2d_grouped"],
1735
  "dispatch": {
1736
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1737
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1741
  ]
1742
  },
1743
  {
1744
+ "id": "nchw2d_grouped_bias",
1745
+ "priority": 15,
1746
+ "when": ["biasContract", "f32Type or f16Type", "rank4GroupedContract", "rank4OutputShapeOk"],
1747
+ "derive": { "grouped": true, "usesF16": "dtypes.T == \"f16\"" },
1748
  "passes": [
1749
  {
1750
  "id": "main",
1751
  "name": "ConvTransposeGrouped",
1752
  "shader": "conv-transpose2d-nchw.wgsl.jinja",
1753
+ "bindings": ["x", "w_main", "bias", "y", "params_nchw2d_grouped"],
1754
  "dispatch": {
1755
  "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
1756
  "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
 
1760
  ]
1761
  },
1762
  {
1763
+ "id": "nchw2d_depthwise_pointwise_vec4",
1764
+ "priority": 30,
1765
+ "when": ["noBiasContract", "f32Type", "rank4DepthwisePointwiseVec4Contract"],
1766
+ "derive": { "grouped": true, "depthwisePointwiseVec4": true },
1767
  "passes": [
1768
  {
1769
  "id": "main",
1770
+ "name": "ConvTransposeDepthwisePointwiseVec4",
1771
  "shader": "conv-transpose2d-nchw.wgsl.jinja",
1772
+ "bindings": ["x", "w_main", "y", "params_conv_transpose2d_nchw"],
1773
  "dispatch": {
1774
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
1775
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
1776
  "z": 1
1777
  }
1778
  }
1779
  ]
1780
  },
1781
  {
1782
+ "id": "nchw2d_exact_store_gemm_f32",
1783
+ "priority": 38,
1784
+ "when": ["noBiasContract", "f32Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f32GemmScratchFits", "rank4OutputShapeOk", "exactTiling2d", "ceilDiv(gemmM, 64) * ceilDiv(gemmN, 64) >= tunables.GEMM_REG_MIN_TILE_PRODUCT", "device.limits.maxComputeInvocationsPerWorkgroup >= 128", "device.limits.maxComputeWorkgroupSizeX >= 16", "device.limits.maxComputeWorkgroupSizeY >= 8", "device.limits.maxComputeWorkgroupStorageSize >= 8192", "device.limits.maxStorageBuffersPerShaderStage >= 4"],
1785
+ "derive": {
1786
+ "usesF16": false,
1787
+ "convTransposeExactStore": true,
1788
+ "convTransposeKernelH": "dim(shapes.w, 2)",
1789
+ "convTransposeKernelW": "dim(shapes.w, 3)",
1790
+ "convTransposeInputW": "dim(shapes.x, 3)",
1791
+ "convTransposeOutputH": "dim(shapes.y, 2)",
1792
+ "convTransposeOutputW": "dim(shapes.y, 3)"
1793
+ },
1794
+ "intermediates": [
1795
  {
1796
+ "id": "wt",
1797
+ "dtype": "float32",
1798
+ "shape": "[dim(shapes.w, 0) * dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)]"
 
 
 
 
 
 
1799
  }
1800
+ ],
 
 
 
 
 
 
1801
  "passes": [
1802
  {
1803
+ "id": "reorder",
1804
+ "name": "ConvTransposeWeightReorder",
1805
+ "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1806
+ "bindings": ["wsrc", "wt", "params_reorder"],
1807
  "dispatch": {
1808
+ "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1809
+ "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1810
  "z": 1
1811
  }
1812
+ },
1813
+ {
1814
+ "id": "gemm",
1815
+ "name": "ConvTransposeExactOutputGemm",
1816
+ "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
1817
+ "bindings": ["w_t", "xm", "y", "params_gemm"],
1818
+ "dispatch": { "x": "ceilDiv(gemmN, 64)", "y": "ceilDiv(gemmM, 64)", "z": "dim(shapes.x, 0)" }
1819
  }
1820
  ]
1821
  },
1822
  {
1823
+ "id": "nchw2d_exact_store_gemm_f32_bias",
1824
+ "priority": 38,
1825
+ "when": ["biasContract", "f32Type", "rank4DenseContract", "gemmN > 0", "gemmDispatchFits", "f32GemmScratchFits", "rank4OutputShapeOk", "exactTiling2d", "ceilDiv(gemmM, 64) * ceilDiv(gemmN, 64) >= tunables.GEMM_REG_MIN_TILE_PRODUCT", "device.limits.maxComputeInvocationsPerWorkgroup >= 128", "device.limits.maxComputeWorkgroupSizeX >= 16", "device.limits.maxComputeWorkgroupSizeY >= 8", "device.limits.maxComputeWorkgroupStorageSize >= 8192", "device.limits.maxStorageBuffersPerShaderStage >= 4"],
1826
+ "derive": {
1827
+ "usesF16": false,
1828
+ "convTransposeExactStore": true,
1829
+ "convTransposeKernelH": "dim(shapes.w, 2)",
1830
+ "convTransposeKernelW": "dim(shapes.w, 3)",
1831
+ "convTransposeInputW": "dim(shapes.x, 3)",
1832
+ "convTransposeOutputH": "dim(shapes.y, 2)",
1833
+ "convTransposeOutputW": "dim(shapes.y, 3)"
1834
+ },
1835
+ "intermediates": [
1836
  {
1837
+ "id": "wt",
1838
+ "dtype": "float32",
1839
+ "shape": "[dim(shapes.w, 0) * dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)]"
 
 
 
 
 
 
1840
  }
1841
+ ],
 
 
 
 
 
 
1842
  "passes": [
1843
  {
1844
+ "id": "reorder",
1845
+ "name": "ConvTransposeWeightReorder",
1846
+ "shader": "conv-transpose2d-weight-reorder.wgsl.jinja",
1847
+ "bindings": ["wsrc", "wt", "params_reorder"],
1848
  "dispatch": {
1849
+ "x": "min(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1850
+ "y": "ceilDiv(ceilDiv((numel(shapes.w)), (tunables.WORKGROUP_SIZE)), 65535)",
1851
  "z": 1
1852
  }
1853
+ },
1854
+ {
1855
+ "id": "gemm",
1856
+ "name": "ConvTransposeExactOutputGemm",
1857
+ "shader": "conv-1x1-gemm-tiled-reg.wgsl.jinja",
1858
+ "bindings": ["w_t", "xm", "y", "bias", "params_gemm"],
1859
+ "dispatch": { "x": "ceilDiv(gemmN, 64)", "y": "ceilDiv(gemmM, 64)", "z": "dim(shapes.x, 0)" }
1860
  }
1861
  ]
1862
  }
build/webgpu/metadata.json CHANGED
@@ -1,35 +1,35 @@
1
  {
2
  "name": "ai.onnx.ConvTranspose",
3
- "id": "_ai_onnx_convtranspose_webgpu_56773ae",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "dDVq94oHWtxnmS2Wl6meZDLrI+Y9O97/LkYR48NM3V0=",
11
- "conv-1x1-gemm-tiled-reg.wgsl.jinja": "XGiW/6HmanaqmNZH0rmwfYTirJyt8+/EwXnirJFyBHc=",
12
- "conv-1x1-gemm-tiled.wgsl.jinja": "iFg95DLXfwwm52C2vQZPQ2f2NwKnQ/YkeKfXTcqcRTw=",
13
- "conv-1x1-subgroup-matrix.wgsl.jinja": "6F2l/7FfatK1TB/0lmXl/lQmOw2Tgq384e/Z4A9YTIY=",
14
- "conv-transpose-empty-input.wgsl.jinja": "Ui7q8I1UJMw4zfQHWYUErVRg80V/l6f9UMUdsSKThG8=",
15
- "conv-transpose1d-ncl.wgsl.jinja": "TVGioAump1OMC6qd8iyd/wuVQoQnMwCDh4+nWWjTKEA=",
16
  "conv-transpose1d-phase-tiled.wgsl.jinja": "bMqHC0PYMPoDgBX8QjGyF4amRjVyyl2tUgWWXSmJQeY=",
17
- "conv-transpose2d-col2im.wgsl.jinja": "hQb4bZVoepiuY7w+IioYT6CPYQmtbmWgrSHjno6lG/w=",
18
  "conv-transpose2d-grouped-stride-phase-blocked.wgsl.jinja": "p4uIVP2LZItD34v/s79/GIozvNDnXb/PqS3tRGFQhe8=",
19
- "conv-transpose2d-grouped-stride-phase.wgsl.jinja": "d1fM5Z2hJ8W2zIPwMfSj0udfo1J8VrMzaqfzZScb1ok=",
20
- "conv-transpose2d-nchw.wgsl.jinja": "fZJUH7iDzqcapU1Ddjz7o1JP5J2uBB9qCWLE7QNuSAw=",
21
- "conv-transpose2d-phase-gemm-tiled.wgsl.jinja": "FW6lWrHoqI76BAw1GtSzOPgFAZPMtxkxITAy/6X7ckM=",
22
- "conv-transpose2d-weight-reorder.wgsl.jinja": "tmJqqJBwS6L4d9nqC0maYJyFI593/O3JyaGm5qBlAI8=",
23
- "conv-transpose3d-col2im.wgsl.jinja": "IaBjirYXO0HWlomg6qMzalECCUcXO4zQiSHR/zct8WE=",
24
- "conv-transpose3d-ncdhw.wgsl.jinja": "kzq3eqznM7d7n2aUgEkWqjog0zGW6Pdsxynwq7sCTws=",
25
- "conv-transpose3d-weight-reorder.wgsl.jinja": "XBM0oifIImJjNT8OoMyutVpoPvE9iGebry251Gcanls=",
26
- "manifest.json": "wY9St4yXt/42sQaWNvjxkzpY0slhkzuGR6yLd8/VtG0=",
27
- "test.json": "l57YIRQh+ROu5iMw+EoPUOGz8/DplIWgGtvYu8ALGUw="
28
  }
29
  },
30
- "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
31
  "webgpu": {
32
- "manifestSpec": "2.0",
33
  "variants": {
34
  "empty_input_reduction": ["conv-transpose-empty-input.wgsl.jinja"],
35
  "empty_input_reduction_bias": ["conv-transpose-empty-input.wgsl.jinja"],
@@ -54,28 +54,22 @@
54
  "nchw2d_phase_gemm_tiled": ["conv-transpose2d-phase-gemm-tiled.wgsl.jinja"],
55
  "nchw2d_phase_gemm_tiled_bias": ["conv-transpose2d-phase-gemm-tiled.wgsl.jinja"],
56
  "nchw2d_grouped_stride_phase_blocked": ["conv-transpose2d-grouped-stride-phase-blocked.wgsl.jinja"],
57
- "nchw2d_grouped_stride_phase_blocked_bias": ["conv-transpose2d-grouped-stride-phase-blocked.wgsl.jinja"],
58
  "nchw2d_grouped_stride_phase": ["conv-transpose2d-grouped-stride-phase.wgsl.jinja"],
 
59
  "nchw2d_grouped_stride_phase_bias": ["conv-transpose2d-grouped-stride-phase.wgsl.jinja"],
60
  "ncl1d": ["conv-transpose1d-ncl.wgsl.jinja"],
61
- "ncl1d_f16": ["conv-transpose1d-ncl.wgsl.jinja"],
62
  "ncl1d_bias": ["conv-transpose1d-ncl.wgsl.jinja"],
63
- "ncl1d_bias_f16": ["conv-transpose1d-ncl.wgsl.jinja"],
64
  "ncl1d_grouped": ["conv-transpose1d-ncl.wgsl.jinja"],
65
- "ncl1d_grouped_f16": ["conv-transpose1d-ncl.wgsl.jinja"],
66
  "ncl1d_grouped_bias": ["conv-transpose1d-ncl.wgsl.jinja"],
67
- "ncl1d_grouped_bias_f16": ["conv-transpose1d-ncl.wgsl.jinja"],
68
  "nchw2d": ["conv-transpose2d-nchw.wgsl.jinja"],
69
  "nchw2d_bias": ["conv-transpose2d-nchw.wgsl.jinja"],
70
  "ncdhw3d": ["conv-transpose3d-ncdhw.wgsl.jinja"],
71
  "ncdhw3d_bias": ["conv-transpose3d-ncdhw.wgsl.jinja"],
72
  "nchw2d_grouped": ["conv-transpose2d-nchw.wgsl.jinja"],
73
- "nchw2d_grouped_f16": ["conv-transpose2d-nchw.wgsl.jinja"],
74
- "nchw2d_grouped_bias_f16": ["conv-transpose2d-nchw.wgsl.jinja"],
75
  "nchw2d_grouped_bias": ["conv-transpose2d-nchw.wgsl.jinja"],
76
- "nchw2d_f16": ["conv-transpose2d-nchw.wgsl.jinja"],
77
- "nchw2d_bias_f16": ["conv-transpose2d-nchw.wgsl.jinja"],
78
- "nchw2d_depthwise_pointwise_vec4": ["conv-transpose2d-nchw.wgsl.jinja"]
79
  }
80
  }
81
  }
 
1
  {
2
  "name": "ai.onnx.ConvTranspose",
3
+ "id": "_ai_onnx_convtranspose_webgpu_0e6933f",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "vJsBuZpz7kxmXlf9dInpQAZItXPZxN+5xPRrDmUUfrY=",
11
+ "conv-1x1-gemm-tiled-reg.wgsl.jinja": "oOxUhgEAuPbZrr0h5rD0E7KnTqroZOk/lVT4AYQjngA=",
12
+ "conv-1x1-gemm-tiled.wgsl.jinja": "9xyemcOew6wTxIZw0vCzuFUNrx1kouVK4qrmKefko80=",
13
+ "conv-1x1-subgroup-matrix.wgsl.jinja": "rRCXC/HSy3WXK5TzvmRTWkNTAZ80EpnekwWU83oTrkI=",
14
+ "conv-transpose-empty-input.wgsl.jinja": "I5t4SroHcl3JqmeiggiIDwjUEGTPLyyX27ShKVhw33c=",
15
+ "conv-transpose1d-ncl.wgsl.jinja": "rMEEIJ8o3ag4cADd0WmCzaxMlTzKRvKvuvtyCMyknso=",
16
  "conv-transpose1d-phase-tiled.wgsl.jinja": "bMqHC0PYMPoDgBX8QjGyF4amRjVyyl2tUgWWXSmJQeY=",
17
+ "conv-transpose2d-col2im.wgsl.jinja": "fyDOZ6oCcj0h4R05wvmNKBnCKf+7xXXZpL7fQF15zMI=",
18
  "conv-transpose2d-grouped-stride-phase-blocked.wgsl.jinja": "p4uIVP2LZItD34v/s79/GIozvNDnXb/PqS3tRGFQhe8=",
19
+ "conv-transpose2d-grouped-stride-phase.wgsl.jinja": "3M2QhoCoMwtaATTUwz9jphQMRex0DEurNOimwlAW8Tc=",
20
+ "conv-transpose2d-nchw.wgsl.jinja": "C1ftXjB5oTtPuHLtKKVwshB1AMU+YXMArwmyS9NuTKo=",
21
+ "conv-transpose2d-phase-gemm-tiled.wgsl.jinja": "ftnzjnquOMpNm0hesCDaCp4hNwRrZwZp+b5RtBdUn9w=",
22
+ "conv-transpose2d-weight-reorder.wgsl.jinja": "KySJAoLUvLxoaTVjyEndrtcrS5hrlD/kYR8mWe/FSbY=",
23
+ "conv-transpose3d-col2im.wgsl.jinja": "GWbXZCcp7MorH/SUgtpiMlJKQEZK4Djq6VqqqYy3cVM=",
24
+ "conv-transpose3d-ncdhw.wgsl.jinja": "E82Z/wkc6hQYhWbuzEEU7YBptljE8DZpy0HPhOAfguc=",
25
+ "conv-transpose3d-weight-reorder.wgsl.jinja": "RJ5NvBPIZM3y61n+pvcQ7Vb+YpMEg6yR6gcjc1qUcQ0=",
26
+ "manifest.json": "BEwtw+Cge4ZoOyuZle8VlOGJjCo/tF01SCY4n6/CZj4=",
27
+ "test.json": "83npgDmD6vc3C8HyVruLUbPLuMIjDznYNU8oq2G9nek="
28
  }
29
  },
30
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
31
  "webgpu": {
32
+ "manifestSpec": "2.1",
33
  "variants": {
34
  "empty_input_reduction": ["conv-transpose-empty-input.wgsl.jinja"],
35
  "empty_input_reduction_bias": ["conv-transpose-empty-input.wgsl.jinja"],
 
54
  "nchw2d_phase_gemm_tiled": ["conv-transpose2d-phase-gemm-tiled.wgsl.jinja"],
55
  "nchw2d_phase_gemm_tiled_bias": ["conv-transpose2d-phase-gemm-tiled.wgsl.jinja"],
56
  "nchw2d_grouped_stride_phase_blocked": ["conv-transpose2d-grouped-stride-phase-blocked.wgsl.jinja"],
 
57
  "nchw2d_grouped_stride_phase": ["conv-transpose2d-grouped-stride-phase.wgsl.jinja"],
58
+ "nchw2d_grouped_stride_phase_blocked_bias": ["conv-transpose2d-grouped-stride-phase-blocked.wgsl.jinja"],
59
  "nchw2d_grouped_stride_phase_bias": ["conv-transpose2d-grouped-stride-phase.wgsl.jinja"],
60
  "ncl1d": ["conv-transpose1d-ncl.wgsl.jinja"],
 
61
  "ncl1d_bias": ["conv-transpose1d-ncl.wgsl.jinja"],
 
62
  "ncl1d_grouped": ["conv-transpose1d-ncl.wgsl.jinja"],
 
63
  "ncl1d_grouped_bias": ["conv-transpose1d-ncl.wgsl.jinja"],
 
64
  "nchw2d": ["conv-transpose2d-nchw.wgsl.jinja"],
65
  "nchw2d_bias": ["conv-transpose2d-nchw.wgsl.jinja"],
66
  "ncdhw3d": ["conv-transpose3d-ncdhw.wgsl.jinja"],
67
  "ncdhw3d_bias": ["conv-transpose3d-ncdhw.wgsl.jinja"],
68
  "nchw2d_grouped": ["conv-transpose2d-nchw.wgsl.jinja"],
 
 
69
  "nchw2d_grouped_bias": ["conv-transpose2d-nchw.wgsl.jinja"],
70
+ "nchw2d_depthwise_pointwise_vec4": ["conv-transpose2d-nchw.wgsl.jinja"],
71
+ "nchw2d_exact_store_gemm_f32": ["conv-1x1-gemm-tiled-reg.wgsl.jinja", "conv-transpose2d-weight-reorder.wgsl.jinja"],
72
+ "nchw2d_exact_store_gemm_f32_bias": ["conv-1x1-gemm-tiled-reg.wgsl.jinja", "conv-transpose2d-weight-reorder.wgsl.jinja"]
73
  }
74
  }
75
  }
build/webgpu/test.json CHANGED
@@ -5,7 +5,12 @@
5
  "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],
6
  "onnx_backend_convtranspose_input_w": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
7
  "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],
8
- "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]
 
 
 
 
 
9
  },
10
  "cases": [
11
  {
@@ -133,7 +138,7 @@
133
  {
134
  "name": "phase_tiled_1d_register_tile_channel_position_tails",
135
  "provenance": {
136
- "notes": "Crosses the occupancy-derived two-output register-tile threshold while retaining input-channel, output-channel, and position tails."
137
  },
138
  "attrs": { "strides": [2], "pads": [1, 1] },
139
  "inputs": {
@@ -1323,7 +1328,7 @@
1323
  {
1324
  "name": "f16_phase_gemm_k4s2_channel_and_spatial_tails",
1325
  "provenance": {
1326
- "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; offset operands keep each accumulation proportional to its tap count."
1327
  },
1328
  "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
1329
  "inputs": {
@@ -1368,7 +1373,7 @@
1368
  {
1369
  "name": "f16_gemm_2d_no_bias",
1370
  "provenance": {
1371
- "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."
1372
  },
1373
  "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
1374
  "inputs": {
@@ -1388,7 +1393,7 @@
1388
  {
1389
  "name": "f16_gemm_2d_bias",
1390
  "provenance": {
1391
- "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."
1392
  },
1393
  "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
1394
  "inputs": {
@@ -1586,7 +1591,7 @@
1586
  "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 5, 1, 1], "tolerance": 0.000001 } }
1587
  },
1588
  {
1589
- "name": "dispatch_cliff_batch_over_65535_bias_forces_nchw_scalar",
1590
  "provenance": {
1591
  "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."
1592
  },
@@ -1603,9 +1608,9 @@
1603
  "outputs": { "y": { "dtype": "float32", "shape": [65536, 1, 1, 1], "tolerance": 0.00001 } }
1604
  },
1605
  {
1606
- "name": "dispatch_cliff_batch_over_65535_bias_f16_forces_nchw_scalar",
1607
  "provenance": {
1608
- "notes": "For a float16 batch above the dispatch limit, matrix paths are ineligible and the geometry does not admit stride-2 phase GEMM, selecting the scalar NCHW bias path."
1609
  },
1610
  "attrs": {},
1611
  "inputs": {
@@ -1690,7 +1695,7 @@
1690
  {
1691
  "name": "convtranspose3d_subgroup_matrix_bias_f32",
1692
  "provenance": {
1693
- "notes": "An aligned rank-5 pointwise volumetric matrix path isolates the bias epilogue while preserving M/K/N tile eligibility."
1694
  },
1695
  "attrs": {},
1696
  "inputs": {
@@ -1711,7 +1716,7 @@
1711
  {
1712
  "name": "convtranspose3d_tiled_reg_large_spatial_f32",
1713
  "provenance": {
1714
- "notes": "A portable register-tiled rank-5 case with N=8*32*32 and M=32 uses pointwise weights to isolate the large matrix path and three-dimensional tensor indexing."
1715
  },
1716
  "attrs": {},
1717
  "inputs": {
@@ -1774,7 +1779,7 @@
1774
  {
1775
  "name": "convtranspose3d_implicit_sgmat_k3_nopad_bias_f32",
1776
  "provenance": {
1777
- "notes": "Zero-pad stride-1 k3 growth shape with bias: the forward equivalence uses full begin pads of kernel-1 on every axis and the bias epilogue lands on directly stored output tiles."
1778
  },
1779
  "inputs": {
1780
  "x": {
@@ -2004,6 +2009,366 @@
2004
  "bias": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.75] } }
2005
  },
2006
  "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 0, 2, 2], "tolerance": 0 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2007
  }
2008
  ]
2009
  }
 
5
  "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],
6
  "onnx_backend_convtranspose_input_w": [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
7
  "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],
8
+ "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],
9
+ "f16_phase_perimeter_h1_w1_first_nan_input_w": [0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, "NaN", 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125],
10
+ "f16_phase_perimeter_h1_w5_last_nan_input_x": [0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875, 0.21875, 0.25, 0.28125, 0.3125, 0.34375, 0.375, 0.40625, 0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875, 0.21875, 0.25, 0.28125, 0.3125, 0.34375, 0.375, 0.40625, 0.03125, 0.0625, 0.09375, 0.125],
11
+ "f16_phase_perimeter_h1_w5_last_infinity_input_w": [0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, "Infinity"],
12
+ "f16_phase_perimeter_h4_w1_first_infinity_input_x": [0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875, 0.21875, 0.25, 0.28125, 0.3125, 0.34375, 0.375, 0.40625, 0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875, 0.21875, 0.25, 0.28125, 0.3125, 0.34375],
13
+ "f16_phase_perimeter_h3_w5_first_nan_input_x": [0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875, 0.21875, 0.25, 0.28125, 0.3125, 0.34375, 0.375, 0.40625, 0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875, 0.21875, 0.25, 0.28125, 0.3125, 0.34375, 0.375, 0.40625, 0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875, 0.21875, 0.25, 0.28125, 0.3125, 0.34375, 0.375, 0.40625, 0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875, 0.21875, 0.25, 0.28125, 0.3125, 0.34375, 0.375, 0.40625, 0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875, 0.21875, 0.25, 0.28125, 0.3125, 0.34375, 0.375, 0.40625, 0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875, 0.21875, 0.25, 0.28125, 0.3125, 0.34375, 0.375, 0.40625, 0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875, 0.21875, 0.25, 0.28125, 0.3125, 0.34375, 0.375]
14
  },
15
  "cases": [
16
  {
 
138
  {
139
  "name": "phase_tiled_1d_register_tile_channel_position_tails",
140
  "provenance": {
141
+ "notes": "Input-channel, output-channel and position tails exercise transposed convolution at a large output shape."
142
  },
143
  "attrs": { "strides": [2], "pads": [1, 1] },
144
  "inputs": {
 
1328
  {
1329
  "name": "f16_phase_gemm_k4s2_channel_and_spatial_tails",
1330
  "provenance": {
1331
+ "notes": "A stride-two transposed convolution with a four-tap kernel, odd channel and spatial sizes checks padding and reduction tails. Offset operands make a missing tap observable."
1332
  },
1333
  "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
1334
  "inputs": {
 
1373
  {
1374
  "name": "f16_gemm_2d_no_bias",
1375
  "provenance": {
1376
+ "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 kernel."
1377
  },
1378
  "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
1379
  "inputs": {
 
1393
  {
1394
  "name": "f16_gemm_2d_bias",
1395
  "provenance": {
1396
+ "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_bias kernel."
1397
  },
1398
  "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
1399
  "inputs": {
 
1591
  "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 5, 1, 1], "tolerance": 0.000001 } }
1592
  },
1593
  {
1594
+ "name": "dispatch_cliff_batch_over_65535_bias_nchw_scalar",
1595
  "provenance": {
1596
  "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."
1597
  },
 
1608
  "outputs": { "y": { "dtype": "float32", "shape": [65536, 1, 1, 1], "tolerance": 0.00001 } }
1609
  },
1610
  {
1611
+ "name": "dispatch_cliff_batch_over_65535_bias_f16_nchw_scalar",
1612
  "provenance": {
1613
+ "notes": "A batch of 65,536 float16 rows, beyond a single dispatch grid, checks a 1x1 transposed convolution with a single-element bias at default stride 1."
1614
  },
1615
  "attrs": {},
1616
  "inputs": {
 
1695
  {
1696
  "name": "convtranspose3d_subgroup_matrix_bias_f32",
1697
  "provenance": {
1698
+ "notes": "A 3-D pointwise (1x1x1) transposed convolution with 32 input and 32 output channels uses constant inputs (1.0), weights (0.125) and bias (0.5); every output equals exactly 4.5, directly checking the accumulation and bias addition."
1699
  },
1700
  "attrs": {},
1701
  "inputs": {
 
1716
  {
1717
  "name": "convtranspose3d_tiled_reg_large_spatial_f32",
1718
  "provenance": {
1719
+ "notes": "A rank-five pointwise transposed convolution with N=8*32*32 and M=32 checks a large spatial extent and three-dimensional tensor indexing."
1720
  },
1721
  "attrs": {},
1722
  "inputs": {
 
1779
  {
1780
  "name": "convtranspose3d_implicit_sgmat_k3_nopad_bias_f32",
1781
  "provenance": {
1782
+ "notes": "A zero-pad, stride-one 3D transposed convolution grows each spatial axis and applies channel bias to the output."
1783
  },
1784
  "inputs": {
1785
  "x": {
 
2009
  "bias": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.75] } }
2010
  },
2011
  "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 0, 2, 2], "tolerance": 0 } }
2012
+ },
2013
+ {
2014
+ "name": "f16_phase_perimeter_h1_w1_first_nan",
2015
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
2016
+ "inputs": {
2017
+ "x": {
2018
+ "dtype": "float16",
2019
+ "shape": [2, 3, 1, 1],
2020
+ "data": { "kind": "values", "values": [0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875] }
2021
+ },
2022
+ "w": {
2023
+ "dtype": "float16",
2024
+ "shape": [3, 2, 4, 4],
2025
+ "data": {
2026
+ "kind": "values",
2027
+ "values": { "$ref": "#/fixtureArrays/f16_phase_perimeter_h1_w1_first_nan_input_w" }
2028
+ }
2029
+ },
2030
+ "bias": { "dtype": "float16", "shape": [2], "data": { "kind": "values", "values": [0.25, -0.5] } }
2031
+ },
2032
+ "outputs": {
2033
+ "y": { "dtype": "float16", "shape": [2, 2, 2, 2], "tolerance": 0.001, "allowNaN": true, "relTolerance": 0.002 }
2034
+ },
2035
+ "provenance": {
2036
+ "notes": "A non-finite weight at the first or last tap of the final input/output channel is unreachable for some perimeter outputs. The phase GEMM must omit those taps instead of multiplying synthetic padding zeros by NaN/Infinity. Batch two and one-row/one-column cases cover phase addressing and degenerate axes."
2037
+ }
2038
+ },
2039
+ {
2040
+ "name": "f16_phase_perimeter_h1_w1_first_neg_infinity",
2041
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
2042
+ "inputs": {
2043
+ "x": {
2044
+ "dtype": "float16",
2045
+ "shape": [2, 3, 1, 1],
2046
+ "data": { "kind": "values", "values": [0.03125, 0.0625, 0.09375, 0.125, 0.15625, 0.1875] }
2047
+ },
2048
+ "w": {
2049
+ "dtype": "float16",
2050
+ "shape": [3, 2, 4, 4],
2051
+ "data": {
2052
+ "kind": "values",
2053
+ "values": [0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, "-Infinity", 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125]
2054
+ }
2055
+ },
2056
+ "bias": { "dtype": "float16", "shape": [2], "data": { "kind": "values", "values": [0.25, -0.5] } }
2057
+ },
2058
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 2, 2, 2], "tolerance": 0.001, "relTolerance": 0.002 } },
2059
+ "provenance": {
2060
+ "notes": "A non-finite weight at the first or last tap of the final input/output channel is unreachable for some perimeter outputs. The phase GEMM must omit those taps instead of multiplying synthetic padding zeros by NaN/Infinity. Batch two and one-row/one-column cases cover phase addressing and degenerate axes."
2061
+ }
2062
+ },
2063
+ {
2064
+ "name": "f16_phase_perimeter_h1_w5_last_nan",
2065
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
2066
+ "inputs": {
2067
+ "x": {
2068
+ "dtype": "float16",
2069
+ "shape": [2, 3, 1, 5],
2070
+ "data": {
2071
+ "kind": "values",
2072
+ "values": { "$ref": "#/fixtureArrays/f16_phase_perimeter_h1_w5_last_nan_input_x" }
2073
+ }
2074
+ },
2075
+ "w": {
2076
+ "dtype": "float16",
2077
+ "shape": [3, 2, 4, 4],
2078
+ "data": {
2079
+ "kind": "values",
2080
+ "values": [0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, "NaN"]
2081
+ }
2082
+ },
2083
+ "bias": { "dtype": "float16", "shape": [2], "data": { "kind": "values", "values": [0.25, -0.5] } }
2084
+ },
2085
+ "outputs": {
2086
+ "y": { "dtype": "float16", "shape": [2, 2, 2, 10], "tolerance": 0.001, "allowNaN": true, "relTolerance": 0.002 }
2087
+ },
2088
+ "provenance": {
2089
+ "notes": "A non-finite weight at the first or last tap of the final input/output channel is unreachable for some perimeter outputs. The phase GEMM must omit those taps instead of multiplying synthetic padding zeros by NaN/Infinity. Batch two and one-row/one-column cases cover phase addressing and degenerate axes."
2090
+ }
2091
+ },
2092
+ {
2093
+ "name": "f16_phase_perimeter_h1_w5_last_infinity",
2094
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
2095
+ "inputs": {
2096
+ "x": {
2097
+ "dtype": "float16",
2098
+ "shape": [2, 3, 1, 5],
2099
+ "data": {
2100
+ "kind": "values",
2101
+ "values": { "$ref": "#/fixtureArrays/f16_phase_perimeter_h1_w5_last_nan_input_x" }
2102
+ }
2103
+ },
2104
+ "w": {
2105
+ "dtype": "float16",
2106
+ "shape": [3, 2, 4, 4],
2107
+ "data": {
2108
+ "kind": "values",
2109
+ "values": { "$ref": "#/fixtureArrays/f16_phase_perimeter_h1_w5_last_infinity_input_w" }
2110
+ }
2111
+ },
2112
+ "bias": { "dtype": "float16", "shape": [2], "data": { "kind": "values", "values": [0.25, -0.5] } }
2113
+ },
2114
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 2, 2, 10], "tolerance": 0.001, "relTolerance": 0.002 } },
2115
+ "provenance": {
2116
+ "notes": "A non-finite weight at the first or last tap of the final input/output channel is unreachable for some perimeter outputs. The phase GEMM must omit those taps instead of multiplying synthetic padding zeros by NaN/Infinity. Batch two and one-row/one-column cases cover phase addressing and degenerate axes."
2117
+ }
2118
+ },
2119
+ {
2120
+ "name": "f16_phase_perimeter_h4_w1_first_infinity",
2121
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
2122
+ "inputs": {
2123
+ "x": {
2124
+ "dtype": "float16",
2125
+ "shape": [2, 3, 4, 1],
2126
+ "data": {
2127
+ "kind": "values",
2128
+ "values": { "$ref": "#/fixtureArrays/f16_phase_perimeter_h4_w1_first_infinity_input_x" }
2129
+ }
2130
+ },
2131
+ "w": {
2132
+ "dtype": "float16",
2133
+ "shape": [3, 2, 4, 4],
2134
+ "data": {
2135
+ "kind": "values",
2136
+ "values": [0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, "Infinity", 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125]
2137
+ }
2138
+ },
2139
+ "bias": { "dtype": "float16", "shape": [2], "data": { "kind": "values", "values": [0.25, -0.5] } }
2140
+ },
2141
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 2, 8, 2], "tolerance": 0.001, "relTolerance": 0.002 } },
2142
+ "provenance": {
2143
+ "notes": "A non-finite weight at the first or last tap of the final input/output channel is unreachable for some perimeter outputs. The phase GEMM must omit those taps instead of multiplying synthetic padding zeros by NaN/Infinity. Batch two and one-row/one-column cases cover phase addressing and degenerate axes."
2144
+ }
2145
+ },
2146
+ {
2147
+ "name": "f16_phase_perimeter_h4_w1_last_neg_infinity",
2148
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
2149
+ "inputs": {
2150
+ "x": {
2151
+ "dtype": "float16",
2152
+ "shape": [2, 3, 4, 1],
2153
+ "data": {
2154
+ "kind": "values",
2155
+ "values": { "$ref": "#/fixtureArrays/f16_phase_perimeter_h4_w1_first_infinity_input_x" }
2156
+ }
2157
+ },
2158
+ "w": {
2159
+ "dtype": "float16",
2160
+ "shape": [3, 2, 4, 4],
2161
+ "data": {
2162
+ "kind": "values",
2163
+ "values": [0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, 0.078125, 0.09375, 0.109375, 0.015625, 0.03125, 0.046875, 0.0625, "-Infinity"]
2164
+ }
2165
+ },
2166
+ "bias": { "dtype": "float16", "shape": [2], "data": { "kind": "values", "values": [0.25, -0.5] } }
2167
+ },
2168
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 2, 8, 2], "tolerance": 0.001, "relTolerance": 0.002 } },
2169
+ "provenance": {
2170
+ "notes": "A non-finite weight at the first or last tap of the final input/output channel is unreachable for some perimeter outputs. The phase GEMM must omit those taps instead of multiplying synthetic padding zeros by NaN/Infinity. Batch two and one-row/one-column cases cover phase addressing and degenerate axes."
2171
+ }
2172
+ },
2173
+ {
2174
+ "name": "f16_phase_perimeter_h3_w5_first_nan",
2175
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
2176
+ "inputs": {
2177
+ "x": {
2178
+ "dtype": "float16",
2179
+ "shape": [2, 3, 3, 5],
2180
+ "data": {
2181
+ "kind": "values",
2182
+ "values": { "$ref": "#/fixtureArrays/f16_phase_perimeter_h3_w5_first_nan_input_x" }
2183
+ }
2184
+ },
2185
+ "w": {
2186
+ "dtype": "float16",
2187
+ "shape": [3, 2, 4, 4],
2188
+ "data": {
2189
+ "kind": "values",
2190
+ "values": { "$ref": "#/fixtureArrays/f16_phase_perimeter_h1_w1_first_nan_input_w" }
2191
+ }
2192
+ },
2193
+ "bias": { "dtype": "float16", "shape": [2], "data": { "kind": "values", "values": [0.25, -0.5] } }
2194
+ },
2195
+ "outputs": {
2196
+ "y": { "dtype": "float16", "shape": [2, 2, 6, 10], "tolerance": 0.001, "allowNaN": true, "relTolerance": 0.002 }
2197
+ },
2198
+ "provenance": {
2199
+ "notes": "A non-finite weight at the first or last tap of the final input/output channel is unreachable for some perimeter outputs. The phase GEMM must omit those taps instead of multiplying synthetic padding zeros by NaN/Infinity. Batch two and one-row/one-column cases cover phase addressing and degenerate axes."
2200
+ }
2201
+ },
2202
+ {
2203
+ "name": "f16_phase_perimeter_h3_w5_last_infinity",
2204
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
2205
+ "inputs": {
2206
+ "x": {
2207
+ "dtype": "float16",
2208
+ "shape": [2, 3, 3, 5],
2209
+ "data": {
2210
+ "kind": "values",
2211
+ "values": { "$ref": "#/fixtureArrays/f16_phase_perimeter_h3_w5_first_nan_input_x" }
2212
+ }
2213
+ },
2214
+ "w": {
2215
+ "dtype": "float16",
2216
+ "shape": [3, 2, 4, 4],
2217
+ "data": {
2218
+ "kind": "values",
2219
+ "values": { "$ref": "#/fixtureArrays/f16_phase_perimeter_h1_w5_last_infinity_input_w" }
2220
+ }
2221
+ },
2222
+ "bias": { "dtype": "float16", "shape": [2], "data": { "kind": "values", "values": [0.25, -0.5] } }
2223
+ },
2224
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 2, 6, 10], "tolerance": 0.001, "relTolerance": 0.002 } },
2225
+ "provenance": {
2226
+ "notes": "A non-finite weight at the first or last tap of the final input/output channel is unreachable for some perimeter outputs. The phase GEMM must omit those taps instead of multiplying synthetic padding zeros by NaN/Infinity. Batch two and one-row/one-column cases cover phase addressing and degenerate axes."
2227
+ }
2228
+ },
2229
+ {
2230
+ "name": "exact_store_tail_b2_c17_m5_k2x3_nobias",
2231
+ "attrs": { "strides": [2, 3] },
2232
+ "tunables": { "GEMM_REG_MIN_TILE_PRODUCT": 0 },
2233
+ "inputs": {
2234
+ "x": {
2235
+ "dtype": "float32",
2236
+ "shape": [2, 17, 7, 11],
2237
+ "data": { "kind": "cycle", "values": [0.25, -0.5, 0.125, 0.75, -1.0] }
2238
+ },
2239
+ "w": {
2240
+ "dtype": "float32",
2241
+ "shape": [17, 5, 2, 3],
2242
+ "data": { "kind": "cycle", "values": [-0.5, 0.25, 0.75, -0.125, 0.5, 0.0625] }
2243
+ }
2244
+ },
2245
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 5, 14, 33], "tolerance": 0, "relTolerance": 0 } },
2246
+ "provenance": {
2247
+ "source": "derived",
2248
+ "notes": "Nonoverlapping transposed convolution with batch, contraction, matrix-row and spatial tails. Dyadic inputs keep the CPU reference exact on a compact tensor."
2249
+ }
2250
+ },
2251
+ {
2252
+ "name": "exact_store_tail_b2_c17_m5_k2x3_bias",
2253
+ "attrs": { "strides": [2, 3] },
2254
+ "tunables": { "GEMM_REG_MIN_TILE_PRODUCT": 0 },
2255
+ "inputs": {
2256
+ "x": {
2257
+ "dtype": "float32",
2258
+ "shape": [2, 17, 7, 11],
2259
+ "data": { "kind": "cycle", "values": [0.25, -0.5, 0.125, 0.75, -1.0] }
2260
+ },
2261
+ "w": {
2262
+ "dtype": "float32",
2263
+ "shape": [17, 5, 2, 3],
2264
+ "data": { "kind": "cycle", "values": [-0.5, 0.25, 0.75, -0.125, 0.5, 0.0625] }
2265
+ },
2266
+ "bias": { "dtype": "float32", "shape": [5], "data": { "kind": "cycle", "values": [0.25, -0.5, 0.125] } }
2267
+ },
2268
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 5, 14, 33], "tolerance": 0, "relTolerance": 0 } },
2269
+ "provenance": {
2270
+ "source": "derived",
2271
+ "notes": "Nonoverlapping transposed convolution with batch, contraction, matrix-row and spatial tails. Dyadic inputs keep the CPU reference exact on a compact tensor."
2272
+ }
2273
+ },
2274
+ {
2275
+ "name": "exact_store_tail_b1_c19_m7_k3x2_nobias",
2276
+ "attrs": { "strides": [3, 2] },
2277
+ "tunables": { "GEMM_REG_MIN_TILE_PRODUCT": 0 },
2278
+ "inputs": {
2279
+ "x": {
2280
+ "dtype": "float32",
2281
+ "shape": [1, 19, 5, 13],
2282
+ "data": { "kind": "cycle", "values": [0.25, -0.5, 0.125, 0.75, -1.0] }
2283
+ },
2284
+ "w": {
2285
+ "dtype": "float32",
2286
+ "shape": [19, 7, 3, 2],
2287
+ "data": { "kind": "cycle", "values": [-0.5, 0.25, 0.75, -0.125, 0.5, 0.0625] }
2288
+ }
2289
+ },
2290
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 7, 15, 26], "tolerance": 0, "relTolerance": 0 } },
2291
+ "provenance": {
2292
+ "source": "derived",
2293
+ "notes": "Nonoverlapping transposed convolution with batch, contraction, matrix-row and spatial tails. Dyadic inputs keep the CPU reference exact on a compact tensor."
2294
+ }
2295
+ },
2296
+ {
2297
+ "name": "exact_store_tail_b1_c19_m7_k3x2_bias",
2298
+ "attrs": { "strides": [3, 2] },
2299
+ "tunables": { "GEMM_REG_MIN_TILE_PRODUCT": 0 },
2300
+ "inputs": {
2301
+ "x": {
2302
+ "dtype": "float32",
2303
+ "shape": [1, 19, 5, 13],
2304
+ "data": { "kind": "cycle", "values": [0.25, -0.5, 0.125, 0.75, -1.0] }
2305
+ },
2306
+ "w": {
2307
+ "dtype": "float32",
2308
+ "shape": [19, 7, 3, 2],
2309
+ "data": { "kind": "cycle", "values": [-0.5, 0.25, 0.75, -0.125, 0.5, 0.0625] }
2310
+ },
2311
+ "bias": { "dtype": "float32", "shape": [7], "data": { "kind": "cycle", "values": [0.25, -0.5, 0.125] } }
2312
+ },
2313
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 7, 15, 26], "tolerance": 0, "relTolerance": 0 } },
2314
+ "provenance": {
2315
+ "source": "derived",
2316
+ "notes": "Nonoverlapping transposed convolution with batch, contraction, matrix-row and spatial tails. Dyadic inputs keep the CPU reference exact on a compact tensor."
2317
+ }
2318
+ },
2319
+ {
2320
+ "name": "exact_store_tail_kw2_last_infinity",
2321
+ "attrs": { "strides": [2, 2] },
2322
+ "tunables": { "GEMM_REG_MIN_TILE_PRODUCT": 0 },
2323
+ "inputs": {
2324
+ "x": {
2325
+ "dtype": "float32",
2326
+ "shape": [2, 17, 3, 5],
2327
+ "data": { "kind": "cycle", "values": [0.25, -0.5, 0.125, 0.75, -1.0] }
2328
+ },
2329
+ "w": {
2330
+ "dtype": "float32",
2331
+ "shape": [17, 5, 2, 2],
2332
+ "data": {
2333
+ "kind": "values",
2334
+ "values": [0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, "Infinity"]
2335
+ }
2336
+ },
2337
+ "bias": { "dtype": "float32", "shape": [5], "data": { "kind": "cycle", "values": [0.25, -0.5, 0.125] } }
2338
+ },
2339
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 5, 6, 10], "tolerance": 0, "relTolerance": 0 } },
2340
+ "provenance": {
2341
+ "source": "derived",
2342
+ "notes": "A nonfinite final weight exercises the partial contraction tile and spatial tap mapping without contaminating unrelated output channels or taps."
2343
+ }
2344
+ },
2345
+ {
2346
+ "name": "exact_store_tail_kw3_last_nan",
2347
+ "attrs": { "strides": [2, 3] },
2348
+ "tunables": { "GEMM_REG_MIN_TILE_PRODUCT": 0 },
2349
+ "inputs": {
2350
+ "x": {
2351
+ "dtype": "float32",
2352
+ "shape": [2, 17, 3, 5],
2353
+ "data": { "kind": "cycle", "values": [0.25, -0.5, 0.125, 0.75, -1.0] }
2354
+ },
2355
+ "w": {
2356
+ "dtype": "float32",
2357
+ "shape": [17, 5, 2, 3],
2358
+ "data": {
2359
+ "kind": "values",
2360
+ "values": [0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, 0.125, "NaN"]
2361
+ }
2362
+ },
2363
+ "bias": { "dtype": "float32", "shape": [5], "data": { "kind": "cycle", "values": [0.25, -0.5, 0.125] } }
2364
+ },
2365
+ "outputs": {
2366
+ "y": { "dtype": "float32", "shape": [2, 5, 6, 15], "tolerance": 0, "relTolerance": 0, "allowNaN": true }
2367
+ },
2368
+ "provenance": {
2369
+ "source": "derived",
2370
+ "notes": "A nonfinite final weight exercises the partial contraction tile and spatial tap mapping without contaminating unrelated output channels or taps."
2371
+ }
2372
  }
2373
  ]
2374
  }