Xenova HF Staff commited on
Commit
bb8ee13
·
verified ·
1 Parent(s): 8386dfb

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,16 +18,16 @@ See the [ONNX Runtime `FusedMatMul` contrib-operator spec](https://github.com/mi
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
 
@@ -47,17 +47,31 @@ Default values (overridable per request):
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)
@@ -66,10 +80,14 @@ Some implementation variants require `subgroup-matrix` and `subgroups`. These ar
66
 
67
  ## Use with `@huggingface/kernels`
68
 
69
- The loader derives every required output's shape and logical dtype from the manifest contract and this call.
70
- It then allocates the result tensors automatically.
 
 
 
71
 
72
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
73
 
74
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
75
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- |
23
+ | `A` | `T` | — | — | N-dimensional matrix A. | required |
24
+ | `B` | `T` | — | — | N-dimensional matrix B. | required |
25
 
26
  ## Outputs
27
 
28
+ | Name | Logical dtype | Rank | Shape | Description | Presence |
29
+ | --- | --- | --- | --- | --- | --- |
30
+ | `Y` | `T` | derived | derived | Matrix-multiplication result whose shape follows NumPy-style rules after applying the requested batch and matrix transpositions. | required |
31
 
32
  ## Attributes
33
 
 
47
  | --- | --- |
48
  | `T` | `float32`, `float16` |
49
 
50
+ ## Implementation variants
51
+
52
+ One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
53
+
54
+ - `subgroup_matrix_transbatch_b_f16` — Aligned rank-3 transBatchB product with sufficient reduction depth and output tiles to amortize subgroup-matrix staging.
55
+ - `subgroup_matrix_transbatch_b_f32` — Aligned rank-3 transBatchB product with sufficient reduction depth and output tiles to amortize subgroup-matrix staging.
56
+ - `rank2_band_vec4_splitk` — Splits the vec4 band's K axis across up to sixteen workgroups. Each range writes an f32 partial band with alpha applied, and a combine pass sums the partials.
57
+ - `rank2_band_vec4` — Few-row band for a rank-2 product without transposes. Each lane owns one vec4 column group and one accumulator per row, so every B word feeds all 2 to 16 rows; alpha is applied at the store.
58
+ - `rank2_band_vec4_f32_preferred` — Few-row band for a rank-2 product without transposes. Each lane owns one vec4 column group and one accumulator per row, so every B word feeds all 2 to 16 rows; alpha is applied at the store.
59
+ - `subgroup_matrix_splitk` — Partitions the K reduction of small-M rank-2 products across subgroup-matrix workgroups, then combines float32 partials that already include alpha.
60
+ - `plain_rank2_tiled_reg` — Register-blocked rank-2 `Y = alpha * A @ B` specialization for non-transposed inputs on tiers without subgroup-matrix support.
61
+ - `transbatch_b_tiled_reg` — Register-blocked logical rank3 product with an interleaved physical B batch axis.
62
+
63
  ## Device requirements
64
 
65
  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.
66
 
67
  ## Files
68
 
69
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
70
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
71
  - [`test.json`](build/webgpu/test.json) — correctness cases
72
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
73
  - [`fused-matmul-subgroup-matrix.wgsl.jinja`](build/webgpu/fused-matmul-subgroup-matrix.wgsl.jinja)
74
+ - [`matmul-band-vec4.wgsl.jinja`](build/webgpu/matmul-band-vec4.wgsl.jinja)
75
  - [`matmul-subgroup-matrix-ext.wgsl.jinja`](build/webgpu/matmul-subgroup-matrix-ext.wgsl.jinja)
76
  - [`matmul-tiled-general-reg.wgsl.jinja`](build/webgpu/matmul-tiled-general-reg.wgsl.jinja)
77
  - [`matmul-tiled-general.wgsl.jinja`](build/webgpu/matmul-tiled-general.wgsl.jinja)
 
80
 
81
  ## Use with `@huggingface/kernels`
82
 
83
+ ```sh
84
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
85
+ ```
86
+
87
+ Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
88
 
89
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
90
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
91
 
92
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
93
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "com.microsoft.FusedMatMul",
3
  "cases": [
4
  {
5
  "name": "fusedmatmul-f32-attn-scores-transB-8x512x64",
@@ -164,7 +163,7 @@
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 },
@@ -191,7 +190,7 @@
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 },
@@ -278,6 +277,228 @@
278
  },
279
  "outputs": { "Y": { "shape": [8, 4, 512], "dtype": "float16", "dist": "empty" } },
280
  "bench": { "metrics": [{ "type": "gflops", "value": "2 * 8 * 4 * 64 * 512" }] }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  }
282
  ]
283
  }
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "fusedmatmul-f32-attn-scores-transB-8x512x64",
 
163
  "name": "fusedmatmul-f16-rank4-by-rank2-shared-weight-b2h8-m512-k2048-n512-pathology",
164
  "preset": "stress",
165
  "provenance": {
166
+ "notes": "A production-scale batched projection shares one rank-2 weight across batches. This NumPy-style broadcast shape exercises the register-blocked portable route."
167
  },
168
  "attrs": { "alpha": 1 },
169
  "vars": { "dtype": "float16", "M": 512, "K": 2048, "N": 512 },
 
190
  "name": "fusedmatmul-f16-transbatch-b-8x512x2048x512-pathology",
191
  "preset": "stress",
192
  "provenance": {
193
+ "notes": "Interleaved B batches in a production-scale projection; compares stride-aware subgroup matrices with the portable tiled path."
194
  },
195
  "attrs": { "alpha": 1, "transBatchB": 1 },
196
  "vars": { "batch": 8, "M": 512, "K": 2048, "N": 512 },
 
277
  },
278
  "outputs": { "Y": { "shape": [8, 4, 512], "dtype": "float16", "dist": "empty" } },
279
  "bench": { "metrics": [{ "type": "gflops", "value": "2 * 8 * 4 * 64 * 512" }] }
280
+ },
281
+ {
282
+ "name": "fusedmatmul-f32-decode-gemv-m1-k4096-n4096-alpha-half",
283
+ "provenance": {
284
+ "notes": "A 1x4096 by 4096x4096 decode GEMV with alpha=0.5 exercises the single-row route with a baked non-unit output multiplier."
285
+ },
286
+ "preset": "stress",
287
+ "attrs": { "alpha": 0.5 },
288
+ "vars": { "dtype": "float32", "M": 1, "K": 4096, "N": 4096 },
289
+ "inputs": {
290
+ "A": { "shape": [1, 4096], "dtype": "float32", "dist": "normal", "seed": 740, "scale": 0.1 },
291
+ "B": { "shape": [4096, 4096], "dtype": "float32", "dist": "normal", "seed": 741, "scale": 0.05 }
292
+ },
293
+ "outputs": { "Y": { "shape": [1, 4096], "dtype": "float32", "dist": "empty" } },
294
+ "bench": {
295
+ "primary": true,
296
+ "metrics": [
297
+ {
298
+ "type": "bandwidth",
299
+ "value": "(args.M * args.K + args.K * args.N + args.M * args.N) * dtypeBytes(args.dtype)"
300
+ }
301
+ ]
302
+ }
303
+ },
304
+ {
305
+ "name": "fusedmatmul-f32-transbatch-b-8x512x2048x512-pathology",
306
+ "preset": "stress",
307
+ "provenance": {
308
+ "notes": "Interleaved B batches in a production-scale projection; compares stride-aware subgroup matrices with the portable tiled path."
309
+ },
310
+ "attrs": { "alpha": 1, "transBatchB": 1 },
311
+ "vars": { "batch": 8, "M": 512, "K": 2048, "N": 512 },
312
+ "inputs": {
313
+ "A": { "shape": [8, 512, 2048], "dtype": "float32", "dist": "normal", "seed": 722, "scale": 0.05 },
314
+ "B": { "shape": [2048, 8, 512], "dtype": "float32", "dist": "normal", "seed": 723, "scale": 0.05 }
315
+ },
316
+ "outputs": { "Y": { "shape": [8, 512, 512], "dtype": "float32", "dist": "empty" } },
317
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * 8 * 512 * 512 * 2048" }] }
318
+ },
319
+ {
320
+ "name": "fusedmatmul-float16-transbatch-b-k128-workgroups64",
321
+ "preset": "stress",
322
+ "provenance": {
323
+ "notes": "Aligned interleaved B layout at the reduction-length and 64-matrix-workgroup selector floors."
324
+ },
325
+ "attrs": { "alpha": 0.5, "transBatchB": 1 },
326
+ "vars": { "batch": 2, "M": 128, "K": 128, "N": 512 },
327
+ "inputs": {
328
+ "A": { "shape": [2, 128, 128], "dtype": "float16", "dist": "normal", "seed": 722, "scale": 0.05 },
329
+ "B": { "shape": [128, 2, 512], "dtype": "float16", "dist": "normal", "seed": 723, "scale": 0.05 }
330
+ },
331
+ "outputs": { "Y": { "shape": [2, 128, 512], "dtype": "float16", "dist": "empty" } },
332
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * args.batch * args.M * args.N * args.K" }] }
333
+ },
334
+ {
335
+ "name": "fusedmatmul-float32-transbatch-b-k128-workgroups64",
336
+ "preset": "stress",
337
+ "provenance": {
338
+ "notes": "Aligned interleaved B layout at the reduction-length and 64-matrix-workgroup selector floors."
339
+ },
340
+ "attrs": { "alpha": 0.5, "transBatchB": 1 },
341
+ "vars": { "batch": 2, "M": 128, "K": 128, "N": 512 },
342
+ "inputs": {
343
+ "A": { "shape": [2, 128, 128], "dtype": "float32", "dist": "normal", "seed": 722, "scale": 0.05 },
344
+ "B": { "shape": [128, 2, 512], "dtype": "float32", "dist": "normal", "seed": 723, "scale": 0.05 }
345
+ },
346
+ "outputs": { "Y": { "shape": [2, 128, 512], "dtype": "float32", "dist": "empty" } },
347
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * args.batch * args.M * args.N * args.K" }] }
348
+ },
349
+ {
350
+ "name": "transbatch_b_portable_reg_aligned_float16",
351
+ "attrs": { "transBatchB": 1, "alpha": -0.5 },
352
+ "inputs": {
353
+ "A": {
354
+ "dtype": "float16",
355
+ "shape": [4, 128, 128],
356
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.05, "offset": 0.2 }
357
+ },
358
+ "B": {
359
+ "dtype": "float16",
360
+ "shape": [128, 4, 512],
361
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.07, "offset": 0.3 }
362
+ }
363
+ },
364
+ "outputs": { "Y": { "dtype": "float16", "shape": [4, 128, 512] } },
365
+ "provenance": {
366
+ "notes": "Portable register tile transBatchB: physical B[K,batch,N], natural tile count reaches the shared occupancy gate; negative alpha and positive inputs avoid cancellation."
367
+ },
368
+ "preset": "smoke",
369
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * numel(shapes.Y) * dim(shapes.A, 2)" }] }
370
+ },
371
+ {
372
+ "name": "transbatch_b_portable_reg_tails_float16",
373
+ "attrs": { "transBatchB": 1, "alpha": -0.5 },
374
+ "inputs": {
375
+ "A": {
376
+ "dtype": "float16",
377
+ "shape": [3, 129, 131],
378
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.05, "offset": 0.2 }
379
+ },
380
+ "B": {
381
+ "dtype": "float16",
382
+ "shape": [131, 3, 513],
383
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.07, "offset": 0.3 }
384
+ }
385
+ },
386
+ "outputs": { "Y": { "dtype": "float16", "shape": [3, 129, 513] } },
387
+ "provenance": {
388
+ "notes": "Portable register tile transBatchB: physical B[K,batch,N], natural tile count reaches the shared occupancy gate; negative alpha and positive inputs avoid cancellation."
389
+ },
390
+ "preset": "smoke",
391
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * numel(shapes.Y) * dim(shapes.A, 2)" }] }
392
+ },
393
+ {
394
+ "name": "transbatch_b_portable_reg_aligned_float32",
395
+ "attrs": { "transBatchB": 1, "alpha": -0.5 },
396
+ "inputs": {
397
+ "A": {
398
+ "dtype": "float32",
399
+ "shape": [4, 128, 128],
400
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.05, "offset": 0.2 }
401
+ },
402
+ "B": {
403
+ "dtype": "float32",
404
+ "shape": [128, 4, 512],
405
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.07, "offset": 0.3 }
406
+ }
407
+ },
408
+ "outputs": { "Y": { "dtype": "float32", "shape": [4, 128, 512] } },
409
+ "provenance": {
410
+ "notes": "Portable register tile transBatchB: physical B[K,batch,N], natural tile count reaches the shared occupancy gate; negative alpha and positive inputs avoid cancellation."
411
+ },
412
+ "preset": "smoke",
413
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * numel(shapes.Y) * dim(shapes.A, 2)" }] }
414
+ },
415
+ {
416
+ "name": "transbatch_b_portable_reg_tails_float32",
417
+ "attrs": { "transBatchB": 1, "alpha": -0.5 },
418
+ "inputs": {
419
+ "A": {
420
+ "dtype": "float32",
421
+ "shape": [3, 129, 131],
422
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.05, "offset": 0.2 }
423
+ },
424
+ "B": {
425
+ "dtype": "float32",
426
+ "shape": [131, 3, 513],
427
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.07, "offset": 0.3 }
428
+ }
429
+ },
430
+ "outputs": { "Y": { "dtype": "float32", "shape": [3, 129, 513] } },
431
+ "provenance": {
432
+ "notes": "Portable register tile transBatchB: physical B[K,batch,N], natural tile count reaches the shared occupancy gate; negative alpha and positive inputs avoid cancellation."
433
+ },
434
+ "preset": "smoke",
435
+ "bench": { "metrics": [{ "type": "gflops", "value": "2 * numel(shapes.Y) * dim(shapes.A, 2)" }] }
436
+ },
437
+ {
438
+ "name": "f32-band-preference-m4-k2048-n4096",
439
+ "preset": "model",
440
+ "attrs": { "alpha": 0.5 },
441
+ "inputs": {
442
+ "A": { "dtype": "float32", "shape": [4, 2048], "dist": "normal", "seed": 9214, "scale": 0.1 },
443
+ "B": { "dtype": "float32", "shape": [2048, 4096], "dist": "normal", "seed": 9215, "scale": 0.1 }
444
+ },
445
+ "outputs": { "Y": { "dtype": "float32", "shape": [4, 4096], "dist": "empty" } },
446
+ "bench": { "metrics": [{ "type": "gflops", "value": 67108864 }] }
447
+ },
448
+ {
449
+ "name": "f32-band-preference-m4-k8192-n4096",
450
+ "preset": "model",
451
+ "attrs": { "alpha": 0.5 },
452
+ "inputs": {
453
+ "A": { "dtype": "float32", "shape": [4, 8192], "dist": "normal", "seed": 9216, "scale": 0.1 },
454
+ "B": { "dtype": "float32", "shape": [8192, 4096], "dist": "normal", "seed": 9217, "scale": 0.1 }
455
+ },
456
+ "outputs": { "Y": { "dtype": "float32", "shape": [4, 4096], "dist": "empty" } },
457
+ "bench": { "metrics": [{ "type": "gflops", "value": 268435456 }] }
458
+ },
459
+ {
460
+ "name": "f32-band-preference-m8-k2048-n4096",
461
+ "preset": "model",
462
+ "attrs": { "alpha": 0.5 },
463
+ "inputs": {
464
+ "A": { "dtype": "float32", "shape": [8, 2048], "dist": "normal", "seed": 9222, "scale": 0.1 },
465
+ "B": { "dtype": "float32", "shape": [2048, 4096], "dist": "normal", "seed": 9223, "scale": 0.1 }
466
+ },
467
+ "outputs": { "Y": { "dtype": "float32", "shape": [8, 4096], "dist": "empty" } },
468
+ "bench": { "metrics": [{ "type": "gflops", "value": 134217728 }] }
469
+ },
470
+ {
471
+ "name": "f32-band-preference-m8-k8192-n4096",
472
+ "preset": "model",
473
+ "attrs": { "alpha": 0.5 },
474
+ "inputs": {
475
+ "A": { "dtype": "float32", "shape": [8, 8192], "dist": "normal", "seed": 9224, "scale": 0.1 },
476
+ "B": { "dtype": "float32", "shape": [8192, 4096], "dist": "normal", "seed": 9225, "scale": 0.1 }
477
+ },
478
+ "outputs": { "Y": { "dtype": "float32", "shape": [8, 4096], "dist": "empty" } },
479
+ "bench": { "metrics": [{ "type": "gflops", "value": 536870912 }] }
480
+ },
481
+ {
482
+ "name": "f32-band-preference-m16-k2048-n4096",
483
+ "preset": "model",
484
+ "attrs": { "alpha": 0.5 },
485
+ "inputs": {
486
+ "A": { "dtype": "float32", "shape": [16, 2048], "dist": "normal", "seed": 9230, "scale": 0.1 },
487
+ "B": { "dtype": "float32", "shape": [2048, 4096], "dist": "normal", "seed": 9231, "scale": 0.1 }
488
+ },
489
+ "outputs": { "Y": { "dtype": "float32", "shape": [16, 4096], "dist": "empty" } },
490
+ "bench": { "metrics": [{ "type": "gflops", "value": 268435456 }] }
491
+ },
492
+ {
493
+ "name": "f32-band-preference-m16-k8192-n4096",
494
+ "preset": "model",
495
+ "attrs": { "alpha": 0.5 },
496
+ "inputs": {
497
+ "A": { "dtype": "float32", "shape": [16, 8192], "dist": "normal", "seed": 9232, "scale": 0.1 },
498
+ "B": { "dtype": "float32", "shape": [8192, 4096], "dist": "normal", "seed": 9233, "scale": 0.1 }
499
+ },
500
+ "outputs": { "Y": { "dtype": "float32", "shape": [16, 4096], "dist": "empty" } },
501
+ "bench": { "metrics": [{ "type": "gflops", "value": 1073741824 }] }
502
  }
503
  ]
504
  }
build/webgpu/fused-matmul-subgroup-matrix.wgsl.jinja CHANGED
@@ -1,12 +1,9 @@
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;
@@ -14,6 +11,7 @@ enable subgroup_size_control;
14
  enable chromium_experimental_subgroup_matrix;
