Xenova HF Staff commited on
Commit
d1fe430
·
verified ·
1 Parent(s): 14456f5

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -40,6 +40,16 @@ See the [ONNX Runtime `GemmFastGelu` contrib-operator spec](https://github.com/m
40
 
41
  One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
42
 
 
 
 
 
 
 
 
 
 
 
43
  - `sgmat_direct_bias` — Uses supported subgroup matrices with direct activation loads and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
44
  - `sgmat_direct` — Uses supported subgroup matrices with direct activation loads and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
45
  - `sgmat_direct_bias_f16` — Uses supported subgroup matrices with direct activation loads and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
@@ -48,6 +58,8 @@ One implementation is selected per call from the device capabilities, the reques
48
  - `sgmat` — Uses supported subgroup matrices with staged operands. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
49
  - `sgmat_bias_f16` — Uses supported subgroup matrices with staged operands. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
50
  - `sgmat_f16` — Uses supported subgroup matrices with staged operands. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
 
 
51
 
52
  ## Device requirements
53
 
@@ -58,14 +70,15 @@ Some implementation variants require `subgroup-matrix` and `subgroups`. These ar
58
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
59
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
60
  - [`test.json`](build/webgpu/test.json) — correctness cases
61
- - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
62
- - [`gemm-fast-gelu.wgsl.jinja`](build/webgpu/gemm-fast-gelu.wgsl.jinja)
63
  - [`gemm-subgroup-matrix.wgsl.jinja`](build/webgpu/gemm-subgroup-matrix.wgsl.jinja)
 
64
 
65
  ## Use with `@huggingface/kernels`
66
 
67
  ```sh
68
- npm install --save-exact @huggingface/kernels@0.0.1-preview.2
69
  ```
70
 
71
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
@@ -79,5 +92,5 @@ Replace each `*Data` placeholder with a typed array containing the corresponding
79
  import { getKernel } from "@huggingface/kernels";
80
 
81
  const kernel = await getKernel("webgpu-kernels/com.microsoft.GemmFastGelu", { version: 1 });
82
- const { Y } = await kernel({ X: { data: XData, shape: [5, 6] }, W: { data: WData, shape: [6, 4] } });
83
  ```
 
40
 
41
  One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
42
 
43
+ - `vector_matrix_bias` — Partitions a single-row vector-matrix product across device-bounded workgroup slices. Optional bias and FastGelu are fused into the f32 reduction before the single output cast; no subgroup feature is required.
44
+ - `vector_matrix` — Partitions a single-row vector-matrix product across device-bounded workgroup slices. Optional bias and FastGelu are fused into the f32 reduction before the single output cast; no subgroup feature is required.
45
+ - `sgmat_medium_bias` — Uses supported subgroup matrices with 32x32 direct activation tiles and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
46
+ - `sgmat_medium` — Uses supported subgroup matrices with 32x32 direct activation tiles and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
47
+ - `sgmat_medium_bias_f16` — Uses supported subgroup matrices with 32x32 direct activation tiles and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
48
+ - `sgmat_medium_f16` — Uses supported subgroup matrices with 32x32 direct activation tiles and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
49
+ - `sgmat_small_bias` — Uses supported subgroup matrices with 32x16 direct activation tiles and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
50
+ - `sgmat_small` — Uses supported subgroup matrices with 32x16 direct activation tiles and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
51
+ - `sgmat_small_bias_f16` — Uses supported subgroup matrices with 32x16 direct activation tiles and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
52
+ - `sgmat_small_f16` — Uses supported subgroup matrices with 32x16 direct activation tiles and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
53
  - `sgmat_direct_bias` — Uses supported subgroup matrices with direct activation loads and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
54
  - `sgmat_direct` — Uses supported subgroup matrices with direct activation loads and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
55
  - `sgmat_direct_bias_f16` — Uses supported subgroup matrices with direct activation loads and double-buffered weights. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
 
58
  - `sgmat` — Uses supported subgroup matrices with staged operands. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
59
  - `sgmat_bias_f16` — Uses supported subgroup matrices with staged operands. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
60
  - `sgmat_f16` — Uses supported subgroup matrices with staged operands. Bias and FastGelu run on the f32 accumulator before the single output cast. Resource, alignment and matrix-type guards retain the portable path on other devices.
61
+ - `tiled_bias` — Portable register tiles use the output-grid budget and a device-specific f32 tile choice to improve occupancy, with device-bounded shared padding and compensated f32 tile sums before the fused activation.
62
+ - `tiled` — Portable register tiles use the output-grid budget and a device-specific f32 tile choice to improve occupancy, with device-bounded shared padding and compensated f32 tile sums before the fused activation.
63
 
64
  ## Device requirements
65
 
 
70
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
71
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
72
  - [`test.json`](build/webgpu/test.json) — correctness cases
73
+ - [`bench.json`](build/webgpu/bench.json) — benchmark cases
74
+ - [`gemm-epilogue-tiled-reg.wgsl.jinja`](build/webgpu/gemm-epilogue-tiled-reg.wgsl.jinja)
75
  - [`gemm-subgroup-matrix.wgsl.jinja`](build/webgpu/gemm-subgroup-matrix.wgsl.jinja)
76
+ - [`matmul-vector-matrix-vec4.wgsl.jinja`](build/webgpu/matmul-vector-matrix-vec4.wgsl.jinja)
77
 
78
  ## Use with `@huggingface/kernels`
79
 
80
  ```sh
81
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
82
  ```
83
 
84
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
 
92
  import { getKernel } from "@huggingface/kernels";
93
 
94
  const kernel = await getKernel("webgpu-kernels/com.microsoft.GemmFastGelu", { version: 1 });
95
+ const { Y } = await kernel({ X: { data: XData, shape: [1, 1] }, W: { data: WData, shape: [1, 4] } });
96
  ```
build/webgpu/bench.json CHANGED
The diff for this file is too large to render. See raw diff
 
build/webgpu/{gemm-fast-gelu.wgsl.jinja → gemm-epilogue-tiled-reg.wgsl.jinja} RENAMED
@@ -1,47 +1,56 @@
1
- {% if usesF16 is defined and usesF16 %}enable f16;
2
- {% endif %}{% set OUT = "f16" if (usesF16 is defined and usesF16) else "f32" %}
3
  {{ env.wgsl.resourceDeclarations }}
4
 
5
  // Shape-driven, register-blocked f32 GEMM for all four transpose combinations.
6
- // Runtime M/N/K share one compiled pipeline. A 4x4 thread micro-tile forms a
7
- // 64x64 output tile with BK=16; transpose modes change only load strides and
8
- // keep the stored-contiguous axis coalesced. M/N/K tails are bounds-checked.
 
9
  // Both tiles are indexed by their own output axis and group four K values per
10
  // vector word, so the micro-tile accumulates through dot() and one step reads
11
  // TM + TN words instead of 4 * (TM + TN) scalars.
12
- {% set gemmEpi = gemmEpilogue if gemmEpilogue is defined else "none" %}
13
  fn tanh_safe(x: f32) -> f32 {
 
 
14
  if (x > 10.0) { return 1.0; }
15
  if (x < -10.0) { return -1.0; }
 
 
 
16
  return tanh(x);
17
  }
18
 
19
  fn gelu_tanh(v: f32) -> f32 {
20
  return 0.5 * v * (1.0 + tanh_safe(0.7978845608028654 * (v + 0.044715 * v * v * v)));
21
  }
22
- {% set epiOpen = "gelu_tanh(" if gemmEpi == "fastgelu" else ("fused_act(" if gemmEpi == "activation" else "") %}
23
- {% set epiClose = ")" if gemmEpi != "none" else "" %}
24
- const BK: u32 = 16u;
25
- const BM: u32 = 64u;
26
- const BN: u32 = 64u;
27
- const TM: u32 = 4u; // per-thread micro-tile rows
28
- const TN: u32 = 4u; // per-thread micro-tile cols
 
 
 
 
 
29
 
30
  // Stage tiles at the operands' storage width. For f16 inputs this is
31
  // bit-identical to widening on shared load, keeps f32 FMA registers, and halves
32
  // workgroup storage.
33
  {% set tileT = OUT %}
34
  const K_VECS: u32 = BK / 4u;
35
- var<workgroup> tileA: array<array<vec4<{{ tileT }}>, K_VECS>, BM>; // [64][4] logical A'[m][k/4]
36
- var<workgroup> tileB: array<array<vec4<{{ tileT }}>, K_VECS>, BN>; // [64][4] logical B'[n][k/4]
37
 
38
- @compute @workgroup_size(16, 16, 1)
39
  fn main(
40
  @builtin(workgroup_id) wg: vec3<u32>,
41
  @builtin(local_invocation_id) lid: vec3<u32>,
42
  @builtin(num_workgroups) nwg: vec3<u32>
43
  ) {
44
- let li = lid.y * 16u + lid.x;
45
  let M = params.M;
46
  let N = params.N;
47
  let K = params.K;
@@ -55,46 +64,70 @@ fn main(
55
  var acc: array<f32, TM * TN>;
56
  for (var i: u32 = 0u; i < TM * TN; i = i + 1u) { acc[i] = 0.0; }
57
 
 
 
 
58
  let numTiles = (K + BK - 1u) / BK;
59
  for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
60
  let kBase = kt * BK;
61
  // Cooperative load: one vector word per lane per pass. The lane grid is
62
  // laid out so the axis the operand stores contiguously stays coalesced.
63
- for (var idx: u32 = li; idx < BM * K_VECS; idx = idx + 256u) {
64
  // A stored [M, K]: logical A[m,k] = a[m*K + k]. Coalesce on k (contiguous).
65
  let ar = idx / K_VECS;
66
  let ac4 = idx % K_VECS;
67
  let am = mBase + ar;
68
  let ak = kBase + ac4 * 4u;
69
  var aWord = vec4<{{ tileT }}>({{ tileT }}(0.0));
 
70
  if (am < M) {
 
71
  {% for component in range(4) %}
 
72
  if (ak + {{ component }}u < K) {
 
73
  aWord[{{ component }}u] = {{ tileT }}(a[am * K + ak + {{ component }}u]);
 
74
  }
 
75
  {% endfor %}
 
76
  }
 
77
  tileA[ar][ac4] = aWord;
78
  }
79
- for (var idx: u32 = li; idx < BN * K_VECS; idx = idx + 256u) {
80
  // B stored [K, N]: logical B[k,n] = b[k*N + n]. Coalesce on n (contiguous).
81
  let bc = idx % BN;
82
  let br4 = idx / BN;
83
  let bn = nBase + bc;
84
  let bk = kBase + br4 * 4u;
85
  var bWord = vec4<{{ tileT }}>({{ tileT }}(0.0));
 
86
  if (bn < N) {
 
87
  {% for component in range(4) %}
 
88
  if (bk + {{ component }}u < K) {
 
89
  bWord[{{ component }}u] = {{ tileT }}(b[(bk + {{ component }}u) * N + bn]);
 
90
  }
 
91
  {% endfor %}
 
92
  }
 
93
  tileB[bc][br4] = bWord;
94
  }
95
  workgroupBarrier();
 
 
 
 
96
  {% set regT = "f32" %}{% set regCast = 1 if tileT != "f32" else 0 %}
97
  {% filter indent(8, true) %}
 
98
  let aRow = lid.y * TM;
99
  let bCol = lid.x * TN;
100
  for (var kv: u32 = 0u; kv < BK / 4u; kv = kv + 1u) {
@@ -104,11 +137,21 @@ for (var kv: u32 = 0u; kv < BK / 4u; kv = kv + 1u) {
104
  for (var j: u32 = 0u; j < TN; j = j + 1u) { bv[j] = {% if regCast %}vec4<{{ regT }}>(tileB[bCol + j][kv]){% else %}tileB[bCol + j][kv]{% endif %}; }
105
  for (var i: u32 = 0u; i < TM; i = i + 1u) {
106
  for (var j: u32 = 0u; j < TN; j = j + 1u) {
107
- acc[i * TN + j] = acc[i * TN + j] + dot(av[i], bv[j]);
108
  }
109
  }
110
  }
111
  {% endfilter %}
 
 
 
 
 
 
 
 
 
 
112
  workgroupBarrier();
113
  }
114
 
 
1
+ {% set OUT = "f16" if (usesF16 is defined and usesF16) else "f32" %}
 
2
  {{ env.wgsl.resourceDeclarations }}
3
 
4
  // Shape-driven, register-blocked f32 GEMM for all four transpose combinations.
5
+ // Runtime M/N/K share one compiled pipeline for each tile configuration.
6
+ // Register micro-tiles reuse staged K slices; transpose modes change load strides and
7
+ // keep the stored-contiguous axis coalesced. M/N/K tails are bounds-checked
8
+ // unless an authored dimension proves that its tile has no tail.
9
  // Both tiles are indexed by their own output axis and group four K values per
10
  // vector word, so the micro-tile accumulates through dot() and one step reads
11
  // TM + TN words instead of 4 * (TM + TN) scalars.
 
12
  fn tanh_safe(x: f32) -> f32 {
13
+ // tanh rounds to its saturated value for these tails in f32. Return that
14
+ // value directly, including for infinite input, before invoking the builtin.
15
  if (x > 10.0) { return 1.0; }
16
  if (x < -10.0) { return -1.0; }
17
+ // For tiny |x|, return x directly to preserve its sign and magnitude without
18
+ // relying on backend-specific builtin behavior near zero.
19
+ if (x > -1.0e-4 && x < 1.0e-4) { return x; }
20
  return tanh(x);
21
  }
22
 
23
  fn gelu_tanh(v: f32) -> f32 {
24
  return 0.5 * v * (1.0 + tanh_safe(0.7978845608028654 * (v + 0.044715 * v * v * v)));
25
  }
26
+ {% set epiOpen = "gelu_tanh(" %}
27
+ {% set epiClose = ")" %}
28
+ {% set gemmWgX = ((gemmTileCols / gemmMicroCols) | int) %}
29
+ {% set gemmWgY = ((gemmTileRows / gemmMicroRows) | int) %}
30
+ {% set gemmRowTail = gemmStaticM is not defined or gemmStaticM % gemmTileRows != 0 %}
31
+ {% set gemmColTail = gemmStaticN is not defined or gemmStaticN % gemmTileCols != 0 %}
32
+ {% set gemmKTileTail = gemmStaticK is not defined or gemmStaticK % gemmTileK != 0 %}
33
+ const BK: u32 = {{ gemmTileK }}u;
34
+ const BM: u32 = {{ gemmTileRows }}u;
35
+ const BN: u32 = {{ gemmTileCols }}u;
36
+ const TM: u32 = {{ gemmMicroRows }}u; // per-thread micro-tile rows
37
+ const TN: u32 = {{ gemmMicroCols }}u; // per-thread micro-tile cols
38
 
39
  // Stage tiles at the operands' storage width. For f16 inputs this is
40
  // bit-identical to widening on shared load, keeps f32 FMA registers, and halves
41
  // workgroup storage.
42
  {% set tileT = OUT %}
43
  const K_VECS: u32 = BK / 4u;
44
+ var<workgroup> tileA: array<array<vec4<{{ tileT }}>, K_VECS{% if gemmTilePadding > 0 %} + {{ gemmTilePadding }}u{% endif %}>, BM>; // logical A'[m][k/4]
45
+ var<workgroup> tileB: array<array<vec4<{{ tileT }}>, K_VECS{% if gemmTilePadding > 0 %} + {{ gemmTilePadding }}u{% endif %}>, BN>; // logical B'[n][k/4]
46
 
47
+ @compute @workgroup_size({{ gemmWgX }}, {{ gemmWgY }}, 1)
48
  fn main(
49
  @builtin(workgroup_id) wg: vec3<u32>,
50
  @builtin(local_invocation_id) lid: vec3<u32>,
51
  @builtin(num_workgroups) nwg: vec3<u32>
52
  ) {
53
+ let li = lid.y * {{ gemmWgX }}u + lid.x;
54
  let M = params.M;
55
  let N = params.N;
56
  let K = params.K;
 
64
  var acc: array<f32, TM * TN>;
65
  for (var i: u32 = 0u; i < TM * TN; i = i + 1u) { acc[i] = 0.0; }
66
 
67
+ {% if gemmCompensatedTiles %}
68
+ var accError: array<f32, TM * TN>;
69
+ {% endif %}
70
  let numTiles = (K + BK - 1u) / BK;
71
  for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
72
  let kBase = kt * BK;
73
  // Cooperative load: one vector word per lane per pass. The lane grid is
74
  // laid out so the axis the operand stores contiguously stays coalesced.
75
+ for (var idx: u32 = li; idx < BM * K_VECS; idx = idx + {{ gemmWgX * gemmWgY }}u) {
76
  // A stored [M, K]: logical A[m,k] = a[m*K + k]. Coalesce on k (contiguous).
77
  let ar = idx / K_VECS;
78
  let ac4 = idx % K_VECS;
79
  let am = mBase + ar;
80
  let ak = kBase + ac4 * 4u;
81
  var aWord = vec4<{{ tileT }}>({{ tileT }}(0.0));
82
+ {% if gemmRowTail %}
83
  if (am < M) {
84
+ {% endif %}
85
  {% for component in range(4) %}
86
+ {% if gemmKTileTail %}
87
  if (ak + {{ component }}u < K) {
88
+ {% endif %}
89
  aWord[{{ component }}u] = {{ tileT }}(a[am * K + ak + {{ component }}u]);
90
+ {% if gemmKTileTail %}
91
  }
92
+ {% endif %}
93
  {% endfor %}
94
+ {% if gemmRowTail %}
95
  }
96
+ {% endif %}
97
  tileA[ar][ac4] = aWord;
98
  }
99
+ for (var idx: u32 = li; idx < BN * K_VECS; idx = idx + {{ gemmWgX * gemmWgY }}u) {
100
  // B stored [K, N]: logical B[k,n] = b[k*N + n]. Coalesce on n (contiguous).
101
  let bc = idx % BN;
102
  let br4 = idx / BN;
103
  let bn = nBase + bc;
104
  let bk = kBase + br4 * 4u;
105
  var bWord = vec4<{{ tileT }}>({{ tileT }}(0.0));
106
+ {% if gemmColTail %}
107
  if (bn < N) {
108
+ {% endif %}
109
  {% for component in range(4) %}
110
+ {% if gemmKTileTail %}
111
  if (bk + {{ component }}u < K) {
112
+ {% endif %}
113
  bWord[{{ component }}u] = {{ tileT }}(b[(bk + {{ component }}u) * N + bn]);
114
+ {% if gemmKTileTail %}
115
  }
116
+ {% endif %}
117
  {% endfor %}
118
+ {% if gemmColTail %}
119
  }
120
+ {% endif %}
121
  tileB[bc][br4] = bWord;
122
  }
123
  workgroupBarrier();
124
+ {% if gemmCompensatedTiles %}
125
+ var tileAcc: array<f32, TM * TN>;
126
+ {% endif %}
127
+ {% set regAccumulator = "tileAcc" if gemmCompensatedTiles else "acc" %}
128
  {% set regT = "f32" %}{% set regCast = 1 if tileT != "f32" else 0 %}
129
  {% filter indent(8, true) %}
130
+ {% set regAccumulator = regAccumulator | default("acc") %}
131
  let aRow = lid.y * TM;
132
  let bCol = lid.x * TN;
133
  for (var kv: u32 = 0u; kv < BK / 4u; kv = kv + 1u) {
 
137
  for (var j: u32 = 0u; j < TN; j = j + 1u) { bv[j] = {% if regCast %}vec4<{{ regT }}>(tileB[bCol + j][kv]){% else %}tileB[bCol + j][kv]{% endif %}; }
138
  for (var i: u32 = 0u; i < TM; i = i + 1u) {
139
  for (var j: u32 = 0u; j < TN; j = j + 1u) {
140
+ {{ regAccumulator }}[i * TN + j] = {{ regAccumulator }}[i * TN + j] + dot(av[i], bv[j]);
141
  }
142
  }
143
  }
144
  {% endfilter %}
145
+ {% if gemmCompensatedTiles %}
146
+ for (var i: u32 = 0u; i < TM * TN; i = i + 1u) {
147
+ // Explicit fma preserves the rounded differences in this correction.
148
+ // Plain subtraction can be reassociated away by fast-math backends.
149
+ let corrected = fma(-1.0, accError[i], tileAcc[i]);
150
+ let next = acc[i] + corrected;
151
+ accError[i] = select(0.0, fma(-1.0, corrected, fma(-1.0, acc[i], next)), abs(next) <= 3.4028234663852886e38);
152
+ acc[i] = next;
153
+ }
154
+ {% endif %}
155
  workgroupBarrier();
156
  }
157
 
build/webgpu/gemm-subgroup-matrix.wgsl.jinja CHANGED
@@ -5,7 +5,6 @@ enable subgroup_size_control;
5
  enable chromium_experimental_subgroup_matrix;
6
  diagnostic(off, chromium.subgroup_matrix_uniformity);
7
 
8
-
9
  {{ env.wgsl.resourceDeclarations }}
10
 
11
  {% set operandScalar = mmaScalar %}
@@ -14,18 +13,15 @@ diagnostic(off, chromium.subgroup_matrix_uniformity);
14
  {% set OUT_SCALAR = outScalar if outScalar is defined else yScalar %}
15
  {% set tileMValue = tileM if tileM is defined else 32 %}
16
  {% set tileNValue = tileN if tileN is defined else 64 %}
17
- {% set tileKValue = tileK if tileK is defined else 32 %}
18
- {% set gemmEpi = gemmEpilogue if gemmEpilogue is defined else "none" %}
19
- {% set epiOpen = "gelu_tanh(" if gemmEpi == "fastgelu" else ("fused_act(" if gemmEpi == "activation" else "") %}
20
- {% set epiClose = ")" if gemmEpi != "none" else "" %}
21
- {% set useDirectStore = directStore is defined and directStore and gemmEpi == "none" %}
22
  {% set useDirectAStorage = directAStorage is defined and directAStorage %}
23
  {% set useDoubleBufferedB = doubleBufferedB is defined and doubleBufferedB %}
24
  {% set useBalancedAccumulation = balancedMatrixAccumulation is defined and balancedMatrixAccumulation and useDoubleBufferedB and not useDirectStore and operandScalar == "f32" %}
25
- {% set aTransposed = aTransposedStorage if aTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
26
- {% set bTransposed = bTransposedStorage if bTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
27
- {% set columnTileBaseValue = columnTileBase if columnTileBase is defined else 0 %}
28
- {% set rowTileBaseValue = rowTileBase if rowTileBase is defined else 0 %}
29
  // Four subgroups cover 32xN as a 2x2 grid, or taller tiles as four row bands.
30
  {% if tileMValue > 32 %}
31
  {% set subtileCols = tileNValue %}
@@ -38,7 +34,6 @@ diagnostic(off, chromium.subgroup_matrix_uniformity);
38
  {% set bTilesPerSubgroup = (subtileCols / 8)|int %}
39
  {% set subgroupCount = 4 %}
40
  {% set scratchBanks = subgroupCount * aTilesPerSubgroup * bTilesPerSubgroup %}
41
-
42
  {% macro matrixAccumulators() %}
43
  {% for m in range(aTilesPerSubgroup) %}
44
  {% for n in range(bTilesPerSubgroup) %}
@@ -50,9 +45,7 @@ diagnostic(off, chromium.subgroup_matrix_uniformity);
50
  {% endif %}
51
  {% endfor %}
52
  {% endfor %}
53
-
54
  {% endmacro %}
55
-
56
  {% macro storeMatrixAccumulators() %}
57
  {% for m in range(aTilesPerSubgroup) %}
58
  {% for n in range(bTilesPerSubgroup) %}
@@ -65,8 +58,13 @@ diagnostic(off, chromium.subgroup_matrix_uniformity);
65
  {% endmacro %}
66
 
67
  fn tanh_safe(x: f32) -> f32 {
 
 
68
  if (x > 10.0) { return 1.0; }
69
  if (x < -10.0) { return -1.0; }
 
 
 
70
  return tanh(x);
71
  }
72
 
@@ -75,7 +73,7 @@ fn gelu_tanh(v: f32) -> f32 {
75
  }
76
  const IN_F: u32 = {{ inFeatures }}u;
77
  const OUT_F: u32 = {{ outFeatures }}u;
78
- {% set kLoopEndValue = kLoopEnd if kLoopEnd is defined else ("K_LOOP" if tailSafe is defined and tailSafe else "IN_F") %}
79
  const TILE_COLS: u32 = {{ tileNValue }}u;
80
  const TILE_ROWS: u32 = {{ tileMValue }}u;
81
  const TILE_K: u32 = {{ tileKValue }}u;
@@ -191,40 +189,28 @@ fn main(
191
  {% set matrixAccumulator = matrixAccumulator | default("matC") %}
192
  {% set matrixStepOffset = matrixStepOffset | default(0) %}
193
  {% set matrixStep = "step" if matrixStepOffset == 0 else "(step + " ~ matrixStepOffset ~ "u)" %}
194
- {% set aTransposed = aTransposedStorage if aTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
195
- {% set bTransposed = bTransposedStorage if bTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
196
  {% set directA = directAStorage is defined and directAStorage %}
197
  {% set B_BANK = "b_bank_offset + " if doubleBufferedB is defined and doubleBufferedB else "" %}
198
  {% if directA %}
 
 
199
  // Direct storage guarantees a full aligned M/K tile, so subgroup-matrix
200
  // loads need no guarded workgroup staging.
201
- let matrix_a_offset = (a_global_base + base_A) * IN_F + kidx + {{ matrixStep }};
202
- {% elif aTransposed %}
203
- let matrix_a_offset = {{ matrixStep }} * TILE_ROWS + subtile_idy * SUB_ROWS;
204
  {% else %}
205
  let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + {{ matrixStep }};
206
  {% endif %}
207
  {% for m in range(dynamicATiles) %}
208
  {% if directA %}
209
- var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&x, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * IN_F{% endif %}, IN_F);
210
- {% elif aTransposed %}
211
- var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, col_major>(&tile_A, matrix_a_offset + {{ m * 8 }}u, TILE_ROWS);
212
  {% else %}
213
  var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * TILE_K{% endif %}, TILE_K);
214
  {% endif %}
215
  {% endfor %}
216
 
217
- {% if bTransposed %}
218
  let matrix_b_offset = {{ B_BANK }}{{ matrixStep }} * TILE_COLS + subtile_idx * SUB_COLS;
219
- {% else %}
220
- let matrix_b_offset = {{ B_BANK }}subtile_idx * SUB_COLS * TILE_K + {{ matrixStep }};
221
- {% endif %}
222
  {% for n in range(bTilesPerSubgroup) %}
223
- {% if bTransposed %}
224
  var matB{{ n }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, row_major>(&tile_B, matrix_b_offset + {{ n * 8 }}u, TILE_COLS);
225
- {% else %}
226
- var matB{{ n }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + {{ n * 8 }}u * TILE_K, TILE_K);
227
- {% endif %}
228
  {% endfor %}
229
 
230
  {% for m in range(dynamicATiles) %}
@@ -232,7 +218,6 @@ fn main(
232
  {{ matrixAccumulator }}{{ m }}{{ n }} = subgroupMatrixMultiplyAccumulate(matA{{ m }}, matB{{ n }}, {{ matrixAccumulator }}{{ m }}{{ n }});
233
  {% endfor %}
234
  {% endfor %}
235
-
236
  }
237
  {% endfor %}
238
  {% else %}
@@ -240,40 +225,28 @@ fn main(
240
  {% set matrixAccumulator = matrixAccumulator | default("matC") %}
241
  {% set matrixStepOffset = matrixStepOffset | default(0) %}
242
  {% set matrixStep = "step" if matrixStepOffset == 0 else "(step + " ~ matrixStepOffset ~ "u)" %}
243
- {% set aTransposed = aTransposedStorage if aTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
244
- {% set bTransposed = bTransposedStorage if bTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
245
  {% set directA = directAStorage is defined and directAStorage %}
246
  {% set B_BANK = "b_bank_offset + " if doubleBufferedB is defined and doubleBufferedB else "" %}
247
  {% if directA %}
 
 
248
  // Direct storage guarantees a full aligned M/K tile, so subgroup-matrix
249
  // loads need no guarded workgroup staging.
250
- let matrix_a_offset = (a_global_base + base_A) * IN_F + kidx + {{ matrixStep }};
251
- {% elif aTransposed %}
252
- let matrix_a_offset = {{ matrixStep }} * TILE_ROWS + subtile_idy * SUB_ROWS;
253
  {% else %}
254
  let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + {{ matrixStep }};
255
  {% endif %}
256
  {% for m in range(dynamicATiles) %}
257
  {% if directA %}
258
- var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&x, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * IN_F{% endif %}, IN_F);
259
- {% elif aTransposed %}
260
- var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, col_major>(&tile_A, matrix_a_offset + {{ m * 8 }}u, TILE_ROWS);
261
  {% else %}
262
  var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * TILE_K{% endif %}, TILE_K);
263
  {% endif %}
264
  {% endfor %}
265
 
266
- {% if bTransposed %}
267
  let matrix_b_offset = {{ B_BANK }}{{ matrixStep }} * TILE_COLS + subtile_idx * SUB_COLS;
268
- {% else %}
269
- let matrix_b_offset = {{ B_BANK }}subtile_idx * SUB_COLS * TILE_K + {{ matrixStep }};
270
- {% endif %}
271
  {% for n in range(bTilesPerSubgroup) %}
272
- {% if bTransposed %}
273
  var matB{{ n }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, row_major>(&tile_B, matrix_b_offset + {{ n * 8 }}u, TILE_COLS);
274
- {% else %}
275
- var matB{{ n }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + {{ n * 8 }}u * TILE_K, TILE_K);
276
- {% endif %}
277
  {% endfor %}
278
 
279
  {% for m in range(dynamicATiles) %}
@@ -281,7 +254,6 @@ fn main(
281
  {{ matrixAccumulator }}{{ m }}{{ n }} = subgroupMatrixMultiplyAccumulate(matA{{ m }}, matB{{ n }}, {{ matrixAccumulator }}{{ m }}{{ n }});
282
  {% endfor %}
283
  {% endfor %}
284
-
285
  {% endif %}
286
  }
287
  let next_kidx = kidx + TILE_K;
@@ -305,40 +277,28 @@ fn main(
305
  {% set matrixAccumulator = matrixAccumulator | default("matC") %}
306
  {% set matrixStepOffset = matrixStepOffset | default(0) %}
307
  {% set matrixStep = "step" if matrixStepOffset == 0 else "(step + " ~ matrixStepOffset ~ "u)" %}
308
- {% set aTransposed = aTransposedStorage if aTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
309
- {% set bTransposed = bTransposedStorage if bTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
310
  {% set directA = directAStorage is defined and directAStorage %}
311
  {% set B_BANK = "b_bank_offset + " if doubleBufferedB is defined and doubleBufferedB else "" %}
312
  {% if directA %}
 
 
313
  // Direct storage guarantees a full aligned M/K tile, so subgroup-matrix
314
  // loads need no guarded workgroup staging.
315
- let matrix_a_offset = (a_global_base + base_A) * IN_F + kidx + {{ matrixStep }};
316
- {% elif aTransposed %}
317
- let matrix_a_offset = {{ matrixStep }} * TILE_ROWS + subtile_idy * SUB_ROWS;
318
  {% else %}
319
  let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + {{ matrixStep }};
320
  {% endif %}
321
  {% for m in range(dynamicATiles) %}
322
  {% if directA %}
323
- var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&x, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * IN_F{% endif %}, IN_F);
324
- {% elif aTransposed %}
325
- var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, col_major>(&tile_A, matrix_a_offset + {{ m * 8 }}u, TILE_ROWS);
326
  {% else %}
327
  var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * TILE_K{% endif %}, TILE_K);
328
  {% endif %}
329
  {% endfor %}
330
 
331
- {% if bTransposed %}
332
  let matrix_b_offset = {{ B_BANK }}{{ matrixStep }} * TILE_COLS + subtile_idx * SUB_COLS;
333
- {% else %}
334
- let matrix_b_offset = {{ B_BANK }}subtile_idx * SUB_COLS * TILE_K + {{ matrixStep }};
335
- {% endif %}
336
  {% for n in range(bTilesPerSubgroup) %}
337
- {% if bTransposed %}
338
  var matB{{ n }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, row_major>(&tile_B, matrix_b_offset + {{ n * 8 }}u, TILE_COLS);
339
- {% else %}
340
- var matB{{ n }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, col_major>(&tile_B, matrix_b_offset + {{ n * 8 }}u * TILE_K, TILE_K);
341
- {% endif %}
342
  {% endfor %}
343
 
344
  {% for m in range(dynamicATiles) %}
@@ -346,7 +306,6 @@ fn main(
346
  {{ matrixAccumulator }}{{ m }}{{ n }} = subgroupMatrixMultiplyAccumulate(matA{{ m }}, matB{{ n }}, {{ matrixAccumulator }}{{ m }}{{ n }});
347
  {% endfor %}
348
  {% endfor %}
349
-
350
  }
351
  workgroupBarrier();
352
  }
 
5
  enable chromium_experimental_subgroup_matrix;
6
  diagnostic(off, chromium.subgroup_matrix_uniformity);
7
 
 
8
  {{ env.wgsl.resourceDeclarations }}
9
 
10
  {% set operandScalar = mmaScalar %}
 
13
  {% set OUT_SCALAR = outScalar if outScalar is defined else yScalar %}
14
  {% set tileMValue = tileM if tileM is defined else 32 %}
15
  {% set tileNValue = tileN if tileN is defined else 64 %}
16
+ {% set tileKValue = 32 %}
17
+ {% set epiOpen = "gelu_tanh(" %}
18
+ {% set epiClose = ")" %}
19
+ {% set useDirectStore = false %}
 
20
  {% set useDirectAStorage = directAStorage is defined and directAStorage %}
21
  {% set useDoubleBufferedB = doubleBufferedB is defined and doubleBufferedB %}
22
  {% set useBalancedAccumulation = balancedMatrixAccumulation is defined and balancedMatrixAccumulation and useDoubleBufferedB and not useDirectStore and operandScalar == "f32" %}
23
+ {% set columnTileBaseValue = 0 %}
24
+ {% set rowTileBaseValue = 0 %}
 
 
25
  // Four subgroups cover 32xN as a 2x2 grid, or taller tiles as four row bands.
26
  {% if tileMValue > 32 %}
27
  {% set subtileCols = tileNValue %}
 
34
  {% set bTilesPerSubgroup = (subtileCols / 8)|int %}
35
  {% set subgroupCount = 4 %}
36
  {% set scratchBanks = subgroupCount * aTilesPerSubgroup * bTilesPerSubgroup %}
 
37
  {% macro matrixAccumulators() %}
38
  {% for m in range(aTilesPerSubgroup) %}
39
  {% for n in range(bTilesPerSubgroup) %}
 
45
  {% endif %}
46
  {% endfor %}
47
  {% endfor %}
 
48
  {% endmacro %}
 
49
  {% macro storeMatrixAccumulators() %}
50
  {% for m in range(aTilesPerSubgroup) %}
51
  {% for n in range(bTilesPerSubgroup) %}
 
58
  {% endmacro %}
59
 
60
  fn tanh_safe(x: f32) -> f32 {
61
+ // tanh rounds to its saturated value for these tails in f32. Return that
62
+ // value directly, including for infinite input, before invoking the builtin.
63
  if (x > 10.0) { return 1.0; }
64
  if (x < -10.0) { return -1.0; }
65
+ // For tiny |x|, return x directly to preserve its sign and magnitude without
66
+ // relying on backend-specific builtin behavior near zero.
67
+ if (x > -1.0e-4 && x < 1.0e-4) { return x; }
68
  return tanh(x);
69
  }
70
 
 
73
  }
74
  const IN_F: u32 = {{ inFeatures }}u;
75
  const OUT_F: u32 = {{ outFeatures }}u;
76
+ {% set kLoopEndValue = "IN_F" %}
77
  const TILE_COLS: u32 = {{ tileNValue }}u;
78
  const TILE_ROWS: u32 = {{ tileMValue }}u;
79
  const TILE_K: u32 = {{ tileKValue }}u;
 
189
  {% set matrixAccumulator = matrixAccumulator | default("matC") %}
190
  {% set matrixStepOffset = matrixStepOffset | default(0) %}
191
  {% set matrixStep = "step" if matrixStepOffset == 0 else "(step + " ~ matrixStepOffset ~ "u)" %}
 
 
192
  {% set directA = directAStorage is defined and directAStorage %}
193
  {% set B_BANK = "b_bank_offset + " if doubleBufferedB is defined and doubleBufferedB else "" %}
194
  {% if directA %}
195
+ {% set matrixAStorage = matrixAStorage | default("x") %}
196
+ {% set matrixAStride = matrixAStride | default("IN_F") %}
197
  // Direct storage guarantees a full aligned M/K tile, so subgroup-matrix
198
  // loads need no guarded workgroup staging.
199
+ let matrix_a_offset = (a_global_base + base_A) * {{ matrixAStride }} + kidx + {{ matrixStep }};
 
 
200
  {% else %}
201
  let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + {{ matrixStep }};
202
  {% endif %}
203
  {% for m in range(dynamicATiles) %}
204
  {% if directA %}
205
+ var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&{{ matrixAStorage }}, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * {{ matrixAStride }}{% endif %}, {{ matrixAStride }});
 
 
206
  {% else %}
207
  var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * TILE_K{% endif %}, TILE_K);
208
  {% endif %}
209
  {% endfor %}
210
 
 
211
  let matrix_b_offset = {{ B_BANK }}{{ matrixStep }} * TILE_COLS + subtile_idx * SUB_COLS;
 
 
 
212
  {% for n in range(bTilesPerSubgroup) %}
 
213
  var matB{{ n }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, row_major>(&tile_B, matrix_b_offset + {{ n * 8 }}u, TILE_COLS);
 
 
 
214
  {% endfor %}
215
 
216
  {% for m in range(dynamicATiles) %}
 
218
  {{ matrixAccumulator }}{{ m }}{{ n }} = subgroupMatrixMultiplyAccumulate(matA{{ m }}, matB{{ n }}, {{ matrixAccumulator }}{{ m }}{{ n }});
219
  {% endfor %}
220
  {% endfor %}
 
221
  }
222
  {% endfor %}
223
  {% else %}
 
225
  {% set matrixAccumulator = matrixAccumulator | default("matC") %}
226
  {% set matrixStepOffset = matrixStepOffset | default(0) %}
227
  {% set matrixStep = "step" if matrixStepOffset == 0 else "(step + " ~ matrixStepOffset ~ "u)" %}
 
 
228
  {% set directA = directAStorage is defined and directAStorage %}
229
  {% set B_BANK = "b_bank_offset + " if doubleBufferedB is defined and doubleBufferedB else "" %}
230
  {% if directA %}
231
+ {% set matrixAStorage = matrixAStorage | default("x") %}
232
+ {% set matrixAStride = matrixAStride | default("IN_F") %}
233
  // Direct storage guarantees a full aligned M/K tile, so subgroup-matrix
234
  // loads need no guarded workgroup staging.
235
+ let matrix_a_offset = (a_global_base + base_A) * {{ matrixAStride }} + kidx + {{ matrixStep }};
 
 
236
  {% else %}
237
  let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + {{ matrixStep }};
238
  {% endif %}
239
  {% for m in range(dynamicATiles) %}
240
  {% if directA %}
241
+ var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&{{ matrixAStorage }}, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * {{ matrixAStride }}{% endif %}, {{ matrixAStride }});
 
 
242
  {% else %}
243
  var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * TILE_K{% endif %}, TILE_K);
244
  {% endif %}
245
  {% endfor %}
246
 
 
247
  let matrix_b_offset = {{ B_BANK }}{{ matrixStep }} * TILE_COLS + subtile_idx * SUB_COLS;
 
 
 
248
  {% for n in range(bTilesPerSubgroup) %}
 
249
  var matB{{ n }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, row_major>(&tile_B, matrix_b_offset + {{ n * 8 }}u, TILE_COLS);
 
 
 
250
  {% endfor %}
251
 
252
  {% for m in range(dynamicATiles) %}
 
254
  {{ matrixAccumulator }}{{ m }}{{ n }} = subgroupMatrixMultiplyAccumulate(matA{{ m }}, matB{{ n }}, {{ matrixAccumulator }}{{ m }}{{ n }});
255
  {% endfor %}
256
  {% endfor %}
 
257
  {% endif %}
258
  }
259
  let next_kidx = kidx + TILE_K;
 
277
  {% set matrixAccumulator = matrixAccumulator | default("matC") %}
278
  {% set matrixStepOffset = matrixStepOffset | default(0) %}
279
  {% set matrixStep = "step" if matrixStepOffset == 0 else "(step + " ~ matrixStepOffset ~ "u)" %}
 
 
280
  {% set directA = directAStorage is defined and directAStorage %}
281
  {% set B_BANK = "b_bank_offset + " if doubleBufferedB is defined and doubleBufferedB else "" %}
282
  {% if directA %}
283
+ {% set matrixAStorage = matrixAStorage | default("x") %}
284
+ {% set matrixAStride = matrixAStride | default("IN_F") %}
285
  // Direct storage guarantees a full aligned M/K tile, so subgroup-matrix
286
  // loads need no guarded workgroup staging.
287
+ let matrix_a_offset = (a_global_base + base_A) * {{ matrixAStride }} + kidx + {{ matrixStep }};
 
 
288
  {% else %}
289
  let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + {{ matrixStep }};
290
  {% endif %}
291
  {% for m in range(dynamicATiles) %}
292
  {% if directA %}
293
+ var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&{{ matrixAStorage }}, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * {{ matrixAStride }}{% endif %}, {{ matrixAStride }});
 
 
294
  {% else %}
295
  var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>, row_major>(&tile_A, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * TILE_K{% endif %}, TILE_K);
296
  {% endif %}
297
  {% endfor %}
298
 
 
299
  let matrix_b_offset = {{ B_BANK }}{{ matrixStep }} * TILE_COLS + subtile_idx * SUB_COLS;
 
 
 
300
  {% for n in range(bTilesPerSubgroup) %}
 
301
  var matB{{ n }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>, row_major>(&tile_B, matrix_b_offset + {{ n * 8 }}u, TILE_COLS);
 
 
 
302
  {% endfor %}
303
 
304
  {% for m in range(dynamicATiles) %}
 
306
  {{ matrixAccumulator }}{{ m }}{{ n }} = subgroupMatrixMultiplyAccumulate(matA{{ m }}, matB{{ n }}, {{ matrixAccumulator }}{{ m }}{{ n }});
307
  {% endfor %}
308
  {% endfor %}
 
309
  }
310
  workgroupBarrier();
311
  }
build/webgpu/manifest.json CHANGED
@@ -10,13 +10,21 @@
10
  "outputs": { "Y": { "dtype": "T", "rank": "ranks.X", "shape": "matmulShape(shapes.X, shapes.W)" } },
11
  "typeConstraints": { "T": ["float32", "float16"] },
12
  "derive": {
 
 
 
13
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
14
  "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
15
  "canPinSubgroupSize32": "device.features.has(\"subgroups\") and device.features.has(\"subgroup-size-control\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize <= 32 and device.adapterInfo.subgroupMaxSize >= 32",
16
  "pinSubgroupSize32": "canPinSubgroupSize32 and not wave32Adapter",
17
  "wave32Effective": "wave32Adapter or pinSubgroupSize32",
18
- "kDim": "dim(shapes.W, 0)",
19
- "nDim": "dim(shapes.W, 1)",
 
 
 
 
 
20
  "gfgRows": "numel(shapes.X) / kDim if kDim > 0 else 0",
21
  "shapeOk": "ranks.W == 2 and ranks.X >= 2 and kDim > 0 and nDim > 0 and dim(shapes.X, ranks.X - 1) == kDim and ranks.Y == ranks.X and dim(shapes.Y, ranks.Y - 1) == nDim and numel(shapes.Y) == gfgRows * nDim",
22
  "dtypeOk": "(tensorDtypes.X == \"float32\" or tensorDtypes.X == \"float16\") and tensorDtypes.W == tensorDtypes.X and tensorDtypes.Y == tensorDtypes.X and f16Ok(dtypes.T)",
@@ -30,24 +38,416 @@
30
  "sgmatLayoutOk": "gfgRows > 0 and kDim % 32 == 0 and nDim % 64 == 0 and ((tensorDtypes.X == \"float16\" and device.features.has(\"shader-f16\") and gfgRows >= 2) or (tensorDtypes.X == \"float32\" and gfgRows >= 32)) and ceilDiv(gfgRows, 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(nDim, 64) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
31
  "sgmatContract": "wave32Effective and sgmatLayoutOk and sgmatResourcesFit",
32
  "usesF16": "tensorDtypes.X == \"float16\"",
33
- "transA": false,
34
  "transB": false,
35
- "gemmEpilogue": "\"fastgelu\"",
36
  "sgmatDirectRows": 64,
37
  "sgmatDirectCols": 32,
38
  "sgmatDirectStorageBytes": "2 * sgmatDirectCols * 32 * sgmatOperandBytes + sgmatDirectRows * sgmatDirectCols * 4",
39
- "sgmatDirectContract": "wave32Effective and gfgRows > 0 and gfgRows % sgmatDirectRows == 0 and kDim % 32 == 0 and nDim % sgmatDirectCols == 0 and deviceWorkgroupCap >= 128 and sgmatDirectStorageBytes <= device.limits.maxComputeWorkgroupStorageSize and ceilDiv(gfgRows, sgmatDirectRows) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(nDim, sgmatDirectCols) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  },
41
  "bindings": {
42
- "x": { "arg": "X", "buffer": "read-only-storage", "elementType": "$xScalar" },
43
- "w": { "arg": "W", "buffer": "read-only-storage", "elementType": "$wScalar" },
44
- "y": { "arg": "Y", "buffer": "storage", "elementType": "$yScalar" },
45
- "params": { "buffer": "uniform", "struct": [{ "name": "M", "type": "u32", "value": "gfgRows" }] },
46
- "a": { "arg": "X", "buffer": "read-only-storage", "elementType": "$T" },
47
- "b": { "arg": "W", "buffer": "read-only-storage", "elementType": "$T" },
48
- "y_2": { "arg": "Y", "name": "y", "buffer": "storage", "elementType": "$T" }
49
  },
50
  "variants": [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  {
52
  "id": "sgmat_direct_bias",
53
  "priority": 110,
@@ -58,7 +458,6 @@
58
  },
59
  "derive": {
60
  "hasBias": true,
61
- "bTransposedStorage": true,
62
  "xScalar": "\"f32\"",
63
  "wScalar": "\"f32\"",
64
  "bScalar": "\"f32\"",
@@ -68,7 +467,6 @@
68
  "outFeatures": "nDim",
69
  "tileM": "sgmatDirectRows",
70
  "tileN": "sgmatDirectCols",
71
- "tileK": 32,
72
  "directAStorage": true,
73
  "doubleBufferedB": true,
74
  "balancedMatrixAccumulation": true
@@ -76,7 +474,7 @@
76
  "passes": [
77
  {
78
  "id": "main",
79
- "name": "GemmFastGelu.sgmat_direct_bias",
80
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
81
  "bindings": ["x", "w", { "arg": "bias", "elementType": "$bScalar" }, "y", "params"],
82
  "dispatch": {
@@ -85,7 +483,8 @@
85
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
86
  }
87
  }
88
- ]
 
89
  },
90
  {
91
  "id": "sgmat_direct",
@@ -97,7 +496,6 @@
97
  },
98
  "derive": {
99
  "hasBias": false,
100
- "bTransposedStorage": true,
101
  "xScalar": "\"f32\"",
102
  "wScalar": "\"f32\"",
103
  "bScalar": "\"f32\"",
@@ -107,7 +505,6 @@
107
  "outFeatures": "nDim",
108
  "tileM": "sgmatDirectRows",
109
  "tileN": "sgmatDirectCols",
110
- "tileK": 32,
111
  "directAStorage": true,
112
  "doubleBufferedB": true,
113
  "balancedMatrixAccumulation": true
@@ -115,7 +512,7 @@
115
  "passes": [
116
  {
117
  "id": "main",
118
- "name": "GemmFastGelu.sgmat_direct",
119
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
120
  "bindings": ["x", "w", "y", "params"],
121
  "dispatch": {
@@ -124,7 +521,8 @@
124
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
125
  }
126
  }
127
- ]
 
128
  },
129
  {
130
  "id": "sgmat_direct_bias_f16",
@@ -136,7 +534,6 @@
136
  },
137
  "derive": {
138
  "hasBias": true,
139
- "bTransposedStorage": true,
140
  "xScalar": "\"f16\"",
141
  "wScalar": "\"f16\"",
142
  "bScalar": "\"f16\"",
@@ -146,7 +543,6 @@
146
  "outFeatures": "nDim",
147
  "tileM": "sgmatDirectRows",
148
  "tileN": "sgmatDirectCols",
149
- "tileK": 32,
150
  "directAStorage": true,
151
  "doubleBufferedB": true,
152
  "balancedMatrixAccumulation": false
@@ -154,7 +550,7 @@
154
  "passes": [
155
  {
156
  "id": "main",
157
- "name": "GemmFastGelu.sgmat_direct_bias_f16",
158
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
159
  "bindings": ["x", "w", { "arg": "bias", "elementType": "$bScalar" }, "y", "params"],
160
  "dispatch": {
@@ -163,7 +559,8 @@
163
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
164
  }
165
  }
166
- ]
 
167
  },
168
  {
169
  "id": "sgmat_direct_f16",
@@ -175,7 +572,6 @@
175
  },
176
  "derive": {
177
  "hasBias": false,
178
- "bTransposedStorage": true,
179
  "xScalar": "\"f16\"",
180
  "wScalar": "\"f16\"",
181
  "bScalar": "\"f16\"",
@@ -185,7 +581,6 @@
185
  "outFeatures": "nDim",
186
  "tileM": "sgmatDirectRows",
187
  "tileN": "sgmatDirectCols",
188
- "tileK": 32,
189
  "directAStorage": true,
190
  "doubleBufferedB": true,
191
  "balancedMatrixAccumulation": false
@@ -193,7 +588,7 @@
193
  "passes": [
194
  {
195
  "id": "main",
196
- "name": "GemmFastGelu.sgmat_direct_f16",
197
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
198
  "bindings": ["x", "w", "y", "params"],
199
  "dispatch": {
@@ -202,7 +597,8 @@
202
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
203
  }
204
  }
205
- ]
 
206
  },
207
  {
208
  "id": "sgmat_bias",
@@ -214,7 +610,6 @@
214
  },
215
  "derive": {
216
  "hasBias": true,
217
- "bTransposedStorage": true,
218
  "xScalar": "\"f32\"",
219
  "wScalar": "\"f32\"",
220
  "bScalar": "\"f32\"",
@@ -224,7 +619,6 @@
224
  "outFeatures": "nDim",
225
  "tileM": 32,
226
  "tileN": 64,
227
- "tileK": 32,
228
  "directAStorage": false,
229
  "doubleBufferedB": false,
230
  "balancedMatrixAccumulation": false
@@ -232,7 +626,7 @@
232
  "passes": [
233
  {
234
  "id": "main",
235
- "name": "GemmFastGelu.sgmat_bias",
236
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
237
  "bindings": ["x", "w", { "arg": "bias", "elementType": "$bScalar" }, "y", "params"],
238
  "dispatch": {
@@ -241,7 +635,8 @@
241
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
242
  }
243
  }
244
- ]
 
245
  },
246
  {
247
  "id": "sgmat",
@@ -253,7 +648,6 @@
253
  },
254
  "derive": {
255
  "hasBias": false,
256
- "bTransposedStorage": true,
257
  "xScalar": "\"f32\"",
258
  "wScalar": "\"f32\"",
259
  "bScalar": "\"f32\"",
@@ -263,7 +657,6 @@
263
  "outFeatures": "nDim",
264
  "tileM": 32,
265
  "tileN": 64,
266
- "tileK": 32,
267
  "directAStorage": false,
268
  "doubleBufferedB": false,
269
  "balancedMatrixAccumulation": false
@@ -271,7 +664,7 @@
271
  "passes": [
272
  {
273
  "id": "main",
274
- "name": "GemmFastGelu.sgmat",
275
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
276
  "bindings": ["x", "w", "y", "params"],
277
  "dispatch": {
@@ -280,7 +673,8 @@
280
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
281
  }
282
  }
283
- ]
 
284
  },
285
  {
286
  "id": "sgmat_bias_f16",
@@ -292,7 +686,6 @@
292
  },
293
  "derive": {
294
  "hasBias": true,
295
- "bTransposedStorage": true,
296
  "xScalar": "\"f16\"",
297
  "wScalar": "\"f16\"",
298
  "bScalar": "\"f16\"",
@@ -302,7 +695,6 @@
302
  "outFeatures": "nDim",
303
  "tileM": 32,
304
  "tileN": 64,
305
- "tileK": 32,
306
  "directAStorage": false,
307
  "doubleBufferedB": false,
308
  "balancedMatrixAccumulation": false
@@ -310,7 +702,7 @@
310
  "passes": [
311
  {
312
  "id": "main",
313
- "name": "GemmFastGelu.sgmat_bias_f16",
314
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
315
  "bindings": ["x", "w", { "arg": "bias", "elementType": "$bScalar" }, "y", "params"],
316
  "dispatch": {
@@ -319,7 +711,8 @@
319
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
320
  }
321
  }
322
- ]
 
323
  },
324
  {
325
  "id": "sgmat_f16",
@@ -331,7 +724,6 @@
331
  },
332
  "derive": {
333
  "hasBias": false,
334
- "bTransposedStorage": true,
335
  "xScalar": "\"f16\"",
336
  "wScalar": "\"f16\"",
337
  "bScalar": "\"f16\"",
@@ -341,7 +733,6 @@
341
  "outFeatures": "nDim",
342
  "tileM": 32,
343
  "tileN": 64,
344
- "tileK": 32,
345
  "directAStorage": false,
346
  "doubleBufferedB": false,
347
  "balancedMatrixAccumulation": false
@@ -349,7 +740,7 @@
349
  "passes": [
350
  {
351
  "id": "main",
352
- "name": "GemmFastGelu.sgmat_f16",
353
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
354
  "bindings": ["x", "w", "y", "params"],
355
  "dispatch": {
@@ -358,7 +749,8 @@
358
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
359
  }
360
  }
361
- ]
 
362
  },
363
  {
364
  "id": "tiled_bias",
@@ -369,12 +761,12 @@
369
  {
370
  "id": "main",
371
  "name": "GemmFastGelu.Bias",
372
- "shader": "gemm-fast-gelu.wgsl.jinja",
373
  "bindings": [
374
  "a",
375
  "b",
376
  "bias",
377
- "y_2",
378
  {
379
  "name": "params",
380
  "struct": [
@@ -387,8 +779,8 @@
387
  }
388
  ],
389
  "dispatch": {
390
- "x": "min(ceilDiv(nDim, 64), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
391
- "y": "min(ceilDiv(gfgRows, 64), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))"
392
  }
393
  }
394
  ]
@@ -402,11 +794,11 @@
402
  {
403
  "id": "main",
404
  "name": "GemmFastGelu",
405
- "shader": "gemm-fast-gelu.wgsl.jinja",
406
  "bindings": [
407
  "a",
408
  "b",
409
- "y_2",
410
  {
411
  "name": "params",
412
  "struct": [
@@ -418,11 +810,19 @@
418
  }
419
  ],
420
  "dispatch": {
421
- "x": "min(ceilDiv(nDim, 64), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
422
- "y": "min(ceilDiv(gfgRows, 64), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))"
423
  }
424
  }
425
  ]
426
  }
427
- ]
 
 
 
 
 
 
 
 
428
  }
 
10
  "outputs": { "Y": { "dtype": "T", "rank": "ranks.X", "shape": "matmulShape(shapes.X, shapes.W)" } },
11
  "typeConstraints": { "T": ["float32", "float16"] },
12
  "derive": {
13
+ "kDim": "dim(shapes.W, 0)",
14
+ "nDim": "dim(shapes.W, 1)",
15
+ "gemvN": "nDim",
16
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
17
  "wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
18
  "canPinSubgroupSize32": "device.features.has(\"subgroups\") and device.features.has(\"subgroup-size-control\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize <= 32 and device.adapterInfo.subgroupMaxSize >= 32",
19
  "pinSubgroupSize32": "canPinSubgroupSize32 and not wave32Adapter",
20
  "wave32Effective": "wave32Adapter or pinSubgroupSize32",
21
+ "variableSubgroup16To32": "device.features.has(\"subgroups\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 16 and device.adapterInfo.subgroupMaxSize == 32",
22
+ "gemvLanes": 32,
23
+ "vec4OutputTile": "4 * gemvLanes",
24
+ "gemvWorkgroups": "ceilDiv(gemvN, vec4OutputTile)",
25
+ "gemvSliceCap": "min(32, device.limits.maxComputeWorkgroupSizeY, floor(device.limits.maxComputeInvocationsPerWorkgroup / gemvLanes), floor(device.limits.maxComputeWorkgroupStorageSize / (16 * gemvLanes)))",
26
+ "gemvSlices": "max(1, min(gemvSliceCap, max(8, pow2ceil(ceilDiv(tunables.GEMV_TARGET_BLOCKS, gemvWorkgroups)))))",
27
+ "gemvResourcesFit": "gemvLanes <= device.limits.maxComputeWorkgroupSizeX and gemvSlices <= device.limits.maxComputeWorkgroupSizeY and gemvLanes * gemvSlices <= device.limits.maxComputeInvocationsPerWorkgroup and 16 * gemvLanes * gemvSlices <= device.limits.maxComputeWorkgroupStorageSize",
28
  "gfgRows": "numel(shapes.X) / kDim if kDim > 0 else 0",
29
  "shapeOk": "ranks.W == 2 and ranks.X >= 2 and kDim > 0 and nDim > 0 and dim(shapes.X, ranks.X - 1) == kDim and ranks.Y == ranks.X and dim(shapes.Y, ranks.Y - 1) == nDim and numel(shapes.Y) == gfgRows * nDim",
30
  "dtypeOk": "(tensorDtypes.X == \"float32\" or tensorDtypes.X == \"float16\") and tensorDtypes.W == tensorDtypes.X and tensorDtypes.Y == tensorDtypes.X and f16Ok(dtypes.T)",
 
38
  "sgmatLayoutOk": "gfgRows > 0 and kDim % 32 == 0 and nDim % 64 == 0 and ((tensorDtypes.X == \"float16\" and device.features.has(\"shader-f16\") and gfgRows >= 2) or (tensorDtypes.X == \"float32\" and gfgRows >= 32)) and ceilDiv(gfgRows, 32) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(nDim, 64) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
39
  "sgmatContract": "wave32Effective and sgmatLayoutOk and sgmatResourcesFit",
40
  "usesF16": "tensorDtypes.X == \"float16\"",
 
41
  "transB": false,
 
42
  "sgmatDirectRows": 64,
43
  "sgmatDirectCols": 32,
44
  "sgmatDirectStorageBytes": "2 * sgmatDirectCols * 32 * sgmatOperandBytes + sgmatDirectRows * sgmatDirectCols * 4",
45
+ "sgmatDirectContract": "wave32Effective and gfgRows > 0 and gfgRows % sgmatDirectRows == 0 and kDim % 32 == 0 and nDim % sgmatDirectCols == 0 and deviceWorkgroupCap >= 128 and sgmatDirectStorageBytes <= device.limits.maxComputeWorkgroupStorageSize and ceilDiv(gfgRows, sgmatDirectRows) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(nDim, sgmatDirectCols) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
46
+ "sgmatOutputGroups": "ceilDiv(gfgRows, sgmatDirectRows) * ceilDiv(nDim, sgmatDirectCols)",
47
+ "sgmatSmallRows": 32,
48
+ "sgmatSmallCols": 16,
49
+ "sgmatSmallStorageBytes": "2 * sgmatSmallCols * 32 * sgmatOperandBytes + sgmatSmallRows * sgmatSmallCols * 4",
50
+ "sgmatSmallContract": "wave32Effective and gfgRows > 0 and gfgRows % sgmatSmallRows == 0 and kDim % 32 == 0 and nDim % sgmatSmallCols == 0 and deviceWorkgroupCap >= 128 and sgmatSmallStorageBytes <= device.limits.maxComputeWorkgroupStorageSize and ceilDiv(gfgRows, sgmatSmallRows) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(nDim, sgmatSmallCols) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
51
+ "sgmatMediumRows": 32,
52
+ "sgmatMediumCols": 32,
53
+ "sgmatMediumStorageBytes": "2 * sgmatMediumCols * 32 * sgmatOperandBytes + sgmatMediumRows * sgmatMediumCols * 4",
54
+ "sgmatMediumContract": "wave32Effective and gfgRows > 0 and gfgRows % sgmatMediumRows == 0 and kDim % 32 == 0 and nDim % sgmatMediumCols == 0 and deviceWorkgroupCap >= 128 and sgmatMediumStorageBytes <= device.limits.maxComputeWorkgroupStorageSize and ceilDiv(gfgRows, sgmatMediumRows) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535) and ceilDiv(nDim, sgmatMediumCols) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
55
+ "portableSmallGroups": "ceilDiv(gfgRows, 32) * ceilDiv(nDim, 32)",
56
+ "portableLargeGroups": "ceilDiv(gfgRows, 64) * ceilDiv(nDim, 64)",
57
+ "portableSmallBudget": "tunables.PORTABLE_SMALL_TILE_GROUPS_F16 if usesF16 else tunables.PORTABLE_SMALL_TILE_GROUPS_F32",
58
+ "portableAvoidsPadding": "portableLargeGroups * 3 >= portableSmallGroups",
59
+ "portableXe3LpgF32": "not usesF16 and has(device.adapterInfo, \"vendor\") and device.adapterInfo.vendor == \"intel\" and has(device.adapterInfo, \"architecture\") and device.adapterInfo.architecture == \"xe-3lpg\"",
60
+ "portableFixedWave32NoF16": "wave32Effective and not device.features.has(\"shader-f16\") and gfgRows >= device.adapterInfo.subgroupMaxSize and kDim >= 16 * device.adapterInfo.subgroupMaxSize and portableSmallGroups >= 8 * device.adapterInfo.subgroupMaxSize",
61
+ "portableLargeNoSubgroupGrid": "not usesF16 and not device.features.has(\"subgroups\") and portableSmallGroups >= 4 * deviceWorkgroupCap",
62
+ "gemmTileRows": "32 if portableXe3LpgF32 or portableFixedWave32NoF16 or portableLargeNoSubgroupGrid or portableSmallGroups <= portableSmallBudget * (2 if portableAvoidsPadding else 1) else 64",
63
+ "gemmTileCols": "gemmTileRows",
64
+ "gfgWideMicroPreferred": "(variableSubgroup16To32 or portableFixedWave32NoF16) and (8 if usesF16 else 4) * device.adapterInfo.subgroupMaxSize <= ceilDiv(gfgRows, gemmTileRows) * ceilDiv(nDim, gemmTileCols) and gemmTileRows * gemmTileCols / 8 <= deviceWorkgroupCap and gemmTileCols / 2 <= device.limits.maxComputeWorkgroupSizeX and gemmTileRows / 4 <= device.limits.maxComputeWorkgroupSizeY",
65
+ "gemmMicroRows": "4 if gfgWideMicroPreferred else gemmTileRows / 16",
66
+ "gemmMicroCols": "2 if gfgWideMicroPreferred else gemmTileCols / 16",
67
+ "gemmTileK": "(16 * 16 * 4) / gemmTileRows if usesF16 else (16 if portableLargeNoSubgroupGrid and has(device.adapterInfo, \"vendor\") and device.adapterInfo.vendor == \"intel\" else 32)",
68
+ "gemmTilePadding": "1 if not usesF16 and 2 * gemmTileRows * (gemmTileK + 4) * 4 <= device.limits.maxComputeWorkgroupStorageSize else 0",
69
+ "gemmCompensatedTiles": "not usesF16 and kDim > gemmTileK",
70
+ "gemmStaticM": "gfgRows",
71
+ "gemmStaticN": "nDim",
72
+ "gemmStaticK": "kDim"
73
  },
74
  "bindings": {
75
+ "x": { "arg": "X", "elementType": "$xScalar" },
76
+ "w": { "arg": "W", "elementType": "$wScalar" },
77
+ "y": { "arg": "Y", "elementType": "$yScalar" },
78
+ "params": { "struct": [{ "name": "M", "type": "u32", "value": "gfgRows" }] },
79
+ "a": { "arg": "X", "elementType": "$T" },
80
+ "b": { "arg": "W", "elementType": "$T" },
81
+ "y_t": { "arg": "Y", "name": "y", "elementType": "$T" }
82
  },
83
  "variants": [
84
+ {
85
+ "id": "vector_matrix_bias",
86
+ "priority": 130,
87
+ "when": ["biasContract", "gfgRows == 1", "nDim % 4 == 0", "gemvWorkgroups <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "gemvResourcesFit"],
88
+ "derive": {
89
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
90
+ "outputBuffer": "\"y\"",
91
+ "gemvBias": true,
92
+ "unrollK2": "usesF16"
93
+ },
94
+ "passes": [
95
+ {
96
+ "id": "main",
97
+ "name": "GemmFastGelu.VectorMatrixBias",
98
+ "shader": "matmul-vector-matrix-vec4.wgsl.jinja",
99
+ "bindings": [
100
+ "a",
101
+ { "name": "b", "arg": "W", "elementType": "$vectorScalar" },
102
+ { "arg": "bias", "elementType": "$vectorScalar" },
103
+ { "name": "y", "arg": "Y", "elementType": "$vectorScalar" },
104
+ {
105
+ "name": "params",
106
+ "struct": [
107
+ { "name": "K", "type": "u32", "value": "kDim" },
108
+ { "name": "N4", "type": "u32", "value": "nDim / 4" }
109
+ ]
110
+ }
111
+ ],
112
+ "dispatch": { "x": "gemvWorkgroups" }
113
+ }
114
+ ]
115
+ },
116
+ {
117
+ "id": "vector_matrix",
118
+ "priority": 130,
119
+ "when": ["noBiasContract", "gfgRows == 1", "nDim % 4 == 0", "gemvWorkgroups <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)", "gemvResourcesFit"],
120
+ "derive": {
121
+ "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"",
122
+ "outputBuffer": "\"y\"",
123
+ "gemvBias": false,
124
+ "unrollK2": "usesF16"
125
+ },
126
+ "passes": [
127
+ {
128
+ "id": "main",
129
+ "name": "GemmFastGelu.VectorMatrix",
130
+ "shader": "matmul-vector-matrix-vec4.wgsl.jinja",
131
+ "bindings": [
132
+ "a",
133
+ { "name": "b", "arg": "W", "elementType": "$vectorScalar" },
134
+ { "name": "y", "arg": "Y", "elementType": "$vectorScalar" },
135
+ {
136
+ "name": "params",
137
+ "struct": [
138
+ { "name": "K", "type": "u32", "value": "kDim" },
139
+ { "name": "N4", "type": "u32", "value": "nDim / 4" }
140
+ ]
141
+ }
142
+ ],
143
+ "dispatch": { "x": "gemvWorkgroups" }
144
+ }
145
+ ]
146
+ },
147
+ {
148
+ "id": "sgmat_medium_bias",
149
+ "priority": 120,
150
+ "when": ["biasContract", "sgmatMediumContract", "tensorDtypes.X == \"float32\""],
151
+ "requires": {
152
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
153
+ "subgroupMatrixConfigs": [{ "componentType": "f32", "M": 8, "N": 8, "K": 8, "resultComponentType": "f32" }]
154
+ },
155
+ "derive": {
156
+ "hasBias": true,
157
+ "xScalar": "\"f32\"",
158
+ "wScalar": "\"f32\"",
159
+ "bScalar": "\"f32\"",
160
+ "yScalar": "\"f32\"",
161
+ "mmaScalar": "\"f32\"",
162
+ "inFeatures": "kDim",
163
+ "outFeatures": "nDim",
164
+ "tileM": "sgmatMediumRows",
165
+ "tileN": "sgmatMediumCols",
166
+ "directAStorage": true,
167
+ "doubleBufferedB": true,
168
+ "balancedMatrixAccumulation": true
169
+ },
170
+ "passes": [
171
+ {
172
+ "id": "main",
173
+ "name": "GemmFastGelu.SgmatMediumBias",
174
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
175
+ "bindings": ["x", "w", { "arg": "bias", "elementType": "$bScalar" }, "y", "params"],
176
+ "dispatch": {
177
+ "x": "ceilDiv(nDim, tileN)",
178
+ "y": "min(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)",
179
+ "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
180
+ }
181
+ }
182
+ ],
183
+ "demoteWhen": ["sgmatOutputGroups > (tunables.MEDIUM_MATRIX_MAX_GROUPS_F16 if usesF16 else tunables.MEDIUM_MATRIX_MAX_GROUPS_F32) and (sgmatDirectContract or sgmatContract)"]
184
+ },
185
+ {
186
+ "id": "sgmat_medium",
187
+ "priority": 120,
188
+ "when": ["noBiasContract", "sgmatMediumContract", "tensorDtypes.X == \"float32\""],
189
+ "requires": {
190
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
191
+ "subgroupMatrixConfigs": [{ "componentType": "f32", "M": 8, "N": 8, "K": 8, "resultComponentType": "f32" }]
192
+ },
193
+ "derive": {
194
+ "hasBias": false,
195
+ "xScalar": "\"f32\"",
196
+ "wScalar": "\"f32\"",
197
+ "bScalar": "\"f32\"",
198
+ "yScalar": "\"f32\"",
199
+ "mmaScalar": "\"f32\"",
200
+ "inFeatures": "kDim",
201
+ "outFeatures": "nDim",
202
+ "tileM": "sgmatMediumRows",
203
+ "tileN": "sgmatMediumCols",
204
+ "directAStorage": true,
205
+ "doubleBufferedB": true,
206
+ "balancedMatrixAccumulation": true
207
+ },
208
+ "passes": [
209
+ {
210
+ "id": "main",
211
+ "name": "GemmFastGelu.SgmatMedium",
212
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
213
+ "bindings": ["x", "w", "y", "params"],
214
+ "dispatch": {
215
+ "x": "ceilDiv(nDim, tileN)",
216
+ "y": "min(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)",
217
+ "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
218
+ }
219
+ }
220
+ ],
221
+ "demoteWhen": ["sgmatOutputGroups > (tunables.MEDIUM_MATRIX_MAX_GROUPS_F16 if usesF16 else tunables.MEDIUM_MATRIX_MAX_GROUPS_F32) and (sgmatDirectContract or sgmatContract)"]
222
+ },
223
+ {
224
+ "id": "sgmat_medium_bias_f16",
225
+ "priority": 120,
226
+ "when": ["biasContract", "sgmatMediumContract", "tensorDtypes.X == \"float16\""],
227
+ "requires": {
228
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
229
+ "subgroupMatrixConfigs": [{ "componentType": "f16", "M": 8, "N": 8, "K": 8 }]
230
+ },
231
+ "derive": {
232
+ "hasBias": true,
233
+ "xScalar": "\"f16\"",
234
+ "wScalar": "\"f16\"",
235
+ "bScalar": "\"f16\"",
236
+ "yScalar": "\"f16\"",
237
+ "mmaScalar": "\"f16\"",
238
+ "inFeatures": "kDim",
239
+ "outFeatures": "nDim",
240
+ "tileM": "sgmatMediumRows",
241
+ "tileN": "sgmatMediumCols",
242
+ "directAStorage": true,
243
+ "doubleBufferedB": true,
244
+ "balancedMatrixAccumulation": false
245
+ },
246
+ "passes": [
247
+ {
248
+ "id": "main",
249
+ "name": "GemmFastGelu.SgmatMediumBiasF16",
250
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
251
+ "bindings": ["x", "w", { "arg": "bias", "elementType": "$bScalar" }, "y", "params"],
252
+ "dispatch": {
253
+ "x": "ceilDiv(nDim, tileN)",
254
+ "y": "min(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)",
255
+ "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
256
+ }
257
+ }
258
+ ],
259
+ "demoteWhen": ["sgmatOutputGroups > (tunables.MEDIUM_MATRIX_MAX_GROUPS_F16 if usesF16 else tunables.MEDIUM_MATRIX_MAX_GROUPS_F32) and (sgmatDirectContract or sgmatContract)"]
260
+ },
261
+ {
262
+ "id": "sgmat_medium_f16",
263
+ "priority": 120,
264
+ "when": ["noBiasContract", "sgmatMediumContract", "tensorDtypes.X == \"float16\""],
265
+ "requires": {
266
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
267
+ "subgroupMatrixConfigs": [{ "componentType": "f16", "M": 8, "N": 8, "K": 8 }]
268
+ },
269
+ "derive": {
270
+ "hasBias": false,
271
+ "xScalar": "\"f16\"",
272
+ "wScalar": "\"f16\"",
273
+ "bScalar": "\"f16\"",
274
+ "yScalar": "\"f16\"",
275
+ "mmaScalar": "\"f16\"",
276
+ "inFeatures": "kDim",
277
+ "outFeatures": "nDim",
278
+ "tileM": "sgmatMediumRows",
279
+ "tileN": "sgmatMediumCols",
280
+ "directAStorage": true,
281
+ "doubleBufferedB": true,
282
+ "balancedMatrixAccumulation": false
283
+ },
284
+ "passes": [
285
+ {
286
+ "id": "main",
287
+ "name": "GemmFastGelu.SgmatMediumF16",
288
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
289
+ "bindings": ["x", "w", "y", "params"],
290
+ "dispatch": {
291
+ "x": "ceilDiv(nDim, tileN)",
292
+ "y": "min(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)",
293
+ "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
294
+ }
295
+ }
296
+ ],
297
+ "demoteWhen": ["sgmatOutputGroups > (tunables.MEDIUM_MATRIX_MAX_GROUPS_F16 if usesF16 else tunables.MEDIUM_MATRIX_MAX_GROUPS_F32) and (sgmatDirectContract or sgmatContract)"]
298
+ },
299
+ {
300
+ "id": "sgmat_small_bias",
301
+ "priority": 125,
302
+ "when": ["biasContract", "sgmatSmallContract", "tensorDtypes.X == \"float32\""],
303
+ "requires": {
304
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
305
+ "subgroupMatrixConfigs": [{ "componentType": "f32", "M": 8, "N": 8, "K": 8, "resultComponentType": "f32" }]
306
+ },
307
+ "derive": {
308
+ "hasBias": true,
309
+ "xScalar": "\"f32\"",
310
+ "wScalar": "\"f32\"",
311
+ "bScalar": "\"f32\"",
312
+ "yScalar": "\"f32\"",
313
+ "mmaScalar": "\"f32\"",
314
+ "inFeatures": "kDim",
315
+ "outFeatures": "nDim",
316
+ "tileM": "sgmatSmallRows",
317
+ "tileN": "sgmatSmallCols",
318
+ "directAStorage": true,
319
+ "doubleBufferedB": true,
320
+ "balancedMatrixAccumulation": true
321
+ },
322
+ "passes": [
323
+ {
324
+ "id": "main",
325
+ "name": "GemmFastGelu.SgmatSmallBias",
326
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
327
+ "bindings": ["x", "w", { "arg": "bias", "elementType": "$bScalar" }, "y", "params"],
328
+ "dispatch": {
329
+ "x": "ceilDiv(nDim, tileN)",
330
+ "y": "min(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)",
331
+ "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
332
+ }
333
+ }
334
+ ],
335
+ "demoteWhen": ["sgmatOutputGroups > tunables.SMALL_MATRIX_MAX_GROUPS and (sgmatMediumContract or sgmatDirectContract or sgmatContract)"]
336
+ },
337
+ {
338
+ "id": "sgmat_small",
339
+ "priority": 125,
340
+ "when": ["noBiasContract", "sgmatSmallContract", "tensorDtypes.X == \"float32\""],
341
+ "requires": {
342
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
343
+ "subgroupMatrixConfigs": [{ "componentType": "f32", "M": 8, "N": 8, "K": 8, "resultComponentType": "f32" }]
344
+ },
345
+ "derive": {
346
+ "hasBias": false,
347
+ "xScalar": "\"f32\"",
348
+ "wScalar": "\"f32\"",
349
+ "bScalar": "\"f32\"",
350
+ "yScalar": "\"f32\"",
351
+ "mmaScalar": "\"f32\"",
352
+ "inFeatures": "kDim",
353
+ "outFeatures": "nDim",
354
+ "tileM": "sgmatSmallRows",
355
+ "tileN": "sgmatSmallCols",
356
+ "directAStorage": true,
357
+ "doubleBufferedB": true,
358
+ "balancedMatrixAccumulation": true
359
+ },
360
+ "passes": [
361
+ {
362
+ "id": "main",
363
+ "name": "GemmFastGelu.SgmatSmall",
364
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
365
+ "bindings": ["x", "w", "y", "params"],
366
+ "dispatch": {
367
+ "x": "ceilDiv(nDim, tileN)",
368
+ "y": "min(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)",
369
+ "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
370
+ }
371
+ }
372
+ ],
373
+ "demoteWhen": ["sgmatOutputGroups > tunables.SMALL_MATRIX_MAX_GROUPS and (sgmatMediumContract or sgmatDirectContract or sgmatContract)"]
374
+ },
375
+ {
376
+ "id": "sgmat_small_bias_f16",
377
+ "priority": 125,
378
+ "when": ["biasContract", "sgmatSmallContract", "tensorDtypes.X == \"float16\""],
379
+ "requires": {
380
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
381
+ "subgroupMatrixConfigs": [{ "componentType": "f16", "M": 8, "N": 8, "K": 8 }]
382
+ },
383
+ "derive": {
384
+ "hasBias": true,
385
+ "xScalar": "\"f16\"",
386
+ "wScalar": "\"f16\"",
387
+ "bScalar": "\"f16\"",
388
+ "yScalar": "\"f16\"",
389
+ "mmaScalar": "\"f16\"",
390
+ "inFeatures": "kDim",
391
+ "outFeatures": "nDim",
392
+ "tileM": "sgmatSmallRows",
393
+ "tileN": "sgmatSmallCols",
394
+ "directAStorage": true,
395
+ "doubleBufferedB": true,
396
+ "balancedMatrixAccumulation": false
397
+ },
398
+ "passes": [
399
+ {
400
+ "id": "main",
401
+ "name": "GemmFastGelu.SgmatSmallBiasF16",
402
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
403
+ "bindings": ["x", "w", { "arg": "bias", "elementType": "$bScalar" }, "y", "params"],
404
+ "dispatch": {
405
+ "x": "ceilDiv(nDim, tileN)",
406
+ "y": "min(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)",
407
+ "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
408
+ }
409
+ }
410
+ ],
411
+ "demoteWhen": ["sgmatOutputGroups > tunables.SMALL_MATRIX_MAX_GROUPS and (sgmatMediumContract or sgmatDirectContract or sgmatContract)"]
412
+ },
413
+ {
414
+ "id": "sgmat_small_f16",
415
+ "priority": 125,
416
+ "when": ["noBiasContract", "sgmatSmallContract", "tensorDtypes.X == \"float16\""],
417
+ "requires": {
418
+ "features": ["subgroups", "chromium-experimental-subgroup-matrix"],
419
+ "subgroupMatrixConfigs": [{ "componentType": "f16", "M": 8, "N": 8, "K": 8 }]
420
+ },
421
+ "derive": {
422
+ "hasBias": false,
423
+ "xScalar": "\"f16\"",
424
+ "wScalar": "\"f16\"",
425
+ "bScalar": "\"f16\"",
426
+ "yScalar": "\"f16\"",
427
+ "mmaScalar": "\"f16\"",
428
+ "inFeatures": "kDim",
429
+ "outFeatures": "nDim",
430
+ "tileM": "sgmatSmallRows",
431
+ "tileN": "sgmatSmallCols",
432
+ "directAStorage": true,
433
+ "doubleBufferedB": true,
434
+ "balancedMatrixAccumulation": false
435
+ },
436
+ "passes": [
437
+ {
438
+ "id": "main",
439
+ "name": "GemmFastGelu.SgmatSmallF16",
440
+ "shader": "gemm-subgroup-matrix.wgsl.jinja",
441
+ "bindings": ["x", "w", "y", "params"],
442
+ "dispatch": {
443
+ "x": "ceilDiv(nDim, tileN)",
444
+ "y": "min(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)",
445
+ "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
446
+ }
447
+ }
448
+ ],
449
+ "demoteWhen": ["sgmatOutputGroups > tunables.SMALL_MATRIX_MAX_GROUPS and (sgmatMediumContract or sgmatDirectContract or sgmatContract)"]
450
+ },
451
  {
452
  "id": "sgmat_direct_bias",
453
  "priority": 110,
 
458
  },
459
  "derive": {
460
  "hasBias": true,
 
461
  "xScalar": "\"f32\"",
462
  "wScalar": "\"f32\"",
463
  "bScalar": "\"f32\"",
 
467
  "outFeatures": "nDim",
468
  "tileM": "sgmatDirectRows",
469
  "tileN": "sgmatDirectCols",
 
470
  "directAStorage": true,
471
  "doubleBufferedB": true,
472
  "balancedMatrixAccumulation": true
 
474
  "passes": [
475
  {
476
  "id": "main",
477
+ "name": "GemmFastGelu.SgmatDirectBias",
478
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
479
  "bindings": ["x", "w", { "arg": "bias", "elementType": "$bScalar" }, "y", "params"],
480
  "dispatch": {
 
483
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
484
  }
485
  }
486
+ ],
487
+ "demoteWhen": ["false"]
488
  },
489
  {
490
  "id": "sgmat_direct",
 
496
  },
497
  "derive": {
498
  "hasBias": false,
 
499
  "xScalar": "\"f32\"",
500
  "wScalar": "\"f32\"",
501
  "bScalar": "\"f32\"",
 
505
  "outFeatures": "nDim",
506
  "tileM": "sgmatDirectRows",
507
  "tileN": "sgmatDirectCols",
 
508
  "directAStorage": true,
509
  "doubleBufferedB": true,
510
  "balancedMatrixAccumulation": true
 
512
  "passes": [
513
  {
514
  "id": "main",
515
+ "name": "GemmFastGelu.SgmatDirect",
516
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
517
  "bindings": ["x", "w", "y", "params"],
518
  "dispatch": {
 
521
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
522
  }
523
  }
524
+ ],
525
+ "demoteWhen": ["false"]
526
  },
527
  {
528
  "id": "sgmat_direct_bias_f16",
 
534
  },
535
  "derive": {
536
  "hasBias": true,
 
537
  "xScalar": "\"f16\"",
538
  "wScalar": "\"f16\"",
539
  "bScalar": "\"f16\"",
 
543
  "outFeatures": "nDim",
544
  "tileM": "sgmatDirectRows",
545
  "tileN": "sgmatDirectCols",
 
546
  "directAStorage": true,
547
  "doubleBufferedB": true,
548
  "balancedMatrixAccumulation": false
 
550
  "passes": [
551
  {
552
  "id": "main",
553
+ "name": "GemmFastGelu.SgmatDirectBiasF16",
554
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
555
  "bindings": ["x", "w", { "arg": "bias", "elementType": "$bScalar" }, "y", "params"],
556
  "dispatch": {
 
559
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
560
  }
561
  }
562
+ ],
563
+ "demoteWhen": ["false"]
564
  },
565
  {
566
  "id": "sgmat_direct_f16",
 
572
  },
573
  "derive": {
574
  "hasBias": false,
 
575
  "xScalar": "\"f16\"",
576
  "wScalar": "\"f16\"",
577
  "bScalar": "\"f16\"",
 
581
  "outFeatures": "nDim",
582
  "tileM": "sgmatDirectRows",
583
  "tileN": "sgmatDirectCols",
 
584
  "directAStorage": true,
585
  "doubleBufferedB": true,
586
  "balancedMatrixAccumulation": false
 
588
  "passes": [
589
  {
590
  "id": "main",
591
+ "name": "GemmFastGelu.SgmatDirectF16",
592
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
593
  "bindings": ["x", "w", "y", "params"],
594
  "dispatch": {
 
597
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
598
  }
599
  }
600
+ ],
601
+ "demoteWhen": ["false"]
602
  },
603
  {
604
  "id": "sgmat_bias",
 
610
  },
611
  "derive": {
612
  "hasBias": true,
 
613
  "xScalar": "\"f32\"",
614
  "wScalar": "\"f32\"",
615
  "bScalar": "\"f32\"",
 
619
  "outFeatures": "nDim",
620
  "tileM": 32,
621
  "tileN": 64,
 
622
  "directAStorage": false,
623
  "doubleBufferedB": false,
624
  "balancedMatrixAccumulation": false
 
626
  "passes": [
627
  {
628
  "id": "main",
629
+ "name": "GemmFastGelu.SgmatBias",
630
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
631
  "bindings": ["x", "w", { "arg": "bias", "elementType": "$bScalar" }, "y", "params"],
632
  "dispatch": {
 
635
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
636
  }
637
  }
638
+ ],
639
+ "demoteWhen": ["false"]
640
  },
641
  {
642
  "id": "sgmat",
 
648
  },
649
  "derive": {
650
  "hasBias": false,
 
651
  "xScalar": "\"f32\"",
652
  "wScalar": "\"f32\"",
653
  "bScalar": "\"f32\"",
 
657
  "outFeatures": "nDim",
658
  "tileM": 32,
659
  "tileN": 64,
 
660
  "directAStorage": false,
661
  "doubleBufferedB": false,
662
  "balancedMatrixAccumulation": false
 
664
  "passes": [
665
  {
666
  "id": "main",
667
+ "name": "GemmFastGelu.Sgmat",
668
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
669
  "bindings": ["x", "w", "y", "params"],
670
  "dispatch": {
 
673
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
674
  }
675
  }
676
+ ],
677
+ "demoteWhen": ["false"]
678
  },
679
  {
680
  "id": "sgmat_bias_f16",
 
686
  },
687
  "derive": {
688
  "hasBias": true,
 
689
  "xScalar": "\"f16\"",
690
  "wScalar": "\"f16\"",
691
  "bScalar": "\"f16\"",
 
695
  "outFeatures": "nDim",
696
  "tileM": 32,
697
  "tileN": 64,
 
698
  "directAStorage": false,
699
  "doubleBufferedB": false,
700
  "balancedMatrixAccumulation": false
 
702
  "passes": [
703
  {
704
  "id": "main",
705
+ "name": "GemmFastGelu.SgmatBiasF16",
706
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
707
  "bindings": ["x", "w", { "arg": "bias", "elementType": "$bScalar" }, "y", "params"],
708
  "dispatch": {
 
711
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
712
  }
713
  }
714
+ ],
715
+ "demoteWhen": ["false"]
716
  },
717
  {
718
  "id": "sgmat_f16",
 
724
  },
725
  "derive": {
726
  "hasBias": false,
 
727
  "xScalar": "\"f16\"",
728
  "wScalar": "\"f16\"",
729
  "bScalar": "\"f16\"",
 
733
  "outFeatures": "nDim",
734
  "tileM": 32,
735
  "tileN": 64,
 
736
  "directAStorage": false,
737
  "doubleBufferedB": false,
738
  "balancedMatrixAccumulation": false
 
740
  "passes": [
741
  {
742
  "id": "main",
743
+ "name": "GemmFastGelu.SgmatF16",
744
  "shader": "gemm-subgroup-matrix.wgsl.jinja",
745
  "bindings": ["x", "w", "y", "params"],
746
  "dispatch": {
 
749
  "z": "ceilDiv(ceilDiv(gfgRows, tileM), DISPATCH_FOLD_WIDTH)"
750
  }
751
  }
752
+ ],
753
+ "demoteWhen": ["false"]
754
  },
755
  {
756
  "id": "tiled_bias",
 
761
  {
762
  "id": "main",
763
  "name": "GemmFastGelu.Bias",
764
+ "shader": "gemm-epilogue-tiled-reg.wgsl.jinja",
765
  "bindings": [
766
  "a",
767
  "b",
768
  "bias",
769
+ "y_t",
770
  {
771
  "name": "params",
772
  "struct": [
 
779
  }
780
  ],
781
  "dispatch": {
782
+ "x": "min(ceilDiv(nDim, gemmTileCols), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
783
+ "y": "min(ceilDiv(gfgRows, gemmTileRows), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))"
784
  }
785
  }
786
  ]
 
794
  {
795
  "id": "main",
796
  "name": "GemmFastGelu",
797
+ "shader": "gemm-epilogue-tiled-reg.wgsl.jinja",
798
  "bindings": [
799
  "a",
800
  "b",
801
+ "y_t",
802
  {
803
  "name": "params",
804
  "struct": [
 
810
  }
811
  ],
812
  "dispatch": {
813
+ "x": "min(ceilDiv(nDim, gemmTileCols), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
814
+ "y": "min(ceilDiv(gfgRows, gemmTileRows), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))"
815
  }
816
  }
817
  ]
818
  }
819
+ ],
820
+ "tunables": {
821
+ "SMALL_MATRIX_MAX_GROUPS": { "default": 48 },
822
+ "MEDIUM_MATRIX_MAX_GROUPS_F32": { "default": 96 },
823
+ "MEDIUM_MATRIX_MAX_GROUPS_F16": { "default": 80 },
824
+ "GEMV_TARGET_BLOCKS": { "default": 512 },
825
+ "PORTABLE_SMALL_TILE_GROUPS_F32": { "default": 64 },
826
+ "PORTABLE_SMALL_TILE_GROUPS_F16": { "default": 128 }
827
+ }
828
  }
build/webgpu/matmul-vector-matrix-vec4.wgsl.jinja ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = "y" %}
5
+ fn tanh_safe(x: f32) -> f32 {
6
+ // tanh rounds to its saturated value for these tails in f32. Return that
7
+ // value directly, including for infinite input, before invoking the builtin.
8
+ if (x > 10.0) { return 1.0; }
9
+ if (x < -10.0) { return -1.0; }
10
+ // For tiny |x|, return x directly to preserve its sign and magnitude without
11
+ // relying on backend-specific builtin behavior near zero.
12
+ if (x > -1.0e-4 && x < 1.0e-4) { return x; }
13
+ return tanh(x);
14
+ }
15
+
16
+ fn gelu_tanh(v: f32) -> f32 {
17
+ return 0.5 * v * (1.0 + tanh_safe(0.7978845608028654 * (v + 0.044715 * v * v * v)));
18
+ }
19
+
20
+ const LANES: u32 = {{ gemvLanes }}u;
21
+ // SLICES partitions the K reduction across the workgroup's second dimension.
22
+ // Thread zero of each column group combines the slice partials in index order.
23
+ const SLICES: u32 = {{ gemvSlices }}u;
24
+
25
+ var<workgroup> partials: array<vec4<f32>, LANES * SLICES>;
26
+
27
+ @compute @workgroup_size({{ gemvLanes }}, {{ gemvSlices }}, 1)
28
+ fn main(
29
+ @builtin(workgroup_id) workgroup_id: vec3<u32>,
30
+ @builtin(local_invocation_id) lid: vec3<u32>
31
+ ) {
32
+ let lane = lid.x;
33
+ let slice = lid.y;
34
+ let cg = workgroup_id.x * LANES + lane;
35
+ var acc = vec4<f32>(0.0);
36
+ if (cg < params.N4) {
37
+ {% if unrollK2 is defined and unrollK2 %}
38
+ // Two K positions per iteration amortize loop/address arithmetic on long
39
+ // decode projections while preserving each slice's exact strided order.
40
+ var k = slice;
41
+ for (; k + SLICES < params.K; k = k + 2u * SLICES) {
42
+ acc = acc + f32(a[k]) * vec4<f32>(b[k * params.N4 + cg]);
43
+ let k1 = k + SLICES;
44
+ acc = acc + f32(a[k1]) * vec4<f32>(b[k1 * params.N4 + cg]);
45
+ }
46
+ if (k < params.K) {
47
+ acc = acc + f32(a[k]) * vec4<f32>(b[k * params.N4 + cg]);
48
+ }
49
+ {% else %}
50
+ for (var k = slice; k < params.K; k = k + SLICES) {
51
+ acc = acc + f32(a[k]) * vec4<f32>(b[k * params.N4 + cg]);
52
+ }
53
+ {% endif %}
54
+ }
55
+ partials[slice * LANES + lane] = acc;
56
+ workgroupBarrier();
57
+ if (slice == 0u && cg < params.N4) {
58
+ var total = partials[lane];
59
+ for (var s = 1u; s < SLICES; s = s + 1u) {
60
+ total = total + partials[s * LANES + lane];
61
+ }
62
+ {% if gemvBias | default(false) %}
63
+ total = total + vec4<f32>(bias[cg]);
64
+ {% endif %}
65
+ {% for i in range(4) %}
66
+ total[{{ i }}] = gelu_tanh(total[{{ i }}]);
67
+ {% endfor %}
68
+ {{ OUT }}[cg] = vec4<{{ T }}>(total);
69
+ }
70
+ }
build/webgpu/metadata.json CHANGED
@@ -1,23 +1,34 @@
1
  {
2
  "name": "com.microsoft.GemmFastGelu",
3
- "id": "_com_microsoft_gemmfastgelu_webgpu_a713abb",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "oqlnhXJEZ8273ZAZC7ET+BMVMM8J84BrfZAVpaboErU=",
11
- "gemm-fast-gelu.wgsl.jinja": "qh+CurhpiRX3buxeBnrOX2J2mzPrlqMP+m++faB5SfI=",
12
- "gemm-subgroup-matrix.wgsl.jinja": "aaVCZdPC5akSTqDdYvM/MP8NnncAcK9kwSbvrd4Bvj8=",
13
- "manifest.json": "EespK6n0UbB6+9TwNvJ40Pp/lYYHsHCO0BCKXxLRZhM=",
14
- "test.json": "LFwbPPwea77gPJaoCjDJtA/uWfmqDYMUtIoDxd8iphY="
 
15
  }
16
  },
17
- "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
18
  "webgpu": {
19
- "manifestSpec": "2.0",
20
  "variants": {
 
 
 
 
 
 
 
 
 
 
21
  "sgmat_direct_bias": ["gemm-subgroup-matrix.wgsl.jinja"],
22
  "sgmat_direct": ["gemm-subgroup-matrix.wgsl.jinja"],
23
  "sgmat_direct_bias_f16": ["gemm-subgroup-matrix.wgsl.jinja"],
@@ -26,8 +37,8 @@
26
  "sgmat": ["gemm-subgroup-matrix.wgsl.jinja"],
27
  "sgmat_bias_f16": ["gemm-subgroup-matrix.wgsl.jinja"],
28
  "sgmat_f16": ["gemm-subgroup-matrix.wgsl.jinja"],
29
- "tiled_bias": ["gemm-fast-gelu.wgsl.jinja"],
30
- "tiled": ["gemm-fast-gelu.wgsl.jinja"]
31
  }
32
  }
33
  }
 
1
  {
2
  "name": "com.microsoft.GemmFastGelu",
3
+ "id": "_com_microsoft_gemmfastgelu_webgpu_f5e93e1",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "FfMbeBQWBZxpDttjltTuzAV+j6kE32ZmQfmcYdMKHQk=",
11
+ "gemm-epilogue-tiled-reg.wgsl.jinja": "R5ytrlOP+b62VdPVNEBfcMFde6AXx522owT2aH53EsQ=",
12
+ "gemm-subgroup-matrix.wgsl.jinja": "iDyC5cRpAczYx/bZ4E9ye5mFljkK0mlPhB0ZMAaLjRg=",
13
+ "manifest.json": "1QpiQuqS1C0R7nLKk7zC6wA9zt9GPnOdYFZ1+xNlE/U=",
14
+ "matmul-vector-matrix-vec4.wgsl.jinja": "uU7qRZ2/JhR5TZ4kZEeaf2pg1ZGqTvpO5VG1r2ft00E=",
15
+ "test.json": "IB4HCRTq8pos9TIr+Oy8FeeM8GCWURJ+d85QJYDv+bc="
16
  }
17
  },
