Xenova HF Staff commited on
Commit
746b1e6
·
verified ·
1 Parent(s): fb9dcfe

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,80 @@
1
  ---
 
2
  license: apache-2.0
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ library_name: kernels
3
  license: apache-2.0
4
+ tags:
5
+ - kernel
6
+ - webgpu
7
+ - wgsl
8
  ---
9
+ # com.microsoft.FusedMatMul
10
+
11
+ `com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
12
+
13
+ ## Description
14
+
15
+ Matrix product of two N-dimensional tensors `A` and `B`, following NumPy-style matrix-multiplication broadcasting. Supports optional transposition of either operand's last two dimensions, optional batch-dimension transposition, and a scalar `alpha` multiplier. Float32 and float16 are supported; double and bfloat16 are not.
16
+
17
+ See the [ONNX Runtime `FusedMatMul` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.FusedMatMul) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `A` | `A` | `T` | — | — | N-dimensional matrix A. | required |
24
+ | `B` | `B` | `T` | — | — | N-dimensional matrix B. | required |
25
+
26
+ ## Outputs
27
+
28
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
29
+ | --- | --- | --- | --- | --- | --- | --- |
30
+ | `Y` | `Y` | `T` | derived | derived; see description | Matrix-multiplication result whose shape follows NumPy-style rules after applying the requested batch and matrix transpositions. | required |
31
+
32
+ ## Attributes
33
+
34
+ Default values (overridable per request):
35
+
36
+ | Attribute | Default | Description |
37
+ | --- | --- | --- |
38
+ | `alpha` | `1` | Scalar multiplier applied to the product of the input tensors. |
39
+ | `transA` | `0` | When non-zero, transposes `A` on its last two dimensions before multiplication. |
40
+ | `transB` | `0` | When non-zero, transposes `B` on its last two dimensions before multiplication. |
41
+ | `transBatchA` | `0` | When non-zero, transposes `A` on its first dimension and batch dimensions (dim-1 to dim-rank-2) before multiplication. |
42
+ | `transBatchB` | `0` | When non-zero, transposes `B` on its first dimension and batch dimensions (dim-1 to dim-rank-2) before multiplication. |
43
+
44
+ ## Type constraints
45
+
46
+ | Variable | Allowed dtypes |
47
+ | --- | --- |
48
+ | `T` | `float32`, `float16` |
49
+
50
+ ## Device requirements
51
+
52
+ Some implementation variants require `subgroup-matrix` and `subgroups`. These are route-specific capabilities, not package-wide requirements; availability also depends on the request shape and dtype.
53
+
54
+ ## Files
55
+
56
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
57
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
58
+ - [`test.json`](build/webgpu/test.json) — correctness cases
59
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
60
+ - [`fused-matmul-subgroup-matrix.wgsl.jinja`](build/webgpu/fused-matmul-subgroup-matrix.wgsl.jinja)
61
+ - [`matmul-subgroup-matrix-ext.wgsl.jinja`](build/webgpu/matmul-subgroup-matrix-ext.wgsl.jinja)
62
+ - [`matmul-tiled-general-reg.wgsl.jinja`](build/webgpu/matmul-tiled-general-reg.wgsl.jinja)
63
+ - [`matmul-tiled-general.wgsl.jinja`](build/webgpu/matmul-tiled-general.wgsl.jinja)
64
+ - [`matmul-vector-matrix-vec4.wgsl.jinja`](build/webgpu/matmul-vector-matrix-vec4.wgsl.jinja)
65
+
66
+ ## Use with `@huggingface/kernels`
67
+
68
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
69
+ It then allocates the result tensors automatically.
70
+
71
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
72
+
73
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
74
+
75
+ ```js
76
+ import { getKernel } from "@huggingface/kernels";
77
+
78
+ const kernel = await getKernel("webgpu-kernels/com.microsoft.FusedMatMul", { version: 1 });
79
+ const { Y } = await kernel({ A: { data: AData, shape: [3] }, B: { data: BData, shape: [3] } });
80
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "com.microsoft.FusedMatMul",
3
+ "cases": [
4
+ {
5
+ "name": "fusedmatmul-f32-attn-scores-transB-8x512x64",
6
+ "preset": "model",
7
+ "attrs": { "alpha": 0.125, "transB": 1 },
8
+ "inputs": {
9
+ "A": { "shape": [8, 512, 64], "dtype": "float32", "dist": "normal", "seed": 510, "scale": 0.2 },
10
+ "B": { "shape": [8, 512, 64], "dtype": "float32", "dist": "normal", "seed": 511, "scale": 0.2 }
11
+ },
12
+ "outputs": { "Y": { "shape": [8, 512, 512], "dtype": "float32" } },
13
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 8 * 512 * 512 * 64" }] }
14
+ },
15
+ {
16
+ "name": "fusedmatmul-f32-plain-512x2048x512",
17
+ "preset": "model",
18
+ "attrs": { "alpha": 1 },
19
+ "inputs": {
20
+ "A": { "shape": [512, 2048], "dtype": "float32", "dist": "normal", "seed": 520, "scale": 0.1 },
21
+ "B": { "shape": [2048, 512], "dtype": "float32", "dist": "normal", "seed": 521, "scale": 0.1 }
22
+ },
23
+ "outputs": { "Y": { "shape": [512, 512], "dtype": "float32" } },
24
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 512 * 512 * 2048" }] }
25
+ },
26
+ {
27
+ "name": "fusedmatmul-f16-plain-512x2048x512",
28
+ "preset": "model",
29
+ "attrs": { "alpha": 1 },
30
+ "inputs": {
31
+ "A": { "shape": [512, 2048], "dtype": "float16", "dist": "normal", "seed": 530, "scale": 0.1 },
32
+ "B": { "shape": [2048, 512], "dtype": "float16", "dist": "normal", "seed": 531, "scale": 0.1 }
33
+ },
34
+ "outputs": { "Y": { "shape": [512, 512], "dtype": "float16" } },
35
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 512 * 512 * 2048" }] }
36
+ },
37
+ {
38
+ "name": "fusedmatmul-f32-unaligned-500x2000x500",
39
+ "preset": "model",
40
+ "attrs": { "alpha": 1 },
41
+ "inputs": {
42
+ "A": { "shape": [500, 2000], "dtype": "float32", "dist": "normal", "seed": 540, "scale": 0.1 },
43
+ "B": { "shape": [2000, 500], "dtype": "float32", "dist": "normal", "seed": 541, "scale": 0.1 }
44
+ },
45
+ "outputs": { "Y": { "shape": [500, 500], "dtype": "float32" } },
46
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 500 * 500 * 2000" }] }
47
+ },
48
+ {
49
+ "name": "fusedmatmul-f32-unaligned2-384x1536x400",
50
+ "preset": "model",
51
+ "attrs": { "alpha": 1 },
52
+ "inputs": {
53
+ "A": { "shape": [384, 1536], "dtype": "float32", "dist": "normal", "seed": 560, "scale": 0.1 },
54
+ "B": { "shape": [1536, 400], "dtype": "float32", "dist": "normal", "seed": 561, "scale": 0.1 }
55
+ },
56
+ "outputs": { "Y": { "shape": [384, 400], "dtype": "float32" } },
57
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 384 * 400 * 1536" }] }
58
+ },
59
+ {
60
+ "name": "fusedmatmul-f16-aligned-n512-512x2048x512-healthy",
61
+ "preset": "smoke",
62
+ "attrs": { "alpha": 1 },
63
+ "vars": { "M": 512, "K": 2048, "N": 512 },
64
+ "inputs": {
65
+ "A": { "shape": [512, 2048], "dtype": "float16", "dist": "normal", "seed": 600, "scale": 0.1 },
66
+ "B": { "shape": [2048, 512], "dtype": "float16", "dist": "normal", "seed": 601, "scale": 0.1 }
67
+ },
68
+ "outputs": { "Y": { "shape": [512, 512], "dtype": "float16" } },
69
+ "bench": { "primary": true, "metrics": [{ "type": "gflops", "value": "2 * 512 * 512 * 2048" }] }
70
+ },
71
+ {
72
+ "name": "fusedmatmul-f16-unaligned-n500-512x2048x500-cliff",
73
+ "preset": "smoke",
74
+ "attrs": { "alpha": 1 },
75
+ "vars": { "M": 512, "K": 2048, "N": 500 },
76
+ "inputs": {
77
+ "A": { "shape": [512, 2048], "dtype": "float16", "dist": "normal", "seed": 602, "scale": 0.1 },
78
+ "B": { "shape": [2048, 500], "dtype": "float16", "dist": "normal", "seed": 603, "scale": 0.1 }
79
+ },
80
+ "outputs": { "Y": { "shape": [512, 500], "dtype": "float16" } },
81
+ "bench": { "primary": true, "metrics": [{ "type": "gflops", "value": "2 * 512 * 500 * 2048" }] }
82
+ },
83
+ {
84
+ "name": "fusedmatmul-f16-unaligned-k2050-512x2050x512-cliff",
85
+ "preset": "smoke",
86
+ "attrs": { "alpha": 1 },
87
+ "vars": { "M": 512, "K": 2050, "N": 512 },
88
+ "inputs": {
89
+ "A": { "shape": [512, 2050], "dtype": "float16", "dist": "normal", "seed": 604, "scale": 0.1 },
90
+ "B": { "shape": [2050, 512], "dtype": "float16", "dist": "normal", "seed": 605, "scale": 0.1 }
91
+ },
92
+ "outputs": { "Y": { "shape": [512, 512], "dtype": "float16" } },
93
+ "bench": { "primary": true, "metrics": [{ "type": "gflops", "value": "2 * 512 * 512 * 2050" }] }
94
+ },
95
+ {
96
+ "name": "fusedmatmul-f16-decode-gemv-m1-1x2048x512",
97
+ "preset": "smoke",
98
+ "attrs": { "alpha": 1 },
99
+ "vars": { "M": 1, "K": 2048, "N": 512 },
100
+ "inputs": {
101
+ "A": { "shape": [1, 2048], "dtype": "float16", "dist": "normal", "seed": 606, "scale": 0.1 },
102
+ "B": { "shape": [2048, 512], "dtype": "float16", "dist": "normal", "seed": 607, "scale": 0.1 }
103
+ },
104
+ "outputs": { "Y": { "shape": [1, 512], "dtype": "float16" } },
105
+ "bench": { "primary": true, "metrics": [{ "type": "gflops", "value": "2 * 1 * 512 * 2048" }] }
106
+ },
107
+ {
108
+ "name": "fusedmatmul-f16-attn-scores-transB-8x512x64",
109
+ "preset": "smoke",
110
+ "attrs": { "alpha": 0.125, "transB": 1 },
111
+ "vars": { "M": 512, "K": 64, "N": 512 },
112
+ "inputs": {
113
+ "A": { "shape": [8, 512, 64], "dtype": "float16", "dist": "normal", "seed": 608, "scale": 0.2 },
114
+ "B": { "shape": [8, 512, 64], "dtype": "float16", "dist": "normal", "seed": 609, "scale": 0.2 }
115
+ },
116
+ "outputs": { "Y": { "shape": [8, 512, 512], "dtype": "float16" } },
117
+ "bench": { "primary": true, "metrics": [{ "type": "gflops", "value": "2 * 8 * 512 * 512 * 64" }] }
118
+ },
119
+ {
120
+ "name": "fusedmatmul-f16-broadcast-batch-rank4x3-2x8x512x64-tiled",
121
+ "preset": "smoke",
122
+ "attrs": { "alpha": 0.125, "transB": 1 },
123
+ "vars": { "M": 512, "K": 64, "N": 512 },
124
+ "inputs": {
125
+ "A": { "shape": [2, 8, 512, 64], "dtype": "float16", "dist": "normal", "seed": 610, "scale": 0.2 },
126
+ "B": { "shape": [8, 512, 64], "dtype": "float16", "dist": "normal", "seed": 611, "scale": 0.2 }
127
+ },
128
+ "outputs": { "Y": { "shape": [2, 8, 512, 512], "dtype": "float16" } },
129
+ "bench": { "primary": true, "metrics": [{ "type": "gflops", "value": "2 * 2 * 8 * 512 * 512 * 64" }] }
130
+ },
131
+ {
132
+ "name": "fusedmatmul-f32-decode-gemv-m1-k4096-n4096-pathology",
133
+ "preset": "stress",
134
+ "attrs": { "alpha": 1 },
135
+ "vars": { "dtype": "float32", "M": 1, "K": 4096, "N": 4096 },
136
+ "inputs": {
137
+ "A": { "shape": [1, 4096], "dtype": "float32", "dist": "normal", "seed": 740, "scale": 0.1 },
138
+ "B": { "shape": [4096, 4096], "dtype": "float32", "dist": "normal", "seed": 741, "scale": 0.05 }
139
+ },
140
+ "outputs": { "Y": { "shape": [1, 4096], "dtype": "float32", "dist": "empty" } },
141
+ "bench": {
142
+ "primary": true,
143
+ "metrics": [
144
+ {
145
+ "type": "bandwidth",
146
+ "value": "(args.M * args.K + args.K * args.N + args.M * args.N) * dtypeBytes(args.dtype)"
147
+ }
148
+ ]
149
+ }
150
+ },
151
+ {
152
+ "name": "fusedmatmul-f32-rank4-by-rank2-shared-weight-b2h8-m128-k64-n128-pathology",
153
+ "preset": "stress",
154
+ "attrs": { "alpha": 1 },
155
+ "vars": { "dtype": "float32", "M": 128, "K": 64, "N": 128 },
156
+ "inputs": {
157
+ "A": { "shape": [2, 8, 128, 64], "dtype": "float32", "dist": "normal", "seed": 742, "scale": 0.2 },
158
+ "B": { "shape": [64, 128], "dtype": "float32", "dist": "normal", "seed": 743, "scale": 0.2 }
159
+ },
160
+ "outputs": { "Y": { "shape": [2, 8, 128, 128], "dtype": "float32", "dist": "empty" } },
161
+ "bench": { "primary": true, "metrics": [{ "type": "gflops", "value": "2 * numel(shapes.Y) * args.K" }] }
162
+ },
163
+ {
164
+ "name": "fusedmatmul-f16-rank4-by-rank2-shared-weight-b2h8-m512-k2048-n512-pathology",
165
+ "preset": "stress",
166
+ "provenance": {
167
+ "notes": "Production-scale batched projection with a shared rank-2 weight. This common numpy.matmul broadcast shape is excluded from the subgroup-matrix rank combinations and locks the register-blocked portable fallback that replaced the lower-throughput generic tiled kernel."
168
+ },
169
+ "attrs": { "alpha": 1 },
170
+ "vars": { "dtype": "float16", "M": 512, "K": 2048, "N": 512 },
171
+ "inputs": {
172
+ "A": { "shape": [2, 8, 512, 2048], "dtype": "float16", "dist": "normal", "seed": 744, "scale": 0.05 },
173
+ "B": { "shape": [2048, 512], "dtype": "float16", "dist": "normal", "seed": 745, "scale": 0.05 }
174
+ },
175
+ "outputs": { "Y": { "shape": [2, 8, 512, 512], "dtype": "float16", "dist": "empty" } },
176
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * numel(shapes.Y) * args.K" }] }
177
+ },
178
+ {
179
+ "name": "fusedmatmul-f16-transbatch-a-forces-tiled-8x512x2048x512-stress",
180
+ "preset": "stress",
181
+ "attrs": { "alpha": 1, "transBatchA": 1 },
182
+ "vars": { "batch": 8, "M": 512, "K": 2048, "N": 512 },
183
+ "inputs": {
184
+ "A": { "shape": [512, 8, 2048], "dtype": "float16", "dist": "normal", "seed": 720, "scale": 0.05 },
185
+ "B": { "shape": [8, 2048, 512], "dtype": "float16", "dist": "normal", "seed": 721, "scale": 0.05 }
186
+ },
187
+ "outputs": { "Y": { "shape": [8, 512, 512], "dtype": "float16", "dist": "empty" } },
188
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 8 * 512 * 512 * 2048" }] }
189
+ },
190
+ {
191
+ "name": "fusedmatmul-f16-transbatch-b-8x512x2048x512-pathology",
192
+ "preset": "stress",
193
+ "provenance": {
194
+ "notes": "The operator's valid transBatchB semantic at a production projection size. No subgroup-matrix or specialized trans-batch-B variant accepts this layout, so it selects the lower-throughput generic tiled kernel."
195
+ },
196
+ "attrs": { "alpha": 1, "transBatchB": 1 },
197
+ "vars": { "batch": 8, "M": 512, "K": 2048, "N": 512 },
198
+ "inputs": {
199
+ "A": { "shape": [8, 512, 2048], "dtype": "float16", "dist": "normal", "seed": 722, "scale": 0.05 },
200
+ "B": { "shape": [2048, 8, 512], "dtype": "float16", "dist": "normal", "seed": 723, "scale": 0.05 }
201
+ },
202
+ "outputs": { "Y": { "shape": [8, 512, 512], "dtype": "float16", "dist": "empty" } },
203
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 8 * 512 * 512 * 2048" }] }
204
+ },
205
+ {
206
+ "name": "fusedmatmul-f16-broadcast-batch-forces-tiled-1x8x512x2048x512-stress",
207
+ "preset": "stress",
208
+ "attrs": { "alpha": 1 },
209
+ "inputs": {
210
+ "A": { "shape": [1, 8, 512, 2048], "dtype": "float16", "dist": "normal", "seed": 730, "scale": 0.05 },
211
+ "B": { "shape": [8, 2048, 512], "dtype": "float16", "dist": "normal", "seed": 731, "scale": 0.05 }
212
+ },
213
+ "outputs": { "Y": { "shape": [1, 8, 512, 512], "dtype": "float16", "dist": "empty" } },
214
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 8 * 512 * 512 * 2048" }] }
215
+ }
216
+ ]
217
+ }
build/webgpu/fused-matmul-subgroup-matrix.wgsl.jinja ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // com.microsoft.FusedMatMul subgroup-matrix specialization: Y = alpha * A @ op(B),
2
+ // op(B) = B^T when transB. Dense batches map through workgroup_id.z, and
3
+ // M-tail rows are guarded by row_limit. transA uses the tiled specialization.
4
+ // Alignment gates keep K % 32 == 0 and N % 64 == 0 so subgroupMatrixLoad never
5
+ // sees partial 8x8 tiles. The batch is required to match between A and B
6
+ // (no broadcast) because a_base/b_base both index by the same workgroup_id.z.
7
+ {% if usesF16 %}
8
+ enable f16;
9
+ {% endif %}
10
+ enable subgroups;
11
+ {% if pinSubgroupSize32 %}
12
+ enable subgroup_size_control;
13
+ {% endif %}
14
+ enable chromium_experimental_subgroup_matrix;
15
+ diagnostic(off, chromium.subgroup_matrix_uniformity);
16
+
17
+ {{ env.wgsl.resourceDeclarations }}
18
+
19
+ {% set operandScalar = fScalar %}
20
+ {% set accScalar = "f32" %}
21
+
22
+ const M: u32 = {{ M }}u;
23
+ const K: u32 = {{ K }}u;
24
+ const N: u32 = {{ N }}u;
25
+ {% if transBatchA %}
26
+ const BATCH_COUNT: u32 = {{ batchCount }}u;
27
+ const A_BATCH_STRIDE: u32 = K;
28
+ const A_M_STRIDE: u32 = BATCH_COUNT * K;
29
+ {% else %}
30
+ const A_BATCH_STRIDE: u32 = M * K;
31
+ {% if not transA %}
32
+ const A_M_STRIDE: u32 = K;
33
+ {% endif %}
34
+ {% endif %}
35
+ const B_BATCH_STRIDE: u32 = K * N;
36
+ const C_BATCH_STRIDE: u32 = M * N;
37
+ const ALPHA: {{ accScalar }} = {{ accScalar }}({{ source.alpha }});
38
+ const TILE_COLS: u32 = 64u;
39
+ const TILE_ROWS: u32 = 32u;
40
+ const TILE_K: u32 = 32u;
41
+ const SUB_COLS: u32 = 32u;
42
+ const SUB_ROWS: u32 = 16u;
43
+
44
+ var<workgroup> tile_A: array<{{ operandScalar }}, 32 * 32>;
45
+ var<workgroup> tile_B: array<{{ operandScalar }}, 64 * 32>;
46
+ var<workgroup> scratch: array<array<array<{{ accScalar }}, 64>, 4>, 4>;
47
+
48
+ fn loadSHMA(a_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
49
+ let a_global = tile_base + row;
50
+ let col = c_idx * 8u;
51
+ for (var col_offset = 0u; col_offset < 8u; col_offset = col_offset + 1u) {
52
+ let k = k_idx + col + col_offset;
53
+ if (a_global < M) {
54
+ {% if transA %}
55
+ // op(A) = A^T: A stored [.., K, M], so op(A)[a_global, k] = A[k, a_global].
56
+ tile_A[row * TILE_K + col + col_offset] = {{ operandScalar }}(a[a_base + k * M + a_global]);
57
+ {% else %}
58
+ tile_A[row * TILE_K + col + col_offset] = {{ operandScalar }}(a[a_base + a_global * A_M_STRIDE + k]);
59
+ {% endif %}
60
+ } else {
61
+ tile_A[row * TILE_K + col + col_offset] = {{ operandScalar }}(0.0);
62
+ }
63
+ }
64
+ }
65
+
66
+ fn loadSHMB(b_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
67
+ let b_col = tile_base + row;
68
+ let col = c_idx * 16u;
69
+ for (var i = 0u; i < 16u; i = i + 1u) {
70
+ let k = k_idx + col + i;
71
+ {% if transB %}
72
+ // op(B) = B^T: B stored [.., N, K], so op(B)[k, b_col] = B[b_col, k].
73
+ tile_B[row * TILE_K + col + i] = {{ operandScalar }}(b[b_base + b_col * K + k]);
74
+ {% else %}
75
+ tile_B[row * TILE_K + col + i] = {{ operandScalar }}(b[b_base + k * N + b_col]);
76
+ {% endif %}
77
+ }
78
+ }
79
+
80
+ fn storeOutput(offset: u32, row: u32, col: u32, src_slot: u32, row_limit: i32) {
81
+ if (row_limit > 0 && row < u32(row_limit)) {
82
+ let col2 = col + 1u;
83
+ y[offset + row * N + col] = {{ outScalar }}(ALPHA * scratch[src_slot][0][row * 8u + col]);
84
+ y[offset + row * N + col + 8u] = {{ outScalar }}(ALPHA * scratch[src_slot][1][row * 8u + col]);
85
+ y[offset + row * N + col + 16u] = {{ outScalar }}(ALPHA * scratch[src_slot][2][row * 8u + col]);
86
+ y[offset + row * N + col + 24u] = {{ outScalar }}(ALPHA * scratch[src_slot][3][row * 8u + col]);
87
+
88
+ y[offset + row * N + col2] = {{ outScalar }}(ALPHA * scratch[src_slot][0][row * 8u + col2]);
89
+ y[offset + row * N + col2 + 8u] = {{ outScalar }}(ALPHA * scratch[src_slot][1][row * 8u + col2]);
90
+ y[offset + row * N + col2 + 16u] = {{ outScalar }}(ALPHA * scratch[src_slot][2][row * 8u + col2]);
91
+ y[offset + row * N + col2 + 24u] = {{ outScalar }}(ALPHA * scratch[src_slot][3][row * 8u + col2]);
92
+ }
93
+ }
94
+
95
+ @compute @workgroup_size(128, 1, 1){{ " @subgroup_size(32)" if pinSubgroupSize32 else "" }}
96
+ fn main(
97
+ @builtin(workgroup_id) workgroup_id: vec3<u32>,
98
+ @builtin(local_invocation_index) local_idx: u32,
99
+ @builtin(subgroup_invocation_id) sg_id: u32,
100
+ @builtin(subgroup_size) sg_size: u32
101
+ ) {
102
+ let batch = workgroup_id.z;
103
+ let a_base = batch * A_BATCH_STRIDE;
104
+ let b_base = batch * B_BATCH_STRIDE;
105
+ let c_base = batch * C_BATCH_STRIDE;
106
+ let a_global_base = workgroup_id.y * TILE_ROWS;
107
+ let b_global_base = workgroup_id.x * TILE_COLS;
108
+
109
+ let subtile_id = local_idx / sg_size;
110
+ let subtile_idx = subtile_id / 2u;
111
+ let subtile_idy = subtile_id % 2u;
112
+ let base_A = subtile_idy * SUB_ROWS;
113
+ let base_B = subtile_idx * SUB_COLS;
114
+
115
+ var matC00: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
116
+ var matC01: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
117
+ var matC02: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
118
+ var matC03: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
119
+ var matC10: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
120
+ var matC11: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
121
+ var matC12: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
122
+ var matC13: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
123
+
124
+ for (var kidx = 0u; kidx < K; kidx = kidx + TILE_K) {
125
+ loadSHMA(a_base, a_global_base, kidx, local_idx / 4u, local_idx % 4u);
126
+ loadSHMB(b_base, b_global_base, kidx, local_idx / 2u, local_idx % 2u);
127
+ workgroupBarrier();
128
+
129
+ for (var step = 0u; step < TILE_K; step = step + 8u) {
130
+ let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
131
+ var matA0: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset, false, TILE_K);
132
+ var matA1: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset + 8u * TILE_K, false, TILE_K);
133
+
134
+ let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
135
+ var matB0: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset, true, TILE_K);
136
+ var matB1: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 8u * TILE_K, true, TILE_K);
137
+ var matB2: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 16u * TILE_K, true, TILE_K);
138
+ var matB3: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 24u * TILE_K, true, TILE_K);
139
+
140
+ matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
141
+ matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
142
+ matC02 = subgroupMatrixMultiplyAccumulate(matA0, matB2, matC02);
143
+ matC03 = subgroupMatrixMultiplyAccumulate(matA0, matB3, matC03);
144
+ matC10 = subgroupMatrixMultiplyAccumulate(matA1, matB0, matC10);
145
+ matC11 = subgroupMatrixMultiplyAccumulate(matA1, matB1, matC11);
146
+ matC12 = subgroupMatrixMultiplyAccumulate(matA1, matB2, matC12);
147
+ matC13 = subgroupMatrixMultiplyAccumulate(matA1, matB3, matC13);
148
+ }
149
+ workgroupBarrier();
150
+ }
151
+
152
+ // The four scratch banks are reused across the two row-groups, and each is written
153
+ // by a collective subgroupMatrixStore then read across lanes by storeOutput. Barriers
154
+ // give the reads visibility of the store and stop the second row-group's store from
155
+ // clobbering the first's still-in-flight readback when a partial final M-tile
156
+ // diverges storeOutput's guard. Without both barriers the last valid row can be corrupted.
157
+ subgroupMatrixStore(&scratch[subtile_id][0], 0u, matC00, false, 8u);
158
+ subgroupMatrixStore(&scratch[subtile_id][1], 0u, matC01, false, 8u);
159
+ subgroupMatrixStore(&scratch[subtile_id][2], 0u, matC02, false, 8u);
160
+ subgroupMatrixStore(&scratch[subtile_id][3], 0u, matC03, false, 8u);
161
+ workgroupBarrier();
162
+ let row = sg_id / 4u;
163
+ let col = (sg_id % 4u) * 2u;
164
+ var matrix_c_offset = c_base + (a_global_base + base_A) * N + b_global_base + base_B;
165
+ var row_limit = i32(M) - i32(a_global_base + base_A);
166
+ storeOutput(matrix_c_offset, row, col, subtile_id, row_limit);
167
+ workgroupBarrier();
168
+
169
+ subgroupMatrixStore(&scratch[subtile_id][0], 0u, matC10, false, 8u);
170
+ subgroupMatrixStore(&scratch[subtile_id][1], 0u, matC11, false, 8u);
171
+ subgroupMatrixStore(&scratch[subtile_id][2], 0u, matC12, false, 8u);
172
+ subgroupMatrixStore(&scratch[subtile_id][3], 0u, matC13, false, 8u);
173
+ workgroupBarrier();
174
+ matrix_c_offset = matrix_c_offset + 8u * N;
175
+ row_limit = i32(M) - i32(a_global_base + base_A + 8u);
176
+ storeOutput(matrix_c_offset, row, col, subtile_id, row_limit);
177
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "com.microsoft",
3
+ "name": "FusedMatMul",
4
+ "sinceVersion": 1,
5
+ "description": "Matrix product of two N-dimensional tensors `A` and `B`, following NumPy-style matrix-multiplication broadcasting. Supports optional transposition of either operand's last two dimensions, optional batch-dimension transposition, and a scalar `alpha` multiplier. Float32 and float16 are supported; double and bfloat16 are not.",
6
+ "inputs": [
7
+ { "role": "A", "dtype": "T", "description": "N-dimensional matrix A." },
8
+ { "role": "B", "dtype": "T", "description": "N-dimensional matrix B." }
9
+ ],
10
+ "outputs": [
11
+ {
12
+ "role": "Y",
13
+ "dtype": "T",
14
+ "rank": "max(ranks.A, ranks.B) - (1 if ranks.A == 1 or ranks.B == 1 else 0)",
15
+ "shape": "matmulShape(logicalAShape, logicalBShape)",
16
+ "description": "Matrix-multiplication result whose shape follows NumPy-style rules after applying the requested batch and matrix transpositions."
17
+ }
18
+ ],
19
+ "attributes": { "alpha": 1, "transA": 0, "transB": 0, "transBatchA": 0, "transBatchB": 0 },
20
+ "attributeDescriptions": {
21
+ "alpha": "Scalar multiplier applied to the product of the input tensors.",
22
+ "transA": "When non-zero, transposes `A` on its last two dimensions before multiplication.",
23
+ "transB": "When non-zero, transposes `B` on its last two dimensions before multiplication.",
24
+ "transBatchA": "When non-zero, transposes `A` on its first dimension and batch dimensions (dim-1 to dim-rank-2) before multiplication.",
25
+ "transBatchB": "When non-zero, transposes `B` on its first dimension and batch dimensions (dim-1 to dim-rank-2) before multiplication."
26
+ },
27
+ "typeConstraints": { "T": ["float32", "float16"] },
28
+ "args": {
29
+ "A": { "kind": "tensor", "semantic": "A", "role": "input" },
30
+ "B": { "kind": "tensor", "semantic": "B", "role": "input" },
31
+ "Y": { "kind": "tensor", "semantic": "Y", "role": "output" }
32
+ },
33
+ "tunables": { "TILED_REG_MIN_WORKGROUPS": 64, "GEMV_TARGET_BLOCKS": 512 },
34
+ "bindingSets": {
35
+ "matrix": [
36
+ { "name": "a", "arg": "A", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
37
+ { "name": "b", "arg": "B", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
38
+ { "name": "y", "arg": "Y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" }
39
+ ],
40
+ "gemvF32": [
41
+ { "name": "a", "arg": "A", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
42
+ {
43
+ "name": "b",
44
+ "arg": "B",
45
+ "semantic": "B",
46
+ "buffer": { "type": "read-only-storage" },
47
+ "elementType": "vec4<f32>"
48
+ },
49
+ { "name": "c", "arg": "Y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "vec4<f32>" },
50
+ {
51
+ "name": "params",
52
+ "semantic": "kernel.params",
53
+ "buffer": { "type": "uniform" },
54
+ "struct": {
55
+ "name": "Params",
56
+ "fields": [
57
+ { "name": "K", "type": "u32", "value": "dim(shapes.A, 1)" },
58
+ { "name": "N4", "type": "u32", "value": "dim(shapes.B, 1) / 4" }
59
+ ]
60
+ }
61
+ }
62
+ ]
63
+ },
64
+ "derive": {
65
+ "gemvWorkgroups": "ceilDiv(dim(shapes.B, 1), 128)",
66
+ "gemvSliceCap": "min(32, device.limits.maxComputeWorkgroupSizeY, floor(device.limits.maxComputeInvocationsPerWorkgroup / 32), floor(device.limits.maxComputeWorkgroupStorageSize / 512))",
67
+ "gemvSlices": "max(1, min(gemvSliceCap, max(8, pow2ceil(ceilDiv(tunables.GEMV_TARGET_BLOCKS, gemvWorkgroups)))))",
68
+ "batchMovedAShape": "moveAxis(shapes.A, 0, -2) if attrs.transBatchA != 0 else shapes.A",
69
+ "batchMovedBShape": "moveAxis(shapes.B, 0, -2) if attrs.transBatchB != 0 else shapes.B",
70
+ "logicalAShape": "moveAxis(batchMovedAShape, -1, -2) if attrs.transA != 0 and ranks.A > 1 else batchMovedAShape",
71
+ "logicalBShape": "moveAxis(batchMovedBShape, -1, -2) if attrs.transB != 0 and ranks.B > 1 else batchMovedBShape",
72
+ "transBatchContract": "(attrs.transBatchA == 0 and attrs.transBatchB == 0) or (ranks.A == ranks.B and ranks.A >= 3)",
73
+ "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
74
+ "canPinSubgroupSize32": "device.features.has(\"subgroups\") and device.features.has(\"subgroup-size-control\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize <= 32 and device.adapterInfo.subgroupMaxSize >= 32",
75
+ "pinSubgroupSize32": "canPinSubgroupSize32 and not wave32Adapter",
76
+ "wave32Effective": "wave32Adapter or pinSubgroupSize32"
77
+ },
78
+ "variants": [
79
+ {
80
+ "id": "f32_m1_gemv_vec4",
81
+ "priority": 30,
82
+ "when": ["dtypes.T == \"f32\"", "attrs.alpha == 1", "attrs.transA == 0", "attrs.transB == 0", "attrs.transBatchA == 0", "attrs.transBatchB == 0", "ranks.A == 2", "ranks.B == 2", "ranks.Y == 2", "dim(shapes.A, 0) == 1", "dim(shapes.Y, 0) == 1", "dim(shapes.A, 1) == dim(shapes.B, 0)", "dim(shapes.Y, 1) == dim(shapes.B, 1)", "dim(shapes.B, 1) > 0", "dim(shapes.B, 1) % 4 == 0", "ceil(dim(shapes.B, 1) / 128) <= device.limits.maxComputeWorkgroupsPerDimension"],
83
+ "constants": { "T": "\"f32\"", "usesF16": false, "gemvSlices": "gemvSlices" },
84
+ "passes": [
85
+ {
86
+ "id": "main",
87
+ "name": "FusedMatMul.F32M1GemvVec4",
88
+ "shader": "matmul-vector-matrix-vec4.wgsl.jinja",
89
+ "bindings": "gemvF32",
90
+ "dispatch": { "x": "ceil(dim(shapes.B, 1) / 128)" }
91
+ }
92
+ ]
93
+ },
94
+ {
95
+ "id": "subgroup_matrix_tail_broadcast",
96
+ "priority": 11,
97
+ "requires": {
98
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
99
+ "subgroupMatrixConfigs": [{ "componentType": "f16", "M": 8, "N": 8, "K": 8 }]
100
+ },
101
+ "when": ["dtypes.T == \"f16\"", "f16Ok(dtypes.T)", "attrs.transA == 0", "attrs.transB == 0", "attrs.transBatchA == 0", "attrs.transBatchB == 0", "((ranks.A == 2 and ranks.B == 2 and ranks.Y == 2) or (ranks.A == 4 and ranks.B == 3 and ranks.Y == 4 and dim(shapes.Y, 0) == dim(shapes.A, 0) and (dim(shapes.A, 1) == dim(shapes.B, 0) or dim(shapes.A, 1) == 1 or dim(shapes.B, 0) == 1) and dim(shapes.Y, 1) == max(dim(shapes.A, 1), dim(shapes.B, 0))))", "dim(shapes.A, ranks.A - 1) == dim(shapes.B, ranks.B - 2)", "dim(shapes.A, ranks.A - 2) >= 32", "dim(shapes.A, ranks.A - 1) >= 32", "dim(shapes.B, ranks.B - 1) >= 64", "dim(shapes.Y, ranks.Y - 2) == dim(shapes.A, ranks.A - 2)", "dim(shapes.Y, ranks.Y - 1) == dim(shapes.B, ranks.B - 1)", "ceil(dim(shapes.B, ranks.B - 1) / 64) <= device.limits.maxComputeWorkgroupsPerDimension", "ceil(dim(shapes.A, ranks.A - 2) / 32) <= device.limits.maxComputeWorkgroupsPerDimension", "numel(shapes.Y) / (dim(shapes.A, ranks.A - 2) * dim(shapes.B, ranks.B - 1)) <= device.limits.maxComputeWorkgroupsPerDimension", "wave32Effective"],
102
+ "constants": {
103
+ "hasBias": false,
104
+ "usesF16": true,
105
+ "fScalar": "\"f16\"",
106
+ "outScalar": "\"f16\"",
107
+ "scalar": "dtypes.T",
108
+ "generalAddressing": true,
109
+ "tailSafe": "dim(shapes.A, ranks.A - 1) % 32 != 0 or dim(shapes.B, ranks.B - 1) % 64 != 0",
110
+ "outputBuffer": "\"y\"",
111
+ "alpha": "attrs.alpha",
112
+ "M": "dim(shapes.A, ranks.A - 2)",
113
+ "K": "dim(shapes.A, ranks.A - 1)",
114
+ "N": "dim(shapes.B, ranks.B - 1)",
115
+ "batchCount": "numel(shapes.Y) / (dim(shapes.A, ranks.A - 2) * dim(shapes.B, ranks.B - 1))"
116
+ },
117
+ "passes": [
118
+ {
119
+ "id": "main",
120
+ "name": "FusedMatMul.SubgroupMatrixTailBroadcast",
121
+ "source": {
122
+ "shader": "matmul-subgroup-matrix-ext.wgsl.jinja",
123
+ "inputs": { "aShape": "shapes.A", "bShape": "shapes.B", "aRank": "ranks.A", "bRank": "ranks.B" }
124
+ },
125
+ "bindings": "matrix",
126
+ "dispatch": { "x": "ceil(constants.N / 64)", "y": "ceil(constants.M / 32)", "z": "constants.batchCount" }
127
+ }
128
+ ]
129
+ },
130
+ {
131
+ "id": "subgroup_matrix",
132
+ "priority": 10,
133
+ "requires": {
134
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
135
+ "subgroupMatrixConfigs": [
136
+ { "componentType": "f16", "M": 8, "N": 8, "K": 8 },
137
+ { "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
138
+ ]
139
+ },
140
+ "when": ["f16Ok(dtypes.T)", "attrs.transBatchA == 0 or (attrs.transA == 0 and ranks.A == 3)", "attrs.transBatchB == 0", "ranks.A >= 2", "ranks.B == ranks.A", "ranks.Y == ranks.A", "(dim(shapes.A, ranks.A - 2) if attrs.transA != 0 else dim(shapes.A, ranks.A - 1)) == (dim(shapes.B, ranks.B - 1) if attrs.transB != 0 else dim(shapes.B, ranks.B - 2))", "(dim(shapes.A, ranks.A - 1) if attrs.transA != 0 else (dim(shapes.A, 0) if attrs.transBatchA != 0 else dim(shapes.A, ranks.A - 2))) >= 32", "(dim(shapes.A, ranks.A - 2) if attrs.transA != 0 else dim(shapes.A, ranks.A - 1)) % 32 == 0", "(dim(shapes.B, ranks.B - 2) if attrs.transB != 0 else dim(shapes.B, ranks.B - 1)) % 64 == 0", "(ranks.A == 2 or (ranks.A == 3 and (dim(shapes.A, 1) if attrs.transBatchA != 0 else dim(shapes.A, 0)) == dim(shapes.B, 0) and dim(shapes.Y, 0) == dim(shapes.B, 0)) or (ranks.A == 4 and dim(shapes.A, 0) == dim(shapes.B, 0) and dim(shapes.A, 1) == dim(shapes.B, 1) and dim(shapes.Y, 0) == dim(shapes.A, 0) and dim(shapes.Y, 1) == dim(shapes.A, 1)))", "dim(shapes.Y, ranks.Y - 2) == (dim(shapes.A, ranks.A - 1) if attrs.transA != 0 else (dim(shapes.A, 0) if attrs.transBatchA != 0 else dim(shapes.A, ranks.A - 2)))", "dim(shapes.Y, ranks.Y - 1) == (dim(shapes.B, ranks.B - 2) if attrs.transB != 0 else dim(shapes.B, ranks.B - 1))", "ceil((dim(shapes.B, ranks.B - 2) if attrs.transB != 0 else dim(shapes.B, ranks.B - 1)) / 64) <= device.limits.maxComputeWorkgroupsPerDimension", "ceil((dim(shapes.A, ranks.A - 1) if attrs.transA != 0 else (dim(shapes.A, 0) if attrs.transBatchA != 0 else dim(shapes.A, ranks.A - 2))) / 32) <= device.limits.maxComputeWorkgroupsPerDimension", "numel(shapes.Y) / ((dim(shapes.A, ranks.A - 1) if attrs.transA != 0 else (dim(shapes.A, 0) if attrs.transBatchA != 0 else dim(shapes.A, ranks.A - 2))) * (dim(shapes.B, ranks.B - 2) if attrs.transB != 0 else dim(shapes.B, ranks.B - 1))) <= device.limits.maxComputeWorkgroupsPerDimension", "wave32Effective"],
141
+ "constants": {
142
+ "usesF16": "dtypes.T == \"f16\"",
143
+ "fScalar": "\"f16\" if dtypes.T == \"f16\" else \"f32\"",
144
+ "outScalar": "\"f16\" if dtypes.T == \"f16\" else \"f32\"",
145
+ "scalar": "dtypes.T",
146
+ "transA": "attrs.transA != 0",
147
+ "transB": "attrs.transB != 0",
148
+ "transBatchA": "attrs.transBatchA != 0",
149
+ "M": "(dim(shapes.A, ranks.A - 1) if attrs.transA != 0 else (dim(shapes.A, 0) if attrs.transBatchA != 0 else dim(shapes.A, ranks.A - 2)))",
150
+ "K": "(dim(shapes.A, ranks.A - 2) if attrs.transA != 0 else dim(shapes.A, ranks.A - 1))",
151
+ "N": "(dim(shapes.B, ranks.B - 2) if attrs.transB != 0 else dim(shapes.B, ranks.B - 1))",
152
+ "batchCount": "numel(shapes.Y) / ((dim(shapes.A, ranks.A - 1) if attrs.transA != 0 else (dim(shapes.A, 0) if attrs.transBatchA != 0 else dim(shapes.A, ranks.A - 2))) * (dim(shapes.B, ranks.B - 2) if attrs.transB != 0 else dim(shapes.B, ranks.B - 1)))"
153
+ },
154
+ "passes": [
155
+ {
156
+ "id": "main",
157
+ "name": "FusedMatMul.SubgroupMatrix",
158
+ "source": { "shader": "fused-matmul-subgroup-matrix.wgsl.jinja", "inputs": { "alpha": "attrs.alpha" } },
159
+ "bindings": "matrix",
160
+ "dispatch": {
161
+ "x": "ceil(constants.N / 64)",
162
+ "y": "ceil(constants.M / 32)",
163
+ "z": "numel(shapes.Y) / (constants.M * constants.N)"
164
+ }
165
+ }
166
+ ]
167
+ },
168
+ {
169
+ "id": "broadcast_rank4_tiled_reg",
170
+ "priority": 6,
171
+ "when": ["f16Ok(dtypes.T)", "attrs.transA == 0", "attrs.transB == 0", "attrs.transBatchA == 0", "attrs.transBatchB == 0", "ranks.A == 4", "(ranks.B == 2 or ranks.B == 3)", "ranks.Y == 4", "dim(shapes.Y, 0) == dim(shapes.A, 0)", "(ranks.B == 2 or dim(shapes.A, 1) == dim(shapes.B, 0) or dim(shapes.A, 1) == 1 or dim(shapes.B, 0) == 1)", "dim(shapes.Y, 1) == (dim(shapes.A, 1) if ranks.B == 2 else max(dim(shapes.A, 1), dim(shapes.B, 0)))", "dim(shapes.A, 3) == dim(shapes.B, ranks.B - 2)", "dim(shapes.Y, 2) == dim(shapes.A, 2)", "dim(shapes.Y, 3) == dim(shapes.B, ranks.B - 1)", "dim(shapes.A, 2) >= 64", "dim(shapes.A, 3) >= 32", "dim(shapes.B, ranks.B - 1) >= 64", "ceil(dim(shapes.B, ranks.B - 1) / 64) <= device.limits.maxComputeWorkgroupsPerDimension", "ceil(dim(shapes.A, 2) / 64) <= device.limits.maxComputeWorkgroupsPerDimension", "numel(shapes.Y) / (dim(shapes.A, 2) * dim(shapes.B, ranks.B - 1)) <= device.limits.maxComputeWorkgroupsPerDimension"],
172
+ "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
173
+ "passes": [
174
+ {
175
+ "id": "main",
176
+ "name": "FusedMatMul.BroadcastRank4TiledReg",
177
+ "source": {
178
+ "shader": "matmul-tiled-general-reg.wgsl.jinja",
179
+ "inputs": {
180
+ "aShape": "shapes.A",
181
+ "bShape": "shapes.B",
182
+ "alpha": "attrs.alpha",
183
+ "aRank": "ranks.A",
184
+ "bRank": "ranks.B",
185
+ "transBatchA": "false"
186
+ }
187
+ },
188
+ "bindings": "matrix",
189
+ "dispatch": {
190
+ "x": "ceil(dim(shapes.B, ranks.B - 1) / 64)",
191
+ "y": "ceil(dim(shapes.A, 2) / 64)",
192
+ "z": "numel(shapes.Y) / (dim(shapes.A, 2) * dim(shapes.B, ranks.B - 1))"
193
+ }
194
+ }
195
+ ]
196
+ },
197
+ {
198
+ "id": "plain_rank2_tiled_reg",
199
+ "description": "Register-blocked rank-2 Y = alpha * A @ B for the tier without subgroup matrices. It is the non-transposed rung the two specialised register-blocked variants beside it never covered, so plain rank-2 shapes fell to the scalar-indexed tile.",
200
+ "priority": 4,
201
+ "when": ["f16Ok(dtypes.T)", "attrs.transA == 0", "attrs.transB == 0", "attrs.transBatchA == 0", "attrs.transBatchB == 0", "ranks.A == 2", "ranks.B == 2", "ranks.Y == 2", "dim(shapes.A, 1) == dim(shapes.B, 0)", "dim(shapes.Y, 0) == dim(shapes.A, 0)", "dim(shapes.Y, 1) == dim(shapes.B, 1)", "dim(shapes.A, 0) >= 64", "dim(shapes.A, 1) >= 32", "dim(shapes.B, 1) >= 64", "ceil(dim(shapes.A, 0) / 64) * ceil(dim(shapes.B, 1) / 64) >= tunables.TILED_REG_MIN_WORKGROUPS", "ceil(dim(shapes.B, 1) / 64) <= device.limits.maxComputeWorkgroupsPerDimension", "ceil(dim(shapes.A, 0) / 64) <= device.limits.maxComputeWorkgroupsPerDimension"],
202
+ "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
203
+ "passes": [
204
+ {
205
+ "id": "main",
206
+ "name": "FusedMatMul.PlainRank2TiledReg",
207
+ "source": {
208
+ "shader": "matmul-tiled-general-reg.wgsl.jinja",
209
+ "inputs": {
210
+ "aShape": "shapes.A",
211
+ "bShape": "shapes.B",
212
+ "alpha": "attrs.alpha",
213
+ "aRank": "ranks.A",
214
+ "bRank": "ranks.B",
215
+ "transBatchA": "false"
216
+ }
217
+ },
218
+ "bindings": "matrix",
219
+ "dispatch": { "x": "ceil(dim(shapes.B, 1) / 64)", "y": "ceil(dim(shapes.A, 0) / 64)", "z": 1 }
220
+ }
221
+ ]
222
+ },
223
+ {
224
+ "id": "transbatch_a_tiled_reg",
225
+ "priority": 5,
226
+ "when": ["f16Ok(dtypes.T)", "attrs.transBatchA != 0", "attrs.transBatchB == 0", "attrs.transA == 0", "attrs.transB == 0", "ranks.A == 3", "ranks.B == 3", "ranks.Y == 3", "dim(shapes.A, 1) == dim(shapes.B, 0)", "dim(shapes.Y, 0) == dim(shapes.B, 0)", "dim(shapes.A, 2) == dim(shapes.B, 1)", "dim(shapes.Y, 1) == dim(shapes.A, 0)", "dim(shapes.Y, 2) == dim(shapes.B, 2)", "dim(shapes.A, 0) >= 64", "dim(shapes.A, 2) >= 32", "dim(shapes.B, 2) >= 64", "ceil(dim(shapes.B, 2) / 64) <= device.limits.maxComputeWorkgroupsPerDimension", "ceil(dim(shapes.A, 0) / 64) <= device.limits.maxComputeWorkgroupsPerDimension", "dim(shapes.Y, 0) <= device.limits.maxComputeWorkgroupsPerDimension"],
227
+ "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
228
+ "passes": [
229
+ {
230
+ "id": "main",
231
+ "name": "FusedMatMul.TransBatchATiledReg",
232
+ "source": {
233
+ "shader": "matmul-tiled-general-reg.wgsl.jinja",
234
+ "inputs": {
235
+ "aShape": "shapes.A",
236
+ "bShape": "shapes.B",
237
+ "alpha": "attrs.alpha",
238
+ "aRank": "ranks.A",
239
+ "bRank": "ranks.B",
240
+ "transBatchA": "true",
241
+ "kTile": "4"
242
+ }
243
+ },
244
+ "bindings": "matrix",
245
+ "dispatch": {
246
+ "x": "ceil(dim(shapes.B, 2) / 64)",
247
+ "y": "ceil(dim(shapes.A, 0) / 64)",
248
+ "z": "dim(shapes.Y, 0)"
249
+ }
250
+ }
251
+ ]
252
+ },
253
+ {
254
+ "id": "tiled",
255
+ "priority": 0,
256
+ "when": ["ranks.A >= 1", "ranks.B >= 1", "f16Ok(dtypes.T)", "transBatchContract", "(dim(shapes.A, 0) if ranks.A == 1 else (dim(shapes.A, ranks.A - 1) if attrs.transA == 0 else (dim(shapes.A, 0) if attrs.transBatchA != 0 else dim(shapes.A, ranks.A - 2)))) == (dim(shapes.B, 0) if ranks.B == 1 else (dim(shapes.B, ranks.B - 1) if attrs.transB != 0 else (dim(shapes.B, 0) if attrs.transBatchB != 0 else dim(shapes.B, ranks.B - 2))))", "ceil((1 if ranks.A == 1 else (dim(shapes.A, ranks.A - 1) if attrs.transA != 0 else (dim(shapes.A, 0) if attrs.transBatchA != 0 else dim(shapes.A, ranks.A - 2)))) / 16) <= device.limits.maxComputeWorkgroupsPerDimension", "ceil((1 if ranks.B == 1 else (dim(shapes.B, ranks.B - 1) if attrs.transB == 0 else (dim(shapes.B, 0) if attrs.transBatchB != 0 else dim(shapes.B, ranks.B - 2)))) / 16) <= device.limits.maxComputeWorkgroupsPerDimension", "numel(shapes.Y) / max(1, (1 if ranks.A == 1 else (dim(shapes.A, ranks.A - 1) if attrs.transA != 0 else (dim(shapes.A, 0) if attrs.transBatchA != 0 else dim(shapes.A, ranks.A - 2)))) * (1 if ranks.B == 1 else (dim(shapes.B, ranks.B - 1) if attrs.transB == 0 else (dim(shapes.B, 0) if attrs.transBatchB != 0 else dim(shapes.B, ranks.B - 2))))) <= device.limits.maxComputeWorkgroupsPerDimension"],
257
+ "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
258
+ "passes": [
259
+ {
260
+ "id": "main",
261
+ "name": "FusedMatMul.Tiled",
262
+ "source": {
263
+ "shader": "matmul-tiled-general.wgsl.jinja",
264
+ "inputs": {
265
+ "aShape": "shapes.A",
266
+ "bShape": "shapes.B",
267
+ "transA": "attrs.transA != 0",
268
+ "transB": "attrs.transB != 0",
269
+ "alpha": "attrs.alpha",
270
+ "aRank": "ranks.A",
271
+ "bRank": "ranks.B",
272
+ "transBatchA": "attrs.transBatchA != 0",
273
+ "transBatchB": "attrs.transBatchB != 0"
274
+ }
275
+ },
276
+ "bindings": "matrix",
277
+ "dispatch": {
278
+ "x": "ceil((1 if ranks.B == 1 else (dim(shapes.B, ranks.B - 1) if attrs.transB == 0 else (dim(shapes.B, 0) if attrs.transBatchB != 0 else dim(shapes.B, ranks.B - 2)))) / 32)",
279
+ "y": "ceil((1 if ranks.A == 1 else (dim(shapes.A, ranks.A - 1) if attrs.transA != 0 else (dim(shapes.A, 0) if attrs.transBatchA != 0 else dim(shapes.A, ranks.A - 2)))) / 32)",
280
+ "z": "numel(shapes.Y) / max(1, (1 if ranks.A == 1 else (dim(shapes.A, ranks.A - 1) if attrs.transA != 0 else (dim(shapes.A, 0) if attrs.transBatchA != 0 else dim(shapes.A, ranks.A - 2)))) * (1 if ranks.B == 1 else (dim(shapes.B, ranks.B - 1) if attrs.transB == 0 else (dim(shapes.B, 0) if attrs.transBatchB != 0 else dim(shapes.B, ranks.B - 2)))))"
281
+ }
282
+ }
283
+ ]
284
+ }
285
+ ]
286
+ }
build/webgpu/matmul-subgroup-matrix-ext.wgsl.jinja ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Subgroup-matrix matmul over row-major, batch-outermost operands, with alpha,
2
+ // dense/broadcast batching and guarded K/N tails under `generalAddressing`, and
3
+ // an optional fused bias on the direct dense path that omits it.
4
+ {% if usesF16 %}
5
+ enable f16;
6
+ {% endif %}
7
+ enable subgroups;
8
+ {% if pinSubgroupSize32 %}
9
+ enable subgroup_size_control;
10
+ {% endif %}
11
+ enable chromium_experimental_subgroup_matrix;
12
+ diagnostic(off, chromium.subgroup_matrix_uniformity);
13
+
14
+ {{ env.wgsl.resourceDeclarations }}
15
+
16
+ {% set operandScalar = fScalar %}
17
+ {% set accScalar = "f32" %}
18
+ {% set GENERAL = generalAddressing is defined and generalAddressing %}
19
+ {% set TAIL = tailSafe is defined and tailSafe %}
20
+ {% set OUT = outputBuffer if outputBuffer is defined else "c" %}
21
+ {% set OUT_SCALAR = outScalar if outScalar is defined else T %}
22
+ {% if GENERAL %}
23
+ {% set aR = source.aRank %}
24
+ {% set bR = source.bRank %}
25
+ {% set aBatchLen = aR - 2 %}
26
+ {% set bBatchLen = bR - 2 %}
27
+ {% set batchRank = aBatchLen %}
28
+ {% set aMStride = source.aShape[aR-1] %}
29
+ {% set aKStride = 1 %}
30
+ {% set bKStride = source.bShape[bR-1] %}
31
+ {% set bNStride = 1 %}
32
+ {% endif %}
33
+
34
+ const M: u32 = {{ M }}u;
35
+ const K: u32 = {{ K }}u;
36
+ const N: u32 = {{ N }}u;
37
+ const BATCH_COUNT: u32 = {{ batchCount if batchCount is defined else 1 }}u;
38
+ {% if GENERAL %}
39
+ const A_M_STRIDE: u32 = {{ aMStride }}u;
40
+ const A_K_STRIDE: u32 = {{ aKStride }}u;
41
+ const B_K_STRIDE: u32 = {{ bKStride }}u;
42
+ const B_N_STRIDE: u32 = {{ bNStride }}u;
43
+ {% if TAIL %}const K_FULL: u32 = (K / 32u) * 32u;
44
+ {% endif %}
45
+ const ALPHA: f32 = f32({{ alpha }});
46
+ {% else %}
47
+ const A_BATCH_STRIDE: u32 = M * K;
48
+ const B_BATCH_STRIDE: u32 = K * N;
49
+ {% endif %}
50
+ const C_BATCH_STRIDE: u32 = M * N;
51
+ const TILE_COLS: u32 = 64u;
52
+ const TILE_ROWS: u32 = 32u;
53
+ const TILE_K: u32 = 32u;
54
+ const SUB_COLS: u32 = 32u;
55
+ const SUB_ROWS: u32 = 16u;
56
+
57
+ var<workgroup> tile_A: array<{{ operandScalar }}, 32 * 32>;
58
+ var<workgroup> tile_B: array<{{ operandScalar }}, 64 * 32>;
59
+ var<workgroup> scratch: array<array<array<{{ accScalar }}, 64>, 4>, 4>;
60
+
61
+ fn loadSHMA(a_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
62
+ let a_global = tile_base + row;
63
+ let col = c_idx * 8u;
64
+ for (var col_offset = 0u; col_offset < 8u; col_offset = col_offset + 1u) {
65
+ let k = k_idx + col + col_offset;
66
+ if (a_global < M) {
67
+ {% if operandScalar == "f16" %}
68
+ {% if GENERAL %}
69
+ tile_A[row * TILE_K + col + col_offset] = f16(a[a_base + a_global * A_M_STRIDE + k * A_K_STRIDE]);
70
+ {% else %}
71
+ tile_A[row * TILE_K + col + col_offset] = f16(a[a_base + a_global * K + k]);
72
+ {% endif %}
73
+ {% else %}
74
+ {% if GENERAL %}
75
+ tile_A[row * TILE_K + col + col_offset] = f32(a[a_base + a_global * A_M_STRIDE + k * A_K_STRIDE]);
76
+ {% else %}
77
+ tile_A[row * TILE_K + col + col_offset] = f32(a[a_base + a_global * K + k]);
78
+ {% endif %}
79
+ {% endif %}
80
+ } else {
81
+ {% if operandScalar == "f16" %}
82
+ tile_A[row * TILE_K + col + col_offset] = 0.0h;
83
+ {% else %}
84
+ tile_A[row * TILE_K + col + col_offset] = 0.0;
85
+ {% endif %}
86
+ }
87
+ }
88
+ }
89
+ {% if GENERAL and TAIL %}
90
+
91
+ fn loadSHMAKTail(a_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
92
+ let a_global = tile_base + row;
93
+ let col = c_idx * 8u;
94
+ for (var col_offset = 0u; col_offset < 8u; col_offset = col_offset + 1u) {
95
+ let k = k_idx + col + col_offset;
96
+ if (a_global < M && k < K) {
97
+ tile_A[row * TILE_K + col + col_offset] = {{ operandScalar }}(a[a_base + a_global * A_M_STRIDE + k * A_K_STRIDE]);
98
+ } else {
99
+ tile_A[row * TILE_K + col + col_offset] = {{ operandScalar }}(0);
100
+ }
101
+ }
102
+ }
103
+
104
+ {% endif %}
105
+ fn loadSHMB(b_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
106
+ let b_col = tile_base + row;
107
+ let col = c_idx * 16u;
108
+ for (var i = 0u; i < 16u; i = i + 1u) {
109
+ let k = k_idx + col + i;
110
+ {% if GENERAL %}
111
+ {% if TAIL %}
112
+ let b_safe = min(b_col, N - 1u);
113
+ tile_B[row * TILE_K + col + i] = {{ operandScalar }}(b[b_base + k * B_K_STRIDE + b_safe * B_N_STRIDE]);
114
+ {% else %}
115
+ {% if operandScalar == "f16" %}
116
+ tile_B[row * TILE_K + col + i] = f16(b[b_base + k * B_K_STRIDE + b_col * B_N_STRIDE]);
117
+ {% else %}
118
+ tile_B[row * TILE_K + col + i] = f32(b[b_base + k * B_K_STRIDE + b_col * B_N_STRIDE]);
119
+ {% endif %}
120
+ {% endif %}
121
+ {% else %}
122
+ {% set bIndex = "b_col * K + k" if bTransposed is defined and bTransposed else "k * N + b_col" %}
123
+ tile_B[row * TILE_K + col + i] = {{ operandScalar }}(b[b_base + {{ bIndex }}]);
124
+ {% endif %}
125
+ }
126
+ }
127
+ {% if GENERAL and TAIL %}
128
+
129
+ fn loadSHMBKTail(b_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
130
+ let b_col = min(tile_base + row, N - 1u);
131
+ let col = c_idx * 16u;
132
+ for (var i = 0u; i < 16u; i = i + 1u) {
133
+ let k = k_idx + col + i;
134
+ if (k < K) {
135
+ tile_B[row * TILE_K + col + i] = {{ operandScalar }}(b[b_base + k * B_K_STRIDE + b_col * B_N_STRIDE]);
136
+ } else {
137
+ tile_B[row * TILE_K + col + i] = {{ operandScalar }}(0);
138
+ }
139
+ }
140
+ }
141
+
142
+ {% endif %}
143
+ {% set needsColBase = hasBias or (GENERAL and TAIL) %}
144
+ fn storeOutput(offset: u32{% if needsColBase %}, col_base: u32{% endif %}, row: u32, col: u32, src_slot: u32, row_limit: i32) {
145
+ if (row_limit > 0 && row < u32(row_limit)) {
146
+ let col2 = col + 1u;
147
+ {% if GENERAL %}
148
+ {% for block in range(4) %}
149
+ {% if TAIL %}
150
+ if (col_base + col + {{ block * 8 }}u < N) {
151
+ {% endif %}
152
+ {{ OUT }}[offset + row * N + col + {{ block * 8 }}u] = {{ OUT_SCALAR }}(
153
+ ALPHA * scratch[src_slot][{{ block }}][row * 8u + col]
154
+ );
155
+ {% if TAIL %}
156
+ }
157
+ if (col_base + col2 + {{ block * 8 }}u < N) {
158
+ {% endif %}
159
+ {{ OUT }}[offset + row * N + col2 + {{ block * 8 }}u] = {{ OUT_SCALAR }}(
160
+ ALPHA * scratch[src_slot][{{ block }}][row * 8u + col2]
161
+ );
162
+ {% if TAIL %}
163
+ }
164
+ {% endif %}
165
+ {% endfor %}
166
+ {% else %}
167
+ {% if hasBias %}
168
+ {{ OUT }}[offset + row * N + col] = {{ T }}(scratch[src_slot][0][row * 8u + col] + {{ accScalar }}(bias[col_base + col]));
169
+ {{ OUT }}[offset + row * N + col + 8u] = {{ T }}(scratch[src_slot][1][row * 8u + col] + {{ accScalar }}(bias[col_base + col + 8u]));
170
+ {{ OUT }}[offset + row * N + col + 16u] = {{ T }}(scratch[src_slot][2][row * 8u + col] + {{ accScalar }}(bias[col_base + col + 16u]));
171
+ {{ OUT }}[offset + row * N + col + 24u] = {{ T }}(scratch[src_slot][3][row * 8u + col] + {{ accScalar }}(bias[col_base + col + 24u]));
172
+
173
+ {{ OUT }}[offset + row * N + col2] = {{ T }}(scratch[src_slot][0][row * 8u + col2] + {{ accScalar }}(bias[col_base + col2]));
174
+ {{ OUT }}[offset + row * N + col2 + 8u] = {{ T }}(scratch[src_slot][1][row * 8u + col2] + {{ accScalar }}(bias[col_base + col2 + 8u]));
175
+ {{ OUT }}[offset + row * N + col2 + 16u] = {{ T }}(scratch[src_slot][2][row * 8u + col2] + {{ accScalar }}(bias[col_base + col2 + 16u]));
176
+ {{ OUT }}[offset + row * N + col2 + 24u] = {{ T }}(scratch[src_slot][3][row * 8u + col2] + {{ accScalar }}(bias[col_base + col2 + 24u]));
177
+ {% else %}
178
+ {{ OUT }}[offset + row * N + col] = {{ T }}(scratch[src_slot][0][row * 8u + col]);
179
+ {{ OUT }}[offset + row * N + col + 8u] = {{ T }}(scratch[src_slot][1][row * 8u + col]);
180
+ {{ OUT }}[offset + row * N + col + 16u] = {{ T }}(scratch[src_slot][2][row * 8u + col]);
181
+ {{ OUT }}[offset + row * N + col + 24u] = {{ T }}(scratch[src_slot][3][row * 8u + col]);
182
+
183
+ {{ OUT }}[offset + row * N + col2] = {{ T }}(scratch[src_slot][0][row * 8u + col2]);
184
+ {{ OUT }}[offset + row * N + col2 + 8u] = {{ T }}(scratch[src_slot][1][row * 8u + col2]);
185
+ {{ OUT }}[offset + row * N + col2 + 16u] = {{ T }}(scratch[src_slot][2][row * 8u + col2]);
186
+ {{ OUT }}[offset + row * N + col2 + 24u] = {{ T }}(scratch[src_slot][3][row * 8u + col2]);
187
+ {% endif %}
188
+ {% endif %}
189
+ }
190
+ }
191
+
192
+ @compute @workgroup_size(128, 1, 1){{ " @subgroup_size(32)" if pinSubgroupSize32 else "" }}
193
+ fn main(
194
+ @builtin(workgroup_id) workgroup_id: vec3<u32>,
195
+ @builtin(num_workgroups) num_wg: vec3<u32>,
196
+ @builtin(local_invocation_index) local_idx: u32,
197
+ @builtin(subgroup_invocation_id) sg_id: u32,
198
+ @builtin(subgroup_size) sg_size: u32
199
+ ) {
200
+ let b_global_base = workgroup_id.x * TILE_COLS;
201
+
202
+ let subtile_id = local_idx / sg_size;
203
+ let subtile_idx = subtile_id / 2u;
204
+ let subtile_idy = subtile_id % 2u;
205
+ let base_A = subtile_idy * SUB_ROWS;
206
+ let base_B = subtile_idx * SUB_COLS;
207
+
208
+ // Grid-stride over both the M-tile (y) and batch (z) axes so the dispatch stays
209
+ // <= maxComputeWorkgroupsPerDimension per dimension even when ceil(M/TILE_ROWS) or BATCH_COUNT exceed the
210
+ // limit. workgroup_size.z = 1 so num_wg.z is the batch dispatch stride, and
211
+ // num_wg.y * TILE_ROWS is the row-tile dispatch stride. The loop bounds (M and
212
+ // BATCH_COUNT are compile-time / uniform; num_wg and workgroup_id are uniform)
213
+ // are workgroup-uniform, so the trailing workgroupBarrier()s and the subgroup
214
+ // matrix operations stay reconverged. When neither axis is clamped,
215
+ // num_wg.y * TILE_ROWS > M and num_wg.z > BATCH_COUNT, so each loop executes
216
+ // exactly once at workgroup_id.y/workgroup_id.z.
217
+ let row_tile_stride = num_wg.y * TILE_ROWS;
218
+ for (var a_global_base = workgroup_id.y * TILE_ROWS; a_global_base < M; a_global_base += row_tile_stride) {
219
+ // workgroup_size.z = 1, so num_wg.z is the dispatch stride over the batch axis.
220
+ for (var batch = workgroup_id.z; batch < BATCH_COUNT; batch += num_wg.z) {
221
+ {% if GENERAL %}
222
+ {% set hasBatchCoord = namespace(value=false) %}
223
+ {% for i in range(batchRank) %}
224
+ {% set axis = batchRank - 1 - i %}
225
+ {% set aAxis = axis - (batchRank - aBatchLen) %}
226
+ {% set bAxis = axis - (batchRank - bBatchLen) %}
227
+ {% set aDim = source.aShape[aAxis] %}
228
+ {% set bDim = source.bShape[bAxis] if bAxis >= 0 else 1 %}
229
+ {% if aDim > 1 or bDim > 1 %}{% set hasBatchCoord.value = true %}{% endif %}
230
+ {% endfor %}
231
+ // Right-aligned broadcast offsets, decomposed from the flattened output batch.
232
+ {% if hasBatchCoord.value %}
233
+ var zTmp = batch;
234
+ {% endif %}
235
+ var a_base: u32 = 0u;
236
+ var b_base: u32 = 0u;
237
+ {% for i in range(batchRank) %}
238
+ {% set axis = batchRank - 1 - i %}
239
+ {% set aAxis = axis - (batchRank - aBatchLen) %}
240
+ {% set bAxis = axis - (batchRank - bBatchLen) %}
241
+ {% set aDim = source.aShape[aAxis] %}
242
+ {% set bDim = source.bShape[bAxis] if bAxis >= 0 else 1 %}
243
+ {% set outDim = aDim if aDim >= bDim else bDim %}
244
+ {% set aStride = namespace(v=1) %}
245
+ {% if aDim != 1 %}{% for j in range(aAxis + 1, aR) %}{% set aStride.v = aStride.v * source.aShape[j] %}{% endfor %}{% else %}{% set aStride.v = 0 %}{% endif %}
246
+ {% set bStride = namespace(v=1) %}
247
+ {% if bAxis >= 0 and bDim != 1 %}{% for j in range(bAxis + 1, bR) %}{% set bStride.v = bStride.v * source.bShape[j] %}{% endfor %}{% else %}{% set bStride.v = 0 %}{% endif %}
248
+ {% if outDim > 1 %}
249
+ let c{{ axis }} = zTmp % {{ outDim }}u;
250
+ zTmp = zTmp / {{ outDim }}u;
251
+ {% if aStride.v != 0 %} a_base = a_base + c{{ axis }} * {{ aStride.v }}u;
252
+ {% endif %}
253
+ {% if bStride.v != 0 %} b_base = b_base + c{{ axis }} * {{ bStride.v }}u;
254
+ {% endif %}
255
+ {% endif %}
256
+ {% endfor %}
257
+ let c_base = batch * C_BATCH_STRIDE;
258
+ {% else %}
259
+ let a_base = batch * A_BATCH_STRIDE;
260
+ let b_base = batch * B_BATCH_STRIDE;
261
+ let c_base = batch * C_BATCH_STRIDE;
262
+ {% endif %}
263
+
264
+ var matC00: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
265
+ var matC01: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
266
+ var matC02: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
267
+ var matC03: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
268
+ var matC10: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
269
+ var matC11: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
270
+ var matC12: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
271
+ var matC13: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
272
+
273
+ for (var kidx = 0u; kidx < {% if GENERAL and TAIL %}K_FULL{% else %}K{% endif %}; kidx = kidx + TILE_K) {
274
+ loadSHMA(a_base, a_global_base, kidx, local_idx / 4u, local_idx % 4u);
275
+ loadSHMB(b_base, b_global_base, kidx, local_idx / 2u, local_idx % 2u);
276
+ workgroupBarrier();
277
+
278
+ for (var step = 0u; step < TILE_K; step = step + 8u) {
279
+ let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
280
+ var matA0: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset, false, TILE_K);
281
+ var matA1: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset + 8u * TILE_K, false, TILE_K);
282
+
283
+ let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
284
+ var matB0: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset, true, TILE_K);
285
+ var matB1: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 8u * TILE_K, true, TILE_K);
286
+ var matB2: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 16u * TILE_K, true, TILE_K);
287
+ var matB3: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 24u * TILE_K, true, TILE_K);
288
+
289
+ matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
290
+ matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
291
+ matC02 = subgroupMatrixMultiplyAccumulate(matA0, matB2, matC02);
292
+ matC03 = subgroupMatrixMultiplyAccumulate(matA0, matB3, matC03);
293
+ matC10 = subgroupMatrixMultiplyAccumulate(matA1, matB0, matC10);
294
+ matC11 = subgroupMatrixMultiplyAccumulate(matA1, matB1, matC11);
295
+ matC12 = subgroupMatrixMultiplyAccumulate(matA1, matB2, matC12);
296
+ matC13 = subgroupMatrixMultiplyAccumulate(matA1, matB3, matC13);
297
+ }
298
+ workgroupBarrier();
299
+ }
300
+ {% if GENERAL and TAIL %}
301
+ if (K_FULL < K) {
302
+ loadSHMAKTail(a_base, a_global_base, K_FULL, local_idx / 4u, local_idx % 4u);
303
+ loadSHMBKTail(b_base, b_global_base, K_FULL, local_idx / 2u, local_idx % 2u);
304
+ workgroupBarrier();
305
+
306
+ for (var step = 0u; step < TILE_K; step = step + 8u) {
307
+ let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
308
+ var matA0: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset, false, TILE_K);
309
+ var matA1: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset + 8u * TILE_K, false, TILE_K);
310
+
311
+ let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
312
+ var matB0: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset, true, TILE_K);
313
+ var matB1: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 8u * TILE_K, true, TILE_K);
314
+ var matB2: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 16u * TILE_K, true, TILE_K);
315
+ var matB3: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + 24u * TILE_K, true, TILE_K);
316
+
317
+ matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
318
+ matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
319
+ matC02 = subgroupMatrixMultiplyAccumulate(matA0, matB2, matC02);
320
+ matC03 = subgroupMatrixMultiplyAccumulate(matA0, matB3, matC03);
321
+ matC10 = subgroupMatrixMultiplyAccumulate(matA1, matB0, matC10);
322
+ matC11 = subgroupMatrixMultiplyAccumulate(matA1, matB1, matC11);
323
+ matC12 = subgroupMatrixMultiplyAccumulate(matA1, matB2, matC12);
324
+ matC13 = subgroupMatrixMultiplyAccumulate(matA1, matB3, matC13);
325
+ }
326
+ workgroupBarrier();
327
+ }
328
+
329
+ {% endif %}
330
+ // The four scratch banks are reused across the two row-groups, and each is written
331
+ // by a collective subgroupMatrixStore then read CROSS-LANE by storeOutput. Barriers
332
+ // give the reads visibility of the store AND stop the second row-group's store from
333
+ // clobbering the first's still-in-flight readback when a partial final M-tile
334
+ // diverges storeOutput's guard. Without both barriers the last valid row can be corrupted.
335
+ subgroupMatrixStore(&scratch[subtile_id][0], 0u, matC00, false, 8u);
336
+ subgroupMatrixStore(&scratch[subtile_id][1], 0u, matC01, false, 8u);
337
+ subgroupMatrixStore(&scratch[subtile_id][2], 0u, matC02, false, 8u);
338
+ subgroupMatrixStore(&scratch[subtile_id][3], 0u, matC03, false, 8u);
339
+ workgroupBarrier();
340
+ let row = sg_id / 4u;
341
+ let col = (sg_id % 4u) * 2u;
342
+ let col_base = b_global_base + base_B;
343
+ var matrix_c_offset = c_base + (a_global_base + base_A) * N + col_base;
344
+ var row_limit = i32(M) - i32(a_global_base + base_A);
345
+ storeOutput(matrix_c_offset{% if needsColBase %}, col_base{% endif %}, row, col, subtile_id, row_limit);
346
+ workgroupBarrier();
347
+
348
+ subgroupMatrixStore(&scratch[subtile_id][0], 0u, matC10, false, 8u);
349
+ subgroupMatrixStore(&scratch[subtile_id][1], 0u, matC11, false, 8u);
350
+ subgroupMatrixStore(&scratch[subtile_id][2], 0u, matC12, false, 8u);
351
+ subgroupMatrixStore(&scratch[subtile_id][3], 0u, matC13, false, 8u);
352
+ workgroupBarrier();
353
+ matrix_c_offset = matrix_c_offset + 8u * N;
354
+ row_limit = i32(M) - i32(a_global_base + base_A + 8u);
355
+ storeOutput(matrix_c_offset{% if needsColBase %}, col_base{% endif %}, row, col, subtile_id, row_limit);
356
+
357
+ // Re-stage workgroup tiles/scratch before the next batch iteration reuses them.
358
+ workgroupBarrier();
359
+ }
360
+ // Re-stage workgroup tiles/scratch before the next M-tile iteration reuses them.
361
+ // The loop bound is workgroup-uniform (M is a compile-time const, num_wg.y and
362
+ // workgroup_id.y are uniform), so every invocation reaches this barrier together.
363
+ workgroupBarrier();
364
+ }
365
+ }
build/webgpu/matmul-tiled-general-reg.wgsl.jinja ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ // Register-blocked MatMul for the no-subgroup-matrix
7
+ // tier: Y = alpha * A @ B. It retains the bounds-checked addressing and
8
+ // batch-broadcast of the general kernel, and its transposed-batch-A layout.
9
+ // Transposed and 1-D operands select other variants and are not handled here.
10
+ // Each thread computes a 4x4 micro-tile within a 64x64 workgroup tile, reusing
11
+ // each staged operand across four accumulators. Both tiles are indexed by their
12
+ // own output axis and group four K values per vector word, so the micro-tile
13
+ // accumulates through dot() and one step reads TM + TN words rather than
14
+ // 4 * (TM + TN) scalars. A stores K contiguously and B stores N contiguously,
15
+ // so each staging lane walks the axis its operand already has.
16
+ {% set aR = source.aRank %}
17
+ {% set bR = source.bRank %}
18
+ {% set aBatchLen = aR - 2 %}
19
+ {% set bBatchLen = bR - 2 %}
20
+ {% set batchRank = aBatchLen %}
21
+ {% set aTailStride = namespace(v=1) %}
22
+ {% for j in range(1, aR) %}{% set aTailStride.v = aTailStride.v * source.aShape[j] %}{% endfor %}
23
+ {% set bTailStride = namespace(v=1) %}
24
+ {% for j in range(1, bR) %}{% set bTailStride.v = bTailStride.v * source.bShape[j] %}{% endfor %}
25
+ {% if source.transBatchA %}{% set M = source.aShape[0] %}{% set K = source.aShape[aR-1] %}
26
+ {% else %}{% set M = source.aShape[aR-2] %}{% set K = source.aShape[aR-1] %}{% endif %}
27
+ {% set N = source.bShape[bR-1] %}
28
+ {% if source.transBatchA %}{% set aMStride = aTailStride.v %}{% set aKStride = 1 %}
29
+ {% else %}{% set aMStride = source.aShape[aR-1] %}{% set aKStride = 1 %}{% endif %}
30
+ {% set bKStride = source.bShape[bR-1] %}{% set bNStride = 1 %}
31
+
32
+ // Integer MatMul accumulates in its integer type because f32's 24-bit mantissa
33
+ // rounds integer values above 2^24.
34
+ {% set is_int = (scalar == "i32" or scalar == "u32") %}
35
+ {% set accT = scalar if is_int else "f32" %}
36
+ {% set outScalar = source.outScalar if source.outScalar is defined else scalar %}
37
+ // f16 operands stay packed in workgroup memory and widen on shared load.
38
+ // Accumulation remains f32 with the same FMA order, while shared traffic halves.
39
+ {% set tileT = scalar if scalar == "f16" else accT %}
40
+ {% set kTile = source.kTile if source.kTile is defined else 16 %}
41
+ const M: u32 = {{ M }}u;
42
+ const K: u32 = {{ K }}u;
43
+ const N: u32 = {{ N }}u;
44
+ const A_M_STRIDE: u32 = {{ aMStride }}u;
45
+ const A_K_STRIDE: u32 = {{ aKStride }}u;
46
+ const B_K_STRIDE: u32 = {{ bKStride }}u;
47
+ const B_N_STRIDE: u32 = {{ bNStride }}u;
48
+ {% if is_int %}const ALPHA: {{ accT }} = {{ accT }}(1);{% else %}const ALPHA: f32 = f32({{ source.alpha }});{% endif %}
49
+ // A 4x4 micro-tile over a 64x64 output tile reuses each staged operand across
50
+ // four accumulators. It increases arithmetic work per load without the large
51
+ // per-thread accumulator footprint of an 8x8 micro-tile.
52
+ const BK: u32 = {{ kTile }}u;
53
+ const BM: u32 = 64u;
54
+ const BN: u32 = 64u;
55
+ const TM: u32 = 4u; // per-thread micro-tile rows
56
+ const TN: u32 = 4u; // per-thread micro-tile cols
57
+ const K_VECS: u32 = BK / 4u;
58
+ var<workgroup> tileA: array<array<vec4<{{ tileT }}>, K_VECS>, BM>; // A[m][k/4]
59
+ var<workgroup> tileB: array<array<vec4<{{ tileT }}>, K_VECS>, BN>; // B[n][k/4]
60
+ {% set hasBatchCoord = namespace(value=false) %}
61
+ {% for i in range(batchRank) %}
62
+ {% set axis = batchRank - 1 - i %}
63
+ {% set aAxis = axis - (batchRank - aBatchLen) %}
64
+ {% set bAxis = axis - (batchRank - bBatchLen) %}
65
+ {% set aStored = (aAxis + 1) if (source.transBatchA and aAxis >= 0) else aAxis %}
66
+ {% set bStored = bAxis %}
67
+ {% set aDim = source.aShape[aStored] %}
68
+ {% set bDim = source.bShape[bStored] if bStored >= 0 else 1 %}
69
+ {% if aDim > 1 or bDim > 1 %}{% set hasBatchCoord.value = true %}{% endif %}
70
+ {% endfor %}
71
+
72
+ @compute @workgroup_size(16, 16, 1)
73
+ fn main(
74
+ @builtin(workgroup_id) wg: vec3<u32>,
75
+ @builtin(local_invocation_id) lid: vec3<u32>
76
+ ) {
77
+ let mBase = wg.y * BM;
78
+ let nBase = wg.x * BN;
79
+ let li = lid.y * 16u + lid.x;
80
+
81
+ let zOut = wg.z;
82
+ {% if hasBatchCoord.value %}
83
+ var zTmp = wg.z;
84
+ {% endif %}
85
+ var aBatchOff: u32 = 0u;
86
+ var bBatchOff: u32 = 0u;
87
+ {% for i in range(batchRank) %}
88
+ {% set axis = batchRank - 1 - i %}
89
+ {% set aAxis = axis - (batchRank - aBatchLen) %}
90
+ {% set bAxis = axis - (batchRank - bBatchLen) %}
91
+ {% set aStored = (aAxis + 1) if (source.transBatchA and aAxis >= 0) else aAxis %}
92
+ {% set bStored = bAxis %}
93
+ {% set aDim = source.aShape[aStored] %}
94
+ {% set bDim = source.bShape[bStored] if bStored >= 0 else 1 %}
95
+ {% set outDim = aDim if aDim >= bDim else bDim %}
96
+ {% set aStride = namespace(v=1) %}
97
+ {% if aStored >= 0 and aDim != 1 %}{% for j in range(aStored + 1, aR) %}{% set aStride.v = aStride.v * source.aShape[j] %}{% endfor %}{% else %}{% set aStride.v = 0 %}{% endif %}
98
+ {% set bStride = namespace(v=1) %}
99
+ {% if bStored >= 0 and bDim != 1 %}{% for j in range(bStored + 1, bR) %}{% set bStride.v = bStride.v * source.bShape[j] %}{% endfor %}{% else %}{% set bStride.v = 0 %}{% endif %}
100
+ {% if outDim > 1 %}
101
+ let c{{ axis }} = zTmp % {{ outDim }}u;
102
+ zTmp = zTmp / {{ outDim }}u;
103
+ {% if aStride.v != 0 %} aBatchOff = aBatchOff + c{{ axis }} * {{ aStride.v }}u;
104
+ {% endif %}
105
+ {% if bStride.v != 0 %} bBatchOff = bBatchOff + c{{ axis }} * {{ bStride.v }}u;
106
+ {% endif %}
107
+ {% endif %}
108
+ {% endfor %}
109
+
110
+ var acc: array<{{ accT }}, TM * TN>; // [ti*TN + tj] for the TMxTN micro-tile
111
+ for (var i: u32 = 0u; i < TM * TN; i = i + 1u) { acc[i] = {{ accT }}(0); }
112
+
113
+ let numTiles = (K + BK - 1u) / BK;
114
+ for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
115
+ let kBase = kt * BK;
116
+ // Cooperative load: one vector word per lane per pass. A's lanes walk K, which
117
+ // it stores contiguously; B's walk N, which it stores contiguously.
118
+ for (var idx: u32 = li; idx < BM * K_VECS; idx = idx + 256u) {
119
+ let ar = idx / K_VECS;
120
+ let ac4 = idx % K_VECS;
121
+ let am = mBase + ar;
122
+ let ak = kBase + ac4 * 4u;
123
+ var aWord = vec4<{{ tileT }}>({{ tileT }}(0));
124
+ if (am < M) {
125
+ let aRowOff = aBatchOff + am * A_M_STRIDE;
126
+ {% for component in range(4) %}
127
+ if (ak + {{ component }}u < K) { aWord[{{ component }}u] = {{ tileT }}(a[aRowOff + (ak + {{ component }}u) * A_K_STRIDE]); }
128
+ {% endfor %}
129
+ }
130
+ tileA[ar][ac4] = aWord;
131
+ }
132
+ for (var idx: u32 = li; idx < BN * K_VECS; idx = idx + 256u) {
133
+ let bc = idx % BN;
134
+ let br4 = idx / BN;
135
+ let bn = nBase + bc;
136
+ let bk = kBase + br4 * 4u;
137
+ var bWord = vec4<{{ tileT }}>({{ tileT }}(0));
138
+ if (bn < N) {
139
+ let bColOff = bBatchOff + bn * B_N_STRIDE;
140
+ {% for component in range(4) %}
141
+ if (bk + {{ component }}u < K) { bWord[{{ component }}u] = {{ tileT }}(b[bColOff + (bk + {{ component }}u) * B_K_STRIDE]); }
142
+ {% endfor %}
143
+ }
144
+ tileB[bc][br4] = bWord;
145
+ }
146
+ workgroupBarrier();
147
+ {% set regIndent = " " %}{% set regT = accT %}{{ regIndent }}let aRow = lid.y * TM;
148
+ {{ regIndent }}let bCol = lid.x * TN;
149
+ {{ regIndent }}for (var kv: u32 = 0u; kv < BK / 4u; kv = kv + 1u) {
150
+ {{ regIndent }} var av: array<vec4<{{ regT }}>, TM>;
151
+ {{ regIndent }} var bv: array<vec4<{{ regT }}>, TN>;
152
+ {{ regIndent }} for (var i: u32 = 0u; i < TM; i = i + 1u) { av[i] = vec4<{{ regT }}>(tileA[aRow + i][kv]); }
153
+ {{ regIndent }} for (var j: u32 = 0u; j < TN; j = j + 1u) { bv[j] = vec4<{{ regT }}>(tileB[bCol + j][kv]); }
154
+ {{ regIndent }} for (var i: u32 = 0u; i < TM; i = i + 1u) {
155
+ {{ regIndent }} for (var j: u32 = 0u; j < TN; j = j + 1u) {
156
+ {{ regIndent }} acc[i * TN + j] = acc[i * TN + j] + dot(av[i], bv[j]);
157
+ {{ regIndent }} }
158
+ {{ regIndent }} }
159
+ {{ regIndent }}}
160
+ workgroupBarrier();
161
+ }
162
+
163
+ let rowBase = zOut * M * N;
164
+ let m0 = mBase + lid.y * TM;
165
+ let n0 = nBase + lid.x * TN;
166
+ for (var ti: u32 = 0u; ti < TM; ti = ti + 1u) {
167
+ let m = m0 + ti;
168
+ if (m >= M) { continue; }
169
+ for (var tj: u32 = 0u; tj < TN; tj = tj + 1u) {
170
+ let n = n0 + tj;
171
+ if (n < N) {
172
+ y[rowBase + m * N + n] = {{ outScalar }}(ALPHA * acc[ti * TN + tj]);
173
+ }
174
+ }
175
+ }
176
+ }
build/webgpu/matmul-tiled-general.wgsl.jinja ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ // Shared tiled matrix multiplication: Y = alpha * op(A) @ op(B), where op
7
+ // transposes the last two axes when requested. This bounds-checked kernel
8
+ // handles any M/K/N (no alignment requirement), all four transpose combinations,
9
+ // right-aligned batch broadcasting, 1-D operand promotion (M==1 / N==1), and empty
10
+ // K/M/N. transA/transB only change which stored stride the logical (m,k)/(k,n)
11
+ // walk, using compiled stride constants here; the batch broadcast strides are
12
+ // compiled the same way (a 0 literal means "broadcast / absent on that operand").
13
+ {% set aR = source.aRank %}
14
+ {% set bR = source.bRank %}
15
+ {% set aVec = (aR == 1) %}
16
+ {% set bVec = (bR == 1) %}
17
+ {% set aBatchLen = (aR - 2) if aR >= 2 else 0 %}
18
+ {% set bBatchLen = (bR - 2) if bR >= 2 else 0 %}
19
+ {% set batchRank = aBatchLen if aBatchLen >= bBatchLen else bBatchLen %}
20
+ /* transBatch maps stored [d0, d1, ..., dR-2, dR-1] to logical
21
+ * [d1, ..., dR-2, d0, dR-1]. Stored axis zero becomes the M axis, the final K
22
+ * axis is unchanged, and the remaining axes form the batch. This stride
23
+ * permutation composes with the ordinary last-two-axis transpose. */
24
+ {% set aTailStride = namespace(v=1) %}
25
+ {% for j in range(1, aR) %}{% set aTailStride.v = aTailStride.v * source.aShape[j] %}{% endfor %}
26
+ {% set bTailStride = namespace(v=1) %}
27
+ {% for j in range(1, bR) %}{% set bTailStride.v = bTailStride.v * source.bShape[j] %}{% endfor %}
28
+ {% if aVec %}{% set M = 1 %}{% set K = source.aShape[0] %}
29
+ {% elif source.transBatchA and source.transA %}{% set M = source.aShape[aR-1] %}{% set K = source.aShape[0] %}
30
+ {% elif source.transBatchA %}{% set M = source.aShape[0] %}{% set K = source.aShape[aR-1] %}
31
+ {% elif source.transA %}{% set M = source.aShape[aR-1] %}{% set K = source.aShape[aR-2] %}
32
+ {% else %}{% set M = source.aShape[aR-2] %}{% set K = source.aShape[aR-1] %}{% endif %}
33
+ {% if bVec %}{% set N = 1 %}
34
+ {% elif source.transBatchB and source.transB %}{% set N = source.bShape[0] %}
35
+ {% elif source.transB %}{% set N = source.bShape[bR-2] %}
36
+ {% else %}{% set N = source.bShape[bR-1] %}{% endif %}
37
+ {% if aVec %}{% set aMStride = 0 %}{% set aKStride = 1 %}
38
+ {% elif source.transBatchA and source.transA %}{% set aMStride = 1 %}{% set aKStride = aTailStride.v %}
39
+ {% elif source.transBatchA %}{% set aMStride = aTailStride.v %}{% set aKStride = 1 %}
40
+ {% elif source.transA %}{% set aMStride = 1 %}{% set aKStride = source.aShape[aR-1] %}
41
+ {% else %}{% set aMStride = source.aShape[aR-1] %}{% set aKStride = 1 %}{% endif %}
42
+ {% if bVec %}{% set bKStride = 1 %}{% set bNStride = 0 %}
43
+ {% elif source.transBatchB and source.transB %}{% set bKStride = 1 %}{% set bNStride = bTailStride.v %}
44
+ {% elif source.transBatchB %}{% set bKStride = bTailStride.v %}{% set bNStride = 1 %}
45
+ {% elif source.transB %}{% set bKStride = 1 %}{% set bNStride = source.bShape[bR-1] %}
46
+ {% else %}{% set bKStride = source.bShape[bR-1] %}{% set bNStride = 1 %}{% endif %}
47
+
48
+ {% set is_int = (scalar == "i32" or scalar == "u32") %}
49
+ {% set accT = scalar if is_int else "f32" %}
50
+ {% set tileT = scalar if scalar == "f16" else accT %}
51
+ const M: u32 = {{ M }}u;
52
+ const K: u32 = {{ K }}u;
53
+ const N: u32 = {{ N }}u;
54
+ const A_M_STRIDE: u32 = {{ aMStride }}u;
55
+ const A_K_STRIDE: u32 = {{ aKStride }}u;
56
+ const B_K_STRIDE: u32 = {{ bKStride }}u;
57
+ const B_N_STRIDE: u32 = {{ bNStride }}u;
58
+ {% if is_int %}/* Integer matrix multiplication accumulates in the integer type. Widening
59
+ * through f32 would round values above 2^24. Integer MatMul has alpha = 1. */
60
+ const ALPHA: {{ accT }} = {{ accT }}(1);{% else %}const ALPHA: f32 = f32({{ source.alpha }});{% endif %}
61
+ // 2x2 register-blocked tile: 16x16 threads each compute a 2x2 micro-tile, for a
62
+ // 32x32 output tile per workgroup with K stepped in BK=16 chunks. Each loaded
63
+ // shared-mem element feeds 2 FMAs, favoring register reuse in the inner loop.
64
+ const BK: u32 = 16u;
65
+ const BM: u32 = 32u;
66
+ const BN: u32 = 32u;
67
+
68
+ var<workgroup> tileA: array<array<{{ tileT }}, 16>, 32>;
69
+ var<workgroup> tileB: array<array<{{ tileT }}, 32>, 16>;
70
+ {% set hasBatchCoord = namespace(value=false) %}
71
+ {% for i in range(batchRank) %}
72
+ {% set axis = batchRank - 1 - i %}
73
+ {% set aAxis = axis - (batchRank - aBatchLen) %}
74
+ {% set bAxis = axis - (batchRank - bBatchLen) %}
75
+ {% set aStored = (aAxis + 1) if (source.transBatchA and aAxis >= 0) else aAxis %}
76
+ {% set bStored = (bAxis + 1) if (source.transBatchB and bAxis >= 0) else bAxis %}
77
+ {% set aDim = source.aShape[aStored] if aStored >= 0 else 1 %}
78
+ {% set bDim = source.bShape[bStored] if bStored >= 0 else 1 %}
79
+ {% if aDim > 1 or bDim > 1 %}{% set hasBatchCoord.value = true %}{% endif %}
80
+ {% endfor %}
81
+
82
+ @compute @workgroup_size(16, 16, 1)
83
+ fn main(
84
+ @builtin(workgroup_id) wg: vec3<u32>,
85
+ @builtin(local_invocation_id) lid: vec3<u32>
86
+ ) {
87
+ let mBase = wg.y * BM;
88
+ let nBase = wg.x * BN;
89
+ let li = lid.y * 16u + lid.x;
90
+
91
+ // Per-batch base offsets into A and B using right-aligned broadcast strides.
92
+ // Decompose the flat output-batch index from the innermost axis outward.
93
+ let zOut = wg.z;
94
+ {% if hasBatchCoord.value %}
95
+ var zTmp = wg.z;
96
+ {% endif %}
97
+ var aBatchOff: u32 = 0u;
98
+ var bBatchOff: u32 = 0u;
99
+ {% for i in range(batchRank) %}
100
+ {% set axis = batchRank - 1 - i %}
101
+ {% set aAxis = axis - (batchRank - aBatchLen) %}
102
+ {% set bAxis = axis - (batchRank - bBatchLen) %}
103
+ {% set aStored = (aAxis + 1) if (source.transBatchA and aAxis >= 0) else aAxis %}
104
+ {% set bStored = (bAxis + 1) if (source.transBatchB and bAxis >= 0) else bAxis %}
105
+ {% set aDim = source.aShape[aStored] if aStored >= 0 else 1 %}
106
+ {% set bDim = source.bShape[bStored] if bStored >= 0 else 1 %}
107
+ {% set outDim = aDim if aDim >= bDim else bDim %}
108
+ {% set aStride = namespace(v=1) %}
109
+ {% if aStored >= 0 and aDim != 1 %}{% for j in range(aStored + 1, aR) %}{% set aStride.v = aStride.v * source.aShape[j] %}{% endfor %}{% else %}{% set aStride.v = 0 %}{% endif %}
110
+ {% set bStride = namespace(v=1) %}
111
+ {% if bStored >= 0 and bDim != 1 %}{% for j in range(bStored + 1, bR) %}{% set bStride.v = bStride.v * source.bShape[j] %}{% endfor %}{% else %}{% set bStride.v = 0 %}{% endif %}
112
+ {% if outDim > 1 %}
113
+ {% if aStride.v != 0 or bStride.v != 0 %}
114
+ let c{{ axis }} = zTmp % {{ outDim }}u;
115
+ {% endif %}
116
+ zTmp = zTmp / {{ outDim }}u;
117
+ {% if aStride.v != 0 %} aBatchOff = aBatchOff + c{{ axis }} * {{ aStride.v }}u;
118
+ {% endif %}
119
+ {% if bStride.v != 0 %} bBatchOff = bBatchOff + c{{ axis }} * {{ bStride.v }}u;
120
+ {% endif %}
121
+ {% endif %}
122
+ {% endfor %}
123
+
124
+ var acc00: {{ accT }} = {{ accT }}(0);
125
+ var acc01: {{ accT }} = {{ accT }}(0);
126
+ var acc10: {{ accT }} = {{ accT }}(0);
127
+ var acc11: {{ accT }} = {{ accT }}(0);
128
+ let numTiles = (K + BK - 1u) / BK;
129
+ for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
130
+ let kBase = kt * BK;
131
+ // Cooperative load: 32x16 A tile + 16x32 B tile, 256 threads x 2 each.
132
+ for (var e: u32 = 0u; e < 2u; e = e + 1u) {
133
+ let idx = li + e * 256u;
134
+ let ar = idx / BK;
135
+ let ac = idx % BK;
136
+ let am = mBase + ar;
137
+ let ak = kBase + ac;
138
+ if (am < M && ak < K) {
139
+ tileA[ar][ac] = {{ tileT }}(a[aBatchOff + am * A_M_STRIDE + ak * A_K_STRIDE]);
140
+ } else {
141
+ tileA[ar][ac] = {{ tileT }}(0);
142
+ }
143
+ let br = idx / BN;
144
+ let bc = idx % BN;
145
+ let bk = kBase + br;
146
+ let bn = nBase + bc;
147
+ if (bk < K && bn < N) {
148
+ tileB[br][bc] = {{ tileT }}(b[bBatchOff + bk * B_K_STRIDE + bn * B_N_STRIDE]);
149
+ } else {
150
+ tileB[br][bc] = {{ tileT }}(0);
151
+ }
152
+ }
153
+ workgroupBarrier();
154
+ for (var kk: u32 = 0u; kk < BK; kk = kk + 1u) {
155
+ let a0 = {{ accT }}(tileA[lid.y * 2u][kk]);
156
+ let a1 = {{ accT }}(tileA[lid.y * 2u + 1u][kk]);
157
+ let b0 = {{ accT }}(tileB[kk][lid.x * 2u]);
158
+ let b1 = {{ accT }}(tileB[kk][lid.x * 2u + 1u]);
159
+ acc00 = acc00 + a0 * b0;
160
+ acc01 = acc01 + a0 * b1;
161
+ acc10 = acc10 + a1 * b0;
162
+ acc11 = acc11 + a1 * b1;
163
+ }
164
+ workgroupBarrier();
165
+ }
166
+
167
+ let m0 = mBase + lid.y * 2u;
168
+ let m1 = m0 + 1u;
169
+ let n0 = nBase + lid.x * 2u;
170
+ let n1 = n0 + 1u;
171
+ let rowBase = zOut * M * N;
172
+ if (m0 < M && n0 < N) { y[rowBase + m0 * N + n0] = {{ scalar }}(ALPHA * acc00); }
173
+ if (m0 < M && n1 < N) { y[rowBase + m0 * N + n1] = {{ scalar }}(ALPHA * acc01); }
174
+ if (m1 < M && n0 < N) { y[rowBase + m1 * N + n0] = {{ scalar }}(ALPHA * acc10); }
175
+ if (m1 < M && n1 < N) { y[rowBase + m1 * N + n1] = {{ scalar }}(ALPHA * acc11); }
176
+ }
build/webgpu/matmul-vector-matrix-vec4.wgsl.jinja ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // GEMV specialization for y[N] = a[K] @ B[K, N]: each workgroup owns 32
2
+ // consecutive vec4 column groups (128 output columns). Shared by MatMul and
3
+ // matrix-shaped Einsum decode projections.
4
+ {% if usesF16 %}
5
+ enable f16;
6
+
7
+ {% endif %}
8
+ {{ env.wgsl.resourceDeclarations }}
9
+ {% set OUT = outputBuffer if outputBuffer is defined else "c" %}
10
+
11
+ const LANES: u32 = 32u;
12
+ // SLICES is chosen from the workgroup COUNT, not from a fixed default: this
13
+ // kernel launches ceilDiv(N, 128) workgroups, so a narrow-N projection starves
14
+ // the device unless each workgroup carries more of the K reduction.
15
+ const SLICES: u32 = {{ gemvSlices }}u;
16
+
17
+ var<workgroup> partials: array<vec4<f32>, LANES * SLICES>;
18
+
19
+ @compute @workgroup_size(32, {{ gemvSlices }}, 1)
20
+ fn main(
21
+ @builtin(workgroup_id) workgroup_id: vec3<u32>,
22
+ @builtin(local_invocation_id) lid: vec3<u32>
23
+ ) {
24
+ let lane = lid.x;
25
+ let slice = lid.y;
26
+ let cg = workgroup_id.x * LANES + lane;
27
+ var acc = vec4<f32>(0.0);
28
+ if (cg < params.N4) {
29
+ for (var k = slice; k < params.K; k = k + SLICES) {
30
+ acc = acc + f32(a[k]) * vec4<f32>(b[k * params.N4 + cg]);
31
+ }
32
+ }
33
+ partials[slice * LANES + lane] = acc;
34
+ workgroupBarrier();
35
+ if (slice == 0u && cg < params.N4) {
36
+ var total = partials[lane];
37
+ for (var s = 1u; s < SLICES; s = s + 1u) {
38
+ total = total + partials[s * LANES + lane];
39
+ }
40
+ {{ OUT }}[cg] = vec4<{{ T }}>(total);
41
+ }
42
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "com.microsoft.FusedMatMul",
3
+ "id": "_com_microsoft_fusedmatmul_webgpu_80c34de",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "uw58QO9KL2kJYYMWMZJSUGIwBmsy6bLImlj0E1NNCyw=",
11
+ "fused-matmul-subgroup-matrix.wgsl.jinja": "d8LaODNB5B+qCRM5SSDSbasoJTBoTkjR8BtwuO8AW6U=",
12
+ "manifest.json": "F7nDLMjrVNWkpIGc9rUB5ZrhRoW06xk+PPX+M6X7XCA=",
13
+ "matmul-subgroup-matrix-ext.wgsl.jinja": "Q/5RQ4YPRDz/EiGmw1HigsSCohWzsx6muR4ghzigfWs=",
14
+ "matmul-tiled-general-reg.wgsl.jinja": "8E6pn6tg6t7LuoU6ktkjL5AZOgrd0HHkFRe8VJC7jwk=",
15
+ "matmul-tiled-general.wgsl.jinja": "pqYgOQCvHqeTthCEShE8dbN1GJjpFz3LD1K3sBYARMc=",
16
+ "matmul-vector-matrix-vec4.wgsl.jinja": "9syYZdAgoDnCn5rurCHP9tHtlwm1IXwKvPoj48hH3+E=",
17
+ "test.json": "Au5pkdVg7fcbm2EDBuhQvc4CPiri5lrQs394cLC5iL0="
18
+ }
19
+ },
20
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
21
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.FusedMatMul" }
22
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,1420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "com.microsoft.FusedMatMul",
3
+ "fixtureArrays": {
4
+ "ort_float32_broadcast_rank3_by_rank4_output_Y": [1, 3, 5, 33, 43, 53, 5, 23, 41, 85, 111, 137, 9, 43, 77, 137, 179, 221],
5
+ "ort_float32_rank3_by_rank2_output_Y": [20, 23, 26, 29, 56, 68, 80, 92, 92, 113, 134, 155, 128, 158, 188, 218],
6
+ "ort_float32_batched_rank4_input_A": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
7
+ "ort_float32_trans_batch_a_input_A": [0, 1, 2, 3, 12, 13, 14, 15, 4, 5, 6, 7, 16, 17, 18, 19, 8, 9, 10, 11, 20, 21, 22, 23],
8
+ "ort_float32_trans_batch_b_input_B": [1, 0, 1, 2, 0, 1, -1, 0, 1, 1, 0, 1, 2, -1, 1, 1]
9
+ },
10
+ "cases": [
11
+ {
12
+ "name": "ort_float32_broadcast_rank4_by_rank3",
13
+ "provenance": {
14
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
15
+ "test": "FusedMatMulOpTest.FloatTypeNoTranspose"
16
+ },
17
+ "inputs": {
18
+ "A": {
19
+ "dtype": "float32",
20
+ "shape": [3, 1, 1, 2],
21
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] }
22
+ },
23
+ "B": {
24
+ "dtype": "float32",
25
+ "shape": [2, 2, 2],
26
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] }
27
+ }
28
+ },
29
+ "outputs": {
30
+ "Y": {
31
+ "dtype": "float32",
32
+ "shape": [3, 2, 1, 2],
33
+ "tolerance": 0.000001,
34
+ "data": { "kind": "values", "values": [2.0, 3.0, 6.0, 7.0, 6.0, 11.0, 26.0, 31.0, 10.0, 19.0, 46.0, 55.0] }
35
+ }
36
+ }
37
+ },
38
+ {
39
+ "name": "ort_float32_broadcast_rank3_by_rank4",
40
+ "provenance": {
41
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
42
+ "test": "FusedMatMulOpTest.FloatTypeNoTranspose"
43
+ },
44
+ "inputs": {
45
+ "A": {
46
+ "dtype": "float32",
47
+ "shape": [2, 3, 2],
48
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] }
49
+ },
50
+ "B": {
51
+ "dtype": "float32",
52
+ "shape": [3, 2, 2, 1],
53
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] }
54
+ }
55
+ },
56
+ "outputs": {
57
+ "Y": {
58
+ "dtype": "float32",
59
+ "shape": [3, 2, 3, 1],
60
+ "tolerance": 0.000001,
61
+ "data": {
62
+ "kind": "values",
63
+ "values": { "$ref": "#/fixtureArrays/ort_float32_broadcast_rank3_by_rank4_output_Y" }
64
+ }
65
+ }
66
+ }
67
+ },
68
+ {
69
+ "name": "ort_float32_left_1d_batched_rhs",
70
+ "provenance": {
71
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
72
+ "test": "FusedMatMulOpTest.FloatTypeNoTranspose"
73
+ },
74
+ "inputs": {
75
+ "A": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 1.0] } },
76
+ "B": {
77
+ "dtype": "float32",
78
+ "shape": [3, 2, 1],
79
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] }
80
+ }
81
+ },
82
+ "outputs": {
83
+ "Y": {
84
+ "dtype": "float32",
85
+ "shape": [3, 1],
86
+ "tolerance": 0.000001,
87
+ "data": { "kind": "values", "values": [1.0, 3.0, 5.0] }
88
+ }
89
+ }
90
+ },
91
+ {
92
+ "name": "ort_float32_right_1d_batched_lhs",
93
+ "provenance": {
94
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
95
+ "test": "FusedMatMulOpTest.FloatTypeNoTranspose"
96
+ },
97
+ "inputs": {
98
+ "A": {
99
+ "dtype": "float32",
100
+ "shape": [3, 1, 2],
101
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] }
102
+ },
103
+ "B": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 1.0] } }
104
+ },
105
+ "outputs": {
106
+ "Y": {
107
+ "dtype": "float32",
108
+ "shape": [3, 1],
109
+ "tolerance": 0.000001,
110
+ "data": { "kind": "values", "values": [1.0, 3.0, 5.0] }
111
+ }
112
+ }
113
+ },
114
+ {
115
+ "name": "ort_float32_plain_2d",
116
+ "provenance": {
117
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
118
+ "test": "FusedMatMulOpTest.FloatTypeNoTranspose"
119
+ },
120
+ "inputs": {
121
+ "A": {
122
+ "dtype": "float32",
123
+ "shape": [3, 4],
124
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] }
125
+ },
126
+ "B": {
127
+ "dtype": "float32",
128
+ "shape": [4, 3],
129
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] }
130
+ }
131
+ },
132
+ "outputs": {
133
+ "Y": {
134
+ "dtype": "float32",
135
+ "shape": [3, 3],
136
+ "tolerance": 0.000001,
137
+ "data": { "kind": "values", "values": [42.0, 48.0, 54.0, 114.0, 136.0, 158.0, 186.0, 224.0, 262.0] }
138
+ }
139
+ }
140
+ },
141
+ {
142
+ "name": "ort_float32_rank3_by_rank2",
143
+ "provenance": {
144
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
145
+ "test": "FusedMatMulOpTest.FloatTypeNoTranspose"
146
+ },
147
+ "inputs": {
148
+ "A": {
149
+ "dtype": "float32",
150
+ "shape": [2, 2, 3],
151
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] }
152
+ },
153
+ "B": {
154
+ "dtype": "float32",
155
+ "shape": [3, 4],
156
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] }
157
+ }
158
+ },
159
+ "outputs": {
160
+ "Y": {
161
+ "dtype": "float32",
162
+ "shape": [2, 2, 4],
163
+ "tolerance": 0.000001,
164
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_float32_rank3_by_rank2_output_Y" } }
165
+ }
166
+ }
167
+ },
168
+ {
169
+ "name": "ort_float32_rank3_by_broadcast_rank3",
170
+ "provenance": {
171
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
172
+ "test": "FusedMatMulOpTest.FloatTypeNoTranspose"
173
+ },
174
+ "inputs": {
175
+ "A": {
176
+ "dtype": "float32",
177
+ "shape": [2, 2, 3],
178
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] }
179
+ },
180
+ "B": {
181
+ "dtype": "float32",
182
+ "shape": [1, 3, 4],
183
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] }
184
+ }
185
+ },
186
+ "outputs": {
187
+ "Y": {
188
+ "dtype": "float32",
189
+ "shape": [2, 2, 4],
190
+ "tolerance": 0.000001,
191
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_float32_rank3_by_rank2_output_Y" } }
192
+ }
193
+ }
194
+ },
195
+ {
196
+ "name": "ort_float32_singleton_rank3_by_rank3",
197
+ "provenance": {
198
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
199
+ "test": "FusedMatMulOpTest.FloatTypeNoTranspose"
200
+ },
201
+ "inputs": {
202
+ "A": {
203
+ "dtype": "float32",
204
+ "shape": [1, 2, 3],
205
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] }
206
+ },
207
+ "B": {
208
+ "dtype": "float32",
209
+ "shape": [1, 3, 4],
210
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] }
211
+ }
212
+ },
213
+ "outputs": {
214
+ "Y": {
215
+ "dtype": "float32",
216
+ "shape": [1, 2, 4],
217
+ "tolerance": 0.000001,
218
+ "data": { "kind": "values", "values": [20.0, 23.0, 26.0, 29.0, 56.0, 68.0, 80.0, 92.0] }
219
+ }
220
+ }
221
+ },
222
+ {
223
+ "name": "ort_float32_batched_rank4",
224
+ "provenance": {
225
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
226
+ "test": "FusedMatMulOpTest.FloatTypeNoTranspose"
227
+ },
228
+ "inputs": {
229
+ "A": {
230
+ "dtype": "float32",
231
+ "shape": [2, 2, 2, 2],
232
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_float32_batched_rank4_input_A" } }
233
+ },
234
+ "B": {
235
+ "dtype": "float32",
236
+ "shape": [2, 2, 2, 2],
237
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_float32_batched_rank4_input_A" } }
238
+ }
239
+ },
240
+ "outputs": {
241
+ "Y": {
242
+ "dtype": "float32",
243
+ "shape": [2, 2, 2, 2],
244
+ "tolerance": 0.000001,
245
+ "data": {
246
+ "kind": "values",
247
+ "values": [2.0, 3.0, 6.0, 11.0, 46.0, 55.0, 66.0, 79.0, 154.0, 171.0, 190.0, 211.0, 326.0, 351.0, 378.0, 407.0]
248
+ }
249
+ }
250
+ }
251
+ },
252
+ {
253
+ "name": "ort_float32_broadcast_rank4_by_rank4",
254
+ "provenance": {
255
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
256
+ "test": "FusedMatMulOpTest.FloatTypeNoTranspose"
257
+ },
258
+ "inputs": {
259
+ "A": {
260
+ "dtype": "float32",
261
+ "shape": [1, 2, 3, 2],
262
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] }
263
+ },
264
+ "B": {
265
+ "dtype": "float32",
266
+ "shape": [3, 2, 2, 1],
267
+ "data": { "kind": "values", "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0] }
268
+ }
269
+ },
270
+ "outputs": {
271
+ "Y": {
272
+ "dtype": "float32",
273
+ "shape": [3, 2, 3, 1],
274
+ "tolerance": 0.000001,
275
+ "data": {
276
+ "kind": "values",
277
+ "values": { "$ref": "#/fixtureArrays/ort_float32_broadcast_rank3_by_rank4_output_Y" }
278
+ }
279
+ }
280
+ }
281
+ },
282
+ {
283
+ "name": "ort_float32_vector_dot_scalar_output",
284
+ "provenance": {
285
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
286
+ "test": "FusedMatMulOpTest.FloatTypeNoTranspose"
287
+ },
288
+ "inputs": {
289
+ "A": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [0.0, 1.0, 2.0] } },
290
+ "B": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [0.0, 1.0, 2.0] } }
291
+ },
292
+ "outputs": {
293
+ "Y": { "dtype": "float32", "shape": [], "tolerance": 0.000001, "data": { "kind": "values", "values": [5.0] } }
294
+ }
295
+ },
296
+ {
297
+ "name": "ort_float32_alpha_zero_outputs_zero",
298
+ "provenance": {
299
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
300
+ "test": "FusedMatMulOpTest.DoubleTypeAlphaZero"
301
+ },
302
+ "attrs": { "alpha": 0 },
303
+ "inputs": {
304
+ "A": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0] } },
305
+ "B": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [5.0, 6.0, 7.0, 8.0] } }
306
+ },
307
+ "outputs": {
308
+ "Y": {
309
+ "dtype": "float32",
310
+ "shape": [2, 2],
311
+ "tolerance": 0.000001,
312
+ "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0] }
313
+ }
314
+ }
315
+ },
316
+ {
317
+ "name": "ort_float32_empty_k_dimension_outputs_zero",
318
+ "provenance": {
319
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
320
+ "test": "FusedMatMulOpTest.DoubleTypeEmptyKDim"
321
+ },
322
+ "inputs": {
323
+ "A": { "dtype": "float32", "shape": [2, 0], "data": { "kind": "values", "values": [] } },
324
+ "B": { "dtype": "float32", "shape": [0, 3], "data": { "kind": "values", "values": [] } }
325
+ },
326
+ "outputs": {
327
+ "Y": {
328
+ "dtype": "float32",
329
+ "shape": [2, 3],
330
+ "tolerance": 0.000001,
331
+ "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] }
332
+ }
333
+ }
334
+ },
335
+ {
336
+ "name": "ort_float32_transpose_a_scaled",
337
+ "provenance": {
338
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
339
+ "test": "FusedMatMulOpTest.DoubleTypeScale"
340
+ },
341
+ "attrs": { "alpha": 0.5, "transA": 1 },
342
+ "inputs": {
343
+ "A": {
344
+ "dtype": "float32",
345
+ "shape": [2, 3],
346
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] }
347
+ },
348
+ "B": {
349
+ "dtype": "float32",
350
+ "shape": [2, 3],
351
+ "data": { "kind": "values", "values": [7.0, 8.0, 9.0, 10.0, 11.0, 12.0] }
352
+ }
353
+ },
354
+ "outputs": {
355
+ "Y": {
356
+ "dtype": "float32",
357
+ "shape": [3, 3],
358
+ "tolerance": 0.000001,
359
+ "data": { "kind": "values", "values": [23.5, 26.0, 28.5, 32.0, 35.5, 39.0, 40.5, 45.0, 49.5] }
360
+ }
361
+ }
362
+ },
363
+ {
364
+ "name": "ort_float32_transpose_b",
365
+ "provenance": {
366
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
367
+ "test": "FusedMatMulOpTest.FloatTypeTransposeB"
368
+ },
369
+ "attrs": { "transB": 1 },
370
+ "inputs": {
371
+ "A": {
372
+ "dtype": "float32",
373
+ "shape": [2, 3],
374
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] }
375
+ },
376
+ "B": {
377
+ "dtype": "float32",
378
+ "shape": [4, 3],
379
+ "data": { "kind": "values", "values": [7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0] }
380
+ }
381
+ },
382
+ "outputs": {
383
+ "Y": {
384
+ "dtype": "float32",
385
+ "shape": [2, 4],
386
+ "tolerance": 0.000001,
387
+ "data": { "kind": "values", "values": [50.0, 68.0, 86.0, 104.0, 122.0, 167.0, 212.0, 257.0] }
388
+ }
389
+ }
390
+ },
391
+ {
392
+ "name": "ort_float32_transpose_ab_scaled",
393
+ "provenance": {
394
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
395
+ "test": "FusedMatMulOpTest.FloatTypeScale"
396
+ },
397
+ "attrs": { "alpha": 4, "transA": 1, "transB": 1 },
398
+ "inputs": {
399
+ "A": {
400
+ "dtype": "float32",
401
+ "shape": [3, 2],
402
+ "data": { "kind": "values", "values": [1.0, 4.0, 2.0, 5.0, 3.0, 6.0] }
403
+ },
404
+ "B": {
405
+ "dtype": "float32",
406
+ "shape": [4, 3],
407
+ "data": { "kind": "values", "values": [7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0] }
408
+ }
409
+ },
410
+ "outputs": {
411
+ "Y": {
412
+ "dtype": "float32",
413
+ "shape": [2, 4],
414
+ "tolerance": 0.000001,
415
+ "data": { "kind": "values", "values": [200.0, 272.0, 344.0, 416.0, 488.0, 668.0, 848.0, 1028.0] }
416
+ }
417
+ }
418
+ },
419
+ {
420
+ "name": "ort_float32_trans_batch_a",
421
+ "provenance": {
422
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
423
+ "test": "FusedMatMulOpTest.FloatTypeTransposeBatch",
424
+ "notes": "Exercises transBatchA, where logical [batch,N,M] input A is stored as [N,batch,M]."
425
+ },
426
+ "attrs": { "transBatchA": 1 },
427
+ "inputs": {
428
+ "A": {
429
+ "dtype": "float32",
430
+ "shape": [3, 2, 4],
431
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_float32_trans_batch_a_input_A" } }
432
+ },
433
+ "B": {
434
+ "dtype": "float32",
435
+ "shape": [2, 4, 2],
436
+ "data": {
437
+ "kind": "values",
438
+ "values": [1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, -1.0, 1.0, 2.0, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0]
439
+ }
440
+ }
441
+ },
442
+ "outputs": {
443
+ "Y": {
444
+ "dtype": "float32",
445
+ "shape": [2, 3, 2],
446
+ "tolerance": 0.000001,
447
+ "data": { "kind": "values", "values": [8.0, 0.0, 24.0, 4.0, 40.0, 8.0, 14.0, 53.0, 18.0, 69.0, 22.0, 85.0] }
448
+ }
449
+ }
450
+ },
451
+ {
452
+ "name": "ort_float32_trans_batch_b",
453
+ "provenance": {
454
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
455
+ "test": "FusedMatMulOpTest.FloatTypeTransposeBatch",
456
+ "notes": "Exercises transBatchB, where logical [batch,K,N] input B is stored as [K,batch,N]."
457
+ },
458
+ "attrs": { "transBatchB": 1 },
459
+ "inputs": {
460
+ "A": {
461
+ "dtype": "float32",
462
+ "shape": [2, 3, 4],
463
+ "data": {
464
+ "kind": "values",
465
+ "values": [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0]
466
+ }
467
+ },
468
+ "B": {
469
+ "dtype": "float32",
470
+ "shape": [4, 2, 2],
471
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_float32_trans_batch_b_input_B" } }
472
+ }
473
+ },
474
+ "outputs": { "Y": { "dtype": "float32", "shape": [2, 3, 2], "tolerance": 0.000001 } }
475
+ },
476
+ {
477
+ "name": "ort_float32_trans_batch_ab_scaled",
478
+ "provenance": {
479
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
480
+ "test": "FusedMatMulOpTest.FloatTypeTransposeBatch",
481
+ "notes": "Exercises combined transBatchA and transBatchB with alpha scaling."
482
+ },
483
+ "attrs": { "transBatchA": 1, "transBatchB": 1, "alpha": 0.5 },
484
+ "inputs": {
485
+ "A": {
486
+ "dtype": "float32",
487
+ "shape": [3, 2, 4],
488
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_float32_trans_batch_a_input_A" } }
489
+ },
490
+ "B": {
491
+ "dtype": "float32",
492
+ "shape": [4, 2, 2],
493
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_float32_trans_batch_b_input_B" } }
494
+ }
495
+ },
496
+ "outputs": {
497
+ "Y": {
498
+ "dtype": "float32",
499
+ "shape": [2, 3, 2],
500
+ "tolerance": 0.000001,
501
+ "data": { "kind": "values", "values": [4.0, 0.0, 12.0, 2.0, 20.0, 4.0, 7.0, 26.5, 9.0, 34.5, 11.0, 42.5] }
502
+ }
503
+ }
504
+ },
505
+ {
506
+ "name": "ort_float32_scaled_no_transpose",
507
+ "provenance": {
508
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
509
+ "test": "FusedMatMulOpTest.FloatTypeScale"
510
+ },
511
+ "attrs": { "alpha": 0.5 },
512
+ "inputs": {
513
+ "A": {
514
+ "dtype": "float32",
515
+ "shape": [2, 3],
516
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] }
517
+ },
518
+ "B": {
519
+ "dtype": "float32",
520
+ "shape": [3, 2],
521
+ "data": { "kind": "values", "values": [7.0, 8.0, 9.0, 10.0, 11.0, 12.0] }
522
+ }
523
+ },
524
+ "outputs": {
525
+ "Y": {
526
+ "dtype": "float32",
527
+ "shape": [2, 2],
528
+ "tolerance": 0.000001,
529
+ "data": { "kind": "values", "values": [29.0, 32.0, 69.5, 77.0] }
530
+ }
531
+ }
532
+ },
533
+ {
534
+ "name": "ort_float32_empty_input_m_zero",
535
+ "provenance": {
536
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
537
+ "test": "FusedMatMulOpTest.DoubleTypeEmptyInput"
538
+ },
539
+ "inputs": {
540
+ "A": { "dtype": "float32", "shape": [0, 3], "data": { "kind": "values", "values": [] } },
541
+ "B": {
542
+ "dtype": "float32",
543
+ "shape": [3, 4],
544
+ "data": { "kind": "values", "values": [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0] }
545
+ }
546
+ },
547
+ "outputs": {
548
+ "Y": { "dtype": "float32", "shape": [0, 4], "tolerance": 0.000001, "data": { "kind": "values", "values": [] } }
549
+ }
550
+ },
551
+ {
552
+ "name": "aligned_plain_64x32x64",
553
+ "inputs": {
554
+ "A": {
555
+ "dtype": "float32",
556
+ "shape": [64, 32],
557
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.2 }
558
+ },
559
+ "B": {
560
+ "dtype": "float32",
561
+ "shape": [32, 64],
562
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.007, "scale": 0.2 }
563
+ }
564
+ },
565
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 64], "tolerance": 0.0001 } }
566
+ },
567
+ {
568
+ "name": "register_blocked_plain_512x64x512_alpha_scaled",
569
+ "inputs": {
570
+ "A": {
571
+ "dtype": "float32",
572
+ "shape": [512, 64],
573
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.2 }
574
+ },
575
+ "B": {
576
+ "dtype": "float32",
577
+ "shape": [64, 512],
578
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.007, "scale": 0.2 }
579
+ }
580
+ },
581
+ "outputs": { "Y": { "dtype": "float32", "shape": [512, 512], "tolerance": 0.0001 } },
582
+ "attrs": { "alpha": 0.5 },
583
+ "provenance": {
584
+ "notes": "Rank-2 M=N=512 and K=64 produce 64 aligned 64x64 workgroup tiles, exercising register-blocked vec4 staging and 4x4 per-thread accumulation. alpha=0.5 verifies scaling in the output epilogue."
585
+ }
586
+ },
587
+ {
588
+ "name": "aligned_transB_alpha_64x32",
589
+ "attrs": { "transB": 1, "alpha": 0.5 },
590
+ "inputs": {
591
+ "A": {
592
+ "dtype": "float32",
593
+ "shape": [64, 32],
594
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.023, "scale": 0.2 }
595
+ },
596
+ "B": {
597
+ "dtype": "float32",
598
+ "shape": [64, 32],
599
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.009, "scale": 0.2 }
600
+ }
601
+ },
602
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 64], "tolerance": 0.0001 } }
603
+ },
604
+ {
605
+ "name": "aligned_batched_plain_2x64x32x64",
606
+ "inputs": {
607
+ "A": {
608
+ "dtype": "float32",
609
+ "shape": [2, 64, 32],
610
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.2 }
611
+ },
612
+ "B": {
613
+ "dtype": "float32",
614
+ "shape": [2, 32, 64],
615
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.007, "scale": 0.2 }
616
+ }
617
+ },
618
+ "outputs": { "Y": { "dtype": "float32", "shape": [2, 64, 64], "tolerance": 0.0001 } }
619
+ },
620
+ {
621
+ "name": "aligned_batched_transB_alpha_2x64x32",
622
+ "attrs": { "transB": 1, "alpha": 0.25 },
623
+ "inputs": {
624
+ "A": {
625
+ "dtype": "float32",
626
+ "shape": [2, 64, 32],
627
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.023, "scale": 0.2 }
628
+ },
629
+ "B": {
630
+ "dtype": "float32",
631
+ "shape": [2, 64, 32],
632
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.009, "scale": 0.2 }
633
+ }
634
+ },
635
+ "outputs": { "Y": { "dtype": "float32", "shape": [2, 64, 64], "tolerance": 0.0001 } }
636
+ },
637
+ {
638
+ "name": "aligned_mtail_50x32x128",
639
+ "inputs": {
640
+ "A": {
641
+ "dtype": "float32",
642
+ "shape": [50, 32],
643
+ "data": { "kind": "fillFloat32", "sinStep": 0.015, "cosStep": 0.021, "scale": 0.2 }
644
+ },
645
+ "B": {
646
+ "dtype": "float32",
647
+ "shape": [32, 128],
648
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.011, "scale": 0.2 }
649
+ }
650
+ },
651
+ "outputs": { "Y": { "dtype": "float32", "shape": [50, 128], "tolerance": 0.0001 } }
652
+ },
653
+ {
654
+ "name": "aligned_transA_64x32",
655
+ "attrs": { "transA": 1 },
656
+ "inputs": {
657
+ "A": {
658
+ "dtype": "float32",
659
+ "shape": [32, 64],
660
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.2 }
661
+ },
662
+ "B": {
663
+ "dtype": "float32",
664
+ "shape": [32, 64],
665
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.007, "scale": 0.2 }
666
+ }
667
+ },
668
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 64], "tolerance": 0.0001 } }
669
+ },
670
+ {
671
+ "name": "aligned_transA_transB_alpha_64x32",
672
+ "attrs": { "transA": 1, "transB": 1, "alpha": 0.5 },
673
+ "inputs": {
674
+ "A": {
675
+ "dtype": "float32",
676
+ "shape": [32, 64],
677
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.023, "scale": 0.2 }
678
+ },
679
+ "B": {
680
+ "dtype": "float32",
681
+ "shape": [64, 32],
682
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.009, "scale": 0.2 }
683
+ }
684
+ },
685
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 64], "tolerance": 0.0001 } }
686
+ },
687
+ {
688
+ "name": "f32_subgroup_matrix_subnormal_dot_products_gpu_gap",
689
+ "skipGpu": {
690
+ "category": "permanent",
691
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: WebGPU/Metal flushes subnormals to zero in f32; the ~3e-39 subnormal dot products collapse to zero (subgroup-matrix path)."
692
+ },
693
+ "provenance": {
694
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
695
+ "test": "FusedMatMulOpTest.FloatTypeNoTranspose",
696
+ "notes": "M=32, K=32, N=64 selects the subgroup-matrix path; finite subnormal dot products must not flush to zero."
697
+ },
698
+ "inputs": {
699
+ "A": { "dtype": "float32", "shape": [32, 32], "data": { "kind": "constant", "value": 1e-20 } },
700
+ "B": { "dtype": "float32", "shape": [32, 64], "data": { "kind": "constant", "value": 1e-20 } }
701
+ },
702
+ "outputs": { "Y": { "dtype": "float32", "shape": [32, 64], "tolerance": 1e-43 } }
703
+ },
704
+ {
705
+ "name": "f32_subgroup_matrix_scaled_subnormal_dot_products_gpu_gap",
706
+ "skipGpu": {
707
+ "category": "permanent",
708
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: WebGPU/Metal flushes subnormals to zero in f32; the ~3e-39 subnormal dot products collapse to zero before alpha scaling (subgroup-matrix path)."
709
+ },
710
+ "provenance": {
711
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
712
+ "test": "FusedMatMulOpTest.FloatTypeScale",
713
+ "notes": "Alpha scaling is applied after accumulation, so finite subnormal products remain valid nonzero outputs."
714
+ },
715
+ "attrs": { "alpha": 0.5 },
716
+ "inputs": {
717
+ "A": { "dtype": "float32", "shape": [32, 32], "data": { "kind": "constant", "value": 1e-20 } },
718
+ "B": { "dtype": "float32", "shape": [32, 64], "data": { "kind": "constant", "value": 1e-20 } }
719
+ },
720
+ "outputs": { "Y": { "dtype": "float32", "shape": [32, 64], "tolerance": 1e-43 } }
721
+ },
722
+ {
723
+ "name": "f32_subgroup_matrix_transB_subnormal_dot_products_gpu_gap",
724
+ "skipGpu": {
725
+ "category": "permanent",
726
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: WebGPU/Metal flushes subnormals to zero in f32; the ~3e-39 subnormal dot products collapse to zero (subgroup-matrix transB path)."
727
+ },
728
+ "provenance": {
729
+ "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
730
+ "test": "FusedMatMulOpTest.FloatTypeTransposeB",
731
+ "notes": "The transposed-B subgroup-matrix path has the same finite subnormal accumulation requirement."
732
+ },
733
+ "attrs": { "transB": 1 },
734
+ "inputs": {
735
+ "A": { "dtype": "float32", "shape": [32, 32], "data": { "kind": "constant", "value": 1e-20 } },
736
+ "B": { "dtype": "float32", "shape": [64, 32], "data": { "kind": "constant", "value": 1e-20 } }
737
+ },
738
+ "outputs": { "Y": { "dtype": "float32", "shape": [32, 64], "tolerance": 1e-43 } }
739
+ },
740
+ {
741
+ "name": "aligned_f16_plain_64x32x64",
742
+ "inputs": {
743
+ "A": {
744
+ "dtype": "float16",
745
+ "shape": [64, 32],
746
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.2 }
747
+ },
748
+ "B": {
749
+ "dtype": "float16",
750
+ "shape": [32, 64],
751
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.007, "scale": 0.2 }
752
+ }
753
+ },
754
+ "outputs": { "Y": { "dtype": "float16", "shape": [64, 64], "tolerance": 0.02 } }
755
+ },
756
+ {
757
+ "name": "aligned_f16_transB_alpha_64x32",
758
+ "attrs": { "transB": 1, "alpha": 0.5 },
759
+ "inputs": {
760
+ "A": {
761
+ "dtype": "float16",
762
+ "shape": [64, 32],
763
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.023, "scale": 0.2 }
764
+ },
765
+ "B": {
766
+ "dtype": "float16",
767
+ "shape": [64, 32],
768
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.009, "scale": 0.2 }
769
+ }
770
+ },
771
+ "outputs": { "Y": { "dtype": "float16", "shape": [64, 64], "tolerance": 0.02 } }
772
+ },
773
+ {
774
+ "name": "f16_unaligned_3x5x7",
775
+ "inputs": {
776
+ "A": {
777
+ "dtype": "float16",
778
+ "shape": [3, 5],
779
+ "data": { "kind": "fillFloat32", "sinStep": 0.015, "cosStep": 0.021, "scale": 0.2 }
780
+ },
781
+ "B": {
782
+ "dtype": "float16",
783
+ "shape": [5, 7],
784
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.011, "scale": 0.2 }
785
+ }
786
+ },
787
+ "outputs": { "Y": { "dtype": "float16", "shape": [3, 7], "tolerance": 0.02 } }
788
+ },
789
+ {
790
+ "name": "aligned_f16_transA_64x32",
791
+ "attrs": { "transA": 1 },
792
+ "inputs": {
793
+ "A": {
794
+ "dtype": "float16",
795
+ "shape": [32, 64],
796
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.2 }
797
+ },
798
+ "B": {
799
+ "dtype": "float16",
800
+ "shape": [32, 64],
801
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.007, "scale": 0.2 }
802
+ }
803
+ },
804
+ "outputs": { "Y": { "dtype": "float16", "shape": [64, 64], "tolerance": 0.03 } }
805
+ },
806
+ {
807
+ "name": "aligned_f16_batched_plain_2x64x32x64",
808
+ "inputs": {
809
+ "A": {
810
+ "dtype": "float16",
811
+ "shape": [2, 64, 32],
812
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.2 }
813
+ },
814
+ "B": {
815
+ "dtype": "float16",
816
+ "shape": [2, 32, 64],
817
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.007, "scale": 0.2 }
818
+ }
819
+ },
820
+ "outputs": { "Y": { "dtype": "float16", "shape": [2, 64, 64], "tolerance": 0.03 } }
821
+ },
822
+ {
823
+ "name": "f16_rank3_by_broadcast_rank3",
824
+ "inputs": {
825
+ "A": {
826
+ "dtype": "float16",
827
+ "shape": [2, 2, 3],
828
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.2 }
829
+ },
830
+ "B": {
831
+ "dtype": "float16",
832
+ "shape": [1, 3, 4],
833
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.007, "scale": 0.2 }
834
+ }
835
+ },
836
+ "outputs": { "Y": { "dtype": "float16", "shape": [2, 2, 4], "tolerance": 0.02 } }
837
+ },
838
+ {
839
+ "name": "aligned_f16_transA_transB_alpha_64x32",
840
+ "attrs": { "transA": 1, "transB": 1, "alpha": 0.5 },
841
+ "inputs": {
842
+ "A": {
843
+ "dtype": "float16",
844
+ "shape": [32, 64],
845
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.023, "scale": 0.2 }
846
+ },
847
+ "B": {
848
+ "dtype": "float16",
849
+ "shape": [64, 32],
850
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.009, "scale": 0.2 }
851
+ }
852
+ },
853
+ "outputs": { "Y": { "dtype": "float16", "shape": [64, 64], "tolerance": 0.03 } }
854
+ },
855
+ {
856
+ "name": "subgroup_matrix_m_tail_57_partial_block_f16",
857
+ "inputs": {
858
+ "A": {
859
+ "dtype": "float16",
860
+ "shape": [57, 32],
861
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.023, "scale": 0.2 }
862
+ },
863
+ "B": {
864
+ "dtype": "float16",
865
+ "shape": [32, 64],
866
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.011, "scale": 0.2 }
867
+ }
868
+ },
869
+ "outputs": { "Y": { "dtype": "float16", "shape": [57, 64], "tolerance": 0.03 } }
870
+ },
871
+ {
872
+ "name": "subgroup_matrix_m_tail_33_alpha_scaled_f32",
873
+ "attrs": { "alpha": 0.5 },
874
+ "inputs": {
875
+ "A": {
876
+ "dtype": "float32",
877
+ "shape": [33, 32],
878
+ "data": { "kind": "fillFloat32", "sinStep": 0.015, "cosStep": 0.021, "scale": 0.2 }
879
+ },
880
+ "B": {
881
+ "dtype": "float32",
882
+ "shape": [32, 64],
883
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.019, "scale": 0.2 }
884
+ }
885
+ },
886
+ "outputs": { "Y": { "dtype": "float32", "shape": [33, 64], "tolerance": 0.0002 } }
887
+ },
888
+ {
889
+ "name": "trans_batch_a_broadcast_rank4_f32",
890
+ "attrs": { "transBatchA": 1 },
891
+ "inputs": {
892
+ "A": {
893
+ "dtype": "float32",
894
+ "shape": [2, 3, 2, 4],
895
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.2 }
896
+ },
897
+ "B": {
898
+ "dtype": "float32",
899
+ "shape": [3, 1, 4, 5],
900
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.007, "scale": 0.2 }
901
+ }
902
+ },
903
+ "outputs": { "Y": { "dtype": "float32", "shape": [3, 2, 2, 5], "tolerance": 0.0002 } }
904
+ },
905
+ {
906
+ "name": "empty_n_dimension_zero_width_output",
907
+ "inputs": {
908
+ "A": {
909
+ "dtype": "float32",
910
+ "shape": [3, 4],
911
+ "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0] }
912
+ },
913
+ "B": { "dtype": "float32", "shape": [4, 0], "data": { "kind": "values", "values": [] } }
914
+ },
915
+ "outputs": {
916
+ "Y": { "dtype": "float32", "shape": [3, 0], "tolerance": 0.000001, "data": { "kind": "values", "values": [] } }
917
+ }
918
+ },
919
+ {
920
+ "name": "transA_transB_subgroup_matrix_m_tail_50_f16",
921
+ "attrs": { "transA": 1, "transB": 1, "alpha": 1 },
922
+ "inputs": {
923
+ "A": {
924
+ "dtype": "float16",
925
+ "shape": [32, 50],
926
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.023, "scale": 0.2 }
927
+ },
928
+ "B": {
929
+ "dtype": "float16",
930
+ "shape": [64, 32],
931
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.009, "scale": 0.2 }
932
+ }
933
+ },
934
+ "outputs": { "Y": { "dtype": "float16", "shape": [50, 64], "tolerance": 0.05 } }
935
+ },
936
+ {
937
+ "name": "f32_decode_gemv_m1_k65_n68_vec4_compact",
938
+ "provenance": {
939
+ "notes": "Compact M=1 float32 GEMV correctness lock for the model-shaped K=4096,N=4096 bandwidth-bound benchmark. Odd K preserves the sliced reduction while N=68 exercises the final partial 128-column workgroup."
940
+ },
941
+ "attrs": { "alpha": 1 },
942
+ "inputs": {
943
+ "A": {
944
+ "dtype": "float32",
945
+ "shape": [1, 65],
946
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.2 }
947
+ },
948
+ "B": {
949
+ "dtype": "float32",
950
+ "shape": [65, 68],
951
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.2 }
952
+ }
953
+ },
954
+ "outputs": { "Y": { "dtype": "float32", "shape": [1, 68], "tolerance": 0.0002 } }
955
+ },
956
+ {
957
+ "name": "f32_rank4_by_rank2_shared_weight_compact",
958
+ "provenance": {
959
+ "notes": "Compact rank-4 by rank-2 shared-weight broadcast lock for the attention-shaped benchmark. Odd M/K/N exercise batch offset and tile-tail handling."
960
+ },
961
+ "attrs": { "alpha": 0.5 },
962
+ "inputs": {
963
+ "A": {
964
+ "dtype": "float32",
965
+ "shape": [2, 3, 5, 7],
966
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.2 }
967
+ },
968
+ "B": {
969
+ "dtype": "float32",
970
+ "shape": [7, 9],
971
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.2 }
972
+ }
973
+ },
974
+ "outputs": { "Y": { "dtype": "float32", "shape": [2, 3, 5, 9], "tolerance": 0.0002 } }
975
+ },
976
+ {
977
+ "name": "subgroup_matrix_kn_tail_f16_compact",
978
+ "provenance": { "notes": "Compact f16 subgroup-matrix lock with both a partial K=34 tile and N=66 output tail." },
979
+ "attrs": { "alpha": 0.5 },
980
+ "inputs": {
981
+ "A": {
982
+ "dtype": "float16",
983
+ "shape": [33, 34],
984
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.1 }
985
+ },
986
+ "B": {
987
+ "dtype": "float16",
988
+ "shape": [34, 66],
989
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.1 }
990
+ }
991
+ },
992
+ "outputs": { "Y": { "dtype": "float16", "shape": [33, 66], "tolerance": 0.05 } }
993
+ },
994
+ {
995
+ "name": "subgroup_matrix_broadcast_rank4x3_f16_compact",
996
+ "provenance": {
997
+ "notes": "Compact rank-4 by rank-3 broadcast lock for the model-shaped [1,8,M,K] x [8,K,N] stress case."
998
+ },
999
+ "attrs": { "alpha": 1 },
1000
+ "inputs": {
1001
+ "A": {
1002
+ "dtype": "float16",
1003
+ "shape": [1, 2, 33, 32],
1004
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.1 }
1005
+ },
1006
+ "B": {
1007
+ "dtype": "float16",
1008
+ "shape": [2, 32, 64],
1009
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.1 }
1010
+ }
1011
+ },
1012
+ "outputs": { "Y": { "dtype": "float16", "shape": [1, 2, 33, 64], "tolerance": 0.05 } }
1013
+ },
1014
+ {
1015
+ "name": "broadcast_rank4_tiled_reg_f16_compact",
1016
+ "provenance": {
1017
+ "notes": "Compact rank-4 by rank-3 broadcast lock for the register-blocked non-subgroup-matrix path. Odd M/K/N exercise every output and reduction tail."
1018
+ },
1019
+ "attrs": { "alpha": 0.5 },
1020
+ "inputs": {
1021
+ "A": {
1022
+ "dtype": "float16",
1023
+ "shape": [1, 2, 65, 33],
1024
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.1 }
1025
+ },
1026
+ "B": {
1027
+ "dtype": "float16",
1028
+ "shape": [2, 33, 67],
1029
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.1 }
1030
+ }
1031
+ },
1032
+ "outputs": { "Y": { "dtype": "float16", "shape": [1, 2, 65, 67], "tolerance": 0.05 } }
1033
+ },
1034
+ {
1035
+ "name": "broadcast_rank4_tiled_reg_shared_f32_compact",
1036
+ "provenance": {
1037
+ "notes": "Float32 shared rank-2 weight counterpart for the register-blocked rank-4 path. Odd M/K/N cover all output and reduction tails."
1038
+ },
1039
+ "attrs": { "alpha": 0.5 },
1040
+ "inputs": {
1041
+ "A": {
1042
+ "dtype": "float32",
1043
+ "shape": [1, 2, 65, 33],
1044
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.1 }
1045
+ },
1046
+ "B": {
1047
+ "dtype": "float32",
1048
+ "shape": [33, 67],
1049
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.1 }
1050
+ }
1051
+ },
1052
+ "outputs": { "Y": { "dtype": "float32", "shape": [1, 2, 65, 67], "tolerance": 0.0003 } }
1053
+ },
1054
+ {
1055
+ "name": "transbatch_a_dense_m_tail_f16_compact",
1056
+ "provenance": {
1057
+ "notes": "Compact lock for stored [M,batch,K] transBatchA addressing. M=65 exercises the subgroup-matrix row tail; all/no-mma/no-subgroups select the MMA/register-blocked portable paths used by the model-shaped stress case."
1058
+ },
1059
+ "attrs": { "alpha": 0.5, "transBatchA": 1 },
1060
+ "inputs": {
1061
+ "A": {
1062
+ "dtype": "float16",
1063
+ "shape": [65, 2, 32],
1064
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.1 }
1065
+ },
1066
+ "B": {
1067
+ "dtype": "float16",
1068
+ "shape": [2, 32, 64],
1069
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.1 }
1070
+ }
1071
+ },
1072
+ "outputs": { "Y": { "dtype": "float16", "shape": [2, 65, 64], "tolerance": 0.05 } }
1073
+ },
1074
+ {
1075
+ "name": "transbatch_a_dense_m_tail_f32_compact",
1076
+ "provenance": {
1077
+ "notes": "Float32 counterpart proving the same transBatchA MMA/register-blocked split remains correct when shader-f16 is unavailable."
1078
+ },
1079
+ "attrs": { "alpha": 0.5, "transBatchA": 1 },
1080
+ "inputs": {
1081
+ "A": {
1082
+ "dtype": "float32",
1083
+ "shape": [65, 2, 32],
1084
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.1 }
1085
+ },
1086
+ "B": {
1087
+ "dtype": "float32",
1088
+ "shape": [2, 32, 64],
1089
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.1 }
1090
+ }
1091
+ },
1092
+ "outputs": { "Y": { "dtype": "float32", "shape": [2, 65, 64], "tolerance": 0.0003 } }
1093
+ },
1094
+ {
1095
+ "name": "rank5_three_batch_dims",
1096
+ "attrs": { "alpha": 1, "transA": 0, "transB": 0, "transBatchA": 0, "transBatchB": 0 },
1097
+ "inputs": {
1098
+ "A": {
1099
+ "dtype": "float32",
1100
+ "shape": [2, 1, 2, 2, 3],
1101
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
1102
+ },
1103
+ "B": {
1104
+ "dtype": "float32",
1105
+ "shape": [1, 3, 1, 3, 4],
1106
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.25 }
1107
+ }
1108
+ },
1109
+ "outputs": { "Y": { "dtype": "float32", "shape": [2, 3, 2, 2, 4], "tolerance": 0.000001 } }
1110
+ },
1111
+ {
1112
+ "name": "subgroup_matrix_kn_tail_f16_offset_alpha_scale_lock",
1113
+ "provenance": {
1114
+ "notes": "subgroup_matrix_kn_tail_f16_compact draws both operands from a zero-mean sinusoid, so the K=34 dot random-walks to 0.013 and the 0.05 absolute tolerance is 3.7x the whole output: alpha could be ignored, a K tile dropped, or Y halved and it still passes. Offsetting both operands makes every element approach alpha * K * aOffset * bOffset (~3.4), a value proportional to the contraction length, so the K=34 partial tile, the N=66 column tail and the alpha=0.5 epilogue are all under test on the tail-broadcast subgroup-matrix route."
1115
+ },
1116
+ "attrs": { "alpha": 0.5 },
1117
+ "inputs": {
1118
+ "A": {
1119
+ "dtype": "float16",
1120
+ "shape": [33, 34],
1121
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.1, "offset": 0.5 }
1122
+ },
1123
+ "B": {
1124
+ "dtype": "float16",
1125
+ "shape": [34, 66],
1126
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.1, "offset": 0.4 }
1127
+ }
1128
+ },
1129
+ "outputs": { "Y": { "dtype": "float16", "shape": [33, 66], "tolerance": 0.03, "relTolerance": 0.01 } }
1130
+ },
1131
+ {
1132
+ "name": "subgroup_matrix_broadcast_rank4x3_f16_offset_scale_lock",
1133
+ "provenance": {
1134
+ "notes": "Well-conditioned sibling of subgroup_matrix_broadcast_rank4x3_f16_compact, whose zero-mean operands cancel to 0.021 under a 0.05 absolute tolerance (2.4x blind). Offsetting both operands drives each output to ~K * aOffset * bOffset, so the rank-4 by rank-3 batch broadcast now pins the per-batch B slice and the K=32 count instead of only the addressing pattern."
1135
+ },
1136
+ "attrs": { "alpha": 1 },
1137
+ "inputs": {
1138
+ "A": {
1139
+ "dtype": "float16",
1140
+ "shape": [1, 2, 33, 32],
1141
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.1, "offset": 0.5 }
1142
+ },
1143
+ "B": {
1144
+ "dtype": "float16",
1145
+ "shape": [2, 32, 64],
1146
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.1, "offset": 0.4 }
1147
+ }
1148
+ },
1149
+ "outputs": { "Y": { "dtype": "float16", "shape": [1, 2, 33, 64], "tolerance": 0.03, "relTolerance": 0.01 } }
1150
+ },
1151
+ {
1152
+ "name": "subgroup_matrix_a_batch_broadcast_rank4x3_f16",
1153
+ "provenance": {
1154
+ "notes": "A-side batch broadcast: A carries dim 1 on the batch axis while B carries 2, so one A slice feeds both output batches. Every other rank-4 by rank-3 fixture broadcasts B (or matches both extents), leaving the A-broadcast stride - a zero A batch stride against a non-zero B one - unrendered. Operands are offset so each output lands near alpha * K * aOffset * bOffset and a swapped or dropped batch stride moves the result rather than cancelling."
1155
+ },
1156
+ "attrs": { "alpha": 1 },
1157
+ "inputs": {
1158
+ "A": {
1159
+ "dtype": "float16",
1160
+ "shape": [1, 1, 33, 32],
1161
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.019, "scale": 0.1, "offset": 0.5 }
1162
+ },
1163
+ "B": {
1164
+ "dtype": "float16",
1165
+ "shape": [2, 32, 64],
1166
+ "data": { "kind": "fillFloat32", "sinStep": 0.007, "cosStep": 0.031, "scale": 0.1, "offset": 0.4 }
1167
+ }
1168
+ },
1169
+ "outputs": { "Y": { "dtype": "float16", "shape": [1, 2, 33, 64], "tolerance": 0.03, "relTolerance": 0.01 } }
1170
+ },
1171
+ {
1172
+ "name": "broadcast_rank4_tiled_reg_f16_offset_alpha_scale_lock",
1173
+ "provenance": {
1174
+ "notes": "broadcast_rank4_tiled_reg_f16_compact cancels to 0.015 against a 0.05 absolute tolerance, so the register-blocked rank-4 route was 3.4x blind to any uniform scale error - including a silently dropped alpha. Offsetting both operands makes each output ~alpha * K * aOffset * bOffset with K=33 (one full 32-wide k tile plus a 1-element tail), so a mis-counted k tile, a doubled accumulator or a missing alpha all move it far outside tolerance."
1175
+ },
1176
+ "attrs": { "alpha": 0.5 },
1177
+ "inputs": {
1178
+ "A": {
1179
+ "dtype": "float16",
1180
+ "shape": [1, 2, 65, 33],
1181
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.1, "offset": 0.5 }
1182
+ },
1183
+ "B": {
1184
+ "dtype": "float16",
1185
+ "shape": [2, 33, 67],
1186
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.1, "offset": 0.4 }
1187
+ }
1188
+ },
1189
+ "outputs": { "Y": { "dtype": "float16", "shape": [1, 2, 65, 67], "tolerance": 0.03, "relTolerance": 0.01 } }
1190
+ },
1191
+ {
1192
+ "name": "transbatch_a_dense_m_tail_f16_offset_alpha_scale_lock",
1193
+ "provenance": {
1194
+ "notes": "transbatch_a_dense_m_tail_f16_compact was the most blind fixture of this op: a 0.05 absolute tolerance over a 0.010 output, so a 4.8x uniform scale error passes. Offsetting both operands makes every element ~alpha * K * aOffset * bOffset, so the [M,batch,K] transBatchA stride, the K=32 contraction and the alpha=0.5 scale are pinned rather than just the row/column addressing."
1195
+ },
1196
+ "attrs": { "alpha": 0.5, "transBatchA": 1 },
1197
+ "inputs": {
1198
+ "A": {
1199
+ "dtype": "float16",
1200
+ "shape": [65, 2, 32],
1201
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.1, "offset": 0.5 }
1202
+ },
1203
+ "B": {
1204
+ "dtype": "float16",
1205
+ "shape": [2, 32, 64],
1206
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.1, "offset": 0.4 }
1207
+ }
1208
+ },
1209
+ "outputs": { "Y": { "dtype": "float16", "shape": [2, 65, 64], "tolerance": 0.03, "relTolerance": 0.01 } }
1210
+ },
1211
+ {
1212
+ "name": "aligned_f16_transA_transB_alpha_offset_scale_lock",
1213
+ "provenance": {
1214
+ "notes": "aligned_f16_transA_transB_alpha_64x32 cancels to 0.11 under a 0.03 absolute tolerance, so a 27% scale error - alpha 0.5 applied twice, say - still passes. Offsetting both operands makes each output ~alpha * K * aOffset * bOffset, which keeps the doubly-transposed addressing under test while making the alpha epilogue and the K=32 count observable on both the subgroup-matrix and portable tiled routes."
1215
+ },
1216
+ "attrs": { "transA": 1, "transB": 1, "alpha": 0.5 },
1217
+ "inputs": {
1218
+ "A": {
1219
+ "dtype": "float16",
1220
+ "shape": [32, 64],
1221
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.023, "scale": 0.2, "offset": 0.5 }
1222
+ },
1223
+ "B": {
1224
+ "dtype": "float16",
1225
+ "shape": [64, 32],
1226
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.009, "scale": 0.2, "offset": 0.4 }
1227
+ }
1228
+ },
1229
+ "outputs": { "Y": { "dtype": "float16", "shape": [64, 64], "tolerance": 0.03, "relTolerance": 0.01 } }
1230
+ },
1231
+ {
1232
+ "name": "f16_unaligned_3x5x7_offset_scale_lock",
1233
+ "provenance": {
1234
+ "notes": "f16_unaligned_3x5x7 cancels to 0.15 under a 0.02 absolute tolerance (13% blind). The same 3x5 by 5x7 shape with offset operands lands at ~K * aOffset * bOffset, so the odd-K/odd-N scalar and tiled fallbacks - the routes small unaligned shapes actually take - now catch a dropped K element or a doubled tail."
1235
+ },
1236
+ "inputs": {
1237
+ "A": {
1238
+ "dtype": "float16",
1239
+ "shape": [3, 5],
1240
+ "data": { "kind": "fillFloat32", "sinStep": 0.015, "cosStep": 0.021, "scale": 0.2, "offset": 0.6 }
1241
+ },
1242
+ "B": {
1243
+ "dtype": "float16",
1244
+ "shape": [5, 7],
1245
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.011, "scale": 0.2, "offset": 0.5 }
1246
+ }
1247
+ },
1248
+ "outputs": { "Y": { "dtype": "float16", "shape": [3, 7], "tolerance": 0.01, "relTolerance": 0.005 } }
1249
+ },
1250
+ {
1251
+ "name": "aligned_f16_plain_64x32x64_offset_scale_lock",
1252
+ "provenance": {
1253
+ "notes": "aligned_f16_plain_64x32x64 cancels to 0.124 under a 0.02 absolute tolerance, so a 16% uniform scale error passes on the fully aligned f16 subgroup-matrix path (M=64, K=32, N=64 - no tail at all). Offsetting both operands makes each element ~K * aOffset * bOffset, so the aligned 32x64 tile now pins the k-block count and the scratch drain, not just the addressing."
1254
+ },
1255
+ "inputs": {
1256
+ "A": {
1257
+ "dtype": "float16",
1258
+ "shape": [64, 32],
1259
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.2, "offset": 0.5 }
1260
+ },
1261
+ "B": {
1262
+ "dtype": "float16",
1263
+ "shape": [32, 64],
1264
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.007, "scale": 0.2, "offset": 0.4 }
1265
+ }
1266
+ },
1267
+ "outputs": { "Y": { "dtype": "float16", "shape": [64, 64], "tolerance": 0.03, "relTolerance": 0.01 } }
1268
+ },
1269
+ {
1270
+ "name": "aligned_f16_batched_plain_2x64x32x64_offset_scale_lock",
1271
+ "provenance": {
1272
+ "notes": "Batched twin of aligned_f16_plain_64x32x64_offset_scale_lock. aligned_f16_batched_plain_2x64x32x64 cancels to 0.124 under a 0.03 absolute tolerance (24% blind); with offset operands each element is ~K * aOffset * bOffset and the two batches carry different data, so the batch stride is pinned alongside the magnitude."
1273
+ },
1274
+ "inputs": {
1275
+ "A": {
1276
+ "dtype": "float16",
1277
+ "shape": [2, 64, 32],
1278
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.2, "offset": 0.5 }
1279
+ },
1280
+ "B": {
1281
+ "dtype": "float16",
1282
+ "shape": [2, 32, 64],
1283
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.007, "scale": 0.2, "offset": 0.4 }
1284
+ }
1285
+ },
1286
+ "outputs": { "Y": { "dtype": "float16", "shape": [2, 64, 64], "tolerance": 0.03, "relTolerance": 0.01 } }
1287
+ },
1288
+ {
1289
+ "name": "subgroup_matrix_m_tail_57_partial_block_f16_offset_scale_lock",
1290
+ "provenance": {
1291
+ "notes": "subgroup_matrix_m_tail_57_partial_block_f16 cancels to 0.062 under a 0.03 absolute tolerance, so the partial 8-row MMA block at M=57 (one full 32-row tile plus a 25-row remainder) was 49% blind to a scale error. With offset operands each element is ~K * aOffset * bOffset, so the guarded rows have to carry the same magnitude as the full ones - a tail block that accumulates one k step short, or drains a stale scratch slot, no longer looks like noise."
1292
+ },
1293
+ "inputs": {
1294
+ "A": {
1295
+ "dtype": "float16",
1296
+ "shape": [57, 32],
1297
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.023, "scale": 0.2, "offset": 0.5 }
1298
+ },
1299
+ "B": {
1300
+ "dtype": "float16",
1301
+ "shape": [32, 64],
1302
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.011, "scale": 0.2, "offset": 0.4 }
1303
+ }
1304
+ },
1305
+ "outputs": { "Y": { "dtype": "float16", "shape": [57, 64], "tolerance": 0.03, "relTolerance": 0.01 } }
1306
+ },
1307
+ {
1308
+ "name": "aligned_f16_transA_64x32_offset_scale_lock",
1309
+ "provenance": {
1310
+ "notes": "aligned_f16_transA_64x32 cancels to 0.053 under a 0.03 absolute tolerance - 57% blind, the loosest of the transpose fixtures. transA alone (transB = 0) is its own render of the operand loader, and with offset operands each element is ~K * aOffset * bOffset, so the transposed A stride is pinned against a k-count error rather than only against a wrong-element read."
1311
+ },
1312
+ "attrs": { "transA": 1 },
1313
+ "inputs": {
1314
+ "A": {
1315
+ "dtype": "float16",
1316
+ "shape": [32, 64],
1317
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.2, "offset": 0.5 }
1318
+ },
1319
+ "B": {
1320
+ "dtype": "float16",
1321
+ "shape": [32, 64],
1322
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.007, "scale": 0.2, "offset": 0.4 }
1323
+ }
1324
+ },
1325
+ "outputs": { "Y": { "dtype": "float16", "shape": [64, 64], "tolerance": 0.03, "relTolerance": 0.01 } }
1326
+ },
1327
+ {
1328
+ "name": "transA_transB_subgroup_matrix_m_tail_50_f16_offset_scale_lock",
1329
+ "provenance": {
1330
+ "notes": "Combines both transposes with an M tail (50 = 32 + 18). Offset operands make each element approximately K * aOffset * bOffset, checking the guarded rows of the doubly-transposed load for magnitude as well as placement; the zero-mean companion cancels to 0.099 under a 0.05 absolute tolerance and cannot provide that scale lock."
1331
+ },
1332
+ "attrs": { "transA": 1, "transB": 1, "alpha": 1 },
1333
+ "inputs": {
1334
+ "A": {
1335
+ "dtype": "float16",
1336
+ "shape": [32, 50],
1337
+ "data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.023, "scale": 0.2, "offset": 0.5 }
1338
+ },
1339
+ "B": {
1340
+ "dtype": "float16",
1341
+ "shape": [64, 32],
1342
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.009, "scale": 0.2, "offset": 0.4 }
1343
+ }
1344
+ },
1345
+ "outputs": { "Y": { "dtype": "float16", "shape": [50, 64], "tolerance": 0.03, "relTolerance": 0.01 } }
1346
+ },
1347
+ {
1348
+ "name": "f16_rank3_by_broadcast_rank3_offset_scale_lock",
1349
+ "provenance": {
1350
+ "notes": "f16_rank3_by_broadcast_rank3 cancels to 0.076 under a 0.02 absolute tolerance (26% blind). Offsetting both operands makes each element ~K * aOffset * bOffset over K=3, so the shared single-batch B - read by both output batches - is pinned for value as well as for broadcast addressing."
1351
+ },
1352
+ "inputs": {
1353
+ "A": {
1354
+ "dtype": "float16",
1355
+ "shape": [2, 2, 3],
1356
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.027, "scale": 0.2, "offset": 1.0 }
1357
+ },
1358
+ "B": {
1359
+ "dtype": "float16",
1360
+ "shape": [1, 3, 4],
1361
+ "data": { "kind": "fillFloat32", "sinStep": 0.019, "cosStep": 0.007, "scale": 0.2, "offset": 0.8 }
1362
+ }
1363
+ },
1364
+ "outputs": { "Y": { "dtype": "float16", "shape": [2, 2, 4], "tolerance": 0.01, "relTolerance": 0.005 } }
1365
+ },
1366
+ {
1367
+ "name": "rank6_four_batch_dims",
1368
+ "attrs": { "alpha": 1, "transA": 0, "transB": 0, "transBatchA": 0, "transBatchB": 0 },
1369
+ "inputs": {
1370
+ "A": {
1371
+ "dtype": "float32",
1372
+ "shape": [2, 2, 1, 2, 2, 3],
1373
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 0.5 }
1374
+ },
1375
+ "B": {
1376
+ "dtype": "float32",
1377
+ "shape": [1, 1, 3, 1, 3, 4],
1378
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31, "scale": 0.25 }
1379
+ }
1380
+ },
1381
+ "outputs": { "Y": { "dtype": "float32", "shape": [2, 2, 3, 2, 2, 4], "tolerance": 0.000001 } }
1382
+ },
1383
+ {
1384
+ "name": "trans_batch_a_and_trans_a_with_trans_batch_b_and_trans_b",
1385
+ "provenance": {
1386
+ "notes": "Combines transBatchA with transA and transBatchB with transB, checking both logical extents and both stride pairs. Expected values follow the documented storage orders A[k][batch][m] and B[n][batch][k]."
1387
+ },
1388
+ "attrs": { "transA": 1, "transB": 1, "transBatchA": 1, "transBatchB": 1 },
1389
+ "inputs": {
1390
+ "A": {
1391
+ "dtype": "float32",
1392
+ "shape": [4, 2, 3],
1393
+ "data": {
1394
+ "kind": "values",
1395
+ "values": [0.0, 1.0, 2.0, 4.0, 5.0, 6.0, 10.0, 11.0, 12.0, 14.0, 15.0, 16.0, 20.0, 21.0, 22.0, 24.0, 25.0, 26.0, 30.0, 31.0, 32.0, 34.0, 35.0, 36.0]
1396
+ }
1397
+ },
1398
+ "B": {
1399
+ "dtype": "float32",
1400
+ "shape": [5, 2, 4],
1401
+ "data": {
1402
+ "kind": "values",
1403
+ "values": [0.0, 1.0, 2.0, 3.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 31.0, 32.0, 33.0, 34.0]
1404
+ }
1405
+ }
1406
+ },
1407
+ "outputs": {
1408
+ "Y": {
1409
+ "dtype": "float32",
1410
+ "shape": [2, 3, 5],
1411
+ "tolerance": 0.000001,
1412
+ "data": {
1413
+ "kind": "values",
1414
+ "values": [140.0, 560.0, 980.0, 1400.0, 1820.0, 146.0, 594.0, 1042.0, 1490.0, 1938.0, 152.0, 628.0, 1104.0, 1580.0, 2056.0, 392.0, 924.0, 1456.0, 1988.0, 2520.0, 410.0, 970.0, 1530.0, 2090.0, 2650.0, 428.0, 1016.0, 1604.0, 2192.0, 2780.0]
1415
+ }
1416
+ }
1417
+ }
1418
+ }
1419
+ ]
1420
+ }