15
  diagnostic(off, chromium.subgroup_matrix_uniformity);
16
 
 
17
  {{ env.wgsl.resourceDeclarations }}
18
 
19
  {% set operandScalar = fScalar %}
@@ -34,7 +32,7 @@ const A_M_STRIDE: u32 = K;
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;
@@ -127,15 +125,16 @@ fn main(
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>, row_major>(&tile_A, matrix_a_offset, TILE_K);
132
- var matA1: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset + 8u * TILE_K, 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>, col_major>(&tile_B, matrix_b_offset, TILE_K);
136
- var matB1: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 8u * TILE_K, TILE_K);
137
- var matB2: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 16u * TILE_K, TILE_K);
138
- var matB3: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 24u * TILE_K, TILE_K);
139
 
140
  matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
141
  matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
 
1
+ // com.microsoft.FusedMatMul subgroup-matrix specialization: Y = alpha * op(A) @ op(B).
2
+ // transA and transB transpose the corresponding matrix operand on load.
3
+ // Dense batches map through workgroup_id.z, and M-tail rows are guarded by row_limit.
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
  enable subgroups;
8
  {% if pinSubgroupSize32 %}
9
  enable subgroup_size_control;
 
11
  enable chromium_experimental_subgroup_matrix;
12
  diagnostic(off, chromium.subgroup_matrix_uniformity);
13
 
14
+
15
  {{ env.wgsl.resourceDeclarations }}
16
 
17
  {% set operandScalar = fScalar %}
 
32
  {% endif %}
33
  const B_BATCH_STRIDE: u32 = K * N;
34
  const C_BATCH_STRIDE: u32 = M * N;
35
+ const ALPHA: {{ accScalar }} = {{ accScalar }}({{ alpha }});
36
  const TILE_COLS: u32 = 64u;
37
  const TILE_ROWS: u32 = 32u;
38
  const TILE_K: u32 = 32u;
 
125
  workgroupBarrier();
126
 
