Xenova HF Staff commited on
Commit
078aa71
·
verified ·
1 Parent(s): 47389e2

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,68 @@
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.LinearAttentionGate
10
+
11
+ `com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
12
+
13
+ ## Description
14
+
15
+ Fuses the gate projections used by `com.microsoft.LinearAttention`'s gated-delta recurrence: `decay = decay_scale * softplus(a + dt_bias)` and, when requested, `beta = sigmoid(b)`. The last input axis is the head axis; `dt_bias` and `decay_scale` are float32 per-head vectors. Gate arithmetic is performed in float32 and narrowed only on store. Requesting `beta` requires `b`; an unconsumed `b` is permitted when `beta` is omitted.
16
+
17
+ See the [ONNX Runtime `LinearAttentionGate` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.LinearAttentionGate) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `a` | `aT` | `T` | — | — | Decay gate projection with shape `(B, T, H)`. Any rank of at least 1 is accepted; the last axis is the head count and the leading axes are folded. | required |
24
+ | `dt_bias` | `dtBiasT` | `TF` | `1` | — | Per-head float32 bias added to `a`, with shape (H). | required |
25
+ | `decay_scale` | `decayScaleT` | `TF` | `1` | — | Per-head float32 multiplier applied to `softplus(a + dt_bias)`, with shape `(H)`. For gated DeltaNet this is `-exp(A_log)`. | required |
26
+ | `b` | `bT` | `T` | — | — | Update-rate projection with the same shape as `a` when `beta` is requested. It is accepted but unused when `beta` is omitted. | optional |
27
+
28
+ ## Outputs
29
+
30
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
31
+ | --- | --- | --- | --- | --- | --- | --- |
32
+ | `decay` | `decayT` | `T` | same as `a` | same as `a` | `decay_scale * softplus(a + dt_bias)`, with the same shape as `a`. | required |
33
+ | `beta` | `betaT` | `T` | same as `a` | same as `a` | sigmoid(b), with the same shape as `a`. Requires the `b` input. | optional |
34
+
35
+ ## Type constraints
36
+
37
+ | Variable | Allowed dtypes |
38
+ | --- | --- |
39
+ | `T` | `float32`, `float16` |
40
+ | `TF` | `float32` |
41
+
42
+ ## Files
43
+
44
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
45
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
46
+ - [`test.json`](build/webgpu/test.json) — correctness cases
47
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
48
+ - [`linear-attention-gate.wgsl.jinja`](build/webgpu/linear-attention-gate.wgsl.jinja)
49
+
50
+ ## Use with `@huggingface/kernels`
51
+
52
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
53
+ It then allocates the result tensors automatically.
54
+
55
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
56
+
57
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
58
+
59
+ ```js
60
+ import { getKernel } from "@huggingface/kernels";
61
+
62
+ const kernel = await getKernel("webgpu-kernels/com.microsoft.LinearAttentionGate", { version: 1 });
63
+ const { decayT } = await kernel({
64
+ aT: { data: aTData, shape: [5] },
65
+ dtBiasT: { data: dtBiasTData, shape: [5] },
66
+ decayScaleT: { data: decayScaleTData, shape: [5] },
67
+ });
68
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "com.microsoft.LinearAttentionGate",
3
+ "tunableSpace": { "WORKGROUP_SIZE": [32, 64, 128, 256] },
4
+ "cases": [
5
+ {
6
+ "name": "gate-smoke-decode-h64",
7
+ "preset": "smoke",
8
+ "provenance": {
9
+ "notes": "One token: the decode shape, where the whole op is a single tiny dispatch and launch overhead dominates."
10
+ },
11
+ "inputs": {
12
+ "aT": {
13
+ "dtype": "float32",
14
+ "shape": [1, 1, 64],
15
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 2.0 }
16
+ },
17
+ "dtBiasT": { "dtype": "float32", "shape": [64], "data": { "kind": "linspace", "start": -1.5, "end": 1.0 } },
18
+ "decayScaleT": { "dtype": "float32", "shape": [64], "data": { "kind": "linspace", "start": -3.0, "end": -0.2 } },
19
+ "bT": {
20
+ "dtype": "float32",
21
+ "shape": [1, 1, 64],
22
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 2.5 }
23
+ }
24
+ },
25
+ "outputs": {
26
+ "decayT": { "dtype": "float32", "shape": [1, 1, 64] },
27
+ "betaT": { "dtype": "float32", "shape": [1, 1, 64] }
28
+ },
29
+ "bench": {
30
+ "metrics": [{ "type": "bandwidth", "value": "4 * (4 * numel(shapes.aT) + 2 * numel(shapes.dtBiasT))" }]
31
+ }
32
+ },
33
+ {
34
+ "name": "gate-smoke-prefill-t512-h32",
35
+ "preset": "smoke",
36
+ "provenance": { "notes": "Prefill over 512 tokens on the vectorized path." },
37
+ "inputs": {
38
+ "aT": {
39
+ "dtype": "float32",
40
+ "shape": [1, 512, 32],
41
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 2.0 }
42
+ },
43
+ "dtBiasT": { "dtype": "float32", "shape": [32], "data": { "kind": "linspace", "start": -1.5, "end": 1.0 } },
44
+ "decayScaleT": { "dtype": "float32", "shape": [32], "data": { "kind": "linspace", "start": -3.0, "end": -0.2 } },
45
+ "bT": {
46
+ "dtype": "float32",
47
+ "shape": [1, 512, 32],
48
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 2.5 }
49
+ }
50
+ },
51
+ "outputs": {
52
+ "decayT": { "dtype": "float32", "shape": [1, 512, 32] },
53
+ "betaT": { "dtype": "float32", "shape": [1, 512, 32] }
54
+ },
55
+ "bench": {
56
+ "metrics": [{ "type": "bandwidth", "value": "4 * (4 * numel(shapes.aT) + 2 * numel(shapes.dtBiasT))" }]
57
+ }
58
+ },
59
+ {
60
+ "name": "gate-smoke-prefill-t256-h30",
61
+ "preset": "smoke",
62
+ "provenance": { "notes": "Head count 30 is not a multiple of four, so this is the scalar path at prefill width." },
63
+ "inputs": {
64
+ "aT": {
65
+ "dtype": "float32",
66
+ "shape": [1, 256, 30],
67
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 2.0 }
68
+ },
69
+ "dtBiasT": { "dtype": "float32", "shape": [30], "data": { "kind": "linspace", "start": -1.5, "end": 1.0 } },
70
+ "decayScaleT": { "dtype": "float32", "shape": [30], "data": { "kind": "linspace", "start": -3.0, "end": -0.2 } },
71
+ "bT": {
72
+ "dtype": "float32",
73
+ "shape": [1, 256, 30],
74
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.23, "scale": 2.5 }
75
+ }
76
+ },
77
+ "outputs": {
78
+ "decayT": { "dtype": "float32", "shape": [1, 256, 30] },
79
+ "betaT": { "dtype": "float32", "shape": [1, 256, 30] }
80
+ },
81
+ "bench": {
82
+ "metrics": [{ "type": "bandwidth", "value": "4 * (4 * numel(shapes.aT) + 2 * numel(shapes.dtBiasT))" }]
83
+ }
84
+ },
85
+ {
86
+ "name": "gate-smoke-decay-only-b2-t128-h64",
87
+ "preset": "smoke",
88
+ "provenance": {
89
+ "notes": "No b input: two of the four tensors disappear, which is the bandwidth the optional-pair binding actually saves."
90
+ },
91
+ "inputs": {
92
+ "aT": {
93
+ "dtype": "float32",
94
+ "shape": [2, 128, 64],
95
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29, "scale": 2.0 }
96
+ },
97
+ "dtBiasT": { "dtype": "float32", "shape": [64], "data": { "kind": "linspace", "start": -1.5, "end": 1.0 } },
98
+ "decayScaleT": { "dtype": "float32", "shape": [64], "data": { "kind": "linspace", "start": -3.0, "end": -0.2 } }
99
+ },
100
+ "outputs": { "decayT": { "dtype": "float32", "shape": [2, 128, 64] } },
101
+ "bench": {
102
+ "metrics": [{ "type": "bandwidth", "value": "4 * (2 * numel(shapes.aT) + 2 * numel(shapes.dtBiasT))" }]
103
+ }
104
+ },
105
+ {
106
+ "name": "gate-qwen3next-decode-s1-h32",
107
+ "preset": "model",
108
+ "provenance": {
109
+ "notes": "Qwen3-Next class defaults (linear_num_value_heads 32): the decay/beta gate for one decode step."
110
+ },
111
+ "vars": { "batch": 1, "seq": 1, "heads": 32 },
112
+ "inputs": {
113
+ "aT": { "shape": [1, 1, 32], "dtype": "float32", "dist": "normal", "seed": 8400, "scale": 0.5 },
114
+ "dtBiasT": { "shape": [32], "dtype": "float32", "dist": "normal", "seed": 8401, "scale": 0.2 },
115
+ "decayScaleT": { "shape": [32], "dtype": "float32", "dist": "uniform", "seed": 8402, "min": 0.5, "max": 1.5 },
116
+ "bT": { "shape": [1, 1, 32], "dtype": "float32", "dist": "normal", "seed": 8403, "scale": 0.5 }
117
+ },
118
+ "outputs": {
119
+ "decayT": { "shape": [1, 1, 32], "dtype": "float32" },
120
+ "betaT": { "shape": [1, 1, 32], "dtype": "float32" }
121
+ },
122
+ "bench": {
123
+ "metrics": [{ "type": "bandwidth", "value": "4 * (4 * numel(shapes.aT) + 2 * numel(shapes.dtBiasT))" }]
124
+ }
125
+ },
126
+ {
127
+ "name": "gate-qwen3next-prefill-s2048-h32",
128
+ "preset": "model",
129
+ "provenance": { "notes": "Qwen3-Next class defaults over a 2048-token prefill chunk." },
130
+ "vars": { "batch": 1, "seq": 2048, "heads": 32 },
131
+ "inputs": {
132
+ "aT": { "shape": [1, 2048, 32], "dtype": "float32", "dist": "normal", "seed": 8500, "scale": 0.5 },
133
+ "dtBiasT": { "shape": [32], "dtype": "float32", "dist": "normal", "seed": 8501, "scale": 0.2 },
134
+ "decayScaleT": { "shape": [32], "dtype": "float32", "dist": "uniform", "seed": 8502, "min": 0.5, "max": 1.5 },
135
+ "bT": { "shape": [1, 2048, 32], "dtype": "float32", "dist": "normal", "seed": 8503, "scale": 0.5 }
136
+ },
137
+ "outputs": {
138
+ "decayT": { "shape": [1, 2048, 32], "dtype": "float32" },
139
+ "betaT": { "shape": [1, 2048, 32], "dtype": "float32" }
140
+ },
141
+ "bench": {
142
+ "metrics": [{ "type": "bandwidth", "value": "4 * (4 * numel(shapes.aT) + 2 * numel(shapes.dtBiasT))" }]
143
+ }
144
+ },
145
+ {
146
+ "name": "gate-qwen3next-prefill-s8192-h32",
147
+ "preset": "model",
148
+ "provenance": {
149
+ "notes": "Qwen3-Next class defaults over an 8192-token prefill chunk, where the gate is purely bandwidth-bound."
150
+ },
151
+ "vars": { "batch": 1, "seq": 8192, "heads": 32 },
152
+ "inputs": {
153
+ "aT": { "shape": [1, 8192, 32], "dtype": "float32", "dist": "normal", "seed": 8600, "scale": 0.5 },
154
+ "dtBiasT": { "shape": [32], "dtype": "float32", "dist": "normal", "seed": 8601, "scale": 0.2 },
155
+ "decayScaleT": { "shape": [32], "dtype": "float32", "dist": "uniform", "seed": 8602, "min": 0.5, "max": 1.5 },
156
+ "bT": { "shape": [1, 8192, 32], "dtype": "float32", "dist": "normal", "seed": 8603, "scale": 0.5 }
157
+ },
158
+ "outputs": {
159
+ "decayT": { "shape": [1, 8192, 32], "dtype": "float32" },
160
+ "betaT": { "shape": [1, 8192, 32], "dtype": "float32" }
161
+ },
162
+ "bench": {
163
+ "metrics": [{ "type": "bandwidth", "value": "4 * (4 * numel(shapes.aT) + 2 * numel(shapes.dtBiasT))" }]
164
+ }
165
+ }
166
+ ]
167
+ }
build/webgpu/linear-attention-gate.wgsl.jinja ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif -%}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ {% if vectorized %}
7
+ // Number of vec4 parameter slots across the head axis.
8
+ {% else %}
9
+ // Number of scalar parameter slots across the head axis.
10
+ {% endif %}
11
+ const HEAD_ITEMS: u32 = {{ headItems }}u;
12
+ const GATE_ITEMS: u32 = {{ gateItems }}u;
13
+ const WORKGROUP_SIZE: u32 = {{ workgroupSize }}u;
14
+
15
+ {% if hasBeta %}
16
+ fn sigmoid_safe(x: f32) -> f32 {
17
+ if (x >= 0.0) {
18
+ let z = exp(-x);
19
+ return 1.0 / (1.0 + z);
20
+ }
21
+ let z = exp(x);
22
+ return z / (1.0 + z);
23
+ }
24
+
25
+ {% endif %}
26
+ // WGSL has no log1p builtin, and log(1.0 + y) loses relative accuracy for small y: rounding
27
+ // 1.0 + y costs up to half an ulp of 1.0, which is enormous next to y itself. Below the
28
+ // crossover the series is the accurate form -- its truncation is y^7 / 8, under 6e-9 relative
29
+ // at y = 0.125 -- and above it the same half-ulp is under 6e-7 relative because log1p(y) has
30
+ // grown past 0.118. The algebraic (1.0 + y) - 1.0 correction is not usable here: a backend is
31
+ // free to fold that subtraction back to y, which silently restores the error it removes.
32
+ fn log1p_pos(y: f32) -> f32 {
33
+ if (y < 0.125) {
34
+ return y * (1.0 + y * (-0.5 + y * (0.33333334 + y * (-0.25 + y * (0.2 + y * (-0.16666667 + y * 0.14285714))))));
35
+ }
36
+ return log(1.0 + y);
37
+ }
38
+
39
+ fn softplus(x: f32) -> f32 {
40
+ // Both arms feed log1p an exp() of a non-positive argument, so the operand stays in (0, 1]
41
+ // and never overflows; for large x the increment rounds away and the result is exactly x,
42
+ // which is the correct limit.
43
+ if (x > 0.0) {
44
+ return x + log1p_pos(exp(-x));
45
+ }
46
+ return log1p_pos(exp(x));
47
+ }
48
+
49
+ // The gate arithmetic is FP32 for every tensor type. decay feeds exp() inside the
50
+ // LinearAttention recurrence, where any precision lost here is amplified exponentially,
51
+ // so the operands are widened on load and the result is narrowed only on store.
52
+ @compute @workgroup_size(WORKGROUP_SIZE, 1, 1)
53
+ fn main(
54
+ @builtin(global_invocation_id) gid: vec3<u32>,
55
+ @builtin(num_workgroups) nwg: vec3<u32>
56
+ ) {
57
+ // Rebuild the flat invocation index after the 2D dispatch fold.
58
+ let item = gid.x + gid.y * nwg.x * WORKGROUP_SIZE;
59
+ if (item >= GATE_ITEMS) {
60
+ return;
61
+ }
62
+
63
+ // The last axis is the head axis, so the per-head parameter index is the flat index
64
+ // modulo the head count. The vectorized path holds because the head count is a multiple
65
+ // of four: a vec4 of adjacent gates never straddles two heads' parameter slots.
66
+ let head = item % HEAD_ITEMS;
67
+ {% if vectorized %}
68
+ let biased = vec4<f32>(a[item]) + dt_bias[head];
69
+ let activated = vec4<f32>(
70
+ softplus(biased.x),
71
+ softplus(biased.y),
72
+ softplus(biased.z),
73
+ softplus(biased.w)
74
+ );
75
+ decay[item] = {{ gateElement }}(decay_scale[head] * activated);
76
+ {% if hasBeta %}
77
+ let raw_beta = vec4<f32>(b[item]);
78
+ beta[item] = {{ gateElement }}(vec4<f32>(
79
+ sigmoid_safe(raw_beta.x),
80
+ sigmoid_safe(raw_beta.y),
81
+ sigmoid_safe(raw_beta.z),
82
+ sigmoid_safe(raw_beta.w)
83
+ ));
84
+ {% endif %}
85
+ {% else %}
86
+ let biased = f32(a[item]) + dt_bias[head];
87
+ decay[item] = {{ gateElement }}(decay_scale[head] * softplus(biased));
88
+ {% if hasBeta %}
89
+ beta[item] = {{ gateElement }}(sigmoid_safe(f32(b[item])));
90
+ {% endif %}
91
+ {% endif %}
92
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "com.microsoft",
3
+ "name": "LinearAttentionGate",
4
+ "sinceVersion": 1,
5
+ "description": "Fuses the gate projections used by `com.microsoft.LinearAttention`'s gated-delta recurrence: `decay = decay_scale * softplus(a + dt_bias)` and, when requested, `beta = sigmoid(b)`. The last input axis is the head axis; `dt_bias` and `decay_scale` are float32 per-head vectors. Gate arithmetic is performed in float32 and narrowed only on store. Requesting `beta` requires `b`; an unconsumed `b` is permitted when `beta` is omitted.",
6
+ "inputs": [
7
+ {
8
+ "role": "a",
9
+ "dtype": "T",
10
+ "description": "Decay gate projection with shape `(B, T, H)`. Any rank of at least 1 is accepted; the last axis is the head count and the leading axes are folded."
11
+ },
12
+ {
13
+ "role": "dt_bias",
14
+ "dtype": "TF",
15
+ "rank": 1,
16
+ "description": "Per-head float32 bias added to `a`, with shape (H)."
17
+ },
18
+ {
19
+ "role": "decay_scale",
20
+ "dtype": "TF",
21
+ "rank": 1,
22
+ "description": "Per-head float32 multiplier applied to `softplus(a + dt_bias)`, with shape `(H)`. For gated DeltaNet this is `-exp(A_log)`."
23
+ },
24
+ {
25
+ "role": "b",
26
+ "dtype": "T",
27
+ "optional": true,
28
+ "description": "Update-rate projection with the same shape as `a` when `beta` is requested. It is accepted but unused when `beta` is omitted."
29
+ }
30
+ ],
31
+ "outputs": [
32
+ {
33
+ "role": "decay",
34
+ "dtype": "T",
35
+ "rank": "ranks.aT",
36
+ "shape": "shapes.aT",
37
+ "description": "`decay_scale * softplus(a + dt_bias)`, with the same shape as `a`."
38
+ },
39
+ {
40
+ "role": "beta",
41
+ "dtype": "T",
42
+ "rank": "ranks.aT",
43
+ "optional": true,
44
+ "shape": "shapes.aT",
45
+ "description": "sigmoid(b), with the same shape as `a`. Requires the `b` input."
46
+ }
47
+ ],
48
+ "typeConstraints": { "T": ["float32", "float16"], "TF": ["float32"] },
49
+ "tunables": { "WORKGROUP_SIZE": 64 },
50
+ "args": {
51
+ "aT": { "kind": "tensor", "semantic": "a", "role": "input" },
52
+ "dtBiasT": { "kind": "tensor", "semantic": "dt_bias", "role": "weights" },
53
+ "decayScaleT": { "kind": "tensor", "semantic": "decay_scale", "role": "weights" },
54
+ "bT": { "kind": "tensor", "semantic": "b", "role": "input", "required": false },
55
+ "decayT": { "kind": "tensor", "semantic": "decay", "role": "output" },
56
+ "betaT": { "kind": "tensor", "semantic": "beta", "role": "output", "required": false }
57
+ },
58
+ "derive": {
59
+ "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
60
+ "foldedDispatchCapacity": "device.limits.maxComputeWorkgroupsPerDimension * device.limits.maxComputeWorkgroupsPerDimension",
61
+ "numHeads": "dim(shapes.aT, ranks.aT - 1)",
62
+ "gateCount": "numel(shapes.aT)",
63
+ "headsVec4": "numHeads / 4",
64
+ "gateVec4Count": "gateCount / 4",
65
+ "gateDtype": "tensorDtypes.aT",
66
+ "gateDtypeOk": "(gateDtype == \"float32\" or gateDtype == \"float16\") and f16Ok(dtypes.T)",
67
+ "paramsOk": "ranks.aT >= 1 and numHeads > 0 and ranks.dtBiasT == 1 and ranks.decayScaleT == 1 and tensorDtypes.dtBiasT == \"float32\" and tensorDtypes.decayScaleT == \"float32\" and dim(shapes.dtBiasT, 0) == numHeads and dim(shapes.decayScaleT, 0) == numHeads",
68
+ "tensorContract": "gateDtypeOk and paramsOk and sameShape(shapes.decayT, shapes.aT) and tensorDtypes.decayT == gateDtype",
69
+ "betaContract": "tensorContract and present.bT and present.betaT and sameShape(shapes.bT, shapes.aT) and sameShape(shapes.betaT, shapes.aT) and tensorDtypes.bT == gateDtype and tensorDtypes.betaT == gateDtype",
70
+ "decayOnlyContract": "tensorContract and not present.betaT",
71
+ "workgroupFits": "tunables.WORKGROUP_SIZE > 0 and tunables.WORKGROUP_SIZE <= deviceWorkgroupCap",
72
+ "scalarDispatchFits": "ceilDiv(gateCount, tunables.WORKGROUP_SIZE) <= foldedDispatchCapacity",
73
+ "vec4DispatchFits": "numHeads % 4 == 0 and ceilDiv(gateVec4Count, tunables.WORKGROUP_SIZE) <= foldedDispatchCapacity"
74
+ },
75
+ "bindingSets": {
76
+ "decayOnlyIo": [
77
+ {
78
+ "name": "a",
79
+ "arg": "aT",
80
+ "semantic": "a",
81
+ "buffer": { "type": "read-only-storage" },
82
+ "elementType": "$gateElement",
83
+ "length": "$gateItems"
84
+ },
85
+ {
86
+ "name": "dt_bias",
87
+ "arg": "dtBiasT",
88
+ "semantic": "dt_bias",
89
+ "buffer": { "type": "read-only-storage" },
90
+ "elementType": "$paramElement",
91
+ "length": "$headItems"
92
+ },
93
+ {
94
+ "name": "decay_scale",
95
+ "arg": "decayScaleT",
96
+ "semantic": "decay_scale",
97
+ "buffer": { "type": "read-only-storage" },
98
+ "elementType": "$paramElement",
99
+ "length": "$headItems"
100
+ },
101
+ {
102
+ "name": "decay",
103
+ "arg": "decayT",
104
+ "semantic": "decay",
105
+ "buffer": { "type": "storage" },
106
+ "elementType": "$gateElement",
107
+ "length": "$gateItems"
108
+ }
109
+ ],
110
+ "withBetaIo": [
111
+ {
112
+ "name": "a",
113
+ "arg": "aT",
114
+ "semantic": "a",
115
+ "buffer": { "type": "read-only-storage" },
116
+ "elementType": "$gateElement",
117
+ "length": "$gateItems"
118
+ },
119
+ {
120
+ "name": "dt_bias",
121
+ "arg": "dtBiasT",
122
+ "semantic": "dt_bias",
123
+ "buffer": { "type": "read-only-storage" },
124
+ "elementType": "$paramElement",
125
+ "length": "$headItems"
126
+ },
127
+ {
128
+ "name": "decay_scale",
129
+ "arg": "decayScaleT",
130
+ "semantic": "decay_scale",
131
+ "buffer": { "type": "read-only-storage" },
132
+ "elementType": "$paramElement",
133
+ "length": "$headItems"
134
+ },
135
+ {
136
+ "name": "b",
137
+ "arg": "bT",
138
+ "semantic": "b",
139
+ "buffer": { "type": "read-only-storage" },
140
+ "elementType": "$gateElement",
141
+ "length": "$gateItems"
142
+ },
143
+ {
144
+ "name": "decay",
145
+ "arg": "decayT",
146
+ "semantic": "decay",
147
+ "buffer": { "type": "storage" },
148
+ "elementType": "$gateElement",
149
+ "length": "$gateItems"
150
+ },
151
+ {
152
+ "name": "beta",
153
+ "arg": "betaT",
154
+ "semantic": "beta",
155
+ "buffer": { "type": "storage" },
156
+ "elementType": "$gateElement",
157
+ "length": "$gateItems"
158
+ }
159
+ ]
160
+ },
161
+ "variants": [
162
+ {
163
+ "id": "vec4_gate_beta",
164
+ "priority": 30,
165
+ "when": ["betaContract", "workgroupFits", "vec4DispatchFits"],
166
+ "constants": {
167
+ "vectorized": true,
168
+ "hasBeta": true,
169
+ "usesF16": "gateDtype == \"float16\"",
170
+ "gateElement": "\"vec4<f16>\" if gateDtype == \"float16\" else \"vec4<f32>\"",
171
+ "paramElement": "\"vec4<f32>\"",
172
+ "headItems": "headsVec4",
173
+ "gateItems": "gateVec4Count",
174
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
175
+ },
176
+ "passes": [
177
+ {
178
+ "id": "main",
179
+ "name": "LinearAttentionGate.Vec4GateBeta",
180
+ "shader": "linear-attention-gate.wgsl.jinja",
181
+ "bindings": "withBetaIo",
182
+ "dispatch": { "threads": "gateVec4Count", "workgroupSize": "tunables.WORKGROUP_SIZE" }
183
+ }
184
+ ]
185
+ },
186
+ {
187
+ "id": "vec4_gate",
188
+ "priority": 20,
189
+ "when": ["decayOnlyContract", "workgroupFits", "vec4DispatchFits"],
190
+ "constants": {
191
+ "vectorized": true,
192
+ "hasBeta": false,
193
+ "usesF16": "gateDtype == \"float16\"",
194
+ "gateElement": "\"vec4<f16>\" if gateDtype == \"float16\" else \"vec4<f32>\"",
195
+ "paramElement": "\"vec4<f32>\"",
196
+ "headItems": "headsVec4",
197
+ "gateItems": "gateVec4Count",
198
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
199
+ },
200
+ "passes": [
201
+ {
202
+ "id": "main",
203
+ "name": "LinearAttentionGate.Vec4Gate",
204
+ "shader": "linear-attention-gate.wgsl.jinja",
205
+ "bindings": "decayOnlyIo",
206
+ "dispatch": { "threads": "gateVec4Count", "workgroupSize": "tunables.WORKGROUP_SIZE" }
207
+ }
208
+ ]
209
+ },
210
+ {
211
+ "id": "scalar_gate_beta",
212
+ "priority": 10,
213
+ "when": ["betaContract", "workgroupFits", "scalarDispatchFits"],
214
+ "constants": {
215
+ "vectorized": false,
216
+ "hasBeta": true,
217
+ "usesF16": "gateDtype == \"float16\"",
218
+ "gateElement": "\"f16\" if gateDtype == \"float16\" else \"f32\"",
219
+ "paramElement": "\"f32\"",
220
+ "headItems": "numHeads",
221
+ "gateItems": "gateCount",
222
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
223
+ },
224
+ "passes": [
225
+ {
226
+ "id": "main",
227
+ "name": "LinearAttentionGate.ScalarGateBeta",
228
+ "shader": "linear-attention-gate.wgsl.jinja",
229
+ "bindings": "withBetaIo",
230
+ "dispatch": { "threads": "gateCount", "workgroupSize": "tunables.WORKGROUP_SIZE" }
231
+ }
232
+ ]
233
+ },
234
+ {
235
+ "id": "scalar_gate",
236
+ "priority": 0,
237
+ "when": ["decayOnlyContract", "workgroupFits", "scalarDispatchFits"],
238
+ "constants": {
239
+ "vectorized": false,
240
+ "hasBeta": false,
241
+ "usesF16": "gateDtype == \"float16\"",
242
+ "gateElement": "\"f16\" if gateDtype == \"float16\" else \"f32\"",
243
+ "paramElement": "\"f32\"",
244
+ "headItems": "numHeads",
245
+ "gateItems": "gateCount",
246
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
247
+ },
248
+ "passes": [
249
+ {
250
+ "id": "main",
251
+ "name": "LinearAttentionGate.ScalarGate",
252
+ "shader": "linear-attention-gate.wgsl.jinja",
253
+ "bindings": "decayOnlyIo",
254
+ "dispatch": { "threads": "gateCount", "workgroupSize": "tunables.WORKGROUP_SIZE" }
255
+ }
256
+ ]
257
+ }
258
+ ]
259
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "com.microsoft.LinearAttentionGate",
3
+ "id": "_com_microsoft_linearattentiongate_webgpu_cfa91bb",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "1NTbbJYE62OsV1lkReUeXTrI7rnBiOY3HTg5ldTfPyk=",
11
+ "linear-attention-gate.wgsl.jinja": "NRzrWvQ2VrRHu5YsGLn5RGMIXHePxHum+umyWdbcWJs=",
12
+ "manifest.json": "s1kmQ2WU1JJPknyUoE8HJj3gUC7F44ubx+ZkAtbc8To=",
13
+ "test.json": "mRMxucSUiqKh/upG/J2xGsRxpeEDFhVLOYGYRCMQd2Y="
14
+ }
15
+ },
16
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
17
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.LinearAttentionGate" }
18
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "com.microsoft.LinearAttentionGate",
3
+ "cases": [
4
+ {
5
+ "name": "rank3_h8_vec4_gate_beta",
6
+ "provenance": {
7
+ "notes": "The (B, T, H) shape the schema names, with a head count that is a multiple of four so the vectorized path runs. Covers vec4_gate_beta. Tolerance: the measured kernel-vs-reference max relative deviation over this op's fixtures is 4.4e-7, which is the f32 floor for softplus just above the log1p series crossover; 2e-6 keeps roughly four times that as device margin."
8
+ },
9
+ "inputs": {
10
+ "aT": {
11
+ "dtype": "float32",
12
+ "shape": [2, 3, 8],
13
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.17, "scale": 2.5 }
14
+ },
15
+ "dtBiasT": { "dtype": "float32", "shape": [8], "data": { "kind": "linspace", "start": -1.5, "end": 1.1 } },
16
+ "decayScaleT": { "dtype": "float32", "shape": [8], "data": { "kind": "linspace", "start": -3.2, "end": -0.15 } },
17
+ "bT": {
18
+ "dtype": "float32",
19
+ "shape": [2, 3, 8],
20
+ "data": { "kind": "fillFloat32", "sinStep": 0.31, "cosStep": 0.13, "scale": 3.0 }
21
+ }
22
+ },
23
+ "outputs": {
24
+ "decayT": { "dtype": "float32", "shape": [2, 3, 8], "tolerance": 0, "relTolerance": 0.000002 },
25
+ "betaT": { "dtype": "float32", "shape": [2, 3, 8], "tolerance": 0, "relTolerance": 0.000002 }
26
+ }
27
+ },
28
+ {
29
+ "name": "rank2_h6_scalar_gate_beta",
30
+ "provenance": {
31
+ "notes": "Head count 6 is not a multiple of four, so the vectorized head-to-parameter mapping does not hold and the scalar path is the only eligible one. Covers scalar_gate_beta."
32
+ },
33
+ "inputs": {
34
+ "aT": {
35
+ "dtype": "float32",
36
+ "shape": [5, 6],
37
+ "data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 2.2 }
38
+ },
39
+ "dtBiasT": { "dtype": "float32", "shape": [6], "data": { "kind": "linspace", "start": -2.0, "end": 0.9 } },
40
+ "decayScaleT": { "dtype": "float32", "shape": [6], "data": { "kind": "linspace", "start": -2.6, "end": -0.2 } },
41
+ "bT": {
42
+ "dtype": "float32",
43
+ "shape": [5, 6],
44
+ "data": { "kind": "fillFloat32", "sinStep": 0.37, "cosStep": 0.11, "scale": 2.8 }
45
+ }
46
+ },
47
+ "outputs": {
48
+ "decayT": { "dtype": "float32", "shape": [5, 6], "tolerance": 0, "relTolerance": 0.000002 },
49
+ "betaT": { "dtype": "float32", "shape": [5, 6], "tolerance": 0, "relTolerance": 0.000002 }
50
+ }
51
+ },
52
+ {
53
+ "name": "rank4_h4_vec4_gate_only",
54
+ "provenance": {
55
+ "notes": "Rank 4 proves the leading axes are folded rather than parsed: only the last axis is the head axis. No b input, so no beta output. Covers vec4_gate."
56
+ },
57
+ "inputs": {
58
+ "aT": {
59
+ "dtype": "float32",
60
+ "shape": [2, 2, 3, 4],
61
+ "data": { "kind": "fillFloat32", "sinStep": 0.41, "cosStep": 0.23, "scale": 3.1 }
62
+ },
63
+ "dtBiasT": {
64
+ "dtype": "float32",
65
+ "shape": [4],
66
+ "data": { "kind": "values", "values": [-1.25, 0.4, 1.75, -0.6] }
67
+ },
68
+ "decayScaleT": {
69
+ "dtype": "float32",
70
+ "shape": [4],
71
+ "data": { "kind": "values", "values": [-1.5, -0.35, -2.8, -0.9] }
72
+ }
73
+ },
74
+ "outputs": { "decayT": { "dtype": "float32", "shape": [2, 2, 3, 4], "tolerance": 0, "relTolerance": 0.000002 } }
75
+ },
76
+ {
77
+ "name": "rank1_h5_scalar_gate_only",
78
+ "provenance": {
79
+ "notes": "Rank 1 is the single-token decode shape: the whole tensor is one head row, so every element reads a different parameter slot. Covers scalar_gate."
80
+ },
81
+ "inputs": {
82
+ "aT": {
83
+ "dtype": "float32",
84
+ "shape": [5],
85
+ "data": { "kind": "values", "values": [-9.5, -1.25, 0.0, 2.5, 14.0] }
86
+ },
87
+ "dtBiasT": {
88
+ "dtype": "float32",
89
+ "shape": [5],
90
+ "data": { "kind": "values", "values": [0.75, -0.5, 1.25, -1.75, 0.3] }
91
+ },
92
+ "decayScaleT": {
93
+ "dtype": "float32",
94
+ "shape": [5],
95
+ "data": { "kind": "values", "values": [-0.4, -1.6, -3.05, -0.85, -2.2] }
96
+ }
97
+ },
98
+ "outputs": { "decayT": { "dtype": "float32", "shape": [5], "tolerance": 0, "relTolerance": 0.000002 } }
99
+ },
100
+ {
101
+ "name": "b_without_beta_is_ignored",
102
+ "provenance": {
103
+ "notes": "The public schema requires b only in the forward direction: requesting beta requires b. ORT CPU and WebGPU accept b when beta is omitted and do not read it; this fixture prevents the manifest from imposing the stronger, non-schema b-implies-beta rule."
104
+ },
105
+ "inputs": {
106
+ "aT": {
107
+ "dtype": "float32",
108
+ "shape": [2, 4],
109
+ "data": { "kind": "fillFloat32", "sinStep": 0.27, "cosStep": 0.19, "scale": 1.7 }
110
+ },
111
+ "dtBiasT": { "dtype": "float32", "shape": [4], "data": { "kind": "linspace", "start": -0.7, "end": 0.8 } },
112
+ "decayScaleT": {
113
+ "dtype": "float32",
114
+ "shape": [4],
115
+ "data": { "kind": "values", "values": [-0.5, -1.0, -1.5, -2.0] }
116
+ },
117
+ "bT": {
118
+ "dtype": "float32",
119
+ "shape": [2, 4],
120
+ "data": { "kind": "fillFloat32", "sinStep": 0.43, "cosStep": 0.11, "scale": 4.0 }
121
+ }
122
+ },
123
+ "outputs": { "decayT": { "dtype": "float32", "shape": [2, 4], "tolerance": 0, "relTolerance": 0.000002 } }
124
+ },
125
+ {
126
+ "name": "rank2_h4_vec4_pinned_head_parameters",
127
+ "provenance": {
128
+ "notes": "Hand-computed, anchoring the op off its own reference. Row 0 is built so a + dt_bias == 0 in every head, making softplus exactly ln(2) = 0.69314718, so each head's decay is its decay_scale times that one constant -- a wrong head-to-parameter mapping cannot reproduce the column pattern. Row 1 biases to 25 in every head, above the x > 20 softplus threshold where softplus(x) == x exactly, so those four values are exact. beta pins both sigmoid branches (x >= 0 and x < 0) at 0, +/-1, +/-2 and +/-6."
129
+ },
130
+ "inputs": {
131
+ "aT": {
132
+ "dtype": "float32",
133
+ "shape": [2, 4],
134
+ "data": { "kind": "values", "values": [0.0, -1.0, 1.0, -0.5, 25.0, 24.0, 26.0, 24.5] }
135
+ },
136
+ "dtBiasT": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.0, 1.0, -1.0, 0.5] } },
137
+ "decayScaleT": {
138
+ "dtype": "float32",
139
+ "shape": [4],
140
+ "data": { "kind": "values", "values": [-1.0, -2.0, -0.5, -4.0] }
141
+ },
142
+ "bT": {
143
+ "dtype": "float32",
144
+ "shape": [2, 4],
145
+ "data": { "kind": "values", "values": [0.0, 6.0, -6.0, 1.0, -1.0, 2.0, -2.0, 0.0] }
146
+ }
147
+ },
148
+ "outputs": {
149
+ "decayT": {
150
+ "dtype": "float32",
151
+ "shape": [2, 4],
152
+ "data": {
153
+ "kind": "values",
154
+ "values": [-0.69314718, -1.3862944, -0.34657359, -2.7725887, -25.0, -50.0, -12.5, -100.0]
155
+ },
156
+ "tolerance": 0,
157
+ "relTolerance": 0.000001
158
+ },
159
+ "betaT": {
160
+ "dtype": "float32",
161
+ "shape": [2, 4],
162
+ "data": {
163
+ "kind": "values",
164
+ "values": [0.5, 0.99752738, 0.0024726232, 0.73105858, 0.26894142, 0.88079708, 0.11920292, 0.5]
165
+ },
166
+ "tolerance": 0,
167
+ "relTolerance": 0.000001
168
+ }
169
+ }
170
+ },
171
+ {
172
+ "name": "rank2_h5_scalar_pinned_saturation",
173
+ "provenance": {
174
+ "notes": "Hand-computed on the scalar path (head count 5). Both rows bias to one shared value -- 25 in row 0 (the x > 20 softplus identity) and 0 in row 1 (ln 2) -- so the five distinct decay values in each row are exactly the five decay_scale entries times one constant. That is the per-head parameter index, index % head_count, stated as arithmetic."
175
+ },
176
+ "inputs": {
177
+ "aT": {
178
+ "dtype": "float32",
179
+ "shape": [2, 5],
180
+ "data": { "kind": "values", "values": [24.5, 25.5, 25.0, 23.0, 27.0, -0.5, 0.5, 0.0, -2.0, 2.0] }
181
+ },
182
+ "dtBiasT": {
183
+ "dtype": "float32",
184
+ "shape": [5],
185
+ "data": { "kind": "values", "values": [0.5, -0.5, 0.0, 2.0, -2.0] }
186
+ },
187
+ "decayScaleT": {
188
+ "dtype": "float32",
189
+ "shape": [5],
190
+ "data": { "kind": "values", "values": [-1.0, -2.0, -0.25, -4.0, -0.5] }
191
+ },
192
+ "bT": {
193
+ "dtype": "float32",
194
+ "shape": [2, 5],
195
+ "data": { "kind": "values", "values": [-6.0, -2.0, 0.0, 2.0, 6.0, -1.0, -0.25, 0.25, 1.0, 3.0] }
196
+ }
197
+ },
198
+ "outputs": {
199
+ "decayT": {
200
+ "dtype": "float32",
201
+ "shape": [2, 5],
202
+ "data": {
203
+ "kind": "values",
204
+ "values": [-25.0, -50.0, -6.25, -100.0, -12.5, -0.69314718, -1.3862944, -0.1732868, -2.7725887, -0.34657359]
205
+ },
206
+ "tolerance": 0,
207
+ "relTolerance": 0.000001
208
+ },
209
+ "betaT": {
210
+ "dtype": "float32",
211
+ "shape": [2, 5],
212
+ "data": {
213
+ "kind": "values",
214
+ "values": [0.0024726232, 0.11920292, 0.5, 0.88079708, 0.99752738, 0.26894142, 0.4378235, 0.5621765, 0.73105858, 0.95257413]
215
+ },
216
+ "tolerance": 0,
217
+ "relTolerance": 0.000001
218
+ }
219
+ }
220
+ },
221
+ {
222
+ "name": "f16_rank3_h8_vec4_gate_beta",
223
+ "provenance": {
224
+ "notes": "float16 a/b/decay/beta with float32 dt_bias and decay_scale -- the schema's T/TF split. The gate arithmetic still runs in f32 and only the store narrows."
225
+ },
226
+ "inputs": {
227
+ "aT": {
228
+ "dtype": "float16",
229
+ "shape": [2, 3, 8],
230
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.17, "scale": 2.5 }
231
+ },
232
+ "dtBiasT": { "dtype": "float32", "shape": [8], "data": { "kind": "linspace", "start": -1.5, "end": 1.1 } },
233
+ "decayScaleT": { "dtype": "float32", "shape": [8], "data": { "kind": "linspace", "start": -3.2, "end": -0.15 } },
234
+ "bT": {
235
+ "dtype": "float16",
236
+ "shape": [2, 3, 8],
237
+ "data": { "kind": "fillFloat32", "sinStep": 0.31, "cosStep": 0.13, "scale": 3.0 }
238
+ }
239
+ },
240
+ "outputs": {
241
+ "decayT": { "dtype": "float16", "shape": [2, 3, 8], "tolerance": 0, "relTolerance": 0.002 },
242
+ "betaT": { "dtype": "float16", "shape": [2, 3, 8], "tolerance": 0, "relTolerance": 0.002 }
243
+ }
244
+ },
245
+ {
246
+ "name": "f16_rank2_h6_scalar_gate_beta",
247
+ "provenance": { "notes": "float16 gate tensors on the scalar path (head count 6)." },
248
+ "inputs": {
249
+ "aT": {
250
+ "dtype": "float16",
251
+ "shape": [5, 6],
252
+ "data": { "kind": "fillFloat32", "sinStep": 0.29, "cosStep": 0.19, "scale": 2.2 }
253
+ },
254
+ "dtBiasT": { "dtype": "float32", "shape": [6], "data": { "kind": "linspace", "start": -2.0, "end": 0.9 } },
255
+ "decayScaleT": { "dtype": "float32", "shape": [6], "data": { "kind": "linspace", "start": -2.6, "end": -0.2 } },
256
+ "bT": {
257
+ "dtype": "float16",
258
+ "shape": [5, 6],
259
+ "data": { "kind": "fillFloat32", "sinStep": 0.37, "cosStep": 0.11, "scale": 2.8 }
260
+ }
261
+ },
262
+ "outputs": {
263
+ "decayT": { "dtype": "float16", "shape": [5, 6], "tolerance": 0, "relTolerance": 0.002 },
264
+ "betaT": { "dtype": "float16", "shape": [5, 6], "tolerance": 0, "relTolerance": 0.002 }
265
+ }
266
+ }
267
+ ]
268
+ }