sync 2e7068faf55e
Browse files- README.md +63 -0
- build/webgpu/bench.json +52 -0
- build/webgpu/gemm-fast-gelu.wgsl.jinja +186 -0
- build/webgpu/gemm-subgroup-matrix.wgsl.jinja +241 -0
- build/webgpu/manifest.json +246 -0
- build/webgpu/metadata.json +19 -0
- build/webgpu/test.json +289 -0
README.md
CHANGED
|
@@ -1,3 +1,66 @@
|
|
| 1 |
---
|
|
|
|
| 2 |
license: apache-2.0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
library_name: kernels
|
| 3 |
license: apache-2.0
|
| 4 |
+
tags:
|
| 5 |
+
- kernel
|
| 6 |
+
- webgpu
|
| 7 |
+
- wgsl
|
| 8 |
---
|
| 9 |
+
# com.microsoft.GemmFastGelu
|
| 10 |
+
|
| 11 |
+
`com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
|
| 12 |
+
|
| 13 |
+
## Description
|
| 14 |
+
|
| 15 |
+
Fuses MatMul, an optional bias, and FastGelu: `Y = FastGelu(X @ W + bias)`. `X` has rank at least 2 with shape `(..., K)`, `W` has shape `(K, N)`, and `bias` has shape `(N)`. The activation runs in the float32 accumulator before the output is narrowed, avoiding an intermediate `(..., N)` tensor. Bfloat16 is not implemented.
|
| 16 |
+
|
| 17 |
+
See the [ONNX Runtime `GemmFastGelu` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.GemmFastGelu) for the reference semantics.
|
| 18 |
+
|
| 19 |
+
## Inputs
|
| 20 |
+
|
| 21 |
+
| Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
|
| 22 |
+
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `X` | `X` | `T` | — | — | Left operand of rank 2 or greater with shape `(..., K)`; every leading-axis coordinate identifies a row of the product. | required |
|
| 24 |
+
| `W` | `W` | `T` | `2` | — | Right operand with shape `(K, N)`. | required |
|
| 25 |
+
| `bias` | `bias` | `T` | `1` | — | Optional bias with shape `(N)`, added before the activation. | optional |
|
| 26 |
+
|
| 27 |
+
## Outputs
|
| 28 |
+
|
| 29 |
+
| Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
|
| 30 |
+
| --- | --- | --- | --- | --- | --- | --- |
|
| 31 |
+
| `Y` | `Y` | `T` | same as `X` | ONNX MatMul result of `X` and `W` | `FastGelu(X @ W + bias)`, with the same rank and leading dimensions as `X` and a trailing `N`. | required |
|
| 32 |
+
|
| 33 |
+
## Type constraints
|
| 34 |
+
|
| 35 |
+
| Variable | Allowed dtypes |
|
| 36 |
+
| --- | --- |
|
| 37 |
+
| `T` | `float32`, `float16` |
|
| 38 |
+
|
| 39 |
+
## Device requirements
|
| 40 |
+
|
| 41 |
+
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.
|
| 42 |
+
|
| 43 |
+
## Files
|
| 44 |
+
|
| 45 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
|
| 46 |
+
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 47 |
+
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 48 |
+
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
| 49 |
+
- [`gemm-fast-gelu.wgsl.jinja`](build/webgpu/gemm-fast-gelu.wgsl.jinja)
|
| 50 |
+
- [`gemm-subgroup-matrix.wgsl.jinja`](build/webgpu/gemm-subgroup-matrix.wgsl.jinja)
|
| 51 |
+
|
| 52 |
+
## Use with `@huggingface/kernels`
|
| 53 |
+
|
| 54 |
+
The loader derives every required output's shape and logical dtype from the manifest contract and this call.
|
| 55 |
+
It then allocates the result tensors automatically.
|
| 56 |
+
|
| 57 |
+
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 58 |
+
|
| 59 |
+
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 60 |
+
|
| 61 |
+
```js
|
| 62 |
+
import { getKernel } from "@huggingface/kernels";
|
| 63 |
+
|
| 64 |
+
const kernel = await getKernel("webgpu-kernels/com.microsoft.GemmFastGelu", { version: 1 });
|
| 65 |
+
const { Y } = await kernel({ X: { data: XData, shape: [5, 6] }, W: { data: WData, shape: [6, 4] } });
|
| 66 |
+
```
|
build/webgpu/bench.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"op": "com.microsoft.GemmFastGelu",
|
| 3 |
+
"cases": [
|
| 4 |
+
{
|
| 5 |
+
"name": "gemmfastgelu-bert-base-b8-s384-h768-i3072",
|
| 6 |
+
"preset": "smoke",
|
| 7 |
+
"vars": { "dtype": "float32" },
|
| 8 |
+
"inputs": {
|
| 9 |
+
"X": { "shape": [8, 384, 768], "dtype": "float32", "dist": "normal", "seed": 5200, "scale": 1 },
|
| 10 |
+
"W": { "shape": [768, 3072], "dtype": "float32", "dist": "normal", "seed": 5201, "scale": 1 },
|
| 11 |
+
"bias": { "shape": [3072], "dtype": "float32", "dist": "normal", "seed": 5202, "scale": 1 }
|
| 12 |
+
},
|
| 13 |
+
"outputs": { "Y": { "shape": [8, 384, 3072], "dtype": "float32" } },
|
| 14 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "2 * 3072 * 768 * 3072" }] }
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"name": "gemmfastgelu-f16-bert-base-b8-s384-h768-i3072",
|
| 18 |
+
"preset": "smoke",
|
| 19 |
+
"vars": { "dtype": "float16" },
|
| 20 |
+
"inputs": {
|
| 21 |
+
"X": { "shape": [8, 384, 768], "dtype": "float16", "dist": "normal", "seed": 5210, "scale": 1 },
|
| 22 |
+
"W": { "shape": [768, 3072], "dtype": "float16", "dist": "normal", "seed": 5211, "scale": 1 },
|
| 23 |
+
"bias": { "shape": [3072], "dtype": "float16", "dist": "normal", "seed": 5212, "scale": 1 }
|
| 24 |
+
},
|
| 25 |
+
"outputs": { "Y": { "shape": [8, 384, 3072], "dtype": "float16" } },
|
| 26 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "2 * 3072 * 768 * 3072" }] }
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"name": "gemmfastgelu-bert-large-b4-s512-h1024-i4096",
|
| 30 |
+
"preset": "model",
|
| 31 |
+
"vars": { "dtype": "float32" },
|
| 32 |
+
"inputs": {
|
| 33 |
+
"X": { "shape": [4, 512, 1024], "dtype": "float32", "dist": "normal", "seed": 5220, "scale": 1 },
|
| 34 |
+
"W": { "shape": [1024, 4096], "dtype": "float32", "dist": "normal", "seed": 5221, "scale": 1 },
|
| 35 |
+
"bias": { "shape": [4096], "dtype": "float32", "dist": "normal", "seed": 5222, "scale": 1 }
|
| 36 |
+
},
|
| 37 |
+
"outputs": { "Y": { "shape": [4, 512, 4096], "dtype": "float32" } },
|
| 38 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "2 * 2048 * 1024 * 4096" }] }
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"name": "gemmfastgelu-nobias-b8-s384-h768-i3072",
|
| 42 |
+
"preset": "model",
|
| 43 |
+
"vars": { "dtype": "float32" },
|
| 44 |
+
"inputs": {
|
| 45 |
+
"X": { "shape": [8, 384, 768], "dtype": "float32", "dist": "normal", "seed": 5230, "scale": 1 },
|
| 46 |
+
"W": { "shape": [768, 3072], "dtype": "float32", "dist": "normal", "seed": 5231, "scale": 1 }
|
| 47 |
+
},
|
| 48 |
+
"outputs": { "Y": { "shape": [8, 384, 3072], "dtype": "float32" } },
|
| 49 |
+
"bench": { "metrics": [{ "type": "gflops", "value": "2 * 3072 * 768 * 3072" }] }
|
| 50 |
+
}
|
| 51 |
+
]
|
| 52 |
+
}
|
build/webgpu/gemm-fast-gelu.wgsl.jinja
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% if usesF16 is defined and usesF16 %}enable f16;
|
| 2 |
+
{% endif %}{% set OUT = "f16" if (usesF16 is defined and usesF16) else "f32" %}
|
| 3 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 4 |
+
|
| 5 |
+
// Shape-driven, register-blocked f32 GEMM for all four transpose combinations.
|
| 6 |
+
// Runtime M/N/K share one compiled pipeline. A 4x4 thread micro-tile forms a
|
| 7 |
+
// 64x64 output tile with BK=16; transpose modes change only load strides and
|
| 8 |
+
// keep the stored-contiguous axis coalesced. M/N/K tails are bounds-checked.
|
| 9 |
+
// Both tiles are indexed by their own output axis and group four K values per
|
| 10 |
+
// vector word, so the micro-tile accumulates through dot() and one step reads
|
| 11 |
+
// TM + TN words instead of 4 * (TM + TN) scalars.
|
| 12 |
+
{% set gemmEpi = gemmEpilogue if gemmEpilogue is defined else "none" %}
|
| 13 |
+
{% if gemmEpi == "fastgelu" %}
|
| 14 |
+
fn tanh_safe(x: f32) -> f32 {
|
| 15 |
+
if (x > 10.0) { return 1.0; }
|
| 16 |
+
if (x < -10.0) { return -1.0; }
|
| 17 |
+
return tanh(x);
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
fn gelu_tanh(v: f32) -> f32 {
|
| 21 |
+
return 0.5 * v * (1.0 + tanh_safe(0.7978845608028654 * (v + 0.044715 * v * v * v)));
|
| 22 |
+
}
|
| 23 |
+
{% elif gemmEpi == "activation" %}
|
| 24 |
+
{% set actMode = gemmActivation | default("") %}
|
| 25 |
+
{% set actA = gemmActAlpha | default(0.0) %}
|
| 26 |
+
{% set actB = gemmActBeta | default(0.0) %}
|
| 27 |
+
fn fused_act(v: f32) -> f32 {
|
| 28 |
+
{% if actMode == "Relu" %}
|
| 29 |
+
return max(v, 0.0);
|
| 30 |
+
{% endif %}
|
| 31 |
+
{% if actMode == "LeakyRelu" %}
|
| 32 |
+
return select(v * f32({{ actA }}), v, v >= 0.0);
|
| 33 |
+
{% endif %}
|
| 34 |
+
{% if actMode == "Sigmoid" %}
|
| 35 |
+
return 1.0 / (1.0 + exp(-v));
|
| 36 |
+
{% endif %}
|
| 37 |
+
{% if actMode == "Tanh" %}
|
| 38 |
+
return tanh(v);
|
| 39 |
+
{% endif %}
|
| 40 |
+
{% if actMode == "HardSigmoid" %}
|
| 41 |
+
return clamp(f32({{ actA }}) * v + f32({{ actB }}), 0.0, 1.0);
|
| 42 |
+
{% endif %}
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
{% endif %}
|
| 46 |
+
{% set epiOpen = "gelu_tanh(" if gemmEpi == "fastgelu" else ("fused_act(" if gemmEpi == "activation" else "") %}
|
| 47 |
+
{% set epiClose = ")" if gemmEpi != "none" else "" %}
|
| 48 |
+
const BK: u32 = 16u;
|
| 49 |
+
const BM: u32 = 64u;
|
| 50 |
+
const BN: u32 = 64u;
|
| 51 |
+
const TM: u32 = 4u; // per-thread micro-tile rows
|
| 52 |
+
const TN: u32 = 4u; // per-thread micro-tile cols
|
| 53 |
+
|
| 54 |
+
// Stage tiles at the operands' storage width. For f16 inputs this is
|
| 55 |
+
// bit-identical to widening on shared load, keeps f32 FMA registers, and halves
|
| 56 |
+
// workgroup storage.
|
| 57 |
+
{% set tileT = OUT %}
|
| 58 |
+
const K_VECS: u32 = BK / 4u;
|
| 59 |
+
var<workgroup> tileA: array<array<vec4<{{ tileT }}>, K_VECS>, BM>; // [64][4] logical A'[m][k/4]
|
| 60 |
+
var<workgroup> tileB: array<array<vec4<{{ tileT }}>, K_VECS>, BN>; // [64][4] logical B'[n][k/4]
|
| 61 |
+
|
| 62 |
+
@compute @workgroup_size(16, 16, 1)
|
| 63 |
+
fn main(
|
| 64 |
+
@builtin(workgroup_id) wg: vec3<u32>,
|
| 65 |
+
@builtin(local_invocation_id) lid: vec3<u32>,
|
| 66 |
+
@builtin(num_workgroups) nwg: vec3<u32>
|
| 67 |
+
) {
|
| 68 |
+
let li = lid.y * 16u + lid.x;
|
| 69 |
+
let M = params.M;
|
| 70 |
+
let N = params.N;
|
| 71 |
+
let K = params.K;
|
| 72 |
+
let n_tiles = (N + BN - 1u) / BN;
|
| 73 |
+
let m_tiles = (M + BM - 1u) / BM;
|
| 74 |
+
for (var nt: u32 = wg.x; nt < n_tiles; nt = nt + nwg.x) {
|
| 75 |
+
let nBase = nt * BN;
|
| 76 |
+
for (var mt: u32 = wg.y; mt < m_tiles; mt = mt + nwg.y) {
|
| 77 |
+
let mBase = mt * BM;
|
| 78 |
+
|
| 79 |
+
var acc: array<f32, TM * TN>;
|
| 80 |
+
for (var i: u32 = 0u; i < TM * TN; i = i + 1u) { acc[i] = 0.0; }
|
| 81 |
+
|
| 82 |
+
let numTiles = (K + BK - 1u) / BK;
|
| 83 |
+
for (var kt: u32 = 0u; kt < numTiles; kt = kt + 1u) {
|
| 84 |
+
let kBase = kt * BK;
|
| 85 |
+
// Cooperative load: one vector word per lane per pass. The lane grid is
|
| 86 |
+
// laid out so the axis the operand stores contiguously stays coalesced.
|
| 87 |
+
for (var idx: u32 = li; idx < BM * K_VECS; idx = idx + 256u) {
|
| 88 |
+
{% if transA %}
|
| 89 |
+
// A stored [K, M]: logical A'[m,k] = a[k*M + m]. Coalesce on m (contiguous).
|
| 90 |
+
let ar = idx % BM;
|
| 91 |
+
let ac4 = idx / BM;
|
| 92 |
+
{% else %}
|
| 93 |
+
// A stored [M, K]: logical A[m,k] = a[m*K + k]. Coalesce on k (contiguous).
|
| 94 |
+
let ar = idx / K_VECS;
|
| 95 |
+
let ac4 = idx % K_VECS;
|
| 96 |
+
{% endif %}
|
| 97 |
+
let am = mBase + ar;
|
| 98 |
+
let ak = kBase + ac4 * 4u;
|
| 99 |
+
var aWord = vec4<{{ tileT }}>({{ tileT }}(0.0));
|
| 100 |
+
if (am < M) {
|
| 101 |
+
{% for component in range(4) %}
|
| 102 |
+
if (ak + {{ component }}u < K) {
|
| 103 |
+
{% if transA %}
|
| 104 |
+
aWord[{{ component }}u] = {{ tileT }}(a[(ak + {{ component }}u) * M + am]);
|
| 105 |
+
{% else %}
|
| 106 |
+
aWord[{{ component }}u] = {{ tileT }}(a[am * K + ak + {{ component }}u]);
|
| 107 |
+
{% endif %}
|
| 108 |
+
}
|
| 109 |
+
{% endfor %}
|
| 110 |
+
}
|
| 111 |
+
tileA[ar][ac4] = aWord;
|
| 112 |
+
}
|
| 113 |
+
for (var idx: u32 = li; idx < BN * K_VECS; idx = idx + 256u) {
|
| 114 |
+
{% if transB is defined and transB %}
|
| 115 |
+
// B stored [N, K]: logical B[k,n] = b[n*K + k]. Coalesce on k (contiguous).
|
| 116 |
+
let bc = idx / K_VECS;
|
| 117 |
+
let br4 = idx % K_VECS;
|
| 118 |
+
{% else %}
|
| 119 |
+
// B stored [K, N]: logical B[k,n] = b[k*N + n]. Coalesce on n (contiguous).
|
| 120 |
+
let bc = idx % BN;
|
| 121 |
+
let br4 = idx / BN;
|
| 122 |
+
{% endif %}
|
| 123 |
+
let bn = nBase + bc;
|
| 124 |
+
let bk = kBase + br4 * 4u;
|
| 125 |
+
var bWord = vec4<{{ tileT }}>({{ tileT }}(0.0));
|
| 126 |
+
if (bn < N) {
|
| 127 |
+
{% for component in range(4) %}
|
| 128 |
+
if (bk + {{ component }}u < K) {
|
| 129 |
+
{% if transB is defined and transB %}
|
| 130 |
+
bWord[{{ component }}u] = {{ tileT }}(b[bn * K + bk + {{ component }}u]);
|
| 131 |
+
{% else %}
|
| 132 |
+
bWord[{{ component }}u] = {{ tileT }}(b[(bk + {{ component }}u) * N + bn]);
|
| 133 |
+
{% endif %}
|
| 134 |
+
}
|
| 135 |
+
{% endfor %}
|
| 136 |
+
}
|
| 137 |
+
tileB[bc][br4] = bWord;
|
| 138 |
+
}
|
| 139 |
+
workgroupBarrier();
|
| 140 |
+
{% set regIndent = " " %}{% set regT = "f32" %}{% set regCast = 1 if tileT != "f32" else 0 %}{{ regIndent }}let aRow = lid.y * TM;
|
| 141 |
+
{{ regIndent }}let bCol = lid.x * TN;
|
| 142 |
+
{{ regIndent }}for (var kv: u32 = 0u; kv < BK / 4u; kv = kv + 1u) {
|
| 143 |
+
{{ regIndent }} var av: array<vec4<{{ regT }}>, TM>;
|
| 144 |
+
{{ regIndent }} var bv: array<vec4<{{ regT }}>, TN>;
|
| 145 |
+
{{ regIndent }} for (var i: u32 = 0u; i < TM; i = i + 1u) { av[i] = {% if regCast %}vec4<{{ regT }}>(tileA[aRow + i][kv]){% else %}tileA[aRow + i][kv]{% endif %}; }
|
| 146 |
+
{{ regIndent }} for (var j: u32 = 0u; j < TN; j = j + 1u) { bv[j] = {% if regCast %}vec4<{{ regT }}>(tileB[bCol + j][kv]){% else %}tileB[bCol + j][kv]{% endif %}; }
|
| 147 |
+
{{ regIndent }} for (var i: u32 = 0u; i < TM; i = i + 1u) {
|
| 148 |
+
{{ regIndent }} for (var j: u32 = 0u; j < TN; j = j + 1u) {
|
| 149 |
+
{{ regIndent }} acc[i * TN + j] = acc[i * TN + j] + dot(av[i], bv[j]);
|
| 150 |
+
{{ regIndent }} }
|
| 151 |
+
{{ regIndent }} }
|
| 152 |
+
{{ regIndent }}}
|
| 153 |
+
workgroupBarrier();
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
let m0 = mBase + lid.y * TM;
|
| 157 |
+
let n0 = nBase + lid.x * TN;
|
| 158 |
+
for (var ti: u32 = 0u; ti < TM; ti = ti + 1u) {
|
| 159 |
+
let m = m0 + ti;
|
| 160 |
+
if (m >= M) { continue; }
|
| 161 |
+
for (var tj: u32 = 0u; tj < TN; tj = tj + 1u) {
|
| 162 |
+
let n = n0 + tj;
|
| 163 |
+
if (n < N) {
|
| 164 |
+
// alpha == 0 nulls A*B exactly and avoids Inf*0 becoming NaN.
|
| 165 |
+
let product = select(acc[ti * TN + tj] * params.alpha, 0.0, params.alpha == 0.0);
|
| 166 |
+
{% if hasBias is defined and hasBias %}
|
| 167 |
+
{% if rowBias is defined and rowBias %}
|
| 168 |
+
let biasValue = f32(bias[n]);
|
| 169 |
+
{% elif scalarBias is defined and scalarBias %}
|
| 170 |
+
let biasValue = f32(bias[0u]);
|
| 171 |
+
{% elif columnBias is defined and columnBias %}
|
| 172 |
+
let biasValue = f32(bias[m]);
|
| 173 |
+
{% else %}
|
| 174 |
+
let biasValue = f32(bias[m * N + n]);
|
| 175 |
+
{% endif %}
|
| 176 |
+
let biasTerm = select(biasValue * params.beta, 0.0, params.beta == 0.0);
|
| 177 |
+
y[m * N + n] = {{ OUT }}({{ epiOpen }}product + biasTerm{{ epiClose }});
|
| 178 |
+
{% else %}
|
| 179 |
+
y[m * N + n] = {{ OUT }}({{ epiOpen }}product{{ epiClose }});
|
| 180 |
+
{% endif %}
|
| 181 |
+
}
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
}
|
| 186 |
+
}
|
build/webgpu/gemm-subgroup-matrix.wgsl.jinja
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% if usesF16 %}
|
| 2 |
+
enable f16;
|
| 3 |
+
{% endif %}
|
| 4 |
+
enable subgroups;
|
| 5 |
+
{% if pinSubgroupSize32 %}
|
| 6 |
+
enable subgroup_size_control;
|
| 7 |
+
{% endif %}
|
| 8 |
+
enable chromium_experimental_subgroup_matrix;
|
| 9 |
+
diagnostic(off, chromium.subgroup_matrix_uniformity);
|
| 10 |
+
|
| 11 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 12 |
+
|
| 13 |
+
{% set operandScalar = mmaScalar %}
|
| 14 |
+
{% set accScalar = "f32" %}
|
| 15 |
+
{% set tileMValue = tileM if tileM is defined else 32 %}
|
| 16 |
+
{% set tileNValue = tileN if tileN is defined else 64 %}
|
| 17 |
+
{% set tileKValue = tileK if tileK is defined else 32 %}
|
| 18 |
+
{% set gemmEpi = gemmEpilogue if gemmEpilogue is defined else "none" %}
|
| 19 |
+
{% set epiOpen = "gelu_tanh(" if gemmEpi == "fastgelu" else ("fused_act(" if gemmEpi == "activation" else "") %}
|
| 20 |
+
{% set epiClose = ")" if gemmEpi != "none" else "" %}
|
| 21 |
+
{% set useDoubleBufferedB = doubleBufferedB is defined and doubleBufferedB %}
|
| 22 |
+
{% set bTransposed = bTransposedStorage if bTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
|
| 23 |
+
{% set columnTileBaseValue = columnTileBase if columnTileBase is defined else 0 %}
|
| 24 |
+
{% set rowTileBaseValue = rowTileBase if rowTileBase is defined else 0 %}
|
| 25 |
+
// Four subgroups cover 32xN as a 2x2 grid, or taller tiles as four row bands.
|
| 26 |
+
{% set subtileCols = (tileNValue / 2)|int %}
|
| 27 |
+
{% set subtileRows = 16 %}
|
| 28 |
+
{% set aTilesPerSubgroup = (subtileRows / 8)|int %}
|
| 29 |
+
{% set bTilesPerSubgroup = (subtileCols / 8)|int %}
|
| 30 |
+
{% set subgroupCount = 4 %}
|
| 31 |
+
{% set scratchBanks = subgroupCount * aTilesPerSubgroup * bTilesPerSubgroup %}
|
| 32 |
+
|
| 33 |
+
{% if gemmEpi == "fastgelu" %}
|
| 34 |
+
fn tanh_safe(x: f32) -> f32 {
|
| 35 |
+
if (x > 10.0) { return 1.0; }
|
| 36 |
+
if (x < -10.0) { return -1.0; }
|
| 37 |
+
return tanh(x);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
fn gelu_tanh(v: f32) -> f32 {
|
| 41 |
+
return 0.5 * v * (1.0 + tanh_safe(0.7978845608028654 * (v + 0.044715 * v * v * v)));
|
| 42 |
+
}
|
| 43 |
+
{% elif gemmEpi == "activation" %}
|
| 44 |
+
{% set actMode = gemmActivation | default("") %}
|
| 45 |
+
{% set actA = gemmActAlpha | default(0.0) %}
|
| 46 |
+
{% set actB = gemmActBeta | default(0.0) %}
|
| 47 |
+
fn fused_act(v: f32) -> f32 {
|
| 48 |
+
{% if actMode == "Relu" %}
|
| 49 |
+
return max(v, 0.0);
|
| 50 |
+
{% endif %}
|
| 51 |
+
{% if actMode == "LeakyRelu" %}
|
| 52 |
+
return select(v * f32({{ actA }}), v, v >= 0.0);
|
| 53 |
+
{% endif %}
|
| 54 |
+
{% if actMode == "Sigmoid" %}
|
| 55 |
+
return 1.0 / (1.0 + exp(-v));
|
| 56 |
+
{% endif %}
|
| 57 |
+
{% if actMode == "Tanh" %}
|
| 58 |
+
return tanh(v);
|
| 59 |
+
{% endif %}
|
| 60 |
+
{% if actMode == "HardSigmoid" %}
|
| 61 |
+
return clamp(f32({{ actA }}) * v + f32({{ actB }}), 0.0, 1.0);
|
| 62 |
+
{% endif %}
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
{% endif %}
|
| 66 |
+
const IN_F: u32 = {{ inFeatures }}u;
|
| 67 |
+
const OUT_F: u32 = {{ outFeatures }}u;
|
| 68 |
+
{% set kLoopEndValue = kLoopEnd if kLoopEnd is defined else ("K_LOOP" if tailSafe is defined and tailSafe else "IN_F") %}
|
| 69 |
+
const TILE_COLS: u32 = {{ tileNValue }}u;
|
| 70 |
+
const TILE_ROWS: u32 = {{ tileMValue }}u;
|
| 71 |
+
const TILE_K: u32 = {{ tileKValue }}u;
|
| 72 |
+
const SUB_COLS: u32 = {{ subtileCols }}u;
|
| 73 |
+
const SUB_ROWS: u32 = {{ subtileRows }}u;
|
| 74 |
+
const COLUMN_TILE_BASE: u32 = {{ columnTileBaseValue }}u;
|
| 75 |
+
const ROW_TILE_BASE: u32 = {{ rowTileBaseValue }}u;
|
| 76 |
+
|
| 77 |
+
var<workgroup> tile_A: array<{{ operandScalar }}, {{ tileMValue }} * {{ tileKValue }}>;
|
| 78 |
+
var<workgroup> tile_B: array<{{ operandScalar }}, {{ (2 if useDoubleBufferedB else 1) * tileNValue }} * {{ tileKValue }}>;
|
| 79 |
+
// Distinct readback banks for every result matrix, so one barrier can publish
|
| 80 |
+
// the full subtile without write-after-read reuse.
|
| 81 |
+
var<workgroup> scratch: array<array<{{ accScalar }}, 64>, {{ scratchBanks }}>;
|
| 82 |
+
|
| 83 |
+
fn loadSHMA(tile_base: u32, k_idx: u32, row: u32, c_idx: u32) {
|
| 84 |
+
// Clamp the partial-M tail to the last valid row (M-1) instead of zero-filling
|
| 85 |
+
// it. Some subgroup-matrix implementations let a zero-padded left tile corrupt
|
| 86 |
+
// the adjacent valid row. Replicating row M-1 avoids that boundary; the
|
| 87 |
+
// replicated rows are discarded by storeOutput's row_limit guard, so every valid
|
| 88 |
+
// output row stays bit-identical. M >= 1 is guaranteed by the when-clause args.M > 0.
|
| 89 |
+
let col: u32 = c_idx * 8u;
|
| 90 |
+
for (var row_offset: u32 = 0u; row_offset < TILE_ROWS; row_offset += {{ (1024 / tileKValue)|int }}u) {
|
| 91 |
+
let r: u32 = row + row_offset;
|
| 92 |
+
let a_global: u32 = min(tile_base + r, params.M - 1u);
|
| 93 |
+
for (var col_offset: u32 = 0u; col_offset < 8u; col_offset++) {
|
| 94 |
+
let k: u32 = k_idx + col + col_offset;
|
| 95 |
+
tile_A[r * TILE_K + col + col_offset] =
|
| 96 |
+
x[a_global * IN_F + k];
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
fn loadSHMB(tile_base: u32, k_idx: u32, row: u32, c_idx: u32, bank_offset: u32) {
|
| 102 |
+
{% if bTransposed %}
|
| 103 |
+
// Generic transB=0 stores B as [K,N]. Stage that native orientation and load
|
| 104 |
+
// the subgroup right operand without a transpose.
|
| 105 |
+
let local_idx = row * {{ (tileKValue / 16)|int }}u + c_idx;
|
| 106 |
+
let vectors_per_k = TILE_COLS / 4u;
|
| 107 |
+
let total_vectors = TILE_K * vectors_per_k;
|
| 108 |
+
for (var vector_idx = local_idx; vector_idx < total_vectors; vector_idx += 128u) {
|
| 109 |
+
let k_local = vector_idx / vectors_per_k;
|
| 110 |
+
let n4 = (vector_idx - k_local * vectors_per_k) * 4u;
|
| 111 |
+
for (var component = 0u; component < 4u; component++) {
|
| 112 |
+
let n_local = n4 + component;
|
| 113 |
+
let global_n = tile_base + n_local;
|
| 114 |
+
let global_k = k_idx + k_local;
|
| 115 |
+
tile_B[bank_offset + k_local * TILE_COLS + n_local] = w[global_k * OUT_F + global_n];
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
{% else %}
|
| 119 |
+
let col: u32 = c_idx * 16u;
|
| 120 |
+
for (var row_offset: u32 = 0u; row_offset < TILE_COLS; row_offset += {{ (2048 / tileKValue)|int }}u) {
|
| 121 |
+
let b_row: u32 = row + row_offset;
|
| 122 |
+
// Non-power-of-two tuning widths (48/96) use only a prefix of lanes on the
|
| 123 |
+
// final row band. Keep those inactive lanes out of tile_B and global memory.
|
| 124 |
+
if (b_row < TILE_COLS) {
|
| 125 |
+
let w_global: u32 = tile_base + b_row;
|
| 126 |
+
for (var i: u32 = 0u; i < 16u; i++) {
|
| 127 |
+
let k: u32 = k_idx + col + i;
|
| 128 |
+
tile_B[bank_offset + b_row * TILE_K + col + i] =
|
| 129 |
+
w[w_global * IN_F + k];
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
{% endif %}
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
{% set needsColBase = hasBias or (tailSafe is defined and tailSafe) %}
|
| 137 |
+
{% set BIAS_1 = (" + " ~ accScalar ~ "(bias[col_base + col])") if hasBias else "" %}
|
| 138 |
+
{% set BIAS_2 = (" + " ~ accScalar ~ "(bias[col_base + col2])") if hasBias else "" %}
|
| 139 |
+
fn storeOutput(offset: u32{% if needsColBase %}, col_base: u32{% endif %}, row: u32, col: u32, src_slot: u32, row_limit: i32) {
|
| 140 |
+
if (row_limit > 0 && row < u32(row_limit)) {
|
| 141 |
+
let col2: u32 = col + 1u;
|
| 142 |
+
y[offset + row * OUT_F + col] = {{ yScalar }}({{ epiOpen }}scratch[src_slot][row * 8u + col]{{ BIAS_1 }}{{ epiClose }});
|
| 143 |
+
y[offset + row * OUT_F + col2] = {{ yScalar }}({{ epiOpen }}scratch[src_slot][row * 8u + col2]{{ BIAS_2 }}{{ epiClose }});
|
| 144 |
+
}
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
@compute @workgroup_size(128, 1, 1){{ " @subgroup_size(32)" if pinSubgroupSize32 else "" }}
|
| 148 |
+
fn main(
|
| 149 |
+
@builtin(workgroup_id) workgroup_id: vec3<u32>,
|
| 150 |
+
@builtin(num_workgroups) nwg: vec3<u32>,
|
| 151 |
+
@builtin(local_invocation_index) local_idx: u32,
|
| 152 |
+
@builtin(subgroup_invocation_id) sg_id: u32,
|
| 153 |
+
@builtin(subgroup_size) sg_size: u32
|
| 154 |
+
) {
|
| 155 |
+
// 2D-folded M-tile (row) base: workgroup_id.z carries the high bits when the
|
| 156 |
+
// row tile exceeds the per-dimension dispatch limit.
|
| 157 |
+
// Reduces to workgroup_id.y when nwg.z == 1; the row_limit guard in storeOutput
|
| 158 |
+
// (i32(M) - i32(a_global_base + ...)) drops the over-dispatched tail.
|
| 159 |
+
let a_global_base: u32 = (ROW_TILE_BASE + workgroup_id.y + workgroup_id.z * nwg.y) * TILE_ROWS;
|
| 160 |
+
let w_global_base: u32 = (COLUMN_TILE_BASE + workgroup_id.x) * TILE_COLS;
|
| 161 |
+
|
| 162 |
+
let subtile_id: u32 = local_idx / sg_size;
|
| 163 |
+
let subtile_idx: u32 = subtile_id / 2u;
|
| 164 |
+
let subtile_idy: u32 = subtile_id % 2u;
|
| 165 |
+
let base_A: u32 = subtile_idy * SUB_ROWS;
|
| 166 |
+
let base_B: u32 = subtile_idx * SUB_COLS;
|
| 167 |
+
|
| 168 |
+
{% for m in range(aTilesPerSubgroup) %}
|
| 169 |
+
{% for n in range(bTilesPerSubgroup) %}
|
| 170 |
+
var matC{{ m }}{{ n }}: subgroup_matrix_result<{{ accScalar }}, 8, 8>;
|
| 171 |
+
{% endfor %}
|
| 172 |
+
{% endfor %}
|
| 173 |
+
|
| 174 |
+
for (var kidx: u32 = 0u; kidx < {{ kLoopEndValue }}; kidx += TILE_K) {
|
| 175 |
+
loadSHMA(a_global_base, kidx, local_idx / {{ (tileKValue / 8)|int }}u, local_idx % {{ (tileKValue / 8)|int }}u);
|
| 176 |
+
loadSHMB(w_global_base, kidx, local_idx / {{ (tileKValue / 16)|int }}u, local_idx % {{ (tileKValue / 16)|int }}u, 0u);
|
| 177 |
+
workgroupBarrier();
|
| 178 |
+
|
| 179 |
+
for (var step: u32 = 0u; step < TILE_K; step += 8u) {
|
| 180 |
+
{% set dynamicATiles = aTilesPerSubgroup if aTilesPerSubgroup is defined else 2 %}
|
| 181 |
+
{% set bTransposed = bTransposedStorage if bTransposedStorage is defined else (matrixInputsTransposed is defined and matrixInputsTransposed) %}
|
| 182 |
+
{% set B_BANK = "b_bank_offset + " if doubleBufferedB is defined and doubleBufferedB else "" %}
|
| 183 |
+
let matrix_a_offset = subtile_idy * SUB_ROWS * TILE_K + step;
|
| 184 |
+
{% for m in range(dynamicATiles) %}
|
| 185 |
+
var matA{{ m }}: subgroup_matrix_left<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_left<{{ operandScalar }}, 8, 8>>(&tile_A, matrix_a_offset{% if m > 0 %} + {{ m * 8 }}u * TILE_K{% endif %}, false, TILE_K);
|
| 186 |
+
{% endfor %}
|
| 187 |
+
|
| 188 |
+
{% if bTransposed %}
|
| 189 |
+
let matrix_b_offset = {{ B_BANK }}step * TILE_COLS + subtile_idx * SUB_COLS;
|
| 190 |
+
{% else %}
|
| 191 |
+
let matrix_b_offset = {{ B_BANK }}subtile_idx * SUB_COLS * TILE_K + step;
|
| 192 |
+
{% endif %}
|
| 193 |
+
{% for n in range(bTilesPerSubgroup) %}
|
| 194 |
+
{% if bTransposed %}
|
| 195 |
+
var matB{{ n }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + {{ n * 8 }}u, false, TILE_COLS);
|
| 196 |
+
{% else %}
|
| 197 |
+
var matB{{ n }}: subgroup_matrix_right<{{ operandScalar }}, 8, 8> = subgroupMatrixLoad<subgroup_matrix_right<{{ operandScalar }}, 8, 8>>(&tile_B, matrix_b_offset + {{ n * 8 }}u * TILE_K, true, TILE_K);
|
| 198 |
+
{% endif %}
|
| 199 |
+
{% endfor %}
|
| 200 |
+
|
| 201 |
+
{% for m in range(dynamicATiles) %}
|
| 202 |
+
{% for n in range(bTilesPerSubgroup) %}
|
| 203 |
+
matC{{ m }}{{ n }} = subgroupMatrixMultiplyAccumulate(matA{{ m }}, matB{{ n }}, matC{{ m }}{{ n }});
|
| 204 |
+
{% endfor %}
|
| 205 |
+
{% endfor %}
|
| 206 |
+
|
| 207 |
+
}
|
| 208 |
+
workgroupBarrier();
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
let matrix_c_offset: u32 = (a_global_base + base_A) * OUT_F + w_global_base + base_B;
|
| 212 |
+
let row: u32 = sg_id / 4u;
|
| 213 |
+
let col: u32 = (sg_id % 4u) * 2u;
|
| 214 |
+
// Stage every result into a distinct bank before one barrier. Tail/bias paths
|
| 215 |
+
// retain this guarded scalar epilogue: subgroupMatrixStore scatters values
|
| 216 |
+
// across lanes, so its cross-lane readback must be published before partial-M
|
| 217 |
+
// guards diverge. The epilogue also handles output conversion and bias.
|
| 218 |
+
let bank: u32 = subtile_id * {{ aTilesPerSubgroup * bTilesPerSubgroup }}u;
|
| 219 |
+
{% for m in range(aTilesPerSubgroup) %}
|
| 220 |
+
{% for n in range(bTilesPerSubgroup) %}
|
| 221 |
+
subgroupMatrixStore(&scratch[bank + {{ m * bTilesPerSubgroup + n }}u], 0u, matC{{ m }}{{ n }}, false, 8u);
|
| 222 |
+
{% endfor %}
|
| 223 |
+
{% endfor %}
|
| 224 |
+
workgroupBarrier();
|
| 225 |
+
|
| 226 |
+
{% for m in range(aTilesPerSubgroup) %}
|
| 227 |
+
let row_limit_{{ m }}: i32 = i32(params.M) - i32(a_global_base + base_A + {{ m * 8 }}u);
|
| 228 |
+
{% for n in range(bTilesPerSubgroup) %}
|
| 229 |
+
storeOutput(
|
| 230 |
+
matrix_c_offset + {{ m * 8 }}u * OUT_F + {{ n * 8 }}u{% if needsColBase %},
|
| 231 |
+
w_global_base + base_B + {{ n * 8 }}u,
|
| 232 |
+
{% else %},
|
| 233 |
+
{% endif %}
|
| 234 |
+
row,
|
| 235 |
+
col,
|
| 236 |
+
bank + {{ m * bTilesPerSubgroup + n }}u,
|
| 237 |
+
row_limit_{{ m }}
|
| 238 |
+
);
|
| 239 |
+
{% endfor %}
|
| 240 |
+
{% endfor %}
|
| 241 |
+
}
|
build/webgpu/manifest.json
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"domain": "com.microsoft",
|
| 3 |
+
"name": "GemmFastGelu",
|
| 4 |
+
"sinceVersion": 1,
|
| 5 |
+
"description": "Fuses MatMul, an optional bias, and FastGelu: `Y = FastGelu(X @ W + bias)`. `X` has rank at least 2 with shape `(..., K)`, `W` has shape `(K, N)`, and `bias` has shape `(N)`. The activation runs in the float32 accumulator before the output is narrowed, avoiding an intermediate `(..., N)` tensor. Bfloat16 is not implemented.",
|
| 6 |
+
"inputs": [
|
| 7 |
+
{
|
| 8 |
+
"role": "X",
|
| 9 |
+
"dtype": "T",
|
| 10 |
+
"description": "Left operand of rank 2 or greater with shape `(..., K)`; every leading-axis coordinate identifies a row of the product."
|
| 11 |
+
},
|
| 12 |
+
{ "role": "W", "dtype": "T", "rank": 2, "description": "Right operand with shape `(K, N)`." },
|
| 13 |
+
{
|
| 14 |
+
"role": "bias",
|
| 15 |
+
"dtype": "T",
|
| 16 |
+
"rank": 1,
|
| 17 |
+
"optional": true,
|
| 18 |
+
"description": "Optional bias with shape `(N)`, added before the activation."
|
| 19 |
+
}
|
| 20 |
+
],
|
| 21 |
+
"outputs": [
|
| 22 |
+
{
|
| 23 |
+
"role": "Y",
|
| 24 |
+
"dtype": "T",
|
| 25 |
+
"rank": "ranks.X",
|
| 26 |
+
"shape": "matmulShape(shapes.X, shapes.W)",
|
| 27 |
+
"description": "`FastGelu(X @ W + bias)`, with the same rank and leading dimensions as `X` and a trailing `N`."
|
| 28 |
+
}
|
| 29 |
+
],
|
| 30 |
+
"typeConstraints": { "T": ["float32", "float16"] },
|
| 31 |
+
"args": {
|
| 32 |
+
"X": { "kind": "tensor", "semantic": "X", "role": "input" },
|
| 33 |
+
"W": { "kind": "tensor", "semantic": "W", "role": "weights" },
|
| 34 |
+
"bias": { "kind": "tensor", "semantic": "bias", "role": "weights", "required": false },
|
| 35 |
+
"Y": { "kind": "tensor", "semantic": "Y", "role": "output" }
|
| 36 |
+
},
|
| 37 |
+
"derive": {
|
| 38 |
+
"deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
|
| 39 |
+
"wave32Adapter": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == 32 and device.adapterInfo.subgroupMaxSize == 32",
|
| 40 |
+
"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",
|
| 41 |
+
"pinSubgroupSize32": "canPinSubgroupSize32 and not wave32Adapter",
|
| 42 |
+
"wave32Effective": "wave32Adapter or pinSubgroupSize32",
|
| 43 |
+
"kDim": "dim(shapes.W, 0)",
|
| 44 |
+
"nDim": "dim(shapes.W, 1)",
|
| 45 |
+
"gfgRows": "numel(shapes.X) / kDim if kDim > 0 else 0",
|
| 46 |
+
"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",
|
| 47 |
+
"dtypeOk": "(tensorDtypes.X == \"float32\" or tensorDtypes.X == \"float16\") and tensorDtypes.W == tensorDtypes.X and tensorDtypes.Y == tensorDtypes.X and f16Ok(dtypes.T)",
|
| 48 |
+
"biasOk": "ranks.bias == 1 and dim(shapes.bias, 0) == nDim and tensorDtypes.bias == tensorDtypes.X",
|
| 49 |
+
"baseContract": "shapeOk and dtypeOk",
|
| 50 |
+
"noBiasContract": "baseContract and not present.bias",
|
| 51 |
+
"biasContract": "baseContract and present.bias and biasOk",
|
| 52 |
+
"sgmatOperandBytes": "2 if tensorDtypes.X == \"float16\" and tensorDtypes.W == \"float16\" else 4",
|
| 53 |
+
"sgmatStorageBytes": "(32 * 32 + 64 * 32) * sgmatOperandBytes + 32 * 64 * 4",
|
| 54 |
+
"sgmatResourcesFit": "128 <= deviceWorkgroupCap and sgmatStorageBytes <= device.limits.maxComputeWorkgroupStorageSize",
|
| 55 |
+
"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) <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(nDim, 64) <= device.limits.maxComputeWorkgroupsPerDimension",
|
| 56 |
+
"sgmatContract": "wave32Effective and sgmatLayoutOk and sgmatResourcesFit"
|
| 57 |
+
},
|
| 58 |
+
"constants": {
|
| 59 |
+
"usesF16": "tensorDtypes.X == \"float16\"",
|
| 60 |
+
"transA": false,
|
| 61 |
+
"transB": false,
|
| 62 |
+
"gemmEpilogue": "\"fastgelu\""
|
| 63 |
+
},
|
| 64 |
+
"bindingSets": {
|
| 65 |
+
"noBias": [
|
| 66 |
+
{ "name": "a", "arg": "X", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
|
| 67 |
+
{ "name": "b", "arg": "W", "semantic": "W", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
|
| 68 |
+
{ "name": "y", "arg": "Y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
|
| 69 |
+
{
|
| 70 |
+
"name": "params",
|
| 71 |
+
"semantic": "kernel.params",
|
| 72 |
+
"buffer": { "type": "uniform" },
|
| 73 |
+
"struct": {
|
| 74 |
+
"name": "Params",
|
| 75 |
+
"fields": [
|
| 76 |
+
{ "name": "M", "type": "u32", "value": "gfgRows" },
|
| 77 |
+
{ "name": "N", "type": "u32", "value": "nDim" },
|
| 78 |
+
{ "name": "K", "type": "u32", "value": "kDim" },
|
| 79 |
+
{ "name": "alpha", "type": "f32", "value": 1 }
|
| 80 |
+
]
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
],
|
| 84 |
+
"withBias": [
|
| 85 |
+
{ "name": "a", "arg": "X", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
|
| 86 |
+
{ "name": "b", "arg": "W", "semantic": "W", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
|
| 87 |
+
{
|
| 88 |
+
"name": "bias",
|
| 89 |
+
"arg": "bias",
|
| 90 |
+
"semantic": "bias",
|
| 91 |
+
"buffer": { "type": "read-only-storage" },
|
| 92 |
+
"elementType": "$T"
|
| 93 |
+
},
|
| 94 |
+
{ "name": "y", "arg": "Y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
|
| 95 |
+
{
|
| 96 |
+
"name": "params",
|
| 97 |
+
"semantic": "kernel.params",
|
| 98 |
+
"buffer": { "type": "uniform" },
|
| 99 |
+
"struct": {
|
| 100 |
+
"name": "Params",
|
| 101 |
+
"fields": [
|
| 102 |
+
{ "name": "M", "type": "u32", "value": "gfgRows" },
|
| 103 |
+
{ "name": "N", "type": "u32", "value": "nDim" },
|
| 104 |
+
{ "name": "K", "type": "u32", "value": "kDim" },
|
| 105 |
+
{ "name": "alpha", "type": "f32", "value": 1 },
|
| 106 |
+
{ "name": "beta", "type": "f32", "value": 1 }
|
| 107 |
+
]
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
],
|
| 111 |
+
"sgmatNoBias": [
|
| 112 |
+
{ "name": "x", "arg": "X", "semantic": "a", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
|
| 113 |
+
{ "name": "w", "arg": "W", "semantic": "b", "buffer": { "type": "read-only-storage" }, "elementType": "$wScalar" },
|
| 114 |
+
{ "name": "y", "arg": "Y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
|
| 115 |
+
{
|
| 116 |
+
"name": "params",
|
| 117 |
+
"semantic": "kernel.params",
|
| 118 |
+
"buffer": { "type": "uniform" },
|
| 119 |
+
"struct": { "name": "Params", "fields": [{ "name": "M", "type": "u32", "value": "gfgRows" }] }
|
| 120 |
+
}
|
| 121 |
+
],
|
| 122 |
+
"sgmatBias": [
|
| 123 |
+
{ "name": "x", "arg": "X", "semantic": "a", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
|
| 124 |
+
{ "name": "w", "arg": "W", "semantic": "b", "buffer": { "type": "read-only-storage" }, "elementType": "$wScalar" },
|
| 125 |
+
{
|
| 126 |
+
"name": "bias",
|
| 127 |
+
"arg": "bias",
|
| 128 |
+
"semantic": "bias",
|
| 129 |
+
"buffer": { "type": "read-only-storage" },
|
| 130 |
+
"elementType": "$bScalar"
|
| 131 |
+
},
|
| 132 |
+
{ "name": "y", "arg": "Y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
|
| 133 |
+
{
|
| 134 |
+
"name": "params",
|
| 135 |
+
"semantic": "kernel.params",
|
| 136 |
+
"buffer": { "type": "uniform" },
|
| 137 |
+
"struct": { "name": "Params", "fields": [{ "name": "M", "type": "u32", "value": "gfgRows" }] }
|
| 138 |
+
}
|
| 139 |
+
]
|
| 140 |
+
},
|
| 141 |
+
"variants": [
|
| 142 |
+
{
|
| 143 |
+
"id": "sgmat_bias",
|
| 144 |
+
"priority": 100,
|
| 145 |
+
"requires": {
|
| 146 |
+
"features": ["subgroups", "chromium-experimental-subgroup-matrix"],
|
| 147 |
+
"subgroupMatrixConfigs": [
|
| 148 |
+
{ "componentType": "f16", "M": 8, "N": 8, "K": 8 },
|
| 149 |
+
{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
|
| 150 |
+
]
|
| 151 |
+
},
|
| 152 |
+
"when": ["biasContract", "sgmatContract"],
|
| 153 |
+
"constants": {
|
| 154 |
+
"hasBias": true,
|
| 155 |
+
"gemmEpilogue": "\"fastgelu\"",
|
| 156 |
+
"bTransposedStorage": true,
|
| 157 |
+
"xScalar": "\"f16\" if tensorDtypes.X == \"float16\" else \"f32\"",
|
| 158 |
+
"wScalar": "\"f16\" if tensorDtypes.W == \"float16\" else \"f32\"",
|
| 159 |
+
"bScalar": "\"f16\" if tensorDtypes.bias == \"float16\" else \"f32\"",
|
| 160 |
+
"yScalar": "\"f16\" if tensorDtypes.Y == \"float16\" else \"f32\"",
|
| 161 |
+
"mmaScalar": "\"f16\" if tensorDtypes.X == \"float16\" and tensorDtypes.W == \"float16\" else \"f32\"",
|
| 162 |
+
"usesF16": "tensorDtypes.X == \"float16\" or tensorDtypes.W == \"float16\" or tensorDtypes.Y == \"float16\"",
|
| 163 |
+
"inFeatures": "kDim",
|
| 164 |
+
"outFeatures": "nDim"
|
| 165 |
+
},
|
| 166 |
+
"passes": [
|
| 167 |
+
{
|
| 168 |
+
"id": "main",
|
| 169 |
+
"name": "GemmFastGelu.SubgroupMatrixBias",
|
| 170 |
+
"shader": "gemm-subgroup-matrix.wgsl.jinja",
|
| 171 |
+
"bindings": "sgmatBias",
|
| 172 |
+
"dispatch": { "workgroups": "ceilDiv(gfgRows, 32)", "x": "ceilDiv(nDim, 64)" }
|
| 173 |
+
}
|
| 174 |
+
]
|
| 175 |
+
},
|
| 176 |
+
{
|
| 177 |
+
"id": "sgmat",
|
| 178 |
+
"priority": 100,
|
| 179 |
+
"requires": {
|
| 180 |
+
"features": ["subgroups", "chromium-experimental-subgroup-matrix"],
|
| 181 |
+
"subgroupMatrixConfigs": [
|
| 182 |
+
{ "componentType": "f16", "M": 8, "N": 8, "K": 8 },
|
| 183 |
+
{ "componentType": "f32", "resultComponentType": "f32", "M": 8, "N": 8, "K": 8 }
|
| 184 |
+
]
|
| 185 |
+
},
|
| 186 |
+
"when": ["noBiasContract", "sgmatContract"],
|
| 187 |
+
"constants": {
|
| 188 |
+
"hasBias": false,
|
| 189 |
+
"gemmEpilogue": "\"fastgelu\"",
|
| 190 |
+
"bTransposedStorage": true,
|
| 191 |
+
"xScalar": "\"f16\" if tensorDtypes.X == \"float16\" else \"f32\"",
|
| 192 |
+
"wScalar": "\"f16\" if tensorDtypes.W == \"float16\" else \"f32\"",
|
| 193 |
+
"yScalar": "\"f16\" if tensorDtypes.Y == \"float16\" else \"f32\"",
|
| 194 |
+
"mmaScalar": "\"f16\" if tensorDtypes.X == \"float16\" and tensorDtypes.W == \"float16\" else \"f32\"",
|
| 195 |
+
"usesF16": "tensorDtypes.X == \"float16\" or tensorDtypes.W == \"float16\" or tensorDtypes.Y == \"float16\"",
|
| 196 |
+
"inFeatures": "kDim",
|
| 197 |
+
"outFeatures": "nDim"
|
| 198 |
+
},
|
| 199 |
+
"passes": [
|
| 200 |
+
{
|
| 201 |
+
"id": "main",
|
| 202 |
+
"name": "GemmFastGelu.SubgroupMatrix",
|
| 203 |
+
"shader": "gemm-subgroup-matrix.wgsl.jinja",
|
| 204 |
+
"bindings": "sgmatNoBias",
|
| 205 |
+
"dispatch": { "workgroups": "ceilDiv(gfgRows, 32)", "x": "ceilDiv(nDim, 64)" }
|
| 206 |
+
}
|
| 207 |
+
]
|
| 208 |
+
},
|
| 209 |
+
{
|
| 210 |
+
"id": "tiled_bias",
|
| 211 |
+
"priority": 0,
|
| 212 |
+
"when": ["biasContract"],
|
| 213 |
+
"constants": { "hasBias": true, "rowBias": true, "scalarBias": false, "columnBias": false },
|
| 214 |
+
"passes": [
|
| 215 |
+
{
|
| 216 |
+
"id": "main",
|
| 217 |
+
"name": "GemmFastGelu.Bias",
|
| 218 |
+
"shader": "gemm-fast-gelu.wgsl.jinja",
|
| 219 |
+
"bindings": "withBias",
|
| 220 |
+
"dispatch": {
|
| 221 |
+
"x": "min(ceilDiv(nDim, 64), device.limits.maxComputeWorkgroupsPerDimension)",
|
| 222 |
+
"y": "min(ceilDiv(gfgRows, 64), device.limits.maxComputeWorkgroupsPerDimension)"
|
| 223 |
+
}
|
| 224 |
+
}
|
| 225 |
+
]
|
| 226 |
+
},
|
| 227 |
+
{
|
| 228 |
+
"id": "tiled",
|
| 229 |
+
"priority": 0,
|
| 230 |
+
"when": ["noBiasContract"],
|
| 231 |
+
"constants": { "hasBias": false },
|
| 232 |
+
"passes": [
|
| 233 |
+
{
|
| 234 |
+
"id": "main",
|
| 235 |
+
"name": "GemmFastGelu",
|
| 236 |
+
"shader": "gemm-fast-gelu.wgsl.jinja",
|
| 237 |
+
"bindings": "noBias",
|
| 238 |
+
"dispatch": {
|
| 239 |
+
"x": "min(ceilDiv(nDim, 64), device.limits.maxComputeWorkgroupsPerDimension)",
|
| 240 |
+
"y": "min(ceilDiv(gfgRows, 64), device.limits.maxComputeWorkgroupsPerDimension)"
|
| 241 |
+
}
|
| 242 |
+
}
|
| 243 |
+
]
|
| 244 |
+
}
|
| 245 |
+
]
|
| 246 |
+
}
|
build/webgpu/metadata.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "com.microsoft.GemmFastGelu",
|
| 3 |
+
"id": "_com_microsoft_gemmfastgelu_webgpu_372327f",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "Apache-2.0",
|
| 6 |
+
"backend": { "type": "webgpu" },
|
| 7 |
+
"digest": {
|
| 8 |
+
"algorithm": "sha256",
|
| 9 |
+
"files": {
|
| 10 |
+
"bench.json": "CPFUVoaXFHE1nKK5oZMKmB0LohpzyltnfuRq/70nJAk=",
|
| 11 |
+
"gemm-fast-gelu.wgsl.jinja": "IkB53JqVNn9Xezw0YcKYDvm9IwDvoZNnSeA/UY4Eci4=",
|
| 12 |
+
"gemm-subgroup-matrix.wgsl.jinja": "2PtV5JZcKiR+CKWoCbReeevzatqfe9l/O6U5wwbDgoU=",
|
| 13 |
+
"manifest.json": "bcpv0SoGGVwU23+3nF7YJ2BSX80r64AInCEZgAuCRoI=",
|
| 14 |
+
"test.json": "WNyAFlYnnkTg3N4r9ja8Kh2HwlO8jr7EncOBBa9oI48="
|
| 15 |
+
}
|
| 16 |
+
},
|
| 17 |
+
"provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
|
| 18 |
+
"webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.GemmFastGelu" }
|
| 19 |
+
}
|
build/webgpu/test.json
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"op": "com.microsoft.GemmFastGelu",
|
| 3 |
+
"cases": [
|
| 4 |
+
{
|
| 5 |
+
"name": "rank2_bias_f32",
|
| 6 |
+
"provenance": {
|
| 7 |
+
"notes": "A plain (M, K) x (K, N) with bias, both dimensions past one 64-wide tile so the kernel's tile loop and its bounds checks both run."
|
| 8 |
+
},
|
| 9 |
+
"inputs": {
|
| 10 |
+
"X": {
|
| 11 |
+
"dtype": "float32",
|
| 12 |
+
"shape": [96, 80],
|
| 13 |
+
"data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.18000000000000002, "cosStep": 0.32 }
|
| 14 |
+
},
|
| 15 |
+
"W": {
|
| 16 |
+
"dtype": "float32",
|
| 17 |
+
"shape": [80, 72],
|
| 18 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.12, "cosStep": 0.24000000000000002 }
|
| 19 |
+
},
|
| 20 |
+
"bias": {
|
| 21 |
+
"dtype": "float32",
|
| 22 |
+
"shape": [72],
|
| 23 |
+
"data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.3, "cosStep": 0.14 }
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
"outputs": { "Y": { "dtype": "float32", "shape": [96, 72], "tolerance": 0.00002, "relTolerance": 0.0002 } }
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"name": "rank2_nobias_f32",
|
| 30 |
+
"provenance": { "notes": "The same shape without a bias, which takes the other binding set." },
|
| 31 |
+
"inputs": {
|
| 32 |
+
"X": {
|
| 33 |
+
"dtype": "float32",
|
| 34 |
+
"shape": [96, 80],
|
| 35 |
+
"data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.19, "cosStep": 0.33 }
|
| 36 |
+
},
|
| 37 |
+
"W": {
|
| 38 |
+
"dtype": "float32",
|
| 39 |
+
"shape": [80, 72],
|
| 40 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.13, "cosStep": 0.25 }
|
| 41 |
+
}
|
| 42 |
+
},
|
| 43 |
+
"outputs": { "Y": { "dtype": "float32", "shape": [96, 72], "tolerance": 0.00002, "relTolerance": 0.0002 } }
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"name": "rank3_batched_bias_f32",
|
| 47 |
+
"provenance": {
|
| 48 |
+
"notes": "A rank-3 X: every leading axis is another row, so a (2, 48, 64) input is a 96-row product rather than a batched matmul."
|
| 49 |
+
},
|
| 50 |
+
"inputs": {
|
| 51 |
+
"X": {
|
| 52 |
+
"dtype": "float32",
|
| 53 |
+
"shape": [2, 48, 64],
|
| 54 |
+
"data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.2, "cosStep": 0.33999999999999997 }
|
| 55 |
+
},
|
| 56 |
+
"W": {
|
| 57 |
+
"dtype": "float32",
|
| 58 |
+
"shape": [64, 40],
|
| 59 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.14, "cosStep": 0.26 }
|
| 60 |
+
},
|
| 61 |
+
"bias": {
|
| 62 |
+
"dtype": "float32",
|
| 63 |
+
"shape": [40],
|
| 64 |
+
"data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.31999999999999995, "cosStep": 0.16 }
|
| 65 |
+
}
|
| 66 |
+
},
|
| 67 |
+
"outputs": { "Y": { "dtype": "float32", "shape": [2, 48, 40], "tolerance": 0.00002, "relTolerance": 0.0002 } }
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"name": "rank4_batched_nobias_f32",
|
| 71 |
+
"provenance": {
|
| 72 |
+
"notes": "Rank 4, no bias -- the leading-axis collapse must not depend on how many axes there are."
|
| 73 |
+
},
|
| 74 |
+
"inputs": {
|
| 75 |
+
"X": {
|
| 76 |
+
"dtype": "float32",
|
| 77 |
+
"shape": [2, 3, 16, 32],
|
| 78 |
+
"data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.21000000000000002, "cosStep": 0.35 }
|
| 79 |
+
},
|
| 80 |
+
"W": {
|
| 81 |
+
"dtype": "float32",
|
| 82 |
+
"shape": [32, 24],
|
| 83 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.15, "cosStep": 0.27 }
|
| 84 |
+
}
|
| 85 |
+
},
|
| 86 |
+
"outputs": { "Y": { "dtype": "float32", "shape": [2, 3, 16, 24], "tolerance": 0.00002, "relTolerance": 0.0002 } }
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
"name": "small_unaligned_bias_f32",
|
| 90 |
+
"provenance": {
|
| 91 |
+
"notes": "M, K and N all smaller than one tile and none a multiple of the 4x4 micro-tile, so every bounds check is exercised."
|
| 92 |
+
},
|
| 93 |
+
"inputs": {
|
| 94 |
+
"X": {
|
| 95 |
+
"dtype": "float32",
|
| 96 |
+
"shape": [7, 13],
|
| 97 |
+
"data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.22000000000000003, "cosStep": 0.36 }
|
| 98 |
+
},
|
| 99 |
+
"W": {
|
| 100 |
+
"dtype": "float32",
|
| 101 |
+
"shape": [13, 11],
|
| 102 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.16, "cosStep": 0.28 }
|
| 103 |
+
},
|
| 104 |
+
"bias": {
|
| 105 |
+
"dtype": "float32",
|
| 106 |
+
"shape": [11],
|
| 107 |
+
"data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.33999999999999997, "cosStep": 0.18 }
|
| 108 |
+
}
|
| 109 |
+
},
|
| 110 |
+
"outputs": { "Y": { "dtype": "float32", "shape": [7, 11], "tolerance": 0.00002, "relTolerance": 0.0002 } }
|
| 111 |
+
},
|
| 112 |
+
{
|
| 113 |
+
"name": "single_row_bias_f32",
|
| 114 |
+
"provenance": { "notes": "One row: the GEMV shape, where the M tile is almost entirely padding." },
|
| 115 |
+
"inputs": {
|
| 116 |
+
"X": {
|
| 117 |
+
"dtype": "float32",
|
| 118 |
+
"shape": [1, 64],
|
| 119 |
+
"data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.23, "cosStep": 0.37 }
|
| 120 |
+
},
|
| 121 |
+
"W": {
|
| 122 |
+
"dtype": "float32",
|
| 123 |
+
"shape": [64, 48],
|
| 124 |
+
"data": {
|
| 125 |
+
"kind": "fillFloat32",
|
| 126 |
+
"scale": 0.5,
|
| 127 |
+
"sinStep": 0.16999999999999998,
|
| 128 |
+
"cosStep": 0.29000000000000004
|
| 129 |
+
}
|
| 130 |
+
},
|
| 131 |
+
"bias": {
|
| 132 |
+
"dtype": "float32",
|
| 133 |
+
"shape": [48],
|
| 134 |
+
"data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.35, "cosStep": 0.19 }
|
| 135 |
+
}
|
| 136 |
+
},
|
| 137 |
+
"outputs": { "Y": { "dtype": "float32", "shape": [1, 48], "tolerance": 0.00002, "relTolerance": 0.0002 } }
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
"name": "f16_rank3_bias",
|
| 141 |
+
"provenance": {
|
| 142 |
+
"notes": "float16 operands; the product accumulates in f32 and the activation runs before the single narrowing store."
|
| 143 |
+
},
|
| 144 |
+
"inputs": {
|
| 145 |
+
"X": {
|
| 146 |
+
"dtype": "float16",
|
| 147 |
+
"shape": [2, 32, 48],
|
| 148 |
+
"data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.24000000000000002, "cosStep": 0.38 }
|
| 149 |
+
},
|
| 150 |
+
"W": {
|
| 151 |
+
"dtype": "float16",
|
| 152 |
+
"shape": [48, 32],
|
| 153 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.18, "cosStep": 0.30000000000000004 }
|
| 154 |
+
},
|
| 155 |
+
"bias": {
|
| 156 |
+
"dtype": "float16",
|
| 157 |
+
"shape": [32],
|
| 158 |
+
"data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.36, "cosStep": 0.2 }
|
| 159 |
+
}
|
| 160 |
+
},
|
| 161 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [2, 32, 32], "tolerance": 0.006, "relTolerance": 0.006 } }
|
| 162 |
+
},
|
| 163 |
+
{
|
| 164 |
+
"name": "f16_rank2_nobias",
|
| 165 |
+
"provenance": { "notes": "float16 without a bias." },
|
| 166 |
+
"inputs": {
|
| 167 |
+
"X": {
|
| 168 |
+
"dtype": "float16",
|
| 169 |
+
"shape": [48, 32],
|
| 170 |
+
"data": { "kind": "fillFloat32", "scale": 0.9, "sinStep": 0.25, "cosStep": 0.39 }
|
| 171 |
+
},
|
| 172 |
+
"W": {
|
| 173 |
+
"dtype": "float16",
|
| 174 |
+
"shape": [32, 24],
|
| 175 |
+
"data": { "kind": "fillFloat32", "scale": 0.5, "sinStep": 0.19, "cosStep": 0.31 }
|
| 176 |
+
}
|
| 177 |
+
},
|
| 178 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [48, 24], "tolerance": 0.006, "relTolerance": 0.006 } }
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
"name": "pinned_rank2_bias",
|
| 182 |
+
"provenance": {
|
| 183 |
+
"notes": "Hand-computed from the schema's FastGelu definition -- 0.5x(1 + tanh(sqrt(2/pi)(x + 0.044715 x^3))) -- applied to a float64 product, independent of the port's tiling and of its shared gelu helper. The operands are large enough that FastGelu is well outside its near-linear region, so the pin is sensitive to the activation itself."
|
| 184 |
+
},
|
| 185 |
+
"inputs": {
|
| 186 |
+
"X": {
|
| 187 |
+
"dtype": "float32",
|
| 188 |
+
"shape": [5, 6],
|
| 189 |
+
"data": {
|
| 190 |
+
"kind": "values",
|
| 191 |
+
"values": [-0.568, 1.402, -1.744, 0.805, -0.684, -0.366, -1.898, -0.274, 0.008, -1.719, 0.344, 1.185, -0.865, 1.863, -0.792, 1.783, 0.703, -0.614, -1.244, 1.186, 1.21, -0.428, -0.236, 1.268, -1.492, -1.469, 0.673, 1.956, -1.883, -0.872]
|
| 192 |
+
}
|
| 193 |
+
},
|
| 194 |
+
"W": {
|
| 195 |
+
"dtype": "float32",
|
| 196 |
+
"shape": [6, 4],
|
| 197 |
+
"data": {
|
| 198 |
+
"kind": "values",
|
| 199 |
+
"values": [-0.362, -1.699, 1.664, -0.171, 0.412, -0.373, -1.421, 1.657, 1.194, -0.13, -1.707, -0.827, -0.156, -1.618, 1.339, -0.16, -1.723, 1.951, 0.603, 1.677, 1.217, 0.273, 1.35, 0.713]
|
| 200 |
+
}
|
| 201 |
+
},
|
| 202 |
+
"bias": {
|
| 203 |
+
"dtype": "float32",
|
| 204 |
+
"shape": [4],
|
| 205 |
+
"data": { "kind": "values", "values": [-0.818, 0.02, 0.585, 0.387] }
|
| 206 |
+
}
|
| 207 |
+
},
|
| 208 |
+
"outputs": {
|
| 209 |
+
"Y": {
|
| 210 |
+
"dtype": "float32",
|
| 211 |
+
"shape": [5, 4],
|
| 212 |
+
"data": {
|
| 213 |
+
"kind": "values",
|
| 214 |
+
"values": [-0.099208, -0.041387, 0.626313, 2.704125, 0.716692, 7.121855, -0.009083, 1.89771, -0.004677, -0.16984, -0.072533, 4.732758, 3.581808, 2.075684, -0.000021, 2.106721, 1.733585, -0.000052, -0.167767, -0.0]
|
| 215 |
+
},
|
| 216 |
+
"tolerance": 0.00002,
|
| 217 |
+
"relTolerance": 0.0002
|
| 218 |
+
}
|
| 219 |
+
}
|
| 220 |
+
},
|
| 221 |
+
{
|
| 222 |
+
"name": "sgmat_aligned_bias_f32",
|
| 223 |
+
"provenance": {
|
| 224 |
+
"notes": "M, K and N all meet the subgroup-matrix gate (M >= 32, K % 32 == 0, N % 64 == 0), so this is the fixture that selects sgmat_bias rather than the tiled kernel. Two row tiles and two column tiles past the 32x64 tile, so the tile loop runs and the fused gelu is checked on every store site."
|
| 225 |
+
},
|
| 226 |
+
"inputs": {
|
| 227 |
+
"X": {
|
| 228 |
+
"dtype": "float32",
|
| 229 |
+
"shape": [64, 64],
|
| 230 |
+
"data": { "kind": "fillFloat32", "scale": 0.7, "sinStep": 0.13, "cosStep": 0.29 }
|
| 231 |
+
},
|
| 232 |
+
"W": {
|
| 233 |
+
"dtype": "float32",
|
| 234 |
+
"shape": [64, 128],
|
| 235 |
+
"data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.21, "cosStep": 0.17 }
|
| 236 |
+
},
|
| 237 |
+
"bias": {
|
| 238 |
+
"dtype": "float32",
|
| 239 |
+
"shape": [128],
|
| 240 |
+
"data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.11, "cosStep": 0.37 }
|
| 241 |
+
}
|
| 242 |
+
},
|
| 243 |
+
"outputs": { "Y": { "dtype": "float32", "shape": [64, 128], "tolerance": 0.00002, "relTolerance": 0.0002 } }
|
| 244 |
+
},
|
| 245 |
+
{
|
| 246 |
+
"name": "sgmat_aligned_nobias_f32",
|
| 247 |
+
"provenance": {
|
| 248 |
+
"notes": "The no-bias arm of the same gate: sgmat renders its store without the bias term, which is a separate set of write sites from the bias arm."
|
| 249 |
+
},
|
| 250 |
+
"inputs": {
|
| 251 |
+
"X": {
|
| 252 |
+
"dtype": "float32",
|
| 253 |
+
"shape": [96, 96],
|
| 254 |
+
"data": { "kind": "fillFloat32", "scale": 0.7, "sinStep": 0.13, "cosStep": 0.29 }
|
| 255 |
+
},
|
| 256 |
+
"W": {
|
| 257 |
+
"dtype": "float32",
|
| 258 |
+
"shape": [96, 192],
|
| 259 |
+
"data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.21, "cosStep": 0.17 }
|
| 260 |
+
}
|
| 261 |
+
},
|
| 262 |
+
"outputs": { "Y": { "dtype": "float32", "shape": [96, 192], "tolerance": 0.00002, "relTolerance": 0.0002 } }
|
| 263 |
+
},
|
| 264 |
+
{
|
| 265 |
+
"name": "sgmat_aligned_bias_f16",
|
| 266 |
+
"provenance": {
|
| 267 |
+
"notes": "f16 operands drive the matrix units at their f16 component type while the accumulator and the fused gelu stay f32. The f16 gate admits M >= 2, so this also covers a row count under one tile."
|
| 268 |
+
},
|
| 269 |
+
"inputs": {
|
| 270 |
+
"X": {
|
| 271 |
+
"dtype": "float16",
|
| 272 |
+
"shape": [32, 64],
|
| 273 |
+
"data": { "kind": "fillFloat32", "scale": 0.7, "sinStep": 0.13, "cosStep": 0.29 }
|
| 274 |
+
},
|
| 275 |
+
"W": {
|
| 276 |
+
"dtype": "float16",
|
| 277 |
+
"shape": [64, 128],
|
| 278 |
+
"data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.21, "cosStep": 0.17 }
|
| 279 |
+
},
|
| 280 |
+
"bias": {
|
| 281 |
+
"dtype": "float16",
|
| 282 |
+
"shape": [128],
|
| 283 |
+
"data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.11, "cosStep": 0.37 }
|
| 284 |
+
}
|
| 285 |
+
},
|
| 286 |
+
"outputs": { "Y": { "dtype": "float16", "shape": [32, 128], "tolerance": 0.02, "relTolerance": 0.02 } }
|
| 287 |
+
}
|
| 288 |
+
]
|
| 289 |
+
}
|