127
  for (var step = 0u; step < TILE_K; step = step + 8u) {
128
+ {% set directInputs = directMatrixInputs is defined and directMatrixInputs %}
129
  let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
130
+ {% for r in range(2) %}
131
+ var matA{{ r }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&{{ "w" if directInputs else "tile_A" }}, matrix_a_offset{% if r > 0 %} + 8u * {{ "K" if directInputs else "TILE_K" }}{% endif %}, {{ "K" if directInputs else "TILE_K" }});
132
+ {% endfor %}
133
 
134
  let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
135
+ {% for c in range(4) %}
136
+ var matB{{ c }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&{{ "xm" if directInputs else "tile_B" }}, matrix_b_offset{% if c > 0 %} + {{ c * 8 }}u * TILE_K{% endif %}, {{ "N" if directInputs else "TILE_K" }});
137
+ {% endfor %}
 
138
 
139
  matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
140
  matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
build/webgpu/manifest.json CHANGED
@@ -2,91 +2,43 @@
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": {
34
- "TILED_REG_MIN_WORKGROUPS": 64,
35
- "GEMV_TARGET_BLOCKS": 512,
36
- "SUBGROUP_MATRIX_MIN_M": 2,
37
- "SUBGROUP_MATRIX_SPLITK_TARGET_WGS": 512,
38
- "SUBGROUP_MATRIX_SPLITK_MIN_K": 1024,
39
- "SUBGROUP_MATRIX_SPLITK_MAX_TILES": 128
40
- },
41
- "bindingSets": {
42
- "matrix": [
43
- { "name": "a", "arg": "A", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
44
- { "name": "b", "arg": "B", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
45
- { "name": "y", "arg": "Y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" }
46
- ],
47
- "sgmatSplitPartial": [
48
- { "name": "a", "arg": "A", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
49
- { "name": "b", "arg": "B", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "$scalar" },
50
- { "name": "partials", "semantic": "partials", "buffer": { "type": "storage" }, "elementType": "f32" }
51
- ],
52
- "sgmatSplitCombine": [
53
- { "name": "partials", "semantic": "partials", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
54
- { "name": "y", "arg": "Y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$scalar" },
55
- {
56
- "name": "params",
57
- "semantic": "kernel.params",
58
- "buffer": { "type": "uniform" },
59
- "struct": { "name": "Params", "fields": [{ "name": "cols", "type": "u32", "value": "numel(shapes.Y)" }] }
60
- }
61
- ],
62
- "gemvF32": [
63
- { "name": "a", "arg": "A", "semantic": "A", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
64
- {
65
- "name": "b",
66
- "arg": "B",
67
- "semantic": "B",
68
- "buffer": { "type": "read-only-storage" },
69
- "elementType": "vec4<f32>"
70
- },
71
- { "name": "c", "arg": "Y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "vec4<f32>" },
72
- {
73
- "name": "params",
74
- "semantic": "kernel.params",
75
- "buffer": { "type": "uniform" },
76
- "struct": {
77
- "name": "Params",
78
- "fields": [
79
- { "name": "K", "type": "u32", "value": "dim(shapes.A, 1)" },
80
- { "name": "N4", "type": "u32", "value": "dim(shapes.B, 1) / 4" }
81
- ]
82
- }
83
- }
84
- ]
85
  },
86
  "derive": {
87
  "gemvWorkgroups": "ceilDiv(dim(shapes.B, 1), 128)",
88
  "gemvSliceCap": "min(32, device.limits.maxComputeWorkgroupSizeY, floor(device.limits.maxComputeInvocationsPerWorkgroup / 32), floor(device.limits.maxComputeWorkgroupStorageSize / 512))",
89
- "gemvSlices": "max(1, min(gemvSliceCap, max(8, pow2ceil(ceilDiv(tunables.GEMV_TARGET_BLOCKS, gemvWorkgroups)))))",
90
  "batchMovedAShape": "moveAxis(shapes.A, 0, -2) if attrs.transBatchA != 0 else shapes.A",
91
  "batchMovedBShape": "moveAxis(shapes.B, 0, -2) if attrs.transBatchB != 0 else shapes.B",
92
  "logicalAShape": "moveAxis(batchMovedAShape, -1, -2) if attrs.transA != 0 and ranks.A > 1 else batchMovedAShape",
@@ -107,28 +59,224 @@
107
  "sgmatSplitK8Ok": "sgmatSplitKDepth % 256 == 0",
108
  "sgmatSplitK4Ok": "sgmatSplitKDepth % 128 == 0",
109
  "sgmatSplitK2Ok": "sgmatSplitKDepth % 64 == 0",
110
- "sgmatSplitK": "32 if (sgmatSplitKWant > 16 and sgmatSplitK32Ok) else (16 if (sgmatSplitKWant > 8 and sgmatSplitK16Ok) else (8 if (sgmatSplitKWant > 4 and sgmatSplitK8Ok) else (4 if (sgmatSplitKWant > 2 and sgmatSplitK4Ok) else (2 if sgmatSplitK2Ok else 1))))"
 
 
 
 
 
 
 
 
 
 
 
111
  },
112
  "variants": [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  {
114
  "id": "f32_m1_gemv_vec4",
115
  "priority": 30,
116
- "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"],
117
- "constants": { "T": "\"f32\"", "usesF16": false, "gemvSlices": "gemvSlices" },
118
  "passes": [
119
  {
120
  "id": "main",
121
  "name": "FusedMatMul.F32M1GemvVec4",
122
  "shader": "matmul-vector-matrix-vec4.wgsl.jinja",
123
- "bindings": "gemvF32",
 
 
 
 
 
 
 
 
 
 
 
124
  "dispatch": { "x": "ceil(dim(shapes.B, 1) / 128)" }
125
  }
126
  ]
127
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  {
129
  "id": "subgroup_matrix_splitk",
130
- "description": "Partitions the K reduction across workgroups while retaining subgroup-matrix tiles, then combines float32 partials that already carry alpha. It serves small-M rank-two products whose unsplit tile grids cannot occupy the device.",
131
  "priority": 12,
 
132
  "requires": {
133
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
134
  "subgroupMatrixConfigs": [
@@ -136,8 +284,7 @@
136
  { "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
137
  ]
138
  },
139
- "when": ["(dtypes.T == \"f16\" or dtypes.T == \"f32\") and f16Ok(dtypes.T)", "fusedSgmatRank2Ok", "dim(shapes.A, 0) >= tunables.SUBGROUP_MATRIX_MIN_M", "dim(shapes.A, 1) >= tunables.SUBGROUP_MATRIX_SPLITK_MIN_K", "dim(shapes.B, 1) % 64 == 0", "sgmatSplitK >= 2", "sgmatOutTiles < tunables.SUBGROUP_MATRIX_SPLITK_MAX_TILES", "sgmatSplitK * numel(shapes.Y) * 4 <= device.limits.maxStorageBufferBindingSize", "sgmatSplitK <= device.limits.maxComputeWorkgroupsPerDimension", "ceilDiv(dim(shapes.Y, 1), 64) <= device.limits.maxComputeWorkgroupsPerDimension", "ceilDiv(dim(shapes.Y, 0), 32) <= device.limits.maxComputeWorkgroupsPerDimension", "subgroupMatrixResourcesFit", "wave32Effective"],
140
- "constants": {
141
  "usesF16": "dtypes.T == \"f16\"",
142
  "fScalar": "\"f16\" if dtypes.T == \"f16\" else \"f32\"",
143
  "scalar": "dtypes.T",
@@ -161,39 +308,39 @@
161
  {
162
  "id": "partial",
163
  "name": "FusedMatMul.SubgroupMatrixSplitK",
164
- "source": {
165
- "shader": "matmul-subgroup-matrix-ext.wgsl.jinja",
166
- "inputs": {
167
- "aShape": ["dim(shapes.A, 0)", "dim(shapes.A, 1)"],
168
- "bShape": ["dim(shapes.B, 0)", "dim(shapes.B, 1)"],
169
- "aRank": 2,
170
- "bRank": 2
171
- }
172
  },
173
- "bindings": "sgmatSplitPartial",
174
  "dispatch": { "x": "ceilDiv(dim(shapes.Y, 1), 64)", "y": "ceilDiv(dim(shapes.Y, 0), 32)", "z": "sgmatSplitK" }
175
  },
176
  {
177
  "id": "combine",
178
  "name": "FusedMatMul.SubgroupMatrixSplitKCombine",
179
- "source": {
180
- "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
181
- "inputs": { "op": "\"sum\"", "outputF16": "dtypes.T == \"f16\"", "intMode": false }
182
- },
183
- "bindings": "sgmatSplitCombine",
184
- "dispatch": { "threads": "numel(shapes.Y)", "workgroupSize": 256 }
 
 
185
  }
186
  ]
187
  },
188
  {
189
  "id": "subgroup_matrix_tail_broadcast",
190
  "priority": 11,
 
191
  "requires": {
192
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
193
  "subgroupMatrixConfigs": [{ "componentType": "f16", "M": 8, "N": 8, "K": 8 }]
194
  },
195
- "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) >= tunables.SUBGROUP_MATRIX_MIN_M", "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"],
196
- "constants": {
197
  "hasBias": false,
198
  "usesF16": true,
199
  "fScalar": "\"f16\"",
@@ -212,18 +359,17 @@
212
  {
213
  "id": "main",
214
  "name": "FusedMatMul.SubgroupMatrixTailBroadcast",
215
- "source": {
216
- "shader": "matmul-subgroup-matrix-ext.wgsl.jinja",
217
- "inputs": { "aShape": "shapes.A", "bShape": "shapes.B", "aRank": "ranks.A", "bRank": "ranks.B" }
218
- },
219
- "bindings": "matrix",
220
- "dispatch": { "x": "ceil(constants.N / 64)", "y": "ceil(constants.M / 32)", "z": "constants.batchCount" }
221
  }
222
  ]
223
  },
224
  {
225
  "id": "subgroup_matrix",
226
  "priority": 10,
 
227
  "requires": {
228
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
229
  "subgroupMatrixConfigs": [
@@ -231,8 +377,7 @@
231
  { "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
232
  ]
233
  },
234
- "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))) >= tunables.SUBGROUP_MATRIX_MIN_M", "(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"],
235
- "constants": {
236
  "usesF16": "dtypes.T == \"f16\"",
237
  "fScalar": "\"f16\" if dtypes.T == \"f16\" else \"f32\"",
238
  "outScalar": "\"f16\" if dtypes.T == \"f16\" else \"f32\"",
@@ -249,37 +394,32 @@
249
  {
250
  "id": "main",
251
  "name": "FusedMatMul.SubgroupMatrix",
252
- "source": { "shader": "fused-matmul-subgroup-matrix.wgsl.jinja", "inputs": { "alpha": "attrs.alpha" } },
253
- "bindings": "matrix",
254
- "dispatch": {
255
- "x": "ceil(constants.N / 64)",
256
- "y": "ceil(constants.M / 32)",
257
- "z": "numel(shapes.Y) / (constants.M * constants.N)"
258
- }
259
  }
260
  ]
261
  },
262
  {
263
  "id": "broadcast_rank4_tiled_reg",
264
  "priority": 6,
265
- "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"],
266
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
267
  "passes": [
268
  {
269
  "id": "main",
270
  "name": "FusedMatMul.BroadcastRank4TiledReg",
271
- "source": {
272
- "shader": "matmul-tiled-general-reg.wgsl.jinja",
273
- "inputs": {
274
- "aShape": "shapes.A",
275
- "bShape": "shapes.B",
276
- "alpha": "attrs.alpha",
277
- "aRank": "ranks.A",
278
- "bRank": "ranks.B",
279
- "transBatchA": "false"
280
- }
281
  },
282
- "bindings": "matrix",
283
  "dispatch": {
284
  "x": "ceil(dim(shapes.B, ranks.B - 1) / 64)",
285
  "y": "ceil(dim(shapes.A, 2) / 64)",
@@ -290,26 +430,23 @@
290
  },
291
  {
292
  "id": "plain_rank2_tiled_reg",
293
- "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.",
294
  "priority": 4,
295
- "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"],
296
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
297
  "passes": [
298
  {
299
  "id": "main",
300
  "name": "FusedMatMul.PlainRank2TiledReg",
301
- "source": {
302
- "shader": "matmul-tiled-general-reg.wgsl.jinja",
303
- "inputs": {
304
- "aShape": "shapes.A",
305
- "bShape": "shapes.B",
306
- "alpha": "attrs.alpha",
307
- "aRank": "ranks.A",
308
- "bRank": "ranks.B",
309
- "transBatchA": "false"
310
- }
311
  },
312
- "bindings": "matrix",
313
  "dispatch": { "x": "ceil(dim(shapes.B, 1) / 64)", "y": "ceil(dim(shapes.A, 0) / 64)", "z": 1 }
314
  }
315
  ]
@@ -317,25 +454,23 @@
317
  {
318
  "id": "transbatch_a_tiled_reg",
319
  "priority": 5,
320
- "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"],
321
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
322
  "passes": [
323
  {
324
  "id": "main",
325
  "name": "FusedMatMul.TransBatchATiledReg",
326
- "source": {
327
- "shader": "matmul-tiled-general-reg.wgsl.jinja",
328
- "inputs": {
329
- "aShape": "shapes.A",
330
- "bShape": "shapes.B",
331
- "alpha": "attrs.alpha",
332
- "aRank": "ranks.A",
333
- "bRank": "ranks.B",
334
- "transBatchA": "true",
335
- "kTile": "4"
336
- }
337
  },
338
- "bindings": "matrix",
339
  "dispatch": {
340
  "x": "ceil(dim(shapes.B, 2) / 64)",
341
  "y": "ceil(dim(shapes.A, 0) / 64)",
@@ -347,27 +482,25 @@
347
  {
348
  "id": "tiled",
349
  "priority": 0,
350
- "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"],
351
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
352
  "passes": [
353
  {
354
  "id": "main",
355
  "name": "FusedMatMul.Tiled",
356
- "source": {
357
- "shader": "matmul-tiled-general.wgsl.jinja",
358
- "inputs": {
359
- "aShape": "shapes.A",
360
- "bShape": "shapes.B",
361
- "transA": "attrs.transA != 0",
362
- "transB": "attrs.transB != 0",
363
- "alpha": "attrs.alpha",
364
- "aRank": "ranks.A",
365
- "bRank": "ranks.B",
366
- "transBatchA": "attrs.transBatchA != 0",
367
- "transBatchB": "attrs.transBatchB != 0"
368
- }
369
  },
370
- "bindings": "matrix",
371
  "dispatch": {
372
  "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)",
373
  "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)",
@@ -375,6 +508,35 @@
375
  }
376
  }
377
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
  }
379
  ]
380
  }
 
2
  "domain": "com.microsoft",
3
  "name": "FusedMatMul",
4
  "sinceVersion": 1,
5
+ "inputs": { "A": { "dtype": "T" }, "B": { "dtype": "T" } },
6
+ "outputs": {
7
+ "Y": {
 
 
 
 
 
8
  "dtype": "T",
9
  "rank": "max(ranks.A, ranks.B) - (1 if ranks.A == 1 or ranks.B == 1 else 0)",
10
+ "shape": "matmulShape(logicalAShape, logicalBShape)"
 
11
  }
 
 
 
 
 
 
 
 
12
  },
13
+ "attributes": {
14
+ "alpha": { "default": 1 },
15
+ "transA": { "default": 0 },
16
+ "transB": { "default": 0 },
17
+ "transBatchA": { "default": 0 },
18
+ "transBatchB": { "default": 0 }
19
  },
20
+ "typeConstraints": { "T": ["float32", "float16"] },
21
  "tunables": {
22
+ "TILED_REG_MIN_WORKGROUPS": { "default": 64 },
23
+ "GEMV_TARGET_BLOCKS": { "default": 512 },
24
+ "SUBGROUP_MATRIX_MIN_M": { "default": 2 },
25
+ "SUBGROUP_MATRIX_SPLITK_TARGET_WGS": { "default": 512 },
26
+ "SUBGROUP_MATRIX_SPLITK_MIN_K": { "default": 1024 },
27
+ "SUBGROUP_MATRIX_SPLITK_MAX_TILES": { "default": 128 },
28
+ "BAND_VEC4_MAX_ROWS": { "default": 16 },
29
+ "BAND_SPLIT_TARGET_WORKGROUPS": { "default": 256 },
30
+ "BAND_SPLIT_MAX_COLUMN_GROUPS": { "default": 24 },
31
+ "BAND_SPLIT_SLICES": { "default": 8 },
32
+ "TRANSBATCH_B_SUBGROUP_MATRIX_MIN_K": { "default": 128 },
33
+ "TRANSBATCH_B_SUBGROUP_MATRIX_MIN_WORKGROUPS": { "default": 64 },
34
+ "TRANSBATCH_REG_MIN_K": { "default": 128 },
35
+ "BAND_PREFER_MAX_ROWS": { "default": 8 },
36
+ "BAND_PREFER_DEEP_K": { "default": 4096 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  },
38
  "derive": {
39
  "gemvWorkgroups": "ceilDiv(dim(shapes.B, 1), 128)",
40
  "gemvSliceCap": "min(32, device.limits.maxComputeWorkgroupSizeY, floor(device.limits.maxComputeInvocationsPerWorkgroup / 32), floor(device.limits.maxComputeWorkgroupStorageSize / 512))",
41
+ "gemvSlicesPlan": "max(1, min(gemvSliceCap, max(8, pow2ceil(ceilDiv(tunables.GEMV_TARGET_BLOCKS, gemvWorkgroups)))))",
42
  "batchMovedAShape": "moveAxis(shapes.A, 0, -2) if attrs.transBatchA != 0 else shapes.A",
43
  "batchMovedBShape": "moveAxis(shapes.B, 0, -2) if attrs.transBatchB != 0 else shapes.B",
44
  "logicalAShape": "moveAxis(batchMovedAShape, -1, -2) if attrs.transA != 0 and ranks.A > 1 else batchMovedAShape",
 
59
  "sgmatSplitK8Ok": "sgmatSplitKDepth % 256 == 0",
60
  "sgmatSplitK4Ok": "sgmatSplitKDepth % 128 == 0",
61
  "sgmatSplitK2Ok": "sgmatSplitKDepth % 64 == 0",
62
+ "sgmatSplitK": "32 if (sgmatSplitKWant > 16 and sgmatSplitK32Ok) else (16 if (sgmatSplitKWant > 8 and sgmatSplitK16Ok) else (8 if (sgmatSplitKWant > 4 and sgmatSplitK8Ok) else (4 if (sgmatSplitKWant > 2 and sgmatSplitK4Ok) else (2 if sgmatSplitK2Ok else 1))))",
63
+ "bandRank2Ok": "ranks.A == 2 and ranks.B == 2 and ranks.Y == 2 and attrs.transA == 0 and attrs.transB == 0 and attrs.transBatchA == 0 and attrs.transBatchB == 0 and dim(shapes.A, 1) == dim(shapes.B, 0) and dim(shapes.Y, 0) == dim(shapes.A, 0) and dim(shapes.Y, 1) == dim(shapes.B, 1)",
64
+ "bandSplitWant": "pow2ceil(ceilDiv(tunables.BAND_SPLIT_TARGET_WORKGROUPS, max(1, gemvWorkgroups)))",
65
+ "bandSplitK": "16 if (bandSplitWant >= 16 and dim(shapes.A, ranks.A - 1) >= 4096) else (8 if (bandSplitWant >= 8 and dim(shapes.A, ranks.A - 1) >= 2048) else (4 if (bandSplitWant >= 4 and dim(shapes.A, ranks.A - 1) >= 1024) else (2 if (bandSplitWant >= 2 and dim(shapes.A, ranks.A - 1) >= 512) else 1)))"
66
+ },
67
+ "bindings": {
68
+ "a": { "arg": "A", "buffer": "read-only-storage", "elementType": "$scalar" },
69
+ "b": { "arg": "B", "buffer": "read-only-storage", "elementType": "$vectorScalar" },
70
+ "partials": { "buffer": "read-only-storage", "elementType": "f32" },
71
+ "y": { "arg": "Y", "buffer": "storage", "elementType": "$scalar" },
72
+ "params": { "buffer": "uniform", "struct": [{ "name": "cols", "type": "u32", "value": "numel(shapes.Y)" }] },
73
+ "b_3": { "arg": "B", "name": "b", "buffer": "read-only-storage", "elementType": "$scalar" }
74
  },
75
  "variants": [
76
+ {
77
+ "id": "subgroup_matrix_transbatch_b_f16",
78
+ "priority": 11,
79
+ "when": ["dtypes.T == \"f16\" and f16Ok(dtypes.T)", "ranks.A == 3 and ranks.B == 3 and ranks.Y == 3", "attrs.transA == 0 and attrs.transB == 0 and attrs.transBatchA == 0 and attrs.transBatchB != 0", "dim(shapes.A, 0) == dim(shapes.B, 1) and dim(shapes.Y, 0) == dim(shapes.A, 0)", "dim(shapes.A, 2) == dim(shapes.B, 0)", "dim(shapes.Y, 1) == dim(shapes.A, 1) and dim(shapes.Y, 2) == dim(shapes.B, 2)", "dim(shapes.A, 0) > 0", "dim(shapes.A, 1) >= tunables.SUBGROUP_MATRIX_MIN_M", "dim(shapes.A, 2) >= 32", "dim(shapes.B, 2) >= 64", "ceilDiv(dim(shapes.B, 2), 64) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "ceilDiv(dim(shapes.A, 1), 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "dim(shapes.A, 0) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "subgroupMatrixResourcesFit", "wave32Effective", "dim(shapes.A, 1) % 32 == 0", "dim(shapes.A, 2) % 32 == 0", "dim(shapes.B, 2) % 64 == 0", "dim(shapes.A, 2) >= tunables.TRANSBATCH_B_SUBGROUP_MATRIX_MIN_K", "dim(shapes.A, 0) * ceilDiv(dim(shapes.A, 1), 32) * ceilDiv(dim(shapes.B, 2), 64) >= tunables.TRANSBATCH_B_SUBGROUP_MATRIX_MIN_WORKGROUPS"],
80
+ "requires": {
81
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
82
+ "subgroupMatrixConfigs": [{ "componentType": "f16", "M": 8, "N": 8, "K": 8 }]
83
+ },
84
+ "derive": {
85
+ "hasBias": false,
86
+ "usesF16": "dtypes.T == \"f16\"",
87
+ "fScalar": "dtypes.T",
88
+ "outScalar": "dtypes.T",
89
+ "scalar": "dtypes.T",
90
+ "generalAddressing": true,
91
+ "outputBuffer": "\"y\"",
92
+ "alpha": "attrs.alpha",
93
+ "M": "dim(shapes.A, ranks.A - 2)",
94
+ "K": "dim(shapes.A, ranks.A - 1)",
95
+ "N": "dim(shapes.B, ranks.B - 1)",
96
+ "batchCount": "numel(shapes.Y) / (dim(shapes.A, ranks.A - 2) * dim(shapes.B, ranks.B - 1))"
97
+ },
98
+ "passes": [
99
+ {
100
+ "id": "main",
101
+ "name": "FusedMatMul.SubgroupMatrixTransBatchB",
102
+ "shader": "matmul-subgroup-matrix-ext.wgsl.jinja",
103
+ "derive": {
104
+ "aShape": "shapes.A",
105
+ "bShape": "logicalBShape",
106
+ "aRank": "ranks.A",
107
+ "bRank": "ranks.B",
108
+ "bStorageStrides": "[dim(shapes.B, 2), dim(shapes.B, 1) * dim(shapes.B, 2), 1]"
109
+ },
110
+ "bindings": ["a", "b_3", "y"],
111
+ "dispatch": { "x": "ceil(N / 64)", "y": "ceil(M / 32)", "z": "batchCount" }
112
+ }
113
+ ]
114
+ },
115
+ {
116
+ "id": "subgroup_matrix_transbatch_b_f32",
117
+ "priority": 11,
118
+ "when": ["dtypes.T == \"f32\" and f16Ok(dtypes.T)", "ranks.A == 3 and ranks.B == 3 and ranks.Y == 3", "attrs.transA == 0 and attrs.transB == 0 and attrs.transBatchA == 0 and attrs.transBatchB != 0", "dim(shapes.A, 0) == dim(shapes.B, 1) and dim(shapes.Y, 0) == dim(shapes.A, 0)", "dim(shapes.A, 2) == dim(shapes.B, 0)", "dim(shapes.Y, 1) == dim(shapes.A, 1) and dim(shapes.Y, 2) == dim(shapes.B, 2)", "dim(shapes.A, 0) > 0", "dim(shapes.A, 1) >= tunables.SUBGROUP_MATRIX_MIN_M", "dim(shapes.A, 2) >= 32", "dim(shapes.B, 2) >= 64", "ceilDiv(dim(shapes.B, 2), 64) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "ceilDiv(dim(shapes.A, 1), 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "dim(shapes.A, 0) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "subgroupMatrixResourcesFit", "wave32Effective", "dim(shapes.A, 1) % 32 == 0", "dim(shapes.A, 2) % 32 == 0", "dim(shapes.B, 2) % 64 == 0", "dim(shapes.A, 2) >= tunables.TRANSBATCH_B_SUBGROUP_MATRIX_MIN_K", "dim(shapes.A, 0) * ceilDiv(dim(shapes.A, 1), 32) * ceilDiv(dim(shapes.B, 2), 64) >= tunables.TRANSBATCH_B_SUBGROUP_MATRIX_MIN_WORKGROUPS"],
119
+ "requires": {
120
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
121
+ "subgroupMatrixConfigs": [{ "componentType": "f32", "M": 8, "N": 8, "K": 8, "resultComponentType": "f32" }]
122
+ },
123
+ "derive": {
124
+ "hasBias": false,
125
+ "usesF16": "dtypes.T == \"f16\"",
126
+ "fScalar": "dtypes.T",
127
+ "outScalar": "dtypes.T",
128
+ "scalar": "dtypes.T",
129
+ "generalAddressing": true,
130
+ "outputBuffer": "\"y\"",
131
+ "alpha": "attrs.alpha",
132
+ "M": "dim(shapes.A, ranks.A - 2)",
133
+ "K": "dim(shapes.A, ranks.A - 1)",
134
+ "N": "dim(shapes.B, ranks.B - 1)",
135
+ "batchCount": "numel(shapes.Y) / (dim(shapes.A, ranks.A - 2) * dim(shapes.B, ranks.B - 1))"
136
+ },
137
+ "passes": [
138
+ {
139
+ "id": "main",
140
+ "name": "FusedMatMul.SubgroupMatrixTransBatchB",
141
+ "shader": "matmul-subgroup-matrix-ext.wgsl.jinja",
142
+ "derive": {
143
+ "aShape": "shapes.A",
144
+ "bShape": "logicalBShape",
145
+ "aRank": "ranks.A",
146
+ "bRank": "ranks.B",
147
+ "bStorageStrides": "[dim(shapes.B, 2), dim(shapes.B, 1) * dim(shapes.B, 2), 1]"
148
+ },
149
+ "bindings": ["a", "b_3", "y"],
150
+ "dispatch": { "x": "ceil(N / 64)", "y": "ceil(M / 32)", "z": "batchCount" }
151
+ }
152
+ ]
153
+ },
154
  {
155
  "id": "f32_m1_gemv_vec4",
156
  "priority": 30,
157
+ "when": ["dtypes.T == \"f32\"", "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) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"],
158
+ "derive": { "usesF16": false, "gemvSlices": "gemvSlicesPlan", "alphaScale": "attrs.alpha" },
159
  "passes": [
160
  {
161
  "id": "main",
162
  "name": "FusedMatMul.F32M1GemvVec4",
163
  "shader": "matmul-vector-matrix-vec4.wgsl.jinja",
164
+ "bindings": [
165
+ { "arg": "A", "name": "a", "elementType": "f32" },
166
+ { "arg": "B", "name": "b", "elementType": "vec4<f32>" },
167
+ { "arg": "Y", "name": "c", "elementType": "vec4<f32>" },
168
+ {
169
+ "name": "params",
170
+ "struct": [
171
+ { "name": "K", "type": "u32", "value": "dim(shapes.A, 1)" },
172
+ { "name": "N4", "type": "u32", "value": "dim(shapes.B, 1) / 4" }
173
+ ]
174
+ }
175
+ ],
176
  "dispatch": { "x": "ceil(dim(shapes.B, 1) / 128)" }
177
  }
178
  ]
179
  },
180
+ {
181
+ "id": "rank2_band_vec4_splitk",
182
+ "priority": 11,
183
+ "when": ["(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T)", "bandRank2Ok", "dim(shapes.A, 0) >= 2", "dim(shapes.A, 0) <= tunables.BAND_VEC4_MAX_ROWS", "dim(shapes.A, 1) > 0", "dim(shapes.B, 1) > 0", "dim(shapes.B, 1) % 4 == 0", "gemvWorkgroups <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "32 <= device.limits.maxComputeWorkgroupSizeX", "gemvWorkgroups <= tunables.BAND_SPLIT_MAX_COLUMN_GROUPS", "bandSplitK >= 2", "bandSplitK * numel(shapes.Y) * 4 <= device.limits.maxStorageBufferBindingSize", "bandSplitK <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "tunables.BAND_SPLIT_SLICES <= device.limits.maxComputeWorkgroupSizeY", "32 * tunables.BAND_SPLIT_SLICES <= device.limits.maxComputeInvocationsPerWorkgroup"],
184
+ "derive": {
185
+ "usesF16": "dtypes.T == \"f16\"",
186
+ "scalar": "dtypes.T",
187
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
188
+ "batched": false,
189
+ "outputBuffer": "\"y\"",
190
+ "alpha": "attrs.alpha",
191
+ "M": "dim(shapes.A, 0)",
192
+ "K": "dim(shapes.A, 1)",
193
+ "N": "dim(shapes.B, 1)",
194
+ "gemvSlices": "tunables.BAND_SPLIT_SLICES",
195
+ "kSplits": "bandSplitK",
196
+ "split": "bandSplitK",
197
+ "workgroupSize": 256
198
+ },
199
+ "intermediates": [{ "id": "partials", "dtype": "float32", "shape": "[bandSplitK * numel(shapes.Y)]" }],
200
+ "passes": [
201
+ {
202
+ "id": "partial",
203
+ "name": "FusedMatMul.Rank2BandVec4SplitK",
204
+ "shader": "matmul-band-vec4.wgsl.jinja",
205
+ "bindings": ["a", "b", { "scratch": "partials", "name": "y", "elementType": "vec4<f32>" }],
206
+ "dispatch": { "x": "gemvWorkgroups", "y": "bandSplitK" }
207
+ },
208
+ {
209
+ "id": "combine",
210
+ "name": "FusedMatMul.Rank2BandVec4SplitKCombine",
211
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
212
+ "derive": { "op": "\"sum\"", "outputF16": "dtypes.T == \"f16\"", "intMode": false },
213
+ "bindings": ["partials", "y", "params"],
214
+ "dispatch": {
215
+ "x": "min(ceilDiv((numel(shapes.Y)), (256)), 65535)",
216
+ "y": "ceilDiv(ceilDiv((numel(shapes.Y)), (256)), 65535)",
217
+ "z": 1
218
+ }
219
+ }
220
+ ]
221
+ },
222
+ {
223
+ "id": "rank2_band_vec4",
224
+ "priority": 11,
225
+ "when": ["(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T)", "bandRank2Ok", "dim(shapes.A, 0) >= 2", "dim(shapes.A, 0) <= tunables.BAND_VEC4_MAX_ROWS", "dim(shapes.A, 1) > 0", "dim(shapes.B, 1) > 0", "dim(shapes.B, 1) % 4 == 0", "gemvWorkgroups <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "32 <= device.limits.maxComputeWorkgroupSizeX", "gemvSlicesPlan <= device.limits.maxComputeWorkgroupSizeY", "32 * gemvSlicesPlan <= device.limits.maxComputeInvocationsPerWorkgroup", "32 * gemvSlicesPlan * 16 <= device.limits.maxComputeWorkgroupStorageSize", "not (gemvWorkgroups <= tunables.BAND_SPLIT_MAX_COLUMN_GROUPS and bandSplitK >= 2)"],
226
+ "derive": {
227
+ "usesF16": "dtypes.T == \"f16\"",
228
+ "scalar": "dtypes.T",
229
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
230
+ "batched": false,
231
+ "outputBuffer": "\"y\"",
232
+ "alpha": "attrs.alpha",
233
+ "M": "dim(shapes.A, 0)",
234
+ "K": "dim(shapes.A, 1)",
235
+ "N": "dim(shapes.B, 1)",
236
+ "gemvSlices": "gemvSlicesPlan"
237
+ },
238
+ "passes": [
239
+ {
240
+ "id": "main",
241
+ "name": "FusedMatMul.Rank2BandVec4",
242
+ "shader": "matmul-band-vec4.wgsl.jinja",
243
+ "bindings": ["a", "b", { "arg": "Y", "name": "y", "elementType": "$vectorScalar" }],
244
+ "dispatch": { "x": "gemvWorkgroups" }
245
+ }
246
+ ],
247
+ "demoteWhen": ["false"]
248
+ },
249
+ {
250
+ "id": "rank2_band_vec4_f32_preferred",
251
+ "priority": 13,
252
+ "when": ["(dtypes.T == \"f32\" or dtypes.T == \"f16\") and f16Ok(dtypes.T)", "bandRank2Ok", "dim(shapes.A, 0) >= 2", "dim(shapes.A, 0) <= tunables.BAND_VEC4_MAX_ROWS", "dim(shapes.A, 1) > 0", "dim(shapes.B, 1) > 0", "dim(shapes.B, 1) % 4 == 0", "gemvWorkgroups <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "32 <= device.limits.maxComputeWorkgroupSizeX", "gemvSlicesPlan <= device.limits.maxComputeWorkgroupSizeY", "32 * gemvSlicesPlan <= device.limits.maxComputeInvocationsPerWorkgroup", "32 * gemvSlicesPlan * 16 <= device.limits.maxComputeWorkgroupStorageSize", "not (gemvWorkgroups <= tunables.BAND_SPLIT_MAX_COLUMN_GROUPS and bandSplitK >= 2)"],
253
+ "derive": {
254
+ "usesF16": "dtypes.T == \"f16\"",
255
+ "scalar": "dtypes.T",
256
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
257
+ "batched": false,
258
+ "outputBuffer": "\"y\"",
259
+ "alpha": "attrs.alpha",
260
+ "M": "dim(shapes.A, 0)",
261
+ "K": "dim(shapes.A, 1)",
262
+ "N": "dim(shapes.B, 1)",
263
+ "gemvSlices": "gemvSlicesPlan"
264
+ },
265
+ "passes": [
266
+ {
267
+ "id": "main",
268
+ "name": "FusedMatMul.Rank2BandVec4",
269
+ "shader": "matmul-band-vec4.wgsl.jinja",
270
+ "bindings": ["a", "b", { "arg": "Y", "name": "y", "elementType": "$vectorScalar" }],
271
+ "dispatch": { "x": "gemvWorkgroups" }
272
+ }
273
+ ],
274
+ "demoteWhen": ["dtypes.T != \"f32\" or (dim(shapes.A, 0) > tunables.BAND_PREFER_MAX_ROWS and dim(shapes.A, 1) >= tunables.BAND_PREFER_DEEP_K)"]
275
+ },
276
  {
277
  "id": "subgroup_matrix_splitk",
 
278
  "priority": 12,
279
+ "when": ["(dtypes.T == \"f16\" or dtypes.T == \"f32\") and f16Ok(dtypes.T)", "fusedSgmatRank2Ok", "dim(shapes.A, 0) >= tunables.SUBGROUP_MATRIX_MIN_M", "dim(shapes.A, 1) >= tunables.SUBGROUP_MATRIX_SPLITK_MIN_K", "dim(shapes.B, 1) % 64 == 0", "sgmatSplitK >= 2", "sgmatOutTiles < tunables.SUBGROUP_MATRIX_SPLITK_MAX_TILES", "sgmatSplitK * numel(shapes.Y) * 4 <= device.limits.maxStorageBufferBindingSize", "sgmatSplitK <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "ceilDiv(dim(shapes.Y, 1), 64) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "ceilDiv(dim(shapes.Y, 0), 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "subgroupMatrixResourcesFit", "wave32Effective"],
280
  "requires": {
281
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
282
  "subgroupMatrixConfigs": [
 
284
  { "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
285
  ]
286
  },
287
+ "derive": {
 
288
  "usesF16": "dtypes.T == \"f16\"",
289
  "fScalar": "\"f16\" if dtypes.T == \"f16\" else \"f32\"",
290
  "scalar": "dtypes.T",
 
308
  {
309
  "id": "partial",
310
  "name": "FusedMatMul.SubgroupMatrixSplitK",
311
+ "shader": "matmul-subgroup-matrix-ext.wgsl.jinja",
312
+ "derive": {
313
+ "aShape": ["dim(shapes.A, 0)", "dim(shapes.A, 1)"],
314
+ "bShape": ["dim(shapes.B, 0)", "dim(shapes.B, 1)"],
315
+ "aRank": 2,
316
+ "bRank": 2
 
 
317
  },
318
+ "bindings": ["a", "b_3", { "name": "partials", "buffer": "storage", "elementType": "f32" }],
319
  "dispatch": { "x": "ceilDiv(dim(shapes.Y, 1), 64)", "y": "ceilDiv(dim(shapes.Y, 0), 32)", "z": "sgmatSplitK" }
320
  },
321
  {
322
  "id": "combine",
323
  "name": "FusedMatMul.SubgroupMatrixSplitKCombine",
324
+ "shader": "reduce-axis0-splitk-combine.wgsl.jinja",
325
+ "derive": { "op": "\"sum\"", "outputF16": "dtypes.T == \"f16\"", "intMode": false },
326
+ "bindings": ["partials", "y", "params"],
327
+ "dispatch": {
328
+ "x": "min(ceilDiv((numel(shapes.Y)), (256)), 65535)",
329
+ "y": "ceilDiv(ceilDiv((numel(shapes.Y)), (256)), 65535)",
330
+ "z": 1
331
+ }
332
  }
333
  ]
334
  },
335
  {
336
  "id": "subgroup_matrix_tail_broadcast",
337
  "priority": 11,
338
+ "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)))) or (ranks.A == 4 and ranks.B == 2 and ranks.Y == 4 and sameShape(prefix(shapes.Y, 2), prefix(shapes.A, 2))))", "dim(shapes.A, ranks.A - 1) == dim(shapes.B, ranks.B - 2)", "dim(shapes.A, ranks.A - 2) >= tunables.SUBGROUP_MATRIX_MIN_M", "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) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "ceil(dim(shapes.A, ranks.A - 2) / 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "numel(shapes.Y) / (dim(shapes.A, ranks.A - 2) * dim(shapes.B, ranks.B - 1)) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "wave32Effective"],
339
  "requires": {
340
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
341
  "subgroupMatrixConfigs": [{ "componentType": "f16", "M": 8, "N": 8, "K": 8 }]
342
  },
343
+ "derive": {
 
344
  "hasBias": false,
345
  "usesF16": true,
346
  "fScalar": "\"f16\"",
 
359
  {
360
  "id": "main",
361
  "name": "FusedMatMul.SubgroupMatrixTailBroadcast",
362
+ "shader": "matmul-subgroup-matrix-ext.wgsl.jinja",
363
+ "derive": { "aShape": "shapes.A", "bShape": "shapes.B", "aRank": "ranks.A", "bRank": "ranks.B" },
364
+ "bindings": ["a", "b_3", "y"],
365
+ "dispatch": { "x": "ceil(N / 64)", "y": "ceil(M / 32)", "z": "batchCount" }
 
 
366
  }
367
  ]
368
  },
369
  {
370
  "id": "subgroup_matrix",
371
  "priority": 10,
372
+ "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))) >= tunables.SUBGROUP_MATRIX_MIN_M", "(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) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "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) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "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))) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "wave32Effective"],
373
  "requires": {
374
  "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
375
  "subgroupMatrixConfigs": [
 
377
  { "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
378
  ]
379
  },
380
+ "derive": {
 
381
  "usesF16": "dtypes.T == \"f16\"",
382
  "fScalar": "\"f16\" if dtypes.T == \"f16\" else \"f32\"",
383
  "outScalar": "\"f16\" if dtypes.T == \"f16\" else \"f32\"",
 
394
  {
395
  "id": "main",
396
  "name": "FusedMatMul.SubgroupMatrix",
397
+ "shader": "fused-matmul-subgroup-matrix.wgsl.jinja",
398
+ "derive": { "alpha": "attrs.alpha" },
399
+ "bindings": ["a", "b_3", "y"],
400
+ "dispatch": { "x": "ceil(N / 64)", "y": "ceil(M / 32)", "z": "numel(shapes.Y) / (M * N)" }
 
 
 
401
  }
402
  ]
403
  },
404
  {
405
  "id": "broadcast_rank4_tiled_reg",
406
  "priority": 6,
407
+ "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) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "ceil(dim(shapes.A, 2) / 64) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "numel(shapes.Y) / (dim(shapes.A, 2) * dim(shapes.B, ranks.B - 1)) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"],
408
+ "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
409
  "passes": [
410
  {
411
  "id": "main",
412
  "name": "FusedMatMul.BroadcastRank4TiledReg",
413
+ "shader": "matmul-tiled-general-reg.wgsl.jinja",
414
+ "derive": {
415
+ "aShape": "shapes.A",
416
+ "bShape": "shapes.B",
417
+ "alpha": "attrs.alpha",
418
+ "aRank": "ranks.A",
419
+ "bRank": "ranks.B",
420
+ "transBatchA": "false"
 
 
421
  },
422
+ "bindings": ["a", "b_3", "y"],
423
  "dispatch": {
424
  "x": "ceil(dim(shapes.B, ranks.B - 1) / 64)",
425
  "y": "ceil(dim(shapes.A, 2) / 64)",
 
430
  },
431
  {
432
  "id": "plain_rank2_tiled_reg",
 
433
  "priority": 4,
434
+ "when": ["f16Ok(dtypes.T)", "fusedSgmatRank2Ok", "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) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "ceil(dim(shapes.A, 0) / 64) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"],
435
+ "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
436
  "passes": [
437
  {
438
  "id": "main",
439
  "name": "FusedMatMul.PlainRank2TiledReg",
440
+ "shader": "matmul-tiled-general-reg.wgsl.jinja",
441
+ "derive": {
442
+ "aShape": "shapes.A",
443
+ "bShape": "shapes.B",
444
+ "alpha": "attrs.alpha",
445
+ "aRank": "ranks.A",
446
+ "bRank": "ranks.B",
447
+ "transBatchA": "false"
 
 
448
  },
449
+ "bindings": ["a", "b_3", "y"],
450
  "dispatch": { "x": "ceil(dim(shapes.B, 1) / 64)", "y": "ceil(dim(shapes.A, 0) / 64)", "z": 1 }
451
  }
452
  ]
 
454
  {
455
  "id": "transbatch_a_tiled_reg",
456
  "priority": 5,
457
+ "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) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "ceil(dim(shapes.A, 0) / 64) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "dim(shapes.Y, 0) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"],
458
+ "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
459
  "passes": [
460
  {
461
  "id": "main",
462
  "name": "FusedMatMul.TransBatchATiledReg",
463
+ "shader": "matmul-tiled-general-reg.wgsl.jinja",
464
+ "derive": {
465
+ "aShape": "shapes.A",
466
+ "bShape": "shapes.B",
467
+ "alpha": "attrs.alpha",
468
+ "aRank": "ranks.A",
469
+ "bRank": "ranks.B",
470
+ "transBatchA": "true",
471
+ "kTile": "4"
 
 
472
  },
473
+ "bindings": ["a", "b_3", "y"],
474
  "dispatch": {
475
  "x": "ceil(dim(shapes.B, 2) / 64)",
476
  "y": "ceil(dim(shapes.A, 0) / 64)",
 
482
  {
483
  "id": "tiled",
484
  "priority": 0,
485
+ "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) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "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) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "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))))) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"],
486
+ "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
487
  "passes": [
488
  {
489
  "id": "main",
490
  "name": "FusedMatMul.Tiled",
491
+ "shader": "matmul-tiled-general.wgsl.jinja",
492
+ "derive": {
493
+ "aShape": "shapes.A",
494
+ "bShape": "shapes.B",
495
+ "transA": "attrs.transA != 0",
496
+ "transB": "attrs.transB != 0",
497
+ "alpha": "attrs.alpha",
498
+ "aRank": "ranks.A",
499
+ "bRank": "ranks.B",
500
+ "transBatchA": "attrs.transBatchA != 0",
501
+ "transBatchB": "attrs.transBatchB != 0"
 
 
502
  },
503
+ "bindings": ["a", "b_3", "y"],
504
  "dispatch": {
505
  "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)",
506
  "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)",
 
508
  }
509
  }
510
  ]
511
+ },
512
+ {
513
+ "id": "transbatch_b_tiled_reg",
514
+ "priority": 5,
515
+ "when": ["f16Ok(dtypes.T)", "ranks.A == 3 and ranks.B == 3 and ranks.Y == 3", "attrs.transA == 0 and attrs.transB == 0 and attrs.transBatchA == 0 and attrs.transBatchB != 0", "dim(shapes.A, 0) == dim(shapes.B, 1) and dim(shapes.Y, 0) == dim(shapes.A, 0)", "dim(shapes.A, 2) == dim(shapes.B, 0)", "dim(shapes.Y, 1) == dim(shapes.A, 1) and dim(shapes.Y, 2) == dim(shapes.B, 2)", "dim(shapes.A, 0) > 0", "dim(shapes.A, 1) >= 64", "dim(shapes.A, 2) >= tunables.TRANSBATCH_REG_MIN_K", "dim(shapes.B, 2) >= 64", "ceilDiv(dim(shapes.B, 2), 64) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "ceilDiv(dim(shapes.A, 1), 64) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "dim(shapes.A, 0) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "dim(shapes.A, 0) * ceilDiv(dim(shapes.A, 1), 64) * ceilDiv(dim(shapes.B, 2), 64) >= tunables.TILED_REG_MIN_WORKGROUPS", "16 <= device.limits.maxComputeWorkgroupSizeX and 16 <= device.limits.maxComputeWorkgroupSizeY and 256 <= device.limits.maxComputeInvocationsPerWorkgroup", "(64 * 16 + 64 * 16) * dtypeBytes(dtypes.T) <= device.limits.maxComputeWorkgroupStorageSize"],
516
+ "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
517
+ "passes": [
518
+ {
519
+ "id": "main",
520
+ "name": "FusedMatMul.TransBatchBTiledReg",
521
+ "shader": "matmul-tiled-general-reg.wgsl.jinja",
522
+ "derive": {
523
+ "aShape": "shapes.A",
524
+ "bShape": "logicalBShape",
525
+ "alpha": "attrs.alpha",
526
+ "aRank": "ranks.A",
527
+ "bRank": "ranks.B",
528
+ "transBatchA": false,
529
+ "bStorageStrides": "[dim(shapes.B, 2), dim(shapes.B, 1) * dim(shapes.B, 2), 1]",
530
+ "regSequentialK": "dtypes.T == \"f16\""
531
+ },
532
+ "bindings": ["a", "b_3", "y"],
533
+ "dispatch": {
534
+ "x": "ceilDiv(dim(shapes.B, 2), 64)",
535
+ "y": "ceilDiv(dim(shapes.A, 1), 64)",
536
+ "z": "dim(shapes.Y, 0)"
537
+ }
538
+ }
539
+ ]
540
  }
541
  ]
542
  }
build/webgpu/matmul-band-vec4.wgsl.jinja ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Band GEMM y[M, N] = a[M, K] @ B[K, N]. Each lane owns one vec4 column group
2
+ // and carries one accumulator per row, so a loaded B word feeds all M row
3
+ // accumulators and each A value is reused across four adjacent output columns.
4
+ //
5
+ // A batched consumer runs one band per workgroup row: workgroup_id.y selects
6
+ // the matrix, and every operand is offset by its per-matrix extent.
7
+ {% if usesF16 %}
8
+ enable f16;
9
+
10
+ {% endif %}
11
+ {{ env.wgsl.resourceDeclarations }}
12
+
13
+ const K: u32 = {{ K }}u;
14
+ const N4: u32 = {{ N }}u / 4u;
15
+ const LANES: u32 = 32u;
16
+ // SLICES partitions the K reduction across the workgroup's second dimension.
17
+ const SLICES: u32 = {{ gemvSlices }}u;
18
+ {% set kSplitsValue = kSplits if kSplits is defined else 1 %}
19
+ {% set alphaValue = alpha if alpha is defined else 1 %}
20
+ {% if kSplitsValue > 1 %}
21
+ const K_SPLITS: u32 = {{ kSplitsValue }}u;
22
+ const K_PER_SPLIT: u32 = (K + K_SPLITS - 1u) / K_SPLITS;
23
+ {% endif %}
24
+
25
+ // The rows drain through one 32 x SLICES array in turn, so the workgroup
26
+ // footprint does not grow with the band.
27
+ var<workgroup> partials: array<vec4<f32>, LANES * SLICES>;
28
+
29
+ @compute @workgroup_size(32, {{ gemvSlices }}, 1)
30
+ fn main(
31
+ @builtin(workgroup_id) workgroup_id: vec3<u32>,
32
+ @builtin(local_invocation_id) lid: vec3<u32>
33
+ ) {
34
+ let lane = lid.x;
35
+ let slice = lid.y;
36
+ let cg = workgroup_id.x * LANES + lane;
37
+ {% if kSplitsValue > 1 %}
38
+ let a_base = 0u;
39
+ let b_base = 0u;
40
+ let y_base = workgroup_id.y * ({{ M }}u * N4);
41
+ let k_begin = workgroup_id.y * K_PER_SPLIT;
42
+ let k_end = min(K, k_begin + K_PER_SPLIT);
43
+ {% else %}
44
+ let a_base = 0u;
45
+ let b_base = 0u;
46
+ let y_base = 0u;
47
+ {% endif %}
48
+ {% for r in range(M) %}
49
+ var acc{{ r }} = vec4<f32>(0.0);
50
+ {% endfor %}
51
+ if (cg < N4) {
52
+ {% if kSplitsValue > 1 %}
53
+ for (var k = k_begin + slice; k < k_end; k = k + SLICES) {
54
+ {% else %}
55
+ for (var k = slice; k < K; k = k + SLICES) {
56
+ {% endif %}
57
+ let bv = vec4<f32>(b[b_base + k * N4 + cg]);
58
+ {% for r in range(M) %}
59
+ acc{{ r }} = acc{{ r }} + f32(a[a_base + {{ r }}u * K + k]) * bv;
60
+ {% endfor %}
61
+ }
62
+ }
63
+ {% for r in range(M) %}
64
+ partials[slice * LANES + lane] = acc{{ r }};
65
+ workgroupBarrier();
66
+ if (slice == 0u && cg < N4) {
67
+ var total = partials[lane];
68
+ for (var s = 1u; s < SLICES; s = s + 1u) {
69
+ total = total + partials[s * LANES + lane];
70
+ }
71
+ {% if alphaValue != 1 %}
72
+ total = total * {{ alphaValue }};
73
+ {% endif %}
74
+ {% if kSplitsValue > 1 %}
75
+ {{ outputBuffer }}[y_base + {{ r }}u * N4 + cg] = total;
76
+ {% else %}
77
+ {{ outputBuffer }}[y_base + {{ r }}u * N4 + cg] = vec4<{{ T }}>(total);
78
+ {% endif %}
79
+ }
80
+ {% if not loop.last %}
81
+ workgroupBarrier();
82
+ {% endif %}
83
+ {% endfor %}
84
+ }
build/webgpu/matmul-subgroup-matrix-ext.wgsl.jinja CHANGED
@@ -1,9 +1,6 @@
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;
@@ -11,6 +8,7 @@ enable subgroup_size_control;
11
  enable chromium_experimental_subgroup_matrix;
12
  diagnostic(off, chromium.subgroup_matrix_uniformity);
13
 
 
14
  {{ env.wgsl.resourceDeclarations }}
15
 
16
  {% set operandScalar = fScalar %}
@@ -20,17 +18,16 @@ diagnostic(off, chromium.subgroup_matrix_uniformity);
20
  {% set SPLIT_K = splitK if splitK is defined else 1 %}
21
  {% set OUT = outputBuffer if outputBuffer is defined else "c" %}
22
  {% set OUT_SCALAR = outScalar if outScalar is defined else T %}
23
- {% if GENERAL %}
24
- {% set aR = source.aRank %}
25
- {% set bR = source.bRank %}
26
  {% set aBatchLen = aR - 2 %}
27
  {% set bBatchLen = bR - 2 %}
28
  {% set batchRank = aBatchLen %}
29
- {% set aMStride = source.aShape[aR-1] %}
30
  {% set aKStride = 1 %}
31
- {% set bKStride = source.bShape[bR-1] %}
32
  {% set bNStride = 1 %}
33
- {% endif %}
34
 
35
  const M: u32 = {{ M }}u;
36
  const K: u32 = {{ K }}u;
@@ -40,7 +37,6 @@ const BATCH_COUNT: u32 = {{ batchCount if batchCount is defined else 1 }}u;
40
  const SPLIT_K: u32 = {{ SPLIT_K }}u;
41
  const K_PER_SPLIT: u32 = {{ kPerSplit }}u;
42
  {% endif %}
43
- {% if GENERAL %}
44
  const A_M_STRIDE: u32 = {{ aMStride }}u;
45
  const A_K_STRIDE: u32 = {{ aKStride }}u;
46
  const B_K_STRIDE: u32 = {{ bKStride }}u;
@@ -48,10 +44,6 @@ const B_N_STRIDE: u32 = {{ bNStride }}u;
48
  {% if TAIL %}const K_FULL: u32 = (K / 32u) * 32u;
49
  {% endif %}
50
  const ALPHA: f32 = f32({{ alpha }});
51
- {% else %}
52
- const A_BATCH_STRIDE: u32 = M * K;
53
- const B_BATCH_STRIDE: u32 = K * N;
54
- {% endif %}
55
  const C_BATCH_STRIDE: u32 = M * N;
56
  const TILE_COLS: u32 = 64u;
57
  const TILE_ROWS: u32 = 32u;
@@ -70,17 +62,9 @@ fn loadSHMA(a_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
70
  let k = k_idx + col + col_offset;
71
  if (a_global < M) {
72
  {% if operandScalar == "f16" %}
73
- {% if GENERAL %}
74
  tile_A[row * TILE_K + col + col_offset] = f16(a[a_base + a_global * A_M_STRIDE + k * A_K_STRIDE]);
75
  {% else %}
76
- tile_A[row * TILE_K + col + col_offset] = f16(a[a_base + a_global * K + k]);
77
- {% endif %}
78
- {% else %}
79
- {% if GENERAL %}
80
  tile_A[row * TILE_K + col + col_offset] = f32(a[a_base + a_global * A_M_STRIDE + k * A_K_STRIDE]);
81
- {% else %}
82
- tile_A[row * TILE_K + col + col_offset] = f32(a[a_base + a_global * K + k]);
83
- {% endif %}
84
  {% endif %}
85
  } else {
86
  {% if operandScalar == "f16" %}
@@ -112,7 +96,6 @@ fn loadSHMB(b_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
112
  let col = c_idx * 16u;
113
  for (var i = 0u; i < 16u; i = i + 1u) {
114
  let k = k_idx + col + i;
115
- {% if GENERAL %}
116
  {% if TAIL %}
117
  let b_safe = min(b_col, N - 1u);
118
  tile_B[row * TILE_K + col + i] = {{ operandScalar }}(b[b_base + k * B_K_STRIDE + b_safe * B_N_STRIDE]);
@@ -122,10 +105,6 @@ fn loadSHMB(b_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
122
  {% else %}
123
  tile_B[row * TILE_K + col + i] = f32(b[b_base + k * B_K_STRIDE + b_col * B_N_STRIDE]);
124
  {% endif %}
125
- {% endif %}
126
- {% else %}
127
- {% set bIndex = "b_col * K + k" if bTransposed is defined and bTransposed else "k * N + b_col" %}
128
- tile_B[row * TILE_K + col + i] = {{ operandScalar }}(b[b_base + {{ bIndex }}]);
129
  {% endif %}
130
  }
131
  }
@@ -149,7 +128,6 @@ fn loadSHMBKTail(b_base: u32, tile_base: u32, k_idx: u32, row: u32, c_idx: u32)
149
  fn storeOutput(offset: u32{% if needsColBase %}, col_base: u32{% endif %}, row: u32, col: u32, src_slot: u32, row_limit: i32) {
150
  if (row_limit > 0 && row < u32(row_limit)) {
151
  let col2 = col + 1u;
152
- {% if GENERAL %}
153
  {% for block in range(4) %}
154
  {% if TAIL %}
155
  if (col_base + col + {{ block * 8 }}u < N) {
@@ -168,29 +146,6 @@ fn storeOutput(offset: u32{% if needsColBase %}, col_base: u32{% endif %}, row:
168
  }
169
  {% endif %}
170
  {% endfor %}
171
- {% else %}
172
- {% if hasBias %}
173
- {{ OUT }}[offset + row * N + col] = {{ T }}(scratch[src_slot][0][row * 8u + col] + {{ accScalar }}(bias[col_base + col]));
174
- {{ OUT }}[offset + row * N + col + 8u] = {{ T }}(scratch[src_slot][1][row * 8u + col] + {{ accScalar }}(bias[col_base + col + 8u]));
175
- {{ OUT }}[offset + row * N + col + 16u] = {{ T }}(scratch[src_slot][2][row * 8u + col] + {{ accScalar }}(bias[col_base + col + 16u]));
176
- {{ OUT }}[offset + row * N + col + 24u] = {{ T }}(scratch[src_slot][3][row * 8u + col] + {{ accScalar }}(bias[col_base + col + 24u]));
177
-
178
- {{ OUT }}[offset + row * N + col2] = {{ T }}(scratch[src_slot][0][row * 8u + col2] + {{ accScalar }}(bias[col_base + col2]));
179
- {{ OUT }}[offset + row * N + col2 + 8u] = {{ T }}(scratch[src_slot][1][row * 8u + col2] + {{ accScalar }}(bias[col_base + col2 + 8u]));
180
- {{ OUT }}[offset + row * N + col2 + 16u] = {{ T }}(scratch[src_slot][2][row * 8u + col2] + {{ accScalar }}(bias[col_base + col2 + 16u]));
181
- {{ OUT }}[offset + row * N + col2 + 24u] = {{ T }}(scratch[src_slot][3][row * 8u + col2] + {{ accScalar }}(bias[col_base + col2 + 24u]));
182
- {% else %}
183
- {{ OUT }}[offset + row * N + col] = {{ T }}(scratch[src_slot][0][row * 8u + col]);
184
- {{ OUT }}[offset + row * N + col + 8u] = {{ T }}(scratch[src_slot][1][row * 8u + col]);
185
- {{ OUT }}[offset + row * N + col + 16u] = {{ T }}(scratch[src_slot][2][row * 8u + col]);
186
- {{ OUT }}[offset + row * N + col + 24u] = {{ T }}(scratch[src_slot][3][row * 8u + col]);
187
-
188
- {{ OUT }}[offset + row * N + col2] = {{ T }}(scratch[src_slot][0][row * 8u + col2]);
189
- {{ OUT }}[offset + row * N + col2 + 8u] = {{ T }}(scratch[src_slot][1][row * 8u + col2]);
190
- {{ OUT }}[offset + row * N + col2 + 16u] = {{ T }}(scratch[src_slot][2][row * 8u + col2]);
191
- {{ OUT }}[offset + row * N + col2 + 24u] = {{ T }}(scratch[src_slot][3][row * 8u + col2]);
192
- {% endif %}
193
- {% endif %}
194
  }
195
  }
196
 
@@ -222,8 +177,8 @@ fn main(
222
  let row_tile_stride = num_wg.y * TILE_ROWS;
223
  for (var a_global_base = workgroup_id.y * TILE_ROWS; a_global_base < M; a_global_base += row_tile_stride) {
224
  {% if SPLIT_K > 1 %}
225
- // Split-K maps z to (batch, K segment), multiplying occupancy for narrow
226
- // matrices while every segment still owns a disjoint contiguous K range.
227
  for (var batch_split = workgroup_id.z; batch_split < BATCH_COUNT * SPLIT_K; batch_split += num_wg.z) {
228
  let batch = batch_split / SPLIT_K;
229
  let split_id = batch_split - batch * SPLIT_K;
@@ -231,14 +186,13 @@ fn main(
231
  // workgroup_size.z = 1, so num_wg.z is the dispatch stride over the batch axis.
232
  for (var batch = workgroup_id.z; batch < BATCH_COUNT; batch += num_wg.z) {
233
  {% endif %}
234
- {% if GENERAL %}
235
  {% set hasBatchCoord = namespace(value=false) %}
236
  {% for i in range(batchRank) %}
237
  {% set axis = batchRank - 1 - i %}
238
  {% set aAxis = axis - (batchRank - aBatchLen) %}
239
  {% set bAxis = axis - (batchRank - bBatchLen) %}
240
- {% set aDim = source.aShape[aAxis] %}
241
- {% set bDim = source.bShape[bAxis] if bAxis >= 0 else 1 %}
242
  {% if aDim > 1 or bDim > 1 %}{% set hasBatchCoord.value = true %}{% endif %}
243
  {% endfor %}
244
  // Right-aligned broadcast offsets, decomposed from the flattened output batch.
@@ -251,13 +205,14 @@ fn main(
251
  {% set axis = batchRank - 1 - i %}
252
  {% set aAxis = axis - (batchRank - aBatchLen) %}
253
  {% set bAxis = axis - (batchRank - bBatchLen) %}
254
- {% set aDim = source.aShape[aAxis] %}
255
- {% set bDim = source.bShape[bAxis] if bAxis >= 0 else 1 %}
256
  {% set outDim = aDim if aDim >= bDim else bDim %}
257
  {% set aStride = namespace(v=1) %}
258
- {% 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 %}
259
  {% set bStride = namespace(v=1) %}
260
- {% 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 %}
 
261
  {% if outDim > 1 %}
262
  let c{{ axis }} = zTmp % {{ outDim }}u;
263
  zTmp = zTmp / {{ outDim }}u;
@@ -272,11 +227,6 @@ fn main(
272
  {% else %}
273
  let c_base = batch * C_BATCH_STRIDE;
274
  {% endif %}
275
- {% else %}
276
- let a_base = batch * A_BATCH_STRIDE;
277
- let b_base = batch * B_BATCH_STRIDE;
278
- let c_base = batch * C_BATCH_STRIDE;
279
- {% endif %}
280
 
281
  var matC00: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
282
  var matC01: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
@@ -299,15 +249,16 @@ fn main(
299
  workgroupBarrier();
300
 
301
  for (var step = 0u; step < TILE_K; step = step + 8u) {
302
- let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
303
- var matA0: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset, TILE_K);
304
- var matA1: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset + 8u * TILE_K, TILE_K);
 
 
305
 
306
  let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
307
- var matB0: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset, TILE_K);
308
- var matB1: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 8u * TILE_K, TILE_K);
309
- var matB2: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 16u * TILE_K, TILE_K);
310
- var matB3: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 24u * TILE_K, TILE_K);
311
 
312
  matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
313
  matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
@@ -327,15 +278,16 @@ fn main(
327
  workgroupBarrier();
328
 
329
  for (var step = 0u; step < TILE_K; step = step + 8u) {
330
- let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
331
- var matA0: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset, TILE_K);
332
- var matA1: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset + 8u * TILE_K, TILE_K);
 
 
333
 
334
- let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
335
- var matB0: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset, TILE_K);
336
- var matB1: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 8u * TILE_K, TILE_K);
337
- var matB2: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 16u * TILE_K, TILE_K);
338
- var matB3: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + 24u * TILE_K, TILE_K);
339
 
340
  matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
341
  matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
 
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
  enable subgroups;
5
  {% if pinSubgroupSize32 %}
6
  enable subgroup_size_control;
 
8
  enable chromium_experimental_subgroup_matrix;
9
  diagnostic(off, chromium.subgroup_matrix_uniformity);
10
 
11
+
12
  {{ env.wgsl.resourceDeclarations }}
13
 
14
  {% set operandScalar = fScalar %}
 
18
  {% set SPLIT_K = splitK if splitK is defined else 1 %}
19
  {% set OUT = outputBuffer if outputBuffer is defined else "c" %}
20
  {% set OUT_SCALAR = outScalar if outScalar is defined else T %}
21
+ {% set aR = aRank %}
22
+ {% set bR = bRank %}
 
23
  {% set aBatchLen = aR - 2 %}
24
  {% set bBatchLen = bR - 2 %}
25
  {% set batchRank = aBatchLen %}
26
+ {% set aMStride = aShape[aR-1] %}
27
  {% set aKStride = 1 %}
28
+ {% set bKStride = bShape[bR-1] %}
29
  {% set bNStride = 1 %}
30
+ {% if bStorageStrides is defined %}{% set bKStride = bStorageStrides[bR-2] %}{% set bNStride = bStorageStrides[bR-1] %}{% endif %}
31
 
32
  const M: u32 = {{ M }}u;
33
  const K: u32 = {{ K }}u;
 
37
  const SPLIT_K: u32 = {{ SPLIT_K }}u;
38
  const K_PER_SPLIT: u32 = {{ kPerSplit }}u;
39
  {% endif %}
 
40
  const A_M_STRIDE: u32 = {{ aMStride }}u;
41
  const A_K_STRIDE: u32 = {{ aKStride }}u;
42
  const B_K_STRIDE: u32 = {{ bKStride }}u;
 
44
  {% if TAIL %}const K_FULL: u32 = (K / 32u) * 32u;
45
  {% endif %}
46
  const ALPHA: f32 = f32({{ alpha }});
 
 
 
 
47
  const C_BATCH_STRIDE: u32 = M * N;
48
  const TILE_COLS: u32 = 64u;
49
  const TILE_ROWS: u32 = 32u;
 
62
  let k = k_idx + col + col_offset;
63
  if (a_global < M) {
64
  {% if operandScalar == "f16" %}
 
65
  tile_A[row * TILE_K + col + col_offset] = f16(a[a_base + a_global * A_M_STRIDE + k * A_K_STRIDE]);
66
  {% else %}
 
 
 
 
67
  tile_A[row * TILE_K + col + col_offset] = f32(a[a_base + a_global * A_M_STRIDE + k * A_K_STRIDE]);
 
 
 
68
  {% endif %}
69
  } else {
70
  {% if operandScalar == "f16" %}
 
96
  let col = c_idx * 16u;
97
  for (var i = 0u; i < 16u; i = i + 1u) {
98
  let k = k_idx + col + i;
 
99
  {% if TAIL %}
100
  let b_safe = min(b_col, N - 1u);
101
  tile_B[row * TILE_K + col + i] = {{ operandScalar }}(b[b_base + k * B_K_STRIDE + b_safe * B_N_STRIDE]);
 
105
  {% else %}
106
  tile_B[row * TILE_K + col + i] = f32(b[b_base + k * B_K_STRIDE + b_col * B_N_STRIDE]);
107
  {% endif %}
 
 
 
 
108
  {% endif %}
109
  }
110
  }
 
128
  fn storeOutput(offset: u32{% if needsColBase %}, col_base: u32{% endif %}, row: u32, col: u32, src_slot: u32, row_limit: i32) {
129
  if (row_limit > 0 && row < u32(row_limit)) {
130
  let col2 = col + 1u;
 
131
  {% for block in range(4) %}
132
  {% if TAIL %}
133
  if (col_base + col + {{ block * 8 }}u < N) {
 
146
  }
147
  {% endif %}
148
  {% endfor %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  }
150
  }
151
 
 
177
  let row_tile_stride = num_wg.y * TILE_ROWS;
178
  for (var a_global_base = workgroup_id.y * TILE_ROWS; a_global_base < M; a_global_base += row_tile_stride) {
179
  {% if SPLIT_K > 1 %}
180
+ // Split-K maps z to (batch, K segment), increasing the independent workgroup
181
+ // count for narrow matrices. Every segment owns a disjoint contiguous K range.
182
  for (var batch_split = workgroup_id.z; batch_split < BATCH_COUNT * SPLIT_K; batch_split += num_wg.z) {
183
  let batch = batch_split / SPLIT_K;
184
  let split_id = batch_split - batch * SPLIT_K;
 
186
  // workgroup_size.z = 1, so num_wg.z is the dispatch stride over the batch axis.
187
  for (var batch = workgroup_id.z; batch < BATCH_COUNT; batch += num_wg.z) {
188
  {% endif %}
 
189
  {% set hasBatchCoord = namespace(value=false) %}
190
  {% for i in range(batchRank) %}
191
  {% set axis = batchRank - 1 - i %}
192
  {% set aAxis = axis - (batchRank - aBatchLen) %}
193
  {% set bAxis = axis - (batchRank - bBatchLen) %}
194
+ {% set aDim = aShape[aAxis] %}
195
+ {% set bDim = bShape[bAxis] if bAxis >= 0 else 1 %}
196
  {% if aDim > 1 or bDim > 1 %}{% set hasBatchCoord.value = true %}{% endif %}
197
  {% endfor %}
198
  // Right-aligned broadcast offsets, decomposed from the flattened output batch.
 
205
  {% set axis = batchRank - 1 - i %}
206
  {% set aAxis = axis - (batchRank - aBatchLen) %}
207
  {% set bAxis = axis - (batchRank - bBatchLen) %}
208
+ {% set aDim = aShape[aAxis] %}
209
+ {% set bDim = bShape[bAxis] if bAxis >= 0 else 1 %}
210
  {% set outDim = aDim if aDim >= bDim else bDim %}
211
  {% set aStride = namespace(v=1) %}
212
+ {% if aDim != 1 %}{% for j in range(aAxis + 1, aR) %}{% set aStride.v = aStride.v * aShape[j] %}{% endfor %}{% else %}{% set aStride.v = 0 %}{% endif %}
213
  {% set bStride = namespace(v=1) %}
214
+ {% if bAxis >= 0 and bDim != 1 %}{% for j in range(bAxis + 1, bR) %}{% set bStride.v = bStride.v * bShape[j] %}{% endfor %}{% else %}{% set bStride.v = 0 %}{% endif %}
215
+ {% if bStorageStrides is defined and bAxis >= 0 and bDim != 1 %}{% set bStride.v = bStorageStrides[bAxis] %}{% endif %}
216
  {% if outDim > 1 %}
217
  let c{{ axis }} = zTmp % {{ outDim }}u;
218
  zTmp = zTmp / {{ outDim }}u;
 
227
  {% else %}
228
  let c_base = batch * C_BATCH_STRIDE;
229
  {% endif %}
 
 
 
 
 
230
 
231
  var matC00: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
232
  var matC01: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
 
249
  workgroupBarrier();
250
 
251
  for (var step = 0u; step < TILE_K; step = step + 8u) {
252
+ {% set directInputs = directMatrixInputs is defined and directMatrixInputs %}
253
+ let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
254
+ {% for r in range(2) %}
255
+ var matA{{ r }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&{{ "w" if directInputs else "tile_A" }}, matrix_a_offset{% if r > 0 %} + 8u * {{ "K" if directInputs else "TILE_K" }}{% endif %}, {{ "K" if directInputs else "TILE_K" }});
256
+ {% endfor %}
257
 
258
  let matrix_b_offset = subtile_idx * SUB_COLS * TILE_K + step;
259
+ {% for c in range(4) %}
260
+ var matB{{ c }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&{{ "xm" if directInputs else "tile_B" }}, matrix_b_offset{% if c > 0 %} + {{ c * 8 }}u * TILE_K{% endif %}, {{ "N" if directInputs else "TILE_K" }});
261
+ {% endfor %}
 
262
 
263
  matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
264
  matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
 
278
  workgroupBarrier();
279
 
280
  for (var step = 0u; step < TILE_K; step = step + 8u) {
281
+ {% set directInputs = directMatrixInputs is defined and directMatrixInputs %}
282
+ let matrix_a_offset = {% if directInputs %}(a_global_base + subtile_idy * SUB_ROWS) * K + kidx + step{% else %}subtile_idy * SUB_ROWS * TILE_K + step{% endif %};
283
+ {% for r in range(2) %}
284
+ var matA{{ r }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&{{ "w" if directInputs else "tile_A" }}, matrix_a_offset{% if r > 0 %} + 8u * {{ "K" if directInputs else "TILE_K" }}{% endif %}, {{ "K" if directInputs else "TILE_K" }});
285
+ {% endfor %}
286
 
287
+ let matrix_b_offset = {% if directInputs %}b_base + (kidx + step) * N + b_global_base + subtile_idx * SUB_COLS{% else %}subtile_idx * SUB_COLS * TILE_K + step{% endif %};
288
+ {% for c in range(4) %}
289
+ var matB{{ c }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, {% if directInputs %}row_major{% else %}col_major{% endif %}>(&{{ "xm" if directInputs else "tile_B" }}, matrix_b_offset{% if c > 0 %} + {{ c * 8 }}u{% if not directInputs %} * TILE_K{% endif %}{% endif %}, {{ "N" if directInputs else "TILE_K" }});
290
+ {% endfor %}
 
291
 
292
  matC00 = subgroupMatrixMultiplyAccumulate(matA0, matB0, matC00);
293
  matC01 = subgroupMatrixMultiplyAccumulate(matA0, matB1, matC01);
build/webgpu/matmul-tiled-general-reg.wgsl.jinja CHANGED
@@ -1,6 +1,3 @@
1
- {% if usesF16 %}
2
- enable f16;
3
- {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
5
 
6
  // Register-blocked MatMul for the no-subgroup-matrix
@@ -13,31 +10,35 @@ enable f16;
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;
@@ -45,7 +46,7 @@ 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.
@@ -54,6 +55,11 @@ 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]
@@ -62,10 +68,10 @@ var<workgroup> tileB: array<array<vec4<{{ tileT }}>, K_VECS>, BN>; // B[n][k/4
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
 
@@ -79,8 +85,12 @@ fn main(
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;
@@ -88,15 +98,15 @@ fn main(
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;
@@ -110,9 +120,18 @@ fn main(
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) {
@@ -144,19 +163,24 @@ fn main(
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
 
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  // Register-blocked MatMul for the no-subgroup-matrix
 
10
  // accumulates through dot() and one step reads TM + TN words rather than
11
  // 4 * (TM + TN) scalars. A stores K contiguously and B stores N contiguously,
12
  // so each staging lane walks the axis its operand already has.
13
+ {% set aR = aRank %}
14
+ {% set bR = bRank %}
15
  {% set aBatchLen = aR - 2 %}
16
  {% set bBatchLen = bR - 2 %}
17
  {% set batchRank = aBatchLen %}
18
  {% set aTailStride = namespace(v=1) %}
19
+ {% for j in range(1, aR) %}{% set aTailStride.v = aTailStride.v * aShape[j] %}{% endfor %}
20
  {% set bTailStride = namespace(v=1) %}
21
+ {% for j in range(1, bR) %}{% set bTailStride.v = bTailStride.v * bShape[j] %}{% endfor %}
22
+ {% if transBatchA %}{% set M = aShape[0] %}{% set K = aShape[aR-1] %}
23
+ {% else %}{% set M = aShape[aR-2] %}{% set K = aShape[aR-1] %}{% endif %}
24
+ {% set N = bShape[bR-1] %}
25
+ {% if transBatchA %}{% set aMStride = aTailStride.v %}{% set aKStride = 1 %}
26
+ {% else %}{% set aMStride = aShape[aR-1] %}{% set aKStride = 1 %}{% endif %}
27
+ {% set bKStride = bShape[bR-1] %}{% set bNStride = 1 %}{% if bStorageStrides is defined %}{% set bKStride = bStorageStrides[bR-2] %}{% set bNStride = bStorageStrides[bR-1] %}{% endif %}
 
 
 
28
  {% set is_int = (scalar == "i32" or scalar == "u32") %}
29
+ {% if is_int %}
30
+ // Integer operands accumulate in their integer type, avoiding f32 rounding of
31
+ // values outside the exact 24-bit significand range.
32
+ {% endif %}
33
  {% set accT = scalar if is_int else "f32" %}
34
+ {% set outScalar = outScalar if outScalar is defined else scalar %}
35
+ {% set splitK = splitK if splitK is defined else 1 %}
36
+ {% if scalar == "f16" %}
37
+ // f16 operands stay packed in workgroup memory and widen on shared load;
38
+ // accumulation remains f32.
39
+ {% endif %}
40
  {% set tileT = scalar if scalar == "f16" else accT %}
41
+ {% set kTile = kTile if kTile is defined else 16 %}
42
  const M: u32 = {{ M }}u;
43
  const K: u32 = {{ K }}u;
44
  const N: u32 = {{ N }}u;
 
46
  const A_K_STRIDE: u32 = {{ aKStride }}u;
47
  const B_K_STRIDE: u32 = {{ bKStride }}u;
48
  const B_N_STRIDE: u32 = {{ bNStride }}u;
49
+ {% if is_int %}const ALPHA: {{ accT }} = {{ accT }}(1);{% else %}const ALPHA: f32 = f32({{ alpha }});{% endif %}
50
  // A 4x4 micro-tile over a 64x64 output tile reuses each staged operand across
51
  // four accumulators. It increases arithmetic work per load without the large
52
  // per-thread accumulator footprint of an 8x8 micro-tile.
 
55
  const BN: u32 = 64u;
56
  const TM: u32 = 4u; // per-thread micro-tile rows
57
  const TN: u32 = 4u; // per-thread micro-tile cols
58
+ {% if splitK > 1 %}
59
+ const SPLIT_K: u32 = {{ splitK }}u;
60
+ const K_PER_SPLIT: u32 = {{ kPerSplit }}u;
61
+
62
+ {% endif %}
63
  const K_VECS: u32 = BK / 4u;
64
  var<workgroup> tileA: array<array<vec4<{{ tileT }}>, K_VECS>, BM>; // A[m][k/4]
65
  var<workgroup> tileB: array<array<vec4<{{ tileT }}>, K_VECS>, BN>; // B[n][k/4]
 
68
  {% set axis = batchRank - 1 - i %}
69
  {% set aAxis = axis - (batchRank - aBatchLen) %}
70
  {% set bAxis = axis - (batchRank - bBatchLen) %}
71
+ {% set aStored = (aAxis + 1) if (transBatchA and aAxis >= 0) else aAxis %}
72
  {% set bStored = bAxis %}
73
+ {% set aDim = aShape[aStored] %}
74
+ {% set bDim = bShape[bStored] if bStored >= 0 else 1 %}
75
  {% if aDim > 1 or bDim > 1 %}{% set hasBatchCoord.value = true %}{% endif %}
76
  {% endfor %}
77
 
 
85
  let li = lid.y * 16u + lid.x;
86
 
87
  let zOut = wg.z;
88
+ {% if splitK > 1 %}
89
+ let splitId = wg.z % SPLIT_K;
90
+ {% else %}
91
  {% if hasBatchCoord.value %}
92
  var zTmp = wg.z;
93
+ {% endif %}
94
  {% endif %}
95
  var aBatchOff: u32 = 0u;
96
  var bBatchOff: u32 = 0u;
 
98
  {% set axis = batchRank - 1 - i %}
99
  {% set aAxis = axis - (batchRank - aBatchLen) %}
100
  {% set bAxis = axis - (batchRank - bBatchLen) %}
101
+ {% set aStored = (aAxis + 1) if (transBatchA and aAxis >= 0) else aAxis %}
102
  {% set bStored = bAxis %}
103
+ {% set aDim = aShape[aStored] %}
104
+ {% set bDim = bShape[bStored] if bStored >= 0 else 1 %}
105
  {% set outDim = aDim if aDim >= bDim else bDim %}
106
  {% set aStride = namespace(v=1) %}
107
+ {% if aStored >= 0 and aDim != 1 %}{% for j in range(aStored + 1, aR) %}{% set aStride.v = aStride.v * aShape[j] %}{% endfor %}{% else %}{% set aStride.v = 0 %}{% endif %}
108
  {% set bStride = namespace(v=1) %}
109
+ {% if bStored >= 0 and bDim != 1 %}{% for j in range(bStored + 1, bR) %}{% set bStride.v = bStride.v * bShape[j] %}{% endfor %}{% else %}{% set bStride.v = 0 %}{% endif %}{% if bStorageStrides is defined and bStored >= 0 and bDim != 1 %}{% set bStride.v = bStorageStrides[bStored] %}{% endif %}
110
  {% if outDim > 1 %}
111
  let c{{ axis }} = zTmp % {{ outDim }}u;
112
  zTmp = zTmp / {{ outDim }}u;
 
120
  var acc: array<{{ accT }}, TM * TN>; // [ti*TN + tj] for the TMxTN micro-tile
121
  for (var i: u32 = 0u; i < TM * TN; i = i + 1u) { acc[i] = {{ accT }}(0); }
122
 
123
+ {% if splitK > 1 %}
124
+ let kStart = splitId * K_PER_SPLIT;
125
+ let numTiles = K_PER_SPLIT / BK;
126
+ {% else %}
127
  let numTiles = (K + BK - 1u) / BK;
128
+ {% endif %}
129
  for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
130
+ {% if splitK > 1 %}
131
+ let kBase = kStart + kt * BK;
132
+ {% else %}
133
  let kBase = kt * BK;
134
+ {% endif %}
135
  // Cooperative load: one vector word per lane per pass. A's lanes walk K, which
136
  // it stores contiguously; B's walk N, which it stores contiguously.
137
  for (var idx: u32 = li; idx < BM * K_VECS; idx = idx + 256u) {
 
163
  tileB[bc][br4] = bWord;
164
  }
165
  workgroupBarrier();
166
+ {% set regT = accT %}{% filter indent(4, true) %}
167
+ let aRow = lid.y * TM;
168
+ let bCol = lid.x * TN;
169
+ for (var kv: u32 = 0u; kv < BK / 4u; kv = kv + 1u) {
170
+ var av: array<vec4<{{ regT }}>, TM>;
171
+ var bv: array<vec4<{{ regT }}>, TN>;
172
+ for (var i: u32 = 0u; i < TM; i = i + 1u) { av[i] = vec4<{{ regT }}>(tileA[aRow + i][kv]); }
173
+ for (var j: u32 = 0u; j < TN; j = j + 1u) { bv[j] = vec4<{{ regT }}>(tileB[bCol + j][kv]); }
174
+ for (var i: u32 = 0u; i < TM; i = i + 1u) {
175
+ for (var j: u32 = 0u; j < TN; j = j + 1u) {
176
+ {% if regSequentialK is defined and regSequentialK %}{% for component in range(4) %}
177
+ acc[i * TN + j] = acc[i * TN + j] + av[i][{{ component }}] * bv[j][{{ component }}];
178
+ {% endfor %}{% else %} acc[i * TN + j] = acc[i * TN + j] + dot(av[i], bv[j]);
179
+ {% endif %}
180
+ }
181
+ }
182
+ }
183
+ {% endfilter %}
184
  workgroupBarrier();
185
  }
186
 
build/webgpu/matmul-tiled-general.wgsl.jinja CHANGED
@@ -1,6 +1,3 @@
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
@@ -10,8 +7,8 @@ enable f16;
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 %}
@@ -22,28 +19,28 @@ enable f16;
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" %}
@@ -57,7 +54,7 @@ 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.
@@ -72,10 +69,10 @@ var<workgroup> tileB: array<array<{{ tileT }}, 32>, 16>;
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
 
@@ -100,15 +97,15 @@ fn main(
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;
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  // Shared tiled matrix multiplication: Y = alpha * op(A) @ op(B), where op
 
7
  // K/M/N. transA/transB only change which stored stride the logical (m,k)/(k,n)
8
  // walk, using compiled stride constants here; the batch broadcast strides are
9
  // compiled the same way (a 0 literal means "broadcast / absent on that operand").
10
+ {% set aR = aRank %}
11
+ {% set bR = bRank %}
12
  {% set aVec = (aR == 1) %}
13
  {% set bVec = (bR == 1) %}
14
  {% set aBatchLen = (aR - 2) if aR >= 2 else 0 %}
 
19
  * axis is unchanged, and the remaining axes form the batch. This stride
20
  * permutation composes with the ordinary last-two-axis transpose. */
21
  {% set aTailStride = namespace(v=1) %}
22
+ {% for j in range(1, aR) %}{% set aTailStride.v = aTailStride.v * aShape[j] %}{% endfor %}
23
  {% set bTailStride = namespace(v=1) %}
24
+ {% for j in range(1, bR) %}{% set bTailStride.v = bTailStride.v * bShape[j] %}{% endfor %}
25
+ {% if aVec %}{% set M = 1 %}{% set K = aShape[0] %}
26
+ {% elif transBatchA and transA %}{% set M = aShape[aR-1] %}{% set K = aShape[0] %}
27
+ {% elif transBatchA %}{% set M = aShape[0] %}{% set K = aShape[aR-1] %}
28
+ {% elif transA %}{% set M = aShape[aR-1] %}{% set K = aShape[aR-2] %}
29
+ {% else %}{% set M = aShape[aR-2] %}{% set K = aShape[aR-1] %}{% endif %}
30
  {% if bVec %}{% set N = 1 %}
31
+ {% elif transBatchB and transB %}{% set N = bShape[0] %}
32
+ {% elif transB %}{% set N = bShape[bR-2] %}
33
+ {% else %}{% set N = bShape[bR-1] %}{% endif %}
34
  {% if aVec %}{% set aMStride = 0 %}{% set aKStride = 1 %}
35
+ {% elif transBatchA and transA %}{% set aMStride = 1 %}{% set aKStride = aTailStride.v %}
36
+ {% elif transBatchA %}{% set aMStride = aTailStride.v %}{% set aKStride = 1 %}
37
+ {% elif transA %}{% set aMStride = 1 %}{% set aKStride = aShape[aR-1] %}
38
+ {% else %}{% set aMStride = aShape[aR-1] %}{% set aKStride = 1 %}{% endif %}
39
  {% if bVec %}{% set bKStride = 1 %}{% set bNStride = 0 %}
40
+ {% elif transBatchB and transB %}{% set bKStride = 1 %}{% set bNStride = bTailStride.v %}
41
+ {% elif transBatchB %}{% set bKStride = bTailStride.v %}{% set bNStride = 1 %}
42
+ {% elif transB %}{% set bKStride = 1 %}{% set bNStride = bShape[bR-1] %}
43
+ {% else %}{% set bKStride = bShape[bR-1] %}{% set bNStride = 1 %}{% endif %}
44
 
45
  {% set is_int = (scalar == "i32" or scalar == "u32") %}
46
  {% set accT = scalar if is_int else "f32" %}
 
54
  const B_N_STRIDE: u32 = {{ bNStride }}u;
55
  {% if is_int %}/* Integer matrix multiplication accumulates in the integer type. Widening
56
  * through f32 would round values above 2^24. Integer MatMul has alpha = 1. */
57
+ const ALPHA: {{ accT }} = {{ accT }}(1);{% else %}const ALPHA: f32 = f32({{ alpha }});{% endif %}
58
  // 2x2 register-blocked tile: 16x16 threads each compute a 2x2 micro-tile, for a
59
  // 32x32 output tile per workgroup with K stepped in BK=16 chunks. Each loaded
60
  // shared-mem element feeds 2 FMAs, favoring register reuse in the inner loop.
 
69
  {% set axis = batchRank - 1 - i %}
70
  {% set aAxis = axis - (batchRank - aBatchLen) %}
71
  {% set bAxis = axis - (batchRank - bBatchLen) %}
72
+ {% set aStored = (aAxis + 1) if (transBatchA and aAxis >= 0) else aAxis %}
73
+ {% set bStored = (bAxis + 1) if (transBatchB and bAxis >= 0) else bAxis %}
74
+ {% set aDim = aShape[aStored] if aStored >= 0 else 1 %}
75
+ {% set bDim = bShape[bStored] if bStored >= 0 else 1 %}
76
  {% if aDim > 1 or bDim > 1 %}{% set hasBatchCoord.value = true %}{% endif %}
77
  {% endfor %}
78
 
 
97
  {% set axis = batchRank - 1 - i %}
98
  {% set aAxis = axis - (batchRank - aBatchLen) %}
99
  {% set bAxis = axis - (batchRank - bBatchLen) %}
100
+ {% set aStored = (aAxis + 1) if (transBatchA and aAxis >= 0) else aAxis %}
101
+ {% set bStored = (bAxis + 1) if (transBatchB and bAxis >= 0) else bAxis %}
102
+ {% set aDim = aShape[aStored] if aStored >= 0 else 1 %}
103
+ {% set bDim = bShape[bStored] if bStored >= 0 else 1 %}
104
  {% set outDim = aDim if aDim >= bDim else bDim %}
105
  {% set aStride = namespace(v=1) %}
106
+ {% if aStored >= 0 and aDim != 1 %}{% for j in range(aStored + 1, aR) %}{% set aStride.v = aStride.v * aShape[j] %}{% endfor %}{% else %}{% set aStride.v = 0 %}{% endif %}
107
  {% set bStride = namespace(v=1) %}
108
+ {% if bStored >= 0 and bDim != 1 %}{% for j in range(bStored + 1, bR) %}{% set bStride.v = bStride.v * bShape[j] %}{% endfor %}{% else %}{% set bStride.v = 0 %}{% endif %}
109
  {% if outDim > 1 %}
110
  {% if aStride.v != 0 or bStride.v != 0 %}
111
  let c{{ axis }} = zTmp % {{ outDim }}u;
build/webgpu/matmul-vector-matrix-vec4.wgsl.jinja CHANGED
@@ -1,17 +1,11 @@
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>;
@@ -37,6 +31,11 @@ fn main(
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
  }
 
1
+ // GEMV specialization for y[N] = a[K] @ B[K, N]. Each workgroup owns 32
2
+ // consecutive vec4 column groups, or 128 output columns.
 
 
 
 
 
3
  {{ env.wgsl.resourceDeclarations }}
4
  {% set OUT = outputBuffer if outputBuffer is defined else "c" %}
5
 
6
  const LANES: u32 = 32u;
7
+ // SLICES partitions the K reduction across the workgroup's second dimension.
8
+ // Thread zero of each column group combines the slice partials in index order.
 
9
  const SLICES: u32 = {{ gemvSlices }}u;
10
 
11
  var<workgroup> partials: array<vec4<f32>, LANES * SLICES>;
 
31
  for (var s = 1u; s < SLICES; s = s + 1u) {
32
  total = total + partials[s * LANES + lane];
33
  }
34
+ {% if alphaScale is defined and alphaScale != 1 %}
35
+ // This specialization bakes the output multiplier into the shader and
36
+ // applies it after combining the K slices.
37
+ total = total * f32({{ alphaScale }});
38
+ {% endif %}
39
  {{ OUT }}[cg] = vec4<{{ T }}>(total);
40
  }
41
  }
build/webgpu/metadata.json CHANGED
@@ -1,23 +1,42 @@
1
  {
2
  "name": "com.microsoft.FusedMatMul",
3
- "id": "_com_microsoft_fusedmatmul_webgpu_a7be42e",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "FKzhB1qpaKYuQ28OUS2fiiJaDroyLDXWubS0iFa3Fyw=",
11
- "fused-matmul-subgroup-matrix.wgsl.jinja": "tZdeSq2o0dor9WP7R5Tf5Yzxm8DXHapNXdGci8SH4wI=",
12
- "manifest.json": "TpmC/4RDrMS30inZSCKjBEw3wo+UAldsvOfUrpKuZn0=",
13
- "matmul-subgroup-matrix-ext.wgsl.jinja": "W5PywIfrssv2VQhuCG1lu3LjC3R7VpxGRnkHwMX050Q=",
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
- "reduce-axis0-splitk-combine.wgsl.jinja": "uvQNNkj1ezvpKVhYyLUAAfGVLOk/i7VnAiiNSYYz2uk=",
18
- "test.json": "Nv5BBqSF2s59CpK7a6Zmu1cc32XOn+PNz8J/MiLO0uo="
 
19
  }
20
  },
21
- "provenance": { "kernel": { "sha": "c928d21e6cc1310861cba3bafb75f5f679ecf5f3", "dirty": false } },
22
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.FusedMatMul" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  }
 
1
  {
2
  "name": "com.microsoft.FusedMatMul",
3
+ "id": "_com_microsoft_fusedmatmul_webgpu_8b2cb77",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "ATSeCpumMpLFypVxXuSfXbcCPQhVBBmfOof4PcQd89o=",
11
+ "fused-matmul-subgroup-matrix.wgsl.jinja": "0G1fPWFTk4qK1SiuAcriccOujenbWQfIgE0F0iXPlFo=",
12
+ "manifest.json": "pl4JzUzE2Z8hDvUoxFEkFv1qWvP/cMYQvOPN0s3asPs=",
13
+ "matmul-band-vec4.wgsl.jinja": "CN34bTiT4RnjmEoH/zPKP3a0tRpfYqP4l/X/bKCvHUk=",
14
+ "matmul-subgroup-matrix-ext.wgsl.jinja": "9hswKFk/g2HxvNxKwowsc7cJtEUGvrL53ISLi46Y3Co=",
15
+ "matmul-tiled-general-reg.wgsl.jinja": "Td1g9ghExH+uVbEhwxTEo1jkBpmkgKjBlkWGMD6JZ5I=",
16
+ "matmul-tiled-general.wgsl.jinja": "7vW3jelY9YQHhc92bE/yQZFQk4yeuSRby23ELji4rFs=",
17
+ "matmul-vector-matrix-vec4.wgsl.jinja": "TxVXDiRp6BURoOhTbh2ILDAAL9D9cVDfUhk8e5ViSfU=",
18
+ "reduce-axis0-splitk-combine.wgsl.jinja": "Yz1hjK55R/kndUrw3ugPqgPaOBwKmYupqZoVdJTHO5Q=",
19
+ "test.json": "CuQ/9/rI0a9gPgB8JwczBXvdF7PIuQoXpiFFKVmkeC4="
20
  }
21
  },
22
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
23
+ "webgpu": {
24
+ "manifestSpec": "2.0",
25
+ "variants": {
26
+ "subgroup_matrix_transbatch_b_f16": ["matmul-subgroup-matrix-ext.wgsl.jinja"],
27
+ "subgroup_matrix_transbatch_b_f32": ["matmul-subgroup-matrix-ext.wgsl.jinja"],
28
+ "f32_m1_gemv_vec4": ["matmul-vector-matrix-vec4.wgsl.jinja"],
29
+ "rank2_band_vec4_splitk": ["matmul-band-vec4.wgsl.jinja", "reduce-axis0-splitk-combine.wgsl.jinja"],
30
+ "rank2_band_vec4": ["matmul-band-vec4.wgsl.jinja"],
31
+ "rank2_band_vec4_f32_preferred": ["matmul-band-vec4.wgsl.jinja"],
32
+ "subgroup_matrix_splitk": ["matmul-subgroup-matrix-ext.wgsl.jinja", "reduce-axis0-splitk-combine.wgsl.jinja"],
33
+ "subgroup_matrix_tail_broadcast": ["matmul-subgroup-matrix-ext.wgsl.jinja"],
34
+ "subgroup_matrix": ["fused-matmul-subgroup-matrix.wgsl.jinja"],
35
+ "broadcast_rank4_tiled_reg": ["matmul-tiled-general-reg.wgsl.jinja"],
36
+ "plain_rank2_tiled_reg": ["matmul-tiled-general-reg.wgsl.jinja"],
37
+ "transbatch_a_tiled_reg": ["matmul-tiled-general-reg.wgsl.jinja"],
38
+ "tiled": ["matmul-tiled-general.wgsl.jinja"],
39
+ "transbatch_b_tiled_reg": ["matmul-tiled-general-reg.wgsl.jinja"]
40
+ }
41
+ }
42
  }
build/webgpu/reduce-axis0-splitk-combine.wgsl.jinja CHANGED
@@ -2,33 +2,121 @@
2
  // folds the segment partials and applies the selected reduction's final step.
3
  // Segments are folded in ascending order for deterministic results. This order
4
  // differs from the single-pass reduction but remains within the f32 tolerance.
5
- {% if source.outputF16 %}
 
 
 
 
 
6
  enable f16;
7
  {% endif %}
8
  {{ env.wgsl.resourceDeclarations }}
9
- /* Exact max/min reduction identities. WGSL rejects infinity during constant
10
- * evaluation, so f32 identities are constructed at runtime from their
11
- * IEEE-754 bit patterns. */
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
 
14
  const WG: u32 = {{ workgroupSize }}u;
15
  const SPLIT: u32 = {{ split }}u;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  @compute @workgroup_size(WG, 1, 1)
18
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
19
  @builtin(num_workgroups) nwg: vec3<u32>) {
20
  let stride = nwg.x * WG;
21
- let start = (gid.y * nwg.x * WG) + gid.x;
22
  for (var col = start; col < params.cols; col = col + stride) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  var total = 0.0;
 
 
24
  for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
25
  let p = partials[seg * params.cols + col];
 
 
 
 
 
26
  total = total + p;
 
27
  }
28
- {% if source.outputF16 %}
 
 
 
 
 
 
 
 
 
 
29
  y[col] = f16(total);
30
  {% else %}
31
  y[col] = total;
 
 
32
  {% endif %}
33
  }
34
  }
 
2
  // folds the segment partials and applies the selected reduction's final step.
3
  // Segments are folded in ascending order for deterministic results. This order
4
  // differs from the single-pass reduction but remains within the f32 tolerance.
5
+ {% set addBias = addBias is defined and addBias %}
6
+ {% set biasCols = biasCols | default(0) %}
7
+ {% set intMode = intMode is defined and intMode %}
8
+ {% set yv = "f16(" if outputF16 else "" %}
9
+ {% set vy = ")" if outputF16 else "" %}
10
+ {% if outputF16 %}
11
  enable f16;
12
  {% endif %}
13
  {{ env.wgsl.resourceDeclarations }}
14
+ {% macro wgsl_minmax_identity(name, op, scalar="f32") %}
15
+ /* Exact {{ op }} reduction identity. WGSL rejects infinity during constant
16
+ * evaluation, so an f32 identity is constructed from its IEEE-754 bits. */
17
+ {% set logicalBoolIdentity = logicalBool is defined and logicalBool %}
18
+ fn {{ name }}() -> {{ scalar }} {
19
+ {% if scalar == "i32" %}
20
+ return {{ "-2147483647i - 1i" if op == "max" else "2147483647i" }};
21
+ {% elif scalar == "u32" %}
22
+ return {{ "1u" if logicalBoolIdentity and op == "min" else "0u" if op == "max" else "4294967295u" }};
23
+ {% else %}
24
+ var bits = {{ "0xff800000u" if op == "max" else "0x7f800000u" }};
25
+ return bitcast<f32>(bits);
26
+ {% endif %}
27
+ }
28
+ {%- endmacro %}
29
 
30
 
31
  const WG: u32 = {{ workgroupSize }}u;
32
  const SPLIT: u32 = {{ split }}u;
33
+ {% if addBias %}
34
+ const BIAS_COLS: u32 = {{ biasCols }}u;
35
+ {% endif %}
36
+ {% if op == "logsumexp" %}
37
+ const F32_MIN: f32 = -3.4028234663852886e38;
38
+ const F32_MAX: f32 = 3.4028234663852886e38;
39
+
40
+ fn is_nan_f32(value: f32) -> bool {
41
+ let bits = bitcast<u32>(value);
42
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
43
+ }
44
+ {% elif op == "max" or op == "min" %}
45
+ {{ wgsl_minmax_identity("reduction_identity", op) }}
46
+ {% endif %}
47
 
48
  @compute @workgroup_size(WG, 1, 1)
49
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
50
  @builtin(num_workgroups) nwg: vec3<u32>) {
51
  let stride = nwg.x * WG;
52
+ let start = (gid.y * {{ DISPATCH_FOLD_WIDTH }}u * WG) + gid.x;
53
  for (var col = start; col < params.cols; col = col + stride) {
54
+ {% if op == "logsumexp" %}
55
+ // Merge SPLIT (segMax, segSumExp) pairs stably; carry NaN / +Inf markers.
56
+ var nan_value = 0.0;
57
+ var has_nan = false;
58
+ var global_max = F32_MIN;
59
+ for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
60
+ let nv = partials[(2u * SPLIT + seg) * params.cols + col];
61
+ if (nv != 0.0 || is_nan_f32(nv)) {
62
+ has_nan = true;
63
+ nan_value = nv;
64
+ }
65
+ global_max = max(global_max, partials[seg * params.cols + col]);
66
+ }
67
+ var sum = 0.0;
68
+ for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
69
+ let seg_max = partials[seg * params.cols + col];
70
+ let seg_sum = partials[(SPLIT + seg) * params.cols + col];
71
+ sum = sum + seg_sum * exp(seg_max - global_max);
72
+ }
73
+ let has_positive_inf = global_max > F32_MAX;
74
+ let finite_or_inf = select(global_max + log(sum), global_max, has_positive_inf);
75
+ y[col] = {{ yv }}select(finite_or_inf, nan_value, has_nan){{ vy }};
76
+ {% else %}
77
+ {% if intMode %}
78
+ {% if op == "prod" %}
79
+ var total = 1i;
80
+ {% else %}
81
+ var total = 0i;
82
+ {% endif %}
83
+ {% else %}
84
+ {% if op == "max" %}
85
+ var total = reduction_identity();
86
+ {% elif op == "min" %}
87
+ var total = reduction_identity();
88
+ {% elif op == "prod" %}
89
+ var total = 1.0;
90
+ {% else %}
91
  var total = 0.0;
92
+ {% endif %}
93
+ {% endif %}
94
  for (var seg = 0u; seg < SPLIT; seg = seg + 1u) {
95
  let p = partials[seg * params.cols + col];
96
+ {% if op == "max" or op == "min" %}
97
+ total = {{ op }}(total, p);
98
+ {% elif op == "prod" %}
99
+ total = total * p;
100
+ {% else %}
101
  total = total + p;
102
+ {% endif %}
103
  }
104
+ {% if addBias %}
105
+ total = total + f32(bias[col % BIAS_COLS]);
106
+ {% endif %}
107
+ {% if op == "l2" %}
108
+ y[col] = {{ yv }}sqrt(total){{ vy }};
109
+ {% elif op == "logsum" %}
110
+ y[col] = {{ yv }}log(total){{ vy }};
111
+ {% elif op == "mean" %}
112
+ y[col] = {{ yv }}total / f32(params.rows){{ vy }};
113
+ {% else %}
114
+ {% if outputF16 %}
115
  y[col] = f16(total);
116
  {% else %}
117
  y[col] = total;
118
+ {% endif %}
119
+ {% endif %}
120
  {% endif %}
121
  }
122
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
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],
@@ -297,12 +296,13 @@
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": {
@@ -751,7 +751,7 @@
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",
@@ -784,7 +784,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",
@@ -801,7 +801,7 @@
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",
@@ -817,7 +817,7 @@
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",
@@ -833,7 +833,7 @@
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",
@@ -850,7 +850,7 @@
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",
@@ -866,7 +866,7 @@
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",
@@ -931,7 +931,7 @@
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",
@@ -989,7 +989,7 @@
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",
@@ -1009,7 +1009,7 @@
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",
@@ -1029,7 +1029,7 @@
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",
@@ -1069,7 +1069,7 @@
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",
@@ -1111,7 +1111,7 @@
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": {
@@ -1131,7 +1131,7 @@
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": {
@@ -1151,7 +1151,7 @@
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": {
@@ -1171,7 +1171,7 @@
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": {
@@ -1191,7 +1191,7 @@
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": {
@@ -1211,7 +1211,7 @@
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": {
@@ -1231,7 +1231,7 @@
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": {
@@ -1250,7 +1250,7 @@
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": {
@@ -1269,7 +1269,7 @@
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": {
@@ -1288,7 +1288,7 @@
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": {
@@ -1307,7 +1307,7 @@
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": {
@@ -1327,7 +1327,7 @@
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": {
@@ -1467,6 +1467,26 @@
1467
  },
1468
  "outputs": { "Y": { "dtype": "float32", "shape": [16, 128], "tolerance": 0.0002 } }
1469
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1470
  {
1471
  "name": "subgroup_matrix_batched_transB_small_m_f16",
1472
  "attrs": { "transB": 1, "alpha": 0.25 },
@@ -1500,6 +1520,240 @@
1500
  }
1501
  },
1502
  "outputs": { "Y": { "dtype": "float16", "shape": [8, 64], "tolerance": 0.005 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1503
  }
1504
  ]
1505
  }
 
1
  {
 
2
  "fixtureArrays": {
3
  "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],
4
  "ort_float32_rank3_by_rank2_output_Y": [20, 23, 26, 29, 56, 68, 80, 92, 92, 113, 134, 155, 128, 158, 188, 218],
 
296
  "name": "ort_float32_alpha_zero_outputs_zero",
297
  "provenance": {
298
  "source": "onnxruntime/test/contrib_ops/fused_matmul_op_test.cc",
299
+ "test": "FusedMatMulOpTest.DoubleTypeAlphaZero",
300
+ "notes": "Diverges from the upstream test's inputs (inputs.A values [1.0, 2.0, 3.0, 4.0] -> constant 2.0; inputs.B values [5.0, 6.0, 7.0, 8.0] -> constant 3.0); the expected output is recomputed by the CPU reference for the new inputs. A zero alpha scales the whole product away, so no operand value can reach the result and both operands are uniform fills."
301
  },
302
  "attrs": { "alpha": 0 },
303
  "inputs": {
304
+ "A": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "constant", "value": 2.0 } },
305
+ "B": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "constant", "value": 3.0 } }
306
  },
307
  "outputs": {
308
  "Y": {
 
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.002 } }
755
  },
756
  {
757
  "name": "aligned_f16_transB_alpha_64x32",
 
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.003 } }
788
  },
789
  {
790
  "name": "aligned_f16_transA_64x32",
 
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.001 } }
805
  },
806
  {
807
  "name": "aligned_f16_batched_plain_2x64x32x64",
 
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.002 } }
821
  },
822
  {
823
  "name": "f16_rank3_by_broadcast_rank3",
 
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.002 } }
837
  },
838
  {
839
  "name": "aligned_f16_transA_transB_alpha_64x32",
 
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.002 } }
854
  },
855
  {
856
  "name": "subgroup_matrix_m_tail_57_partial_block_f16",
 
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.001 } }
870
  },
871
  {
872
  "name": "subgroup_matrix_m_tail_33_alpha_scaled_f32",
 
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.002 } }
935
  },
936
  {
937
  "name": "f32_decode_gemv_m1_k65_n68_vec4_compact",
 
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.0004 } }
993
  },
994
  {
995
  "name": "subgroup_matrix_broadcast_rank4x3_f16_compact",
 
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.0005 } }
1013
  },
1014
  {
1015
  "name": "broadcast_rank4_tiled_reg_f16_compact",
 
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.0004 } }
1033
  },
1034
  {
1035
  "name": "broadcast_rank4_tiled_reg_shared_f32_compact",
 
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.0004 } }
1073
  },
