Xenova HF Staff commited on
Commit
4eccb3a
·
verified ·
1 Parent(s): 2437bce

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,78 @@
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
+ # ai.onnx.RotaryEmbedding
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 23
12
+
13
+ ## Description
14
+
15
+ Implements ONNX opset-23 RotaryEmbedding for float16 and float32 tensors. Applies rotary positional embeddings (RoPE) by rotating each head's embedding vector using precomputed `cos_cache` and `sin_cache` values. A partial rotation can be applied by setting `rotary_embedding_dim` to rotate only a prefix of the head dimension. `position_ids` keeps its standard logical int64 type; valid positions are non-negative and bounded by the WebGPU-addressable cache, so the backend stores them losslessly as uint32. Other ONNX floating-point input types are not yet implemented.
16
+
17
+ See the [ONNX `RotaryEmbedding` spec](https://onnx.ai/onnx/operators/onnx__RotaryEmbedding.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- | --- |
23
+ | `X` | `x` | `T` | same as logical dtype | — | — | Input token embeddings. Shape is `(batch_size, sequence_length, hidden_size)` for rank 3 or `(batch_size, num_heads, sequence_length, head_size)` for rank 4. `head_size` must be even, and the `num_heads` attribute is required for rank-3 input. | required |
24
+ | `cos_cache` | `cos` | `T` | same as logical dtype | — | — | Precomputed cosine values. Without `position_ids`, shape is `(batch_size, sequence_length, rotary_dim/2)`; with `position_ids`, shape is `(max_sequence_length, rotary_dim/2)`. | required |
25
+ | `sin_cache` | `sin` | `T` | same as logical dtype | — | — | Precomputed sine values with the same shape and type as `cos_cache`. | required |
26
+ | `position_ids` | `positionIds` | `M` | `uint32` | `2` | — | Optional logical int64 per-token position indices of shape `(batch_size, sequence_length)`. Valid positions are non-negative cache-row indices and use uint32 WebGPU storage. When supplied, the 2D cache tables are gathered at these positions. | optional |
27
+
28
+ ## Outputs
29
+
30
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
31
+ | --- | --- | --- | --- | --- | --- | --- |
32
+ | `Y` | `y` | `T` | same as `X` | same as `X` | Rotary-position-encoded tensor with the same shape and type as `X`. | required |
33
+
34
+ ## Attributes
35
+
36
+ Attributes and default values (overridable per request):
37
+
38
+ | Attribute | Default | Description |
39
+ | --- | --- | --- |
40
+ | `interleaved` | `0` | Set to 1 to rotate using an interleaved pattern (even/odd elements), or 0 to split the head dimension into two contiguous halves. Default is 0. |
41
+ | `rotary_embedding_dim` | `0` | Number of head-dimension elements to rotate; `0` means rotate the full head dimension. When set, only the leading `rotary_embedding_dim` elements are rotated and the rest are passed through unchanged. |
42
+ | `num_heads` | — | Optional number of attention heads. ONNX requires this attribute when `X` is rank 3; it is unnecessary for rank-4 input because the head count is explicit in the shape. |
43
+
44
+ ## Type constraints
45
+
46
+ | Variable | Allowed dtypes |
47
+ | --- | --- |
48
+ | `T` | `float32`, `float16` |
49
+ | `M` | `int64` |
50
+
51
+ ## Files
52
+
53
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
54
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
55
+ - [`test.json`](build/webgpu/test.json) — correctness cases
56
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
57
+ - [`rotary-embedding.wgsl.jinja`](build/webgpu/rotary-embedding.wgsl.jinja)
58
+
59
+ ## Use with `@huggingface/kernels`
60
+
61
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
62
+ It then allocates the result tensors automatically.
63
+
64
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
65
+
66
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
67
+
68
+ ```js
69
+ import { getKernel } from "@huggingface/kernels";
70
+
71
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.RotaryEmbedding", { version: 1 });
72
+ const { y } = await kernel({
73
+ x: { data: xData, shape: [1, 2, 1, 4] },
74
+ cos: { data: cosData, shape: [16, 2] },
75
+ sin: { data: sinData, shape: [16, 2] },
76
+ positionIds: { data: positionIdsData, shape: [1, 1] },
77
+ });
78
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.RotaryEmbedding",
3
+ "tunableSpace": { "WORKGROUP_SIZE": [128, 256] },
4
+ "cases": [
5
+ {
6
+ "name": "rotary-f32-b4s128h64",
7
+ "attrs": { "num_heads": 1 },
8
+ "preset": "smoke",
9
+ "vars": { "batch": 4, "seq": 128, "headSize": 64 },
10
+ "inputs": {
11
+ "x": { "shape": [4, 128, 64], "dtype": "float32", "dist": "normal", "seed": 115, "scale": 0.2 },
12
+ "cos": {
13
+ "shape": [4, 128, 32],
14
+ "dtype": "float32",
15
+ "dist": "uniform",
16
+ "seed": 116,
17
+ "scale": 0.1,
18
+ "offset": 0.9
19
+ },
20
+ "sin": { "shape": [4, 128, 32], "dtype": "float32", "dist": "uniform", "seed": 117, "scale": 0.1 }
21
+ },
22
+ "outputs": { "y": { "shape": [4, 128, 64], "dtype": "float32" } },
23
+ "bench": {
24
+ "primary": true,
25
+ "metrics": [
26
+ {
27
+ "type": "bandwidth",
28
+ "value": "args.batch * args.seq * args.headSize * 4 * 2 + args.batch * args.seq * (args.headSize / 2) * 4 * 2"
29
+ }
30
+ ]
31
+ }
32
+ },
33
+ {
34
+ "name": "rotary-f32-b8s8192h128-large",
35
+ "attrs": { "num_heads": 1 },
36
+ "vars": { "batch": 8, "seq": 8192, "headSize": 128 },
37
+ "inputs": {
38
+ "x": { "shape": [8, 8192, 128], "dtype": "float32", "dist": "normal", "seed": 115, "scale": 0.2 },
39
+ "cos": {
40
+ "shape": [8, 8192, 64],
41
+ "dtype": "float32",
42
+ "dist": "uniform",
43
+ "seed": 116,
44
+ "scale": 0.1,
45
+ "offset": 0.9
46
+ },
47
+ "sin": { "shape": [8, 8192, 64], "dtype": "float32", "dist": "uniform", "seed": 117, "scale": 0.1 }
48
+ },
49
+ "outputs": { "y": { "shape": [8, 8192, 128], "dtype": "float32" } },
50
+ "bench": {
51
+ "primary": true,
52
+ "metrics": [
53
+ {
54
+ "type": "bandwidth",
55
+ "value": "args.batch * args.seq * args.headSize * 4 * 2 + args.batch * args.seq * (args.headSize / 2) * 4 * 2"
56
+ }
57
+ ]
58
+ }
59
+ },
60
+ {
61
+ "name": "rotary-f32-rank4-b8h32s256h128-large",
62
+ "vars": { "batch": 8, "heads": 32, "seq": 256, "headSize": 128 },
63
+ "inputs": {
64
+ "x": { "shape": [8, 32, 256, 128], "dtype": "float32", "dist": "normal", "seed": 115, "scale": 0.2 },
65
+ "cos": {
66
+ "shape": [8, 256, 64],
67
+ "dtype": "float32",
68
+ "dist": "uniform",
69
+ "seed": 116,
70
+ "scale": 0.1,
71
+ "offset": 0.9
72
+ },
73
+ "sin": { "shape": [8, 256, 64], "dtype": "float32", "dist": "uniform", "seed": 117, "scale": 0.1 }
74
+ },
75
+ "outputs": { "y": { "shape": [8, 32, 256, 128], "dtype": "float32" } },
76
+ "bench": {
77
+ "primary": true,
78
+ "metrics": [
79
+ {
80
+ "type": "bandwidth",
81
+ "value": "args.batch * args.heads * args.seq * args.headSize * 4 * 2 + args.batch * args.seq * (args.headSize / 2) * 4 * 2"
82
+ }
83
+ ]
84
+ }
85
+ },
86
+ {
87
+ "name": "rotary-f32-decode-q1-rank4-b1h32s1h128-single-row",
88
+ "preset": "smoke",
89
+ "vars": { "batch": 1, "heads": 32, "seq": 1, "headSize": 128 },
90
+ "inputs": {
91
+ "x": { "shape": [1, 32, 1, 128], "dtype": "float32", "dist": "normal", "seed": 115, "scale": 0.2 },
92
+ "cos": { "shape": [1, 1, 64], "dtype": "float32", "dist": "uniform", "seed": 116, "scale": 0.1, "offset": 0.9 },
93
+ "sin": { "shape": [1, 1, 64], "dtype": "float32", "dist": "uniform", "seed": 117, "scale": 0.1 }
94
+ },
95
+ "outputs": { "y": { "shape": [1, 32, 1, 128], "dtype": "float32" } },
96
+ "bench": {
97
+ "primary": true,
98
+ "metrics": [
99
+ {
100
+ "type": "bandwidth",
101
+ "value": "args.batch * args.heads * args.seq * args.headSize * 4 * 2 + args.batch * args.seq * (args.headSize / 2) * 4 * 2"
102
+ }
103
+ ]
104
+ }
105
+ },
106
+ {
107
+ "name": "rotary-f32-partial-rotary-tail-b8s8192h128-read-amplification",
108
+ "preset": "smoke",
109
+ "vars": { "batch": 8, "seq": 8192, "headSize": 128, "rotaryDim": 64 },
110
+ "attrs": { "rotary_embedding_dim": 64, "num_heads": 1 },
111
+ "inputs": {
112
+ "x": { "shape": [8, 8192, 128], "dtype": "float32", "dist": "normal", "seed": 115, "scale": 0.2 },
113
+ "cos": {
114
+ "shape": [8, 8192, 32],
115
+ "dtype": "float32",
116
+ "dist": "uniform",
117
+ "seed": 116,
118
+ "scale": 0.1,
119
+ "offset": 0.9
120
+ },
121
+ "sin": { "shape": [8, 8192, 32], "dtype": "float32", "dist": "uniform", "seed": 117, "scale": 0.1 }
122
+ },
123
+ "outputs": { "y": { "shape": [8, 8192, 128], "dtype": "float32" } },
124
+ "bench": {
125
+ "primary": true,
126
+ "metrics": [
127
+ {
128
+ "type": "bandwidth",
129
+ "value": "args.batch * args.seq * args.headSize * 4 * 2 + args.batch * args.seq * (args.rotaryDim / 2) * 4 * 2"
130
+ }
131
+ ]
132
+ }
133
+ },
134
+ {
135
+ "name": "rotary-f16-b8s8192h128-large",
136
+ "attrs": { "num_heads": 1 },
137
+ "preset": "smoke",
138
+ "vars": { "batch": 8, "seq": 8192, "headSize": 128 },
139
+ "inputs": {
140
+ "x": { "shape": [8, 8192, 128], "dtype": "float16", "dist": "normal", "seed": 115, "scale": 0.2 },
141
+ "cos": {
142
+ "shape": [8, 8192, 64],
143
+ "dtype": "float16",
144
+ "dist": "uniform",
145
+ "seed": 116,
146
+ "scale": 0.1,
147
+ "offset": 0.9
148
+ },
149
+ "sin": { "shape": [8, 8192, 64], "dtype": "float16", "dist": "uniform", "seed": 117, "scale": 0.1 }
150
+ },
151
+ "outputs": { "y": { "shape": [8, 8192, 128], "dtype": "float16" } },
152
+ "bench": {
153
+ "primary": true,
154
+ "metrics": [
155
+ {
156
+ "type": "bandwidth",
157
+ "value": "args.batch * args.seq * args.headSize * 2 * 2 + args.batch * args.seq * (args.headSize / 2) * 2 * 2"
158
+ }
159
+ ]
160
+ }
161
+ },
162
+ {
163
+ "name": "rotary-f32-multihead-b4s1024h2048-nh32-packed-layout",
164
+ "attrs": { "num_heads": 32 },
165
+ "preset": "smoke",
166
+ "vars": { "batch": 4, "seq": 1024, "hidden": 2048, "headSize": 64 },
167
+ "inputs": {
168
+ "x": { "shape": [4, 1024, 2048], "dtype": "float32", "dist": "normal", "seed": 115, "scale": 0.2 },
169
+ "cos": {
170
+ "shape": [4, 1024, 32],
171
+ "dtype": "float32",
172
+ "dist": "uniform",
173
+ "seed": 116,
174
+ "scale": 0.1,
175
+ "offset": 0.9
176
+ },
177
+ "sin": { "shape": [4, 1024, 32], "dtype": "float32", "dist": "uniform", "seed": 117, "scale": 0.1 }
178
+ },
179
+ "outputs": { "y": { "shape": [4, 1024, 2048], "dtype": "float32" } },
180
+ "bench": {
181
+ "primary": true,
182
+ "metrics": [
183
+ {
184
+ "type": "bandwidth",
185
+ "value": "args.batch * args.seq * args.hidden * 4 * 2 + args.batch * args.seq * (args.headSize / 2) * 4 * 2"
186
+ }
187
+ ]
188
+ }
189
+ },
190
+ {
191
+ "name": "rotary-f32-rank3-partial-rd126-tail-copy-full-read-amplification",
192
+ "preset": "stress",
193
+ "vars": { "batch": 8, "seq": 8192, "headSize": 128, "rotaryDim": 126 },
194
+ "attrs": { "rotary_embedding_dim": 126, "num_heads": 1 },
195
+ "inputs": {
196
+ "x": { "shape": [8, 8192, 128], "dtype": "float32", "dist": "normal", "seed": 215, "scale": 0.2 },
197
+ "cos": {
198
+ "shape": [8, 8192, 63],
199
+ "dtype": "float32",
200
+ "dist": "uniform",
201
+ "seed": 216,
202
+ "scale": 0.1,
203
+ "offset": 0.9
204
+ },
205
+ "sin": { "shape": [8, 8192, 63], "dtype": "float32", "dist": "uniform", "seed": 217, "scale": 0.1 }
206
+ },
207
+ "outputs": { "y": { "shape": [8, 8192, 128], "dtype": "float32", "dist": "empty" } },
208
+ "bench": {
209
+ "primary": true,
210
+ "metrics": [
211
+ {
212
+ "type": "bandwidth",
213
+ "value": "args.batch * args.seq * args.headSize * 4 * 2 + args.batch * args.seq * (args.rotaryDim / 2) * 4 * 2"
214
+ }
215
+ ]
216
+ }
217
+ },
218
+ {
219
+ "name": "rotary-f32-rank4-partial-rd126-tail-copy-b4h16s512h128",
220
+ "preset": "stress",
221
+ "vars": { "batch": 4, "heads": 16, "seq": 512, "headSize": 128, "rotaryDim": 126 },
222
+ "attrs": { "rotary_embedding_dim": 126 },
223
+ "inputs": {
224
+ "x": { "shape": [4, 16, 512, 128], "dtype": "float32", "dist": "normal", "seed": 315, "scale": 0.2 },
225
+ "cos": {
226
+ "shape": [4, 512, 63],
227
+ "dtype": "float32",
228
+ "dist": "uniform",
229
+ "seed": 316,
230
+ "scale": 0.1,
231
+ "offset": 0.9
232
+ },
233
+ "sin": { "shape": [4, 512, 63], "dtype": "float32", "dist": "uniform", "seed": 317, "scale": 0.1 }
234
+ },
235
+ "outputs": { "y": { "shape": [4, 16, 512, 128], "dtype": "float32", "dist": "empty" } },
236
+ "bench": {
237
+ "primary": true,
238
+ "metrics": [
239
+ {
240
+ "type": "bandwidth",
241
+ "value": "args.batch * args.heads * args.seq * args.headSize * 4 * 2 + args.batch * args.seq * (args.rotaryDim / 2) * 4 * 2"
242
+ }
243
+ ]
244
+ }
245
+ }
246
+ ]
247
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "RotaryEmbedding",
4
+ "sinceVersion": 23,
5
+ "description": "Implements ONNX opset-23 RotaryEmbedding for float16 and float32 tensors. Applies rotary positional embeddings (RoPE) by rotating each head's embedding vector using precomputed `cos_cache` and `sin_cache` values. A partial rotation can be applied by setting `rotary_embedding_dim` to rotate only a prefix of the head dimension. `position_ids` keeps its standard logical int64 type; valid positions are non-negative and bounded by the WebGPU-addressable cache, so the backend stores them losslessly as uint32. Other ONNX floating-point input types are not yet implemented.",
6
+ "inputs": [
7
+ {
8
+ "role": "X",
9
+ "dtype": "T",
10
+ "description": "Input token embeddings. Shape is `(batch_size, sequence_length, hidden_size)` for rank 3 or `(batch_size, num_heads, sequence_length, head_size)` for rank 4. `head_size` must be even, and the `num_heads` attribute is required for rank-3 input."
11
+ },
12
+ {
13
+ "role": "cos_cache",
14
+ "dtype": "T",
15
+ "description": "Precomputed cosine values. Without `position_ids`, shape is `(batch_size, sequence_length, rotary_dim/2)`; with `position_ids`, shape is `(max_sequence_length, rotary_dim/2)`."
16
+ },
17
+ {
18
+ "role": "sin_cache",
19
+ "dtype": "T",
20
+ "description": "Precomputed sine values with the same shape and type as `cos_cache`."
21
+ },
22
+ {
23
+ "role": "position_ids",
24
+ "dtype": "M",
25
+ "rank": 2,
26
+ "optional": true,
27
+ "description": "Optional logical int64 per-token position indices of shape `(batch_size, sequence_length)`. Valid positions are non-negative cache-row indices and use uint32 WebGPU storage. When supplied, the 2D cache tables are gathered at these positions."
28
+ }
29
+ ],
30
+ "outputs": [
31
+ {
32
+ "role": "Y",
33
+ "dtype": "T",
34
+ "rank": "ranks.X",
35
+ "shape": "shapes.X",
36
+ "description": "Rotary-position-encoded tensor with the same shape and type as `X`."
37
+ }
38
+ ],
39
+ "attributes": { "interleaved": 0, "rotary_embedding_dim": 0 },
40
+ "attributeDescriptions": {
41
+ "interleaved": "Set to 1 to rotate using an interleaved pattern (even/odd elements), or 0 to split the head dimension into two contiguous halves. Default is 0.",
42
+ "num_heads": "Optional number of attention heads. ONNX requires this attribute when `X` is rank 3; it is unnecessary for rank-4 input because the head count is explicit in the shape.",
43
+ "rotary_embedding_dim": "Number of head-dimension elements to rotate; `0` means rotate the full head dimension. When set, only the leading `rotary_embedding_dim` elements are rotated and the rest are passed through unchanged."
44
+ },
45
+ "attributeConstraints": { "interleaved": { "values": [0, 1] } },
46
+ "typeConstraints": { "T": ["float32", "float16"], "M": ["int64"] },
47
+ "args": {
48
+ "x": { "kind": "tensor", "semantic": "X", "role": "input" },
49
+ "cos": { "kind": "tensor", "semantic": "cos_cache", "role": "input" },
50
+ "sin": { "kind": "tensor", "semantic": "sin_cache", "role": "input" },
51
+ "positionIds": {
52
+ "kind": "tensor",
53
+ "semantic": "position_ids",
54
+ "role": "input",
55
+ "dtype": "uint32",
56
+ "narrowing": "checked",
57
+ "required": false
58
+ },
59
+ "y": { "kind": "tensor", "semantic": "Y", "role": "output" }
60
+ },
61
+ "tunables": { "WORKGROUP_SIZE": 256 },
62
+ "derive": {
63
+ "pairDispatchOk": "ceilDiv(numel(shapes.X) / 2, tunables.WORKGROUP_SIZE) <= device.limits.maxComputeWorkgroupsPerDimension * device.limits.maxComputeWorkgroupsPerDimension",
64
+ "rank3HeadSize": "dim(shapes.X, 2) / attrs.num_heads if attrs.num_heads is defined and attrs.num_heads > 0 else 0",
65
+ "rank3Contract": "f16Ok(dtypes.T) and ranks.X == 3 and ranks.cos_cache == 3 and ranks.sin_cache == 3 and ranks.Y == 3 and sameShape(shapes.X, shapes.Y) and attrs.num_heads is defined and attrs.num_heads >= 1 and dim(shapes.X, 2) % attrs.num_heads == 0 and rank3HeadSize % 2 == 0 and (attrs.rotary_embedding_dim == 0 or attrs.rotary_embedding_dim <= rank3HeadSize) and dim(shapes.cos_cache, 2) * 2 == (attrs.rotary_embedding_dim if attrs.rotary_embedding_dim != 0 else rank3HeadSize) and sameShape(shapes.cos_cache, shapes.sin_cache) and dim(shapes.cos_cache, 0) == dim(shapes.X, 0) and dim(shapes.cos_cache, 1) == dim(shapes.X, 1) and not present.positionIds",
66
+ "rank4Contract": "f16Ok(dtypes.T) and ranks.X == 4 and ranks.cos_cache == 3 and ranks.sin_cache == 3 and ranks.Y == 4 and dim(shapes.X, 3) % 2 == 0 and (attrs.rotary_embedding_dim == 0 or attrs.rotary_embedding_dim <= dim(shapes.X, 3)) and dim(shapes.cos_cache, 2) * 2 == (attrs.rotary_embedding_dim if attrs.rotary_embedding_dim != 0 else dim(shapes.X, 3)) and dim(shapes.sin_cache, 2) == dim(shapes.cos_cache, 2) and sameShape(shapes.X, shapes.Y) and dim(shapes.cos_cache, 0) == dim(shapes.X, 0) and dim(shapes.sin_cache, 0) == dim(shapes.X, 0) and dim(shapes.cos_cache, 1) == dim(shapes.X, 2) and dim(shapes.sin_cache, 1) == dim(shapes.X, 2) and not present.positionIds",
67
+ "rank3PosContract": "f16Ok(dtypes.T) and ranks.X == 3 and ranks.cos_cache == 2 and ranks.sin_cache == 2 and ranks.Y == 3 and sameShape(shapes.X, shapes.Y) and attrs.num_heads is defined and attrs.num_heads >= 1 and dim(shapes.X, 2) % attrs.num_heads == 0 and rank3HeadSize % 2 == 0 and (attrs.rotary_embedding_dim == 0 or attrs.rotary_embedding_dim <= rank3HeadSize) and dim(shapes.cos_cache, 1) * 2 == (attrs.rotary_embedding_dim if attrs.rotary_embedding_dim != 0 else rank3HeadSize) and sameShape(shapes.cos_cache, shapes.sin_cache) and present.positionIds and ranks.position_ids == 2 and dim(shapes.position_ids, 0) == dim(shapes.X, 0) and dim(shapes.position_ids, 1) == dim(shapes.X, 1)",
68
+ "rank4PosContract": "f16Ok(dtypes.T) and ranks.X == 4 and ranks.cos_cache == 2 and ranks.sin_cache == 2 and ranks.Y == 4 and dim(shapes.X, 3) % 2 == 0 and (attrs.rotary_embedding_dim == 0 or attrs.rotary_embedding_dim <= dim(shapes.X, 3)) and dim(shapes.cos_cache, 1) * 2 == (attrs.rotary_embedding_dim if attrs.rotary_embedding_dim != 0 else dim(shapes.X, 3)) and sameShape(shapes.cos_cache, shapes.sin_cache) and sameShape(shapes.X, shapes.Y) and present.positionIds and ranks.position_ids == 2 and dim(shapes.position_ids, 0) == dim(shapes.X, 0) and dim(shapes.position_ids, 1) == dim(shapes.X, 2)"
69
+ },
70
+ "bindingSets": {
71
+ "rank3": [
72
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
73
+ {
74
+ "name": "cos_cache",
75
+ "arg": "cos",
76
+ "semantic": "cos_cache",
77
+ "buffer": { "type": "read-only-storage" },
78
+ "elementType": "$T"
79
+ },
80
+ {
81
+ "name": "sin_cache",
82
+ "arg": "sin",
83
+ "semantic": "sin_cache",
84
+ "buffer": { "type": "read-only-storage" },
85
+ "elementType": "$T"
86
+ },
87
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
88
+ {
89
+ "name": "params",
90
+ "semantic": "kernel.params",
91
+ "buffer": { "type": "uniform" },
92
+ "struct": {
93
+ "name": "Params",
94
+ "fields": [
95
+ { "name": "pairCount", "type": "u32", "value": "numel(shapes.X) / 2" },
96
+ { "name": "sequenceLength", "type": "u32", "value": "dim(shapes.X, 1)" },
97
+ { "name": "numHeads", "type": "u32", "value": "attrs.num_heads" },
98
+ { "name": "headSize", "type": "u32", "value": "rank3HeadSize" },
99
+ { "name": "rotaryDim", "type": "u32", "value": "dim(shapes.cos_cache, 2) * 2" },
100
+ { "name": "halfRotaryDim", "type": "u32", "value": "dim(shapes.cos_cache, 2)" },
101
+ {
102
+ "name": "cacheBatchStride",
103
+ "type": "u32",
104
+ "value": "dim(shapes.cos_cache, 1) * dim(shapes.cos_cache, 2)"
105
+ }
106
+ ]
107
+ }
108
+ }
109
+ ],
110
+ "rank3Pos": [
111
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
112
+ {
113
+ "name": "cos_cache",
114
+ "arg": "cos",
115
+ "semantic": "cos_cache",
116
+ "buffer": { "type": "read-only-storage" },
117
+ "elementType": "$T"
118
+ },
119
+ {
120
+ "name": "sin_cache",
121
+ "arg": "sin",
122
+ "semantic": "sin_cache",
123
+ "buffer": { "type": "read-only-storage" },
124
+ "elementType": "$T"
125
+ },
126
+ {
127
+ "name": "position_ids",
128
+ "arg": "positionIds",
129
+ "semantic": "position_ids",
130
+ "buffer": { "type": "read-only-storage" },
131
+ "elementType": "$M"
132
+ },
133
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
134
+ {
135
+ "name": "params",
136
+ "semantic": "kernel.params",
137
+ "buffer": { "type": "uniform" },
138
+ "struct": {
139
+ "name": "Params",
140
+ "fields": [
141
+ { "name": "pairCount", "type": "u32", "value": "numel(shapes.X) / 2" },
142
+ { "name": "sequenceLength", "type": "u32", "value": "dim(shapes.X, 1)" },
143
+ { "name": "numHeads", "type": "u32", "value": "attrs.num_heads" },
144
+ { "name": "headSize", "type": "u32", "value": "rank3HeadSize" },
145
+ { "name": "rotaryDim", "type": "u32", "value": "dim(shapes.cos_cache, 1) * 2" },
146
+ { "name": "halfRotaryDim", "type": "u32", "value": "dim(shapes.cos_cache, 1)" }
147
+ ]
148
+ }
149
+ }
150
+ ],
151
+ "rank4Pos": [
152
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
153
+ {
154
+ "name": "cos_cache",
155
+ "arg": "cos",
156
+ "semantic": "cos_cache",
157
+ "buffer": { "type": "read-only-storage" },
158
+ "elementType": "$T"
159
+ },
160
+ {
161
+ "name": "sin_cache",
162
+ "arg": "sin",
163
+ "semantic": "sin_cache",
164
+ "buffer": { "type": "read-only-storage" },
165
+ "elementType": "$T"
166
+ },
167
+ {
168
+ "name": "position_ids",
169
+ "arg": "positionIds",
170
+ "semantic": "position_ids",
171
+ "buffer": { "type": "read-only-storage" },
172
+ "elementType": "$M"
173
+ },
174
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
175
+ {
176
+ "name": "params",
177
+ "semantic": "kernel.params",
178
+ "buffer": { "type": "uniform" },
179
+ "struct": {
180
+ "name": "Params",
181
+ "fields": [
182
+ { "name": "pairCount", "type": "u32", "value": "numel(shapes.X) / 2" },
183
+ { "name": "numHeads", "type": "u32", "value": "dim(shapes.X, 1)" },
184
+ { "name": "sequenceLength", "type": "u32", "value": "dim(shapes.X, 2)" },
185
+ { "name": "headSize", "type": "u32", "value": "dim(shapes.X, 3)" },
186
+ { "name": "rotaryDim", "type": "u32", "value": "dim(shapes.cos_cache, 1) * 2" },
187
+ { "name": "halfRotaryDim", "type": "u32", "value": "dim(shapes.cos_cache, 1)" }
188
+ ]
189
+ }
190
+ }
191
+ ],
192
+ "rank4": [
193
+ { "name": "x", "arg": "x", "semantic": "X", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
194
+ {
195
+ "name": "cos_cache",
196
+ "arg": "cos",
197
+ "semantic": "cos_cache",
198
+ "buffer": { "type": "read-only-storage" },
199
+ "elementType": "$T"
200
+ },
201
+ {
202
+ "name": "sin_cache",
203
+ "arg": "sin",
204
+ "semantic": "sin_cache",
205
+ "buffer": { "type": "read-only-storage" },
206
+ "elementType": "$T"
207
+ },
208
+ { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$T" },
209
+ {
210
+ "name": "params",
211
+ "semantic": "kernel.params",
212
+ "buffer": { "type": "uniform" },
213
+ "struct": {
214
+ "name": "Params",
215
+ "fields": [
216
+ { "name": "pairCount", "type": "u32", "value": "numel(shapes.X) / 2" },
217
+ { "name": "numHeads", "type": "u32", "value": "dim(shapes.X, 1)" },
218
+ { "name": "sequenceLength", "type": "u32", "value": "dim(shapes.X, 2)" },
219
+ { "name": "headSize", "type": "u32", "value": "dim(shapes.X, 3)" },
220
+ { "name": "rotaryDim", "type": "u32", "value": "dim(shapes.cos_cache, 2) * 2" },
221
+ { "name": "halfRotaryDim", "type": "u32", "value": "dim(shapes.cos_cache, 2)" },
222
+ {
223
+ "name": "cacheBatchStride",
224
+ "type": "u32",
225
+ "value": "dim(shapes.cos_cache, 1) * dim(shapes.cos_cache, 2)"
226
+ }
227
+ ]
228
+ }
229
+ }
230
+ ]
231
+ },
232
+ "variants": [
233
+ {
234
+ "id": "rank3_cache2_pos",
235
+ "when": ["rank3PosContract", "pairDispatchOk"],
236
+ "constants": { "interleaved": "attrs.interleaved != 0", "usesF16": "dtypes.T == \"f16\"", "scalar": "dtypes.T" },
237
+ "passes": [
238
+ {
239
+ "id": "main",
240
+ "name": "rotary_embedding3d_pos",
241
+ "source": { "shader": "rotary-embedding.wgsl.jinja", "inputs": { "rank": 3, "hasPositionIds": "true" } },
242
+ "bindings": "rank3Pos",
243
+ "dispatch": { "threads": "numel(shapes.X) / 2", "workgroupSize": "tunables.WORKGROUP_SIZE" }
244
+ }
245
+ ]
246
+ },
247
+ {
248
+ "id": "rank4_cache2_pos",
249
+ "when": ["rank4PosContract", "pairDispatchOk"],
250
+ "constants": { "interleaved": "attrs.interleaved != 0", "usesF16": "dtypes.T == \"f16\"", "scalar": "dtypes.T" },
251
+ "passes": [
252
+ {
253
+ "id": "main",
254
+ "name": "rotary_embedding4d_pos",
255
+ "source": { "shader": "rotary-embedding.wgsl.jinja", "inputs": { "rank": 4, "hasPositionIds": "true" } },
256
+ "bindings": "rank4Pos",
257
+ "dispatch": { "threads": "numel(shapes.X) / 2", "workgroupSize": "tunables.WORKGROUP_SIZE" }
258
+ }
259
+ ]
260
+ },
261
+ {
262
+ "id": "rank3_cache3",
263
+ "when": ["rank3Contract", "pairDispatchOk"],
264
+ "constants": { "interleaved": "attrs.interleaved != 0", "usesF16": "dtypes.T == \"f16\"", "scalar": "dtypes.T" },
265
+ "passes": [
266
+ {
267
+ "id": "main",
268
+ "name": "rotary_embedding3d",
269
+ "source": { "shader": "rotary-embedding.wgsl.jinja", "inputs": { "rank": 3 } },
270
+ "bindings": "rank3",
271
+ "dispatch": { "threads": "numel(shapes.X) / 2", "workgroupSize": "tunables.WORKGROUP_SIZE" }
272
+ }
273
+ ]
274
+ },
275
+ {
276
+ "id": "rank4_cache3",
277
+ "when": ["rank4Contract", "pairDispatchOk"],
278
+ "constants": { "interleaved": "attrs.interleaved != 0", "usesF16": "dtypes.T == \"f16\"", "scalar": "dtypes.T" },
279
+ "passes": [
280
+ {
281
+ "id": "main",
282
+ "name": "rotary_embedding4d",
283
+ "source": { "shader": "rotary-embedding.wgsl.jinja", "inputs": { "rank": 4 } },
284
+ "bindings": "rank4",
285
+ "dispatch": { "threads": "numel(shapes.X) / 2", "workgroupSize": "tunables.WORKGROUP_SIZE" }
286
+ }
287
+ ]
288
+ }
289
+ ]
290
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.RotaryEmbedding",
3
+ "id": "_ai_onnx_rotaryembedding_webgpu_23c1d92",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "Uu/zPgVC3ubArN3FjvwWkoSP9bbEqLHRRppel4wsIeA=",
11
+ "manifest.json": "yfsgfGknbiGBH5nzsmJmqJWPq1CskrxPgTz0vc+Qe/A=",
12
+ "rotary-embedding.wgsl.jinja": "dkJWgoDB2IWxw7espG+q4D1MX04J2ofR9xHvntuxg9w=",
13
+ "test.json": "p4hC6UXkzEdV+pQlTtZ0t2LcfXq3psczZWXmN0fRGes="
14
+ }
15
+ },
16
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
17
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.RotaryEmbedding" }
18
+ }
build/webgpu/rotary-embedding.wgsl.jinja ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ {% if usesF16 %}enable f16;
35
+ {% endif %}{{ env.wgsl.resourceDeclarations }}
36
+ // One invocation owns both outputs of a rotated pair, so x/cos/sin are read once.
37
+ // Partial rotations use the remaining pair lanes to copy two unchanged tail values.
38
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
39
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
40
+ {{ flat_index_2d("p", "params.pairCount", note="") }}
41
+
42
+ let tasksPerHead = params.headSize / 2u;
43
+ let task = p % tasksPerHead;
44
+ let headFlat = p / tasksPerHead;
45
+ let base = headFlat * params.headSize;
46
+
47
+ if (task >= params.halfRotaryDim) {
48
+ let tail = params.rotaryDim + (task - params.halfRotaryDim) * 2u;
49
+ y[base + tail] = x[base + tail];
50
+ y[base + tail + 1u] = x[base + tail + 1u];
51
+ return;
52
+ }
53
+
54
+ let pair = task;
55
+ {% if source.rank == 3 %}
56
+ let token = (headFlat / params.numHeads) % params.sequenceLength;
57
+ let batch = headFlat / (params.numHeads * params.sequenceLength);
58
+ {% else %}
59
+ let token = headFlat % params.sequenceLength;
60
+ let batch = (headFlat / params.sequenceLength) / params.numHeads;
61
+ {% endif %}
62
+ {% if source.hasPositionIds is defined and source.hasPositionIds %}
63
+ // Gather the 2D cache tables through this token's position index.
64
+ let cache = u32(position_ids[batch * params.sequenceLength + token]) * params.halfRotaryDim + pair;
65
+ {% else %}
66
+ // The standard no-index cache owns one row per (batch, token).
67
+ let cache = batch * params.cacheBatchStride + token * params.halfRotaryDim + pair;
68
+ {% endif %}
69
+ let cf = f32(cos_cache[cache]);
70
+ let sf = f32(sin_cache[cache]);
71
+ {% if interleaved %}
72
+ let aOffset = pair * 2u;
73
+ let bOffset = aOffset + 1u;
74
+ {% else %}
75
+ let aOffset = pair;
76
+ let bOffset = pair + params.halfRotaryDim;
77
+ {% endif %}
78
+ let a = f32(x[base + aOffset]);
79
+ let b = f32(x[base + bOffset]);
80
+ y[base + aOffset] = {{ scalar }}(a * cf - b * sf);
81
+ y[base + bOffset] = {{ scalar }}(a * sf + b * cf);
82
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,830 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.RotaryEmbedding",
3
+ "fixtureArrays": {
4
+ "position_id_cos_table": [1, 0.994, 0.9553, 0.9171, 0.8253, 0.7584, 0.6216, 0.5319, 0.3624, 0.2579, 0.0707, -0.0392, -0.2272, -0.3327, -0.5048, -0.5966, -0.7374, -0.8071, -0.9041, -0.9455, -0.99, -0.9995, -0.9875, -0.9642, -0.8968, -0.8428, -0.7259, -0.646, -0.4903, -0.3916, -0.2108, -0.1022],
5
+ "position_id_sin_table": [0, 0.1692, 0.2955, 0.4529, 0.5646, 0.6961, 0.7833, 0.8772, 0.932, 0.9799, 0.9975, 0.9951, 0.9738, 0.9214, 0.8632, 0.7654, 0.6755, 0.541, 0.4274, 0.2683, 0.1411, -0.0284, -0.1577, -0.3225, -0.4425, -0.5879, -0.6878, -0.8007, -0.8716, -0.942, -0.9775, -0.9991],
6
+ "pos_ids_rank3_pair_gather_input_x": [0, 0.6442, 0.9854, 0.8632, 0.335, -0.3508, -0.8716, -0.9825, -0.6313, 0.0168, 0.657, 0.9882, 0.8546, 0.3191, -0.3665, -0.8797, -0.9792, -0.6181, 0.0336, 0.6696, 0.9906, 0.8457, 0.3031, -0.3821, -0.8876, -0.9756, -0.6048, 0.0504, 0.682, 0.9928, 0.8367, 0.2871, -0.3976, -0.8952, -0.9718, -0.5914, 0.0672, 0.6942, 0.9946, 0.8273, 0.2709, -0.4129, -0.9026, -0.9677, -0.5777, 0.084, 0.7062, 0.9962]
7
+ },
8
+ "cases": [
9
+ {
10
+ "name": "f32_identity_angle_preserves_subnormal_pair_gpu_gap",
11
+ "attrs": { "num_heads": 1 },
12
+ "skipGpu": {
13
+ "category": "permanent",
14
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: Metal flushes denormals to zero in the ALU; this op's f32 multiply/FMA flushes the subnormal operand on GPU even at identity angle. Permanent FTZ limitation."
15
+ },
16
+ "provenance": {
17
+ "source": "onnxruntime/test/providers/cpu/llm/rotary_embedding_op_test.cc",
18
+ "test": "RotaryEmbedding",
19
+ "notes": "With cos=1 and sin=0, rotary embedding is an identity transform; positive and negative subnormal activations should survive."
20
+ },
21
+ "inputs": {
22
+ "x": { "dtype": "float32", "shape": [1, 1, 2], "data": { "kind": "values", "values": [1e-40, -1e-40] } },
23
+ "cos": { "dtype": "float32", "shape": [1, 1, 1], "data": { "kind": "values", "values": [1.0] } },
24
+ "sin": { "dtype": "float32", "shape": [1, 1, 1], "data": { "kind": "values", "values": [0.0] } }
25
+ },
26
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 2], "tolerance": 0 } }
27
+ },
28
+ {
29
+ "name": "f32_identity_angle_preserves_subnormal_rank4_pair_gpu_gap",
30
+ "skipGpu": {
31
+ "category": "permanent",
32
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal values required by this fixture. Backend evidence: Metal flushes denormals to zero in the ALU; this op's f32 multiply/FMA flushes the subnormal operand on GPU even at identity angle. Permanent FTZ limitation."
33
+ },
34
+ "provenance": {
35
+ "source": "onnxruntime/test/providers/cpu/llm/rotary_embedding_op_test.cc",
36
+ "test": "RotaryEmbedding",
37
+ "notes": "Rank-4 companion for identity-angle rotary embedding preserving subnormal activations."
38
+ },
39
+ "inputs": {
40
+ "x": { "dtype": "float32", "shape": [1, 1, 1, 2], "data": { "kind": "values", "values": [1e-40, -1e-40] } },
41
+ "cos": { "dtype": "float32", "shape": [1, 1, 1], "data": { "kind": "values", "values": [1.0] } },
42
+ "sin": { "dtype": "float32", "shape": [1, 1, 1], "data": { "kind": "values", "values": [0.0] } }
43
+ },
44
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 1, 2], "tolerance": 0 } }
45
+ },
46
+ {
47
+ "name": "dispatch_cliff_rank3_partial_rotary",
48
+ "attrs": { "rotary_embedding_dim": 2, "num_heads": 1 },
49
+ "inputs": {
50
+ "x": {
51
+ "dtype": "float32",
52
+ "shape": [4194241, 1, 4],
53
+ "data": { "kind": "fillFloat32", "sinStep": 0.13, "cosStep": 0.29 }
54
+ },
55
+ "cos": {
56
+ "dtype": "float32",
57
+ "shape": [4194241, 1, 1],
58
+ "data": { "kind": "rotaryCos", "thetaStart": 0.37, "thetaStep": 0.2 }
59
+ },
60
+ "sin": {
61
+ "dtype": "float32",
62
+ "shape": [4194241, 1, 1],
63
+ "data": { "kind": "rotarySin", "thetaStart": 0.37, "thetaStep": 0.2 }
64
+ }
65
+ },
66
+ "outputs": { "y": { "dtype": "float32", "shape": [4194241, 1, 4], "tolerance": 0.0001 } }
67
+ },
68
+ {
69
+ "name": "batch2_seq3_head4",
70
+ "attrs": { "num_heads": 1 },
71
+ "inputs": {
72
+ "x": {
73
+ "dtype": "float32",
74
+ "shape": [2, 3, 4],
75
+ "data": { "kind": "fillFloat32", "sinStep": 0.17, "cosStep": 0.31 }
76
+ },
77
+ "cos": {
78
+ "dtype": "float32",
79
+ "shape": [2, 3, 2],
80
+ "data": { "kind": "rotaryCos", "thetaStart": 0.1, "thetaStep": 0.2 }
81
+ },
82
+ "sin": {
83
+ "dtype": "float32",
84
+ "shape": [2, 3, 2],
85
+ "data": { "kind": "rotarySin", "thetaStart": 0.1, "thetaStep": 0.2 }
86
+ }
87
+ },
88
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 4], "tolerance": 0.000001 } }
89
+ },
90
+ {
91
+ "name": "seq1_head2_identity_angle_zero",
92
+ "attrs": { "num_heads": 1 },
93
+ "inputs": {
94
+ "x": { "dtype": "float32", "shape": [1, 1, 2], "data": { "kind": "values", "values": [3.0, -4.0] } },
95
+ "cos": { "dtype": "float32", "shape": [1, 1, 1], "data": { "kind": "values", "values": [1.0] } },
96
+ "sin": { "dtype": "float32", "shape": [1, 1, 1], "data": { "kind": "values", "values": [0.0] } }
97
+ },
98
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 2], "tolerance": 0.000001 } }
99
+ },
100
+ {
101
+ "name": "batch1_seq2_head8_distinct_pairs",
102
+ "attrs": { "num_heads": 1 },
103
+ "inputs": {
104
+ "x": {
105
+ "dtype": "float32",
106
+ "shape": [1, 2, 8],
107
+ "data": {
108
+ "kind": "values",
109
+ "values": [1.0, 2.0, 3.0, 4.0, -1.0, -2.0, -3.0, -4.0, 5.0, 6.0, 7.0, 8.0, -5.0, -6.0, -7.0, -8.0]
110
+ }
111
+ },
112
+ "cos": {
113
+ "dtype": "float32",
114
+ "shape": [1, 2, 4],
115
+ "data": { "kind": "values", "values": [1.0, 0.0, 0.5, -0.5, 0.0, 1.0, -0.5, 0.5] }
116
+ },
117
+ "sin": {
118
+ "dtype": "float32",
119
+ "shape": [1, 2, 4],
120
+ "data": { "kind": "values", "values": [0.0, 1.0, 0.5, 0.5, 1.0, 0.0, 0.5, -0.5] }
121
+ }
122
+ },
123
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 8], "tolerance": 0.000001 } }
124
+ },
125
+ {
126
+ "name": "backend_style_batch2_seq3_head8",
127
+ "attrs": { "num_heads": 1 },
128
+ "provenance": {
129
+ "source": "cmake/external/onnx/onnx/backend/test/case/node/rotaryembedding.py",
130
+ "notes": "Standard rank-3, no-position-ids case with per-batch 3D cosine and sine caches."
131
+ },
132
+ "inputs": {
133
+ "x": {
134
+ "dtype": "float32",
135
+ "shape": [2, 3, 8],
136
+ "data": { "kind": "fillFloat32", "scale": 0.6, "sinStep": 0.17, "cosStep": 0.31 }
137
+ },
138
+ "cos": {
139
+ "dtype": "float32",
140
+ "shape": [2, 3, 4],
141
+ "data": { "kind": "rotaryCos", "thetaStart": 0.07, "thetaStep": 0.11 }
142
+ },
143
+ "sin": {
144
+ "dtype": "float32",
145
+ "shape": [2, 3, 4],
146
+ "data": { "kind": "rotarySin", "thetaStart": 0.07, "thetaStep": 0.11 }
147
+ }
148
+ },
149
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 8], "tolerance": 0.000001 } }
150
+ },
151
+ {
152
+ "name": "ort_not_interleaved_no_position_ids_multi_head",
153
+ "attrs": { "num_heads": 3 },
154
+ "provenance": {
155
+ "source": "onnxruntime/test/providers/cpu/llm/rotary_embedding_op_test.cc",
156
+ "test": "RotaryEmbeddingTest.RotaryEmbedding_NotInterleaved_NoPosIds_SmallData_LlamaMSFT",
157
+ "notes": "Preserves ORT's standard batch-1 rank-3 cos/sin cache shape."
158
+ },
159
+ "inputs": {
160
+ "x": {
161
+ "dtype": "float32",
162
+ "shape": [1, 2, 18],
163
+ "data": {
164
+ "kind": "values",
165
+ "values": [-1.0408, 0.9166, -1.3042, -1.1097, -1.2188, 1.1676, 1.0076, -0.7529, -0.225, -0.4327, -1.5071, -0.4586, -0.8663, -0.2656, 0.1665, 0.7911, -0.932, -0.8579, -1.0574, -0.1188, -0.9078, 0.3452, -0.5713, -0.2351, -0.848, 0.5266, -1.2944, -0.0243, -0.2354, -0.7087, -0.9647, -0.0991, -0.2994, -0.065, -1.572, -1.3211]
166
+ }
167
+ },
168
+ "cos": {
169
+ "dtype": "float32",
170
+ "shape": [1, 2, 3],
171
+ "data": { "kind": "values", "values": [1.0, 1.0, 1.0, 0.5403, 0.9989, 1.0] }
172
+ },
173
+ "sin": {
174
+ "dtype": "float32",
175
+ "shape": [1, 2, 3],
176
+ "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.8415, 0.0464, 0.0022] }
177
+ }
178
+ },
179
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 18], "tolerance": 0.00001 } }
180
+ },
181
+ {
182
+ "name": "non_unit_cache_values_are_used_directly",
183
+ "attrs": { "num_heads": 1 },
184
+ "provenance": {
185
+ "source": "onnxruntime/test/providers/cpu/llm/rotary_embedding_op_test.cc",
186
+ "notes": "The operator consumes supplied cos/sin cache values directly; they need not form unit rotations."
187
+ },
188
+ "inputs": {
189
+ "x": {
190
+ "dtype": "float32",
191
+ "shape": [1, 3, 4],
192
+ "data": {
193
+ "kind": "values",
194
+ "values": [2.0, -3.0, 5.0, -7.0, -11.0, 13.0, -17.0, 19.0, 23.0, -29.0, 31.0, -37.0]
195
+ }
196
+ },
197
+ "cos": {
198
+ "dtype": "float32",
199
+ "shape": [1, 3, 2],
200
+ "data": { "kind": "values", "values": [1.25, -0.5, 0.75, 0.125, -1.5, 2.0] }
201
+ },
202
+ "sin": {
203
+ "dtype": "float32",
204
+ "shape": [1, 3, 2],
205
+ "data": { "kind": "values", "values": [-0.25, 0.5, 1.5, -0.75, 0.25, -1.0] }
206
+ }
207
+ },
208
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 3, 4], "tolerance": 0.000001 } }
209
+ },
210
+ {
211
+ "name": "head12_non_power_of_two",
212
+ "attrs": { "num_heads": 1 },
213
+ "provenance": {
214
+ "source": "onnxruntime/test/providers/cpu/llm/rotary_embedding_op_test.cc",
215
+ "notes": "Covers an even head size that is not a power of two."
216
+ },
217
+ "inputs": {
218
+ "x": {
219
+ "dtype": "float32",
220
+ "shape": [1, 2, 12],
221
+ "data": { "kind": "fillFloat32", "scale": 0.4, "sinStep": 0.23, "cosStep": 0.19 }
222
+ },
223
+ "cos": {
224
+ "dtype": "float32",
225
+ "shape": [1, 2, 6],
226
+ "data": { "kind": "rotaryCos", "thetaStart": 0.03, "thetaStep": 0.17 }
227
+ },
228
+ "sin": {
229
+ "dtype": "float32",
230
+ "shape": [1, 2, 6],
231
+ "data": { "kind": "rotarySin", "thetaStart": 0.03, "thetaStep": 0.17 }
232
+ }
233
+ },
234
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 12], "tolerance": 0.000001 } }
235
+ },
236
+ {
237
+ "name": "ort_interleaved_no_position_ids_multi_head",
238
+ "provenance": {
239
+ "source": "onnxruntime/test/providers/cpu/llm/rotary_embedding_op_test.cc",
240
+ "test": "RotaryEmbeddingTest.RotaryEmbedding_Interleaved_NoPosIds_SmallData_LlamaMSFT"
241
+ },
242
+ "attrs": { "interleaved": 1, "num_heads": 2 },
243
+ "inputs": {
244
+ "x": {
245
+ "dtype": "float32",
246
+ "shape": [1, 3, 8],
247
+ "data": {
248
+ "kind": "values",
249
+ "values": [-1.0408, 0.9166, -1.3042, -1.1097, -0.132, -0.2751, -0.235, 0.0937, -1.2188, 1.1676, -1.0574, -0.1188, -0.7396, -1.2425, -0.1752, 0.699, -0.811, 0.6737, -1.1233, -0.0919, -0.6861, 0.7202, 0.1963, 0.6142]
250
+ }
251
+ },
252
+ "cos": {
253
+ "dtype": "float32",
254
+ "shape": [1, 3, 2],
255
+ "data": { "kind": "values", "values": [1.0, 1.0, 0.5403, 0.9999, -0.4161, 0.9998] }
256
+ },
257
+ "sin": {
258
+ "dtype": "float32",
259
+ "shape": [1, 3, 2],
260
+ "data": { "kind": "values", "values": [0.0, 0.0, 0.8415, 0.01, 0.9093, 0.02] }
261
+ }
262
+ },
263
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 3, 8], "tolerance": 0.0001 } }
264
+ },
265
+ {
266
+ "name": "ort_interleaved_rank4_small_data",
267
+ "provenance": {
268
+ "source": "onnxruntime/test/providers/cpu/llm/rotary_embedding_op_test.cc",
269
+ "test": "RotaryEmbeddingTest.RotaryEmbedding_Interleaved_SmallData_LlamaMSFT_4D_Input",
270
+ "notes": "ORT uses sequential position_ids [0, 1, 2], represented directly by the rank-4 cache rows."
271
+ },
272
+ "attrs": { "interleaved": 1 },
273
+ "inputs": {
274
+ "x": {
275
+ "dtype": "float32",
276
+ "shape": [1, 2, 3, 4],
277
+ "data": {
278
+ "kind": "values",
279
+ "values": [-1.0408, 0.9166, -1.3042, -1.1097, -1.2188, 1.1676, -1.0574, -0.1188, -0.811, 0.6737, -1.1233, -0.0919, -0.132, -0.2751, -0.235, 0.0937, -0.7396, -1.2425, -0.1752, 0.699, -0.6861, 0.7202, 0.1963, 0.6142]
280
+ }
281
+ },
282
+ "cos": {
283
+ "dtype": "float32",
284
+ "shape": [1, 3, 2],
285
+ "data": { "kind": "values", "values": [1.0, 1.0, 0.5403, 0.9999, -0.4161, 0.9998] }
286
+ },
287
+ "sin": {
288
+ "dtype": "float32",
289
+ "shape": [1, 3, 2],
290
+ "data": { "kind": "values", "values": [0.0, 0.0, 0.8415, 0.01, 0.9093, 0.02] }
291
+ }
292
+ },
293
+ "outputs": {
294
+ "y": {
295
+ "dtype": "float32",
296
+ "shape": [1, 2, 3, 4],
297
+ "tolerance": 0.0001,
298
+ "data": {
299
+ "kind": "values",
300
+ "values": [-1.0408, 0.9166, -1.3042, -1.1097, -1.6411, -0.3948, -1.0561, -0.1294, -0.2751, -1.0178, -1.1212, -0.1143, -0.132, -0.2751, -0.235, 0.0937, 0.646, -1.2937, -0.1822, 0.6972, -0.3694, -0.9235, 0.184, 0.618]
301
+ }
302
+ }
303
+ }
304
+ },
305
+ {
306
+ "name": "ort_partial_rotary_dim_preserves_tail",
307
+ "provenance": {
308
+ "source": "onnxruntime/test/providers/cpu/llm/rotary_embedding_op_test.cc",
309
+ "test": "RotaryEmbeddingTest.RotaryEmbedding_CustomRotaryDim_SmallData_Phi"
310
+ },
311
+ "attrs": { "rotary_embedding_dim": 4, "num_heads": 1 },
312
+ "inputs": {
313
+ "x": {
314
+ "dtype": "float32",
315
+ "shape": [1, 2, 6],
316
+ "data": {
317
+ "kind": "values",
318
+ "values": [-1.0408, 0.9166, -1.3042, -1.1097, -1.2188, 1.1676, 1.0076, -0.7529, -0.225, -0.4327, -1.5071, -0.4586]
319
+ }
320
+ },
321
+ "cos": {
322
+ "dtype": "float32",
323
+ "shape": [1, 2, 2],
324
+ "data": { "kind": "values", "values": [1.0, 1.0, 1.0, 0.5403] }
325
+ },
326
+ "sin": {
327
+ "dtype": "float32",
328
+ "shape": [1, 2, 2],
329
+ "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.8415] }
330
+ }
331
+ },
332
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 6], "tolerance": 0.0001 } }
333
+ },
334
+ {
335
+ "name": "ort_partial_rotary_dim_preserves_tail_f16",
336
+ "provenance": {
337
+ "source": "onnxruntime/test/providers/cpu/llm/rotary_embedding_op_test.cc",
338
+ "test": "RotaryEmbeddingTest.RotaryEmbedding_CustomRotaryDim_SmallData_Phi",
339
+ "notes": "Same Phi-style partial rotary-dimension case as ORT, exercising the fp16 path and confirming tail values stay unrotated."
340
+ },
341
+ "attrs": { "rotary_embedding_dim": 4, "num_heads": 1 },
342
+ "inputs": {
343
+ "x": {
344
+ "dtype": "float16",
345
+ "shape": [1, 2, 6],
346
+ "data": {
347
+ "kind": "values",
348
+ "values": [-1.0408, 0.9166, -1.3042, -1.1097, -1.2188, 1.1676, 1.0076, -0.7529, -0.225, -0.4327, -1.5071, -0.4586]
349
+ }
350
+ },
351
+ "cos": {
352
+ "dtype": "float16",
353
+ "shape": [1, 2, 2],
354
+ "data": { "kind": "values", "values": [1.0, 1.0, 1.0, 0.5403] }
355
+ },
356
+ "sin": {
357
+ "dtype": "float16",
358
+ "shape": [1, 2, 2],
359
+ "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.8415] }
360
+ }
361
+ },
362
+ "outputs": {
363
+ "y": {
364
+ "dtype": "float16",
365
+ "shape": [1, 2, 6],
366
+ "tolerance": 0.002,
367
+ "data": {
368
+ "kind": "values",
369
+ "values": [-1.0408, 0.9166, -1.3042, -1.1097, -1.2188, 1.1676, 1.0076, -0.0427, -0.225, -0.8673, -1.5071, -0.4586]
370
+ }
371
+ }
372
+ }
373
+ },
374
+ {
375
+ "name": "ort_contrib_packed_batching_repeated_cache_rows",
376
+ "provenance": {
377
+ "source": "onnxruntime/test/contrib_ops/rotary_embedding_op_test.cc",
378
+ "test": "ContribOpRotaryEmbeddingTest.RotaryEmbedding_CustomRotaryDim_SmallData_Phi_Packed_Batching",
379
+ "notes": "Materializes the indexed ORT case's gathered rows [0, 0, 1] into the standard per-batch rank-3 cache used when position_ids is omitted."
380
+ },
381
+ "attrs": { "rotary_embedding_dim": 4, "num_heads": 1 },
382
+ "inputs": {
383
+ "x": {
384
+ "dtype": "float32",
385
+ "shape": [1, 3, 6],
386
+ "data": {
387
+ "kind": "values",
388
+ "values": [-1.0408, 0.9166, -1.3042, -1.1097, -1.2188, 1.1676, -1.0408, 0.9166, -1.3042, -1.1097, -1.2188, 1.1676, 1.0076, -0.7529, -0.225, -0.4327, -1.5071, -0.4586]
389
+ }
390
+ },
391
+ "cos": {
392
+ "dtype": "float32",
393
+ "shape": [1, 3, 2],
394
+ "data": { "kind": "values", "values": [1.0, 1.0, 1.0, 1.0, 1.0, 0.5403] }
395
+ },
396
+ "sin": {
397
+ "dtype": "float32",
398
+ "shape": [1, 3, 2],
399
+ "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0, 0.0, 0.8415] }
400
+ }
401
+ },
402
+ "outputs": {
403
+ "y": {
404
+ "dtype": "float32",
405
+ "shape": [1, 3, 6],
406
+ "tolerance": 0.0001,
407
+ "data": {
408
+ "kind": "values",
409
+ "values": [-1.0408, 0.9166, -1.3042, -1.1097, -1.2188, 1.1676, -1.0408, 0.9166, -1.3042, -1.1097, -1.2188, 1.1676, 1.0076, -0.0427, -0.225, -0.8673, -1.5071, -0.4586]
410
+ }
411
+ }
412
+ }
413
+ },
414
+ {
415
+ "name": "onnx_backend_rank4_no_position_ids_batch_cache",
416
+ "provenance": {
417
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_rotary_embedding_no_position_ids",
418
+ "notes": "Compact deterministic data using the official no-position-id rank-4 cache layout."
419
+ },
420
+ "inputs": {
421
+ "x": {
422
+ "dtype": "float32",
423
+ "shape": [2, 2, 3, 4],
424
+ "data": { "kind": "fillFloat32", "scale": 0.55, "sinStep": 0.13, "cosStep": 0.29 }
425
+ },
426
+ "cos": {
427
+ "dtype": "float32",
428
+ "shape": [2, 3, 2],
429
+ "data": { "kind": "values", "values": [1.0, 0.5, 0.25, -0.75, -1.5, 2.0, 0.0, 1.0, 0.75, 0.125, -0.5, -1.0] }
430
+ },
431
+ "sin": {
432
+ "dtype": "float32",
433
+ "shape": [2, 3, 2],
434
+ "data": { "kind": "values", "values": [0.0, 1.0, -0.25, 0.5, 1.5, -0.75, -1.0, 0.0, 0.25, -0.5, 0.5, 0.25] }
435
+ }
436
+ },
437
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 2, 3, 4], "tolerance": 0.000001 } }
438
+ },
439
+ {
440
+ "name": "onnx_backend_rank4_no_position_ids_interleaved",
441
+ "provenance": {
442
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_rotary_embedding_no_position_ids_interleaved",
443
+ "notes": "Compact deterministic data using the official no-position-id rank-4 interleaved layout."
444
+ },
445
+ "attrs": { "interleaved": 1 },
446
+ "inputs": {
447
+ "x": {
448
+ "dtype": "float32",
449
+ "shape": [2, 2, 3, 4],
450
+ "data": { "kind": "fillFloat32", "scale": 0.55, "sinStep": 0.13, "cosStep": 0.29 }
451
+ },
452
+ "cos": {
453
+ "dtype": "float32",
454
+ "shape": [2, 3, 2],
455
+ "data": { "kind": "values", "values": [1.0, 0.5, 0.25, -0.75, -1.5, 2.0, 0.0, 1.0, 0.75, 0.125, -0.5, -1.0] }
456
+ },
457
+ "sin": {
458
+ "dtype": "float32",
459
+ "shape": [2, 3, 2],
460
+ "data": { "kind": "values", "values": [0.0, 1.0, -0.25, 0.5, 1.5, -0.75, -1.0, 0.0, 0.25, -0.5, 0.5, 0.25] }
461
+ }
462
+ },
463
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 2, 3, 4], "tolerance": 0.000001 } }
464
+ },
465
+ {
466
+ "name": "onnx_backend_rank4_no_position_ids_rotary_dim_tail",
467
+ "provenance": {
468
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_rotary_embedding_no_position_ids_rotary_dim",
469
+ "notes": "Compact deterministic data covering partial rotary dimension with unrotated tail values."
470
+ },
471
+ "attrs": { "rotary_embedding_dim": 4 },
472
+ "inputs": {
473
+ "x": {
474
+ "dtype": "float32",
475
+ "shape": [2, 2, 2, 6],
476
+ "data": { "kind": "fillFloat32", "scale": 0.45, "sinStep": 0.19, "cosStep": 0.11 }
477
+ },
478
+ "cos": {
479
+ "dtype": "float32",
480
+ "shape": [2, 2, 2],
481
+ "data": { "kind": "values", "values": [1.0, 0.5, 0.25, -0.75, -1.5, 2.0, 0.0, 1.0] }
482
+ },
483
+ "sin": {
484
+ "dtype": "float32",
485
+ "shape": [2, 2, 2],
486
+ "data": { "kind": "values", "values": [0.0, 1.0, -0.25, 0.5, 1.5, -0.75, -1.0, 0.0] }
487
+ }
488
+ },
489
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 2, 2, 6], "tolerance": 0.000001 } }
490
+ },
491
+ {
492
+ "name": "empty_input_zero_dim",
493
+ "attrs": { "num_heads": 1 },
494
+ "inputs": {
495
+ "x": { "dtype": "float32", "shape": [2, 3, 0], "data": { "kind": "values", "values": [] } },
496
+ "cos": { "dtype": "float32", "shape": [2, 3, 0], "data": { "kind": "values", "values": [] } },
497
+ "sin": { "dtype": "float32", "shape": [2, 3, 0], "data": { "kind": "values", "values": [] } }
498
+ },
499
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 0], "tolerance": 0 } }
500
+ },
501
+ {
502
+ "name": "interleaved_partial_rotary_dim_rank3",
503
+ "provenance": {
504
+ "notes": "Exercises the rank3_cache3 kernel's interleaved + partial-rotary branch: leading pairs rotate while the d>=rotary_embedding_dim tail is copied through unchanged."
505
+ },
506
+ "attrs": { "interleaved": 1, "rotary_embedding_dim": 4, "num_heads": 1 },
507
+ "inputs": {
508
+ "x": {
509
+ "dtype": "float32",
510
+ "shape": [1, 2, 8],
511
+ "data": {
512
+ "kind": "values",
513
+ "values": [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, -1.0, -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, -8.0]
514
+ }
515
+ },
516
+ "cos": {
517
+ "dtype": "float32",
518
+ "shape": [1, 2, 2],
519
+ "data": { "kind": "values", "values": [1.0, 1.0, 0.5403, 0.9999] }
520
+ },
521
+ "sin": {
522
+ "dtype": "float32",
523
+ "shape": [1, 2, 2],
524
+ "data": { "kind": "values", "values": [0.0, 0.0, 0.8415, 0.01] }
525
+ }
526
+ },
527
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 8], "tolerance": 0.0001 } }
528
+ },
529
+ {
530
+ "name": "decode_q1_rank4_head80_multihead_f16",
531
+ "provenance": {
532
+ "notes": "f16 autoregressive decode step (seq=1) with non-power-of-two head_dim 80 over 8 heads, the dominant inference path; exercises the rank4_cache3 kernel under the production fp16 dtype with the single-row decode shape."
533
+ },
534
+ "inputs": {
535
+ "x": {
536
+ "dtype": "float16",
537
+ "shape": [1, 8, 1, 80],
538
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.13, "cosStep": 0.21 }
539
+ },
540
+ "cos": {
541
+ "dtype": "float16",
542
+ "shape": [1, 1, 40],
543
+ "data": { "kind": "rotaryCos", "thetaStart": 0.05, "thetaStep": 0.07 }
544
+ },
545
+ "sin": {
546
+ "dtype": "float16",
547
+ "shape": [1, 1, 40],
548
+ "data": { "kind": "rotarySin", "thetaStart": 0.05, "thetaStep": 0.07 }
549
+ }
550
+ },
551
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 8, 1, 80], "tolerance": 0.02 } }
552
+ },
553
+ {
554
+ "name": "f16_multihead_packed_head96_not_interleaved",
555
+ "attrs": { "num_heads": 4 },
556
+ "provenance": {
557
+ "notes": "f16 rank-3 packed multi-head layout (hidden = num_heads * head_size) with non-power-of-two head_dim 96, exercising the rank3_cache3 kernel's per-head indexing under fp16."
558
+ },
559
+ "inputs": {
560
+ "x": {
561
+ "dtype": "float16",
562
+ "shape": [1, 3, 384],
563
+ "data": { "kind": "fillFloat32", "scale": 0.25, "sinStep": 0.11, "cosStep": 0.19 }
564
+ },
565
+ "cos": {
566
+ "dtype": "float16",
567
+ "shape": [1, 3, 48],
568
+ "data": { "kind": "rotaryCos", "thetaStart": 0.04, "thetaStep": 0.09 }
569
+ },
570
+ "sin": {
571
+ "dtype": "float16",
572
+ "shape": [1, 3, 48],
573
+ "data": { "kind": "rotarySin", "thetaStart": 0.04, "thetaStep": 0.09 }
574
+ }
575
+ },
576
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 3, 384], "tolerance": 0.02 } }
577
+ },
578
+ {
579
+ "name": "interleaved_partial_rotary_rank4_multibatch_cache_stride",
580
+ "attrs": { "interleaved": 1, "rotary_embedding_dim": 4 },
581
+ "provenance": {
582
+ "notes": "Exercises the rank-4 interleaved + partial-rotary path with a per-batch rank-3 cache and batch>1, verifying the leading rotated pairs, unchanged tail, and correct per-batch cache-row selection."
583
+ },
584
+ "inputs": {
585
+ "x": {
586
+ "dtype": "float32",
587
+ "shape": [2, 2, 2, 6],
588
+ "data": { "kind": "fillFloat32", "scale": 0.45, "sinStep": 0.17, "cosStep": 0.23 }
589
+ },
590
+ "cos": {
591
+ "dtype": "float32",
592
+ "shape": [2, 2, 2],
593
+ "data": { "kind": "values", "values": [1.0, 0.5, 0.25, -0.75, -1.5, 2.0, 0.0, 1.0] }
594
+ },
595
+ "sin": {
596
+ "dtype": "float32",
597
+ "shape": [2, 2, 2],
598
+ "data": { "kind": "values", "values": [0.0, 1.0, -0.25, 0.5, 1.5, -0.75, -1.0, 0.0] }
599
+ }
600
+ },
601
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 2, 2, 6], "tolerance": 0.000001 } }
602
+ },
603
+ {
604
+ "name": "interleaved_partial_rotary_multihead_packed_rank3",
605
+ "attrs": { "interleaved": 1, "rotary_embedding_dim": 4, "num_heads": 4 },
606
+ "provenance": {
607
+ "notes": "Interleaved pairing + partial rotary_embedding_dim=4 + packed multi-head layout (hidden=64, num_heads=4 -> head_size=16). Verifies per-head rotation of the leading 4 lanes, unchanged tails, and correct per-head base indexing."
608
+ },
609
+ "inputs": {
610
+ "x": {
611
+ "dtype": "float32",
612
+ "shape": [1, 3, 64],
613
+ "data": { "kind": "fillFloat32", "scale": 0.3, "sinStep": 0.11, "cosStep": 0.19 }
614
+ },
615
+ "cos": {
616
+ "dtype": "float32",
617
+ "shape": [1, 3, 2],
618
+ "data": { "kind": "values", "values": [1.0, 1.0, 0.5403, 0.9999, -0.4161, 0.9998] }
619
+ },
620
+ "sin": {
621
+ "dtype": "float32",
622
+ "shape": [1, 3, 2],
623
+ "data": { "kind": "values", "values": [0.0, 0.0, 0.8415, 0.01, 0.9093, 0.02] }
624
+ }
625
+ },
626
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 3, 64], "tolerance": 0.0001 } }
627
+ },
628
+ {
629
+ "name": "dispatch_fold_rank4_pair_last_row_seq65540",
630
+ "provenance": {
631
+ "notes": "Regression guard for the 2D dispatch fold: pairCount = numel(X)/2 = 16,781,440 -> ceil(/256)=65540 > 65535 forces the gid.y high-bit fold. Confirms the highest pair indices are written at the maxComputeWorkgroupsPerDimension cliff. Periodic inputs preserve coverage without evaluating 33M host transcendentals."
632
+ },
633
+ "inputs": {
634
+ "x": {
635
+ "dtype": "float32",
636
+ "shape": [1, 1, 65540, 512],
637
+ "data": { "kind": "cycle", "values": [0.5, -0.75, 1.25, -1.5] }
638
+ },
639
+ "cos": {
640
+ "dtype": "float32",
641
+ "shape": [1, 65540, 256],
642
+ "data": { "kind": "cycle", "values": [0.5, 0.75, -0.25, 1.0] }
643
+ },
644
+ "sin": {
645
+ "dtype": "float32",
646
+ "shape": [1, 65540, 256],
647
+ "data": { "kind": "cycle", "values": [0.25, -0.5, 0.75, 0.125] }
648
+ }
649
+ },
650
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 65540, 512], "tolerance": 0.0001 } }
651
+ },
652
+ {
653
+ "name": "pos_ids_rank3_pair_gather",
654
+ "attrs": { "num_heads": 2 },
655
+ "inputs": {
656
+ "x": {
657
+ "dtype": "float32",
658
+ "shape": [2, 3, 8],
659
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/pos_ids_rank3_pair_gather_input_x" } }
660
+ },
661
+ "cos": {
662
+ "dtype": "float32",
663
+ "shape": [16, 2],
664
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/position_id_cos_table" } }
665
+ },
666
+ "sin": {
667
+ "dtype": "float32",
668
+ "shape": [16, 2],
669
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/position_id_sin_table" } }
670
+ },
671
+ "positionIds": {
672
+ "dtype": "uint32",
673
+ "shape": [2, 3],
674
+ "data": { "kind": "values", "values": [4, 5, 6, 0, 2, 9] }
675
+ }
676
+ },
677
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 8], "tolerance": 0.0001 } },
678
+ "provenance": {
679
+ "source": "onnxruntime/core/providers/webgpu/llm/rotary_embedding.cc",
680
+ "test": "RotaryEmbedding position_ids gather (opset-23 input 3)",
681
+ "notes": "When position_ids is supplied the cos/sin caches are 2D (max_position, half) tables gathered per (batch, token); without it the caches are addressed by the token index directly."
682
+ }
683
+ },
684
+ {
685
+ "name": "pos_ids_rank3_interleaved_partial",
686
+ "inputs": {
687
+ "x": {
688
+ "dtype": "float32",
689
+ "shape": [1, 4, 12],
690
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/pos_ids_rank3_pair_gather_input_x" } }
691
+ },
692
+ "cos": {
693
+ "dtype": "float32",
694
+ "shape": [16, 2],
695
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/position_id_cos_table" } }
696
+ },
697
+ "sin": {
698
+ "dtype": "float32",
699
+ "shape": [16, 2],
700
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/position_id_sin_table" } }
701
+ },
702
+ "positionIds": { "dtype": "uint32", "shape": [1, 4], "data": { "kind": "values", "values": [9, 0, 9, 2] } }
703
+ },
704
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 12], "tolerance": 0.0001 } },
705
+ "provenance": {
706
+ "source": "onnxruntime/core/providers/webgpu/llm/rotary_embedding.cc",
707
+ "test": "RotaryEmbedding position_ids gather (opset-23 input 3)",
708
+ "notes": "When position_ids is supplied the cos/sin caches are 2D (max_position, half) tables gathered per (batch, token); without it the caches are addressed by the token index directly."
709
+ },
710
+ "attrs": { "interleaved": 1, "rotary_embedding_dim": 4, "num_heads": 2 }
711
+ },
712
+ {
713
+ "name": "pos_ids_rank4_pair_gather",
714
+ "inputs": {
715
+ "x": {
716
+ "dtype": "float32",
717
+ "shape": [1, 2, 3, 4],
718
+ "data": {
719
+ "kind": "values",
720
+ "values": [0.0, 0.6442, 0.9854, 0.8632, 0.335, -0.3508, -0.8716, -0.9825, -0.6313, 0.0168, 0.657, 0.9882, 0.8546, 0.3191, -0.3665, -0.8797, -0.9792, -0.6181, 0.0336, 0.6696, 0.9906, 0.8457, 0.3031, -0.3821]
721
+ }
722
+ },
723
+ "cos": {
724
+ "dtype": "float32",
725
+ "shape": [16, 2],
726
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/position_id_cos_table" } }
727
+ },
728
+ "sin": {
729
+ "dtype": "float32",
730
+ "shape": [16, 2],
731
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/position_id_sin_table" } }
732
+ },
733
+ "positionIds": { "dtype": "uint32", "shape": [1, 3], "data": { "kind": "values", "values": [7, 3, 11] } }
734
+ },
735
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 3, 4], "tolerance": 0.0001 } },
736
+ "provenance": {
737
+ "source": "onnxruntime/core/providers/webgpu/llm/rotary_embedding.cc",
738
+ "test": "RotaryEmbedding position_ids gather (opset-23 input 3)",
739
+ "notes": "When position_ids is supplied the cos/sin caches are 2D (max_position, half) tables gathered per (batch, token); without it the caches are addressed by the token index directly."
740
+ }
741
+ },
742
+ {
743
+ "name": "pos_ids_rank3_decode_step",
744
+ "attrs": { "num_heads": 2 },
745
+ "inputs": {
746
+ "x": {
747
+ "dtype": "float32",
748
+ "shape": [1, 1, 8],
749
+ "data": { "kind": "values", "values": [0.0, 0.6442, 0.9854, 0.8632, 0.335, -0.3508, -0.8716, -0.9825] }
750
+ },
751
+ "cos": {
752
+ "dtype": "float32",
753
+ "shape": [16, 2],
754
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/position_id_cos_table" } }
755
+ },
756
+ "sin": {
757
+ "dtype": "float32",
758
+ "shape": [16, 2],
759
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/position_id_sin_table" } }
760
+ },
761
+ "positionIds": { "dtype": "uint32", "shape": [1, 1], "data": { "kind": "values", "values": [5] } }
762
+ },
763
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 8], "tolerance": 0.0001 } },
764
+ "provenance": {
765
+ "source": "onnxruntime/core/providers/webgpu/llm/rotary_embedding.cc",
766
+ "test": "RotaryEmbedding position_ids gather (opset-23 input 3)",
767
+ "notes": "When position_ids is supplied the cos/sin caches are 2D (max_position, half) tables gathered per (batch, token); without it the caches are addressed by the token index directly."
768
+ }
769
+ },
770
+ {
771
+ "name": "position_ids_rank3_prefill_from_past_len",
772
+ "attrs": { "num_heads": 2 },
773
+ "inputs": {
774
+ "x": {
775
+ "dtype": "float32",
776
+ "shape": [2, 3, 8],
777
+ "data": { "kind": "fillFloat32", "sinStep": 0.19, "cosStep": 0.37, "scale": 0.7 }
778
+ },
779
+ "cos": {
780
+ "dtype": "float32",
781
+ "shape": [16, 2],
782
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/position_id_cos_table" } }
783
+ },
784
+ "sin": {
785
+ "dtype": "float32",
786
+ "shape": [16, 2],
787
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/position_id_sin_table" } }
788
+ },
789
+ "positionIds": {
790
+ "dtype": "uint32",
791
+ "shape": [2, 3],
792
+ "data": { "kind": "values", "values": [5, 6, 7, 5, 6, 7] }
793
+ }
794
+ },
795
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 8], "tolerance": 0.0001 } },
796
+ "provenance": {
797
+ "source": "onnxruntime/core/providers/webgpu/llm/rotary_embedding.cc",
798
+ "test": "RotaryEmbedding position_ids gather (opset-23 input 3)",
799
+ "notes": "The standard [batch, sequence] position table selects cache rows 5, 6, and 7 for both batches, covering a prefill that begins after an existing cache prefix."
800
+ }
801
+ },
802
+ {
803
+ "name": "position_ids_rank4_decode_step_past_len",
804
+ "inputs": {
805
+ "x": {
806
+ "dtype": "float32",
807
+ "shape": [1, 2, 1, 4],
808
+ "data": { "kind": "fillFloat32", "sinStep": 0.23, "cosStep": 0.41, "scale": 0.7 }
809
+ },
810
+ "cos": {
811
+ "dtype": "float32",
812
+ "shape": [16, 2],
813
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/position_id_cos_table" } }
814
+ },
815
+ "sin": {
816
+ "dtype": "float32",
817
+ "shape": [16, 2],
818
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/position_id_sin_table" } }
819
+ },
820
+ "positionIds": { "dtype": "uint32", "shape": [1, 1], "data": { "kind": "values", "values": [9] } }
821
+ },
822
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 1, 4], "tolerance": 0.0001 } },
823
+ "provenance": {
824
+ "source": "onnxruntime/core/providers/webgpu/llm/rotary_embedding.cc",
825
+ "test": "RotaryEmbedding position_ids gather (opset-23 input 3)",
826
+ "notes": "A standard [1,1] position table selects cache row 9 for the single decode token; both heads use that row."
827
+ }
828
+ }
829
+ ]
830
+ }