Xenova HF Staff commited on
Commit
fd5d2b8
·
verified ·
1 Parent(s): e512e42

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -1,3 +1,96 @@
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.GatedDeltaNet
10
+
11
+ `com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
12
+
13
+ ## Description
14
+
15
+ Token-major gated delta network with an explicit recurrent state: `S_t = exp(g_t) * S_{t-1} + k_t * (beta_t * (v_t - exp(g_t) * S_{t-1}^T k_t))^T` and `o_t = scale * S_t^T q_t` per value head; `update_rule` selects which terms survive. Query, key and value are `(total_tokens, heads, head_size)` or `(batch, sequence, heads, head_size)`. `num_heads_v` is a positive multiple of `num_heads_q`, and value head `h` reads query head `h * num_heads_q / num_heads_v`. The V-major state and the gate fusions are float32. Bfloat16, per-key-dimension decay and compact capture are not implemented.
16
+
17
+ See the [ONNX Runtime `GatedDeltaNet` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.GatedDeltaNet) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `queryT` | `query` | `T` | — | — | Query vectors, `(total_tokens, num_heads_q, head_size_qk)` or `(batch_size, sequence_length, num_heads_q, head_size_qk)`. The two spellings share one memory layout; the rank-4 form lets an exporter keep a static reshape target. | required |
24
+ | `keyT` | `key` | `T` | — | — | Key vectors with exactly the shape of `query`. The delta rules need L2-normalized keys, either from upstream or from `qk_l2_norm=1`; without them the per-chunk system is ill-conditioned and the recurrence diverges. | required |
25
+ | `valueT` | `value` | `T` | — | — | Value vectors, `(total_tokens, num_heads_v, head_size_v)`, sharing the leading token axes of `query`. | required |
26
+ | `cuSeqlensT` | `cu_seqlens` | `TI` | `1` | — | Exclusive prefix sums of the per-request token counts, `(batch_size + 1)`. Requires the rank-3 spelling. Offsets are clamped on device to `[0, total_tokens]`, so a decreasing or out-of-range entry yields an empty request rather than an out-of-bounds read. Absent means uniform packing. | optional |
27
+ | `decayT` | `decay` | `TS` | — | — | Log-space decay, `(..., num_heads_v)` over the same leading token axes as `query`, always float32. Present exactly for `update_rule` `gated` and `gated_delta`. The per-key-dimension spelling `(..., num_heads_v, head_size_qk)` is not implemented. | optional |
28
+ | `betaT` | `beta` | `TS` | — | — | Update rate, `(..., num_heads_v)` over the same leading token axes as `query`, always float32. Present exactly for `update_rule` `delta` and `gated_delta`. | optional |
29
+ | `initialStateT` | `initial_state` | `TS` | `4` | — | Incoming recurrent state, V-major `(batch_size, num_heads_v, head_size_v, head_size_qk)`, always float32. Absent means a zero state. It is a separate allocation from `final_state`: WebGPU usage-scope validation tracks whole buffers, so binding one buffer as both read-only and read-write invalidates the command buffer. | optional |
30
+ | `aLogT` | `a_log` | `TS` | `1` | — | Per-head `A_log`, `(num_heads_v)`, always float32. Present exactly when `gate_activation` is `qwen`. | optional |
31
+ | `dtBiasT` | `dt_bias` | `TS` | `1` | — | Per-head gate bias, `(num_heads_v)`, always float32. Present exactly when `gate_activation` is `qwen`. | optional |
32
+
33
+ ## Outputs
34
+
35
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
36
+ | --- | --- | --- | --- | --- | --- | --- |
37
+ | `outputT` | `output` | `T` | same as `queryT` | derived | Attention output, `(total_tokens, num_heads_v, head_size_v)` or `(batch_size, sequence_length, num_heads_v, head_size_v)`. `num_heads_v` is the larger head count because it is a positive multiple of `num_heads_q`. | required |
38
+ | `finalStateT` | `final_state` | `TS` | `4` | derived | State after the last token of each request, V-major `(batch_size, num_heads_v, head_size_v, head_size_qk)`, always float32. A request with no tokens copies its incoming state through. | required |
39
+
40
+ ## Attributes
41
+
42
+ Default values (overridable per request):
43
+
44
+ | Attribute | Default | Description |
45
+ | --- | --- | --- |
46
+ | `beta_activation` | `"none"` | `none` treats `beta` as the effective update rate; `sigmoid` applies a logistic sigmoid to it in float32. Default is `none`. |
47
+ | `chunk_size` | `64` | Accepted scheduling hint for a chunk-parallel prefill algorithm. Every value is accepted and none changes the result. Default is `64`. |
48
+ | `gate_activation` | `"none"` | `none` treats `decay` as the effective log-space decay. `qwen` computes `-exp(a_log) * Softplus(decay + dt_bias)` in float32 and requires both `a_log` and `dt_bias`. Default is `none`. |
49
+ | `qk_l2_norm` | `0` | Set to `1` to L2-normalize each query and key head vector, with epsilon `1e-12`, before the recurrence. Default is `0`. |
50
+ | `scale` | `0` | Output scaling factor applied to `S_t^T q_t`. The default `0.0` selects `1 / sqrt(head_size_qk)`. |
51
+ | `state_update_capacity` | `0` | Capacity for compact transition capture. The default and only supported value is `0`; compact `state_update` capture is not implemented. |
52
+ | `update_rule` | `"gated_delta"` | Which terms of the recurrence survive: `linear` drops the decay and the delta retrieval, `gated` keeps only the decay, `delta` keeps only the retrieval, and `gated_delta` keeps both. Default is `gated_delta`. |
53
+
54
+ ## Type constraints
55
+
56
+ | Variable | Allowed dtypes |
57
+ | --- | --- |
58
+ | `T` | `float32`, `float16` |
59
+ | `TS` | `float32` |
60
+ | `TI` | `int32` |
61
+
62
+ ## Files
63
+
64
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
65
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
66
+ - [`test.json`](build/webgpu/test.json) — correctness cases
67
+ - [`bench.json`](build/webgpu/bench.json) — benchmark cases
68
+ - [`gated-delta-net-gate.wgsl.jinja`](build/webgpu/gated-delta-net-gate.wgsl.jinja)
69
+ - [`gated-delta-net.wgsl.jinja`](build/webgpu/gated-delta-net.wgsl.jinja)
70
+
71
+ ## Use with `@huggingface/kernels`
72
+
73
+ ```sh
74
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
75
+ ```
76
+
77
+ Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
78
+
79
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
80
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
81
+
82
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
83
+
84
+ ```js
85
+ import { getKernel } from "@huggingface/kernels";
86
+
87
+ const kernel = await getKernel("webgpu-kernels/com.microsoft.GatedDeltaNet", { version: 1 });
88
+ const { outputT, finalStateT } = await kernel({
89
+ queryT: { data: queryTData, shape: [4, 1, 12] },
90
+ keyT: { data: keyTData, shape: [4, 1, 12] },
91
+ valueT: { data: valueTData, shape: [4, 2, 9] },
92
+ decayT: { data: decayTData, shape: [4, 2] },
93
+ betaT: { data: betaTData, shape: [4, 2] },
94
+ initialStateT: { data: initialStateTData, shape: [1, 2, 9, 12] },
95
+ });
96
+ ```
build/webgpu/bench.json ADDED
The diff for this file is too large to render. See raw diff
 
build/webgpu/gated-delta-net-gate.wgsl.jinja ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+
3
+ // Per-token/head gate and q/k statistics shared by every value tile.
4
+ // Paired updates also read previous-key cross products. All statistics are f32.
5
+ {% if hasDecayInput or hasBetaInput %}
6
+ const NUM_Q_HEADS: u32 = {{ numQHeads }}u;
7
+ const NUM_V_HEADS: u32 = {{ numVHeads }}u;
8
+ const HEADS_PER_Q: u32 = {{ headsPerQ }}u;
9
+ {% endif %}
10
+ // Words, not elements: vec4 where the key dimension is a multiple of four.
11
+ const DK_WORDS: u32 = {{ dkWords }}u;
12
+ const WG: u32 = {{ gateLanes }}u;
13
+ {% if useL2Norm %}
14
+ // The upstream L2 normalization epsilon is 1e-12, added to the sum of squares.
15
+ const L2_EPS: f32 = 1e-12;
16
+ {% endif %}
17
+
18
+ // One lane's contribution to a key-dimension reduction. The vec4 form is reached
19
+ // wherever the key dimension is a multiple of four; the scalar form carries every
20
+ // other head size with the same schedule.
21
+ fn word_dot(a: {{ wordF32 }}, b: {{ wordF32 }}) -> f32 {
22
+ {% if useVec4 %}
23
+ return dot(a, b);
24
+ {% else %}
25
+ return a * b;
26
+ {% endif %}
27
+ }
28
+
29
+ // The three key-dimension sums share one staging array and one fold.
30
+ var<workgroup> partials: array<vec3<f32>, WG>;
31
+ {% if pairTokens %}
32
+ var<workgroup> crossPartials: array<vec2<f32>, WG>;
33
+ {% endif %}
34
+ {% if sigmoidBeta and hasBetaInput %}
35
+
36
+ fn sigmoid_safe(x: f32) -> f32 {
37
+ if (x >= 0.0) {
38
+ let z = exp(-x);
39
+ return 1.0 / (1.0 + z);
40
+ }
41
+ let z = exp(x);
42
+ return z / (1.0 + z);
43
+ }
44
+ {% endif %}
45
+ {% if qwen %}
46
+
47
+ // WGSL has no log1p builtin, and log(1.0 + y) loses relative accuracy for small y: rounding
48
+ // 1.0 + y costs up to half an ulp of 1.0, which is enormous next to y itself. Below the
49
+ // crossover the series is the accurate form -- its truncation is y^7 / 8, under 6e-9 relative
50
+ // at y = 0.125 -- and above it the same half-ulp is under 6e-7 relative because log1p(y) has
51
+ // grown past 0.118. The algebraic (1.0 + y) - 1.0 correction is not usable here: a backend is
52
+ // free to fold that subtraction back to y, which silently restores the error it removes.
53
+ fn log1p_pos(y: f32) -> f32 {
54
+ if (y < 0.125) {
55
+ return y * (1.0 + y * (-0.5 + y * (0.33333334 + y * (-0.25 + y * (0.2 + y * (-0.16666667 + y * 0.14285714))))));
56
+ }
57
+ return log(1.0 + y);
58
+ }
59
+
60
+ fn softplus(x: f32) -> f32 {
61
+ // Both arms feed log1p an exp() of a non-positive argument, so the operand stays in (0, 1]
62
+ // and never overflows; for large x the increment rounds away and the result is exactly x,
63
+ // which is the correct limit.
64
+ if (x > 0.0) {
65
+ return x + log1p_pos(exp(-x));
66
+ }
67
+ return log1p_pos(exp(x));
68
+ }
69
+ {% endif %}
70
+
71
+ @compute @workgroup_size(WG, 1, 1)
72
+ fn main(
73
+ @builtin(workgroup_id) wg: vec3<u32>,
74
+ @builtin(local_invocation_id) lid: vec3<u32>,
75
+ ) {
76
+ // 2D-folded (token * query head) index: wg.y carries the high bits past the
77
+ // per-axis dispatch fold width.
78
+ let unit = wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u;
79
+ if (unit >= params.units) {
80
+ return;
81
+ }
82
+
83
+ let qk_row = unit * DK_WORDS;
84
+ var acc = vec3<f32>(0.0);
85
+ {% if pairTokens %}
86
+ var crossAcc = vec2<f32>(0.0);
87
+ {% endif %}
88
+ for (var i = lid.x; i < DK_WORDS; i = i + WG) {
89
+ let kw = {{ wordF32 }}(key[qk_row + i]);
90
+ let qw = {{ wordF32 }}(query[qk_row + i]);
91
+ acc = acc + vec3<f32>(word_dot(kw, qw), word_dot(kw, kw), word_dot(qw, qw));
92
+ {% if pairTokens %}
93
+ if (unit >= NUM_Q_HEADS) {
94
+ let previous = {{ wordF32 }}(key[qk_row - NUM_Q_HEADS * DK_WORDS + i]);
95
+ crossAcc = crossAcc + vec2<f32>(word_dot(previous, kw), word_dot(previous, qw));
96
+ }
97
+ {% endif %}
98
+ }
99
+ partials[lid.x] = acc;
100
+ {% if pairTokens %}
101
+ crossPartials[lid.x] = crossAcc;
102
+ {% endif %}
103
+ workgroupBarrier();
104
+ // WG is a power of two, so the halving stride is uniform across the workgroup and
105
+ // every lane reaches every barrier.
106
+ var stride = WG / 2u;
107
+ loop {
108
+ if (stride == 0u) {
109
+ break;
110
+ }
111
+ if (lid.x < stride) {
112
+ partials[lid.x] = partials[lid.x] + partials[lid.x + stride];
113
+ {% if pairTokens %}
114
+ crossPartials[lid.x] = crossPartials[lid.x] + crossPartials[lid.x + stride];
115
+ {% endif %}
116
+ }
117
+ workgroupBarrier();
118
+ stride = stride / 2u;
119
+ }
120
+ let sums = partials[0];
121
+ {% if useL2Norm %}
122
+ let inv_k = inverseSqrt(sums.y + L2_EPS);
123
+ let inv_q = inverseSqrt(sums.z + L2_EPS);
124
+ {% else %}
125
+ let inv_k = 1.0;
126
+ let inv_q = 1.0;
127
+ {% endif %}
128
+ if (lid.x == 0u) {
129
+ let qk_out = params.qkBase + unit * {{ 5 if pairTokens else 3 }}u;
130
+ gate[qk_out] = sums.x * inv_k * inv_q;
131
+ gate[qk_out + 1u] = inv_k;
132
+ gate[qk_out + 2u] = inv_q;
133
+ {% if pairTokens %}
134
+ gate[qk_out + 3u] = crossPartials[0].x;
135
+ gate[qk_out + 4u] = crossPartials[0].y;
136
+ {% endif %}
137
+ }
138
+ {% if hasDecayInput or hasBetaInput %}
139
+
140
+ // Each query head serves HEADS_PER_Q consecutive value heads, so this unit owns
141
+ // exactly that block of region A.
142
+ let head_q = unit % NUM_Q_HEADS;
143
+ let token = unit / NUM_Q_HEADS;
144
+ for (var h = lid.x; h < HEADS_PER_Q; h = h + WG) {
145
+ let head_v = head_q * HEADS_PER_Q + h;
146
+ let item = token * NUM_V_HEADS + head_v;
147
+ {% if hasDecayInput %}
148
+ var raw_decay = decay[item];
149
+ {% if qwen %}
150
+ raw_decay = -exp(a_log[head_v]) * softplus(raw_decay + dt_bias[head_v]);
151
+ {% endif %}
152
+ gate[item * 2u] = exp(raw_decay);
153
+ {% else %}
154
+ // Rules without a decay term contract by exactly one per step.
155
+ gate[item * 2u] = 1.0;
156
+ {% endif %}
157
+ {% if hasBetaInput %}
158
+ let raw_beta = beta[item];
159
+ {% if sigmoidBeta %}
160
+ gate[item * 2u + 1u] = sigmoid_safe(raw_beta);
161
+ {% else %}
162
+ gate[item * 2u + 1u] = raw_beta;
163
+ {% endif %}
164
+ {% else %}
165
+ // Rules without a delta retrieval take the value vector whole.
166
+ gate[item * 2u + 1u] = 1.0;
167
+ {% endif %}
168
+ }
169
+ {% endif %}
170
+ }
build/webgpu/gated-delta-net.wgsl.jinja ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% set comps = ["x", "y", "z", "w"] %}
2
+ {% set skBase = tileV %}
3
+ {% set exactWords = wordsPerLane * lanes == dkWords %}
4
+ {% set foldSteps = log2ceil(lanes) %}
5
+ {% macro fold_values(foldSlots) %}
6
+ {% if lanes > 1 %}
7
+ {% if useSubgroups %}
8
+ // Segmented butterfly over the low lane bits only, so each aligned group of
9
+ // LANES lanes folds its own value tile and one subgroup carries several tiles
10
+ // with no barrier and no workgroup memory. Selection admits this arm only when
11
+ // LANES divides every subgroup width the adapter may use.
12
+ {% for step in range(foldSteps) %}
13
+ {% for s in range(foldSlots) %}
14
+ f{{ s }} = f{{ s }} + subgroupShuffleXor(f{{ s }}, 1u << {{ step }}u);
15
+ {% endfor %}
16
+ {% endfor %}
17
+ {% else %}
18
+ // The previous token's results were read by every lane of the group, so the
19
+ // staging array cannot be rewritten until all of them have passed this point.
20
+ workgroupBarrier();
21
+ {% for s in range(foldSlots) %}
22
+ fold[{{ s }}u * WG + lid.x] = f{{ s }};
23
+ {% endfor %}
24
+ workgroupBarrier();
25
+ // LANES is a power of two, so the halving stride is uniform across the whole
26
+ // workgroup and every lane reaches every barrier.
27
+ var stride = LANES / 2u;
28
+ loop {
29
+ if (stride == 0u) {
30
+ break;
31
+ }
32
+ if (lane < stride) {
33
+ {% for s in range(foldSlots) %}
34
+ fold[{{ s }}u * WG + lid.x] = fold[{{ s }}u * WG + lid.x] + fold[{{ s }}u * WG + lid.x + stride];
35
+ {% endfor %}
36
+ }
37
+ workgroupBarrier();
38
+ stride = stride / 2u;
39
+ }
40
+ {% for s in range(foldSlots) %}
41
+ f{{ s }} = fold[{{ s }}u * WG + v_group * LANES];
42
+ {% endfor %}
43
+ {% endif %}
44
+ {% endif %}
45
+ {% endmacro %}
46
+ {% if pairTokens %}
47
+ {% macro paired_recurrence() %}
48
+ // Two consecutive updates share one cross-lane fold. Both projections and
49
+ // retrievals are formed against the incoming state; the second token applies
50
+ // the first token's rank-one correction through precomputed cross products.
51
+ for (var t = token_begin; t + 1u < token_end; t = t + 2u) {
52
+ {% for p in range(2) %}
53
+ let qk_row{{ p }} = ((t + {{ p }}u) * NUM_Q_HEADS + head_q) * DK_WORDS;
54
+ {% for w in range(wordsPerLane) %}
55
+ {% if exactWords %}
56
+ let kw{{ p }}_{{ w }} = {{ wordF32 }}(key[qk_row{{ p }} + r{{ w }}]);
57
+ let qw{{ p }}_{{ w }} = {{ wordF32 }}(query[qk_row{{ p }} + r{{ w }}]);
58
+ {% else %}
59
+ var kw{{ p }}_{{ w }} = {{ wordF32 }}(0.0);
60
+ var qw{{ p }}_{{ w }} = {{ wordF32 }}(0.0);
61
+ if (word_ok{{ w }}) {
62
+ kw{{ p }}_{{ w }} = {{ wordF32 }}(key[qk_row{{ p }} + r{{ w }}]);
63
+ qw{{ p }}_{{ w }} = {{ wordF32 }}(query[qk_row{{ p }} + r{{ w }}]);
64
+ }
65
+ {% endif %}
66
+ {% endfor %}
67
+ {% endfor %}
68
+ {% for s in range(foldSlots) %}
69
+ var f{{ s }} = vec4<f32>(0.0);
70
+ {% endfor %}
71
+ {% for p in range(2) %}
72
+ {% for j in range(tileV) %}
73
+ {% set qi = p * 2 * tileV + j %}
74
+ {% set ki = qi + skBase %}
75
+ f{{ (qi / 4) | int }}.{{ comps[qi % 4] }} = {% for w in range(wordsPerLane) %}{{ "+ " if not loop.first else "" }}word_dot(s{{ j }}_{{ w }}, qw{{ p }}_{{ w }}) {% endfor %};
76
+ f{{ (ki / 4) | int }}.{{ comps[ki % 4] }} = {% for w in range(wordsPerLane) %}{{ "+ " if not loop.first else "" }}word_dot(s{{ j }}_{{ w }}, kw{{ p }}_{{ w }}) {% endfor %};
77
+ {% endfor %}
78
+ {% endfor %}
79
+ {{ fold_values(foldSlots) }}
80
+ {% for p in range(2) %}
81
+ let qk_base{{ p }} = params.qkBase + ((t + {{ p }}u) * NUM_Q_HEADS + head_q) * 5u;
82
+ let kq{{ p }} = gate[qk_base{{ p }}];
83
+ {% if useL2Norm %}
84
+ let inv_k{{ p }} = gate[qk_base{{ p }} + 1u];
85
+ let inv_q{{ p }} = gate[qk_base{{ p }} + 2u];
86
+ {% else %}
87
+ let inv_k{{ p }} = 1.0;
88
+ let inv_q{{ p }} = 1.0;
89
+ {% endif %}
90
+ let gate_base{{ p }} = ((t + {{ p }}u) * NUM_V_HEADS + head_v) * 2u;
91
+ let decay{{ p }} = gate[gate_base{{ p }}];
92
+ let beta{{ p }} = gate[gate_base{{ p }} + 1u];
93
+ let value_row{{ p }} = ((t + {{ p }}u) * NUM_V_HEADS + head_v) * HEAD_DIM_V + dv_start;
94
+ {% if vectorValueIO %}
95
+ var output_values{{ p }} = vec{{ tileV }}<f32>(0.0);
96
+ var value_values{{ p }} = vec{{ tileV }}<f32>(0.0);
97
+ if (dv_start < HEAD_DIM_V) { value_values{{ p }} = vec{{ tileV }}<f32>(value[value_row{{ p }} / TILE_V]); }
98
+ {% endif %}
99
+ {% endfor %}
100
+ let cross_kk = gate[qk_base1 + 3u];
101
+ let cross_kq = gate[qk_base1 + 4u];
102
+ {% for j in range(tileV) %}
103
+ var d0_{{ j }} = 0.0;
104
+ var d1_{{ j }} = 0.0;
105
+ if (dv_start + {{ j }}u < HEAD_DIM_V) {
106
+ {% for p in range(2) %}
107
+ {% set qi = p * 2 * tileV + j %}
108
+ {% set ki = qi + skBase %}
109
+ let retrieved{{ p }}_{{ j }} = decay{{ p }} * inv_k{{ p }} * {% if p == 1 %}(decay0 * {% endif %}f{{ (ki / 4) | int }}.{{ comps[ki % 4] }}{% if p == 1 %} + d0_{{ j }} * cross_kk){% endif %};
110
+ let delta{{ p }}_{{ j }} = beta{{ p }} * ({% if vectorValueIO %}value_values{{ p }}[{{ j }}]{% else %}f32(value[value_row{{ p }} + {{ j }}u]){% endif %} - retrieved{{ p }}_{{ j }});
111
+ d{{ p }}_{{ j }} = inv_k{{ p }} * delta{{ p }}_{{ j }};
112
+ let projected{{ p }}_{{ j }} = decay{{ p }} * inv_q{{ p }} * {% if p == 1 %}(decay0 * {% endif %}f{{ (qi / 4) | int }}.{{ comps[qi % 4] }}{% if p == 1 %} + d0_{{ j }} * cross_kq){% endif %};
113
+ {% if vectorValueIO %}
114
+ output_values{{ p }}[{{ j }}] = (projected{{ p }}_{{ j }} + delta{{ p }}_{{ j }} * kq{{ p }}) * SCALE;
115
+ {% else %}
116
+ if (lane == {{ j % lanes }}u) {
117
+ output[value_row{{ p }} + {{ j }}u] = {{ T }}((projected{{ p }}_{{ j }} + delta{{ p }}_{{ j }} * kq{{ p }}) * SCALE);
118
+ }
119
+ {% endif %}
120
+ {% endfor %}
121
+ }
122
+ {% endfor %}
123
+ {% if vectorValueIO %}
124
+ if (dv_start < HEAD_DIM_V && lane == 0u) {
125
+ output[value_row0 / TILE_V] = vec{{ tileV }}<{{ T }}>(output_values0);
126
+ output[value_row1 / TILE_V] = vec{{ tileV }}<{{ T }}>(output_values1);
127
+ }
128
+ {% endif %}
129
+ {% for j in range(tileV) %}
130
+ {% for w in range(wordsPerLane) %}
131
+ s{{ j }}_{{ w }} = s{{ j }}_{{ w }} * {{ wordF32 }}(decay0) + kw0_{{ w }} * {{ wordF32 }}(d0_{{ j }});
132
+ s{{ j }}_{{ w }} = s{{ j }}_{{ w }} * {{ wordF32 }}(decay1) + kw1_{{ w }} * {{ wordF32 }}(d1_{{ j }});
133
+ {% endfor %}
134
+ {% endfor %}
135
+ }
136
+ {% endmacro %}
137
+ {% endif %}
138
+ {% macro recurrence_step(foldSlots, qkStride) %}
139
+ let qk_row = (t * NUM_Q_HEADS + head_q) * DK_WORDS;
140
+ {% for w in range(wordsPerLane) %}
141
+ {% if exactWords %}
142
+ let kw{{ w }} = {{ wordF32 }}(key[qk_row + r{{ w }}]);
143
+ let qw{{ w }} = {{ wordF32 }}(query[qk_row + r{{ w }}]);
144
+ {% else %}
145
+ var kw{{ w }} = {{ wordF32 }}(0.0);
146
+ var qw{{ w }} = {{ wordF32 }}(0.0);
147
+ if (word_ok{{ w }}) {
148
+ kw{{ w }} = {{ wordF32 }}(key[qk_row + r{{ w }}]);
149
+ qw{{ w }} = {{ wordF32 }}(query[qk_row + r{{ w }}]);
150
+ }
151
+ {% endif %}
152
+ {% endfor %}
153
+
154
+ {% for s in range(foldSlots) %}
155
+ var f{{ s }} = vec4<f32>(0.0);
156
+ {% endfor %}
157
+ {% for j in range(tileV) %}
158
+ f{{ (j / 4) | int }}.{{ comps[j % 4] }} = {% for w in range(wordsPerLane) %}{{ "+ " if not loop.first else "" }}word_dot(s{{ j }}_{{ w }}, qw{{ w }}) {% endfor %};
159
+ {% endfor %}
160
+ {% if useDelta %}
161
+ {% for j in range(tileV) %}
162
+ f{{ ((skBase + j) / 4) | int }}.{{ comps[(skBase + j) % 4] }} = {% for w in range(wordsPerLane) %}{{ "+ " if not loop.first else "" }}word_dot(s{{ j }}_{{ w }}, kw{{ w }}) {% endfor %};
163
+ {% endfor %}
164
+ {% endif %}
165
+ {{ fold_values(foldSlots) }}
166
+ // Normalizing q and k is a per-vector SCALE, so the state reductions above run on
167
+ // the raw vectors and are rescaled here. The state update then absorbs the key's
168
+ // factor into the delta, and `kq` arrives with both factors already applied.
169
+ let qk_row_base = params.qkBase + (t * NUM_Q_HEADS + head_q) * {{ qkStride }}u;
170
+ let kq = gate[qk_row_base];
171
+ {% if useL2Norm %}
172
+ let inv_k = gate[qk_row_base + 1u];
173
+ let inv_q = gate[qk_row_base + 2u];
174
+ {% else %}
175
+ let inv_k = 1.0;
176
+ let inv_q = 1.0;
177
+ {% endif %}
178
+ {% if hasGate %}
179
+ let gate_base = (t * NUM_V_HEADS + head_v) * 2u;
180
+ let decay = gate[gate_base];
181
+ let beta = gate[gate_base + 1u];
182
+ {% else %}
183
+ let decay = 1.0;
184
+ let beta = 1.0;
185
+ {% endif %}
186
+ let value_row = (t * NUM_V_HEADS + head_v) * HEAD_DIM_V + dv_start;
187
+ {% if vectorValueIO %}
188
+ // Fold a complete value tile into one contiguous store. The template keeps
189
+ // scalar I/O for tails and for float32 groups below the vectorization
190
+ // threshold.
191
+ var output_values = vec{{ tileV }}<f32>(0.0);
192
+ var value_values = vec{{ tileV }}<f32>(0.0);
193
+ if (dv_start < HEAD_DIM_V) { value_values = vec{{ tileV }}<f32>(value[value_row / TILE_V]); }
194
+ {% endif %}
195
+ {% for j in range(tileV) %}
196
+ var d{{ j }} = 0.0;
197
+ if (dv_start + {{ j }}u < HEAD_DIM_V) {
198
+ {% if useDelta %}
199
+ let retrieved{{ j }} = decay * inv_k * f{{ ((skBase + j) / 4) | int }}.{{ comps[(skBase + j) % 4] }};
200
+ {% else %}
201
+ let retrieved{{ j }} = 0.0;
202
+ {% endif %}
203
+ let delta{{ j }} = beta * ({% if vectorValueIO %}value_values[{{ j }}]{% else %}f32(value[value_row + {{ j }}u]){% endif %} - retrieved{{ j }});
204
+ d{{ j }} = inv_k * delta{{ j }};
205
+ {% if vectorValueIO %}
206
+ let projected{{ j }} = decay * inv_q * f{{ (j / 4) | int }}.{{ comps[j % 4] }};
207
+ output_values[{{ j }}] = (projected{{ j }} + delta{{ j }} * kq) * SCALE;
208
+ {% else %}
209
+ if (lane == {{ j % lanes }}u) {
210
+ let projected{{ j }} = decay * inv_q * f{{ (j / 4) | int }}.{{ comps[j % 4] }};
211
+ output[value_row + {{ j }}u] = {{ T }}((projected{{ j }} + delta{{ j }} * kq) * SCALE);
212
+ }
213
+ {% endif %}
214
+ }
215
+ {% endfor %}
216
+ {% if vectorValueIO %}
217
+ if (dv_start < HEAD_DIM_V && lane == 0u) {
218
+ output[value_row / TILE_V] = vec{{ tileV }}<{{ T }}>(output_values);
219
+ }
220
+ {% endif %}
221
+ {% for j in range(tileV) %}
222
+ {% for w in range(wordsPerLane) %}
223
+ s{{ j }}_{{ w }} = s{{ j }}_{{ w }} * {{ wordF32 }}(decay) + kw{{ w }} * {{ wordF32 }}(d{{ j }});
224
+ {% endfor %}
225
+ {% endfor %}
226
+ {% endmacro %}
227
+ {% if useSubgroups %}
228
+ enable subgroups;
229
+ {% endif %}
230
+ {{ env.wgsl.resourceDeclarations }}
231
+
232
+ // One sequential pass of the gated delta recurrence, per (request, value head,
233
+ // value tile):
234
+ //
235
+ // S_t = d_t * S_{t-1} + k_t * (beta_t * (v_t - d_t * S_{t-1}^T k_t))^T
236
+ // o_t = SCALE * S_t^T q_t
237
+ //
238
+ // The state boundary is V-major, (batch, num_heads_v, head_size_v, head_size_qk)
239
+ // with the key dimension contiguous, so one value channel's whole key slice is a
240
+ // contiguous run. A lane group of LANES lanes owns TILE_V such channels and holds
241
+ // WORDS_PER_LANE words of each in registers; the reductions therefore run along
242
+ // the key dimension, across the lanes of one group.
243
+ //
244
+ // The output is folded into the same pass instead of re-reading the updated
245
+ // state: S_t^T q = d * (S_{t-1}^T q) + delta * (k . q). The retrieval and the
246
+ // output projection are therefore both products with the state BEFORE the update
247
+ // and reduce together, while k . q carries no state and arrives as a scalar.
248
+ const HEAD_DIM_V: u32 = {{ headDimV }}u;
249
+ // Words, not elements: vec4 where the key dimension is a multiple of four.
250
+ const DK_WORDS: u32 = {{ dkWords }}u;
251
+ const NUM_Q_HEADS: u32 = {{ numQHeads }}u;
252
+ const NUM_V_HEADS: u32 = {{ numVHeads }}u;
253
+ // num_heads_v is a positive multiple of num_heads_q, so the query head a value
254
+ // head reads is a BLOCK map: value heads 0..HEADS_PER_Q-1 all read query head 0.
255
+ const HEADS_PER_Q: u32 = {{ headsPerQ }}u;
256
+ const LANES: u32 = {{ lanes }}u;
257
+ const V_GROUPS: u32 = {{ vGroups }}u;
258
+ const WG: u32 = LANES * V_GROUPS;
259
+ const TILE_V: u32 = {{ tileV }}u;
260
+ const DV_BLOCKS: u32 = {{ dvBlocks }}u;
261
+ const SCALE: f32 = {{ outScale }};
262
+
263
+ // One lane's contribution to a key-dimension reduction. The vec4 form is reached
264
+ // wherever the key dimension is a multiple of four; the scalar form carries every
265
+ // other head size with the same schedule.
266
+ fn word_dot(a: {{ wordF32 }}, b: {{ wordF32 }}) -> f32 {
267
+ {% if useVec4 %}
268
+ return dot(a, b);
269
+ {% else %}
270
+ return a * b;
271
+ {% endif %}
272
+ }
273
+ {% if not useSubgroups and lanes > 1 %}
274
+
275
+ // Portable fold: every reduced value of one token is staged in the same array and
276
+ // folded once, so the barrier count is a property of the token rather than of how
277
+ // many quantities it reduces. A lane group owns one contiguous LANES-wide slice of
278
+ // each slot, so groups sharing this workgroup never read one another's partials.
279
+ var<workgroup> fold: array<vec4<f32>, WG * {{ foldSlots }}u>;
280
+ {% endif %}
281
+
282
+ @compute @workgroup_size(WG, 1, 1)
283
+ fn main(
284
+ @builtin(workgroup_id) wg: vec3<u32>,
285
+ {% if useSubgroups %}
286
+ @builtin(subgroup_invocation_id) sg_lane: u32,
287
+ @builtin(subgroup_id) sg_id: u32,
288
+ {% if variableSubgroupFold %}
289
+ @builtin(subgroup_size) sg_size: u32,
290
+ {% endif %}
291
+ {% else %}
292
+ @builtin(local_invocation_id) lid: vec3<u32>,
293
+ {% endif %}
294
+ ) {
295
+ {% if useSubgroups %}
296
+ // Subgroup-local coordinates define the segmented fold; local invocation
297
+ // ordering is not a portable substitute. The manifest admits only full
298
+ // subgroups and masks any extra value tiles after rounding a short group up.
299
+ let lane = sg_lane % LANES;
300
+ {% if variableSubgroupFold %}
301
+ let v_group = sg_id * (sg_size / LANES) + sg_lane / LANES;
302
+ {% else %}
303
+ let v_group = sg_id * {{ (subgroupWidth / lanes) | int }}u + sg_lane / LANES;
304
+ {% endif %}
305
+ {% else %}
306
+ let lane = lid.x % LANES;
307
+ let v_group = lid.x / LANES;
308
+ {% endif %}
309
+ // 2D-folded (batch * value head * value-tile block) index: wg.y carries the high
310
+ // bits past the per-axis dispatch fold width.
311
+ let unit = wg.x + wg.y * {{ DISPATCH_FOLD_WIDTH }}u;
312
+ let block = unit % DV_BLOCKS;
313
+ let bh = unit / DV_BLOCKS;
314
+ let head_v = bh % NUM_V_HEADS;
315
+ let batch = bh / NUM_V_HEADS;
316
+ if (batch >= params.batchSize) {
317
+ return;
318
+ }
319
+ let head_q = head_v / HEADS_PER_Q;
320
+ let dv_start = (block * V_GROUPS + v_group) * TILE_V;
321
+ {% if hasCu %}
322
+ // Device-supplied offsets are clamped, not validated: a negative, decreasing or
323
+ // past-the-end entry must yield an empty or truncated request rather than steer
324
+ // an out-of-bounds access.
325
+ let token_limit = i32(params.totalTokens);
326
+ let token_begin = u32(clamp(cu_seqlens[batch], 0, token_limit));
327
+ let token_end = u32(clamp(cu_seqlens[batch + 1u], 0, token_limit));
328
+ {% else %}
329
+ let token_begin = batch * params.seqLength;
330
+ let token_end = token_begin + params.seqLength;
331
+ {% endif %}
332
+
333
+ let state_base = (batch * NUM_V_HEADS + head_v) * HEAD_DIM_V * DK_WORDS;
334
+ {% for j in range(tileV) %}
335
+ {% for w in range(wordsPerLane) %}
336
+ var s{{ j }}_{{ w }} = {{ wordF32 }}(0.0);
337
+ {% endfor %}
338
+ {% endfor %}
339
+ {% for w in range(wordsPerLane) %}
340
+ let r{{ w }} = lane + {{ w }}u * LANES;
341
+ {% if not exactWords %}
342
+ let word_ok{{ w }} = r{{ w }} < DK_WORDS;
343
+ {% endif %}
344
+ {% endfor %}
345
+ {% if hasState %}
346
+ {% for j in range(tileV) %}
347
+ if (dv_start + {{ j }}u < HEAD_DIM_V) {
348
+ let row{{ j }} = state_base + (dv_start + {{ j }}u) * DK_WORDS;
349
+ {% for w in range(wordsPerLane) %}
350
+ {% if exactWords %}
351
+ s{{ j }}_{{ w }} = initial_state[row{{ j }} + r{{ w }}];
352
+ {% else %}
353
+ if (word_ok{{ w }}) { s{{ j }}_{{ w }} = initial_state[row{{ j }} + r{{ w }}]; }
354
+ {% endif %}
355
+ {% endfor %}
356
+ }
357
+ {% endfor %}
358
+ {% endif %}
359
+
360
+ {% if pairTokens %}
361
+ {{ paired_recurrence() }}
362
+ if (token_end > token_begin && (token_end - token_begin) % 2u != 0u) {
363
+ let t = token_end - 1u;
364
+ {{ recurrence_step(foldSlotsDelta, 5) }}
365
+ }
366
+ {% else %}
367
+ for (var t = token_begin; t < token_end; t = t + 1u) {
368
+ {{ recurrence_step(foldSlots, 3) }}
369
+ }
370
+ {% endif %}
371
+
372
+ {% for j in range(tileV) %}
373
+ if (dv_start + {{ j }}u < HEAD_DIM_V) {
374
+ let out_state{{ j }} = state_base + (dv_start + {{ j }}u) * DK_WORDS;
375
+ {% for w in range(wordsPerLane) %}
376
+ {% if exactWords %}
377
+ final_state[out_state{{ j }} + r{{ w }}] = s{{ j }}_{{ w }};
378
+ {% else %}
379
+ if (word_ok{{ w }}) { final_state[out_state{{ j }} + r{{ w }}] = s{{ j }}_{{ w }}; }
380
+ {% endif %}
381
+ {% endfor %}
382
+ }
383
+ {% endfor %}
384
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,1093 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "com.microsoft",
3
+ "name": "GatedDeltaNet",
4
+ "sinceVersion": 1,
5
+ "inputs": {
6
+ "queryT": { "onnx": "query", "dtype": "T" },
7
+ "keyT": { "onnx": "key", "dtype": "T" },
8
+ "valueT": { "onnx": "value", "dtype": "T" },
9
+ "cuSeqlensT": { "onnx": "cu_seqlens", "dtype": "TI", "rank": 1, "optional": true },
10
+ "decayT": { "onnx": "decay", "dtype": "TS", "optional": true },
11
+ "betaT": { "onnx": "beta", "dtype": "TS", "optional": true },
12
+ "initialStateT": { "onnx": "initial_state", "dtype": "TS", "rank": 4, "optional": true },
13
+ "aLogT": { "onnx": "a_log", "dtype": "TS", "rank": 1, "optional": true },
14
+ "dtBiasT": { "onnx": "dt_bias", "dtype": "TS", "rank": 1, "optional": true }
15
+ },
16
+ "outputs": {
17
+ "outputT": {
18
+ "onnx": "output",
19
+ "dtype": "T",
20
+ "rank": "ranks.queryT",
21
+ "shape": "[dim(shapes.queryT, 0), dim(shapes.queryT, 1), numVHeads, headDimV] if rank4 else [dim(shapes.queryT, 0), numVHeads, headDimV]"
22
+ },
23
+ "finalStateT": {
24
+ "onnx": "final_state",
25
+ "dtype": "TS",
26
+ "rank": 4,
27
+ "shape": "[batchCount, numVHeads, headDimV, headDimK]"
28
+ }
29
+ },
30
+ "attributes": {
31
+ "update_rule": { "default": "gated_delta" },
32
+ "scale": { "default": 0 },
33
+ "gate_activation": { "default": "none" },
34
+ "beta_activation": { "default": "none" },
35
+ "qk_l2_norm": { "default": 0 },
36
+ "chunk_size": { "default": 64 },
37
+ "state_update_capacity": { "default": 0 }
38
+ },
39
+ "attributeConstraints": {
40
+ "update_rule": { "values": ["linear", "gated", "delta", "gated_delta"] },
41
+ "gate_activation": { "values": ["none", "qwen"] },
42
+ "beta_activation": { "values": ["none", "sigmoid"] },
43
+ "qk_l2_norm": { "values": [0, 1] },
44
+ "state_update_capacity": { "values": [0] }
45
+ },
46
+ "typeConstraints": { "T": ["float32", "float16"], "TS": ["float32"], "TI": ["int32"] },
47
+ "tunables": {
48
+ "TILE_V": { "default": 0 },
49
+ "LANE_WORDS": { "default": 4 },
50
+ "V_GROUPS": { "default": 8 },
51
+ "MIN_VECTOR_VALUE_GROUPS": { "default": 4 }
52
+ },
53
+ "derive": {
54
+ "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
55
+ "foldedDispatchCapacity": "min(device.limits.maxComputeWorkgroupsPerDimension, 65535) * min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
56
+ "rank4": "ranks.queryT == 4",
57
+ "numQHeads": "dim(shapes.queryT, ranks.queryT - 2)",
58
+ "headDimK": "dim(shapes.queryT, ranks.queryT - 1)",
59
+ "numVHeads": "dim(shapes.valueT, ranks.valueT - 2)",
60
+ "headDimV": "dim(shapes.valueT, ranks.valueT - 1)",
61
+ "totalTokens": "numel(shapes.queryT) / max(1, numQHeads * headDimK)",
62
+ "batchCount": "(dim(shapes.cuSeqlensT, 0) - 1) if present.cuSeqlensT else (dim(shapes.queryT, 0) if rank4 else (dim(shapes.initialStateT, 0) if present.initialStateT else 0))",
63
+ "seqLength": "totalTokens / max(1, batchCount)",
64
+ "headsPerQ": "numVHeads / max(1, numQHeads)",
65
+ "rankOk": "(ranks.queryT == 3 or ranks.queryT == 4) and ranks.keyT == ranks.queryT and ranks.valueT == ranks.queryT and ranks.outputT == ranks.queryT",
66
+ "headsOk": "numQHeads > 0 and numVHeads > 0 and headDimK > 0 and headDimV > 0 and numVHeads % max(1, numQHeads) == 0",
67
+ "tokenAxesOk": "sameShape(shapes.keyT, shapes.queryT) and sameShape(prefix(shapes.valueT, ranks.queryT - 2), prefix(shapes.queryT, ranks.queryT - 2))",
68
+ "batchOk": "batchCount > 0 and (true if present.cuSeqlensT else totalTokens % max(1, batchCount) == 0)",
69
+ "outputOk": "sameShape(prefix(shapes.outputT, ranks.queryT - 2), prefix(shapes.queryT, ranks.queryT - 2)) and dim(shapes.outputT, ranks.outputT - 2) == numVHeads and dim(shapes.outputT, ranks.outputT - 1) == headDimV",
70
+ "stateOk": "ranks.finalStateT == 4 and dim(shapes.finalStateT, 0) == batchCount and dim(shapes.finalStateT, 1) == numVHeads and dim(shapes.finalStateT, 2) == headDimV and dim(shapes.finalStateT, 3) == headDimK and (sameShape(shapes.initialStateT, shapes.finalStateT) if present.initialStateT else true)",
71
+ "gateShapeOk": "(ranks.decayT == ranks.queryT - 1 and sameShape(prefix(shapes.decayT, ranks.queryT - 2), prefix(shapes.queryT, ranks.queryT - 2)) and dim(shapes.decayT, ranks.decayT - 1) == numVHeads if present.decayT else true) and (ranks.betaT == ranks.queryT - 1 and sameShape(prefix(shapes.betaT, ranks.queryT - 2), prefix(shapes.queryT, ranks.queryT - 2)) and dim(shapes.betaT, ranks.betaT - 1) == numVHeads if present.betaT else true) and (ranks.aLogT == 1 and dim(shapes.aLogT, 0) == numVHeads if present.aLogT else true) and (ranks.dtBiasT == 1 and dim(shapes.dtBiasT, 0) == numVHeads if present.dtBiasT else true)",
72
+ "cuOk": "ranks.cuSeqlensT == 1 and ranks.queryT == 3 and dim(shapes.cuSeqlensT, 0) == batchCount + 1 if present.cuSeqlensT else true",
73
+ "dtypeOk": "tensorDtypes.keyT == tensorDtypes.queryT and tensorDtypes.valueT == tensorDtypes.queryT and tensorDtypes.outputT == tensorDtypes.queryT and f16Ok(dtypes.T)",
74
+ "contractOk": "rankOk and headsOk and tokenAxesOk and batchOk and outputOk and stateOk and gateShapeOk and cuOk and dtypeOk",
75
+ "useVec4": "headDimK % 4 == 0",
76
+ "dkWords": "headDimK / 4 if useVec4 else headDimK",
77
+ "laneCap": "pow(2, log2ceil(deviceWorkgroupCap + 1) - 1)",
78
+ "baseLanes": "max(1, min(laneCap, pow2ceil(ceilDiv(dkWords, max(1, tunables.LANE_WORDS)))))",
79
+ "subgroupVariable": "device.features.has(\"subgroups\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize < device.adapterInfo.subgroupMaxSize",
80
+ "wideTileV": "max(1, min(4, headDimV, floor(16 / max(1, ceilDiv(dkWords, baseLanes)))))",
81
+ "wideVGroups": "max(1, min(tunables.V_GROUPS, ceilDiv(headDimV, wideTileV), max(1, floor(deviceWorkgroupCap / baseLanes))))",
82
+ "wideWorkgroupSize": "baseLanes * wideVGroups",
83
+ "variableSubgroupTile": "subgroupVariable and tensorDtypes.queryT == \"float16\" and baseLanes > 1 and baseLanes <= device.adapterInfo.subgroupMinSize and device.adapterInfo.subgroupMinSize % baseLanes == 0 and device.adapterInfo.subgroupMaxSize % baseLanes == 0 and wideWorkgroupSize % device.adapterInfo.subgroupMinSize == 0 and wideWorkgroupSize % device.adapterInfo.subgroupMaxSize == 0 and seqLength >= baseLanes and device.wgslLanguageFeatures.has(\"subgroup_id\")",
84
+ "tileV": "max(1, min(tunables.TILE_V if tunables.TILE_V > 0 else (wideTileV if variableSubgroupTile else 2), headDimV))",
85
+ "subgroupFixed": "device.features.has(\"subgroups\") and has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize == device.adapterInfo.subgroupMaxSize",
86
+ "subgroupWidth": "device.adapterInfo.subgroupMinSize if subgroupFixed else 0",
87
+ "subgroupLaneFit": "not useVec4 and subgroupFixed and subgroupWidth > 1 and subgroupWidth < baseLanes and subgroupWidth <= deviceWorkgroupCap and device.wgslLanguageFeatures.has(\"subgroup_id\") and tileV * ceilDiv(dkWords, subgroupWidth) <= 64 and ceilDiv(dkWords, subgroupWidth) <= 4 * max(1, tunables.LANE_WORDS)",
88
+ "lanes": "subgroupWidth if subgroupLaneFit else baseLanes",
89
+ "wordsPerLane": "ceilDiv(dkWords, max(1, lanes))",
90
+ "dvTiles": "ceilDiv(headDimV, max(1, tileV))",
91
+ "baseVGroups": "max(1, min(tunables.V_GROUPS, dvTiles, max(1, floor(deviceWorkgroupCap / max(1, lanes)))))",
92
+ "vGroups": "min(ceilDiv(baseVGroups, subgroupWidth / lanes), floor(deviceWorkgroupCap / subgroupWidth)) * (subgroupWidth / lanes) if subgroupFixed and lanes > 1 and lanes <= subgroupWidth and subgroupWidth <= deviceWorkgroupCap and device.wgslLanguageFeatures.has(\"subgroup_id\") else baseVGroups",
93
+ "workgroupSize": "lanes * vGroups",
94
+ "variableSubgroupFold": "subgroupVariable and lanes > 1 and lanes <= device.adapterInfo.subgroupMinSize and device.adapterInfo.subgroupMinSize % lanes == 0 and device.adapterInfo.subgroupMaxSize % lanes == 0 and workgroupSize % device.adapterInfo.subgroupMinSize == 0 and workgroupSize % device.adapterInfo.subgroupMaxSize == 0 and seqLength >= lanes and device.wgslLanguageFeatures.has(\"subgroup_id\")",
95
+ "dvBlocks": "ceilDiv(dvTiles, max(1, vGroups))",
96
+ "dispatchUnits": "batchCount * numVHeads * dvBlocks",
97
+ "useL2Norm": "attrs.qk_l2_norm == 1",
98
+ "useSubgroups": "(lanes > 1 and subgroupFixed and lanes <= subgroupWidth and workgroupSize % max(1, subgroupWidth) == 0 and device.wgslLanguageFeatures.has(\"subgroup_id\")) or variableSubgroupFold",
99
+ "foldSlotsPlain": "ceilDiv(tileV, 4)",
100
+ "foldSlotsDelta": "ceilDiv(2 * tileV, 4)",
101
+ "foldFitsPlain": "useSubgroups or lanes == 1 or workgroupSize * foldSlotsPlain * 16 <= device.limits.maxComputeWorkgroupStorageSize",
102
+ "foldFitsDelta": "useSubgroups or lanes == 1 or workgroupSize * foldSlotsDelta * 16 <= device.limits.maxComputeWorkgroupStorageSize",
103
+ "registerWordsOk": "tileV * wordsPerLane <= (64 if subgroupLaneFit else 16)",
104
+ "geometryOk": "registerWordsOk and workgroupSize <= deviceWorkgroupCap and dispatchUnits > 0 and dispatchUnits <= foldedDispatchCapacity",
105
+ "outScale": "attrs.scale if attrs.scale != 0.0 else 1.0 / pow(headDimK, 0.5)",
106
+ "qkElement": "(\"vec4<\" ~ dtypes.T ~ \">\") if useVec4 else dtypes.T",
107
+ "wordF32": "\"vec4<f32>\" if useVec4 else \"f32\"",
108
+ "gateItems": "totalTokens * numVHeads",
109
+ "qkUnits": "totalTokens * numQHeads",
110
+ "gateLanes": "max(1, min(laneCap, 64, pow2ceil(dkWords)))",
111
+ "qwenGate": "attrs.gate_activation == \"qwen\"",
112
+ "betaSigmoid": "attrs.beta_activation == \"sigmoid\"",
113
+ "valueIOWidth": "device.adapterInfo.subgroupMinSize if has(device.adapterInfo, \"subgroupMinSize\") else 0",
114
+ "vectorValueIO": "(tileV == 2 or tileV == 4) and headDimV % tileV == 0 and (tensorDtypes.valueT == \"float16\" or lanes * tunables.MIN_VECTOR_VALUE_GROUPS <= valueIOWidth)",
115
+ "valueIOElement": "(\"vec\" ~ tileV ~ \"<\" ~ dtypes.T ~ \">\") if vectorValueIO else dtypes.T"
116
+ },
117
+ "when": ["contractOk", "geometryOk"],
118
+ "bindings": {
119
+ "query": { "arg": "queryT", "name": "query", "elementType": "$qkElement" },
120
+ "key": { "arg": "keyT", "name": "key", "elementType": "$qkElement" },
121
+ "value": { "arg": "valueT", "name": "value", "elementType": "$valueIOElement" },
122
+ "cu_seqlens": { "arg": "cuSeqlensT", "name": "cu_seqlens" },
123
+ "gate": { "scratch": "gate", "name": "gate", "buffer": "read-only-storage", "elementType": "f32" },
124
+ "initial_state": { "arg": "initialStateT", "name": "initial_state", "elementType": "$wordF32" },
125
+ "output": { "arg": "outputT", "name": "output", "elementType": "$valueIOElement" },
126
+ "final_state": { "arg": "finalStateT", "name": "final_state", "elementType": "$wordF32" },
127
+ "params": {
128
+ "struct": [
129
+ { "name": "batchSize", "type": "u32", "value": "batchCount" },
130
+ { "name": "seqLength", "type": "u32", "value": "seqLength" },
131
+ { "name": "qkBase", "type": "u32", "value": "qkBase" }
132
+ ]
133
+ },
134
+ "params_ragged": {
135
+ "name": "params",
136
+ "struct": [
137
+ { "name": "batchSize", "type": "u32", "value": "batchCount" },
138
+ { "name": "totalTokens", "type": "u32", "value": "totalTokens" },
139
+ { "name": "qkBase", "type": "u32", "value": "qkBase" }
140
+ ]
141
+ },
142
+ "decay": { "arg": "decayT", "name": "decay" },
143
+ "beta": { "arg": "betaT", "name": "beta" },
144
+ "a_log": { "arg": "aLogT", "name": "a_log" },
145
+ "dt_bias": { "arg": "dtBiasT", "name": "dt_bias" },
146
+ "gate_out": { "scratch": "gate", "name": "gate", "elementType": "f32" },
147
+ "params_gate": {
148
+ "name": "params",
149
+ "struct": [
150
+ { "name": "units", "type": "u32", "value": "qkUnits" },
151
+ { "name": "qkBase", "type": "u32", "value": "qkBase" }
152
+ ]
153
+ }
154
+ },
155
+ "variants": [
156
+ {
157
+ "id": "linear_state_ragged",
158
+ "when": ["attrs.update_rule == \"linear\"", "not present.decayT", "not present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "present.initialStateT", "present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
159
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
160
+ "derive": {
161
+ "hasGate": "present.decayT or present.betaT",
162
+ "qkBase": "2 * gateItems if hasGate else 0",
163
+ "useDelta": "present.betaT",
164
+ "hasState": "present.initialStateT",
165
+ "hasCu": "present.cuSeqlensT",
166
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
167
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
168
+ },
169
+ "passes": [
170
+ {
171
+ "id": "gate",
172
+ "name": "GatedDeltaNet.Gate",
173
+ "shader": "gated-delta-net-gate.wgsl.jinja",
174
+ "derive": {
175
+ "hasDecayInput": "present.decayT",
176
+ "hasBetaInput": "present.betaT",
177
+ "qwen": "qwenGate",
178
+ "sigmoidBeta": "betaSigmoid"
179
+ },
180
+ "bindings": ["query", "key", "gate_out", "params_gate"],
181
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
182
+ },
183
+ {
184
+ "id": "main",
185
+ "name": "GatedDeltaNet.Recurrent",
186
+ "shader": "gated-delta-net.wgsl.jinja",
187
+ "bindings": ["query", "key", "value", "cu_seqlens", "gate", "initial_state", "output", "final_state", "params_ragged"],
188
+ "dispatch": {
189
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
190
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
191
+ }
192
+ }
193
+ ]
194
+ },
195
+ {
196
+ "id": "linear_state_packed",
197
+ "when": ["attrs.update_rule == \"linear\"", "not present.decayT", "not present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "present.initialStateT", "not present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
198
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
199
+ "derive": {
200
+ "hasGate": "present.decayT or present.betaT",
201
+ "qkBase": "2 * gateItems if hasGate else 0",
202
+ "useDelta": "present.betaT",
203
+ "hasState": "present.initialStateT",
204
+ "hasCu": "present.cuSeqlensT",
205
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
206
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
207
+ },
208
+ "passes": [
209
+ {
210
+ "id": "gate",
211
+ "name": "GatedDeltaNet.Gate",
212
+ "shader": "gated-delta-net-gate.wgsl.jinja",
213
+ "derive": {
214
+ "hasDecayInput": "present.decayT",
215
+ "hasBetaInput": "present.betaT",
216
+ "qwen": "qwenGate",
217
+ "sigmoidBeta": "betaSigmoid"
218
+ },
219
+ "bindings": ["query", "key", "gate_out", "params_gate"],
220
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
221
+ },
222
+ {
223
+ "id": "main",
224
+ "name": "GatedDeltaNet.Recurrent",
225
+ "shader": "gated-delta-net.wgsl.jinja",
226
+ "bindings": ["query", "key", "value", "gate", "initial_state", "output", "final_state", "params"],
227
+ "dispatch": {
228
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
229
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
230
+ }
231
+ }
232
+ ]
233
+ },
234
+ {
235
+ "id": "linear_zero_ragged",
236
+ "when": ["attrs.update_rule == \"linear\"", "not present.decayT", "not present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "not present.initialStateT", "present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
237
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
238
+ "derive": {
239
+ "hasGate": "present.decayT or present.betaT",
240
+ "qkBase": "2 * gateItems if hasGate else 0",
241
+ "useDelta": "present.betaT",
242
+ "hasState": "present.initialStateT",
243
+ "hasCu": "present.cuSeqlensT",
244
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
245
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
246
+ },
247
+ "passes": [
248
+ {
249
+ "id": "gate",
250
+ "name": "GatedDeltaNet.Gate",
251
+ "shader": "gated-delta-net-gate.wgsl.jinja",
252
+ "derive": {
253
+ "hasDecayInput": "present.decayT",
254
+ "hasBetaInput": "present.betaT",
255
+ "qwen": "qwenGate",
256
+ "sigmoidBeta": "betaSigmoid"
257
+ },
258
+ "bindings": ["query", "key", "gate_out", "params_gate"],
259
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
260
+ },
261
+ {
262
+ "id": "main",
263
+ "name": "GatedDeltaNet.Recurrent",
264
+ "shader": "gated-delta-net.wgsl.jinja",
265
+ "bindings": ["query", "key", "value", "cu_seqlens", "gate", "output", "final_state", "params_ragged"],
266
+ "dispatch": {
267
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
268
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
269
+ }
270
+ }
271
+ ]
272
+ },
273
+ {
274
+ "id": "linear_zero_packed",
275
+ "when": ["attrs.update_rule == \"linear\"", "not present.decayT", "not present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "not present.initialStateT", "not present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
276
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
277
+ "derive": {
278
+ "hasGate": "present.decayT or present.betaT",
279
+ "qkBase": "2 * gateItems if hasGate else 0",
280
+ "useDelta": "present.betaT",
281
+ "hasState": "present.initialStateT",
282
+ "hasCu": "present.cuSeqlensT",
283
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
284
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
285
+ },
286
+ "passes": [
287
+ {
288
+ "id": "gate",
289
+ "name": "GatedDeltaNet.Gate",
290
+ "shader": "gated-delta-net-gate.wgsl.jinja",
291
+ "derive": {
292
+ "hasDecayInput": "present.decayT",
293
+ "hasBetaInput": "present.betaT",
294
+ "qwen": "qwenGate",
295
+ "sigmoidBeta": "betaSigmoid"
296
+ },
297
+ "bindings": ["query", "key", "gate_out", "params_gate"],
298
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
299
+ },
300
+ {
301
+ "id": "main",
302
+ "name": "GatedDeltaNet.Recurrent",
303
+ "shader": "gated-delta-net.wgsl.jinja",
304
+ "bindings": ["query", "key", "value", "gate", "output", "final_state", "params"],
305
+ "dispatch": {
306
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
307
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
308
+ }
309
+ }
310
+ ]
311
+ },
312
+ {
313
+ "id": "gated_state_ragged",
314
+ "when": ["attrs.update_rule == \"gated\"", "present.decayT", "not present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "present.initialStateT", "present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
315
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
316
+ "derive": {
317
+ "hasGate": "present.decayT or present.betaT",
318
+ "qkBase": "2 * gateItems if hasGate else 0",
319
+ "useDelta": "present.betaT",
320
+ "hasState": "present.initialStateT",
321
+ "hasCu": "present.cuSeqlensT",
322
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
323
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
324
+ },
325
+ "passes": [
326
+ {
327
+ "id": "gate",
328
+ "name": "GatedDeltaNet.Gate",
329
+ "shader": "gated-delta-net-gate.wgsl.jinja",
330
+ "derive": {
331
+ "hasDecayInput": "present.decayT",
332
+ "hasBetaInput": "present.betaT",
333
+ "qwen": "qwenGate",
334
+ "sigmoidBeta": "betaSigmoid"
335
+ },
336
+ "bindings": ["query", "key", "decay", "gate_out", "params_gate"],
337
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
338
+ },
339
+ {
340
+ "id": "main",
341
+ "name": "GatedDeltaNet.Recurrent",
342
+ "shader": "gated-delta-net.wgsl.jinja",
343
+ "bindings": ["query", "key", "value", "cu_seqlens", "gate", "initial_state", "output", "final_state", "params_ragged"],
344
+ "dispatch": {
345
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
346
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
347
+ }
348
+ }
349
+ ]
350
+ },
351
+ {
352
+ "id": "gated_state_packed",
353
+ "when": ["attrs.update_rule == \"gated\"", "present.decayT", "not present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "present.initialStateT", "not present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
354
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
355
+ "derive": {
356
+ "hasGate": "present.decayT or present.betaT",
357
+ "qkBase": "2 * gateItems if hasGate else 0",
358
+ "useDelta": "present.betaT",
359
+ "hasState": "present.initialStateT",
360
+ "hasCu": "present.cuSeqlensT",
361
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
362
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
363
+ },
364
+ "passes": [
365
+ {
366
+ "id": "gate",
367
+ "name": "GatedDeltaNet.Gate",
368
+ "shader": "gated-delta-net-gate.wgsl.jinja",
369
+ "derive": {
370
+ "hasDecayInput": "present.decayT",
371
+ "hasBetaInput": "present.betaT",
372
+ "qwen": "qwenGate",
373
+ "sigmoidBeta": "betaSigmoid"
374
+ },
375
+ "bindings": ["query", "key", "decay", "gate_out", "params_gate"],
376
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
377
+ },
378
+ {
379
+ "id": "main",
380
+ "name": "GatedDeltaNet.Recurrent",
381
+ "shader": "gated-delta-net.wgsl.jinja",
382
+ "bindings": ["query", "key", "value", "gate", "initial_state", "output", "final_state", "params"],
383
+ "dispatch": {
384
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
385
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
386
+ }
387
+ }
388
+ ]
389
+ },
390
+ {
391
+ "id": "gated_zero_ragged",
392
+ "when": ["attrs.update_rule == \"gated\"", "present.decayT", "not present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "not present.initialStateT", "present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
393
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
394
+ "derive": {
395
+ "hasGate": "present.decayT or present.betaT",
396
+ "qkBase": "2 * gateItems if hasGate else 0",
397
+ "useDelta": "present.betaT",
398
+ "hasState": "present.initialStateT",
399
+ "hasCu": "present.cuSeqlensT",
400
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
401
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
402
+ },
403
+ "passes": [
404
+ {
405
+ "id": "gate",
406
+ "name": "GatedDeltaNet.Gate",
407
+ "shader": "gated-delta-net-gate.wgsl.jinja",
408
+ "derive": {
409
+ "hasDecayInput": "present.decayT",
410
+ "hasBetaInput": "present.betaT",
411
+ "qwen": "qwenGate",
412
+ "sigmoidBeta": "betaSigmoid"
413
+ },
414
+ "bindings": ["query", "key", "decay", "gate_out", "params_gate"],
415
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
416
+ },
417
+ {
418
+ "id": "main",
419
+ "name": "GatedDeltaNet.Recurrent",
420
+ "shader": "gated-delta-net.wgsl.jinja",
421
+ "bindings": ["query", "key", "value", "cu_seqlens", "gate", "output", "final_state", "params_ragged"],
422
+ "dispatch": {
423
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
424
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
425
+ }
426
+ }
427
+ ]
428
+ },
429
+ {
430
+ "id": "gated_zero_packed",
431
+ "when": ["attrs.update_rule == \"gated\"", "present.decayT", "not present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "not present.initialStateT", "not present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
432
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
433
+ "derive": {
434
+ "hasGate": "present.decayT or present.betaT",
435
+ "qkBase": "2 * gateItems if hasGate else 0",
436
+ "useDelta": "present.betaT",
437
+ "hasState": "present.initialStateT",
438
+ "hasCu": "present.cuSeqlensT",
439
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
440
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
441
+ },
442
+ "passes": [
443
+ {
444
+ "id": "gate",
445
+ "name": "GatedDeltaNet.Gate",
446
+ "shader": "gated-delta-net-gate.wgsl.jinja",
447
+ "derive": {
448
+ "hasDecayInput": "present.decayT",
449
+ "hasBetaInput": "present.betaT",
450
+ "qwen": "qwenGate",
451
+ "sigmoidBeta": "betaSigmoid"
452
+ },
453
+ "bindings": ["query", "key", "decay", "gate_out", "params_gate"],
454
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
455
+ },
456
+ {
457
+ "id": "main",
458
+ "name": "GatedDeltaNet.Recurrent",
459
+ "shader": "gated-delta-net.wgsl.jinja",
460
+ "bindings": ["query", "key", "value", "gate", "output", "final_state", "params"],
461
+ "dispatch": {
462
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
463
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
464
+ }
465
+ }
466
+ ]
467
+ },
468
+ {
469
+ "id": "gated_qwen_state_ragged",
470
+ "when": ["attrs.update_rule == \"gated\"", "present.decayT", "not present.betaT", "present.aLogT", "present.dtBiasT", "qwenGate", "present.initialStateT", "present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
471
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
472
+ "derive": {
473
+ "hasGate": "present.decayT or present.betaT",
474
+ "qkBase": "2 * gateItems if hasGate else 0",
475
+ "useDelta": "present.betaT",
476
+ "hasState": "present.initialStateT",
477
+ "hasCu": "present.cuSeqlensT",
478
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
479
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
480
+ },
481
+ "passes": [
482
+ {
483
+ "id": "gate",
484
+ "name": "GatedDeltaNet.Gate",
485
+ "shader": "gated-delta-net-gate.wgsl.jinja",
486
+ "derive": {
487
+ "hasDecayInput": "present.decayT",
488
+ "hasBetaInput": "present.betaT",
489
+ "qwen": "qwenGate",
490
+ "sigmoidBeta": "betaSigmoid"
491
+ },
492
+ "bindings": ["query", "key", "decay", "a_log", "dt_bias", "gate_out", "params_gate"],
493
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
494
+ },
495
+ {
496
+ "id": "main",
497
+ "name": "GatedDeltaNet.Recurrent",
498
+ "shader": "gated-delta-net.wgsl.jinja",
499
+ "bindings": ["query", "key", "value", "cu_seqlens", "gate", "initial_state", "output", "final_state", "params_ragged"],
500
+ "dispatch": {
501
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
502
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
503
+ }
504
+ }
505
+ ]
506
+ },
507
+ {
508
+ "id": "gated_qwen_state_packed",
509
+ "when": ["attrs.update_rule == \"gated\"", "present.decayT", "not present.betaT", "present.aLogT", "present.dtBiasT", "qwenGate", "present.initialStateT", "not present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
510
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
511
+ "derive": {
512
+ "hasGate": "present.decayT or present.betaT",
513
+ "qkBase": "2 * gateItems if hasGate else 0",
514
+ "useDelta": "present.betaT",
515
+ "hasState": "present.initialStateT",
516
+ "hasCu": "present.cuSeqlensT",
517
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
518
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
519
+ },
520
+ "passes": [
521
+ {
522
+ "id": "gate",
523
+ "name": "GatedDeltaNet.Gate",
524
+ "shader": "gated-delta-net-gate.wgsl.jinja",
525
+ "derive": {
526
+ "hasDecayInput": "present.decayT",
527
+ "hasBetaInput": "present.betaT",
528
+ "qwen": "qwenGate",
529
+ "sigmoidBeta": "betaSigmoid"
530
+ },
531
+ "bindings": ["query", "key", "decay", "a_log", "dt_bias", "gate_out", "params_gate"],
532
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
533
+ },
534
+ {
535
+ "id": "main",
536
+ "name": "GatedDeltaNet.Recurrent",
537
+ "shader": "gated-delta-net.wgsl.jinja",
538
+ "bindings": ["query", "key", "value", "gate", "initial_state", "output", "final_state", "params"],
539
+ "dispatch": {
540
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
541
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
542
+ }
543
+ }
544
+ ]
545
+ },
546
+ {
547
+ "id": "gated_qwen_zero_ragged",
548
+ "when": ["attrs.update_rule == \"gated\"", "present.decayT", "not present.betaT", "present.aLogT", "present.dtBiasT", "qwenGate", "not present.initialStateT", "present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
549
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
550
+ "derive": {
551
+ "hasGate": "present.decayT or present.betaT",
552
+ "qkBase": "2 * gateItems if hasGate else 0",
553
+ "useDelta": "present.betaT",
554
+ "hasState": "present.initialStateT",
555
+ "hasCu": "present.cuSeqlensT",
556
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
557
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
558
+ },
559
+ "passes": [
560
+ {
561
+ "id": "gate",
562
+ "name": "GatedDeltaNet.Gate",
563
+ "shader": "gated-delta-net-gate.wgsl.jinja",
564
+ "derive": {
565
+ "hasDecayInput": "present.decayT",
566
+ "hasBetaInput": "present.betaT",
567
+ "qwen": "qwenGate",
568
+ "sigmoidBeta": "betaSigmoid"
569
+ },
570
+ "bindings": ["query", "key", "decay", "a_log", "dt_bias", "gate_out", "params_gate"],
571
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
572
+ },
573
+ {
574
+ "id": "main",
575
+ "name": "GatedDeltaNet.Recurrent",
576
+ "shader": "gated-delta-net.wgsl.jinja",
577
+ "bindings": ["query", "key", "value", "cu_seqlens", "gate", "output", "final_state", "params_ragged"],
578
+ "dispatch": {
579
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
580
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
581
+ }
582
+ }
583
+ ]
584
+ },
585
+ {
586
+ "id": "gated_qwen_zero_packed",
587
+ "when": ["attrs.update_rule == \"gated\"", "present.decayT", "not present.betaT", "present.aLogT", "present.dtBiasT", "qwenGate", "not present.initialStateT", "not present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
588
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
589
+ "derive": {
590
+ "hasGate": "present.decayT or present.betaT",
591
+ "qkBase": "2 * gateItems if hasGate else 0",
592
+ "useDelta": "present.betaT",
593
+ "hasState": "present.initialStateT",
594
+ "hasCu": "present.cuSeqlensT",
595
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
596
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
597
+ },
598
+ "passes": [
599
+ {
600
+ "id": "gate",
601
+ "name": "GatedDeltaNet.Gate",
602
+ "shader": "gated-delta-net-gate.wgsl.jinja",
603
+ "derive": {
604
+ "hasDecayInput": "present.decayT",
605
+ "hasBetaInput": "present.betaT",
606
+ "qwen": "qwenGate",
607
+ "sigmoidBeta": "betaSigmoid"
608
+ },
609
+ "bindings": ["query", "key", "decay", "a_log", "dt_bias", "gate_out", "params_gate"],
610
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
611
+ },
612
+ {
613
+ "id": "main",
614
+ "name": "GatedDeltaNet.Recurrent",
615
+ "shader": "gated-delta-net.wgsl.jinja",
616
+ "bindings": ["query", "key", "value", "gate", "output", "final_state", "params"],
617
+ "dispatch": {
618
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
619
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
620
+ }
621
+ }
622
+ ]
623
+ },
624
+ {
625
+ "id": "delta_state_ragged",
626
+ "when": ["attrs.update_rule == \"delta\"", "not present.decayT", "present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "present.initialStateT", "present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
627
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
628
+ "derive": {
629
+ "hasGate": "present.decayT or present.betaT",
630
+ "qkBase": "2 * gateItems if hasGate else 0",
631
+ "useDelta": "present.betaT",
632
+ "hasState": "present.initialStateT",
633
+ "hasCu": "present.cuSeqlensT",
634
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
635
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
636
+ },
637
+ "passes": [
638
+ {
639
+ "id": "gate",
640
+ "name": "GatedDeltaNet.Gate",
641
+ "shader": "gated-delta-net-gate.wgsl.jinja",
642
+ "derive": {
643
+ "hasDecayInput": "present.decayT",
644
+ "hasBetaInput": "present.betaT",
645
+ "qwen": "qwenGate",
646
+ "sigmoidBeta": "betaSigmoid"
647
+ },
648
+ "bindings": ["query", "key", "beta", "gate_out", "params_gate"],
649
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
650
+ },
651
+ {
652
+ "id": "main",
653
+ "name": "GatedDeltaNet.Recurrent",
654
+ "shader": "gated-delta-net.wgsl.jinja",
655
+ "bindings": ["query", "key", "value", "cu_seqlens", "gate", "initial_state", "output", "final_state", "params_ragged"],
656
+ "dispatch": {
657
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
658
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
659
+ }
660
+ }
661
+ ]
662
+ },
663
+ {
664
+ "id": "delta_state_packed",
665
+ "when": ["attrs.update_rule == \"delta\"", "not present.decayT", "present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "present.initialStateT", "not present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
666
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
667
+ "derive": {
668
+ "hasGate": "present.decayT or present.betaT",
669
+ "qkBase": "2 * gateItems if hasGate else 0",
670
+ "useDelta": "present.betaT",
671
+ "hasState": "present.initialStateT",
672
+ "hasCu": "present.cuSeqlensT",
673
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
674
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
675
+ },
676
+ "passes": [
677
+ {
678
+ "id": "gate",
679
+ "name": "GatedDeltaNet.Gate",
680
+ "shader": "gated-delta-net-gate.wgsl.jinja",
681
+ "derive": {
682
+ "hasDecayInput": "present.decayT",
683
+ "hasBetaInput": "present.betaT",
684
+ "qwen": "qwenGate",
685
+ "sigmoidBeta": "betaSigmoid"
686
+ },
687
+ "bindings": ["query", "key", "beta", "gate_out", "params_gate"],
688
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
689
+ },
690
+ {
691
+ "id": "main",
692
+ "name": "GatedDeltaNet.Recurrent",
693
+ "shader": "gated-delta-net.wgsl.jinja",
694
+ "bindings": ["query", "key", "value", "gate", "initial_state", "output", "final_state", "params"],
695
+ "dispatch": {
696
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
697
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
698
+ }
699
+ }
700
+ ]
701
+ },
702
+ {
703
+ "id": "delta_zero_ragged",
704
+ "when": ["attrs.update_rule == \"delta\"", "not present.decayT", "present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "not present.initialStateT", "present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
705
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
706
+ "derive": {
707
+ "hasGate": "present.decayT or present.betaT",
708
+ "qkBase": "2 * gateItems if hasGate else 0",
709
+ "useDelta": "present.betaT",
710
+ "hasState": "present.initialStateT",
711
+ "hasCu": "present.cuSeqlensT",
712
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
713
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
714
+ },
715
+ "passes": [
716
+ {
717
+ "id": "gate",
718
+ "name": "GatedDeltaNet.Gate",
719
+ "shader": "gated-delta-net-gate.wgsl.jinja",
720
+ "derive": {
721
+ "hasDecayInput": "present.decayT",
722
+ "hasBetaInput": "present.betaT",
723
+ "qwen": "qwenGate",
724
+ "sigmoidBeta": "betaSigmoid"
725
+ },
726
+ "bindings": ["query", "key", "beta", "gate_out", "params_gate"],
727
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
728
+ },
729
+ {
730
+ "id": "main",
731
+ "name": "GatedDeltaNet.Recurrent",
732
+ "shader": "gated-delta-net.wgsl.jinja",
733
+ "bindings": ["query", "key", "value", "cu_seqlens", "gate", "output", "final_state", "params_ragged"],
734
+ "dispatch": {
735
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
736
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
737
+ }
738
+ }
739
+ ]
740
+ },
741
+ {
742
+ "id": "delta_zero_packed",
743
+ "when": ["attrs.update_rule == \"delta\"", "not present.decayT", "present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "not present.initialStateT", "not present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
744
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
745
+ "derive": {
746
+ "hasGate": "present.decayT or present.betaT",
747
+ "qkBase": "2 * gateItems if hasGate else 0",
748
+ "useDelta": "present.betaT",
749
+ "hasState": "present.initialStateT",
750
+ "hasCu": "present.cuSeqlensT",
751
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
752
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
753
+ },
754
+ "passes": [
755
+ {
756
+ "id": "gate",
757
+ "name": "GatedDeltaNet.Gate",
758
+ "shader": "gated-delta-net-gate.wgsl.jinja",
759
+ "derive": {
760
+ "hasDecayInput": "present.decayT",
761
+ "hasBetaInput": "present.betaT",
762
+ "qwen": "qwenGate",
763
+ "sigmoidBeta": "betaSigmoid"
764
+ },
765
+ "bindings": ["query", "key", "beta", "gate_out", "params_gate"],
766
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
767
+ },
768
+ {
769
+ "id": "main",
770
+ "name": "GatedDeltaNet.Recurrent",
771
+ "shader": "gated-delta-net.wgsl.jinja",
772
+ "bindings": ["query", "key", "value", "gate", "output", "final_state", "params"],
773
+ "dispatch": {
774
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
775
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
776
+ }
777
+ }
778
+ ]
779
+ },
780
+ {
781
+ "id": "gated_delta_state_ragged",
782
+ "when": ["attrs.update_rule == \"gated_delta\"", "present.decayT", "present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "present.initialStateT", "present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
783
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
784
+ "derive": {
785
+ "hasGate": "present.decayT or present.betaT",
786
+ "qkBase": "2 * gateItems if hasGate else 0",
787
+ "useDelta": "present.betaT",
788
+ "hasState": "present.initialStateT",
789
+ "hasCu": "present.cuSeqlensT",
790
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
791
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
792
+ },
793
+ "passes": [
794
+ {
795
+ "id": "gate",
796
+ "name": "GatedDeltaNet.Gate",
797
+ "shader": "gated-delta-net-gate.wgsl.jinja",
798
+ "derive": {
799
+ "hasDecayInput": "present.decayT",
800
+ "hasBetaInput": "present.betaT",
801
+ "qwen": "qwenGate",
802
+ "sigmoidBeta": "betaSigmoid"
803
+ },
804
+ "bindings": ["query", "key", "decay", "beta", "gate_out", "params_gate"],
805
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
806
+ },
807
+ {
808
+ "id": "main",
809
+ "name": "GatedDeltaNet.Recurrent",
810
+ "shader": "gated-delta-net.wgsl.jinja",
811
+ "bindings": ["query", "key", "value", "cu_seqlens", "gate", "initial_state", "output", "final_state", "params_ragged"],
812
+ "dispatch": {
813
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
814
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
815
+ }
816
+ }
817
+ ]
818
+ },
819
+ {
820
+ "id": "gated_delta_state_packed",
821
+ "when": ["attrs.update_rule == \"gated_delta\"", "present.decayT", "present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "present.initialStateT", "not present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
822
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
823
+ "derive": {
824
+ "hasGate": "present.decayT or present.betaT",
825
+ "qkBase": "2 * gateItems if hasGate else 0",
826
+ "useDelta": "present.betaT",
827
+ "hasState": "present.initialStateT",
828
+ "hasCu": "present.cuSeqlensT",
829
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
830
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
831
+ },
832
+ "passes": [
833
+ {
834
+ "id": "gate",
835
+ "name": "GatedDeltaNet.Gate",
836
+ "shader": "gated-delta-net-gate.wgsl.jinja",
837
+ "derive": {
838
+ "hasDecayInput": "present.decayT",
839
+ "hasBetaInput": "present.betaT",
840
+ "qwen": "qwenGate",
841
+ "sigmoidBeta": "betaSigmoid"
842
+ },
843
+ "bindings": ["query", "key", "decay", "beta", "gate_out", "params_gate"],
844
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
845
+ },
846
+ {
847
+ "id": "main",
848
+ "name": "GatedDeltaNet.Recurrent",
849
+ "shader": "gated-delta-net.wgsl.jinja",
850
+ "bindings": ["query", "key", "value", "gate", "initial_state", "output", "final_state", "params"],
851
+ "dispatch": {
852
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
853
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
854
+ }
855
+ }
856
+ ]
857
+ },
858
+ {
859
+ "id": "gated_delta_zero_ragged",
860
+ "when": ["attrs.update_rule == \"gated_delta\"", "present.decayT", "present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "not present.initialStateT", "present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
861
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
862
+ "derive": {
863
+ "hasGate": "present.decayT or present.betaT",
864
+ "qkBase": "2 * gateItems if hasGate else 0",
865
+ "useDelta": "present.betaT",
866
+ "hasState": "present.initialStateT",
867
+ "hasCu": "present.cuSeqlensT",
868
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
869
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
870
+ },
871
+ "passes": [
872
+ {
873
+ "id": "gate",
874
+ "name": "GatedDeltaNet.Gate",
875
+ "shader": "gated-delta-net-gate.wgsl.jinja",
876
+ "derive": {
877
+ "hasDecayInput": "present.decayT",
878
+ "hasBetaInput": "present.betaT",
879
+ "qwen": "qwenGate",
880
+ "sigmoidBeta": "betaSigmoid"
881
+ },
882
+ "bindings": ["query", "key", "decay", "beta", "gate_out", "params_gate"],
883
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
884
+ },
885
+ {
886
+ "id": "main",
887
+ "name": "GatedDeltaNet.Recurrent",
888
+ "shader": "gated-delta-net.wgsl.jinja",
889
+ "bindings": ["query", "key", "value", "cu_seqlens", "gate", "output", "final_state", "params_ragged"],
890
+ "dispatch": {
891
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
892
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
893
+ }
894
+ }
895
+ ]
896
+ },
897
+ {
898
+ "id": "gated_delta_zero_packed",
899
+ "when": ["attrs.update_rule == \"gated_delta\"", "present.decayT", "present.betaT", "not present.aLogT", "not present.dtBiasT", "not qwenGate", "not present.initialStateT", "not present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
900
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
901
+ "derive": {
902
+ "hasGate": "present.decayT or present.betaT",
903
+ "qkBase": "2 * gateItems if hasGate else 0",
904
+ "useDelta": "present.betaT",
905
+ "hasState": "present.initialStateT",
906
+ "hasCu": "present.cuSeqlensT",
907
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
908
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
909
+ },
910
+ "passes": [
911
+ {
912
+ "id": "gate",
913
+ "name": "GatedDeltaNet.Gate",
914
+ "shader": "gated-delta-net-gate.wgsl.jinja",
915
+ "derive": {
916
+ "hasDecayInput": "present.decayT",
917
+ "hasBetaInput": "present.betaT",
918
+ "qwen": "qwenGate",
919
+ "sigmoidBeta": "betaSigmoid"
920
+ },
921
+ "bindings": ["query", "key", "decay", "beta", "gate_out", "params_gate"],
922
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
923
+ },
924
+ {
925
+ "id": "main",
926
+ "name": "GatedDeltaNet.Recurrent",
927
+ "shader": "gated-delta-net.wgsl.jinja",
928
+ "bindings": ["query", "key", "value", "gate", "output", "final_state", "params"],
929
+ "dispatch": {
930
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
931
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
932
+ }
933
+ }
934
+ ]
935
+ },
936
+ {
937
+ "id": "gated_delta_qwen_state_ragged",
938
+ "when": ["attrs.update_rule == \"gated_delta\"", "present.decayT", "present.betaT", "present.aLogT", "present.dtBiasT", "qwenGate", "present.initialStateT", "present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
939
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
940
+ "derive": {
941
+ "hasGate": "present.decayT or present.betaT",
942
+ "qkBase": "2 * gateItems if hasGate else 0",
943
+ "useDelta": "present.betaT",
944
+ "hasState": "present.initialStateT",
945
+ "hasCu": "present.cuSeqlensT",
946
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
947
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
948
+ },
949
+ "passes": [
950
+ {
951
+ "id": "gate",
952
+ "name": "GatedDeltaNet.Gate",
953
+ "shader": "gated-delta-net-gate.wgsl.jinja",
954
+ "derive": {
955
+ "hasDecayInput": "present.decayT",
956
+ "hasBetaInput": "present.betaT",
957
+ "qwen": "qwenGate",
958
+ "sigmoidBeta": "betaSigmoid"
959
+ },
960
+ "bindings": ["query", "key", "decay", "beta", "a_log", "dt_bias", "gate_out", "params_gate"],
961
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
962
+ },
963
+ {
964
+ "id": "main",
965
+ "name": "GatedDeltaNet.Recurrent",
966
+ "shader": "gated-delta-net.wgsl.jinja",
967
+ "bindings": ["query", "key", "value", "cu_seqlens", "gate", "initial_state", "output", "final_state", "params_ragged"],
968
+ "dispatch": {
969
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
970
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
971
+ }
972
+ }
973
+ ]
974
+ },
975
+ {
976
+ "id": "gated_delta_qwen_state_packed",
977
+ "when": ["attrs.update_rule == \"gated_delta\"", "present.decayT", "present.betaT", "present.aLogT", "present.dtBiasT", "qwenGate", "present.initialStateT", "not present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
978
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
979
+ "derive": {
980
+ "hasGate": "present.decayT or present.betaT",
981
+ "qkBase": "2 * gateItems if hasGate else 0",
982
+ "useDelta": "present.betaT",
983
+ "hasState": "present.initialStateT",
984
+ "hasCu": "present.cuSeqlensT",
985
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
986
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
987
+ },
988
+ "passes": [
989
+ {
990
+ "id": "gate",
991
+ "name": "GatedDeltaNet.Gate",
992
+ "shader": "gated-delta-net-gate.wgsl.jinja",
993
+ "derive": {
994
+ "hasDecayInput": "present.decayT",
995
+ "hasBetaInput": "present.betaT",
996
+ "qwen": "qwenGate",
997
+ "sigmoidBeta": "betaSigmoid"
998
+ },
999
+ "bindings": ["query", "key", "decay", "beta", "a_log", "dt_bias", "gate_out", "params_gate"],
1000
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
1001
+ },
1002
+ {
1003
+ "id": "main",
1004
+ "name": "GatedDeltaNet.Recurrent",
1005
+ "shader": "gated-delta-net.wgsl.jinja",
1006
+ "bindings": ["query", "key", "value", "gate", "initial_state", "output", "final_state", "params"],
1007
+ "dispatch": {
1008
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
1009
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
1010
+ }
1011
+ }
1012
+ ]
1013
+ },
1014
+ {
1015
+ "id": "gated_delta_qwen_zero_ragged",
1016
+ "when": ["attrs.update_rule == \"gated_delta\"", "present.decayT", "present.betaT", "present.aLogT", "present.dtBiasT", "qwenGate", "not present.initialStateT", "present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
1017
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
1018
+ "derive": {
1019
+ "hasGate": "present.decayT or present.betaT",
1020
+ "qkBase": "2 * gateItems if hasGate else 0",
1021
+ "useDelta": "present.betaT",
1022
+ "hasState": "present.initialStateT",
1023
+ "hasCu": "present.cuSeqlensT",
1024
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
1025
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
1026
+ },
1027
+ "passes": [
1028
+ {
1029
+ "id": "gate",
1030
+ "name": "GatedDeltaNet.Gate",
1031
+ "shader": "gated-delta-net-gate.wgsl.jinja",
1032
+ "derive": {
1033
+ "hasDecayInput": "present.decayT",
1034
+ "hasBetaInput": "present.betaT",
1035
+ "qwen": "qwenGate",
1036
+ "sigmoidBeta": "betaSigmoid"
1037
+ },
1038
+ "bindings": ["query", "key", "decay", "beta", "a_log", "dt_bias", "gate_out", "params_gate"],
1039
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
1040
+ },
1041
+ {
1042
+ "id": "main",
1043
+ "name": "GatedDeltaNet.Recurrent",
1044
+ "shader": "gated-delta-net.wgsl.jinja",
1045
+ "bindings": ["query", "key", "value", "cu_seqlens", "gate", "output", "final_state", "params_ragged"],
1046
+ "dispatch": {
1047
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
1048
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
1049
+ }
1050
+ }
1051
+ ]
1052
+ },
1053
+ {
1054
+ "id": "gated_delta_qwen_zero_packed",
1055
+ "when": ["attrs.update_rule == \"gated_delta\"", "present.decayT", "present.betaT", "present.aLogT", "present.dtBiasT", "qwenGate", "not present.initialStateT", "not present.cuSeqlensT", "foldFitsDelta if present.betaT else foldFitsPlain"],
1056
+ "intermediates": [{ "id": "gate", "dtype": "float32", "shape": "[qkBase + (5 if pairTokens else 3) * qkUnits]" }],
1057
+ "derive": {
1058
+ "hasGate": "present.decayT or present.betaT",
1059
+ "qkBase": "2 * gateItems if hasGate else 0",
1060
+ "useDelta": "present.betaT",
1061
+ "hasState": "present.initialStateT",
1062
+ "hasCu": "present.cuSeqlensT",
1063
+ "pairTokens": "useDelta and not useSubgroups and tensorDtypes.queryT == \"float16\" and lanes > 1 and seqLength >= 2 * lanes and workgroupSize * tileV * 16 <= device.limits.maxComputeWorkgroupStorageSize and 4 * (qkBase + 5 * qkUnits) <= min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
1064
+ "foldSlots": "tileV if pairTokens else (foldSlotsDelta if useDelta else foldSlotsPlain)"
1065
+ },
1066
+ "passes": [
1067
+ {
1068
+ "id": "gate",
1069
+ "name": "GatedDeltaNet.Gate",
1070
+ "shader": "gated-delta-net-gate.wgsl.jinja",
1071
+ "derive": {
1072
+ "hasDecayInput": "present.decayT",
1073
+ "hasBetaInput": "present.betaT",
1074
+ "qwen": "qwenGate",
1075
+ "sigmoidBeta": "betaSigmoid"
1076
+ },
1077
+ "bindings": ["query", "key", "decay", "beta", "a_log", "dt_bias", "gate_out", "params_gate"],
1078
+ "dispatch": { "x": "min(qkUnits, 65535)", "y": "ceilDiv(qkUnits, 65535)", "z": 1 }
1079
+ },
1080
+ {
1081
+ "id": "main",
1082
+ "name": "GatedDeltaNet.Recurrent",
1083
+ "shader": "gated-delta-net.wgsl.jinja",
1084
+ "bindings": ["query", "key", "value", "gate", "output", "final_state", "params"],
1085
+ "dispatch": {
1086
+ "x": "min(dispatchUnits, DISPATCH_FOLD_WIDTH)",
1087
+ "y": "ceilDiv(dispatchUnits, DISPATCH_FOLD_WIDTH)"
1088
+ }
1089
+ }
1090
+ ]
1091
+ }
1092
+ ]
1093
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "com.microsoft.GatedDeltaNet",
3
+ "id": "_com_microsoft_gateddeltanet_webgpu_327a002",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "JjlCHBCgwhVtN5uX68wTtRvl28U3uxij+8CtQr6ihus=",
11
+ "gated-delta-net-gate.wgsl.jinja": "F2VfVvKYHtF5V+WJYEFCyTIoLtqOZScZjglN7Ld/qZk=",
12
+ "gated-delta-net.wgsl.jinja": "afS/AoT0XbtYWu3HVPbuzBMEij1yt+oz71UGqaUlplU=",
13
+ "manifest.json": "TQScCwpV1Wu5pvm5/XBsrQdcq9np2crCD+QN/pbaUC8=",
14
+ "test.json": "I7KVc4VWBUm1VG7XYAc/1ntOGoAmvG6eWc2/7pW9VVU="
15
+ }
16
+ },
17
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
18
+ "webgpu": {
19
+ "manifestSpec": "2.1",
20
+ "variants": {
21
+ "linear_state_ragged": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
22
+ "linear_state_packed": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
23
+ "linear_zero_ragged": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
24
+ "linear_zero_packed": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
25
+ "gated_state_ragged": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
26
+ "gated_state_packed": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
27
+ "gated_zero_ragged": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
28
+ "gated_zero_packed": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
29
+ "gated_qwen_state_ragged": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
30
+ "gated_qwen_state_packed": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
31
+ "gated_qwen_zero_ragged": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
32
+ "gated_qwen_zero_packed": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
33
+ "delta_state_ragged": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
34
+ "delta_state_packed": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
35
+ "delta_zero_ragged": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
36
+ "delta_zero_packed": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
37
+ "gated_delta_state_ragged": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
38
+ "gated_delta_state_packed": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
39
+ "gated_delta_zero_ragged": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
40
+ "gated_delta_zero_packed": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
41
+ "gated_delta_qwen_state_ragged": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
42
+ "gated_delta_qwen_state_packed": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
43
+ "gated_delta_qwen_zero_ragged": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"],
44
+ "gated_delta_qwen_zero_packed": ["gated-delta-net-gate.wgsl.jinja", "gated-delta-net.wgsl.jinja"]
45
+ }
46
+ }
47
+ }
build/webgpu/test.json ADDED
The diff for this file is too large to render. See raw diff