1074
  {
1075
  "name": "transbatch_a_dense_m_tail_f32_compact",
 
1111
  {
1112
  "name": "subgroup_matrix_kn_tail_f16_offset_alpha_scale_lock",
1113
  "provenance": {
1114
+ "notes": "Offset float16 operands keep each output near `alpha * K * aOffset * bOffset` (about 3.4), making the K=34 reduction tail, N=66 column tail, and `alpha = 0.5` epilogue observable on the subgroup-matrix route."
1115
  },
1116
  "attrs": { "alpha": 0.5 },
1117
  "inputs": {
 
1131
  {
1132
  "name": "subgroup_matrix_broadcast_rank4x3_f16_offset_scale_lock",
1133
  "provenance": {
1134
+ "notes": "Offset operands keep outputs near `K * aOffset * bOffset`, making the per-batch B slice and K=32 contraction observable in a rank-4 by rank-3 broadcast."
1135
  },
1136
  "attrs": { "alpha": 1 },
1137
  "inputs": {
 
1151
  {
1152
  "name": "subgroup_matrix_a_batch_broadcast_rank4x3_f16",
1153
  "provenance": {
1154
+ "notes": "A has batch extent 1 while B has extent 2, so one A slice feeds both output batches. Offset operands keep the expected magnitude nonzero, exposing a swapped or nonzero A batch stride."
1155
  },
1156
  "attrs": { "alpha": 1 },
1157
  "inputs": {
 
1171
  {
1172
  "name": "broadcast_rank4_tiled_reg_f16_offset_alpha_scale_lock",
1173
  "provenance": {
1174
+ "notes": "Offset operands keep outputs near `alpha * K * aOffset * bOffset` with K=33, making the one-element reduction tail and `alpha` multiplier observable on the register-blocked rank-4 route."
1175
  },
1176
  "attrs": { "alpha": 0.5 },
1177
  "inputs": {
 
1191
  {
1192
  "name": "transbatch_a_dense_m_tail_f16_offset_alpha_scale_lock",
1193
  "provenance": {
1194
+ "notes": "Offset operands make each output proportional to `alpha * K`, exposing the [M, batch, K] transBatchA stride, K=32 contraction, and `alpha = 0.5` scale."
1195
  },
1196
  "attrs": { "alpha": 0.5, "transBatchA": 1 },
1197
  "inputs": {
 
1211
  {
1212
  "name": "aligned_f16_transA_transB_alpha_offset_scale_lock",
1213
  "provenance": {
1214
+ "notes": "Offset operands keep the doubly transposed output proportional to `alpha * K`, making the K=32 contraction and `alpha = 0.5` epilogue observable on subgroup-matrix and portable tiled routes."
1215
  },
1216
  "attrs": { "transA": 1, "transB": 1, "alpha": 0.5 },
1217
  "inputs": {
 
1231
  {
1232
  "name": "f16_unaligned_3x5x7_offset_scale_lock",
1233
  "provenance": {
1234
+ "notes": "Offset operands in a 3x5 by 5x7 multiply keep outputs proportional to K, exposing dropped reduction elements or doubled tails on the unaligned scalar and tiled routes."
1235
  },
1236
  "inputs": {
1237
  "A": {
 
1250
  {
1251
  "name": "aligned_f16_plain_64x32x64_offset_scale_lock",
1252
  "provenance": {
1253
+ "notes": "Offset operands keep each fully aligned M=64, K=32, N=64 output proportional to K, making the subgroup-matrix reduction count and scratch drain observable."
1254
  },
1255
  "inputs": {
1256
  "A": {
 
1269
  {
1270
  "name": "aligned_f16_batched_plain_2x64x32x64_offset_scale_lock",
1271
  "provenance": {
1272
+ "notes": "Two batches carry distinct offset operands, keeping outputs proportional to K and making both the batch stride and aligned subgroup-matrix reduction count observable."
1273
  },
1274
  "inputs": {
1275
  "A": {
 
1288
  {
1289
  "name": "subgroup_matrix_m_tail_57_partial_block_f16_offset_scale_lock",
1290
  "provenance": {
1291
+ "notes": "M=57 leaves 25 rows after one full 32-row tile. Offset operands require tail rows to match the full rows' expected magnitude, exposing a short reduction or stale scratch value."
1292
  },
1293
  "inputs": {
1294
  "A": {
 
1307
  {
1308
  "name": "aligned_f16_transA_64x32_offset_scale_lock",
1309
  "provenance": {
1310
+ "notes": "With only A transposed, offset operands keep each output proportional to K and expose both a transposed-A stride error and an incorrect reduction count."
1311
  },
1312
  "attrs": { "transA": 1 },
1313
  "inputs": {
 
1327
  {
1328
  "name": "transA_transB_subgroup_matrix_m_tail_50_f16_offset_scale_lock",
1329
  "provenance": {
1330
+ "notes": "Both operands are transposed and M=50 leaves an 18-row tail. Offset operands make the guarded tail rows' magnitude and placement independently observable."
1331
  },
1332
  "attrs": { "transA": 1, "transB": 1, "alpha": 1 },
1333
  "inputs": {
 
1467
  },
1468
  "outputs": { "Y": { "dtype": "float32", "shape": [16, 128], "tolerance": 0.0002 } }
1469
  },
1470
+ {
1471
+ "name": "band_vec4_alpha_scaled_m8_k256_n512",
1472
+ "provenance": {
1473
+ "notes": "Route lock for the few-row band without a K split: eight rows over four 128-column groups, alpha folded into the vec4 store."
1474
+ },
1475
+ "attrs": { "alpha": 0.5 },
1476
+ "inputs": {
1477
+ "A": {
1478
+ "dtype": "float32",
1479
+ "shape": [8, 256],
1480
+ "data": { "kind": "fillFloat32", "sinStep": 0.07, "cosStep": 0.13, "scale": 0.2 }
1481
+ },
1482
+ "B": {
1483
+ "dtype": "float32",
1484
+ "shape": [256, 512],
1485
+ "data": { "kind": "fillFloat32", "sinStep": 0.05, "cosStep": 0.19, "scale": 0.2 }
1486
+ }
1487
+ },
1488
+ "outputs": { "Y": { "dtype": "float32", "shape": [8, 512], "tolerance": 0.0001 } }
1489
+ },
1490
  {
1491
  "name": "subgroup_matrix_batched_transB_small_m_f16",
1492
  "attrs": { "transB": 1, "alpha": 0.25 },
 
1520
  }
1521
  },
1522
  "outputs": { "Y": { "dtype": "float16", "shape": [8, 64], "tolerance": 0.005 } }
1523
+ },
1524
+ {
1525
+ "name": "f32_decode_gemv_m1_k65_n68_alpha_half",
1526
+ "provenance": {
1527
+ "notes": "A compact M=1 GEMV with alpha=0.5 exercises a non-unit multiplier folded into the store as a baked constant. The expected output verifies that the multiplier is applied exactly once."
1528
+ },
1529
+ "attrs": { "alpha": 0.5 },
1530
+ "inputs": {
1531
+ "A": {
1532
+ "dtype": "float32",
1533
+ "shape": [1, 65],
1534
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.2 }
1535
+ },
1536
+ "B": {
1537
+ "dtype": "float32",
1538
+ "shape": [65, 68],
1539
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.2 }
1540
+ }
1541
+ },
1542
+ "outputs": { "Y": { "dtype": "float32", "shape": [1, 68], "tolerance": 0.0002 } }
1543
+ },
1544
+ {
1545
+ "name": "f16_rank4_by_rank2_shared_weight_m33_k34_n66",
1546
+ "provenance": {
1547
+ "notes": "Shared rank-2 B across both rank-4 batch axes; covers subgroup-matrix admission, partial tiles and its small-M fallback without changing accumulation precision."
1548
+ },
1549
+ "attrs": { "alpha": 0.5 },
1550
+ "inputs": {
1551
+ "A": {
1552
+ "dtype": "float16",
1553
+ "shape": [2, 3, 33, 34],
1554
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021 }
1555
+ },
1556
+ "B": {
1557
+ "dtype": "float16",
1558
+ "shape": [34, 66],
1559
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029 }
1560
+ }
1561
+ },
1562
+ "outputs": { "Y": { "dtype": "float16", "shape": [2, 3, 33, 66], "tolerance": 0.001 } }
1563
+ },
1564
+ {
1565
+ "name": "f16_rank4_by_rank2_shared_weight_m8_k32_n64",
1566
+ "provenance": {
1567
+ "notes": "Shared rank-2 B across both rank-4 batch axes; covers subgroup-matrix admission, partial tiles and its small-M fallback without changing accumulation precision."
1568
+ },
1569
+ "attrs": { "alpha": 0.5 },
1570
+ "inputs": {
1571
+ "A": {
1572
+ "dtype": "float16",
1573
+ "shape": [2, 3, 8, 32],
1574
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021 }
1575
+ },
1576
+ "B": {
1577
+ "dtype": "float16",
1578
+ "shape": [32, 64],
1579
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029 }
1580
+ }
1581
+ },
1582
+ "outputs": { "Y": { "dtype": "float16", "shape": [2, 3, 8, 64], "tolerance": 0.001 } }
1583
+ },
1584
+ {
1585
+ "name": "f16_rank4_by_rank2_shared_weight_m1_k32_n64",
1586
+ "provenance": {
1587
+ "notes": "Shared rank-2 B across both rank-4 batch axes; covers subgroup-matrix admission, partial tiles and its small-M fallback without changing accumulation precision."
1588
+ },
1589
+ "attrs": { "alpha": 0.5 },
1590
+ "inputs": {
1591
+ "A": {
1592
+ "dtype": "float16",
1593
+ "shape": [2, 3, 1, 32],
1594
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021 }
1595
+ },
1596
+ "B": {
1597
+ "dtype": "float16",
1598
+ "shape": [32, 64],
1599
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029 }
1600
+ }
1601
+ },
1602
+ "outputs": { "Y": { "dtype": "float16", "shape": [2, 3, 1, 64], "tolerance": 0.001 } }
1603
+ },
1604
+ {
1605
+ "name": "transbatch_b_aligned_occupancy_float16",
1606
+ "attrs": { "transBatchB": 1, "alpha": 0.5 },
1607
+ "inputs": {
1608
+ "A": {
1609
+ "dtype": "float16",
1610
+ "shape": [2, 128, 128],
1611
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.05, "offset": 0.2 }
1612
+ },
1613
+ "B": {
1614
+ "dtype": "float16",
1615
+ "shape": [128, 2, 512],
1616
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.07, "offset": 0.3 }
1617
+ }
1618
+ },
1619
+ "outputs": { "Y": { "dtype": "float16", "shape": [2, 128, 512], "tolerance": 0.001, "relTolerance": 0.001 } }
1620
+ },
1621
+ {
1622
+ "name": "transbatch_b_aligned_occupancy_float32",
1623
+ "attrs": { "transBatchB": 1, "alpha": 0.5 },
1624
+ "inputs": {
1625
+ "A": {
1626
+ "dtype": "float32",
1627
+ "shape": [2, 128, 128],
1628
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.05, "offset": 0.2 }
1629
+ },
1630
+ "B": {
1631
+ "dtype": "float32",
1632
+ "shape": [128, 2, 512],
1633
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.07, "offset": 0.3 }
1634
+ }
1635
+ },
1636
+ "outputs": { "Y": { "dtype": "float32", "shape": [2, 128, 512], "tolerance": 0.0001, "relTolerance": 0.00002 } }
1637
+ },
1638
+ {
1639
+ "name": "transbatch_b_portable_reg_aligned_float16",
1640
+ "attrs": { "transBatchB": 1, "alpha": -0.5 },
1641
+ "inputs": {
1642
+ "A": {
1643
+ "dtype": "float16",
1644
+ "shape": [4, 128, 128],
1645
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.05, "offset": 0.2 }
1646
+ },
1647
+ "B": {
1648
+ "dtype": "float16",
1649
+ "shape": [128, 4, 512],
1650
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.07, "offset": 0.3 }
1651
+ }
1652
+ },
1653
+ "outputs": { "Y": { "dtype": "float16", "shape": [4, 128, 512], "tolerance": 0.001, "relTolerance": 0.001 } },
1654
+ "provenance": {
1655
+ "notes": "Portable register tile transBatchB: physical B[K,batch,N], natural tile count reaches the shared occupancy gate; negative alpha and positive inputs avoid cancellation."
1656
+ }
1657
+ },
1658
+ {
1659
+ "name": "transbatch_b_portable_reg_tails_float16",
1660
+ "attrs": { "transBatchB": 1, "alpha": -0.5 },
1661
+ "inputs": {
1662
+ "A": {
1663
+ "dtype": "float16",
1664
+ "shape": [3, 129, 131],
1665
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.05, "offset": 0.2 }
1666
+ },
1667
+ "B": {
1668
+ "dtype": "float16",
1669
+ "shape": [131, 3, 513],
1670
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.07, "offset": 0.3 }
1671
+ }
1672
+ },
1673
+ "outputs": { "Y": { "dtype": "float16", "shape": [3, 129, 513], "tolerance": 0.001, "relTolerance": 0.001 } },
1674
+ "provenance": {
1675
+ "notes": "Portable register tile transBatchB: physical B[K,batch,N], natural tile count reaches the shared occupancy gate; negative alpha and positive inputs avoid cancellation."
1676
+ }
1677
+ },
1678
+ {
1679
+ "name": "transbatch_b_portable_reg_aligned_float32",
1680
+ "attrs": { "transBatchB": 1, "alpha": -0.5 },
1681
+ "inputs": {
1682
+ "A": {
1683
+ "dtype": "float32",
1684
+ "shape": [4, 128, 128],
1685
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.05, "offset": 0.2 }
1686
+ },
1687
+ "B": {
1688
+ "dtype": "float32",
1689
+ "shape": [128, 4, 512],
1690
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.07, "offset": 0.3 }
1691
+ }
1692
+ },
1693
+ "outputs": { "Y": { "dtype": "float32", "shape": [4, 128, 512], "tolerance": 0.0001, "relTolerance": 0.00002 } },
1694
+ "provenance": {
1695
+ "notes": "Portable register tile transBatchB: physical B[K,batch,N], natural tile count reaches the shared occupancy gate; negative alpha and positive inputs avoid cancellation."
1696
+ }
1697
+ },
1698
+ {
1699
+ "name": "transbatch_b_portable_reg_tails_float32",
1700
+ "attrs": { "transBatchB": 1, "alpha": -0.5 },
1701
+ "inputs": {
1702
+ "A": {
1703
+ "dtype": "float32",
1704
+ "shape": [3, 129, 131],
1705
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.021, "scale": 0.05, "offset": 0.2 }
1706
+ },
1707
+ "B": {
1708
+ "dtype": "float32",
1709
+ "shape": [131, 3, 513],
1710
+ "data": { "kind": "fillFloat32", "sinStep": 0.009, "cosStep": 0.029, "scale": 0.07, "offset": 0.3 }
1711
+ }
1712
+ },
1713
+ "outputs": { "Y": { "dtype": "float32", "shape": [3, 129, 513], "tolerance": 0.0001, "relTolerance": 0.00002 } },
1714
+ "provenance": {
1715
+ "notes": "Portable register tile transBatchB: physical B[K,batch,N], natural tile count reaches the shared occupancy gate; negative alpha and positive inputs avoid cancellation."
1716
+ }
1717
+ },
1718
+ {
1719
+ "name": "f32_band_preferred_m4_k2048_n4096",
1720
+ "attrs": { "alpha": 0.5 },
1721
+ "inputs": {
1722
+ "A": {
1723
+ "dtype": "float32",
1724
+ "shape": [4, 2048],
1725
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.031, "scale": 0.1, "offset": 0.02 }
1726
+ },
1727
+ "B": {
1728
+ "dtype": "float32",
1729
+ "shape": [2048, 4096],
1730
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.041, "scale": 0.1, "offset": 0.03 }
1731
+ }
1732
+ },
1733
+ "outputs": { "Y": { "dtype": "float32", "shape": [4, 4096], "tolerance": 0.0001, "relTolerance": 0.00001 } },
1734
+ "provenance": {
1735
+ "notes": "Natural f32 preferred single-band geometry with nonuniform operands and alpha0.5. N4096 keeps the existing single-band path eligible at deep K; N2048 would select the split-band family."
1736
+ }
1737
+ },
1738
+ {
1739
+ "name": "f32_band_preferred_m16_k2560_n4096",
1740
+ "attrs": { "alpha": 0.5 },
1741
+ "inputs": {
1742
+ "A": {
1743
+ "dtype": "float32",
1744
+ "shape": [16, 2560],
1745
+ "data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.031, "scale": 0.1, "offset": 0.02 }
1746
+ },
1747
+ "B": {
1748
+ "dtype": "float32",
1749
+ "shape": [2560, 4096],
1750
+ "data": { "kind": "fillFloat32", "sinStep": 0.017, "cosStep": 0.041, "scale": 0.1, "offset": 0.03 }
1751
+ }
1752
+ },
1753
+ "outputs": { "Y": { "dtype": "float32", "shape": [16, 4096], "tolerance": 0.0001, "relTolerance": 0.00001 } },
1754
+ "provenance": {
1755
+ "notes": "Natural f32 preferred single-band geometry with nonuniform operands and alpha0.5. N4096 keeps the existing single-band path eligible at deep K; N2048 would select the split-band family."
1756
+ }
1757
  }
1758
  ]
1759
  }