18
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
19
  "webgpu": {
20
+ "manifestSpec": "2.1",
21
  "variants": {
22
+ "vector_matrix_bias": ["matmul-vector-matrix-vec4.wgsl.jinja"],
23
+ "vector_matrix": ["matmul-vector-matrix-vec4.wgsl.jinja"],
24
+ "sgmat_medium_bias": ["gemm-subgroup-matrix.wgsl.jinja"],
25
+ "sgmat_medium": ["gemm-subgroup-matrix.wgsl.jinja"],
26
+ "sgmat_medium_bias_f16": ["gemm-subgroup-matrix.wgsl.jinja"],
27
+ "sgmat_medium_f16": ["gemm-subgroup-matrix.wgsl.jinja"],
28
+ "sgmat_small_bias": ["gemm-subgroup-matrix.wgsl.jinja"],
29
+ "sgmat_small": ["gemm-subgroup-matrix.wgsl.jinja"],
30
+ "sgmat_small_bias_f16": ["gemm-subgroup-matrix.wgsl.jinja"],
31
+ "sgmat_small_f16": ["gemm-subgroup-matrix.wgsl.jinja"],
32
  "sgmat_direct_bias": ["gemm-subgroup-matrix.wgsl.jinja"],
33
  "sgmat_direct": ["gemm-subgroup-matrix.wgsl.jinja"],
34
  "sgmat_direct_bias_f16": ["gemm-subgroup-matrix.wgsl.jinja"],
 
37
  "sgmat": ["gemm-subgroup-matrix.wgsl.jinja"],
38
  "sgmat_bias_f16": ["gemm-subgroup-matrix.wgsl.jinja"],
39
  "sgmat_f16": ["gemm-subgroup-matrix.wgsl.jinja"],
40
+ "tiled_bias": ["gemm-epilogue-tiled-reg.wgsl.jinja"],
41
+ "tiled": ["gemm-epilogue-tiled-reg.wgsl.jinja"]
42
  }
43
  }
44
  }
build/webgpu/test.json CHANGED
The diff for this file is too large to render. See raw diff