Xenova HF Staff commited on
Commit
5ce7237
·
verified ·
1 Parent(s): 3d17c9b

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,84 @@
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.MRotaryEmbedding
10
+
11
+ `com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
12
+
13
+ ## Description
14
+
15
+ Multimodal rotary position embedding (M-RoPE) for Qwen models. Each token has temporal, height, and width position streams; `mrope_section` partitions the half-rotary axis and `mrope_layout` assigns them. Text-only tokens set all streams equal, reducing the op to `RotaryEmbedding`. The effective rotary dimension must be positive and even; an odd head size is supported with a smaller even `rotary_embedding_dim`. This package supports float16/float32 and non-packed mode; bfloat16 and packed batching are not implemented. Position ids must be valid non-negative cache-row indices.
16
+
17
+ See the [ONNX Runtime `MRotaryEmbedding` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.MRotaryEmbedding) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- | --- |
23
+ | `input` | `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. The effective rotary dimension must be even, and `num_heads` is required for rank-3 input. | required |
24
+ | `position_ids` | `positionIds` | `M` | `uint32` | `3` | — | Logical int64 position indices of shape `(3, batch_size, sequence_length)`, holding the temporal, height and width streams in that order along the first axis. Valid positions are non-negative cache-row indices and use uint32 WebGPU storage. | required |
25
+ | `cos_cache` | `cos` | `T` | same as logical dtype | `2` | — | Precomputed cosine values of shape `(max_sequence_length, rotary_dim/2)`, shared by all three position streams. | required |
26
+ | `sin_cache` | `sin` | `T` | same as logical dtype | `2` | — | Precomputed sine values with the same shape and type as `cos_cache`. | required |
27
+
28
+ ## Outputs
29
+
30
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
31
+ | --- | --- | --- | --- | --- | --- | --- |
32
+ | `output` | `y` | `T` | same as `input` | same as `input` | Rotary-position-encoded tensor with the same shape and type as `input`. | 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. This is the rotation pairing and is independent of `mrope_layout`. |
41
+ | `is_packed_batching` | `0` | Whether `position_ids` uses packed-batch metadata. The default and only supported value is 0; packed batching (1) is not implemented. |
42
+ | `mrope_layout` | `0` | How the three sections are combined into one per-token cos/sin vector: `0` for the sectioned/chunked layout (Qwen2-VL, Qwen2.5-VL) or `1` for the interleaved layout (Qwen3-VL, Qwen3.5). Default is 0. |
43
+ | `num_heads` | `0` | Number of attention heads. The schema default is 0. A positive value is required for rank-3 `input` and whenever `rotary_embedding_dim` is nonzero; rank-4 execution otherwise infers the head count from `input`. |
44
+ | `rotary_embedding_dim` | `0` | Positive even number of head-dimension elements to rotate; `0` means the full head dimension, which must then be even. A smaller even value permits an odd head size and copies the remaining tail unchanged. |
45
+ | `scale` | `1` | Scale applied to the gathered cosine and sine values before the rotation. Default is 1.0. |
46
+ | `mrope_section` | — | Three non-negative integers `[section_t, section_h, section_w]` dividing the half-rotary axis among the temporal, height and width streams. They must sum to `rotary_embedding_dim / 2`, or to `head_size / 2` when `rotary_embedding_dim` is 0. Required. |
47
+
48
+ ## Type constraints
49
+
50
+ | Variable | Allowed dtypes |
51
+ | --- | --- |
52
+ | `T` | `float32`, `float16` |
53
+ | `M` | `int64` |
54
+
55
+ ## Files
56
+
57
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
58
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
59
+ - [`test.json`](build/webgpu/test.json) — correctness cases
60
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
61
+ - [`mrotary-embedding.wgsl.jinja`](build/webgpu/mrotary-embedding.wgsl.jinja)
62
+
63
+ ## Use with `@huggingface/kernels`
64
+
65
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
66
+ It then allocates the result tensors automatically.
67
+
68
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
69
+
70
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
71
+
72
+ ```js
73
+ import { getKernel } from "@huggingface/kernels";
74
+
75
+ const kernel = await getKernel("webgpu-kernels/com.microsoft.MRotaryEmbedding", { version: 1 });
76
+ const { y } = await kernel({
77
+ x: { data: xData, shape: [1, 4, 16] },
78
+ positionIds: { data: positionIdsData, shape: [3, 1, 4] },
79
+ cos: { data: cosData, shape: [8, 4] },
80
+ sin: { data: sinData, shape: [8, 4] },
81
+ }, {
82
+ attrs: { num_heads: 2, mrope_section: [2, 1, 1] },
83
+ });
84
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "com.microsoft.MRotaryEmbedding",
3
+ "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
4
+ "cases": [
5
+ {
6
+ "name": "mrope-f32-smoke-b1h4s64d64",
7
+ "preset": "smoke",
8
+ "attrs": {
9
+ "mrope_layout": 0,
10
+ "interleaved": 0,
11
+ "mrope_section": [16, 8, 8],
12
+ "rotary_embedding_dim": 0,
13
+ "scale": 1
14
+ },
15
+ "vars": { "elems": 16384, "rotPairs": 8192 },
16
+ "inputs": {
17
+ "x": { "shape": [1, 4, 64, 64], "dtype": "float32", "dist": "normal", "seed": 1000, "scale": 0.2 },
18
+ "positionIds": { "shape": [3, 1, 64], "dtype": "uint32", "dist": "uniform", "seed": 1001, "scale": 127 },
19
+ "cos": { "shape": [128, 32], "dtype": "float32", "dist": "uniform", "seed": 1002, "scale": 0.1, "offset": 0.9 },
20
+ "sin": { "shape": [128, 32], "dtype": "float32", "dist": "uniform", "seed": 1003, "scale": 0.1 }
21
+ },
22
+ "outputs": { "y": { "shape": [1, 4, 64, 64], "dtype": "float32" } },
23
+ "bench": {
24
+ "primary": false,
25
+ "metrics": [{ "type": "bandwidth", "value": "args.elems * 4 * 2 + args.rotPairs * 4 * 2" }]
26
+ }
27
+ },
28
+ {
29
+ "name": "qwen3-vl-2b-decode-q-h16-d128",
30
+ "preset": "model",
31
+ "attrs": {
32
+ "mrope_layout": 1,
33
+ "interleaved": 0,
34
+ "mrope_section": [24, 20, 20],
35
+ "rotary_embedding_dim": 0,
36
+ "scale": 1
37
+ },
38
+ "vars": { "elems": 2048, "rotPairs": 1024 },
39
+ "provenance": {
40
+ "model": "Qwen/Qwen3-VL-2B-Instruct",
41
+ "notes": "Qwen3-VL interleaved M-RoPE over the query projection: 16 heads x head_dim 128, mrope_section [24, 20, 20] summing to head_dim/2. Cache rows cover the model's position budget."
42
+ },
43
+ "inputs": {
44
+ "x": { "shape": [1, 16, 1, 128], "dtype": "float32", "dist": "normal", "seed": 1000, "scale": 0.2 },
45
+ "positionIds": { "shape": [3, 1, 1], "dtype": "uint32", "dist": "uniform", "seed": 1001, "scale": 32767 },
46
+ "cos": {
47
+ "shape": [32768, 64],
48
+ "dtype": "float32",
49
+ "dist": "uniform",
50
+ "seed": 1002,
51
+ "scale": 0.1,
52
+ "offset": 0.9
53
+ },
54
+ "sin": { "shape": [32768, 64], "dtype": "float32", "dist": "uniform", "seed": 1003, "scale": 0.1 }
55
+ },
56
+ "outputs": { "y": { "shape": [1, 16, 1, 128], "dtype": "float32" } },
57
+ "bench": {
58
+ "primary": true,
59
+ "metrics": [{ "type": "bandwidth", "value": "args.elems * 4 * 2 + args.rotPairs * 4 * 2" }]
60
+ }
61
+ },
62
+ {
63
+ "name": "qwen3-vl-2b-prefill-t512-q-h16-d128",
64
+ "preset": "model",
65
+ "attrs": {
66
+ "mrope_layout": 1,
67
+ "interleaved": 0,
68
+ "mrope_section": [24, 20, 20],
69
+ "rotary_embedding_dim": 0,
70
+ "scale": 1
71
+ },
72
+ "vars": { "elems": 1048576, "rotPairs": 524288 },
73
+ "provenance": {
74
+ "model": "Qwen/Qwen3-VL-2B-Instruct",
75
+ "notes": "Qwen3-VL interleaved M-RoPE over the query projection: 16 heads x head_dim 128, mrope_section [24, 20, 20] summing to head_dim/2. Cache rows cover the model's position budget."
76
+ },
77
+ "inputs": {
78
+ "x": { "shape": [1, 16, 512, 128], "dtype": "float32", "dist": "normal", "seed": 1000, "scale": 0.2 },
79
+ "positionIds": { "shape": [3, 1, 512], "dtype": "uint32", "dist": "uniform", "seed": 1001, "scale": 32767 },
80
+ "cos": {
81
+ "shape": [32768, 64],
82
+ "dtype": "float32",
83
+ "dist": "uniform",
84
+ "seed": 1002,
85
+ "scale": 0.1,
86
+ "offset": 0.9
87
+ },
88
+ "sin": { "shape": [32768, 64], "dtype": "float32", "dist": "uniform", "seed": 1003, "scale": 0.1 }
89
+ },
90
+ "outputs": { "y": { "shape": [1, 16, 512, 128], "dtype": "float32" } },
91
+ "bench": {
92
+ "primary": false,
93
+ "metrics": [{ "type": "bandwidth", "value": "args.elems * 4 * 2 + args.rotPairs * 4 * 2" }]
94
+ }
95
+ },
96
+ {
97
+ "name": "qwen3-vl-2b-decode-k-h8-d128",
98
+ "preset": "model",
99
+ "attrs": {
100
+ "mrope_layout": 1,
101
+ "interleaved": 0,
102
+ "mrope_section": [24, 20, 20],
103
+ "rotary_embedding_dim": 0,
104
+ "scale": 1
105
+ },
106
+ "vars": { "elems": 1024, "rotPairs": 512 },
107
+ "provenance": {
108
+ "model": "Qwen/Qwen3-VL-2B-Instruct",
109
+ "notes": "Same layer's key projection: 8 kv heads, so half the query work and the smallest dispatch M-RoPE sees in decode."
110
+ },
111
+ "inputs": {
112
+ "x": { "shape": [1, 8, 1, 128], "dtype": "float32", "dist": "normal", "seed": 1000, "scale": 0.2 },
113
+ "positionIds": { "shape": [3, 1, 1], "dtype": "uint32", "dist": "uniform", "seed": 1001, "scale": 32767 },
114
+ "cos": {
115
+ "shape": [32768, 64],
116
+ "dtype": "float32",
117
+ "dist": "uniform",
118
+ "seed": 1002,
119
+ "scale": 0.1,
120
+ "offset": 0.9
121
+ },
122
+ "sin": { "shape": [32768, 64], "dtype": "float32", "dist": "uniform", "seed": 1003, "scale": 0.1 }
123
+ },
124
+ "outputs": { "y": { "shape": [1, 8, 1, 128], "dtype": "float32" } },
125
+ "bench": {
126
+ "primary": false,
127
+ "metrics": [{ "type": "bandwidth", "value": "args.elems * 4 * 2 + args.rotPairs * 4 * 2" }]
128
+ }
129
+ },
130
+ {
131
+ "name": "qwen2-5-vl-3b-prefill-t512-q-h16-d128",
132
+ "preset": "model",
133
+ "attrs": {
134
+ "mrope_layout": 0,
135
+ "interleaved": 0,
136
+ "mrope_section": [16, 24, 24],
137
+ "rotary_embedding_dim": 0,
138
+ "scale": 1
139
+ },
140
+ "vars": { "elems": 1048576, "rotPairs": 524288 },
141
+ "provenance": {
142
+ "model": "Qwen/Qwen2.5-VL-3B-Instruct",
143
+ "notes": "Qwen2.5-VL sectioned M-RoPE, mrope_section [16, 24, 24]. The sectioned branch reads three contiguous chunks where the interleaved branch strides by 3, so the two layouts have different cache access patterns at the same shape."
144
+ },
145
+ "inputs": {
146
+ "x": { "shape": [1, 16, 512, 128], "dtype": "float32", "dist": "normal", "seed": 1000, "scale": 0.2 },
147
+ "positionIds": { "shape": [3, 1, 512], "dtype": "uint32", "dist": "uniform", "seed": 1001, "scale": 32767 },
148
+ "cos": {
149
+ "shape": [32768, 64],
150
+ "dtype": "float32",
151
+ "dist": "uniform",
152
+ "seed": 1002,
153
+ "scale": 0.1,
154
+ "offset": 0.9
155
+ },
156
+ "sin": { "shape": [32768, 64], "dtype": "float32", "dist": "uniform", "seed": 1003, "scale": 0.1 }
157
+ },
158
+ "outputs": { "y": { "shape": [1, 16, 512, 128], "dtype": "float32" } },
159
+ "bench": {
160
+ "primary": false,
161
+ "metrics": [{ "type": "bandwidth", "value": "args.elems * 4 * 2 + args.rotPairs * 4 * 2" }]
162
+ }
163
+ },
164
+ {
165
+ "name": "qwen3-vl-2b-prefill-t512-q-h16-d128-f16",
166
+ "preset": "model",
167
+ "attrs": {
168
+ "mrope_layout": 1,
169
+ "interleaved": 0,
170
+ "mrope_section": [24, 20, 20],
171
+ "rotary_embedding_dim": 0,
172
+ "scale": 1
173
+ },
174
+ "vars": { "elems": 1048576, "rotPairs": 524288 },
175
+ "provenance": {
176
+ "model": "Qwen/Qwen3-VL-2B-Instruct",
177
+ "notes": "f16 activations at the same Qwen3-VL prefill shape."
178
+ },
179
+ "inputs": {
180
+ "x": { "shape": [1, 16, 512, 128], "dtype": "float16", "dist": "normal", "seed": 1000, "scale": 0.2 },
181
+ "positionIds": { "shape": [3, 1, 512], "dtype": "uint32", "dist": "uniform", "seed": 1001, "scale": 32767 },
182
+ "cos": {
183
+ "shape": [32768, 64],
184
+ "dtype": "float16",
185
+ "dist": "uniform",
186
+ "seed": 1002,
187
+ "scale": 0.1,
188
+ "offset": 0.9
189
+ },
190
+ "sin": { "shape": [32768, 64], "dtype": "float16", "dist": "uniform", "seed": 1003, "scale": 0.1 }
191
+ },
192
+ "outputs": { "y": { "shape": [1, 16, 512, 128], "dtype": "float16" } },
193
+ "bench": {
194
+ "primary": false,
195
+ "metrics": [{ "type": "bandwidth", "value": "args.elems * 2 * 2 + args.rotPairs * 2 * 2" }]
196
+ }
197
+ },
198
+ {
199
+ "name": "mrope-f32-stress-b4h32s2048d128",
200
+ "preset": "stress",
201
+ "attrs": {
202
+ "mrope_layout": 1,
203
+ "interleaved": 0,
204
+ "mrope_section": [24, 20, 20],
205
+ "rotary_embedding_dim": 0,
206
+ "scale": 1
207
+ },
208
+ "vars": { "elems": 33554432, "rotPairs": 16777216 },
209
+ "inputs": {
210
+ "x": { "shape": [4, 32, 2048, 128], "dtype": "float32", "dist": "normal", "seed": 1000, "scale": 0.2 },
211
+ "positionIds": { "shape": [3, 4, 2048], "dtype": "uint32", "dist": "uniform", "seed": 1001, "scale": 32767 },
212
+ "cos": {
213
+ "shape": [32768, 64],
214
+ "dtype": "float32",
215
+ "dist": "uniform",
216
+ "seed": 1002,
217
+ "scale": 0.1,
218
+ "offset": 0.9
219
+ },
220
+ "sin": { "shape": [32768, 64], "dtype": "float32", "dist": "uniform", "seed": 1003, "scale": 0.1 }
221
+ },
222
+ "outputs": { "y": { "shape": [4, 32, 2048, 128], "dtype": "float32" } },
223
+ "bench": {
224
+ "primary": false,
225
+ "metrics": [{ "type": "bandwidth", "value": "args.elems * 4 * 2 + args.rotPairs * 4 * 2" }]
226
+ }
227
+ }
228
+ ]
229
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "com.microsoft",
3
+ "name": "MRotaryEmbedding",
4
+ "sinceVersion": 1,
5
+ "description": "Multimodal rotary position embedding (M-RoPE) for Qwen models. Each token has temporal, height, and width position streams; `mrope_section` partitions the half-rotary axis and `mrope_layout` assigns them. Text-only tokens set all streams equal, reducing the op to `RotaryEmbedding`. The effective rotary dimension must be positive and even; an odd head size is supported with a smaller even `rotary_embedding_dim`. This package supports float16/float32 and non-packed mode; bfloat16 and packed batching are not implemented. Position ids must be valid non-negative cache-row indices.",
6
+ "inputs": [
7
+ {
8
+ "role": "input",
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. The effective rotary dimension must be even, and `num_heads` is required for rank-3 input."
11
+ },
12
+ {
13
+ "role": "position_ids",
14
+ "dtype": "M",
15
+ "rank": 3,
16
+ "description": "Logical int64 position indices of shape `(3, batch_size, sequence_length)`, holding the temporal, height and width streams in that order along the first axis. Valid positions are non-negative cache-row indices and use uint32 WebGPU storage."
17
+ },
18
+ {
19
+ "role": "cos_cache",
20
+ "dtype": "T",
21
+ "rank": 2,
22
+ "description": "Precomputed cosine values of shape `(max_sequence_length, rotary_dim/2)`, shared by all three position streams."
23
+ },
24
+ {
25
+ "role": "sin_cache",
26
+ "dtype": "T",
27
+ "rank": 2,
28
+ "description": "Precomputed sine values with the same shape and type as `cos_cache`."
29
+ }
30
+ ],
31
+ "outputs": [
32
+ {
33
+ "role": "output",
34
+ "dtype": "T",
35
+ "rank": "ranks.input",
36
+ "shape": "shapes.input",
37
+ "description": "Rotary-position-encoded tensor with the same shape and type as `input`."
38
+ }
39
+ ],
40
+ "attributes": {
41
+ "interleaved": 0,
42
+ "is_packed_batching": 0,
43
+ "mrope_layout": 0,
44
+ "num_heads": 0,
45
+ "rotary_embedding_dim": 0,
46
+ "scale": 1
47
+ },
48
+ "attributeConstraints": {
49
+ "interleaved": { "values": [0, 1] },
50
+ "is_packed_batching": { "values": [0] },
51
+ "mrope_layout": { "values": [0, 1] },
52
+ "mrope_section": { "required": true }
53
+ },
54
+ "attributeDescriptions": {
55
+ "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. This is the rotation pairing and is independent of `mrope_layout`.",
56
+ "is_packed_batching": "Whether `position_ids` uses packed-batch metadata. The default and only supported value is 0; packed batching (1) is not implemented.",
57
+ "mrope_layout": "How the three sections are combined into one per-token cos/sin vector: `0` for the sectioned/chunked layout (Qwen2-VL, Qwen2.5-VL) or `1` for the interleaved layout (Qwen3-VL, Qwen3.5). Default is 0.",
58
+ "mrope_section": "Three non-negative integers `[section_t, section_h, section_w]` dividing the half-rotary axis among the temporal, height and width streams. They must sum to `rotary_embedding_dim / 2`, or to `head_size / 2` when `rotary_embedding_dim` is 0. Required.",
59
+ "num_heads": "Number of attention heads. The schema default is 0. A positive value is required for rank-3 `input` and whenever `rotary_embedding_dim` is nonzero; rank-4 execution otherwise infers the head count from `input`.",
60
+ "rotary_embedding_dim": "Positive even number of head-dimension elements to rotate; `0` means the full head dimension, which must then be even. A smaller even value permits an odd head size and copies the remaining tail unchanged.",
61
+ "scale": "Scale applied to the gathered cosine and sine values before the rotation. Default is 1.0."
62
+ },
63
+ "typeConstraints": { "T": ["float32", "float16"], "M": ["int64"] },
64
+ "args": {
65
+ "x": { "kind": "tensor", "semantic": "input", "role": "input" },
66
+ "positionIds": {
67
+ "kind": "tensor",
68
+ "semantic": "position_ids",
69
+ "role": "input",
70
+ "dtype": "uint32",
71
+ "narrowing": "checked"
72
+ },
73
+ "cos": { "kind": "tensor", "semantic": "cos_cache", "role": "input" },
74
+ "sin": { "kind": "tensor", "semantic": "sin_cache", "role": "input" },
75
+ "y": { "kind": "tensor", "semantic": "output", "role": "output" }
76
+ },
77
+ "tunables": { "WORKGROUP_SIZE": 256 },
78
+ "derive": {
79
+ "rank3HeadSize": "dim(shapes.input, 2) / attrs.num_heads if attrs.num_heads is defined and attrs.num_heads > 0 else 0",
80
+ "headSize": "rank3HeadSize if ranks.input == 3 else dim(shapes.input, 3)",
81
+ "effectiveRotaryDim": "attrs.rotary_embedding_dim if attrs.rotary_embedding_dim != 0 else headSize",
82
+ "tasksPerHead": "ceilDiv(headSize, 2)",
83
+ "pairCount": "(numel(shapes.input) / max(1, headSize)) * tasksPerHead",
84
+ "pairDispatchOk": "ceilDiv(pairCount, tunables.WORKGROUP_SIZE) <= device.limits.maxComputeWorkgroupsPerDimension * device.limits.maxComputeWorkgroupsPerDimension",
85
+ "halfRotaryDim": "dim(shapes.cos_cache, 1)",
86
+ "sectionsDefined": "attrs.mrope_section is defined and (attrs.mrope_section | length) == 3",
87
+ "sectionsValid": "sectionsDefined and attrs.mrope_section[0] >= 0 and attrs.mrope_section[1] >= 0 and attrs.mrope_section[2] >= 0 and attrs.mrope_section[0] + attrs.mrope_section[1] + attrs.mrope_section[2] == halfRotaryDim",
88
+ "commonContract": "f16Ok(dtypes.T) and sameShape(shapes.input, shapes.output) and (attrs.interleaved == 0 or attrs.interleaved == 1) and (attrs.mrope_layout == 0 or attrs.mrope_layout == 1) and attrs.num_heads >= 0 and attrs.rotary_embedding_dim >= 0 and (attrs.rotary_embedding_dim == 0 or attrs.num_heads > 0) and sameShape(shapes.cos_cache, shapes.sin_cache) and ranks.cos_cache == 2 and ranks.sin_cache == 2 and sectionsValid and ranks.position_ids == 3 and dim(shapes.position_ids, 0) == 3 and dim(shapes.position_ids, 1) == dim(shapes.input, 0) and dim(shapes.position_ids, 2) == dim(shapes.input, 1 if ranks.input == 3 else 2)",
89
+ "rank3Contract": "commonContract and ranks.input == 3 and ranks.output == 3 and attrs.num_heads is defined and attrs.num_heads >= 1 and dim(shapes.input, 2) % attrs.num_heads == 0 and rank3HeadSize > 0 and effectiveRotaryDim % 2 == 0 and (attrs.rotary_embedding_dim == 0 or attrs.rotary_embedding_dim <= rank3HeadSize) and halfRotaryDim * 2 == effectiveRotaryDim",
90
+ "rank4Contract": "commonContract and ranks.input == 4 and ranks.output == 4 and dim(shapes.input, 3) > 0 and effectiveRotaryDim % 2 == 0 and (attrs.rotary_embedding_dim == 0 or attrs.rotary_embedding_dim <= dim(shapes.input, 3)) and halfRotaryDim * 2 == effectiveRotaryDim"
91
+ },
92
+ "bindingSets": {
93
+ "$common": [
94
+ { "name": "x", "arg": "x", "semantic": "input", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
95
+ {
96
+ "name": "position_ids",
97
+ "arg": "positionIds",
98
+ "semantic": "position_ids",
99
+ "buffer": { "type": "read-only-storage" },
100
+ "elementType": "u32"
101
+ },
102
+ {
103
+ "name": "cos_cache",
104
+ "arg": "cos",
105
+ "semantic": "cos_cache",
106
+ "buffer": { "type": "read-only-storage" },
107
+ "elementType": "$T"
108
+ },
109
+ {
110
+ "name": "sin_cache",
111
+ "arg": "sin",
112
+ "semantic": "sin_cache",
113
+ "buffer": { "type": "read-only-storage" },
114
+ "elementType": "$T"
115
+ },
116
+ { "name": "y", "arg": "y", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "$T" }
117
+ ],
118
+ "rank3": [
119
+ { "name": "x", "arg": "x", "semantic": "input", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
120
+ {
121
+ "name": "position_ids",
122
+ "arg": "positionIds",
123
+ "semantic": "position_ids",
124
+ "buffer": { "type": "read-only-storage" },
125
+ "elementType": "u32"
126
+ },
127
+ {
128
+ "name": "cos_cache",
129
+ "arg": "cos",
130
+ "semantic": "cos_cache",
131
+ "buffer": { "type": "read-only-storage" },
132
+ "elementType": "$T"
133
+ },
134
+ {
135
+ "name": "sin_cache",
136
+ "arg": "sin",
137
+ "semantic": "sin_cache",
138
+ "buffer": { "type": "read-only-storage" },
139
+ "elementType": "$T"
140
+ },
141
+ { "name": "y", "arg": "y", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "$T" },
142
+ {
143
+ "name": "params",
144
+ "semantic": "kernel.params",
145
+ "buffer": { "type": "uniform" },
146
+ "struct": {
147
+ "name": "Params",
148
+ "fields": [
149
+ { "name": "pairCount", "type": "u32", "value": "pairCount" },
150
+ { "name": "batchSize", "type": "u32", "value": "dim(shapes.input, 0)" },
151
+ { "name": "sequenceLength", "type": "u32", "value": "dim(shapes.input, 1)" },
152
+ { "name": "numHeads", "type": "u32", "value": "attrs.num_heads" },
153
+ { "name": "headSize", "type": "u32", "value": "rank3HeadSize" },
154
+ { "name": "rotaryDim", "type": "u32", "value": "halfRotaryDim * 2" },
155
+ { "name": "halfRotaryDim", "type": "u32", "value": "halfRotaryDim" },
156
+ { "name": "section0", "type": "u32", "value": "attrs.mrope_section[0]" },
157
+ { "name": "section1", "type": "u32", "value": "attrs.mrope_section[1]" },
158
+ { "name": "section2", "type": "u32", "value": "attrs.mrope_section[2]" },
159
+ { "name": "scale", "type": "f32", "value": "attrs.scale" }
160
+ ]
161
+ }
162
+ }
163
+ ],
164
+ "rank4": [
165
+ { "name": "x", "arg": "x", "semantic": "input", "buffer": { "type": "read-only-storage" }, "elementType": "$T" },
166
+ {
167
+ "name": "position_ids",
168
+ "arg": "positionIds",
169
+ "semantic": "position_ids",
170
+ "buffer": { "type": "read-only-storage" },
171
+ "elementType": "u32"
172
+ },
173
+ {
174
+ "name": "cos_cache",
175
+ "arg": "cos",
176
+ "semantic": "cos_cache",
177
+ "buffer": { "type": "read-only-storage" },
178
+ "elementType": "$T"
179
+ },
180
+ {
181
+ "name": "sin_cache",
182
+ "arg": "sin",
183
+ "semantic": "sin_cache",
184
+ "buffer": { "type": "read-only-storage" },
185
+ "elementType": "$T"
186
+ },
187
+ { "name": "y", "arg": "y", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "$T" },
188
+ {
189
+ "name": "params",
190
+ "semantic": "kernel.params",
191
+ "buffer": { "type": "uniform" },
192
+ "struct": {
193
+ "name": "Params",
194
+ "fields": [
195
+ { "name": "pairCount", "type": "u32", "value": "pairCount" },
196
+ { "name": "batchSize", "type": "u32", "value": "dim(shapes.input, 0)" },
197
+ { "name": "sequenceLength", "type": "u32", "value": "dim(shapes.input, 2)" },
198
+ { "name": "numHeads", "type": "u32", "value": "dim(shapes.input, 1)" },
199
+ { "name": "headSize", "type": "u32", "value": "dim(shapes.input, 3)" },
200
+ { "name": "rotaryDim", "type": "u32", "value": "halfRotaryDim * 2" },
201
+ { "name": "halfRotaryDim", "type": "u32", "value": "halfRotaryDim" },
202
+ { "name": "section0", "type": "u32", "value": "attrs.mrope_section[0]" },
203
+ { "name": "section1", "type": "u32", "value": "attrs.mrope_section[1]" },
204
+ { "name": "section2", "type": "u32", "value": "attrs.mrope_section[2]" },
205
+ { "name": "scale", "type": "f32", "value": "attrs.scale" }
206
+ ]
207
+ }
208
+ }
209
+ ]
210
+ },
211
+ "variants": [
212
+ {
213
+ "id": "rank3",
214
+ "when": ["rank3Contract", "pairDispatchOk"],
215
+ "constants": {
216
+ "interleaved": "attrs.interleaved != 0",
217
+ "mropeSectioned": "attrs.mrope_layout == 0",
218
+ "usesF16": "dtypes.T == \"f16\"",
219
+ "scalar": "dtypes.T"
220
+ },
221
+ "passes": [
222
+ {
223
+ "id": "main",
224
+ "name": "mrotary_embedding3d",
225
+ "source": { "shader": "mrotary-embedding.wgsl.jinja", "inputs": { "rank": 3 } },
226
+ "bindings": "rank3",
227
+ "dispatch": { "threads": "pairCount", "workgroupSize": "tunables.WORKGROUP_SIZE" }
228
+ }
229
+ ]
230
+ },
231
+ {
232
+ "id": "rank4",
233
+ "when": ["rank4Contract", "pairDispatchOk"],
234
+ "constants": {
235
+ "interleaved": "attrs.interleaved != 0",
236
+ "mropeSectioned": "attrs.mrope_layout == 0",
237
+ "usesF16": "dtypes.T == \"f16\"",
238
+ "scalar": "dtypes.T"
239
+ },
240
+ "passes": [
241
+ {
242
+ "id": "main",
243
+ "name": "mrotary_embedding4d",
244
+ "source": { "shader": "mrotary-embedding.wgsl.jinja", "inputs": { "rank": 4 } },
245
+ "bindings": "rank4",
246
+ "dispatch": { "threads": "pairCount", "workgroupSize": "tunables.WORKGROUP_SIZE" }
247
+ }
248
+ ]
249
+ }
250
+ ]
251
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "com.microsoft.MRotaryEmbedding",
3
+ "id": "_com_microsoft_mrotaryembedding_webgpu_2097c43",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "RRGYpzSKyDx1HHSFAzgf4u0UdW+WMXLIo+cRg29orLg=",
11
+ "manifest.json": "rNhF7mp6GzPe1XKu5djfgpCv5MKyJdPU7G06AvbgLXE=",
12
+ "mrotary-embedding.wgsl.jinja": "ywEaYPPZeG6tSo/Ulh3lIOBwXLhdu70TAZJAgus88cA=",
13
+ "test.json": "0mcf/8uDq9fTEq082qLPOGoRnjowxCk+vqzBBFVA0Ls="
14
+ }
15
+ },
16
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
17
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.MRotaryEmbedding" }
18
+ }
build/webgpu/mrotary-embedding.wgsl.jinja ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ // M-RoPE: three position streams (temporal, height, width) index one cos/sin cache.
37
+ // The half-rotary axis is partitioned among the streams, so each rotated pair picks
38
+ // its own stream and gathers that stream's position for this token. One invocation
39
+ // owns both outputs of a rotated pair, so x/cos/sin are read once; partial rotations
40
+ // use the remaining pair lanes to copy two unchanged tail values.
41
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
42
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
43
+ {{ flat_index_2d("p", "params.pairCount", note="") }}
44
+
45
+ let tasksPerHead = (params.headSize + 1u) / 2u;
46
+ let task = p % tasksPerHead;
47
+ let headFlat = p / tasksPerHead;
48
+ let base = headFlat * params.headSize;
49
+
50
+ if (task >= params.halfRotaryDim) {
51
+ let tail = params.rotaryDim + (task - params.halfRotaryDim) * 2u;
52
+ y[base + tail] = x[base + tail];
53
+ if (tail + 1u < params.headSize) {
54
+ y[base + tail + 1u] = x[base + tail + 1u];
55
+ }
56
+ return;
57
+ }
58
+
59
+ // `pair` indexes the half-rotary axis, which is exactly the cos/sin cache column
60
+ // under both rotation pairings: interleaved reads column channel/2 and the split
61
+ // form reads channel % halfRotaryDim, and this lane owns channel == pair in each.
62
+ let pair = task;
63
+ {% if source.rank == 3 %}
64
+ let token = (headFlat / params.numHeads) % params.sequenceLength;
65
+ let batch = headFlat / (params.numHeads * params.sequenceLength);
66
+ {% else %}
67
+ let token = headFlat % params.sequenceLength;
68
+ let batch = (headFlat / params.sequenceLength) / params.numHeads;
69
+ {% endif %}
70
+
71
+ // Which of the three position streams owns this cache column.
72
+ var stream = 0u;
73
+ {% if mropeSectioned %}
74
+ // Sectioned (Qwen2-VL, Qwen2.5-VL): three contiguous chunks [T | H | W].
75
+ if (pair >= params.section0 && pair < params.section0 + params.section1) {
76
+ stream = 1u;
77
+ } else if (pair >= params.section0 + params.section1) {
78
+ stream = 2u;
79
+ }
80
+ {% else %}
81
+ // Interleaved (Qwen3-VL, Qwen3.5): T everywhere, then H over every third column
82
+ // from offset 1 and W over every third column from offset 2, each bounded by its
83
+ // own section length.
84
+ if (pair % 3u == 1u && pair / 3u < params.section1) {
85
+ stream = 1u;
86
+ } else if (pair % 3u == 2u && pair / 3u < params.section2) {
87
+ stream = 2u;
88
+ }
89
+ {% endif %}
90
+
91
+ let pos = position_ids[(stream * params.batchSize + batch) * params.sequenceLength + token];
92
+ let cache = pos * params.halfRotaryDim + pair;
93
+ // Round scaled cache values to T before rotation. This intermediate cast is
94
+ // observable for f16 and must not be deferred to the output store.
95
+ let cf = f32({{ scalar }}(f32(cos_cache[cache]) * params.scale));
96
+ let sf = f32({{ scalar }}(f32(sin_cache[cache]) * params.scale));
97
+ {% if interleaved %}
98
+ let aOffset = pair * 2u;
99
+ let bOffset = aOffset + 1u;
100
+ {% else %}
101
+ let aOffset = pair;
102
+ let bOffset = pair + params.halfRotaryDim;
103
+ {% endif %}
104
+ let a = f32(x[base + aOffset]);
105
+ let b = f32(x[base + bOffset]);
106
+ y[base + aOffset] = {{ scalar }}(a * cf - b * sf);
107
+ y[base + bOffset] = {{ scalar }}(a * sf + b * cf);
108
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "com.microsoft.MRotaryEmbedding",
3
+ "fixtureArrays": {
4
+ "ort_sectioned_rank3_input_x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
5
+ },
6
+ "cases": [
7
+ {
8
+ "name": "rank4_odd_head_even_partial_rotation",
9
+ "provenance": {
10
+ "source": "onnxruntime/contrib_ops/cuda/bert/mrotary_embedding_impl.cu",
11
+ "notes": "The provider requires the effective rotary dimension, not the full head size, to be even. The singleton tail element is copied unchanged."
12
+ },
13
+ "attrs": {
14
+ "num_heads": 1,
15
+ "rotary_embedding_dim": 4,
16
+ "mrope_layout": 0,
17
+ "interleaved": 0,
18
+ "mrope_section": [1, 1, 0]
19
+ },
20
+ "inputs": {
21
+ "x": { "dtype": "float32", "shape": [1, 1, 2, 5], "data": { "kind": "linspace", "start": -1.25, "end": 1.5 } },
22
+ "positionIds": {
23
+ "dtype": "uint32",
24
+ "shape": [3, 1, 2],
25
+ "data": { "kind": "values", "values": [0, 1, 1, 2, 2, 0] }
26
+ },
27
+ "cos": { "dtype": "float32", "shape": [3, 2], "data": { "kind": "linspace", "start": 0.25, "end": 1.0 } },
28
+ "sin": { "dtype": "float32", "shape": [3, 2], "data": { "kind": "linspace", "start": -0.5, "end": 0.5 } }
29
+ },
30
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 1, 2, 5], "tolerance": 0.00001 } }
31
+ },
32
+ {
33
+ "name": "ort_sectioned_rank3",
34
+ "provenance": {
35
+ "source": "onnxruntime/test/contrib_ops/mrotary_embedding_op_test.cc",
36
+ "test": "ContribOpMRotaryEmbeddingTest.SectionedRank3",
37
+ "notes": "Pinned from ORT. Sectioned layout over three one-column sections, so each cache column is fed by a different position stream (T=[0,1], H=[1,2], W=[2,3]) and a stream mix-up changes every output."
38
+ },
39
+ "attrs": {
40
+ "num_heads": 2,
41
+ "rotary_embedding_dim": 6,
42
+ "mrope_layout": 0,
43
+ "interleaved": 0,
44
+ "scale": 1,
45
+ "mrope_section": [1, 1, 1]
46
+ },
47
+ "inputs": {
48
+ "x": {
49
+ "dtype": "float32",
50
+ "shape": [1, 2, 12],
51
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_sectioned_rank3_input_x" } }
52
+ },
53
+ "positionIds": {
54
+ "dtype": "uint32",
55
+ "shape": [3, 1, 2],
56
+ "data": { "kind": "values", "values": [0, 1, 1, 2, 2, 3] }
57
+ },
58
+ "cos": {
59
+ "dtype": "float32",
60
+ "shape": [4, 3],
61
+ "data": { "kind": "values", "values": [1.0, 1.01, 1.02, 1.1, 1.11, 1.12, 1.2, 1.21, 1.22, 1.3, 1.31, 1.32] }
62
+ },
63
+ "sin": {
64
+ "dtype": "float32",
65
+ "shape": [4, 3],
66
+ "data": { "kind": "values", "values": [0.1, 0.11, 0.12, 0.15, 0.16, 0.17, 0.2, 0.21, 0.22, 0.25, 0.26, 0.27] }
67
+ }
68
+ },
69
+ "outputs": {
70
+ "y": {
71
+ "dtype": "float32",
72
+ "shape": [1, 2, 12],
73
+ "tolerance": 0.00001,
74
+ "data": {
75
+ "kind": "values",
76
+ "values": [0.6, 1.42, 2.34, 4.1, 5.87, 7.98, 6.0, 7.12, 8.34, 10.7, 13.49, 16.62, 11.9, 13.37, 14.94, 19.55, 23.51, 27.81, 17.6, 19.37, 21.24, 27.05, 32.03, 37.35]
77
+ }
78
+ }
79
+ }
80
+ },
81
+ {
82
+ "name": "ort_interleaved_rank4",
83
+ "provenance": {
84
+ "source": "onnxruntime/test/contrib_ops/mrotary_embedding_op_test.cc",
85
+ "test": "ContribOpMRotaryEmbeddingTest.InterleavedRank4",
86
+ "notes": "Pinned from ORT. Interleaved mrope_layout together with interleaved rotation pairing and a non-unit scale, so the two independent 'interleaved' knobs and the scale are exercised at once."
87
+ },
88
+ "attrs": {
89
+ "num_heads": 2,
90
+ "rotary_embedding_dim": 6,
91
+ "mrope_layout": 1,
92
+ "interleaved": 1,
93
+ "scale": 0.5,
94
+ "mrope_section": [1, 1, 1]
95
+ },
96
+ "inputs": {
97
+ "x": {
98
+ "dtype": "float32",
99
+ "shape": [1, 2, 2, 6],
100
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_sectioned_rank3_input_x" } }
101
+ },
102
+ "positionIds": {
103
+ "dtype": "uint32",
104
+ "shape": [3, 1, 2],
105
+ "data": { "kind": "values", "values": [0, 1, 1, 2, 2, 3] }
106
+ },
107
+ "cos": {
108
+ "dtype": "float32",
109
+ "shape": [4, 3],
110
+ "data": { "kind": "values", "values": [1.0, 1.01, 1.02, 1.1, 1.11, 1.12, 1.2, 1.21, 1.22, 1.3, 1.31, 1.32] }
111
+ },
112
+ "sin": {
113
+ "dtype": "float32",
114
+ "shape": [4, 3],
115
+ "data": { "kind": "values", "values": [0.1, 0.11, 0.12, 0.15, 0.16, 0.17, 0.2, 0.21, 0.22, 0.25, 0.26, 0.27] }
116
+ }
117
+ },
118
+ "outputs": {
119
+ "y": {
120
+ "dtype": "float32",
121
+ "shape": [1, 2, 2, 6],
122
+ "tolerance": 0.00001,
123
+ "data": {
124
+ "kind": "values",
125
+ "values": [0.4, 1.05, 1.345, 2.46, 2.39, 4.21, 3.25, 4.925, 4.395, 6.995, 5.64, 9.405, 5.8, 7.65, 7.045, 10.08, 8.39, 12.85, 8.95, 12.425, 10.395, 15.515, 11.94, 18.945]
126
+ }
127
+ }
128
+ }
129
+ },
130
+ {
131
+ "name": "rank3_layout1_interleaved0",
132
+ "provenance": {
133
+ "notes": "Interleaved mrope_layout with the split (non-interleaved) rotation pairing — the layout/pairing knobs are independent, and this is the combination neither pinned case covers."
134
+ },
135
+ "attrs": {
136
+ "num_heads": 4,
137
+ "rotary_embedding_dim": 0,
138
+ "mrope_layout": 1,
139
+ "interleaved": 0,
140
+ "scale": 1,
141
+ "mrope_section": [2, 1, 1]
142
+ },
143
+ "inputs": {
144
+ "x": { "dtype": "float32", "shape": [2, 3, 32], "data": { "kind": "linspace", "start": -2.0, "end": 2.0 } },
145
+ "positionIds": {
146
+ "dtype": "uint32",
147
+ "shape": [3, 2, 3],
148
+ "data": { "kind": "values", "values": [0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 6, 5, 6, 7] }
149
+ },
150
+ "cos": { "dtype": "float32", "shape": [8, 4], "data": { "kind": "linspace", "start": -1.0, "end": 1.0 } },
151
+ "sin": { "dtype": "float32", "shape": [8, 4], "data": { "kind": "linspace", "start": 1.0, "end": -1.0 } }
152
+ },
153
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3, 32], "tolerance": 0.00001, "relTolerance": 0.00001 } }
154
+ },
155
+ {
156
+ "name": "rank4_layout0_partial_rotary_tail",
157
+ "provenance": {
158
+ "notes": "Sectioned layout with rotary_embedding_dim < head_size, so the lanes past the rotary window copy their tail values unchanged."
159
+ },
160
+ "attrs": {
161
+ "num_heads": 2,
162
+ "rotary_embedding_dim": 8,
163
+ "mrope_layout": 0,
164
+ "interleaved": 0,
165
+ "scale": 1,
166
+ "mrope_section": [1, 2, 1]
167
+ },
168
+ "inputs": {
169
+ "x": { "dtype": "float32", "shape": [1, 2, 4, 12], "data": { "kind": "linspace", "start": -2.0, "end": 2.0 } },
170
+ "positionIds": {
171
+ "dtype": "uint32",
172
+ "shape": [3, 1, 4],
173
+ "data": { "kind": "values", "values": [0, 1, 2, 3, 1, 2, 3, 4, 2, 3, 4, 5] }
174
+ },
175
+ "cos": { "dtype": "float32", "shape": [6, 4], "data": { "kind": "linspace", "start": -1.0, "end": 1.0 } },
176
+ "sin": { "dtype": "float32", "shape": [6, 4], "data": { "kind": "linspace", "start": 1.0, "end": -1.0 } }
177
+ },
178
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 2, 4, 12], "tolerance": 0.00001, "relTolerance": 0.00001 } }
179
+ },
180
+ {
181
+ "name": "rank3_text_only_streams_collapse",
182
+ "provenance": {
183
+ "notes": "Text-only tokens set T == H == W, which ORT documents as reducing M-RoPE to standard RoPE; the section split must then become unobservable."
184
+ },
185
+ "attrs": { "num_heads": 2, "mrope_section": [2, 1, 1] },
186
+ "inputs": {
187
+ "x": { "dtype": "float32", "shape": [1, 4, 16], "data": { "kind": "linspace", "start": -2.0, "end": 2.0 } },
188
+ "positionIds": {
189
+ "dtype": "uint32",
190
+ "shape": [3, 1, 4],
191
+ "data": { "kind": "values", "values": [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] }
192
+ },
193
+ "cos": { "dtype": "float32", "shape": [8, 4], "data": { "kind": "linspace", "start": -1.0, "end": 1.0 } },
194
+ "sin": { "dtype": "float32", "shape": [8, 4], "data": { "kind": "linspace", "start": 1.0, "end": -1.0 } }
195
+ },
196
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 4, 16], "tolerance": 0.00001, "relTolerance": 0.00001 } }
197
+ },
198
+ {
199
+ "name": "f16_rank4_layout1_interleaved",
200
+ "provenance": {
201
+ "notes": "f16 storage, including the provider's T-rounded scaled-cache intermediate, on the interleaved layout and pairing."
202
+ },
203
+ "attrs": {
204
+ "rotary_embedding_dim": 0,
205
+ "mrope_layout": 1,
206
+ "interleaved": 1,
207
+ "scale": 0.3,
208
+ "mrope_section": [2, 2, 2]
209
+ },
210
+ "inputs": {
211
+ "x": { "dtype": "float16", "shape": [1, 2, 3, 12], "data": { "kind": "linspace", "start": -2.0, "end": 2.0 } },
212
+ "positionIds": {
213
+ "dtype": "uint32",
214
+ "shape": [3, 1, 3],
215
+ "data": { "kind": "values", "values": [0, 1, 2, 1, 2, 3, 2, 3, 4] }
216
+ },
217
+ "cos": { "dtype": "float16", "shape": [6, 6], "data": { "kind": "linspace", "start": -1.0, "end": 1.0 } },
218
+ "sin": { "dtype": "float16", "shape": [6, 6], "data": { "kind": "linspace", "start": 1.0, "end": -1.0 } }
219
+ },
220
+ "outputs": { "y": { "dtype": "float16", "shape": [1, 2, 3, 12], "tolerance": 0.01, "relTolerance": 0.01 } }
221
+ },
222
+ {
223
+ "name": "rank3_scale_half_sectioned",
224
+ "provenance": { "notes": "Non-unit scale on the sectioned layout; scale multiplies both gathered cos and sin." },
225
+ "attrs": {
226
+ "num_heads": 2,
227
+ "rotary_embedding_dim": 0,
228
+ "mrope_layout": 0,
229
+ "interleaved": 0,
230
+ "scale": 0.5,
231
+ "mrope_section": [1, 1, 2]
232
+ },
233
+ "inputs": {
234
+ "x": { "dtype": "float32", "shape": [2, 2, 16], "data": { "kind": "linspace", "start": -2.0, "end": 2.0 } },
235
+ "positionIds": {
236
+ "dtype": "uint32",
237
+ "shape": [3, 2, 2],
238
+ "data": { "kind": "values", "values": [0, 1, 1, 2, 2, 3, 3, 4, 4, 0, 0, 1] }
239
+ },
240
+ "cos": { "dtype": "float32", "shape": [5, 4], "data": { "kind": "linspace", "start": -1.0, "end": 1.0 } },
241
+ "sin": { "dtype": "float32", "shape": [5, 4], "data": { "kind": "linspace", "start": 1.0, "end": -1.0 } }
242
+ },
243
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 2, 16], "tolerance": 0.00001, "relTolerance": 0.00001 } }
244
+ }
245
+ ]
246
+ }