Xenova HF Staff commited on
Commit
993cc3b
·
verified ·
1 Parent(s): fb9dcfe

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,82 @@
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.FusedGemm
10
+
11
+ `com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
12
+
13
+ ## Description
14
+
15
+ Gemm with a fused activation: `Y = act(alpha * A' * B' + beta * C)`, where `A'` and `B'` are optionally transposed and `C` is broadcastable to `(M, N)`. The activation runs in the f32 accumulator before the single output narrowing. This package supports `Relu`, `LeakyRelu`, `Sigmoid`, `Tanh` and `HardSigmoid`; the other activation strings and numeric types admitted by the open schema are not implemented. Omitting `activation` gives plain Gemm.
16
+
17
+ See the [ONNX Runtime `FusedGemm` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.FusedGemm) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `A` | `A` | `T` | `2` | — | Left operand, `(M, K)` when `transA` is 0 and `(K, M)` otherwise. | required |
24
+ | `B` | `B` | `T` | `2` | — | Right operand, `(K, N)` when `transB` is 0 and `(N, K)` otherwise. | required |
25
+ | `C` | `C` | `T` | — | — | Optional additive term, unidirectionally broadcastable to `(M, N)`: a scalar, a row `(N)`, a column `(M, 1)`, or the full matrix. | optional |
26
+
27
+ ## Outputs
28
+
29
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
30
+ | --- | --- | --- | --- | --- | --- | --- |
31
+ | `Y` | `Y` | `T` | `2` | derived; see description | `act(alpha * A' * B' + beta * C)` with shape `(M, N)`. | required |
32
+
33
+ ## Attributes
34
+
35
+ Attributes and default values (overridable per request):
36
+
37
+ | Attribute | Default | Description |
38
+ | --- | --- | --- |
39
+ | `alpha` | `1` | Scalar multiplier for the product `A' * B'`; the standard default is 1. |
40
+ | `beta` | `1` | Scalar multiplier for `C`; the standard default is 1. |
41
+ | `transA` | `0` | Whether `A` is stored transposed. The standard default is 0. |
42
+ | `transB` | `0` | Whether `B` is stored transposed. The standard default is 0. |
43
+ | `activation` | — | Optional fused activation name. Supported modes are `Relu`, `LeakyRelu`, `Sigmoid`, `Tanh` and `HardSigmoid`; omission applies none. |
44
+ | `activation_alpha` | — | First activation parameter: the slope for `LeakyRelu` or `alpha` for `HardSigmoid`. |
45
+ | `activation_beta` | — | Second activation parameter: `beta` for `HardSigmoid`. |
46
+
47
+ ## Type constraints
48
+
49
+ | Variable | Allowed dtypes |
50
+ | --- | --- |
51
+ | `T` | `float32`, `float16` |
52
+
53
+ ## Device requirements
54
+
55
+ 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.
56
+
57
+ ## Files
58
+
59
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
60
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
61
+ - [`test.json`](build/webgpu/test.json) — correctness cases
62
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
63
+ - [`fused-gemm.wgsl.jinja`](build/webgpu/fused-gemm.wgsl.jinja)
64
+ - [`gemm-subgroup-matrix.wgsl.jinja`](build/webgpu/gemm-subgroup-matrix.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.FusedGemm", { version: 1 });
79
+ const { Y } = await kernel({ A: { data: AData, shape: [7, 13] }, B: { data: BData, shape: [13, 11] } }, {
80
+ attrs: { activation: "Relu" },
81
+ });
82
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "com.microsoft.FusedGemm",
3
+ "cases": [
4
+ {
5
+ "name": "fusedgemm-relu-m3072-k768-n3072",
6
+ "preset": "smoke",
7
+ "vars": { "dtype": "float32" },
8
+ "attrs": { "activation": "Relu" },
9
+ "inputs": {
10
+ "A": { "shape": [3072, 768], "dtype": "float32", "dist": "normal", "seed": 5300, "scale": 1 },
11
+ "B": { "shape": [768, 3072], "dtype": "float32", "dist": "normal", "seed": 5301, "scale": 1 },
12
+ "C": { "shape": [3072], "dtype": "float32", "dist": "normal", "seed": 5302, "scale": 1 }
13
+ },
14
+ "outputs": { "Y": { "shape": [3072, 3072], "dtype": "float32" } },
15
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 3072 * 768 * 3072" }] }
16
+ },
17
+ {
18
+ "name": "fusedgemm-transb-relu-m3072-k768-n3072",
19
+ "preset": "smoke",
20
+ "vars": { "dtype": "float32" },
21
+ "attrs": { "transB": 1, "activation": "Relu" },
22
+ "inputs": {
23
+ "A": { "shape": [3072, 768], "dtype": "float32", "dist": "normal", "seed": 5310, "scale": 1 },
24
+ "B": { "shape": [3072, 768], "dtype": "float32", "dist": "normal", "seed": 5311, "scale": 1 },
25
+ "C": { "shape": [3072], "dtype": "float32", "dist": "normal", "seed": 5312, "scale": 1 }
26
+ },
27
+ "outputs": { "Y": { "shape": [3072, 3072], "dtype": "float32" } },
28
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 3072 * 768 * 3072" }] }
29
+ },
30
+ {
31
+ "name": "fusedgemm-f16-relu-m3072-k768-n3072",
32
+ "preset": "model",
33
+ "vars": { "dtype": "float16" },
34
+ "attrs": { "activation": "Relu" },
35
+ "inputs": {
36
+ "A": { "shape": [3072, 768], "dtype": "float16", "dist": "normal", "seed": 5320, "scale": 1 },
37
+ "B": { "shape": [768, 3072], "dtype": "float16", "dist": "normal", "seed": 5321, "scale": 1 },
38
+ "C": { "shape": [3072], "dtype": "float16", "dist": "normal", "seed": 5322, "scale": 1 }
39
+ },
40
+ "outputs": { "Y": { "shape": [3072, 3072], "dtype": "float16" } },
41
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 3072 * 768 * 3072" }] }
42
+ },
43
+ {
44
+ "name": "fusedgemm-nobias-tanh-m2048-k1024-n2048",
45
+ "preset": "model",
46
+ "vars": { "dtype": "float32" },
47
+ "attrs": { "activation": "Tanh" },
48
+ "inputs": {
49
+ "A": { "shape": [2048, 1024], "dtype": "float32", "dist": "normal", "seed": 5330, "scale": 1 },
50
+ "B": { "shape": [1024, 2048], "dtype": "float32", "dist": "normal", "seed": 5331, "scale": 1 }
51
+ },
52
+ "outputs": { "Y": { "shape": [2048, 2048], "dtype": "float32" } },
53
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 2048 * 1024 * 2048" }] }
54
+ },
55
+ {
56
+ "name": "fusedgemm-noactivation-m2048-k1024-n2048",
57
+ "preset": "model",
58
+ "vars": { "dtype": "float32" },
59
+ "attrs": {},
60
+ "inputs": {
61
+ "A": { "shape": [2048, 1024], "dtype": "float32", "dist": "normal", "seed": 5340, "scale": 1 },
62
+ "B": { "shape": [1024, 2048], "dtype": "float32", "dist": "normal", "seed": 5341, "scale": 1 },
63
+ "C": { "shape": [2048], "dtype": "float32", "dist": "normal", "seed": 5342, "scale": 1 }
64
+ },
65
+ "outputs": { "Y": { "shape": [2048, 2048], "dtype": "float32" } },
66
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 2048 * 1024 * 2048" }] }
67
+ }
68
+ ]
69
+ }
build/webgpu/fused-gemm.wgsl.jinja ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 is defined and usesF16 %}enable f16;
2
+ {% endif %}{% set OUT = "f16" if (usesF16 is defined and usesF16) else "f32" %}
3
+ {{ env.wgsl.resourceDeclarations }}
4
+
5
+ // Shape-driven, register-blocked f32 GEMM for all four transpose combinations.
6
+ // Runtime M/N/K share one compiled pipeline. A 4x4 thread micro-tile forms a
7
+ // 64x64 output tile with BK=16; transpose modes change only load strides and
8
+ // keep the stored-contiguous axis coalesced. M/N/K tails are bounds-checked.
9
+ // Both tiles are indexed by their own output axis and group four K values per
10
+ // vector word, so the micro-tile accumulates through dot() and one step reads
11
+ // TM + TN words instead of 4 * (TM + TN) scalars.
12
+ {% set gemmEpi = gemmEpilogue if gemmEpilogue is defined else "none" %}
13
+ {% if gemmEpi == "fastgelu" %}
14
+ fn tanh_safe(x: f32) -> f32 {
15
+ if (x > 10.0) { return 1.0; }
16
+ if (x < -10.0) { return -1.0; }
17
+ return tanh(x);
18
+ }
19
+
20
+ fn gelu_tanh(v: f32) -> f32 {
21
+ return 0.5 * v * (1.0 + tanh_safe(0.7978845608028654 * (v + 0.044715 * v * v * v)));
22
+ }
23
+ {% elif gemmEpi == "activation" %}
24
+ {% set actMode = gemmActivation | default("") %}
25
+ {% set actA = gemmActAlpha | default(0.0) %}
26
+ {% set actB = gemmActBeta | default(0.0) %}
27
+ fn fused_act(v: f32) -> f32 {
28
+ {% if actMode == "Relu" %}
29
+ return max(v, 0.0);
30
+ {% endif %}
31
+ {% if actMode == "LeakyRelu" %}
32
+ return select(v * f32({{ actA }}), v, v >= 0.0);
33
+ {% endif %}
34
+ {% if actMode == "Sigmoid" %}
35
+ return 1.0 / (1.0 + exp(-v));
36
+ {% endif %}
37
+ {% if actMode == "Tanh" %}
38
+ return tanh(v);
39
+ {% endif %}
40
+ {% if actMode == "HardSigmoid" %}
41
+ return clamp(f32({{ actA }}) * v + f32({{ actB }}), 0.0, 1.0);
42
+ {% endif %}
43
+ }
44
+
45
+ {% endif %}
46
+ {% set epiOpen = "gelu_tanh(" if gemmEpi == "fastgelu" else ("fused_act(" if gemmEpi == "activation" else "") %}
47
+ {% set epiClose = ")" if gemmEpi != "none" else "" %}
48
+ const BK: u32 = 16u;
49
+ const BM: u32 = 64u;
50
+ const BN: u32 = 64u;
51
+ const TM: u32 = 4u; // per-thread micro-tile rows
52
+ const TN: u32 = 4u; // per-thread micro-tile cols
53
+
54
+ // Stage tiles at the operands' storage width. For f16 inputs this is
55
+ // bit-identical to widening on shared load, keeps f32 FMA registers, and halves
56
+ // workgroup storage.
57
+ {% set tileT = OUT %}
58
+ const K_VECS: u32 = BK / 4u;
59
+ var<workgroup> tileA: array<array<vec4<{{ tileT }}>, K_VECS>, BM>; // [64][4] logical A'[m][k/4]
60
+ var<workgroup> tileB: array<array<vec4<{{ tileT }}>, K_VECS>, BN>; // [64][4] logical B'[n][k/4]
61
+
62
+ @compute @workgroup_size(16, 16, 1)
63
+ fn main(
64
+ @builtin(workgroup_id) wg: vec3<u32>,
65
+ @builtin(local_invocation_id) lid: vec3<u32>,
66
+ @builtin(num_workgroups) nwg: vec3<u32>
67
+ ) {
68
+ let li = lid.y * 16u + lid.x;
69
+ let M = params.M;
70
+ let N = params.N;
71
+ let K = params.K;
72
+ let n_tiles = (N + BN - 1u) / BN;
73
+ let m_tiles = (M + BM - 1u) / BM;
74
+ for (var nt: u32 = wg.x; nt < n_tiles; nt = nt + nwg.x) {
75
+ let nBase = nt * BN;
76
+ for (var mt: u32 = wg.y; mt < m_tiles; mt = mt + nwg.y) {
77
+ let mBase = mt * BM;
78
+
79
+ var acc: array<f32, TM * TN>;
80
+ for (var i: u32 = 0u; i < TM * TN; i = i + 1u) { acc[i] = 0.0; }
81
+
82
+ let numTiles = (K + BK - 1u) / BK;
83
+ for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
84
+ let kBase = kt * BK;
85
+ // Cooperative load: one vector word per lane per pass. The lane grid is
86
+ // laid out so the axis the operand stores contiguously stays coalesced.
87
+ for (var idx: u32 = li; idx < BM * K_VECS; idx = idx + 256u) {
88
+ {% if transA %}
89
+ // A stored [K, M]: logical A'[m,k] = a[k*M + m]. Coalesce on m (contiguous).
90
+ let ar = idx % BM;
91
+ let ac4 = idx / BM;
92
+ {% else %}
93
+ // A stored [M, K]: logical A[m,k] = a[m*K + k]. Coalesce on k (contiguous).
94
+ let ar = idx / K_VECS;
95
+ let ac4 = idx % K_VECS;
96
+ {% endif %}
97
+ let am = mBase + ar;
98
+ let ak = kBase + ac4 * 4u;
99
+ var aWord = vec4<{{ tileT }}>({{ tileT }}(0.0));
100
+ if (am < M) {
101
+ {% for component in range(4) %}
102
+ if (ak + {{ component }}u < K) {
103
+ {% if transA %}
104
+ aWord[{{ component }}u] = {{ tileT }}(a[(ak + {{ component }}u) * M + am]);
105
+ {% else %}
106
+ aWord[{{ component }}u] = {{ tileT }}(a[am * K + ak + {{ component }}u]);
107
+ {% endif %}
108
+ }
109
+ {% endfor %}
110
+ }
111
+ tileA[ar][ac4] = aWord;
112
+ }
113
+ for (var idx: u32 = li; idx < BN * K_VECS; idx = idx + 256u) {
114
+ {% if transB is defined and transB %}
115
+ // B stored [N, K]: logical B[k,n] = b[n*K + k]. Coalesce on k (contiguous).
116
+ let bc = idx / K_VECS;
117
+ let br4 = idx % K_VECS;
118
+ {% else %}
119
+ // B stored [K, N]: logical B[k,n] = b[k*N + n]. Coalesce on n (contiguous).
120
+ let bc = idx % BN;
121
+ let br4 = idx / BN;
122
+ {% endif %}
123
+ let bn = nBase + bc;
124
+ let bk = kBase + br4 * 4u;
125
+ var bWord = vec4<{{ tileT }}>({{ tileT }}(0.0));
126
+ if (bn < N) {
127
+ {% for component in range(4) %}
128
+ if (bk + {{ component }}u < K) {
129
+ {% if transB is defined and transB %}
130
+ bWord[{{ component }}u] = {{ tileT }}(b[bn * K + bk + {{ component }}u]);
131
+ {% else %}
132
+ bWord[{{ component }}u] = {{ tileT }}(b[(bk + {{ component }}u) * N + bn]);
133
+ {% endif %}
134
+ }
135
+ {% endfor %}
136
+ }
137
+ tileB[bc][br4] = bWord;
138
+ }
139
+ workgroupBarrier();
140
+ {% set regIndent = " " %}{% set regT = "f32" %}{% set regCast = 1 if tileT != "f32" else 0 %}{{ regIndent }}let aRow = lid.y * TM;
141
+ {{ regIndent }}let bCol = lid.x * TN;
142
+ {{ regIndent }}for (var kv: u32 = 0u; kv < BK / 4u; kv = kv + 1u) {
143
+ {{ regIndent }} var av: array<vec4<{{ regT }}>, TM>;
144
+ {{ regIndent }} var bv: array<vec4<{{ regT }}>, TN>;
145
+ {{ regIndent }} for (var i: u32 = 0u; i < TM; i = i + 1u) { av[i] = {% if regCast %}vec4<{{ regT }}>(tileA[aRow + i][kv]){% else %}tileA[aRow + i][kv]{% endif %}; }
146
+ {{ regIndent }} for (var j: u32 = 0u; j < TN; j = j + 1u) { bv[j] = {% if regCast %}vec4<{{ regT }}>(tileB[bCol + j][kv]){% else %}tileB[bCol + j][kv]{% endif %}; }
147
+ {{ regIndent }} for (var i: u32 = 0u; i < TM; i = i + 1u) {
148
+ {{ regIndent }} for (var j: u32 = 0u; j < TN; j = j + 1u) {
149
+ {{ regIndent }} acc[i * TN + j] = acc[i * TN + j] + dot(av[i], bv[j]);
150
+ {{ regIndent }} }
151
+ {{ regIndent }} }
152
+ {{ regIndent }}}
153
+ workgroupBarrier();
154
+ }
155
+
156
+ let m0 = mBase + lid.y * TM;
157
+ let n0 = nBase + lid.x * TN;
158
+ for (var ti: u32 = 0u; ti < TM; ti = ti + 1u) {
159
+ let m = m0 + ti;
160
+ if (m >= M) { continue; }
161
+ for (var tj: u32 = 0u; tj < TN; tj = tj + 1u) {
162
+ let n = n0 + tj;
163
+ if (n < N) {
164
+ // alpha == 0 nulls A*B exactly and avoids Inf*0 becoming NaN.
165
+ let product = select(acc[ti * TN + tj] * params.alpha, 0.0, params.alpha == 0.0);
166
+ {% if hasBias is defined and hasBias %}
167
+ {% if rowBias is defined and rowBias %}
168
+ let biasValue = f32(bias[n]);
169
+ {% elif scalarBias is defined and scalarBias %}
170
+ let biasValue = f32(bias[0u]);
171
+ {% elif columnBias is defined and columnBias %}
172
+ let biasValue = f32(bias[m]);
173
+ {% else %}
174
+ let biasValue = f32(bias[m * N + n]);
175
+ {% endif %}
176
+ let biasTerm = select(biasValue * params.beta, 0.0, params.beta == 0.0);
177
+ y[m * N + n] = {{ OUT }}({{ epiOpen }}product + biasTerm{{ epiClose }});
178
+ {% else %}
179
+ y[m * N + n] = {{ OUT }}({{ epiOpen }}product{{ epiClose }});
180
+ {% endif %}
181
+ }
182
+ }
183
+ }
184
+ }
185
+ }
186
+ }
build/webgpu/gemm-subgroup-matrix.wgsl.jinja ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ enable subgroups;
5
+ {% if pinSubgroupSize32 %}
6
+ enable subgroup_size_control;
7
+ {% endif %}
8
+ enable chromium_experimental_subgroup_matrix;
9
+ diagnostic(off, chromium.subgroup_matrix_uniformity);
10
+
11
+ {{ env.wgsl.resourceDeclarations }}
12
+
13
+ {% set operandScalar = mmaScalar %}
14
+ {% set accScalar = "f32" %}
15
+ {% set tileMValue = tileM if tileM is defined else 32 %}
16
+ {% set tileNValue = tileN if tileN is defined else 64 %}
17
+ {% set tileKValue = tileK if tileK is defined else 32 %}
18
+ {% set gemmEpi = gemmEpilogue if gemmEpilogue is defined else "none" %}
19
+ {% set epiOpen = "gelu_tanh(" if gemmEpi == "fastgelu" else ("fused_act(" if gemmEpi == "activation" else "") %}
20
+ {% set epiClose = ")" if gemmEpi != "none" else "" %}
21
+ {% set useDoubleBufferedB = doubleBufferedB is defined and doubleBufferedB %}
22
+ {% set aTransposed = aTransposedStorage if aTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
23
+ {% set bTransposed = bTransposedStorage if bTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
24
+ {% set columnTileBaseValue = columnTileBase if columnTileBase is defined else 0 %}
25
+ {% set rowTileBaseValue = rowTileBase if rowTileBase is defined else 0 %}
26
+ // Four subgroups cover 32xN as a 2x2 grid, or taller tiles as four row bands.
27
+ {% set subtileCols = (tileNValue / 2)|int %}
28
+ {% set subtileRows = 16 %}
29
+ {% set aTilesPerSubgroup = (subtileRows / 8)|int %}
30
+ {% set bTilesPerSubgroup = (subtileCols / 8)|int %}
31
+ {% set subgroupCount = 4 %}
32
+ {% set scratchBanks = subgroupCount * aTilesPerSubgroup * bTilesPerSubgroup %}
33
+
34
+ {% if gemmEpi == "fastgelu" %}
35
+ fn tanh_safe(x: f32) -> f32 {
36
+ if (x > 10.0) { return 1.0; }
37
+ if (x < -10.0) { return -1.0; }
38
+ return tanh(x);
39
+ }
40
+
41
+ fn gelu_tanh(v: f32) -> f32 {
42
+ return 0.5 * v * (1.0 + tanh_safe(0.7978845608028654 * (v + 0.044715 * v * v * v)));
43
+ }
44
+ {% elif gemmEpi == "activation" %}
45
+ {% set actMode = gemmActivation | default("") %}
46
+ {% set actA = gemmActAlpha | default(0.0) %}
47
+ {% set actB = gemmActBeta | default(0.0) %}
48
+ fn fused_act(v: f32) -> f32 {
49
+ {% if actMode == "Relu" %}
50
+ return max(v, 0.0);
51
+ {% endif %}
52
+ {% if actMode == "LeakyRelu" %}
53
+ return select(v * f32({{ actA }}), v, v >= 0.0);
54
+ {% endif %}
55
+ {% if actMode == "Sigmoid" %}
56
+ return 1.0 / (1.0 + exp(-v));
57
+ {% endif %}
58
+ {% if actMode == "Tanh" %}
59
+ return tanh(v);
60
+ {% endif %}
61
+ {% if actMode == "HardSigmoid" %}
62
+ return clamp(f32({{ actA }}) * v + f32({{ actB }}), 0.0, 1.0);
63
+ {% endif %}
64
+ }
65
+
66
+ {% endif %}
67
+ const IN_F: u32 = {{ inFeatures }}u;
68
+ const OUT_F: u32 = {{ outFeatures }}u;
69
+ {% set kLoopEndValue = kLoopEnd if kLoopEnd is defined else ("K_LOOP" if tailSafe is defined and tailSafe else "IN_F") %}
70
+ const TILE_COLS: u32 = {{ tileNValue }}u;
71
+ const TILE_ROWS: u32 = {{ tileMValue }}u;
72
+ const TILE_K: u32 = {{ tileKValue }}u;
73
+ const SUB_COLS: u32 = {{ subtileCols }}u;
74
+ const SUB_ROWS: u32 = {{ subtileRows }}u;
75
+ const COLUMN_TILE_BASE: u32 = {{ columnTileBaseValue }}u;
76
+ const ROW_TILE_BASE: u32 = {{ rowTileBaseValue }}u;
77
+
78
+ var<workgroup> tile_A: array<{{ operandScalar }}, {{ tileMValue }} * {{ tileKValue }}>;
79
+ var<workgroup> tile_B: array<{{ operandScalar }}, {{ (2 if useDoubleBufferedB else 1) * tileNValue }} * {{ tileKValue }}>;
80
+ // Distinct readback banks for every result matrix, so one barrier can publish
81
+ // the full subtile without write-after-read reuse.
82
+ var<workgroup> scratch: array<array<{{ accScalar }}, 64>, {{ scratchBanks }}>;
83
+
84
+ fn loadSHMA(tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
85
+ {% if aTransposed %}
86
+ // Generic transA stores A as [K,M]. Keep both the global and workgroup writes
87
+ // contiguous as [K,tileM], then transpose in subgroupMatrixLoad.
88
+ let local_idx = row * {{ (tileKValue / 8)|int }}u + c_idx;
89
+ let vectors_per_k = TILE_ROWS / 4u;
90
+ let total_vectors = TILE_K * vectors_per_k;
91
+ for (var vector_idx = local_idx; vector_idx < total_vectors; vector_idx += 128u) {
92
+ let k_local = vector_idx / vectors_per_k;
93
+ let m4 = (vector_idx - k_local * vectors_per_k) * 4u;
94
+ for (var component = 0u; component < 4u; component++) {
95
+ let m_local = m4 + component;
96
+ let global_m = tile_base + m_local;
97
+ let global_k = k_idx + k_local;
98
+ tile_A[k_local * TILE_ROWS + m_local] = x[global_k * params.M + global_m];
99
+ }
100
+ }
101
+ {% else %}
102
+ // Clamp the partial-M tail to the last valid row (M-1) instead of zero-filling
103
+ // it. Some subgroup-matrix implementations let a zero-padded left tile corrupt
104
+ // the adjacent valid row. Replicating row M-1 avoids that boundary; the
105
+ // replicated rows are discarded by storeOutput's row_limit guard, so every valid
106
+ // output row stays bit-identical. M >= 1 is guaranteed by the when-clause args.M > 0.
107
+ let col: u32 = c_idx * 8u;
108
+ for (var row_offset: u32 = 0u; row_offset < TILE_ROWS; row_offset += {{ (1024 / tileKValue)|int }}u) {
109
+ let r: u32 = row + row_offset;
110
+ let a_global: u32 = min(tile_base + r, params.M - 1u);
111
+ for (var col_offset: u32 = 0u; col_offset < 8u; col_offset++) {
112
+ let k: u32 = k_idx + col + col_offset;
113
+ tile_A[r * TILE_K + col + col_offset] =
114
+ x[a_global * IN_F + k];
115
+ }
116
+ }
117
+ {% endif %}
118
+ }
119
+
120
+ fn loadSHMB(tile_base: u32, k_idx: u32, row: u32, c_idx: u32, bank_offset: u32) {
121
+ {% if bTransposed %}
122
+ // Generic transB=0 stores B as [K,N]. Stage that native orientation and load
123
+ // the subgroup right operand without a transpose.
124
+ let local_idx = row * {{ (tileKValue / 16)|int }}u + c_idx;
125
+ let vectors_per_k = TILE_COLS / 4u;
126
+ let total_vectors = TILE_K * vectors_per_k;
127
+ for (var vector_idx = local_idx; vector_idx < total_vectors; vector_idx += 128u) {
128
+ let k_local = vector_idx / vectors_per_k;
129
+ let n4 = (vector_idx - k_local * vectors_per_k) * 4u;
130
+ for (var component = 0u; component < 4u; component++) {
131
+ let n_local = n4 + component;
132
+ let global_n = tile_base + n_local;
133
+ let global_k = k_idx + k_local;
134
+ tile_B[bank_offset + k_local * TILE_COLS + n_local] = w[global_k * OUT_F + global_n];
135
+ }
136
+ }
137
+ {% else %}
138
+ let col: u32 = c_idx * 16u;
139
+ for (var row_offset: u32 = 0u; row_offset < TILE_COLS; row_offset += {{ (2048 / tileKValue)|int }}u) {
140
+ let b_row: u32 = row + row_offset;
141
+ // Non-power-of-two tuning widths (48/96) use only a prefix of lanes on the
142
+ // final row band. Keep those inactive lanes out of tile_B and global memory.
143
+ if (b_row < TILE_COLS) {
144
+ let w_global: u32 = tile_base + b_row;
145
+ for (var i: u32 = 0u; i < 16u; i++) {
146
+ let k: u32 = k_idx + col + i;
147
+ tile_B[bank_offset + b_row * TILE_K + col + i] =
148
+ w[w_global * IN_F + k];
149
+ }
150
+ }
151
+ }
152
+ {% endif %}
153
+ }
154
+
155
+ {% set needsColBase = hasBias or (tailSafe is defined and tailSafe) %}
156
+ {% set BIAS_1 = (" + " ~ accScalar ~ "(bias[col_base + col])") if hasBias else "" %}
157
+ {% set BIAS_2 = (" + " ~ accScalar ~ "(bias[col_base + col2])") if hasBias else "" %}
158
+ fn storeOutput(offset: u32{% if needsColBase %}, col_base: u32{% endif %}, row: u32, col: u32, src_slot: u32, row_limit: i32) {
159
+ if (row_limit > 0 && row < u32(row_limit)) {
160
+ let col2: u32 = col + 1u;
161
+ y[offset + row * OUT_F + col] = {{ yScalar }}({{ epiOpen }}scratch[src_slot][row * 8u + col]{{ BIAS_1 }}{{ epiClose }});
162
+ y[offset + row * OUT_F + col2] = {{ yScalar }}({{ epiOpen }}scratch[src_slot][row * 8u + col2]{{ BIAS_2 }}{{ epiClose }});
163
+ }
164
+ }
165
+
166
+ @compute @workgroup_size(128, 1, 1){{ " @subgroup_size(32)" if pinSubgroupSize32 else "" }}
167
+ fn main(
168
+ @builtin(workgroup_id) workgroup_id: vec3<u32>,
169
+ @builtin(num_workgroups) nwg: vec3<u32>,
170
+ @builtin(local_invocation_index) local_idx: u32,
171
+ @builtin(subgroup_invocation_id) sg_id: u32,
172
+ @builtin(subgroup_size) sg_size: u32
173
+ ) {
174
+ // 2D-folded M-tile (row) base: workgroup_id.z carries the high bits when the
175
+ // row tile exceeds the per-dimension dispatch limit.
176
+ // Reduces to workgroup_id.y when nwg.z == 1; the row_limit guard in storeOutput
177
+ // (i32(M) - i32(a_global_base + ...)) drops the over-dispatched tail.
178
+ let a_global_base: u32 = (ROW_TILE_BASE + workgroup_id.y + workgroup_id.z * nwg.y) * TILE_ROWS;
179
+ let w_global_base: u32 = (COLUMN_TILE_BASE + workgroup_id.x) * TILE_COLS;
180
+
181
+ let subtile_id: u32 = local_idx / sg_size;
182
+ let subtile_idx: u32 = subtile_id / 2u;
183
+ let subtile_idy: u32 = subtile_id % 2u;
184
+ let base_A: u32 = subtile_idy * SUB_ROWS;
185
+ let base_B: u32 = subtile_idx * SUB_COLS;
186
+
187
+ {% for m in range(aTilesPerSubgroup) %}
188
+ {% for n in range(bTilesPerSubgroup) %}
189
+ var matC{{ m }}{{ n }}: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
190
+ {% endfor %}
191
+ {% endfor %}
192
+
193
+ for (var kidx: u32 = 0u; kidx < {{ kLoopEndValue }}; kidx += TILE_K) {
194
+ loadSHMA(a_global_base, kidx, local_idx / {{ (tileKValue / 8)|int }}u, local_idx % {{ (tileKValue / 8)|int }}u);
195
+ loadSHMB(w_global_base, kidx, local_idx / {{ (tileKValue / 16)|int }}u, local_idx % {{ (tileKValue / 16)|int }}u, 0u);
196
+ workgroupBarrier();
197
+
198
+ for (var step: u32 = 0u; step < TILE_K; step += 8u) {
199
+ {% set dynamicATiles = aTilesPerSubgroup if aTilesPerSubgroup is defined else 2 %}
200
+ {% set aTransposed = aTransposedStorage if aTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
201
+ {% set bTransposed = bTransposedStorage if bTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
202
+ {% set B_BANK = "b_bank_offset + " if doubleBufferedB is defined and doubleBufferedB else "" %}
203
+ {% if aTransposed %}
204
+ let matrix_a_offset = step * TILE_ROWS + subtile_idy * SUB_ROWS;
205
+ {% else %}
206
+ let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
207
+ {% endif %}
208
+ {% for m in range(dynamicATiles) %}
209
+ {% if aTransposed %}
210
+ var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset + {{ m * 8 }}u, true, TILE_ROWS);
211
+ {% else %}
212
+ var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * TILE_K{% endif %}, false, TILE_K);
213
+ {% endif %}
214
+ {% endfor %}
215
+
216
+ {% if bTransposed %}
217
+ let matrix_b_offset = {{ B_BANK }}step * TILE_COLS + subtile_idx * SUB_COLS;
218
+ {% else %}
219
+ let matrix_b_offset = {{ B_BANK }}subtile_idx * SUB_COLS * TILE_K + step;
220
+ {% endif %}
221
+ {% for n in range(bTilesPerSubgroup) %}
222
+ {% if bTransposed %}
223
+ var matB{{ n }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + {{ n * 8 }}u, false, TILE_COLS);
224
+ {% else %}
225
+ var matB{{ n }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + {{ n * 8 }}u * TILE_K, true, TILE_K);
226
+ {% endif %}
227
+ {% endfor %}
228
+
229
+ {% for m in range(dynamicATiles) %}
230
+ {% for n in range(bTilesPerSubgroup) %}
231
+ matC{{ m }}{{ n }} = subgroupMatrixMultiplyAccumulate(matA{{ m }}, matB{{ n }}, matC{{ m }}{{ n }});
232
+ {% endfor %}
233
+ {% endfor %}
234
+
235
+ }
236
+ workgroupBarrier();
237
+ }
238
+
239
+ let matrix_c_offset: u32 = (a_global_base + base_A) * OUT_F + w_global_base + base_B;
240
+ let row: u32 = sg_id / 4u;
241
+ let col: u32 = (sg_id % 4u) * 2u;
242
+ // Stage every result into a distinct bank before one barrier. Tail/bias paths
243
+ // retain this guarded scalar epilogue: subgroupMatrixStore scatters values
244
+ // across lanes, so its cross-lane readback must be published before partial-M
245
+ // guards diverge. The epilogue also handles output conversion and bias.
246
+ let bank: u32 = subtile_id * {{ aTilesPerSubgroup * bTilesPerSubgroup }}u;
247
+ {% for m in range(aTilesPerSubgroup) %}
248
+ {% for n in range(bTilesPerSubgroup) %}
249
+ subgroupMatrixStore(&scratch[bank + {{ m * bTilesPerSubgroup + n }}u], 0u, matC{{ m }}{{ n }}, false, 8u);
250
+ {% endfor %}
251
+ {% endfor %}
252
+ workgroupBarrier();
253
+
254
+ {% for m in range(aTilesPerSubgroup) %}
255
+ let row_limit_{{ m }}: i32 = i32(params.M) - i32(a_global_base + base_A + {{ m * 8 }}u);
256
+ {% for n in range(bTilesPerSubgroup) %}
257
+ storeOutput(
258
+ matrix_c_offset + {{ m * 8 }}u * OUT_F + {{ n * 8 }}u{% if needsColBase %},
259
+ w_global_base + base_B + {{ n * 8 }}u,
260
+ {% else %},
261
+ {% endif %}
262
+ row,
263
+ col,
264
+ bank + {{ m * bTilesPerSubgroup + n }}u,
265
+ row_limit_{{ m }}
266
+ );
267
+ {% endfor %}
268
+ {% endfor %}
269
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,594 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "com.microsoft",
3
+ "name": "FusedGemm",
4
+ "sinceVersion": 1,
5
+ "description": "Gemm with a fused activation: `Y = act(alpha * A' * B' + beta * C)`, where `A'` and `B'` are optionally transposed and `C` is broadcastable to `(M, N)`. The activation runs in the f32 accumulator before the single output narrowing. This package supports `Relu`, `LeakyRelu`, `Sigmoid`, `Tanh` and `HardSigmoid`; the other activation strings and numeric types admitted by the open schema are not implemented. Omitting `activation` gives plain Gemm.",
6
+ "inputs": [
7
+ {
8
+ "role": "A",
9
+ "dtype": "T",
10
+ "rank": 2,
11
+ "description": "Left operand, `(M, K)` when `transA` is 0 and `(K, M)` otherwise."
12
+ },
13
+ {
14
+ "role": "B",
15
+ "dtype": "T",
16
+ "rank": 2,
17
+ "description": "Right operand, `(K, N)` when `transB` is 0 and `(N, K)` otherwise."
18
+ },
19
+ {
20
+ "role": "C",
21
+ "dtype": "T",
22
+ "optional": true,
23
+ "description": "Optional additive term, unidirectionally broadcastable to `(M, N)`: a scalar, a row `(N)`, a column `(M, 1)`, or the full matrix."
24
+ }
25
+ ],
26
+ "outputs": [
27
+ {
28
+ "role": "Y",
29
+ "dtype": "T",
30
+ "rank": 2,
31
+ "shape": "[gemmM, gemmN]",
32
+ "description": "`act(alpha * A' * B' + beta * C)` with shape `(M, N)`."
33
+ }
34
+ ],
35
+ "attributes": { "alpha": 1, "beta": 1, "transA": 0, "transB": 0 },
36
+ "attributeDescriptions": {
37
+ "alpha": "Scalar multiplier for the product `A' * B'`; the standard default is 1.",
38
+ "beta": "Scalar multiplier for `C`; the standard default is 1.",
39
+ "transA": "Whether `A` is stored transposed. The standard default is 0.",
40
+ "transB": "Whether `B` is stored transposed. The standard default is 0.",
41
+ "activation": "Optional fused activation name. Supported modes are `Relu`, `LeakyRelu`, `Sigmoid`, `Tanh` and `HardSigmoid`; omission applies none.",
42
+ "activation_alpha": "First activation parameter: the slope for `LeakyRelu` or `alpha` for `HardSigmoid`.",
43
+ "activation_beta": "Second activation parameter: `beta` for `HardSigmoid`."
44
+ },
45
+ "attributeConstraints": { "activation": { "values": ["Relu", "LeakyRelu", "Sigmoid", "Tanh", "HardSigmoid"] } },
46
+ "typeConstraints": { "T": ["float32", "float16"] },
47
+ "args": {
48
+ "A": { "kind": "tensor", "semantic": "A", "role": "input" },
49
+ "B": { "kind": "tensor", "semantic": "B", "role": "weights" },
50
+ "C": { "kind": "tensor", "semantic": "C", "role": "weights", "required": false },
51
+ "Y": { "kind": "tensor", "semantic": "Y", "role": "output" }
52
+ },
53
+ "derive": {
54
+ "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
55
+ "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
56
+ "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",
57
+ "pinSubgroupSize32": "canPinSubgroupSize32 and not wave32Adapter",
58
+ "wave32Effective": "wave32Adapter or pinSubgroupSize32",
59
+ "transAFlag": "has(attrs, \"transA\") and attrs.transA != 0",
60
+ "transBFlag": "has(attrs, \"transB\") and attrs.transB != 0",
61
+ "gemmM": "dim(shapes.A, 1) if transAFlag else dim(shapes.A, 0)",
62
+ "gemmK": "dim(shapes.A, 0) if transAFlag else dim(shapes.A, 1)",
63
+ "gemmN": "dim(shapes.B, 0) if transBFlag else dim(shapes.B, 1)",
64
+ "gemmKB": "dim(shapes.B, 1) if transBFlag else dim(shapes.B, 0)",
65
+ "shapeOk": "ranks.A == 2 and ranks.B == 2 and ranks.Y == 2 and gemmK == gemmKB and dim(shapes.Y, 0) == gemmM and dim(shapes.Y, 1) == gemmN",
66
+ "dtypeOk": "(tensorDtypes.A == \"float32\" or tensorDtypes.A == \"float16\") and tensorDtypes.B == tensorDtypes.A and tensorDtypes.Y == tensorDtypes.A and f16Ok(dtypes.T)",
67
+ "rowBiasOk": "present.C and ((ranks.C == 1 and dim(shapes.C, 0) == gemmN) or (ranks.C == 2 and dim(shapes.C, 0) == 1 and dim(shapes.C, 1) == gemmN))",
68
+ "scalarBiasOk": "present.C and (ranks.C == 0 or (ranks.C == 1 and dim(shapes.C, 0) == 1) or (ranks.C == 2 and dim(shapes.C, 0) == 1 and dim(shapes.C, 1) == 1))",
69
+ "columnBiasOk": "present.C and ranks.C == 2 and dim(shapes.C, 0) == gemmM and dim(shapes.C, 1) == 1",
70
+ "matrixBiasOk": "present.C and ranks.C == 2 and dim(shapes.C, 0) == gemmM and dim(shapes.C, 1) == gemmN",
71
+ "cOk": "tensorDtypes.C == tensorDtypes.A and (rowBiasOk or scalarBiasOk or columnBiasOk or matrixBiasOk)",
72
+ "hasActivation": "has(attrs, \"activation\")",
73
+ "activationName": "attrs.activation if hasActivation else \"\"",
74
+ "activationOk": "not hasActivation or activationName == \"Relu\" or activationName == \"LeakyRelu\" or activationName == \"Sigmoid\" or activationName == \"Tanh\" or activationName == \"HardSigmoid\"",
75
+ "activationAlpha": "attrs.activation_alpha if has(attrs, \"activation_alpha\") else (0.2 if activationName == \"HardSigmoid\" else (0.01 if activationName == \"LeakyRelu\" else 0.0))",
76
+ "activationBeta": "attrs.activation_beta if has(attrs, \"activation_beta\") else (0.5 if activationName == \"HardSigmoid\" else 0.0)",
77
+ "baseContract": "shapeOk and dtypeOk and activationOk",
78
+ "noBiasContract": "baseContract and not present.C",
79
+ "biasContract": "baseContract and present.C and cOk",
80
+ "sgmatOperandBytes": "2 if tensorDtypes.A == \"float16\" and tensorDtypes.B == \"float16\" else 4",
81
+ "sgmatStorageBytes": "(32 * 32 + 64 * 32) * sgmatOperandBytes + 32 * 64 * 4",
82
+ "sgmatResourcesFit": "128 <= deviceWorkgroupCap and sgmatStorageBytes <= device.limits.maxComputeWorkgroupStorageSize",
83
+ "sgmatScalingOk": "(attrs.alpha if has(attrs, \"alpha\") else 1) == 1 and (attrs.beta if has(attrs, \"beta\") else 1) == 1",
84
+ "sgmatLayoutOk": "gemmM > 0 and gemmK % 32 == 0 and gemmN % 64 == 0 and ((tensorDtypes.A == \"float16\" and device.features.has(\"shader-f16\") and gemmM >= 2) or (tensorDtypes.A == \"float32\" and gemmM >= 32)) and ceilDiv(gemmM, 32) <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(gemmN, 64) <= device.limits.maxComputeWorkgroupsPerDimension",
85
+ "sgmatContract": "wave32Effective and sgmatLayoutOk and sgmatScalingOk and sgmatResourcesFit",
86
+ "sgmatRowBiasContract": "sgmatContract and rowBiasOk and ranks.C == 1"
87
+ },
88
+ "constants": {
89
+ "usesF16": "tensorDtypes.A == \"float16\"",
90
+ "gemmActivation": "activationName",
91
+ "gemmActAlpha": "activationAlpha",
92
+ "gemmActBeta": "activationBeta",
93
+ "gemmEpilogue": "\"activation\" if hasActivation else \"none\""
94
+ },
95
+ "bindingSets": {
96
+ "noBias": [
97
+ { "name": "a", "arg": "A", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
98
+ { "name": "b", "arg": "B", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
99
+ { "name": "y", "arg": "Y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
100
+ {
101
+ "name": "params",
102
+ "semantic": "kernel.params",
103
+ "buffer": { "type": "uniform" },
104
+ "struct": {
105
+ "name": "Params",
106
+ "fields": [
107
+ { "name": "M", "type": "u32", "value": "gemmM" },
108
+ { "name": "N", "type": "u32", "value": "gemmN" },
109
+ { "name": "K", "type": "u32", "value": "gemmK" },
110
+ { "name": "alpha", "type": "f32", "value": "attrs.alpha if has(attrs, \"alpha\") else 1" }
111
+ ]
112
+ }
113
+ }
114
+ ],
115
+ "withBias": [
116
+ { "name": "a", "arg": "A", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
117
+ { "name": "b", "arg": "B", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
118
+ { "name": "bias", "arg": "C", "semantic": "C", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
119
+ { "name": "y", "arg": "Y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
120
+ {
121
+ "name": "params",
122
+ "semantic": "kernel.params",
123
+ "buffer": { "type": "uniform" },
124
+ "struct": {
125
+ "name": "Params",
126
+ "fields": [
127
+ { "name": "M", "type": "u32", "value": "gemmM" },
128
+ { "name": "N", "type": "u32", "value": "gemmN" },
129
+ { "name": "K", "type": "u32", "value": "gemmK" },
130
+ { "name": "alpha", "type": "f32", "value": "attrs.alpha if has(attrs, \"alpha\") else 1" },
131
+ { "name": "beta", "type": "f32", "value": "attrs.beta if has(attrs, \"beta\") else 1" }
132
+ ]
133
+ }
134
+ }
135
+ ],
136
+ "sgmatNoBias": [
137
+ { "name": "x", "arg": "A", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
138
+ { "name": "w", "arg": "B", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
139
+ { "name": "y", "arg": "Y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
140
+ {
141
+ "name": "params",
142
+ "semantic": "kernel.params",
143
+ "buffer": { "type": "uniform" },
144
+ "struct": { "name": "Params", "fields": [{ "name": "M", "type": "u32", "value": "gemmM" }] }
145
+ }
146
+ ],
147
+ "sgmatBias": [
148
+ { "name": "x", "arg": "A", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
149
+ { "name": "w", "arg": "B", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
150
+ { "name": "bias", "arg": "C", "semantic": "C", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
151
+ { "name": "y", "arg": "Y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
152
+ {
153
+ "name": "params",
154
+ "semantic": "kernel.params",
155
+ "buffer": { "type": "uniform" },
156
+ "struct": { "name": "Params", "fields": [{ "name": "M", "type": "u32", "value": "gemmM" }] }
157
+ }
158
+ ]
159
+ },
160
+ "variants": [
161
+ {
162
+ "id": "notrans_sgmat_bias",
163
+ "priority": 100,
164
+ "requires": {
165
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
166
+ "subgroupMatrixConfigs": [
167
+ { "componentType": "f16", "M": 8, "N": 8, "K": 8 },
168
+ { "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
169
+ ]
170
+ },
171
+ "when": ["sgmatRowBiasContract", "transAFlag == false", "transBFlag == false"],
172
+ "constants": {
173
+ "hasBias": true,
174
+ "aTransposedStorage": false,
175
+ "bTransposedStorage": "not false",
176
+ "xScalar": "\"f16\" if tensorDtypes.A == \"float16\" else \"f32\"",
177
+ "wScalar": "\"f16\" if tensorDtypes.B == \"float16\" else \"f32\"",
178
+ "bScalar": "\"f16\" if tensorDtypes.C == \"float16\" else \"f32\"",
179
+ "yScalar": "\"f16\" if tensorDtypes.Y == \"float16\" else \"f32\"",
180
+ "mmaScalar": "\"f16\" if tensorDtypes.A == \"float16\" and tensorDtypes.B == \"float16\" else \"f32\"",
181
+ "inFeatures": "gemmK",
182
+ "outFeatures": "gemmN"
183
+ },
184
+ "passes": [
185
+ {
186
+ "id": "main",
187
+ "name": "FusedGemm.SubgroupMatrixBias",
188
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
189
+ "bindings": "sgmatBias",
190
+ "dispatch": { "workgroups": "ceilDiv(gemmM, 32)", "x": "ceilDiv(gemmN, 64)" }
191
+ }
192
+ ]
193
+ },
194
+ {
195
+ "id": "notrans_sgmat",
196
+ "priority": 100,
197
+ "requires": {
198
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
199
+ "subgroupMatrixConfigs": [
200
+ { "componentType": "f16", "M": 8, "N": 8, "K": 8 },
201
+ { "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
202
+ ]
203
+ },
204
+ "when": ["noBiasContract and sgmatContract", "transAFlag == false", "transBFlag == false"],
205
+ "constants": {
206
+ "hasBias": false,
207
+ "aTransposedStorage": false,
208
+ "bTransposedStorage": "not false",
209
+ "xScalar": "\"f16\" if tensorDtypes.A == \"float16\" else \"f32\"",
210
+ "wScalar": "\"f16\" if tensorDtypes.B == \"float16\" else \"f32\"",
211
+ "yScalar": "\"f16\" if tensorDtypes.Y == \"float16\" else \"f32\"",
212
+ "mmaScalar": "\"f16\" if tensorDtypes.A == \"float16\" and tensorDtypes.B == \"float16\" else \"f32\"",
213
+ "inFeatures": "gemmK",
214
+ "outFeatures": "gemmN"
215
+ },
216
+ "passes": [
217
+ {
218
+ "id": "main",
219
+ "name": "FusedGemm.SubgroupMatrix",
220
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
221
+ "bindings": "sgmatNoBias",
222
+ "dispatch": { "workgroups": "ceilDiv(gemmM, 32)", "x": "ceilDiv(gemmN, 64)" }
223
+ }
224
+ ]
225
+ },
226
+ {
227
+ "id": "transA_sgmat_bias",
228
+ "priority": 100,
229
+ "requires": {
230
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
231
+ "subgroupMatrixConfigs": [
232
+ { "componentType": "f16", "M": 8, "N": 8, "K": 8 },
233
+ { "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
234
+ ]
235
+ },
236
+ "when": ["sgmatRowBiasContract", "transAFlag == true", "transBFlag == false"],
237
+ "constants": {
238
+ "hasBias": true,
239
+ "aTransposedStorage": true,
240
+ "bTransposedStorage": "not false",
241
+ "xScalar": "\"f16\" if tensorDtypes.A == \"float16\" else \"f32\"",
242
+ "wScalar": "\"f16\" if tensorDtypes.B == \"float16\" else \"f32\"",
243
+ "bScalar": "\"f16\" if tensorDtypes.C == \"float16\" else \"f32\"",
244
+ "yScalar": "\"f16\" if tensorDtypes.Y == \"float16\" else \"f32\"",
245
+ "mmaScalar": "\"f16\" if tensorDtypes.A == \"float16\" and tensorDtypes.B == \"float16\" else \"f32\"",
246
+ "inFeatures": "gemmK",
247
+ "outFeatures": "gemmN"
248
+ },
249
+ "passes": [
250
+ {
251
+ "id": "main",
252
+ "name": "FusedGemm.SubgroupMatrixBias",
253
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
254
+ "bindings": "sgmatBias",
255
+ "dispatch": { "workgroups": "ceilDiv(gemmM, 32)", "x": "ceilDiv(gemmN, 64)" }
256
+ }
257
+ ]
258
+ },
259
+ {
260
+ "id": "transA_sgmat",
261
+ "priority": 100,
262
+ "requires": {
263
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
264
+ "subgroupMatrixConfigs": [
265
+ { "componentType": "f16", "M": 8, "N": 8, "K": 8 },
266
+ { "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
267
+ ]
268
+ },
269
+ "when": ["noBiasContract and sgmatContract", "transAFlag == true", "transBFlag == false"],
270
+ "constants": {
271
+ "hasBias": false,
272
+ "aTransposedStorage": true,
273
+ "bTransposedStorage": "not false",
274
+ "xScalar": "\"f16\" if tensorDtypes.A == \"float16\" else \"f32\"",
275
+ "wScalar": "\"f16\" if tensorDtypes.B == \"float16\" else \"f32\"",
276
+ "yScalar": "\"f16\" if tensorDtypes.Y == \"float16\" else \"f32\"",
277
+ "mmaScalar": "\"f16\" if tensorDtypes.A == \"float16\" and tensorDtypes.B == \"float16\" else \"f32\"",
278
+ "inFeatures": "gemmK",
279
+ "outFeatures": "gemmN"
280
+ },
281
+ "passes": [
282
+ {
283
+ "id": "main",
284
+ "name": "FusedGemm.SubgroupMatrix",
285
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
286
+ "bindings": "sgmatNoBias",
287
+ "dispatch": { "workgroups": "ceilDiv(gemmM, 32)", "x": "ceilDiv(gemmN, 64)" }
288
+ }
289
+ ]
290
+ },
291
+ {
292
+ "id": "transB_sgmat_bias",
293
+ "priority": 100,
294
+ "requires": {
295
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
296
+ "subgroupMatrixConfigs": [
297
+ { "componentType": "f16", "M": 8, "N": 8, "K": 8 },
298
+ { "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
299
+ ]
300
+ },
301
+ "when": ["sgmatRowBiasContract", "transAFlag == false", "transBFlag == true"],
302
+ "constants": {
303
+ "hasBias": true,
304
+ "aTransposedStorage": false,
305
+ "bTransposedStorage": "not true",
306
+ "xScalar": "\"f16\" if tensorDtypes.A == \"float16\" else \"f32\"",
307
+ "wScalar": "\"f16\" if tensorDtypes.B == \"float16\" else \"f32\"",
308
+ "bScalar": "\"f16\" if tensorDtypes.C == \"float16\" else \"f32\"",
309
+ "yScalar": "\"f16\" if tensorDtypes.Y == \"float16\" else \"f32\"",
310
+ "mmaScalar": "\"f16\" if tensorDtypes.A == \"float16\" and tensorDtypes.B == \"float16\" else \"f32\"",
311
+ "inFeatures": "gemmK",
312
+ "outFeatures": "gemmN"
313
+ },
314
+ "passes": [
315
+ {
316
+ "id": "main",
317
+ "name": "FusedGemm.SubgroupMatrixBias",
318
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
319
+ "bindings": "sgmatBias",
320
+ "dispatch": { "workgroups": "ceilDiv(gemmM, 32)", "x": "ceilDiv(gemmN, 64)" }
321
+ }
322
+ ]
323
+ },
324
+ {
325
+ "id": "transB_sgmat",
326
+ "priority": 100,
327
+ "requires": {
328
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
329
+ "subgroupMatrixConfigs": [
330
+ { "componentType": "f16", "M": 8, "N": 8, "K": 8 },
331
+ { "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
332
+ ]
333
+ },
334
+ "when": ["noBiasContract and sgmatContract", "transAFlag == false", "transBFlag == true"],
335
+ "constants": {
336
+ "hasBias": false,
337
+ "aTransposedStorage": false,
338
+ "bTransposedStorage": "not true",
339
+ "xScalar": "\"f16\" if tensorDtypes.A == \"float16\" else \"f32\"",
340
+ "wScalar": "\"f16\" if tensorDtypes.B == \"float16\" else \"f32\"",
341
+ "yScalar": "\"f16\" if tensorDtypes.Y == \"float16\" else \"f32\"",
342
+ "mmaScalar": "\"f16\" if tensorDtypes.A == \"float16\" and tensorDtypes.B == \"float16\" else \"f32\"",
343
+ "inFeatures": "gemmK",
344
+ "outFeatures": "gemmN"
345
+ },
346
+ "passes": [
347
+ {
348
+ "id": "main",
349
+ "name": "FusedGemm.SubgroupMatrix",
350
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
351
+ "bindings": "sgmatNoBias",
352
+ "dispatch": { "workgroups": "ceilDiv(gemmM, 32)", "x": "ceilDiv(gemmN, 64)" }
353
+ }
354
+ ]
355
+ },
356
+ {
357
+ "id": "transAB_sgmat_bias",
358
+ "priority": 100,
359
+ "requires": {
360
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
361
+ "subgroupMatrixConfigs": [
362
+ { "componentType": "f16", "M": 8, "N": 8, "K": 8 },
363
+ { "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
364
+ ]
365
+ },
366
+ "when": ["sgmatRowBiasContract", "transAFlag == true", "transBFlag == true"],
367
+ "constants": {
368
+ "hasBias": true,
369
+ "aTransposedStorage": true,
370
+ "bTransposedStorage": "not true",
371
+ "xScalar": "\"f16\" if tensorDtypes.A == \"float16\" else \"f32\"",
372
+ "wScalar": "\"f16\" if tensorDtypes.B == \"float16\" else \"f32\"",
373
+ "bScalar": "\"f16\" if tensorDtypes.C == \"float16\" else \"f32\"",
374
+ "yScalar": "\"f16\" if tensorDtypes.Y == \"float16\" else \"f32\"",
375
+ "mmaScalar": "\"f16\" if tensorDtypes.A == \"float16\" and tensorDtypes.B == \"float16\" else \"f32\"",
376
+ "inFeatures": "gemmK",
377
+ "outFeatures": "gemmN"
378
+ },
379
+ "passes": [
380
+ {
381
+ "id": "main",
382
+ "name": "FusedGemm.SubgroupMatrixBias",
383
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
384
+ "bindings": "sgmatBias",
385
+ "dispatch": { "workgroups": "ceilDiv(gemmM, 32)", "x": "ceilDiv(gemmN, 64)" }
386
+ }
387
+ ]
388
+ },
389
+ {
390
+ "id": "transAB_sgmat",
391
+ "priority": 100,
392
+ "requires": {
393
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
394
+ "subgroupMatrixConfigs": [
395
+ { "componentType": "f16", "M": 8, "N": 8, "K": 8 },
396
+ { "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
397
+ ]
398
+ },
399
+ "when": ["noBiasContract and sgmatContract", "transAFlag == true", "transBFlag == true"],
400
+ "constants": {
401
+ "hasBias": false,
402
+ "aTransposedStorage": true,
403
+ "bTransposedStorage": "not true",
404
+ "xScalar": "\"f16\" if tensorDtypes.A == \"float16\" else \"f32\"",
405
+ "wScalar": "\"f16\" if tensorDtypes.B == \"float16\" else \"f32\"",
406
+ "yScalar": "\"f16\" if tensorDtypes.Y == \"float16\" else \"f32\"",
407
+ "mmaScalar": "\"f16\" if tensorDtypes.A == \"float16\" and tensorDtypes.B == \"float16\" else \"f32\"",
408
+ "inFeatures": "gemmK",
409
+ "outFeatures": "gemmN"
410
+ },
411
+ "passes": [
412
+ {
413
+ "id": "main",
414
+ "name": "FusedGemm.SubgroupMatrix",
415
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
416
+ "bindings": "sgmatNoBias",
417
+ "dispatch": { "workgroups": "ceilDiv(gemmM, 32)", "x": "ceilDiv(gemmN, 64)" }
418
+ }
419
+ ]
420
+ },
421
+ {
422
+ "id": "notrans_bias",
423
+ "priority": 0,
424
+ "when": ["biasContract", "transAFlag == false", "transBFlag == false"],
425
+ "constants": {
426
+ "transA": false,
427
+ "transB": false,
428
+ "hasBias": true,
429
+ "rowBias": "rowBiasOk",
430
+ "scalarBias": "scalarBiasOk",
431
+ "columnBias": "columnBiasOk"
432
+ },
433
+ "passes": [
434
+ {
435
+ "id": "main",
436
+ "name": "FusedGemm.Bias",
437
+ "shader": "fused-gemm.wgsl.jinja",
438
+ "bindings": "withBias",
439
+ "dispatch": {
440
+ "x": "min(ceilDiv(gemmN, 64), device.limits.maxComputeWorkgroupsPerDimension)",
441
+ "y": "min(ceilDiv(gemmM, 64), device.limits.maxComputeWorkgroupsPerDimension)"
442
+ }
443
+ }
444
+ ]
445
+ },
446
+ {
447
+ "id": "notrans",
448
+ "priority": 0,
449
+ "when": ["noBiasContract", "transAFlag == false", "transBFlag == false"],
450
+ "constants": { "transA": false, "transB": false, "hasBias": false },
451
+ "passes": [
452
+ {
453
+ "id": "main",
454
+ "name": "FusedGemm",
455
+ "shader": "fused-gemm.wgsl.jinja",
456
+ "bindings": "noBias",
457
+ "dispatch": {
458
+ "x": "min(ceilDiv(gemmN, 64), device.limits.maxComputeWorkgroupsPerDimension)",
459
+ "y": "min(ceilDiv(gemmM, 64), device.limits.maxComputeWorkgroupsPerDimension)"
460
+ }
461
+ }
462
+ ]
463
+ },
464
+ {
465
+ "id": "transA_bias",
466
+ "priority": 0,
467
+ "when": ["biasContract", "transAFlag == true", "transBFlag == false"],
468
+ "constants": {
469
+ "transA": true,
470
+ "transB": false,
471
+ "hasBias": true,
472
+ "rowBias": "rowBiasOk",
473
+ "scalarBias": "scalarBiasOk",
474
+ "columnBias": "columnBiasOk"
475
+ },
476
+ "passes": [
477
+ {
478
+ "id": "main",
479
+ "name": "FusedGemm.Bias",
480
+ "shader": "fused-gemm.wgsl.jinja",
481
+ "bindings": "withBias",
482
+ "dispatch": {
483
+ "x": "min(ceilDiv(gemmN, 64), device.limits.maxComputeWorkgroupsPerDimension)",
484
+ "y": "min(ceilDiv(gemmM, 64), device.limits.maxComputeWorkgroupsPerDimension)"
485
+ }
486
+ }
487
+ ]
488
+ },
489
+ {
490
+ "id": "transA",
491
+ "priority": 0,
492
+ "when": ["noBiasContract", "transAFlag == true", "transBFlag == false"],
493
+ "constants": { "transA": true, "transB": false, "hasBias": false },
494
+ "passes": [
495
+ {
496
+ "id": "main",
497
+ "name": "FusedGemm",
498
+ "shader": "fused-gemm.wgsl.jinja",
499
+ "bindings": "noBias",
500
+ "dispatch": {
501
+ "x": "min(ceilDiv(gemmN, 64), device.limits.maxComputeWorkgroupsPerDimension)",
502
+ "y": "min(ceilDiv(gemmM, 64), device.limits.maxComputeWorkgroupsPerDimension)"
503
+ }
504
+ }
505
+ ]
506
+ },
507
+ {
508
+ "id": "transB_bias",
509
+ "priority": 0,
510
+ "when": ["biasContract", "transAFlag == false", "transBFlag == true"],
511
+ "constants": {
512
+ "transA": false,
513
+ "transB": true,
514
+ "hasBias": true,
515
+ "rowBias": "rowBiasOk",
516
+ "scalarBias": "scalarBiasOk",
517
+ "columnBias": "columnBiasOk"
518
+ },
519
+ "passes": [
520
+ {
521
+ "id": "main",
522
+ "name": "FusedGemm.Bias",
523
+ "shader": "fused-gemm.wgsl.jinja",
524
+ "bindings": "withBias",
525
+ "dispatch": {
526
+ "x": "min(ceilDiv(gemmN, 64), device.limits.maxComputeWorkgroupsPerDimension)",
527
+ "y": "min(ceilDiv(gemmM, 64), device.limits.maxComputeWorkgroupsPerDimension)"
528
+ }
529
+ }
530
+ ]
531
+ },
532
+ {
533
+ "id": "transB",
534
+ "priority": 0,
535
+ "when": ["noBiasContract", "transAFlag == false", "transBFlag == true"],
536
+ "constants": { "transA": false, "transB": true, "hasBias": false },
537
+ "passes": [
538
+ {
539
+ "id": "main",
540
+ "name": "FusedGemm",
541
+ "shader": "fused-gemm.wgsl.jinja",
542
+ "bindings": "noBias",
543
+ "dispatch": {
544
+ "x": "min(ceilDiv(gemmN, 64), device.limits.maxComputeWorkgroupsPerDimension)",
545
+ "y": "min(ceilDiv(gemmM, 64), device.limits.maxComputeWorkgroupsPerDimension)"
546
+ }
547
+ }
548
+ ]
549
+ },
550
+ {
551
+ "id": "transAB_bias",
552
+ "priority": 0,
553
+ "when": ["biasContract", "transAFlag == true", "transBFlag == true"],
554
+ "constants": {
555
+ "transA": true,
556
+ "transB": true,
557
+ "hasBias": true,
558
+ "rowBias": "rowBiasOk",
559
+ "scalarBias": "scalarBiasOk",
560
+ "columnBias": "columnBiasOk"
561
+ },
562
+ "passes": [
563
+ {
564
+ "id": "main",
565
+ "name": "FusedGemm.Bias",
566
+ "shader": "fused-gemm.wgsl.jinja",
567
+ "bindings": "withBias",
568
+ "dispatch": {
569
+ "x": "min(ceilDiv(gemmN, 64), device.limits.maxComputeWorkgroupsPerDimension)",
570
+ "y": "min(ceilDiv(gemmM, 64), device.limits.maxComputeWorkgroupsPerDimension)"
571
+ }
572
+ }
573
+ ]
574
+ },
575
+ {
576
+ "id": "transAB",
577
+ "priority": 0,
578
+ "when": ["noBiasContract", "transAFlag == true", "transBFlag == true"],
579
+ "constants": { "transA": true, "transB": true, "hasBias": false },
580
+ "passes": [
581
+ {
582
+ "id": "main",
583
+ "name": "FusedGemm",
584
+ "shader": "fused-gemm.wgsl.jinja",
585
+ "bindings": "noBias",
586
+ "dispatch": {
587
+ "x": "min(ceilDiv(gemmN, 64), device.limits.maxComputeWorkgroupsPerDimension)",
588
+ "y": "min(ceilDiv(gemmM, 64), device.limits.maxComputeWorkgroupsPerDimension)"
589
+ }
590
+ }
591
+ ]
592
+ }
593
+ ]
594
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "com.microsoft.FusedGemm",
3
+ "id": "_com_microsoft_fusedgemm_webgpu_afa4467",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "5+Pgon1ED+99xPL6smxxz2G47C5gDnfB0kJ+Zj2wZLc=",
11
+ "fused-gemm.wgsl.jinja": "IkB53JqVNn9Xezw0YcKYDvm9IwDvoZNnSeA/UY4Eci4=",
12
+ "gemm-subgroup-matrix.wgsl.jinja": "gk0C1ZhDuTerG8rDtHYjQp74zieMlGP6QeFm0XqRZSw=",
13
+ "manifest.json": "s7cSq5so+VQS0XTbiHalmSHGnQQA2ylTu524o3V815U=",
14
+ "test.json": "xyQ7rg3TZtQ7zZA5W25GP1jcfMvCLIWiBlk6ASHNmi0="
15
+ }
16
+ },
17
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
18
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.FusedGemm" }
19
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,662 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "com.microsoft.FusedGemm",
3
+ "cases": [
4
+ {
5
+ "name": "notrans_rowbias_relu",
6
+ "provenance": { "notes": "No transpose, a row bias, and Relu." },
7
+ "attrs": { "activation": "Relu" },
8
+ "inputs": {
9
+ "A": {
10
+ "dtype": "float32",
11
+ "shape": [96, 80],
12
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.18000000000000002, "cosStep": 0.32 }
13
+ },
14
+ "B": {
15
+ "dtype": "float32",
16
+ "shape": [80, 72],
17
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.12, "cosStep": 0.24000000000000002 }
18
+ },
19
+ "C": {
20
+ "dtype": "float32",
21
+ "shape": [72],
22
+ "data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.3, "cosStep": 0.14 }
23
+ }
24
+ },
25
+ "outputs": { "Y": { "dtype": "float32", "shape": [96, 72], "tolerance": 0.00002, "relTolerance": 0.0002 } }
26
+ },
27
+ {
28
+ "name": "transa_rowbias_relu",
29
+ "provenance": { "notes": "transA: A stored (K, M), which changes only the load index." },
30
+ "attrs": { "transA": 1, "activation": "Relu" },
31
+ "inputs": {
32
+ "A": {
33
+ "dtype": "float32",
34
+ "shape": [80, 96],
35
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.19, "cosStep": 0.33 }
36
+ },
37
+ "B": {
38
+ "dtype": "float32",
39
+ "shape": [80, 72],
40
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.13, "cosStep": 0.25 }
41
+ },
42
+ "C": {
43
+ "dtype": "float32",
44
+ "shape": [72],
45
+ "data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.31, "cosStep": 0.15 }
46
+ }
47
+ },
48
+ "outputs": { "Y": { "dtype": "float32", "shape": [96, 72], "tolerance": 0.00002, "relTolerance": 0.0002 } }
49
+ },
50
+ {
51
+ "name": "transb_rowbias_leaky",
52
+ "provenance": { "notes": "transB with LeakyRelu at its default slope of 0.01." },
53
+ "attrs": { "transB": 1, "activation": "LeakyRelu" },
54
+ "inputs": {
55
+ "A": {
56
+ "dtype": "float32",
57
+ "shape": [96, 80],
58
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.2, "cosStep": 0.33999999999999997 }
59
+ },
60
+ "B": {
61
+ "dtype": "float32",
62
+ "shape": [72, 80],
63
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.14, "cosStep": 0.26 }
64
+ },
65
+ "C": {
66
+ "dtype": "float32",
67
+ "shape": [72],
68
+ "data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.31999999999999995, "cosStep": 0.16 }
69
+ }
70
+ },
71
+ "outputs": { "Y": { "dtype": "float32", "shape": [96, 72], "tolerance": 0.00002, "relTolerance": 0.0002 } }
72
+ },
73
+ {
74
+ "name": "transab_rowbias_tanh",
75
+ "provenance": { "notes": "Both operands transposed, with Tanh." },
76
+ "attrs": { "transA": 1, "transB": 1, "activation": "Tanh" },
77
+ "inputs": {
78
+ "A": {
79
+ "dtype": "float32",
80
+ "shape": [80, 96],
81
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.21000000000000002, "cosStep": 0.35 }
82
+ },
83
+ "B": {
84
+ "dtype": "float32",
85
+ "shape": [72, 80],
86
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.15, "cosStep": 0.27 }
87
+ },
88
+ "C": {
89
+ "dtype": "float32",
90
+ "shape": [72],
91
+ "data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.32999999999999996, "cosStep": 0.17 }
92
+ }
93
+ },
94
+ "outputs": { "Y": { "dtype": "float32", "shape": [96, 72], "tolerance": 0.00002, "relTolerance": 0.0002 } }
95
+ },
96
+ {
97
+ "name": "notrans_nobias_sigmoid",
98
+ "provenance": { "notes": "No bias at all -- the other binding set -- with Sigmoid." },
99
+ "attrs": { "activation": "Sigmoid" },
100
+ "inputs": {
101
+ "A": {
102
+ "dtype": "float32",
103
+ "shape": [96, 80],
104
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.22000000000000003, "cosStep": 0.36 }
105
+ },
106
+ "B": {
107
+ "dtype": "float32",
108
+ "shape": [80, 72],
109
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.16, "cosStep": 0.28 }
110
+ }
111
+ },
112
+ "outputs": { "Y": { "dtype": "float32", "shape": [96, 72], "tolerance": 0.00002, "relTolerance": 0.0002 } }
113
+ },
114
+ {
115
+ "name": "transa_nobias_none",
116
+ "provenance": { "notes": "transA with no bias and no activation, which is plain Gemm." },
117
+ "attrs": { "transA": 1 },
118
+ "inputs": {
119
+ "A": {
120
+ "dtype": "float32",
121
+ "shape": [48, 64],
122
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.23, "cosStep": 0.37 }
123
+ },
124
+ "B": {
125
+ "dtype": "float32",
126
+ "shape": [48, 40],
127
+ "data": {
128
+ "kind": "fillFloat32",
129
+ "scale": 0.6,
130
+ "sinStep": 0.16999999999999998,
131
+ "cosStep": 0.29000000000000004
132
+ }
133
+ }
134
+ },
135
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 40], "tolerance": 0.00002, "relTolerance": 0.0002 } }
136
+ },
137
+ {
138
+ "name": "transab_nobias_hardsigmoid",
139
+ "provenance": { "notes": "Both transposed, HardSigmoid at its 0.2/0.5 defaults." },
140
+ "attrs": { "transA": 1, "transB": 1, "activation": "HardSigmoid" },
141
+ "inputs": {
142
+ "A": {
143
+ "dtype": "float32",
144
+ "shape": [48, 64],
145
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.25, "cosStep": 0.39 }
146
+ },
147
+ "B": {
148
+ "dtype": "float32",
149
+ "shape": [40, 48],
150
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.19, "cosStep": 0.31 }
151
+ }
152
+ },
153
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 40], "tolerance": 0.00002, "relTolerance": 0.0002 } }
154
+ },
155
+ {
156
+ "name": "scalar_bias_relu",
157
+ "provenance": { "notes": "A scalar C, broadcast to every element." },
158
+ "attrs": { "activation": "Relu" },
159
+ "inputs": {
160
+ "A": {
161
+ "dtype": "float32",
162
+ "shape": [48, 32],
163
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.26, "cosStep": 0.4 }
164
+ },
165
+ "B": {
166
+ "dtype": "float32",
167
+ "shape": [32, 24],
168
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.2, "cosStep": 0.32 }
169
+ },
170
+ "C": {
171
+ "dtype": "float32",
172
+ "shape": [1],
173
+ "data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.38, "cosStep": 0.22 }
174
+ }
175
+ },
176
+ "outputs": { "Y": { "dtype": "float32", "shape": [48, 24], "tolerance": 0.00002, "relTolerance": 0.0002 } }
177
+ },
178
+ {
179
+ "name": "column_bias_relu",
180
+ "provenance": { "notes": "A column C of shape (M, 1)." },
181
+ "attrs": { "activation": "Relu" },
182
+ "inputs": {
183
+ "A": {
184
+ "dtype": "float32",
185
+ "shape": [48, 32],
186
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.27, "cosStep": 0.41000000000000003 }
187
+ },
188
+ "B": {
189
+ "dtype": "float32",
190
+ "shape": [32, 24],
191
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.21000000000000002, "cosStep": 0.33 }
192
+ },
193
+ "C": {
194
+ "dtype": "float32",
195
+ "shape": [48, 1],
196
+ "data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.39, "cosStep": 0.23 }
197
+ }
198
+ },
199
+ "outputs": { "Y": { "dtype": "float32", "shape": [48, 24], "tolerance": 0.00002, "relTolerance": 0.0002 } }
200
+ },
201
+ {
202
+ "name": "matrix_bias_relu",
203
+ "provenance": { "notes": "A full (M, N) C." },
204
+ "attrs": { "activation": "Relu" },
205
+ "inputs": {
206
+ "A": {
207
+ "dtype": "float32",
208
+ "shape": [48, 32],
209
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.28, "cosStep": 0.42 }
210
+ },
211
+ "B": {
212
+ "dtype": "float32",
213
+ "shape": [32, 24],
214
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.22, "cosStep": 0.34 }
215
+ },
216
+ "C": {
217
+ "dtype": "float32",
218
+ "shape": [48, 24],
219
+ "data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.39999999999999997, "cosStep": 0.24 }
220
+ }
221
+ },
222
+ "outputs": { "Y": { "dtype": "float32", "shape": [48, 24], "tolerance": 0.00002, "relTolerance": 0.0002 } }
223
+ },
224
+ {
225
+ "name": "row2d_bias_tanh",
226
+ "provenance": { "notes": "A (1, N) C, the rank-2 spelling of a row bias." },
227
+ "attrs": { "activation": "Tanh" },
228
+ "inputs": {
229
+ "A": {
230
+ "dtype": "float32",
231
+ "shape": [48, 32],
232
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.29000000000000004, "cosStep": 0.43 }
233
+ },
234
+ "B": {
235
+ "dtype": "float32",
236
+ "shape": [32, 24],
237
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.22999999999999998, "cosStep": 0.35 }
238
+ },
239
+ "C": {
240
+ "dtype": "float32",
241
+ "shape": [1, 24],
242
+ "data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.41, "cosStep": 0.25 }
243
+ }
244
+ },
245
+ "outputs": { "Y": { "dtype": "float32", "shape": [48, 24], "tolerance": 0.00002, "relTolerance": 0.0002 } }
246
+ },
247
+ {
248
+ "name": "alpha_beta_leaky_explicit",
249
+ "provenance": { "notes": "Explicit alpha, beta and LeakyRelu slope, none of them defaults." },
250
+ "attrs": { "alpha": 0.5, "beta": 2, "activation": "LeakyRelu", "activation_alpha": 0.25 },
251
+ "inputs": {
252
+ "A": {
253
+ "dtype": "float32",
254
+ "shape": [48, 32],
255
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.30000000000000004, "cosStep": 0.44 }
256
+ },
257
+ "B": {
258
+ "dtype": "float32",
259
+ "shape": [32, 24],
260
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.24, "cosStep": 0.36 }
261
+ },
262
+ "C": {
263
+ "dtype": "float32",
264
+ "shape": [24],
265
+ "data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.42, "cosStep": 0.26 }
266
+ }
267
+ },
268
+ "outputs": { "Y": { "dtype": "float32", "shape": [48, 24], "tolerance": 0.00002, "relTolerance": 0.0002 } }
269
+ },
270
+ {
271
+ "name": "alpha_zero_relu",
272
+ "provenance": {
273
+ "notes": "alpha = 0 nulls the product exactly rather than multiplying it, so an infinite accumulator cannot become NaN."
274
+ },
275
+ "attrs": { "alpha": 0, "activation": "Relu" },
276
+ "inputs": {
277
+ "A": {
278
+ "dtype": "float32",
279
+ "shape": [32, 24],
280
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.32, "cosStep": 0.45999999999999996 }
281
+ },
282
+ "B": {
283
+ "dtype": "float32",
284
+ "shape": [24, 16],
285
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.26, "cosStep": 0.38 }
286
+ },
287
+ "C": {
288
+ "dtype": "float32",
289
+ "shape": [16],
290
+ "data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.43999999999999995, "cosStep": 0.28 }
291
+ }
292
+ },
293
+ "outputs": { "Y": { "dtype": "float32", "shape": [32, 16], "tolerance": 0.00002, "relTolerance": 0.0002 } }
294
+ },
295
+ {
296
+ "name": "beta_zero_relu",
297
+ "provenance": { "notes": "beta = 0 nulls the C term the same way." },
298
+ "attrs": { "beta": 0, "activation": "Relu" },
299
+ "inputs": {
300
+ "A": {
301
+ "dtype": "float32",
302
+ "shape": [32, 24],
303
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.33, "cosStep": 0.47 }
304
+ },
305
+ "B": {
306
+ "dtype": "float32",
307
+ "shape": [24, 16],
308
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.27, "cosStep": 0.39 }
309
+ },
310
+ "C": {
311
+ "dtype": "float32",
312
+ "shape": [16],
313
+ "data": {
314
+ "kind": "fillFloat32",
315
+ "scale": 0.4,
316
+ "sinStep": 0.44999999999999996,
317
+ "cosStep": 0.29000000000000004
318
+ }
319
+ }
320
+ },
321
+ "outputs": { "Y": { "dtype": "float32", "shape": [32, 16], "tolerance": 0.00002, "relTolerance": 0.0002 } }
322
+ },
323
+ {
324
+ "name": "small_unaligned_relu",
325
+ "provenance": { "notes": "M, K and N all below one tile and none a multiple of the micro-tile." },
326
+ "attrs": { "activation": "Relu" },
327
+ "inputs": {
328
+ "A": {
329
+ "dtype": "float32",
330
+ "shape": [7, 13],
331
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.34, "cosStep": 0.48 }
332
+ },
333
+ "B": {
334
+ "dtype": "float32",
335
+ "shape": [13, 11],
336
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.28, "cosStep": 0.4 }
337
+ },
338
+ "C": {
339
+ "dtype": "float32",
340
+ "shape": [11],
341
+ "data": {
342
+ "kind": "fillFloat32",
343
+ "scale": 0.4,
344
+ "sinStep": 0.45999999999999996,
345
+ "cosStep": 0.30000000000000004
346
+ }
347
+ }
348
+ },
349
+ "outputs": { "Y": { "dtype": "float32", "shape": [7, 11], "tolerance": 0.00002, "relTolerance": 0.0002 } }
350
+ },
351
+ {
352
+ "name": "f16_notrans_bias_relu",
353
+ "provenance": { "notes": "float16 operands, accumulating in f32." },
354
+ "attrs": { "activation": "Relu" },
355
+ "inputs": {
356
+ "A": {
357
+ "dtype": "float16",
358
+ "shape": [64, 48],
359
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.35, "cosStep": 0.49 }
360
+ },
361
+ "B": {
362
+ "dtype": "float16",
363
+ "shape": [48, 32],
364
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.29, "cosStep": 0.41000000000000003 }
365
+ },
366
+ "C": {
367
+ "dtype": "float16",
368
+ "shape": [32],
369
+ "data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.47, "cosStep": 0.31 }
370
+ }
371
+ },
372
+ "outputs": { "Y": { "dtype": "float16", "shape": [64, 32], "tolerance": 0.006, "relTolerance": 0.006 } }
373
+ },
374
+ {
375
+ "name": "f16_transb_nobias_tanh",
376
+ "provenance": { "notes": "float16 with transB and no bias." },
377
+ "attrs": { "transB": 1, "activation": "Tanh" },
378
+ "inputs": {
379
+ "A": {
380
+ "dtype": "float16",
381
+ "shape": [64, 48],
382
+ "data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.36, "cosStep": 0.5 }
383
+ },
384
+ "B": {
385
+ "dtype": "float16",
386
+ "shape": [32, 48],
387
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.3, "cosStep": 0.42000000000000004 }
388
+ }
389
+ },
390
+ "outputs": { "Y": { "dtype": "float16", "shape": [64, 32], "tolerance": 0.006, "relTolerance": 0.006 } }
391
+ },
392
+ {
393
+ "name": "pinned_transab_leaky_alpha_beta",
394
+ "provenance": {
395
+ "notes": "Hand-computed at float64 from the schema's own definition -- alpha * A' * B' + beta * C, then LeakyRelu -- with both operands transposed and every scalar explicit, so nothing in the pin comes from a default."
396
+ },
397
+ "attrs": {
398
+ "transA": 1,
399
+ "transB": 1,
400
+ "alpha": 0.75,
401
+ "beta": 1.5,
402
+ "activation": "LeakyRelu",
403
+ "activation_alpha": 0.125
404
+ },
405
+ "inputs": {
406
+ "A": {
407
+ "dtype": "float32",
408
+ "shape": [10, 12],
409
+ "data": {
410
+ "kind": "values",
411
+ "values": [0.482, 0.233, -0.17, 1.167, -0.64, 1.418, 0.317, -0.314, 0.628, 0.761, 0.092, -1.224, 1.017, 0.692, -0.239, -1.141, 0.507, 1.027, 0.417, 1.008, -1.377, -1.169, 0.818, 0.125, -0.798, 0.491, -0.397, -0.028, 1.262, 0.159, -1.326, 0.973, -0.57, 0.244, 1.381, -0.037, -1.135, -0.158, -0.767, 0.452, 1.127, -0.526, 1.431, -0.255, -0.505, 0.154, -0.336, 0.414, -0.21, 1.258, -0.143, -0.146, -1.313, 0.676, -0.406, 1.497, -1.321, 1.005, 0.274, -1.266, -1.498, -0.927, 0.328, 0.949, -0.043, 1.29, -0.088, -0.031, 0.975, 0.247, 0.213, -0.993, 0.848, 0.355, 0.745, -0.006, 0.934, -0.819, 0.242, 1.171, -0.907, -1.153, 0.361, -0.612, -0.006, -1.408, 0.302, -0.531, 1.072, 0.002, 1.211, -0.427, 0.901, -0.552, -1.033, 0.857, -1.401, -0.719, -1.259, 0.575, -1.481, 0.209, 0.107, 0.975, 0.802, 1.009, 0.77, 1.493, 1.138, -1.32, 1.026, -0.854, -0.22, 1.076, -1.45, -0.272, 0.24, 1.368, -1.149, 0.598]
412
+ }
413
+ },
414
+ "B": {
415
+ "dtype": "float32",
416
+ "shape": [8, 10],
417
+ "data": {
418
+ "kind": "values",
419
+ "values": [-0.756, 1.16, -0.81, -1.402, -0.32, -0.036, 0.068, -1.181, -1.451, -1.096, -1.089, -0.777, 1.363, 0.017, -1.26, 1.485, 0.309, 1.036, -0.894, 1.06, 0.977, -1.123, -1.369, -1.108, 0.193, 0.923, -0.324, 0.673, 1.35, -1.017, 1.247, -0.487, 0.898, 0.209, 0.78, -1.249, 1.274, -0.422, -1.282, 0.118, 0.51, 0.864, -0.868, 1.06, -1.42, -1.341, -0.608, 0.888, 1.183, 0.199, 1.423, 0.737, -0.285, 0.903, 0.487, -0.826, 0.934, -1.365, -1.034, -1.222, 1.233, -0.428, -0.705, 1.431, -1.459, 0.817, -1.361, 1.45, 0.173, -1.252, -1.415, 1.357, -0.751, 0.894, -0.602, 0.879, 1.197, 0.168, -0.407, 0.107]
420
+ }
421
+ },
422
+ "C": {
423
+ "dtype": "float32",
424
+ "shape": [8],
425
+ "data": { "kind": "values", "values": [0.235, -0.223, 0.474, -0.529, 0.922, 0.252, -0.834, 0.806] }
426
+ }
427
+ },
428
+ "outputs": {
429
+ "Y": {
430
+ "dtype": "float32",
431
+ "shape": [12, 8],
432
+ "data": {
433
+ "kind": "values",
434
+ "values": [3.370844, -0.198133, -0.199107, 2.110806, 2.109943, 2.351428, -0.592314, 1.807759, 3.546125, -0.528558, -0.131459, 2.441212, -0.108338, 5.288458, -0.547352, 0.359243, 1.611335, 2.095594, -0.005854, 0.27388, -0.058539, -0.047104, -0.393263, 2.309827, -0.150555, -0.116139, 3.783087, -0.084227, 0.70041, 1.53985, 1.740793, -0.062086, 0.411694, 4.257617, -0.456835, 0.679208, 1.923927, 0.92273, 0.173925, 4.421494, -0.056756, -0.076294, 1.740675, -0.189957, 0.772669, -0.025928, -0.126539, 0.139902, -0.006079, -0.264435, 2.411458, -0.232052, 4.870933, 2.281323, 3.98095, 3.349453, 0.326566, -0.256703, -0.033174, 0.35729, -0.059635, 1.535964, -0.698491, 1.830789, -0.243493, 1.843096, 5.123723, -0.469259, 2.88782, -0.427406, 3.358449, -0.070443, -0.430807, -0.122892, 2.170899, -0.110731, 0.818956, -0.183576, -0.207642, -0.302042, 1.494748, -0.206163, 0.184732, -0.042274, -0.008104, 2.140054, -0.375211, 0.824137, -0.229193, 0.648641, 0.168956, -0.499118, 5.959787, -0.428765, 0.006611, 2.00139]
435
+ },
436
+ "tolerance": 0.00002,
437
+ "relTolerance": 0.0002
438
+ }
439
+ }
440
+ },
441
+ {
442
+ "name": "pinned_column_bias_hardsigmoid",
443
+ "provenance": { "notes": "Independently pinned column-bias broadcast with HardSigmoid at explicit parameters." },
444
+ "attrs": { "activation": "HardSigmoid", "activation_alpha": 0.3, "activation_beta": 0.4 },
445
+ "inputs": {
446
+ "A": {
447
+ "dtype": "float32",
448
+ "shape": [9, 7],
449
+ "data": {
450
+ "kind": "values",
451
+ "values": [-1.053, 0.354, -0.86, -0.3, 0.047, -0.117, -0.287, -0.319, -1.275, -0.229, 1.483, 0.499, 0.027, -0.762, -0.484, 1.402, -0.067, -1.282, 1.004, -0.316, 0.827, -0.547, 1.007, 0.853, 1.198, 0.01, -0.728, -0.59, 0.576, -0.694, 0.916, -1.297, -1.274, -0.773, -0.042, -0.708, -0.676, -0.463, -0.763, -0.803, 0.241, -0.813, 1.473, 0.728, 0.481, 0.679, -0.263, 0.948, 0.487, -0.141, 0.651, -1.067, 0.751, 1.368, 0.358, 0.552, 0.481, -1.401, 1.166, -0.338, -0.392, 0.853, 1.361]
452
+ }
453
+ },
454
+ "B": {
455
+ "dtype": "float32",
456
+ "shape": [7, 6],
457
+ "data": {
458
+ "kind": "values",
459
+ "values": [-0.006, -1.252, 0.157, -1.417, -1.042, 1.004, -0.995, 0.459, -1.464, 0.63, -0.926, -1.197, -0.321, -0.703, 0.213, 1.042, -0.752, -1.147, -0.595, -0.542, 1.233, 0.919, -1.13, 0.523, -1.206, -1.173, -0.179, 0.925, 0.323, -1.273, 0.567, 0.742, 0.435, 0.361, -1.469, -1.195, -1.211, -0.674, 0.828, 0.501, 0.515, -0.094]
460
+ }
461
+ },
462
+ "C": {
463
+ "dtype": "float32",
464
+ "shape": [9, 1],
465
+ "data": { "kind": "values", "values": [-0.957, 0.099, -0.259, -0.825, -0.224, -0.876, 0.837, -0.309, -0.723] }
466
+ }
467
+ },
468
+ "outputs": {
469
+ "Y": {
470
+ "dtype": "float32",
471
+ "shape": [9, 6],
472
+ "data": {
473
+ "kind": "values",
474
+ "values": [0.212855, 0.802754, 0.0, 0.233126, 0.651217, 0.0, 0.669088, 0.165598, 1.0, 0.688478, 0.351265, 0.924192, 0.0, 0.328861, 0.0, 0.78732, 0.898156, 0.0, 0.0, 0.075651, 0.0, 1.0, 0.0, 0.0, 1.0, 0.323342, 0.200611, 0.0, 0.78929, 1.0, 1.0, 1.0, 0.0, 0.0, 0.599796, 0.449499, 0.3431, 0.191342, 0.941447, 0.602963, 0.0, 0.521214, 0.0, 0.039497, 0.334728, 0.865142, 0.216728, 0.0, 0.340949, 0.0, 1.0, 0.173301, 0.069842, 0.182348]
475
+ },
476
+ "tolerance": 0.00002,
477
+ "relTolerance": 0.0002
478
+ }
479
+ }
480
+ },
481
+ {
482
+ "name": "sgmat_notrans_rowbias_relu",
483
+ "provenance": {
484
+ "notes": "Aligned for the subgroup-matrix gate (M >= 32, K % 32 == 0, N % 64 == 0), so this selects notrans_sgmat_bias where the unaligned fixtures above stay on the tiled kernel. Stages A as [M,K] and B as [K,N], which is the operand orientation this transpose pair asks the matrix kernel for, and fuses Relu in the accumulator domain over a rank-1 row bias."
485
+ },
486
+ "attrs": { "activation": "Relu" },
487
+ "inputs": {
488
+ "A": {
489
+ "dtype": "float32",
490
+ "shape": [64, 64],
491
+ "data": { "kind": "fillFloat32", "scale": 0.8, "sinStep": 0.11, "cosStep": 0.27 }
492
+ },
493
+ "B": {
494
+ "dtype": "float32",
495
+ "shape": [64, 128],
496
+ "data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.19, "cosStep": 0.13 }
497
+ },
498
+ "C": {
499
+ "dtype": "float32",
500
+ "shape": [128],
501
+ "data": { "kind": "fillFloat32", "scale": 0.35, "sinStep": 0.23, "cosStep": 0.41 }
502
+ }
503
+ },
504
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 128], "tolerance": 0.00002, "relTolerance": 0.0002 } }
505
+ },
506
+ {
507
+ "name": "sgmat_notrans_nobias_tanh",
508
+ "provenance": {
509
+ "notes": "The no-bias arm of the same gate and layout: the matrix kernel renders a separate set of store sites without the bias term."
510
+ },
511
+ "attrs": { "activation": "Tanh" },
512
+ "inputs": {
513
+ "A": {
514
+ "dtype": "float32",
515
+ "shape": [64, 64],
516
+ "data": { "kind": "fillFloat32", "scale": 0.8, "sinStep": 0.15, "cosStep": 0.27 }
517
+ },
518
+ "B": {
519
+ "dtype": "float32",
520
+ "shape": [64, 128],
521
+ "data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.19, "cosStep": 0.17 }
522
+ }
523
+ },
524
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 128], "tolerance": 0.00002, "relTolerance": 0.0002 } }
525
+ },
526
+ {
527
+ "name": "sgmat_transa_rowbias_tanh",
528
+ "provenance": {
529
+ "notes": "Aligned for the subgroup-matrix gate (M >= 32, K % 32 == 0, N % 64 == 0), so this selects transa_sgmat_bias where the unaligned fixtures above stay on the tiled kernel. Stages A as [K,M] and B as [K,N], which is the operand orientation this transpose pair asks the matrix kernel for, and fuses Tanh in the accumulator domain over a rank-1 row bias."
530
+ },
531
+ "attrs": { "transA": 1, "activation": "Tanh" },
532
+ "inputs": {
533
+ "A": {
534
+ "dtype": "float32",
535
+ "shape": [64, 64],
536
+ "data": { "kind": "fillFloat32", "scale": 0.8, "sinStep": 0.12, "cosStep": 0.27 }
537
+ },
538
+ "B": {
539
+ "dtype": "float32",
540
+ "shape": [64, 128],
541
+ "data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.19, "cosStep": 0.14 }
542
+ },
543
+ "C": {
544
+ "dtype": "float32",
545
+ "shape": [128],
546
+ "data": { "kind": "fillFloat32", "scale": 0.35, "sinStep": 0.23, "cosStep": 0.41 }
547
+ }
548
+ },
549
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 128], "tolerance": 0.00002, "relTolerance": 0.0002 } }
550
+ },
551
+ {
552
+ "name": "sgmat_transa_nobias_sigmoid",
553
+ "provenance": {
554
+ "notes": "The no-bias arm of the same gate and layout: the matrix kernel renders a separate set of store sites without the bias term."
555
+ },
556
+ "attrs": { "transA": 1, "activation": "Sigmoid" },
557
+ "inputs": {
558
+ "A": {
559
+ "dtype": "float32",
560
+ "shape": [64, 64],
561
+ "data": { "kind": "fillFloat32", "scale": 0.8, "sinStep": 0.16, "cosStep": 0.27 }
562
+ },
563
+ "B": {
564
+ "dtype": "float32",
565
+ "shape": [64, 128],
566
+ "data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.19, "cosStep": 0.18 }
567
+ }
568
+ },
569
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 128], "tolerance": 0.00002, "relTolerance": 0.0002 } }
570
+ },
571
+ {
572
+ "name": "sgmat_transb_rowbias_sigmoid",
573
+ "provenance": {
574
+ "notes": "Aligned for the subgroup-matrix gate (M >= 32, K % 32 == 0, N % 64 == 0), so this selects transb_sgmat_bias where the unaligned fixtures above stay on the tiled kernel. Stages A as [M,K] and B as [N,K], which is the operand orientation this transpose pair asks the matrix kernel for, and fuses Sigmoid in the accumulator domain over a rank-1 row bias."
575
+ },
576
+ "attrs": { "transB": 1, "activation": "Sigmoid" },
577
+ "inputs": {
578
+ "A": {
579
+ "dtype": "float32",
580
+ "shape": [64, 64],
581
+ "data": { "kind": "fillFloat32", "scale": 0.8, "sinStep": 0.13, "cosStep": 0.27 }
582
+ },
583
+ "B": {
584
+ "dtype": "float32",
585
+ "shape": [128, 64],
586
+ "data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.19, "cosStep": 0.15 }
587
+ },
588
+ "C": {
589
+ "dtype": "float32",
590
+ "shape": [128],
591
+ "data": { "kind": "fillFloat32", "scale": 0.35, "sinStep": 0.23, "cosStep": 0.41 }
592
+ }
593
+ },
594
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 128], "tolerance": 0.00002, "relTolerance": 0.0002 } }
595
+ },
596
+ {
597
+ "name": "sgmat_transb_nobias_leakyrelu",
598
+ "provenance": {
599
+ "notes": "The no-bias arm of the same gate and layout: the matrix kernel renders a separate set of store sites without the bias term."
600
+ },
601
+ "attrs": { "transB": 1, "activation": "LeakyRelu" },
602
+ "inputs": {
603
+ "A": {
604
+ "dtype": "float32",
605
+ "shape": [64, 64],
606
+ "data": { "kind": "fillFloat32", "scale": 0.8, "sinStep": 0.16999999999999998, "cosStep": 0.27 }
607
+ },
608
+ "B": {
609
+ "dtype": "float32",
610
+ "shape": [128, 64],
611
+ "data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.19, "cosStep": 0.19 }
612
+ }
613
+ },
614
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 128], "tolerance": 0.00002, "relTolerance": 0.0002 } }
615
+ },
616
+ {
617
+ "name": "sgmat_transab_rowbias_leakyrelu",
618
+ "provenance": {
619
+ "notes": "Aligned for the subgroup-matrix gate (M >= 32, K % 32 == 0, N % 64 == 0), so this selects transab_sgmat_bias where the unaligned fixtures above stay on the tiled kernel. Stages A as [K,M] and B as [N,K], which is the operand orientation this transpose pair asks the matrix kernel for, and fuses LeakyRelu in the accumulator domain over a rank-1 row bias."
620
+ },
621
+ "attrs": { "transA": 1, "transB": 1, "activation": "LeakyRelu" },
622
+ "inputs": {
623
+ "A": {
624
+ "dtype": "float32",
625
+ "shape": [64, 64],
626
+ "data": { "kind": "fillFloat32", "scale": 0.8, "sinStep": 0.14, "cosStep": 0.27 }
627
+ },
628
+ "B": {
629
+ "dtype": "float32",
630
+ "shape": [128, 64],
631
+ "data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.19, "cosStep": 0.16 }
632
+ },
633
+ "C": {
634
+ "dtype": "float32",
635
+ "shape": [128],
636
+ "data": { "kind": "fillFloat32", "scale": 0.35, "sinStep": 0.23, "cosStep": 0.41 }
637
+ }
638
+ },
639
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 128], "tolerance": 0.00002, "relTolerance": 0.0002 } }
640
+ },
641
+ {
642
+ "name": "sgmat_transab_nobias_relu",
643
+ "provenance": {
644
+ "notes": "The no-bias arm of the same gate and layout: the matrix kernel renders a separate set of store sites without the bias term."
645
+ },
646
+ "attrs": { "transA": 1, "transB": 1, "activation": "Relu" },
647
+ "inputs": {
648
+ "A": {
649
+ "dtype": "float32",
650
+ "shape": [64, 64],
651
+ "data": { "kind": "fillFloat32", "scale": 0.8, "sinStep": 0.18, "cosStep": 0.27 }
652
+ },
653
+ "B": {
654
+ "dtype": "float32",
655
+ "shape": [128, 64],
656
+ "data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.19, "cosStep": 0.2 }
657
+ }
658
+ },
659
+ "outputs": { "Y": { "dtype": "float32", "shape": [64, 128], "tolerance": 0.00002, "relTolerance": 0.0002 } }
660
+ }
661
+ ]
662
+ }