Xenova HF Staff commited on
Commit
9953d78
·
verified ·
1 Parent(s): c3bbe43

sync 91d990483a17

Browse files
README.md CHANGED
@@ -12,27 +12,27 @@ tags:
12
 
13
  ## Description
14
 
15
- Support status: the standard ONNX `SplitToSequence` operator is not implemented because this kernel ABI exposes tensors rather than an ONNX sequence value. This internal lowering copies an input into two, three, four, or six caller-shaped tensor outputs and therefore does not implement the standard `output_sequence` contract; it must not be treated as ONNX `SplitToSequence`.
16
 
17
  See the [standard ONNX `SplitToSequence` spec](https://onnx.ai/onnx/operators/onnx__SplitToSequence.html) for the contract this internal lowering does not implement.
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
- | --- | --- | --- | --- | --- | --- | --- |
23
- | `input` | `input` | `T` | — | — | The tensor to split. | required |
24
- | `split` | `split` | `S` | — | — | Length of each output slice: a scalar for uniform chunks or a 1-D tensor of per-output lengths. | optional |
25
 
26
  ## Outputs
27
 
28
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
29
  | --- | --- | --- | --- | --- | --- | --- |
30
- | `Y0` | `y0` | `T` | derived | — | First output tensor slice. | required |
31
- | `Y1` | `y1` | `T` | derived | — | Second output tensor slice. | required |
32
- | `Y2` | `y2` | `T` | derived | — | Third output tensor slice. | optional |
33
- | `Y3` | `y3` | `T` | derived | — | Fourth output tensor slice. | optional |
34
- | `Y4` | `y4` | `T` | derived | — | Fifth output tensor slice. | optional |
35
- | `Y5` | `y5` | `T` | derived | — | Sixth output tensor slice. | optional |
36
 
37
  ## Attributes
38
 
@@ -52,7 +52,7 @@ Default values (overridable per request):
52
 
53
  ## Files
54
 
55
- - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
56
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
57
  - [`test.json`](build/webgpu/test.json) — correctness cases
58
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
@@ -60,16 +60,19 @@ Default values (overridable per request):
60
 
61
  ## Use with `@huggingface/kernels`
62
 
63
- The loader automatically allocates outputs whose metadata it can derive from the manifest contract and this call.
 
 
 
 
64
 
65
- The explicit `outputs` entries provide shape and logical dtype metadata for the results listed below:
66
 
67
  - `y0`
68
  - `y1`
69
 
70
- Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
71
-
72
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
73
 
74
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
75
 
 
12
 
13
  ## Description
14
 
15
+ Internal fixed-output split lowering that returns two, three, four, or six tensors with invocation-supplied shapes. It does not construct an ONNX sequence value.
16
 
17
  See the [standard ONNX `SplitToSequence` spec](https://onnx.ai/onnx/operators/onnx__SplitToSequence.html) for the contract this internal lowering does not implement.
18
 
19
  ## Inputs
20
 
21
+ | Name | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- |
23
+ | `input` | `T` | — | — | The tensor to split. | required |
24
+ | `split` | `S` | — | — | Length of each output slice: a scalar for uniform chunks or a 1-D tensor of per-output lengths. | optional |
25
 
26
  ## Outputs
27
 
28
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
29
  | --- | --- | --- | --- | --- | --- | --- |
30
+ | `y0` | `Y0` | `T` | derived | — | First output tensor slice. | required |
31
+ | `y1` | `Y1` | `T` | derived | — | Second output tensor slice. | required |
32
+ | `y2` | `Y2` | `T` | derived | — | Third output tensor slice. | optional |
33
+ | `y3` | `Y3` | `T` | derived | — | Fourth output tensor slice. | optional |
34
+ | `y4` | `Y4` | `T` | derived | — | Fifth output tensor slice. | optional |
35
+ | `y5` | `Y5` | `T` | derived | — | Sixth output tensor slice. | optional |
36
 
37
  ## Attributes
38
 
 
52
 
53
  ## Files
54
 
55
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
56
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
57
  - [`test.json`](build/webgpu/test.json) — correctness cases
58
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
60
 
61
  ## Use with `@huggingface/kernels`
62
 
63
+ ```sh
64
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
65
+ ```
66
+
67
+ Outputs with inferable metadata are allocated automatically. Explicit `outputs` entries request optional results or provide metadata that cannot be inferred from the supplied inputs and attributes.
68
 
69
+ This example supplies explicit metadata for:
70
 
71
  - `y0`
72
  - `y1`
73
 
 
 
74
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
75
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
76
 
77
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
78
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.SplitToSequence",
3
  "cases": [
4
  {
5
  "name": "split3-1536x256-f32",
@@ -20,8 +19,7 @@
20
  "preset": "stress",
21
  "attrs": { "axis": 1, "keepdims": 0 },
22
  "inputs": {
23
- "input": { "dtype": "float32", "shape": [8, 6, 4, 4, 16, 64], "dist": "normal", "seed": 1234, "scale": 2 },
24
- "split": { "dtype": "uint32", "shape": [], "dist": "constant", "value": 1 }
25
  },
26
  "outputs": {
27
  "y0": { "dtype": "float32", "shape": [8, 4, 4, 16, 64], "dist": "empty" },
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "split3-1536x256-f32",
 
19
  "preset": "stress",
20
  "attrs": { "axis": 1, "keepdims": 0 },
21
  "inputs": {
22
+ "input": { "dtype": "float32", "shape": [8, 6, 4, 4, 16, 64], "dist": "normal", "seed": 1234, "scale": 2 }
 
23
  },
24
  "outputs": {
25
  "y0": { "dtype": "float32", "shape": [8, 4, 4, 16, 64], "dist": "empty" },
build/webgpu/manifest.json CHANGED
@@ -3,85 +3,47 @@
3
  "name": "SplitToSequence",
4
  "conformance": "internal-lowering",
5
  "sinceVersion": 11,
6
- "description": "Support status: the standard ONNX `SplitToSequence` operator is not implemented because this kernel ABI exposes tensors rather than an ONNX sequence value. This internal lowering copies an input into two, three, four, or six caller-shaped tensor outputs and therefore does not implement the standard `output_sequence` contract; it must not be treated as ONNX `SplitToSequence`.",
7
- "inputs": [
8
- { "role": "input", "dtype": "T", "description": "The tensor to split." },
9
- {
10
- "role": "split",
11
- "dtype": "S",
12
- "optional": true,
13
- "description": "Length of each output slice: a scalar for uniform chunks or a 1-D tensor of per-output lengths."
14
- }
15
- ],
16
- "outputs": [
17
- {
18
- "role": "Y0",
19
  "dtype": "T",
20
- "description": "First output tensor slice.",
21
- "rank": "ranks.input if attrs.keepdims == 1 else ranks.input - 1"
22
  },
23
- {
24
- "role": "Y1",
25
  "dtype": "T",
26
- "description": "Second output tensor slice.",
27
- "rank": "ranks.input if attrs.keepdims == 1 else ranks.input - 1"
28
  },
29
- {
30
- "role": "Y2",
31
  "dtype": "T",
32
- "optional": true,
33
- "description": "Third output tensor slice.",
34
- "rank": "ranks.input if attrs.keepdims == 1 else ranks.input - 1"
35
  },
36
- {
37
- "role": "Y3",
38
  "dtype": "T",
39
- "optional": true,
40
- "description": "Fourth output tensor slice.",
41
- "rank": "ranks.input if attrs.keepdims == 1 else ranks.input - 1"
42
- },
43
- {
44
- "role": "Y4",
45
- "dtype": "T",
46
- "optional": true,
47
- "description": "Fifth output tensor slice.",
48
- "rank": "ranks.input if attrs.keepdims == 1 else ranks.input - 1"
49
- },
50
- {
51
- "role": "Y5",
52
- "dtype": "T",
53
- "optional": true,
54
- "description": "Sixth output tensor slice.",
55
- "rank": "ranks.input if attrs.keepdims == 1 else ranks.input - 1"
56
  }
57
- ],
58
- "attributes": { "axis": 0, "keepdims": 1 },
59
- "attributeDescriptions": {
60
- "axis": "Axis along which to split; negative values count from the back. Accepted range is `[-rank, rank-1]`.",
61
- "keepdims": "Whether to keep the split dimension in the output (default `1`). Ignored when `split` is provided."
62
  },
 
63
  "attributeConstraints": { "keepdims": { "values": [0, 1] } },
64
  "typeConstraints": { "T": ["float32", "float16", "bool"], "S": ["uint32"] },
65
- "args": {
66
- "input": { "kind": "tensor", "semantic": "input", "role": "input" },
67
- "split": { "kind": "tensor", "semantic": "split", "role": "split", "required": false },
68
- "y0": { "kind": "tensor", "semantic": "Y0", "role": "output0" },
69
- "y1": { "kind": "tensor", "semantic": "Y1", "role": "output1" },
70
- "y2": { "kind": "tensor", "semantic": "Y2", "role": "output2", "required": false },
71
- "y3": { "kind": "tensor", "semantic": "Y3", "role": "output3", "required": false },
72
- "y4": { "kind": "tensor", "semantic": "Y4", "role": "output4", "required": false },
73
- "y5": { "kind": "tensor", "semantic": "Y5", "role": "output5", "required": false }
74
- },
75
- "tunables": { "WORKGROUP_SIZE": 256 },
76
  "derive": {
77
  "baseContract": "ranks.input >= 1 and (attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input) >= 0 and (attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input) < ranks.input and f16Ok(dtypes.T)",
78
- "outputRankContract": "ranks.Y0 == ranks.input if present.split else (ranks.Y0 == ranks.input if attrs.keepdims == 1 else ranks.Y0 == ranks.input - 1)",
79
- "twoOutputContract": "baseContract and not present.y2 and ranks.Y0 == ranks.Y1 and outputRankContract",
80
- "threeOutputContract": "baseContract and present.y2 and not present.y3 and ranks.Y0 == ranks.Y1 and ranks.Y0 == ranks.Y2 and outputRankContract",
81
- "fourOutputContract": "baseContract and present.y2 and present.y3 and not present.y4 and ranks.Y0 == ranks.Y1 and ranks.Y0 == ranks.Y2 and ranks.Y0 == ranks.Y3 and outputRankContract",
82
- "sixOutputContract": "baseContract and present.y2 and present.y3 and present.y4 and present.y5 and ranks.Y0 == ranks.Y1 and ranks.Y0 == ranks.Y2 and ranks.Y0 == ranks.Y3 and ranks.Y0 == ranks.Y4 and ranks.Y0 == ranks.Y5 and outputRankContract"
83
  },
84
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
85
  "variants": [
86
  {
87
  "id": "two_outputs",
@@ -90,43 +52,31 @@
90
  {
91
  "id": "main",
92
  "name": "SplitToSequence2",
93
- "source": {
94
- "shader": "split-to-sequence.wgsl.jinja",
95
- "inputs": {
96
- "inputShape": "shapes.input",
97
- "rank": "ranks.input",
98
- "axis": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input",
99
- "squeeze": "ranks.Y0 < ranks.input",
100
- "numOutputs": "2",
101
- "outShapes": ["shapes.Y0", "shapes.Y1"]
102
- }
103
  },
104
  "bindings": [
105
- {
106
- "name": "input",
107
- "arg": "input",
108
- "semantic": "input",
109
- "buffer": { "type": "read-only-storage" },
110
- "elementType": "$scalar"
111
- },
112
- { "name": "y0", "arg": "y0", "semantic": "Y0", "buffer": { "type": "storage" }, "elementType": "$scalar" },
113
- { "name": "y1", "arg": "y1", "semantic": "Y1", "buffer": { "type": "storage" }, "elementType": "$scalar" },
114
  {
115
  "name": "params",
116
- "semantic": "kernel.params",
117
- "buffer": { "type": "uniform" },
118
- "struct": {
119
- "name": "Params",
120
- "fields": [
121
- { "name": "y0Count", "type": "u32", "value": "numel(shapes.Y0)" },
122
- { "name": "y1Count", "type": "u32", "value": "numel(shapes.Y1)" }
123
- ]
124
- }
125
  }
126
  ],
127
  "dispatch": {
128
- "threads": "max(numel(shapes.Y0), numel(shapes.Y1))",
129
- "workgroupSize": "tunables.WORKGROUP_SIZE"
 
130
  }
131
  }
132
  ]
@@ -139,45 +89,33 @@
139
  {
140
  "id": "main",
141
  "name": "SplitToSequence3",
142
- "source": {
143
- "shader": "split-to-sequence.wgsl.jinja",
144
- "inputs": {
145
- "inputShape": "shapes.input",
146
- "rank": "ranks.input",
147
- "axis": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input",
148
- "squeeze": "ranks.Y0 < ranks.input",
149
- "numOutputs": "3",
150
- "outShapes": ["shapes.Y0", "shapes.Y1", "shapes.Y2"]
151
- }
152
  },
153
  "bindings": [
154
- {
155
- "name": "input",
156
- "arg": "input",
157
- "semantic": "input",
158
- "buffer": { "type": "read-only-storage" },
159
- "elementType": "$scalar"
160
- },
161
- { "name": "y0", "arg": "y0", "semantic": "Y0", "buffer": { "type": "storage" }, "elementType": "$scalar" },
162
- { "name": "y1", "arg": "y1", "semantic": "Y1", "buffer": { "type": "storage" }, "elementType": "$scalar" },
163
- { "name": "y2", "arg": "y2", "semantic": "Y2", "buffer": { "type": "storage" }, "elementType": "$scalar" },
164
  {
165
  "name": "params",
166
- "semantic": "kernel.params",
167
- "buffer": { "type": "uniform" },
168
- "struct": {
169
- "name": "Params",
170
- "fields": [
171
- { "name": "y0Count", "type": "u32", "value": "numel(shapes.Y0)" },
172
- { "name": "y1Count", "type": "u32", "value": "numel(shapes.Y1)" },
173
- { "name": "y2Count", "type": "u32", "value": "numel(shapes.Y2)" }
174
- ]
175
- }
176
  }
177
  ],
178
  "dispatch": {
179
- "threads": "max(max(numel(shapes.Y0), numel(shapes.Y1)), numel(shapes.Y2))",
180
- "workgroupSize": "tunables.WORKGROUP_SIZE"
 
181
  }
182
  }
183
  ]
@@ -190,47 +128,35 @@
190
  {
191
  "id": "main",
192
  "name": "SplitToSequence4",
193
- "source": {
194
- "shader": "split-to-sequence.wgsl.jinja",
195
- "inputs": {
196
- "inputShape": "shapes.input",
197
- "rank": "ranks.input",
198
- "axis": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input",
199
- "squeeze": "ranks.Y0 < ranks.input",
200
- "numOutputs": "4",
201
- "outShapes": ["shapes.Y0", "shapes.Y1", "shapes.Y2", "shapes.Y3"]
202
- }
203
  },
204
  "bindings": [
205
- {
206
- "name": "input",
207
- "arg": "input",
208
- "semantic": "input",
209
- "buffer": { "type": "read-only-storage" },
210
- "elementType": "$scalar"
211
- },
212
- { "name": "y0", "arg": "y0", "semantic": "Y0", "buffer": { "type": "storage" }, "elementType": "$scalar" },
213
- { "name": "y1", "arg": "y1", "semantic": "Y1", "buffer": { "type": "storage" }, "elementType": "$scalar" },
214
- { "name": "y2", "arg": "y2", "semantic": "Y2", "buffer": { "type": "storage" }, "elementType": "$scalar" },
215
- { "name": "y3", "arg": "y3", "semantic": "Y3", "buffer": { "type": "storage" }, "elementType": "$scalar" },
216
  {
217
  "name": "params",
218
- "semantic": "kernel.params",
219
- "buffer": { "type": "uniform" },
220
- "struct": {
221
- "name": "Params",
222
- "fields": [
223
- { "name": "y0Count", "type": "u32", "value": "numel(shapes.Y0)" },
224
- { "name": "y1Count", "type": "u32", "value": "numel(shapes.Y1)" },
225
- { "name": "y2Count", "type": "u32", "value": "numel(shapes.Y2)" },
226
- { "name": "y3Count", "type": "u32", "value": "numel(shapes.Y3)" }
227
- ]
228
- }
229
  }
230
  ],
231
  "dispatch": {
232
- "threads": "max(max(max(numel(shapes.Y0), numel(shapes.Y1)), numel(shapes.Y2)), numel(shapes.Y3))",
233
- "workgroupSize": "tunables.WORKGROUP_SIZE"
 
234
  }
235
  }
236
  ]
@@ -243,51 +169,39 @@
243
  {
244
  "id": "main",
245
  "name": "SplitToSequence6",
246
- "source": {
247
- "shader": "split-to-sequence.wgsl.jinja",
248
- "inputs": {
249
- "inputShape": "shapes.input",
250
- "rank": "ranks.input",
251
- "axis": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input",
252
- "squeeze": "ranks.Y0 < ranks.input",
253
- "numOutputs": "6",
254
- "outShapes": ["shapes.Y0", "shapes.Y1", "shapes.Y2", "shapes.Y3", "shapes.Y4", "shapes.Y5"]
255
- }
256
  },
257
  "bindings": [
258
- {
259
- "name": "input",
260
- "arg": "input",
261
- "semantic": "input",
262
- "buffer": { "type": "read-only-storage" },
263
- "elementType": "$scalar"
264
- },
265
- { "name": "y0", "arg": "y0", "semantic": "Y0", "buffer": { "type": "storage" }, "elementType": "$scalar" },
266
- { "name": "y1", "arg": "y1", "semantic": "Y1", "buffer": { "type": "storage" }, "elementType": "$scalar" },
267
- { "name": "y2", "arg": "y2", "semantic": "Y2", "buffer": { "type": "storage" }, "elementType": "$scalar" },
268
- { "name": "y3", "arg": "y3", "semantic": "Y3", "buffer": { "type": "storage" }, "elementType": "$scalar" },
269
- { "name": "y4", "arg": "y4", "semantic": "Y4", "buffer": { "type": "storage" }, "elementType": "$scalar" },
270
- { "name": "y5", "arg": "y5", "semantic": "Y5", "buffer": { "type": "storage" }, "elementType": "$scalar" },
271
  {
272
  "name": "params",
273
- "semantic": "kernel.params",
274
- "buffer": { "type": "uniform" },
275
- "struct": {
276
- "name": "Params",
277
- "fields": [
278
- { "name": "y0Count", "type": "u32", "value": "numel(shapes.Y0)" },
279
- { "name": "y1Count", "type": "u32", "value": "numel(shapes.Y1)" },
280
- { "name": "y2Count", "type": "u32", "value": "numel(shapes.Y2)" },
281
- { "name": "y3Count", "type": "u32", "value": "numel(shapes.Y3)" },
282
- { "name": "y4Count", "type": "u32", "value": "numel(shapes.Y4)" },
283
- { "name": "y5Count", "type": "u32", "value": "numel(shapes.Y5)" }
284
- ]
285
- }
286
  }
287
  ],
288
  "dispatch": {
289
- "threads": "max(max(max(max(max(numel(shapes.Y0), numel(shapes.Y1)), numel(shapes.Y2)), numel(shapes.Y3)), numel(shapes.Y4)), numel(shapes.Y5))",
290
- "workgroupSize": "tunables.WORKGROUP_SIZE"
 
291
  }
292
  }
293
  ]
 
3
  "name": "SplitToSequence",
4
  "conformance": "internal-lowering",
5
  "sinceVersion": 11,
6
+ "inputs": { "input": { "dtype": "T" }, "split": { "dtype": "S", "optional": true } },
7
+ "outputs": {
8
+ "y0": { "onnx": "Y0", "dtype": "T", "rank": "ranks.input if attrs.keepdims == 1 else ranks.input - 1" },
9
+ "y1": { "onnx": "Y1", "dtype": "T", "rank": "ranks.input if attrs.keepdims == 1 else ranks.input - 1" },
10
+ "y2": {
11
+ "onnx": "Y2",
 
 
 
 
 
 
 
12
  "dtype": "T",
13
+ "rank": "ranks.input if attrs.keepdims == 1 else ranks.input - 1",
14
+ "optional": true
15
  },
16
+ "y3": {
17
+ "onnx": "Y3",
18
  "dtype": "T",
19
+ "rank": "ranks.input if attrs.keepdims == 1 else ranks.input - 1",
20
+ "optional": true
21
  },
22
+ "y4": {
23
+ "onnx": "Y4",
24
  "dtype": "T",
25
+ "rank": "ranks.input if attrs.keepdims == 1 else ranks.input - 1",
26
+ "optional": true
 
27
  },
28
+ "y5": {
29
+ "onnx": "Y5",
30
  "dtype": "T",
31
+ "rank": "ranks.input if attrs.keepdims == 1 else ranks.input - 1",
32
+ "optional": true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
 
 
 
 
 
34
  },
35
+ "attributes": { "axis": { "default": 0 }, "keepdims": { "default": 1 } },
36
  "attributeConstraints": { "keepdims": { "values": [0, 1] } },
37
  "typeConstraints": { "T": ["float32", "float16", "bool"], "S": ["uint32"] },
38
+ "tunables": { "WORKGROUP_SIZE": { "default": 256 } },
 
 
 
 
 
 
 
 
 
 
39
  "derive": {
40
  "baseContract": "ranks.input >= 1 and (attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input) >= 0 and (attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input) < ranks.input and f16Ok(dtypes.T)",
41
+ "outputRankContract": "ranks.y0 == ranks.input if present.split else (ranks.y0 == ranks.input if attrs.keepdims == 1 else ranks.y0 == ranks.input - 1)",
42
+ "twoOutputContract": "baseContract and not present.y2 and ranks.y0 == ranks.y1 and outputRankContract",
43
+ "threeOutputContract": "baseContract and present.y2 and not present.y3 and ranks.y0 == ranks.y1 and ranks.y0 == ranks.y2 and outputRankContract",
44
+ "fourOutputContract": "baseContract and present.y2 and present.y3 and not present.y4 and ranks.y0 == ranks.y1 and ranks.y0 == ranks.y2 and ranks.y0 == ranks.y3 and outputRankContract",
45
+ "sixOutputContract": "baseContract and present.y2 and present.y3 and present.y4 and present.y5 and ranks.y0 == ranks.y1 and ranks.y0 == ranks.y2 and ranks.y0 == ranks.y3 and ranks.y0 == ranks.y4 and ranks.y0 == ranks.y5 and outputRankContract"
46
  },
 
47
  "variants": [
48
  {
49
  "id": "two_outputs",
 
52
  {
53
  "id": "main",
54
  "name": "SplitToSequence2",
55
+ "shader": "split-to-sequence.wgsl.jinja",
56
+ "derive": {
57
+ "inputShape": "shapes.input",
58
+ "rank": "ranks.input",
59
+ "axisSpec": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input",
60
+ "squeeze": "ranks.y0 < ranks.input",
61
+ "numOutputs": "2",
62
+ "outShapes": ["shapes.y0", "shapes.y1"]
 
 
63
  },
64
  "bindings": [
65
+ "input",
66
+ "y0",
67
+ "y1",
 
 
 
 
 
 
68
  {
69
  "name": "params",
70
+ "struct": [
71
+ { "name": "y0Count", "type": "u32", "value": "numel(shapes.y0)" },
72
+ { "name": "y1Count", "type": "u32", "value": "numel(shapes.y1)" }
73
+ ]
 
 
 
 
 
74
  }
75
  ],
76
  "dispatch": {
77
+ "x": "min(ceilDiv((max(numel(shapes.y0), numel(shapes.y1))), (tunables.WORKGROUP_SIZE)), 65535)",
78
+ "y": "ceilDiv(ceilDiv((max(numel(shapes.y0), numel(shapes.y1))), (tunables.WORKGROUP_SIZE)), 65535)",
79
+ "z": 1
80
  }
81
  }
82
  ]
 
89
  {
90
  "id": "main",
91
  "name": "SplitToSequence3",
92
+ "shader": "split-to-sequence.wgsl.jinja",
93
+ "derive": {
94
+ "inputShape": "shapes.input",
95
+ "rank": "ranks.input",
96
+ "axisSpec": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input",
97
+ "squeeze": "ranks.y0 < ranks.input",
98
+ "numOutputs": "3",
99
+ "outShapes": ["shapes.y0", "shapes.y1", "shapes.y2"]
 
 
100
  },
101
  "bindings": [
102
+ "input",
103
+ "y0",
104
+ "y1",
105
+ "y2",
 
 
 
 
 
 
106
  {
107
  "name": "params",
108
+ "struct": [
109
+ { "name": "y0Count", "type": "u32", "value": "numel(shapes.y0)" },
110
+ { "name": "y1Count", "type": "u32", "value": "numel(shapes.y1)" },
111
+ { "name": "y2Count", "type": "u32", "value": "numel(shapes.y2)" }
112
+ ]
 
 
 
 
 
113
  }
114
  ],
115
  "dispatch": {
116
+ "x": "min(ceilDiv((max(max(numel(shapes.y0), numel(shapes.y1)), numel(shapes.y2))), (tunables.WORKGROUP_SIZE)), 65535)",
117
+ "y": "ceilDiv(ceilDiv((max(max(numel(shapes.y0), numel(shapes.y1)), numel(shapes.y2))), (tunables.WORKGROUP_SIZE)), 65535)",
118
+ "z": 1
119
  }
120
  }
121
  ]
 
128
  {
129
  "id": "main",
130
  "name": "SplitToSequence4",
131
+ "shader": "split-to-sequence.wgsl.jinja",
132
+ "derive": {
133
+ "inputShape": "shapes.input",
134
+ "rank": "ranks.input",
135
+ "axisSpec": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input",
136
+ "squeeze": "ranks.y0 < ranks.input",
137
+ "numOutputs": "4",
138
+ "outShapes": ["shapes.y0", "shapes.y1", "shapes.y2", "shapes.y3"]
 
 
139
  },
140
  "bindings": [
141
+ "input",
142
+ "y0",
143
+ "y1",
144
+ "y2",
145
+ "y3",
 
 
 
 
 
 
146
  {
147
  "name": "params",
148
+ "struct": [
149
+ { "name": "y0Count", "type": "u32", "value": "numel(shapes.y0)" },
150
+ { "name": "y1Count", "type": "u32", "value": "numel(shapes.y1)" },
151
+ { "name": "y2Count", "type": "u32", "value": "numel(shapes.y2)" },
152
+ { "name": "y3Count", "type": "u32", "value": "numel(shapes.y3)" }
153
+ ]
 
 
 
 
 
154
  }
155
  ],
156
  "dispatch": {
157
+ "x": "min(ceilDiv((max(max(max(numel(shapes.y0), numel(shapes.y1)), numel(shapes.y2)), numel(shapes.y3))), (tunables.WORKGROUP_SIZE)), 65535)",
158
+ "y": "ceilDiv(ceilDiv((max(max(max(numel(shapes.y0), numel(shapes.y1)), numel(shapes.y2)), numel(shapes.y3))), (tunables.WORKGROUP_SIZE)), 65535)",
159
+ "z": 1
160
  }
161
  }
162
  ]
 
169
  {
170
  "id": "main",
171
  "name": "SplitToSequence6",
172
+ "shader": "split-to-sequence.wgsl.jinja",
173
+ "derive": {
174
+ "inputShape": "shapes.input",
175
+ "rank": "ranks.input",
176
+ "axisSpec": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input",
177
+ "squeeze": "ranks.y0 < ranks.input",
178
+ "numOutputs": "6",
179
+ "outShapes": ["shapes.y0", "shapes.y1", "shapes.y2", "shapes.y3", "shapes.y4", "shapes.y5"]
 
 
180
  },
181
  "bindings": [
182
+ "input",
183
+ "y0",
184
+ "y1",
185
+ "y2",
186
+ "y3",
187
+ "y4",
188
+ "y5",
 
 
 
 
 
 
189
  {
190
  "name": "params",
191
+ "struct": [
192
+ { "name": "y0Count", "type": "u32", "value": "numel(shapes.y0)" },
193
+ { "name": "y1Count", "type": "u32", "value": "numel(shapes.y1)" },
194
+ { "name": "y2Count", "type": "u32", "value": "numel(shapes.y2)" },
195
+ { "name": "y3Count", "type": "u32", "value": "numel(shapes.y3)" },
196
+ { "name": "y4Count", "type": "u32", "value": "numel(shapes.y4)" },
197
+ { "name": "y5Count", "type": "u32", "value": "numel(shapes.y5)" }
198
+ ]
 
 
 
 
 
199
  }
200
  ],
201
  "dispatch": {
202
+ "x": "min(ceilDiv((max(max(max(max(max(numel(shapes.y0), numel(shapes.y1)), numel(shapes.y2)), numel(shapes.y3)), numel(shapes.y4)), numel(shapes.y5))), (tunables.WORKGROUP_SIZE)), 65535)",
203
+ "y": "ceilDiv(ceilDiv((max(max(max(max(max(numel(shapes.y0), numel(shapes.y1)), numel(shapes.y2)), numel(shapes.y3)), numel(shapes.y4)), numel(shapes.y5))), (tunables.WORKGROUP_SIZE)), 65535)",
204
+ "z": 1
205
  }
206
  }
207
  ]
build/webgpu/metadata.json CHANGED
@@ -1,18 +1,26 @@
1
  {
2
  "name": "ai.onnx.SplitToSequence",
3
- "id": "_ai_onnx_splittosequence_webgpu_e21e7a0",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "EENkbgxvL5j8em9SHo+NktW1UwzMESrX+8p/FKNCoQc=",
11
- "manifest.json": "bKxHYk9Csq9b7FX6sH6WaiOZyBawG9Om33PdtCe3kKs=",
12
- "split-to-sequence.wgsl.jinja": "LTXzbRgOGFK2BVTVM42WPJ9DWr+mx8PcoY9jqf4KBUc=",
13
- "test.json": "r6D3suRTZe+ljNzJvTQu6Le3qDRRZ6RR68+ObgTAIMY="
14
  }
15
  },
16
- "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
17
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.SplitToSequence" }
 
 
 
 
 
 
 
 
18
  }
 
1
  {
2
  "name": "ai.onnx.SplitToSequence",
3
+ "id": "_ai_onnx_splittosequence_webgpu_915cb2a",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "Zs84ZKi7bfjFdmtbBi7218N/ixgvCEbOJMXn6YS5yn4=",
11
+ "manifest.json": "KLNBWHNVH08iKjw2HfJ33n1lHvbg3fmdzBcp6ioyvgk=",
12
+ "split-to-sequence.wgsl.jinja": "RfgQQlUXADMjM2fTdCz0/SSpd5x5CMfNeIjywANBVrM=",
13
+ "test.json": "FAy7gFEfxoFsB44fhBjHbN4cWZYYHKlVqz09kYcJMr0="
14
  }
15
  },
16
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
17
+ "webgpu": {
18
+ "manifestSpec": "2.0",
19
+ "variants": {
20
+ "two_outputs": ["split-to-sequence.wgsl.jinja"],
21
+ "three_outputs": ["split-to-sequence.wgsl.jinja"],
22
+ "four_outputs": ["split-to-sequence.wgsl.jinja"],
23
+ "six_outputs": ["split-to-sequence.wgsl.jinja"]
24
+ }
25
+ }
26
  }
build/webgpu/split-to-sequence.wgsl.jinja CHANGED
@@ -1,33 +1,30 @@
1
- {% if usesF16 %}
2
- enable f16;
3
- {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
5
 
6
- // SplitToSequence moves contiguous slices along the split axis; the output
7
- // tensors form the sequence. For size-one slices with
8
  // keepdims=0, each output drops the axis dimension. The copy reconstructs the
9
  // full input coordinate by reinserting that axis position.
10
- {% set R = source.rank %}
11
- {% set ax = source.axis %}
12
  {% set acc = namespace(offset=0) %}
13
- {% for k in range(source.numOutputs) %}
14
- {% set oShape = source.outShapes[k] %}
15
  fn input_offset_y{{ k }}(out_index: u32) -> u32 {
16
  var rem = out_index;
17
  var offset = 0u;
18
- {% if source.squeeze %}
19
  {% for d in range(R - 1) %}
20
  {% set out_stride = namespace(value=1) %}
21
  {% for j in range(d + 1, R - 1) %}{% set out_stride.value = out_stride.value * oShape[j] %}{% endfor %}
22
  {% set in_dim = d if d < ax else d + 1 %}
23
  {% set in_stride = namespace(value=1) %}
24
- {% for j in range(in_dim + 1, R) %}{% set in_stride.value = in_stride.value * source.inputShape[j] %}{% endfor %}
25
  let c{{ d }} = rem / {{ out_stride.value if out_stride.value > 0 else 1 }}u;
26
  rem = rem % {{ out_stride.value if out_stride.value > 0 else 1 }}u;
27
  offset = offset + c{{ d }} * {{ in_stride.value }}u;
28
  {% endfor %}
29
  {% set axis_stride = namespace(value=1) %}
30
- {% for j in range(ax + 1, R) %}{% set axis_stride.value = axis_stride.value * source.inputShape[j] %}{% endfor %}
31
  offset = offset + {{ acc.offset }}u * {{ axis_stride.value }}u;
32
  {% set acc.offset = acc.offset + 1 %}
33
  {% else %}
@@ -35,7 +32,7 @@ fn input_offset_y{{ k }}(out_index: u32) -> u32 {
35
  {% set out_stride = namespace(value=1) %}
36
  {% for j in range(d + 1, R) %}{% set out_stride.value = out_stride.value * oShape[j] %}{% endfor %}
37
  {% set in_stride = namespace(value=1) %}
38
- {% for j in range(d + 1, R) %}{% set in_stride.value = in_stride.value * source.inputShape[j] %}{% endfor %}
39
  var c{{ d }} = rem / {{ out_stride.value if out_stride.value > 0 else 1 }}u;
40
  rem = rem % {{ out_stride.value if out_stride.value > 0 else 1 }}u;
41
  {% if d == ax %}
@@ -50,11 +47,11 @@ fn input_offset_y{{ k }}(out_index: u32) -> u32 {
50
  {% endfor %}
51
 
52
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
53
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
54
  // 2D-folded flat index: gid.y carries the high bits past the
55
- // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
56
- let i = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
57
- {% for k in range(source.numOutputs) %}
58
  if (i < params.y{{ k }}Count) {
59
  y{{ k }}[i] = input[input_offset_y{{ k }}(i)];
60
  }
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
+ // This internal lowering moves contiguous slices along the split axis into
4
+ // a fixed set of output tensors. For size-one slices with
5
  // keepdims=0, each output drops the axis dimension. The copy reconstructs the
6
  // full input coordinate by reinserting that axis position.
7
+ {% set R = rank %}
8
+ {% set ax = axisSpec %}
9
  {% set acc = namespace(offset=0) %}
10
+ {% for k in range(numOutputs) %}
11
+ {% set oShape = outShapes[k] %}
12
  fn input_offset_y{{ k }}(out_index: u32) -> u32 {
13
  var rem = out_index;
14
  var offset = 0u;
15
+ {% if squeeze %}
16
  {% for d in range(R - 1) %}
17
  {% set out_stride = namespace(value=1) %}
18
  {% for j in range(d + 1, R - 1) %}{% set out_stride.value = out_stride.value * oShape[j] %}{% endfor %}
19
  {% set in_dim = d if d < ax else d + 1 %}
20
  {% set in_stride = namespace(value=1) %}
21
+ {% for j in range(in_dim + 1, R) %}{% set in_stride.value = in_stride.value * inputShape[j] %}{% endfor %}
22
  let c{{ d }} = rem / {{ out_stride.value if out_stride.value > 0 else 1 }}u;
23
  rem = rem % {{ out_stride.value if out_stride.value > 0 else 1 }}u;
24
  offset = offset + c{{ d }} * {{ in_stride.value }}u;
25
  {% endfor %}
26
  {% set axis_stride = namespace(value=1) %}
27
+ {% for j in range(ax + 1, R) %}{% set axis_stride.value = axis_stride.value * inputShape[j] %}{% endfor %}
28
  offset = offset + {{ acc.offset }}u * {{ axis_stride.value }}u;
29
  {% set acc.offset = acc.offset + 1 %}
30
  {% else %}
 
32
  {% set out_stride = namespace(value=1) %}
33
  {% for j in range(d + 1, R) %}{% set out_stride.value = out_stride.value * oShape[j] %}{% endfor %}
34
  {% set in_stride = namespace(value=1) %}
35
+ {% for j in range(d + 1, R) %}{% set in_stride.value = in_stride.value * inputShape[j] %}{% endfor %}
36
  var c{{ d }} = rem / {{ out_stride.value if out_stride.value > 0 else 1 }}u;
37
  rem = rem % {{ out_stride.value if out_stride.value > 0 else 1 }}u;
38
  {% if d == ax %}
 
47
  {% endfor %}
48
 
49
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
50
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
51
  // 2D-folded flat index: gid.y carries the high bits past the
52
+ // per-axis dispatch fold width (outputs > 16.7M elements).
53
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
54
+ {% for k in range(numOutputs) %}
55
  if (i < params.y{{ k }}Count) {
56
  y{{ k }}[i] = input[input_offset_y{{ k }}(i)];
57
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.SplitToSequence",
3
  "fixtureArrays": {
4
  "ort_positive_axis_scalar_split_rank3_input_input": [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
  "onnx_backend_split_scalar_axis1_three_outputs_input_input": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
@@ -303,7 +302,7 @@
303
  "provenance": {
304
  "source": "onnxruntime/test/providers/cpu/sequence/sequence_ops_test.cc",
305
  "test": "SequenceOpsTest.SplitToSequence_Axis0DefaultSplitFloatSetAxisExplicitly",
306
- "notes": "Valid ONNX sequence output modelled as four fixed tensors."
307
  },
308
  "attrs": { "axis": 0 },
309
  "inputs": {
@@ -345,7 +344,7 @@
345
  "provenance": {
346
  "source": "onnxruntime/test/providers/cpu/sequence/sequence_ops_test.cc",
347
  "test": "SequenceOpsTest.SplitToSequence_BoolSplit",
348
- "notes": "Valid ONNX bool sequence output modelled as four fixed tensors."
349
  },
350
  "attrs": { "axis": 0 },
351
  "inputs": {
@@ -363,7 +362,7 @@
363
  "provenance": {
364
  "source": "onnxruntime/test/providers/cpu/sequence/sequence_ops_test.cc",
365
  "test": "SequenceOpsTest.SplitToSequence_PositiveAxisDontKeepDims",
366
- "notes": "Valid ONNX keepdims=0 split along a non-leading axis, modelled as four fixed tensors."
367
  },
368
  "attrs": { "axis": 2, "keepdims": 0 },
369
  "inputs": {
@@ -473,7 +472,7 @@
473
  {
474
  "name": "two_outputs_zero_dim",
475
  "provenance": {
476
- "notes": "Two-chunk split of a tensor with a zero-width trailing axis. The two-output kernel floors each chunk stride at one so a zero-width output cannot divide by zero; the existing zero-dim case splits into three chunks, which is a different kernel."
477
  },
478
  "attrs": { "axis": 0 },
479
  "inputs": {
@@ -488,7 +487,7 @@
488
  {
489
  "name": "two_outputs_zero_dim_squeeze",
490
  "provenance": {
491
- "notes": "Squeezed twin of the case above: keepdims=0 drops the split axis, which is a separate coordinate walk with its own chunk-stride floor. The zero-width trailing axis makes that floor fire."
492
  },
493
  "attrs": { "axis": 0, "keepdims": 0 },
494
  "inputs": { "input": { "dtype": "float32", "shape": [2, 3, 0], "data": { "kind": "values", "values": [] } } },
 
1
  {
 
2
  "fixtureArrays": {
3
  "ort_positive_axis_scalar_split_rank3_input_input": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24],
4
  "onnx_backend_split_scalar_axis1_three_outputs_input_input": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
 
302
  "provenance": {
303
  "source": "onnxruntime/test/providers/cpu/sequence/sequence_ops_test.cc",
304
  "test": "SequenceOpsTest.SplitToSequence_Axis0DefaultSplitFloatSetAxisExplicitly",
305
+ "notes": "The package's fixed-output lowering maps the four sequence elements to tensor outputs y0 through y3."
306
  },
307
  "attrs": { "axis": 0 },
308
  "inputs": {
 
344
  "provenance": {
345
  "source": "onnxruntime/test/providers/cpu/sequence/sequence_ops_test.cc",
346
  "test": "SequenceOpsTest.SplitToSequence_BoolSplit",
347
+ "notes": "The package's fixed-output lowering maps the four boolean sequence elements to tensor outputs y0 through y3."
348
  },
349
  "attrs": { "axis": 0 },
350
  "inputs": {
 
362
  "provenance": {
363
  "source": "onnxruntime/test/providers/cpu/sequence/sequence_ops_test.cc",
364
  "test": "SequenceOpsTest.SplitToSequence_PositiveAxisDontKeepDims",
365
+ "notes": "With keepdims=0, splitting axis 2 removes that axis from each chunk; the fixed-output lowering maps the four chunks to y0 through y3."
366
  },
367
  "attrs": { "axis": 2, "keepdims": 0 },
368
  "inputs": {
 
472
  {
473
  "name": "two_outputs_zero_dim",
474
  "provenance": {
475
+ "notes": "Splitting shape [4,0] along axis 0 produces two fixed tensor outputs of shape [2,0]. Zero-width chunks must not cause division by zero during offset calculation."
476
  },
477
  "attrs": { "axis": 0 },
478
  "inputs": {
 
487
  {
488
  "name": "two_outputs_zero_dim_squeeze",
489
  "provenance": {
490
+ "notes": "With keepdims=0, each fixed output drops the split axis and uses the squeezed coordinate walk. A zero-width trailing axis exercises the chunk-stride floor."
491
  },
492
  "attrs": { "axis": 0, "keepdims": 0 },
493
  "inputs": { "input": { "dtype": "float32", "shape": [2, 3, 0], "data": { "kind": "values", "values": [] } } },