Xenova HF Staff commited on
Commit
0f5881e
·
verified ·
1 Parent(s): 854303d

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,20 +18,20 @@ See the [ONNX `Split` spec](https://onnx.ai/onnx/operators/onnx__Split.html) for
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
22
- | --- | --- | --- | --- | --- | --- | --- | --- |
23
- | `input` | `input` | `T` | runtime-selected; narrow integers and bool use 32-bit slots | — | — | The tensor to split. | required |
24
- | `split` | `split` | `S` | `uint32` | `1` | — | Optional logical int64 1-D tensor specifying the size of each output along the split axis; values must be non-negative, sum to the axis dimension, and use uint32 WebGPU storage. | optional |
25
 
26
  ## Outputs
27
 
28
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
29
  | --- | --- | --- | --- | --- | --- | --- |
30
- | `Y0` | `y0` | `T` | same as `input` | — | First output slice after splitting. | required |
31
- | `Y1` | `y1` | `T` | same as `input` | — | Second output slice after splitting (optional). | optional |
32
- | `Y2` | `y2` | `T` | same as `input` | — | Third output slice after splitting. | optional |
33
- | `Y3` | `y3` | `T` | same as `input` | — | Fourth output slice after splitting. | optional |
34
- | `Y4` | `y4` | `T` | same as `input` | — | Fifth output slice after splitting. | optional |
35
 
36
  ## Attributes
37
 
@@ -51,7 +51,7 @@ Attributes and default values (overridable per request):
51
 
52
  ## Files
53
 
54
- - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
55
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
56
  - [`test.json`](build/webgpu/test.json) — correctness cases
57
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
@@ -61,17 +61,20 @@ Attributes and default values (overridable per request):
61
 
62
  ## Use with `@huggingface/kernels`
63
 
64
- The loader automatically allocates outputs whose metadata it can derive from the manifest contract and this call.
 
 
 
 
65
 
66
- The explicit `outputs` entries provide shape and logical dtype metadata for the results listed below:
67
 
68
  - `y0`
69
  - `y1`
70
  - `y2`
71
 
72
- Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
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
 
76
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
77
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `input` | `T` | runtime-selected; narrow integers and bool use 32-bit slots | — | — | The tensor to split. | required |
24
+ | `split` | `S` | `uint32` | `1` | — | Optional logical int64 1-D tensor specifying the size of each output along the split axis; values must be non-negative, sum to the axis dimension, and use uint32 WebGPU storage. | optional |
25
 
26
  ## Outputs
27
 
28
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
29
  | --- | --- | --- | --- | --- | --- | --- |
30
+ | `y0` | `Y0` | `T` | same as `input` | — | First output slice after splitting. | required |
31
+ | `y1` | `Y1` | `T` | same as `input` | — | Second output slice after splitting (optional). | optional |
32
+ | `y2` | `Y2` | `T` | same as `input` | — | Third output slice after splitting. | optional |
33
+ | `y3` | `Y3` | `T` | same as `input` | — | Fourth output slice after splitting. | optional |
34
+ | `y4` | `Y4` | `T` | same as `input` | — | Fifth output slice after splitting. | optional |
35
 
36
  ## Attributes
37
 
 
51
 
52
  ## Files
53
 
54
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
55
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
56
  - [`test.json`](build/webgpu/test.json) — correctness cases
57
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
61
 
62
  ## Use with `@huggingface/kernels`
63
 
64
+ ```sh
65
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
66
+ ```
67
+
68
+ 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.
69
 
70
+ This example supplies explicit metadata for:
71
 
72
  - `y0`
73
  - `y1`
74
  - `y2`
75
 
 
 
76
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
77
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
78
 
79
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
80
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.Split",
3
  "cases": [
4
  {
5
  "name": "qkv_like",
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "qkv_like",
build/webgpu/datamove-flat-copy.wgsl.jinja CHANGED
@@ -1,9 +1,6 @@
1
- {% if usesF16 %}
2
- enable f16;
3
- {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
5
 
6
- const COUNT: u32 = {{ source.count }}u;
7
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
8
 
9
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
+ const COUNT: u32 = {{ count }}u;
4
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
5
 
6
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
build/webgpu/datamove-split-block.wgsl.jinja CHANGED
@@ -3,35 +3,32 @@
3
  // divisible by four, all run boundaries are vec4-aligned and the input and
4
  // every output bind as vec4<T>. One division and one remainder per element
5
  // replace the per-axis coordinate decomposition of the scalar fallback.
6
- {% if usesF16 %}
7
- enable f16;
8
- {% endif %}
9
  {{ env.wgsl.resourceDeclarations }}
10
 
11
  {% set inner = namespace(value=1) %}
12
- {% for j in range(source.axis + 1, source.rank) %}
13
- {% set inner.value = inner.value * source.inputShape[j] %}
14
  {% endfor %}
15
- {% set y2Shape = source.y2Shape | default([]) %}
16
- {% if source.scalarBoundX4 %}
17
  // Scalar-bound x4 mode keeps arbitrary split boundaries legal. Each invocation
18
  // owns one four-scalar group within one outer slice, so a group never crosses
19
  // into the next slice; only the final group of a run needs scalar tail guards.
20
- {% set inner_in_scalar = source.inputShape[source.axis] * inner.value %}
21
- {% set run0_scalar = source.y0Shape[source.axis] * inner.value %}
22
- {% set run1_scalar = source.y1Shape[source.axis] * inner.value %}
23
  {% set groups0 = (((run0_scalar + 3) / 4) | int) %}
24
  {% set groups1 = (((run1_scalar + 3) / 4) | int) %}
25
  {% set outer_count = namespace(value=1) %}
26
- {% for j in range(source.axis) %}
27
- {% set outer_count.value = outer_count.value * source.inputShape[j] %}
28
  {% endfor %}
29
  {% set count0_groups = outer_count.value * groups0 %}
30
  {% set count1_groups = outer_count.value * groups1 %}
31
  {% set max_groups = namespace(value=count0_groups) %}
32
  {% if count1_groups > max_groups.value %}{% set max_groups.value = count1_groups %}{% endif %}
33
  {% set start1_scalar = run0_scalar %}
34
- {% set run2_scalar = y2Shape[source.axis] * inner.value %}
35
  {% set groups2 = (((run2_scalar + 3) / 4) | int) %}
36
  {% set count2_groups = outer_count.value * groups2 %}
37
  {% set start2_scalar = start1_scalar + run1_scalar %}
@@ -69,36 +66,36 @@ fn main(
69
  }
70
  }
71
  {% else %}
72
- {% set inner_in = (((source.inputShape[source.axis] * inner.value) / 4) | int) %}
73
- {% set run0 = (((source.y0Shape[source.axis] * inner.value) / 4) | int) %}
74
- {% set run1 = (((source.y1Shape[source.axis] * inner.value) / 4) | int) %}
75
  {% set has_nonempty_run = namespace(value=(run0 > 0 or run1 > 0)) %}
76
  {% set start1 = run0 %}
77
  {% set count0 = namespace(value=run0) %}
78
  {% set count1 = namespace(value=run1) %}
79
- {% for j in range(source.axis) %}
80
- {% set count0.value = count0.value * source.inputShape[j] %}
81
- {% set count1.value = count1.value * source.inputShape[j] %}
82
  {% endfor %}
83
  {% set max_count = namespace(value=count0.value) %}
84
  {% if count1.value > max_count.value %}{% set max_count.value = count1.value %}{% endif %}
85
- {% if source.outputCount >= 3 %}
86
- {% set run2 = (((y2Shape[source.axis] * inner.value) / 4) | int) %}
87
  {% if run2 > 0 %}{% set has_nonempty_run.value = true %}{% endif %}
88
  {% set start2 = start1 + run1 %}
89
  {% set count2 = namespace(value=run2) %}
90
- {% for j in range(source.axis) %}
91
- {% set count2.value = count2.value * source.inputShape[j] %}
92
  {% endfor %}
93
  {% if count2.value > max_count.value %}{% set max_count.value = count2.value %}{% endif %}
94
  {% endif %}
95
- {% if source.outputCount >= 4 %}
96
- {% set run3 = (((source.y3Shape[source.axis] * inner.value) / 4) | int) %}
97
  {% if run3 > 0 %}{% set has_nonempty_run.value = true %}{% endif %}
98
  {% set start3 = start2 + run2 %}
99
  {% set count3 = namespace(value=run3) %}
100
- {% for j in range(source.axis) %}
101
- {% set count3.value = count3.value * source.inputShape[j] %}
102
  {% endfor %}
103
  {% if count3.value > max_count.value %}{% set max_count.value = count3.value %}{% endif %}
104
  {% endif %}
@@ -125,12 +122,12 @@ fn main(
125
  y1[i] = input[(i / {{ run1 }}u) * INNER_IN + {{ start1 }}u + (i % {{ run1 }}u)];
126
  }
127
  {% endif %}
128
- {% if source.outputCount >= 3 and run2 > 0 %}
129
  if (i < {{ count2.value }}u) {
130
  y2[i] = input[(i / {{ run2 }}u) * INNER_IN + {{ start2 }}u + (i % {{ run2 }}u)];
131
  }
132
  {% endif %}
133
- {% if source.outputCount >= 4 and run3 > 0 %}
134
  if (i < {{ count3.value }}u) {
135
  y3[i] = input[(i / {{ run3 }}u) * INNER_IN + {{ start3 }}u + (i % {{ run3 }}u)];
136
  }
 
3
  // divisible by four, all run boundaries are vec4-aligned and the input and
4
  // every output bind as vec4<T>. One division and one remainder per element
5
  // replace the per-axis coordinate decomposition of the scalar fallback.
 
 
 
6
  {{ env.wgsl.resourceDeclarations }}
7
 
8
  {% set inner = namespace(value=1) %}
9
+ {% for j in range(axisSpec + 1, rank) %}
10
+ {% set inner.value = inner.value * inputShape[j] %}
11
  {% endfor %}
12
+ {% set y2Shape = y2Shape | default([]) %}
13
+ {% if scalarBoundX4 %}
14
  // Scalar-bound x4 mode keeps arbitrary split boundaries legal. Each invocation
15
  // owns one four-scalar group within one outer slice, so a group never crosses
16
  // into the next slice; only the final group of a run needs scalar tail guards.
17
+ {% set inner_in_scalar = inputShape[axisSpec] * inner.value %}
18
+ {% set run0_scalar = y0Shape[axisSpec] * inner.value %}
19
+ {% set run1_scalar = y1Shape[axisSpec] * inner.value %}
20
  {% set groups0 = (((run0_scalar + 3) / 4) | int) %}
21
  {% set groups1 = (((run1_scalar + 3) / 4) | int) %}
22
  {% set outer_count = namespace(value=1) %}
23
+ {% for j in range(axisSpec) %}
24
+ {% set outer_count.value = outer_count.value * inputShape[j] %}
25
  {% endfor %}
26
  {% set count0_groups = outer_count.value * groups0 %}
27
  {% set count1_groups = outer_count.value * groups1 %}
28
  {% set max_groups = namespace(value=count0_groups) %}
29
  {% if count1_groups > max_groups.value %}{% set max_groups.value = count1_groups %}{% endif %}
30
  {% set start1_scalar = run0_scalar %}
31
+ {% set run2_scalar = y2Shape[axisSpec] * inner.value %}
32
  {% set groups2 = (((run2_scalar + 3) / 4) | int) %}
33
  {% set count2_groups = outer_count.value * groups2 %}
34
  {% set start2_scalar = start1_scalar + run1_scalar %}
 
66
  }
67
  }
68
  {% else %}
69
+ {% set inner_in = (((inputShape[axisSpec] * inner.value) / 4) | int) %}
70
+ {% set run0 = (((y0Shape[axisSpec] * inner.value) / 4) | int) %}
71
+ {% set run1 = (((y1Shape[axisSpec] * inner.value) / 4) | int) %}
72
  {% set has_nonempty_run = namespace(value=(run0 > 0 or run1 > 0)) %}
73
  {% set start1 = run0 %}
74
  {% set count0 = namespace(value=run0) %}
75
  {% set count1 = namespace(value=run1) %}
76
+ {% for j in range(axisSpec) %}
77
+ {% set count0.value = count0.value * inputShape[j] %}
78
+ {% set count1.value = count1.value * inputShape[j] %}
79
  {% endfor %}
80
  {% set max_count = namespace(value=count0.value) %}
81
  {% if count1.value > max_count.value %}{% set max_count.value = count1.value %}{% endif %}
82
+ {% if outputCountSpec >= 3 %}
83
+ {% set run2 = (((y2Shape[axisSpec] * inner.value) / 4) | int) %}
84
  {% if run2 > 0 %}{% set has_nonempty_run.value = true %}{% endif %}
85
  {% set start2 = start1 + run1 %}
86
  {% set count2 = namespace(value=run2) %}
87
+ {% for j in range(axisSpec) %}
88
+ {% set count2.value = count2.value * inputShape[j] %}
89
  {% endfor %}
90
  {% if count2.value > max_count.value %}{% set max_count.value = count2.value %}{% endif %}
91
  {% endif %}
92
+ {% if outputCountSpec >= 4 %}
93
+ {% set run3 = (((y3Shape[axisSpec] * inner.value) / 4) | int) %}
94
  {% if run3 > 0 %}{% set has_nonempty_run.value = true %}{% endif %}
95
  {% set start3 = start2 + run2 %}
96
  {% set count3 = namespace(value=run3) %}
97
+ {% for j in range(axisSpec) %}
98
+ {% set count3.value = count3.value * inputShape[j] %}
99
  {% endfor %}
100
  {% if count3.value > max_count.value %}{% set max_count.value = count3.value %}{% endif %}
101
  {% endif %}
 
122
  y1[i] = input[(i / {{ run1 }}u) * INNER_IN + {{ start1 }}u + (i % {{ run1 }}u)];
123
  }
124
  {% endif %}
125
+ {% if outputCountSpec >= 3 and run2 > 0 %}
126
  if (i < {{ count2.value }}u) {
127
  y2[i] = input[(i / {{ run2 }}u) * INNER_IN + {{ start2 }}u + (i % {{ run2 }}u)];
128
  }
129
  {% endif %}
130
+ {% if outputCountSpec >= 4 and run3 > 0 %}
131
  if (i < {{ count3.value }}u) {
132
  y3[i] = input[(i / {{ run3 }}u) * INNER_IN + {{ start3 }}u + (i % {{ run3 }}u)];
133
  }
build/webgpu/manifest.json CHANGED
@@ -2,74 +2,23 @@
2
  "domain": "ai.onnx",
3
  "name": "Split",
4
  "sinceVersion": 18,
5
- "description": "Splits a tensor into a list of tensors along the specified `axis`. The sizes of each output slice are given by the optional `split` input; if omitted, the tensor is divided into equal parts (the last chunk may be smaller if the axis dimension is not evenly divisible).",
6
- "inputs": [
7
- { "role": "input", "dtype": "T", "description": "The tensor to split." },
8
- {
9
- "role": "split",
10
- "dtype": "S",
11
- "rank": 1,
12
- "optional": true,
13
- "description": "Optional logical int64 1-D tensor specifying the size of each output along the split axis; values must be non-negative, sum to the axis dimension, and use uint32 WebGPU storage."
14
- }
15
- ],
16
- "outputs": [
17
- { "role": "Y0", "dtype": "T", "description": "First output slice after splitting.", "rank": "ranks.input" },
18
- {
19
- "role": "Y1",
20
- "dtype": "T",
21
- "optional": true,
22
- "description": "Second output slice after splitting (optional).",
23
- "rank": "ranks.input"
24
- },
25
- {
26
- "role": "Y2",
27
- "dtype": "T",
28
- "optional": true,
29
- "description": "Third output slice after splitting.",
30
- "rank": "ranks.input"
31
- },
32
- {
33
- "role": "Y3",
34
- "dtype": "T",
35
- "optional": true,
36
- "description": "Fourth output slice after splitting.",
37
- "rank": "ranks.input"
38
- },
39
- {
40
- "role": "Y4",
41
- "dtype": "T",
42
- "optional": true,
43
- "description": "Fifth output slice after splitting.",
44
- "rank": "ranks.input"
45
- }
46
- ],
47
- "attributes": { "axis": 0 },
48
- "attributeDescriptions": {
49
- "axis": "The axis along which to split. Negative values count from the end; accepted range is `[-rank, rank-1]`.",
50
- "num_outputs": "Optional number of outputs when the `split` input is omitted. The final output may be smaller when the axis dimension is not evenly divisible."
51
  },
 
 
 
 
 
 
 
 
52
  "typeConstraints": {
53
  "T": ["float32", "float16", "uint32", "int32", "int16", "uint8", "int8", "bool"],
54
  "S": ["int64"]
55
  },
56
- "args": {
57
- "input": { "kind": "tensor", "semantic": "input", "role": "input" },
58
- "split": {
59
- "kind": "tensor",
60
- "semantic": "split",
61
- "role": "split",
62
- "dtype": "uint32",
63
- "narrowing": "checked",
64
- "required": false
65
- },
66
- "y0": { "kind": "tensor", "semantic": "Y0", "role": "output" },
67
- "y1": { "kind": "tensor", "semantic": "Y1", "role": "output", "required": false },
68
- "y2": { "kind": "tensor", "semantic": "Y2", "role": "output", "required": false },
69
- "y3": { "kind": "tensor", "semantic": "Y3", "role": "output", "required": false },
70
- "y4": { "kind": "tensor", "semantic": "Y4", "role": "output", "required": false }
71
- },
72
- "tunables": { "WORKGROUP_SIZE": 256 },
73
  "derive": {
74
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
75
  "axis": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input",
@@ -77,167 +26,67 @@
77
  "numOutputsContract": "not has(attrs, \"num_outputs\") or attrs.num_outputs == outputCount",
78
  "workgroupSize": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
79
  "baseContract": "ranks.input >= 1 and axis >= 0 and axis < ranks.input and numOutputsContract and f16Ok(dtypes.T)",
80
- "oneOutputContract": "baseContract and not present.y1 and not present.y2 and not present.y3 and not present.y4 and sameShape(shapes.input, shapes.Y0)",
81
- "twoOutputContract": "baseContract and present.y1 and not present.y2 and ranks.input == ranks.Y0 and ranks.input == ranks.Y1 and dim(shapes.input, attrs.axis) == dim(shapes.Y0, attrs.axis) + dim(shapes.Y1, attrs.axis)",
82
- "threeOutputContract": "baseContract and present.y1 and present.y2 and not present.y3 and ranks.input == ranks.Y0 and ranks.input == ranks.Y1 and ranks.input == ranks.Y2 and dim(shapes.input, attrs.axis) == dim(shapes.Y0, attrs.axis) + dim(shapes.Y1, attrs.axis) + dim(shapes.Y2, attrs.axis)",
83
- "fourOutputContract": "baseContract and present.y1 and present.y2 and present.y3 and not present.y4 and ranks.input == ranks.Y0 and ranks.input == ranks.Y1 and ranks.input == ranks.Y2 and ranks.input == ranks.Y3 and dim(shapes.input, attrs.axis) == dim(shapes.Y0, attrs.axis) + dim(shapes.Y1, attrs.axis) + dim(shapes.Y2, attrs.axis) + dim(shapes.Y3, attrs.axis)",
84
- "fiveOutputContract": "baseContract and present.y1 and present.y2 and present.y3 and present.y4 and ranks.input == ranks.Y0 and ranks.input == ranks.Y1 and ranks.input == ranks.Y2 and ranks.input == ranks.Y3 and ranks.input == ranks.Y4 and dim(shapes.input, attrs.axis) == dim(shapes.Y0, attrs.axis) + dim(shapes.Y1, attrs.axis) + dim(shapes.Y2, attrs.axis) + dim(shapes.Y3, attrs.axis) + dim(shapes.Y4, attrs.axis)",
85
- "twoBlockContract": "twoOutputContract and outer(shapes.Y0, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.Y0, attrs.axis) == inner(shapes.input, attrs.axis) and outer(shapes.Y1, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.Y1, attrs.axis) == inner(shapes.input, attrs.axis)",
86
- "threeBlockContract": "threeOutputContract and outer(shapes.Y0, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.Y0, attrs.axis) == inner(shapes.input, attrs.axis) and outer(shapes.Y1, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.Y1, attrs.axis) == inner(shapes.input, attrs.axis) and outer(shapes.Y2, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.Y2, attrs.axis) == inner(shapes.input, attrs.axis)",
87
- "fourBlockContract": "fourOutputContract and outer(shapes.Y0, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.Y0, attrs.axis) == inner(shapes.input, attrs.axis) and outer(shapes.Y1, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.Y1, attrs.axis) == inner(shapes.input, attrs.axis) and outer(shapes.Y2, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.Y2, attrs.axis) == inner(shapes.input, attrs.axis) and outer(shapes.Y3, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.Y3, attrs.axis) == inner(shapes.input, attrs.axis)"
 
88
  },
89
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
90
- "bindingSets": {
91
- "oneOutput": [
92
- {
93
- "name": "src",
94
- "arg": "input",
95
- "semantic": "input",
96
- "buffer": { "type": "read-only-storage" },
97
- "elementType": "$scalar"
98
- },
99
- { "name": "dst", "arg": "y0", "semantic": "Y0", "buffer": { "type": "storage" }, "elementType": "$scalar" }
100
- ],
101
- "twoBlockVec4": [
102
- {
103
- "name": "input",
104
- "arg": "input",
105
- "semantic": "input",
106
- "buffer": { "type": "read-only-storage" },
107
- "elementType": "$vectorScalar"
108
- },
109
- { "name": "y0", "arg": "y0", "semantic": "Y0", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
110
- { "name": "y1", "arg": "y1", "semantic": "Y1", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" }
111
- ],
112
- "threeBlock": [
113
- {
114
- "name": "input",
115
- "arg": "input",
116
- "semantic": "input",
117
- "buffer": { "type": "read-only-storage" },
118
- "elementType": "$ioElement"
119
- },
120
- { "name": "y0", "arg": "y0", "semantic": "Y0", "buffer": { "type": "storage" }, "elementType": "$ioElement" },
121
- { "name": "y1", "arg": "y1", "semantic": "Y1", "buffer": { "type": "storage" }, "elementType": "$ioElement" },
122
- { "name": "y2", "arg": "y2", "semantic": "Y2", "buffer": { "type": "storage" }, "elementType": "$ioElement" }
123
- ],
124
- "fourBlockVec4": [
125
- {
126
- "name": "input",
127
- "arg": "input",
128
- "semantic": "input",
129
- "buffer": { "type": "read-only-storage" },
130
- "elementType": "$vectorScalar"
131
- },
132
- { "name": "y0", "arg": "y0", "semantic": "Y0", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
133
- { "name": "y1", "arg": "y1", "semantic": "Y1", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
134
- { "name": "y2", "arg": "y2", "semantic": "Y2", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" },
135
- { "name": "y3", "arg": "y3", "semantic": "Y3", "buffer": { "type": "storage" }, "elementType": "$vectorScalar" }
136
- ],
137
- "twoScalar": [
138
- {
139
- "name": "input",
140
- "arg": "input",
141
- "semantic": "input",
142
- "buffer": { "type": "read-only-storage" },
143
- "elementType": "$scalar"
144
- },
145
- { "name": "y0", "arg": "y0", "semantic": "Y0", "buffer": { "type": "storage" }, "elementType": "$scalar" },
146
- { "name": "y1", "arg": "y1", "semantic": "Y1", "buffer": { "type": "storage" }, "elementType": "$scalar" },
147
- {
148
- "name": "params",
149
- "semantic": "kernel.params",
150
- "buffer": { "type": "uniform" },
151
- "struct": {
152
- "name": "Params",
153
- "fields": [
154
- { "name": "y0Count", "type": "u32", "value": "numel(shapes.Y0)" },
155
- { "name": "y1Count", "type": "u32", "value": "numel(shapes.Y1)" }
156
- ]
157
- }
158
- }
159
- ],
160
- "fiveScalar": [
161
- {
162
- "name": "input",
163
- "arg": "input",
164
- "semantic": "input",
165
- "buffer": { "type": "read-only-storage" },
166
- "elementType": "$scalar"
167
- },
168
- { "name": "y0", "arg": "y0", "semantic": "Y0", "buffer": { "type": "storage" }, "elementType": "$scalar" },
169
- { "name": "y1", "arg": "y1", "semantic": "Y1", "buffer": { "type": "storage" }, "elementType": "$scalar" },
170
- { "name": "y2", "arg": "y2", "semantic": "Y2", "buffer": { "type": "storage" }, "elementType": "$scalar" },
171
- { "name": "y3", "arg": "y3", "semantic": "Y3", "buffer": { "type": "storage" }, "elementType": "$scalar" },
172
- { "name": "y4", "arg": "y4", "semantic": "Y4", "buffer": { "type": "storage" }, "elementType": "$scalar" },
173
- {
174
- "name": "params",
175
- "semantic": "kernel.params",
176
- "buffer": { "type": "uniform" },
177
- "struct": {
178
- "name": "Params",
179
- "fields": [
180
- { "name": "y0Count", "type": "u32", "value": "numel(shapes.Y0)" },
181
- { "name": "y1Count", "type": "u32", "value": "numel(shapes.Y1)" },
182
- { "name": "y2Count", "type": "u32", "value": "numel(shapes.Y2)" },
183
- { "name": "y3Count", "type": "u32", "value": "numel(shapes.Y3)" },
184
- { "name": "y4Count", "type": "u32", "value": "numel(shapes.Y4)" }
185
- ]
186
- }
187
- }
188
- ],
189
- "fourScalar": [
190
- {
191
- "name": "input",
192
- "arg": "input",
193
- "semantic": "input",
194
- "buffer": { "type": "read-only-storage" },
195
- "elementType": "$scalar"
196
- },
197
- { "name": "y0", "arg": "y0", "semantic": "Y0", "buffer": { "type": "storage" }, "elementType": "$scalar" },
198
- { "name": "y1", "arg": "y1", "semantic": "Y1", "buffer": { "type": "storage" }, "elementType": "$scalar" },
199
- { "name": "y2", "arg": "y2", "semantic": "Y2", "buffer": { "type": "storage" }, "elementType": "$scalar" },
200
- { "name": "y3", "arg": "y3", "semantic": "Y3", "buffer": { "type": "storage" }, "elementType": "$scalar" },
201
- {
202
- "name": "params",
203
- "semantic": "kernel.params",
204
- "buffer": { "type": "uniform" },
205
- "struct": {
206
- "name": "Params",
207
- "fields": [
208
- { "name": "y0Count", "type": "u32", "value": "numel(shapes.Y0)" },
209
- { "name": "y1Count", "type": "u32", "value": "numel(shapes.Y1)" },
210
- { "name": "y2Count", "type": "u32", "value": "numel(shapes.Y2)" },
211
- { "name": "y3Count", "type": "u32", "value": "numel(shapes.Y3)" }
212
- ]
213
- }
214
- }
215
- ],
216
- "threeScalar": [
217
- {
218
- "name": "input",
219
- "arg": "input",
220
- "semantic": "input",
221
- "buffer": { "type": "read-only-storage" },
222
- "elementType": "$scalar"
223
- },
224
- { "name": "y0", "arg": "y0", "semantic": "Y0", "buffer": { "type": "storage" }, "elementType": "$scalar" },
225
- { "name": "y1", "arg": "y1", "semantic": "Y1", "buffer": { "type": "storage" }, "elementType": "$scalar" },
226
- { "name": "y2", "arg": "y2", "semantic": "Y2", "buffer": { "type": "storage" }, "elementType": "$scalar" },
227
- {
228
- "name": "params",
229
- "semantic": "kernel.params",
230
- "buffer": { "type": "uniform" },
231
- "struct": {
232
- "name": "Params",
233
- "fields": [
234
- { "name": "y0Count", "type": "u32", "value": "numel(shapes.Y0)" },
235
- { "name": "y1Count", "type": "u32", "value": "numel(shapes.Y1)" },
236
- { "name": "y2Count", "type": "u32", "value": "numel(shapes.Y2)" }
237
- ]
238
- }
239
- }
240
- ]
241
  },
242
  "variants": [
243
  {
@@ -248,9 +97,17 @@
248
  {
249
  "id": "main",
250
  "name": "Split.copy",
251
- "source": { "shader": "datamove-flat-copy.wgsl.jinja", "inputs": { "count": "numel(shapes.Y0)" } },
252
- "bindings": "oneOutput",
253
- "dispatch": { "gridStride": "numel(shapes.Y0)", "workgroupSize": "workgroupSize" },
 
 
 
 
 
 
 
 
254
  "viewAlias": [{ "input": "src", "output": "dst" }]
255
  }
256
  ]
@@ -258,54 +115,55 @@
258
  {
259
  "id": "two_outputs_block_vec4",
260
  "priority": 15,
261
- "when": ["twoBlockContract", "(dim(shapes.Y0, attrs.axis) * inner(shapes.Y0, attrs.axis)) % 4 == 0", "(dim(shapes.Y1, attrs.axis) * inner(shapes.Y1, attrs.axis)) % 4 == 0"],
262
- "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
263
  "passes": [
264
  {
265
  "id": "main",
266
  "name": "Split.blockVec4",
267
- "source": {
268
- "shader": "datamove-split-block.wgsl.jinja",
269
- "inputs": {
270
- "inputShape": "shapes.input",
271
- "y0Shape": "shapes.Y0",
272
- "y1Shape": "shapes.Y1",
273
- "rank": "ranks.input",
274
- "axis": "axis",
275
- "outputCount": 2
276
- }
277
  },
278
- "bindings": "twoBlockVec4",
279
- "dispatch": { "gridStride": "max(numel(shapes.Y0), numel(shapes.Y1)) / 4", "workgroupSize": "workgroupSize" }
 
 
 
 
280
  }
281
  ]
282
  },
283
  {
284
  "id": "three_outputs_block_scalar_x4",
285
  "priority": 14,
286
- "when": ["threeBlockContract", "dim(shapes.Y0, attrs.axis) > 0", "dim(shapes.Y1, attrs.axis) > 0", "dim(shapes.Y2, attrs.axis) > 0", "max(numel(shapes.Y0), numel(shapes.Y1), numel(shapes.Y2)) >= 16"],
287
- "constants": { "ioElement": "dtypes.T" },
288
  "passes": [
289
  {
290
  "id": "main",
291
  "name": "Split3.blockScalarX4",
292
- "source": {
293
- "shader": "datamove-split-block.wgsl.jinja",
294
- "inputs": {
295
- "inputShape": "shapes.input",
296
- "y0Shape": "shapes.Y0",
297
- "y1Shape": "shapes.Y1",
298
- "y2Shape": "shapes.Y2",
299
- "rank": "ranks.input",
300
- "axis": "axis",
301
- "outputCount": 3,
302
- "scalarBoundX4": true
303
- }
304
  },
305
- "bindings": "threeBlock",
306
  "dispatch": {
307
- "gridStride": "max(outer(shapes.Y0, attrs.axis) * ceil(dim(shapes.Y0, attrs.axis) * inner(shapes.Y0, attrs.axis) / 4), outer(shapes.Y1, attrs.axis) * ceil(dim(shapes.Y1, attrs.axis) * inner(shapes.Y1, attrs.axis) / 4), outer(shapes.Y2, attrs.axis) * ceil(dim(shapes.Y2, attrs.axis) * inner(shapes.Y2, attrs.axis) / 4))",
308
- "workgroupSize": "workgroupSize"
 
309
  }
310
  }
311
  ]
@@ -313,28 +171,27 @@
313
  {
314
  "id": "three_outputs_block_vec4",
315
  "priority": 15,
316
- "when": ["threeBlockContract", "(dim(shapes.Y0, attrs.axis) * inner(shapes.Y0, attrs.axis)) % 4 == 0", "(dim(shapes.Y1, attrs.axis) * inner(shapes.Y1, attrs.axis)) % 4 == 0", "(dim(shapes.Y2, attrs.axis) * inner(shapes.Y2, attrs.axis)) % 4 == 0"],
317
- "constants": { "ioElement": "\"vec4<\" ~ dtypes.T ~ \">\"", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
318
  "passes": [
319
  {
320
  "id": "main",
321
  "name": "Split3.blockVec4",
322
- "source": {
323
- "shader": "datamove-split-block.wgsl.jinja",
324
- "inputs": {
325
- "inputShape": "shapes.input",
326
- "y0Shape": "shapes.Y0",
327
- "y1Shape": "shapes.Y1",
328
- "y2Shape": "shapes.Y2",
329
- "rank": "ranks.input",
330
- "axis": "axis",
331
- "outputCount": 3
332
- }
333
  },
334
- "bindings": "threeBlock",
335
  "dispatch": {
336
- "gridStride": "max(numel(shapes.Y0), numel(shapes.Y1), numel(shapes.Y2)) / 4",
337
- "workgroupSize": "workgroupSize"
 
338
  }
339
  }
340
  ]
@@ -342,29 +199,34 @@
342
  {
343
  "id": "four_outputs_block_vec4",
344
  "priority": 25,
345
- "when": ["fourBlockContract", "(dim(shapes.Y0, attrs.axis) * inner(shapes.Y0, attrs.axis)) % 4 == 0", "(dim(shapes.Y1, attrs.axis) * inner(shapes.Y1, attrs.axis)) % 4 == 0", "(dim(shapes.Y2, attrs.axis) * inner(shapes.Y2, attrs.axis)) % 4 == 0", "(dim(shapes.Y3, attrs.axis) * inner(shapes.Y3, attrs.axis)) % 4 == 0"],
346
- "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
347
  "passes": [
348
  {
349
  "id": "main",
350
  "name": "Split4.blockVec4",
351
- "source": {
352
- "shader": "datamove-split-block.wgsl.jinja",
353
- "inputs": {
354
- "inputShape": "shapes.input",
355
- "y0Shape": "shapes.Y0",
356
- "y1Shape": "shapes.Y1",
357
- "y2Shape": "shapes.Y2",
358
- "y3Shape": "shapes.Y3",
359
- "rank": "ranks.input",
360
- "axis": "axis",
361
- "outputCount": 4
362
- }
363
  },
364
- "bindings": "fourBlockVec4",
 
 
 
 
 
 
365
  "dispatch": {
366
- "gridStride": "max(numel(shapes.Y0), numel(shapes.Y1), numel(shapes.Y2), numel(shapes.Y3)) / 4",
367
- "workgroupSize": "workgroupSize"
 
368
  }
369
  }
370
  ]
@@ -376,17 +238,19 @@
376
  {
377
  "id": "main",
378
  "name": "Split",
379
- "source": {
380
- "shader": "split-n.wgsl.jinja",
381
- "inputs": {
382
- "inputShape": "shapes.input",
383
- "outputs": "[ {\"name\": \"y0\", \"shape\": shapes.Y0}, {\"name\": \"y1\", \"shape\": shapes.Y1} ]",
384
- "rank": "ranks.input",
385
- "axis": "axis"
386
- }
387
  },
388
- "bindings": "twoScalar",
389
- "dispatch": { "threads": "max(numel(shapes.Y0), numel(shapes.Y1))", "workgroupSize": "workgroupSize" }
 
 
 
 
390
  }
391
  ]
392
  },
@@ -398,19 +262,18 @@
398
  {
399
  "id": "main",
400
  "name": "SplitN",
401
- "source": {
402
- "shader": "split-n.wgsl.jinja",
403
- "inputs": {
404
- "inputShape": "shapes.input",
405
- "outputs": "[ {\"name\": \"y0\", \"shape\": shapes.Y0}, {\"name\": \"y1\", \"shape\": shapes.Y1}, {\"name\": \"y2\", \"shape\": shapes.Y2}, {\"name\": \"y3\", \"shape\": shapes.Y3}, {\"name\": \"y4\", \"shape\": shapes.Y4} ]",
406
- "rank": "ranks.input",
407
- "axis": "axis"
408
- }
409
  },
410
- "bindings": "fiveScalar",
411
  "dispatch": {
412
- "threads": "max(numel(shapes.Y0), numel(shapes.Y1), numel(shapes.Y2), numel(shapes.Y3), numel(shapes.Y4))",
413
- "workgroupSize": "workgroupSize"
 
414
  }
415
  }
416
  ]
@@ -423,19 +286,18 @@
423
  {
424
  "id": "main",
425
  "name": "Split4",
426
- "source": {
427
- "shader": "split-n.wgsl.jinja",
428
- "inputs": {
429
- "inputShape": "shapes.input",
430
- "outputs": "[ {\"name\": \"y0\", \"shape\": shapes.Y0}, {\"name\": \"y1\", \"shape\": shapes.Y1}, {\"name\": \"y2\", \"shape\": shapes.Y2}, {\"name\": \"y3\", \"shape\": shapes.Y3} ]",
431
- "rank": "ranks.input",
432
- "axis": "axis"
433
- }
434
  },
435
- "bindings": "fourScalar",
436
  "dispatch": {
437
- "threads": "max(numel(shapes.Y0), numel(shapes.Y1), numel(shapes.Y2), numel(shapes.Y3))",
438
- "workgroupSize": "workgroupSize"
 
439
  }
440
  }
441
  ]
@@ -448,19 +310,18 @@
448
  {
449
  "id": "main",
450
  "name": "Split3",
451
- "source": {
452
- "shader": "split-n.wgsl.jinja",
453
- "inputs": {
454
- "inputShape": "shapes.input",
455
- "outputs": "[ {\"name\": \"y0\", \"shape\": shapes.Y0}, {\"name\": \"y1\", \"shape\": shapes.Y1}, {\"name\": \"y2\", \"shape\": shapes.Y2} ]",
456
- "rank": "ranks.input",
457
- "axis": "axis"
458
- }
459
  },
460
- "bindings": "threeScalar",
461
  "dispatch": {
462
- "threads": "max(numel(shapes.Y0), numel(shapes.Y1), numel(shapes.Y2))",
463
- "workgroupSize": "workgroupSize"
 
464
  }
465
  }
466
  ]
 
2
  "domain": "ai.onnx",
3
  "name": "Split",
4
  "sinceVersion": 18,
5
+ "inputs": {
6
+ "input": { "dtype": "T" },
7
+ "split": { "dtype": "S", "rank": 1, "optional": true, "storage": "uint32", "narrowing": "checked" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  },
9
+ "outputs": {
10
+ "y0": { "onnx": "Y0", "dtype": "T", "rank": "ranks.input" },
11
+ "y1": { "onnx": "Y1", "dtype": "T", "rank": "ranks.input", "optional": true },
12
+ "y2": { "onnx": "Y2", "dtype": "T", "rank": "ranks.input", "optional": true },
13
+ "y3": { "onnx": "Y3", "dtype": "T", "rank": "ranks.input", "optional": true },
14
+ "y4": { "onnx": "Y4", "dtype": "T", "rank": "ranks.input", "optional": true }
15
+ },
16
+ "attributes": { "axis": { "default": 0 }, "num_outputs": {} },
17
  "typeConstraints": {
18
  "T": ["float32", "float16", "uint32", "int32", "int16", "uint8", "int8", "bool"],
19
  "S": ["int64"]
20
  },
21
+ "tunables": { "WORKGROUP_SIZE": { "default": 256 } },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  "derive": {
23
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
24
  "axis": "attrs.axis if attrs.axis >= 0 else attrs.axis + ranks.input",
 
26
  "numOutputsContract": "not has(attrs, \"num_outputs\") or attrs.num_outputs == outputCount",
27
  "workgroupSize": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
28
  "baseContract": "ranks.input >= 1 and axis >= 0 and axis < ranks.input and numOutputsContract and f16Ok(dtypes.T)",
29
+ "oneOutputContract": "baseContract and not present.y1 and not present.y2 and not present.y3 and not present.y4 and sameShape(shapes.input, shapes.y0)",
30
+ "twoOutputContract": "baseContract and present.y1 and not present.y2 and ranks.input == ranks.y0 and ranks.input == ranks.y1 and dim(shapes.input, attrs.axis) == dim(shapes.y0, attrs.axis) + dim(shapes.y1, attrs.axis)",
31
+ "threeOutputContract": "baseContract and present.y1 and present.y2 and not present.y3 and ranks.input == ranks.y0 and ranks.input == ranks.y1 and ranks.input == ranks.y2 and dim(shapes.input, attrs.axis) == dim(shapes.y0, attrs.axis) + dim(shapes.y1, attrs.axis) + dim(shapes.y2, attrs.axis)",
32
+ "fourOutputContract": "baseContract and present.y1 and present.y2 and present.y3 and not present.y4 and ranks.input == ranks.y0 and ranks.input == ranks.y1 and ranks.input == ranks.y2 and ranks.input == ranks.y3 and dim(shapes.input, attrs.axis) == dim(shapes.y0, attrs.axis) + dim(shapes.y1, attrs.axis) + dim(shapes.y2, attrs.axis) + dim(shapes.y3, attrs.axis)",
33
+ "fiveOutputContract": "baseContract and present.y1 and present.y2 and present.y3 and present.y4 and ranks.input == ranks.y0 and ranks.input == ranks.y1 and ranks.input == ranks.y2 and ranks.input == ranks.y3 and ranks.input == ranks.y4 and dim(shapes.input, attrs.axis) == dim(shapes.y0, attrs.axis) + dim(shapes.y1, attrs.axis) + dim(shapes.y2, attrs.axis) + dim(shapes.y3, attrs.axis) + dim(shapes.y4, attrs.axis)",
34
+ "twoBlockContract": "twoOutputContract and outer(shapes.y0, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.y0, attrs.axis) == inner(shapes.input, attrs.axis) and outer(shapes.y1, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.y1, attrs.axis) == inner(shapes.input, attrs.axis)",
35
+ "threeBlockContract": "threeOutputContract and outer(shapes.y0, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.y0, attrs.axis) == inner(shapes.input, attrs.axis) and outer(shapes.y1, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.y1, attrs.axis) == inner(shapes.input, attrs.axis) and outer(shapes.y2, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.y2, attrs.axis) == inner(shapes.input, attrs.axis)",
36
+ "fourBlockContract": "fourOutputContract and outer(shapes.y0, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.y0, attrs.axis) == inner(shapes.input, attrs.axis) and outer(shapes.y1, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.y1, attrs.axis) == inner(shapes.input, attrs.axis) and outer(shapes.y2, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.y2, attrs.axis) == inner(shapes.input, attrs.axis) and outer(shapes.y3, attrs.axis) == outer(shapes.input, attrs.axis) and inner(shapes.y3, attrs.axis) == inner(shapes.input, attrs.axis)",
37
+ "scalar": "dtypes.T"
38
  },
39
+ "bindings": {
40
+ "input": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
41
+ "y0": { "buffer": "storage", "elementType": "$vectorScalar" },
42
+ "y1": { "buffer": "storage", "elementType": "$vectorScalar" },
43
+ "input_2": { "name": "input", "buffer": "read-only-storage", "elementType": "$ioElement" },
44
+ "y0_2": { "name": "y0", "buffer": "storage", "elementType": "$ioElement" },
45
+ "y1_2": { "name": "y1", "buffer": "storage", "elementType": "$ioElement" },
46
+ "y2": { "buffer": "storage", "elementType": "$ioElement" },
47
+ "input_3": { "name": "input", "buffer": "read-only-storage", "elementType": "$scalar" },
48
+ "y0_3": { "name": "y0", "buffer": "storage", "elementType": "$scalar" },
49
+ "y1_3": { "name": "y1", "buffer": "storage", "elementType": "$scalar" },
50
+ "params": {
51
+ "buffer": "uniform",
52
+ "struct": [
53
+ { "name": "y0Count", "type": "u32", "value": "numel(shapes.y0)" },
54
+ { "name": "y1Count", "type": "u32", "value": "numel(shapes.y1)" }
55
+ ]
56
+ },
57
+ "y2_3": { "name": "y2", "buffer": "storage", "elementType": "$scalar" },
58
+ "y3_2": { "name": "y3", "buffer": "storage", "elementType": "$scalar" },
59
+ "y4": { "buffer": "storage", "elementType": "$scalar" },
60
+ "params_2": {
61
+ "name": "params",
62
+ "buffer": "uniform",
63
+ "struct": [
64
+ { "name": "y0Count", "type": "u32", "value": "numel(shapes.y0)" },
65
+ { "name": "y1Count", "type": "u32", "value": "numel(shapes.y1)" },
66
+ { "name": "y2Count", "type": "u32", "value": "numel(shapes.y2)" },
67
+ { "name": "y3Count", "type": "u32", "value": "numel(shapes.y3)" },
68
+ { "name": "y4Count", "type": "u32", "value": "numel(shapes.y4)" }
69
+ ]
70
+ },
71
+ "params_3": {
72
+ "name": "params",
73
+ "buffer": "uniform",
74
+ "struct": [
75
+ { "name": "y0Count", "type": "u32", "value": "numel(shapes.y0)" },
76
+ { "name": "y1Count", "type": "u32", "value": "numel(shapes.y1)" },
77
+ { "name": "y2Count", "type": "u32", "value": "numel(shapes.y2)" },
78
+ { "name": "y3Count", "type": "u32", "value": "numel(shapes.y3)" }
79
+ ]
80
+ },
81
+ "params_4": {
82
+ "name": "params",
83
+ "buffer": "uniform",
84
+ "struct": [
85
+ { "name": "y0Count", "type": "u32", "value": "numel(shapes.y0)" },
86
+ { "name": "y1Count", "type": "u32", "value": "numel(shapes.y1)" },
87
+ { "name": "y2Count", "type": "u32", "value": "numel(shapes.y2)" }
88
+ ]
89
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  },
91
  "variants": [
92
  {
 
97
  {
98
  "id": "main",
99
  "name": "Split.copy",
100
+ "shader": "datamove-flat-copy.wgsl.jinja",
101
+ "derive": { "count": "numel(shapes.y0)" },
102
+ "bindings": [
103
+ { "arg": "input", "name": "src", "elementType": "$scalar" },
104
+ { "arg": "y0", "name": "dst", "elementType": "$scalar" }
105
+ ],
106
+ "dispatch": {
107
+ "x": "min(ceilDiv((numel(shapes.y0)), (workgroupSize)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
108
+ "y": 1,
109
+ "z": 1
110
+ },
111
  "viewAlias": [{ "input": "src", "output": "dst" }]
112
  }
113
  ]
 
115
  {
116
  "id": "two_outputs_block_vec4",
117
  "priority": 15,
118
+ "when": ["twoBlockContract", "(dim(shapes.y0, attrs.axis) * inner(shapes.y0, attrs.axis)) % 4 == 0", "(dim(shapes.y1, attrs.axis) * inner(shapes.y1, attrs.axis)) % 4 == 0"],
119
+ "derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
120
  "passes": [
121
  {
122
  "id": "main",
123
  "name": "Split.blockVec4",
124
+ "shader": "datamove-split-block.wgsl.jinja",
125
+ "derive": {
126
+ "inputShape": "shapes.input",
127
+ "y0Shape": "shapes.y0",
128
+ "y1Shape": "shapes.y1",
129
+ "rank": "ranks.input",
130
+ "axisSpec": "axis",
131
+ "outputCountSpec": 2
 
 
132
  },
133
+ "bindings": ["input", "y0", "y1"],
134
+ "dispatch": {
135
+ "x": "min(ceilDiv((max(numel(shapes.y0), numel(shapes.y1)) / 4), (workgroupSize)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
136
+ "y": 1,
137
+ "z": 1
138
+ }
139
  }
140
  ]
141
  },
142
  {
143
  "id": "three_outputs_block_scalar_x4",
144
  "priority": 14,
145
+ "when": ["threeBlockContract", "dim(shapes.y0, attrs.axis) > 0", "dim(shapes.y1, attrs.axis) > 0", "dim(shapes.y2, attrs.axis) > 0", "max(numel(shapes.y0), numel(shapes.y1), numel(shapes.y2)) >= 16"],
146
+ "derive": { "ioElement": "dtypes.T" },
147
  "passes": [
148
  {
149
  "id": "main",
150
  "name": "Split3.blockScalarX4",
151
+ "shader": "datamove-split-block.wgsl.jinja",
152
+ "derive": {
153
+ "inputShape": "shapes.input",
154
+ "y0Shape": "shapes.y0",
155
+ "y1Shape": "shapes.y1",
156
+ "y2Shape": "shapes.y2",
157
+ "rank": "ranks.input",
158
+ "axisSpec": "axis",
159
+ "outputCountSpec": 3,
160
+ "scalarBoundX4": true
 
 
161
  },
162
+ "bindings": ["input_2", "y0_2", "y1_2", "y2"],
163
  "dispatch": {
164
+ "x": "min(ceilDiv((max(outer(shapes.y0, attrs.axis) * ceil(dim(shapes.y0, attrs.axis) * inner(shapes.y0, attrs.axis) / 4), outer(shapes.y1, attrs.axis) * ceil(dim(shapes.y1, attrs.axis) * inner(shapes.y1, attrs.axis) / 4), outer(shapes.y2, attrs.axis) * ceil(dim(shapes.y2, attrs.axis) * inner(shapes.y2, attrs.axis) / 4))), (workgroupSize)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
165
+ "y": 1,
166
+ "z": 1
167
  }
168
  }
169
  ]
 
171
  {
172
  "id": "three_outputs_block_vec4",
173
  "priority": 15,
174
+ "when": ["threeBlockContract", "(dim(shapes.y0, attrs.axis) * inner(shapes.y0, attrs.axis)) % 4 == 0", "(dim(shapes.y1, attrs.axis) * inner(shapes.y1, attrs.axis)) % 4 == 0", "(dim(shapes.y2, attrs.axis) * inner(shapes.y2, attrs.axis)) % 4 == 0"],
175
+ "derive": { "ioElement": "\"vec4<\" ~ dtypes.T ~ \">\"", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
176
  "passes": [
177
  {
178
  "id": "main",
179
  "name": "Split3.blockVec4",
180
+ "shader": "datamove-split-block.wgsl.jinja",
181
+ "derive": {
182
+ "inputShape": "shapes.input",
183
+ "y0Shape": "shapes.y0",
184
+ "y1Shape": "shapes.y1",
185
+ "y2Shape": "shapes.y2",
186
+ "rank": "ranks.input",
187
+ "axisSpec": "axis",
188
+ "outputCountSpec": 3
 
 
189
  },
190
+ "bindings": ["input_2", "y0_2", "y1_2", "y2"],
191
  "dispatch": {
192
+ "x": "min(ceilDiv((max(numel(shapes.y0), numel(shapes.y1), numel(shapes.y2)) / 4), (workgroupSize)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
193
+ "y": 1,
194
+ "z": 1
195
  }
196
  }
197
  ]
 
199
  {
200
  "id": "four_outputs_block_vec4",
201
  "priority": 25,
202
+ "when": ["fourBlockContract", "(dim(shapes.y0, attrs.axis) * inner(shapes.y0, attrs.axis)) % 4 == 0", "(dim(shapes.y1, attrs.axis) * inner(shapes.y1, attrs.axis)) % 4 == 0", "(dim(shapes.y2, attrs.axis) * inner(shapes.y2, attrs.axis)) % 4 == 0", "(dim(shapes.y3, attrs.axis) * inner(shapes.y3, attrs.axis)) % 4 == 0"],
203
+ "derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
204
  "passes": [
205
  {
206
  "id": "main",
207
  "name": "Split4.blockVec4",
208
+ "shader": "datamove-split-block.wgsl.jinja",
209
+ "derive": {
210
+ "inputShape": "shapes.input",
211
+ "y0Shape": "shapes.y0",
212
+ "y1Shape": "shapes.y1",
213
+ "y2Shape": "shapes.y2",
214
+ "y3Shape": "shapes.y3",
215
+ "rank": "ranks.input",
216
+ "axisSpec": "axis",
217
+ "outputCountSpec": 4
 
 
218
  },
219
+ "bindings": [
220
+ "input",
221
+ "y0",
222
+ "y1",
223
+ { "arg": "y2", "elementType": "$vectorScalar" },
224
+ { "arg": "y3", "elementType": "$vectorScalar" }
225
+ ],
226
  "dispatch": {
227
+ "x": "min(ceilDiv((max(numel(shapes.y0), numel(shapes.y1), numel(shapes.y2), numel(shapes.y3)) / 4), (workgroupSize)), min(device.limits.maxComputeWorkgroupsPerDimension, 65535))",
228
+ "y": 1,
229
+ "z": 1
230
  }
231
  }
232
  ]
 
238
  {
239
  "id": "main",
240
  "name": "Split",
241
+ "shader": "split-n.wgsl.jinja",
242
+ "derive": {
243
+ "inputShape": "shapes.input",
244
+ "outputs": "[ {\"name\": \"y0\", \"shape\": shapes.y0}, {\"name\": \"y1\", \"shape\": shapes.y1} ]",
245
+ "rank": "ranks.input",
246
+ "axisSpec": "axis"
 
 
247
  },
248
+ "bindings": ["input_3", "y0_3", "y1_3", "params"],
249
+ "dispatch": {
250
+ "x": "min(ceilDiv((max(numel(shapes.y0), numel(shapes.y1))), (workgroupSize)), 65535)",
251
+ "y": "ceilDiv(ceilDiv((max(numel(shapes.y0), numel(shapes.y1))), (workgroupSize)), 65535)",
252
+ "z": 1
253
+ }
254
  }
255
  ]
256
  },
 
262
  {
263
  "id": "main",
264
  "name": "SplitN",
265
+ "shader": "split-n.wgsl.jinja",
266
+ "derive": {
267
+ "inputShape": "shapes.input",
268
+ "outputs": "[ {\"name\": \"y0\", \"shape\": shapes.y0}, {\"name\": \"y1\", \"shape\": shapes.y1}, {\"name\": \"y2\", \"shape\": shapes.y2}, {\"name\": \"y3\", \"shape\": shapes.y3}, {\"name\": \"y4\", \"shape\": shapes.y4} ]",
269
+ "rank": "ranks.input",
270
+ "axisSpec": "axis"
 
 
271
  },
272
+ "bindings": ["input_3", "y0_3", "y1_3", "y2_3", "y3_2", "y4", "params_2"],
273
  "dispatch": {
274
+ "x": "min(ceilDiv((max(numel(shapes.y0), numel(shapes.y1), numel(shapes.y2), numel(shapes.y3), numel(shapes.y4))), (workgroupSize)), 65535)",
275
+ "y": "ceilDiv(ceilDiv((max(numel(shapes.y0), numel(shapes.y1), numel(shapes.y2), numel(shapes.y3), numel(shapes.y4))), (workgroupSize)), 65535)",
276
+ "z": 1
277
  }
278
  }
279
  ]
 
286
  {
287
  "id": "main",
288
  "name": "Split4",
289
+ "shader": "split-n.wgsl.jinja",
290
+ "derive": {
291
+ "inputShape": "shapes.input",
292
+ "outputs": "[ {\"name\": \"y0\", \"shape\": shapes.y0}, {\"name\": \"y1\", \"shape\": shapes.y1}, {\"name\": \"y2\", \"shape\": shapes.y2}, {\"name\": \"y3\", \"shape\": shapes.y3} ]",
293
+ "rank": "ranks.input",
294
+ "axisSpec": "axis"
 
 
295
  },
296
+ "bindings": ["input_3", "y0_3", "y1_3", "y2_3", "y3_2", "params_3"],
297
  "dispatch": {
298
+ "x": "min(ceilDiv((max(numel(shapes.y0), numel(shapes.y1), numel(shapes.y2), numel(shapes.y3))), (workgroupSize)), 65535)",
299
+ "y": "ceilDiv(ceilDiv((max(numel(shapes.y0), numel(shapes.y1), numel(shapes.y2), numel(shapes.y3))), (workgroupSize)), 65535)",
300
+ "z": 1
301
  }
302
  }
303
  ]
 
310
  {
311
  "id": "main",
312
  "name": "Split3",
313
+ "shader": "split-n.wgsl.jinja",
314
+ "derive": {
315
+ "inputShape": "shapes.input",
316
+ "outputs": "[ {\"name\": \"y0\", \"shape\": shapes.y0}, {\"name\": \"y1\", \"shape\": shapes.y1}, {\"name\": \"y2\", \"shape\": shapes.y2} ]",
317
+ "rank": "ranks.input",
318
+ "axisSpec": "axis"
 
 
319
  },
320
+ "bindings": ["input_3", "y0_3", "y1_3", "y2_3", "params_4"],
321
  "dispatch": {
322
+ "x": "min(ceilDiv((max(numel(shapes.y0), numel(shapes.y1), numel(shapes.y2))), (workgroupSize)), 65535)",
323
+ "y": "ceilDiv(ceilDiv((max(numel(shapes.y0), numel(shapes.y1), numel(shapes.y2))), (workgroupSize)), 65535)",
324
+ "z": 1
325
  }
326
  }
327
  ]
build/webgpu/metadata.json CHANGED
@@ -1,20 +1,33 @@
1
  {
2
  "name": "ai.onnx.Split",
3
- "id": "_ai_onnx_split_webgpu_fa39047",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "2lxxmh4jHqr1stKdWRuW4p+XgseMJNCWKwNsmypIqeM=",
11
- "datamove-flat-copy.wgsl.jinja": "rEpeYMvIZQuQnB2f3lt51jrrklKICsqUTzCJPRTknNk=",
12
- "datamove-split-block.wgsl.jinja": "BMcc7EVTp6zQzIDQBkTcgdYPqFaNCf1cUTox/l1kTxg=",
13
- "manifest.json": "6wweSEZ8+U9uXmLy9VX0MtrQfDybb8JlpcRfElWwMKo=",
14
- "split-n.wgsl.jinja": "GjmhWg4EmgRJccL6Y0Ll0FAvbeKn3e0JwbX2VP5zrEo=",
15
- "test.json": "z07mxLLaj3ydKbjwPvFvi45EwMDYHoeTdVKoez0SnFM="
16
  }
17
  },
18
- "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
19
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.Split" }
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
 
1
  {
2
  "name": "ai.onnx.Split",
3
+ "id": "_ai_onnx_split_webgpu_905cd69",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "k7jAXfnplRAPstbkQGbKdFPxoWX7R9DW+MK+4NiYTLU=",
11
+ "datamove-flat-copy.wgsl.jinja": "g9d62mer2bmHfbScqX5CIT5Zq/Pdinac0W2dH0CD+5s=",
12
+ "datamove-split-block.wgsl.jinja": "shWWQMUtwoSmMEgOn2yy1spChp86zzPkm/Cb7QiAnWs=",
13
+ "manifest.json": "qnPxFTTkmu2fktjom8/oaTjlNXRcGD2kg+oeCsJ6RQc=",
14
+ "split-n.wgsl.jinja": "z574+TJ8XGpxhV0wwxRfsHXSsSu7Ry4dvhXBxHcB6Cw=",
15
+ "test.json": "71+a2DHyXRRHacOOABnDWrpe60/NRk7A/jP9UqPwpFE="
16
  }
17
  },
18
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
19
+ "webgpu": {
20
+ "manifestSpec": "2.0",
21
+ "variants": {
22
+ "one_output_copy": ["datamove-flat-copy.wgsl.jinja"],
23
+ "two_outputs_block_vec4": ["datamove-split-block.wgsl.jinja"],
24
+ "three_outputs_block_scalar_x4": ["datamove-split-block.wgsl.jinja"],
25
+ "three_outputs_block_vec4": ["datamove-split-block.wgsl.jinja"],
26
+ "four_outputs_block_vec4": ["datamove-split-block.wgsl.jinja"],
27
+ "two_outputs": ["split-n.wgsl.jinja"],
28
+ "five_outputs": ["split-n.wgsl.jinja"],
29
+ "four_outputs": ["split-n.wgsl.jinja"],
30
+ "three_outputs": ["split-n.wgsl.jinja"]
31
+ }
32
+ }
33
  }
build/webgpu/split-n.wgsl.jinja CHANGED
@@ -2,19 +2,16 @@
2
  // adding all preceding outputs' cumulative split-axis extent. One invocation
3
  // handles the same flat position across outputs, and each output writes only
4
  // when that position is within its own element count.
5
- {% if usesF16 %}
6
- enable f16;
7
- {% endif %}
8
  {{ env.wgsl.resourceDeclarations }}
9
 
10
- {% for output in source.outputs %}
11
  {% set out_count = namespace(value=1) %}
12
  {% for d in output.shape %}
13
  {% set out_count.value = out_count.value * d %}
14
  {% endfor %}
15
  {% set prefix = namespace(value=0) %}
16
- {% for prev in source.outputs[:loop.index0] %}
17
- {% set prefix.value = prefix.value + prev.shape[source.axis] %}
18
  {% endfor %}
19
  fn input_offset_{{ output.name }}({% if out_count.value > 0 %}out_index: u32{% endif %}) -> u32 {
20
  {% if out_count.value == 0 %}
@@ -22,18 +19,18 @@ fn input_offset_{{ output.name }}({% if out_count.value > 0 %}out_index: u32{% e
22
  {% else %}
23
  var rem = out_index;
24
  var offset = 0u;
25
- {% for axis in range(source.rank) %}
26
  {% set out_stride = namespace(value=1) %}
27
- {% for j in range(axis + 1, source.rank) %}
28
  {% set out_stride.value = out_stride.value * output.shape[j] %}
29
  {% endfor %}
30
  {% set input_stride = namespace(value=1) %}
31
- {% for j in range(axis + 1, source.rank) %}
32
- {% set input_stride.value = input_stride.value * source.inputShape[j] %}
33
  {% endfor %}
34
  var coord{{ axis }} = rem / {{ out_stride.value }}u;
35
  rem = rem % {{ out_stride.value }}u;
36
- {% if axis == source.axis and prefix.value > 0 %}
37
  coord{{ axis }} = coord{{ axis }} + {{ prefix.value }}u;
38
  {% endif %}
39
  offset = offset + coord{{ axis }} * {{ input_stride.value }}u;
@@ -44,11 +41,11 @@ fn input_offset_{{ output.name }}({% if out_count.value > 0 %}out_index: u32{% e
44
 
45
  {% endfor %}
46
  @compute @workgroup_size({{ workgroupSize }})
47
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
48
  // 2D-folded flat index: gid.y carries the high bits past the
49
- // maxComputeWorkgroupsPerDimension limit.
50
- let i = gid.x + gid.y * nwg.x * {{ workgroupSize }}u;
51
- {% for output in source.outputs %}
52
  {% set out_count = namespace(value=1) %}
53
  {% for d in output.shape %}
54
  {% set out_count.value = out_count.value * d %}
 
2
  // adding all preceding outputs' cumulative split-axis extent. One invocation
3
  // handles the same flat position across outputs, and each output writes only
4
  // when that position is within its own element count.
 
 
 
5
  {{ env.wgsl.resourceDeclarations }}
6
 
7
+ {% for output in outputs %}
8
  {% set out_count = namespace(value=1) %}
9
  {% for d in output.shape %}
10
  {% set out_count.value = out_count.value * d %}
11
  {% endfor %}
12
  {% set prefix = namespace(value=0) %}
13
+ {% for prev in outputs[:loop.index0] %}
14
+ {% set prefix.value = prefix.value + prev.shape[axisSpec] %}
15
  {% endfor %}
16
  fn input_offset_{{ output.name }}({% if out_count.value > 0 %}out_index: u32{% endif %}) -> u32 {
17
  {% if out_count.value == 0 %}
 
19
  {% else %}
20
  var rem = out_index;
21
  var offset = 0u;
22
+ {% for axis in range(rank) %}
23
  {% set out_stride = namespace(value=1) %}
24
+ {% for j in range(axis + 1, rank) %}
25
  {% set out_stride.value = out_stride.value * output.shape[j] %}
26
  {% endfor %}
27
  {% set input_stride = namespace(value=1) %}
28
+ {% for j in range(axis + 1, rank) %}
29
+ {% set input_stride.value = input_stride.value * inputShape[j] %}
30
  {% endfor %}
31
  var coord{{ axis }} = rem / {{ out_stride.value }}u;
32
  rem = rem % {{ out_stride.value }}u;
33
+ {% if axis == axisSpec and prefix.value > 0 %}
34
  coord{{ axis }} = coord{{ axis }} + {{ prefix.value }}u;
35
  {% endif %}
36
  offset = offset + coord{{ axis }} * {{ input_stride.value }}u;
 
41
 
42
  {% endfor %}
43
  @compute @workgroup_size({{ workgroupSize }})
44
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
45
  // 2D-folded flat index: gid.y carries the high bits past the
46
+ // per-axis dispatch fold width.
47
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ workgroupSize }}u;
48
+ {% for output in outputs %}
49
  {% set out_count = namespace(value=1) %}
50
  {% for d in output.shape %}
51
  {% set out_count.value = out_count.value * d %}
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.Split",
3
  "fixtureArrays": {
4
  "ort_axis2_equal_three_outputs_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
  "ort_axis1_middle_dimension_unequal_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, 25, 26, 27, 28, 29, 30, 31, 32]
@@ -655,7 +654,7 @@
655
  "provenance": {
656
  "source": "onnxruntime/test/providers/cpu/tensor/split_op_test.cc",
657
  "test": "SplitOperatorTest.Uint8NegativeAxis",
658
- "notes": "Logical uint8 payload values are stored one per uint32 slot in this framework."
659
  },
660
  "attrs": { "axis": -1 },
661
  "inputs": {
@@ -675,7 +674,7 @@
675
  "provenance": {
676
  "source": "onnxruntime/test/providers/cpu/tensor/split_op_test.cc",
677
  "test": "SplitOperatorTest.Uint8Axis1SplitMiddleDimensionUnequally",
678
- "notes": "Logical uint8 payload values are stored one per uint32 slot in this framework."
679
  },
680
  "attrs": { "axis": 1 },
681
  "inputs": {
@@ -848,7 +847,7 @@
848
  "outputs": { "y0": { "dtype": "float32", "shape": [2] }, "y1": { "dtype": "float32", "shape": [4] } },
849
  "provenance": {
850
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_split_variable_parts_1d_opset18",
851
- "notes": "ONNX int64 tensors use framework storage where representable. The corresponding opset-13 backend vector is execution-identical on this package surface."
852
  }
853
  },
854
  {
@@ -865,7 +864,7 @@
865
  "outputs": { "y0": { "dtype": "float32", "shape": [2, 2] }, "y1": { "dtype": "float32", "shape": [2, 4] } },
866
  "provenance": {
867
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_split_variable_parts_2d_opset18",
868
- "notes": "ONNX int64 tensors use framework storage where representable. The corresponding opset-13 backend vector is execution-identical on this package surface."
869
  }
870
  },
871
  {
@@ -881,7 +880,7 @@
881
  "outputs": { "y0": { "dtype": "float32", "shape": [2] }, "y1": { "dtype": "float32", "shape": [4] } },
882
  "provenance": {
883
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_split_variable_parts_default_axis_opset18",
884
- "notes": "ONNX int64 tensors use framework storage where representable. The corresponding opset-13 backend vector is execution-identical on this package surface."
885
  }
886
  },
887
  {
@@ -897,7 +896,7 @@
897
  },
898
  "provenance": {
899
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_split_zero_size_splits_opset18",
900
- "notes": "ONNX int64 tensors use framework storage where representable. The corresponding opset-13 backend vector is execution-identical on this package surface."
901
  }
902
  },
903
  {
@@ -944,7 +943,7 @@
944
  {
945
  "name": "rank7_middle_axis_two_outputs",
946
  "provenance": {
947
- "notes": "Compact correctness sibling for the rank7 middle-axis split benchmark; exercises high-rank address arithmetic with an equal two-way split on axis 2."
948
  },
949
  "attrs": { "axis": 2 },
950
  "inputs": {
@@ -1266,7 +1265,7 @@
1266
  {
1267
  "name": "block_scalar_x4_three_outputs_y2_dominant_groups",
1268
  "provenance": {
1269
- "notes": "Three-output block split whose runs are 2/3/6 scalars none a multiple of 4 so the vec4 block variant is ineligible and the scalar-bound-x4 block variant renders. Y2 needs two four-scalar groups per outer row while Y0/Y1 need one each, so the third output's group count is the strict maximum and it, not Y0/Y1, sets the loop bound. Plain ONNX Split semantics: axis=1 with split=[2,3,6] summing to the axis dim 11."
1270
  },
1271
  "attrs": { "axis": 1 },
1272
  "inputs": {
@@ -1296,7 +1295,7 @@
1296
  {
1297
  "name": "block_vec4_four_outputs_axis1_y3_dominant_count",
1298
  "provenance": {
1299
- "notes": "Four-output vec4 block split on a middle axis with strictly growing chunks 2/2/4/6 (rows of inner 2, so 4/4/8/12 scalars, all vec4-aligned). The only four-output vec4 fixture until now split axis 0 into four equal chunks, so nothing exercised a non-zero axis (outer slices > 1 with a per-output row stride) nor a last output whose element count is the strict maximum over all four. Plain ONNX Split semantics: axis=1 with split=[2,2,4,6] summing to the axis dim 14."
1300
  },
1301
  "attrs": { "axis": 1 },
1302
  "inputs": {
@@ -1316,7 +1315,7 @@
1316
  {
1317
  "name": "block_vec4_four_outputs_empty_y3",
1318
  "provenance": {
1319
- "notes": "Four-output vec4 block split whose last chunk is empty (split=[4,4,4,0]): ONNX allows a zero-length entry in `split` as long as the entries are non-negative and sum to the axis dim, and the matching empty output must still be produced. The existing empty-chunk vec4 fixture empties Y0 in a two-output split, so no fixture ever gave the four-output block path a zero-length final run — the case where the fourth copy must be elided entirely instead of reading past the input. Values are a 13-long cycle over 12-wide rows so no repeat aligns with a row or chunk boundary."
1320
  },
1321
  "attrs": { "axis": 1 },
1322
  "inputs": {
 
1
  {
 
2
  "fixtureArrays": {
3
  "ort_axis2_equal_three_outputs_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
  "ort_axis1_middle_dimension_unequal_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, 25, 26, 27, 28, 29, 30, 31, 32]
 
654
  "provenance": {
655
  "source": "onnxruntime/test/providers/cpu/tensor/split_op_test.cc",
656
  "test": "SplitOperatorTest.Uint8NegativeAxis",
657
+ "notes": "Logical uint8 payload values are stored one per uint32 slot."
658
  },
659
  "attrs": { "axis": -1 },
660
  "inputs": {
 
674
  "provenance": {
675
  "source": "onnxruntime/test/providers/cpu/tensor/split_op_test.cc",
676
  "test": "SplitOperatorTest.Uint8Axis1SplitMiddleDimensionUnequally",
677
+ "notes": "Logical uint8 payload values are stored one per uint32 slot."
678
  },
679
  "attrs": { "axis": 1 },
680
  "inputs": {
 
847
  "outputs": { "y0": { "dtype": "float32", "shape": [2] }, "y1": { "dtype": "float32", "shape": [4] } },
848
  "provenance": {
849
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_split_variable_parts_1d_opset18",
850
+ "notes": "This WebGPU package stores representable ONNX int64 tensor values in int32 slots."
851
  }
852
  },
853
  {
 
864
  "outputs": { "y0": { "dtype": "float32", "shape": [2, 2] }, "y1": { "dtype": "float32", "shape": [2, 4] } },
865
  "provenance": {
866
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_split_variable_parts_2d_opset18",
867
+ "notes": "This WebGPU package stores representable ONNX int64 tensor values in int32 slots."
868
  }
869
  },
870
  {
 
880
  "outputs": { "y0": { "dtype": "float32", "shape": [2] }, "y1": { "dtype": "float32", "shape": [4] } },
881
  "provenance": {
882
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_split_variable_parts_default_axis_opset18",
883
+ "notes": "This WebGPU package stores representable ONNX int64 tensor values in int32 slots."
884
  }
885
  },
886
  {
 
896
  },
897
  "provenance": {
898
  "source": "cmake/external/onnx/onnx/backend/test/data/node/test_split_zero_size_splits_opset18",
899
+ "notes": "This WebGPU package stores representable ONNX int64 tensor values in int32 slots."
900
  }
901
  },
902
  {
 
943
  {
944
  "name": "rank7_middle_axis_two_outputs",
945
  "provenance": {
946
+ "notes": "A rank-7 tensor split equally along middle axis 2 exercises high-rank address arithmetic."
947
  },
948
  "attrs": { "axis": 2 },
949
  "inputs": {
 
1265
  {
1266
  "name": "block_scalar_x4_three_outputs_y2_dominant_groups",
1267
  "provenance": {
1268
+ "notes": "Splitting axis 1 into runs of 2, 3, and 6 scalars selects grouped scalar copying because no run is four-aligned. The third output needs two four-scalar groups per row and therefore determines the loop bound."
1269
  },
1270
  "attrs": { "axis": 1 },
1271
  "inputs": {
 
1295
  {
1296
  "name": "block_vec4_four_outputs_axis1_y3_dominant_count",
1297
  "provenance": {
1298
+ "notes": "A four-output vec4 block split on axis 1 uses chunks 2/2/4/6 with inner=2, giving 4/4/8/12 aligned scalars per outer slice. Multiple outer slices exercise each output row stride, and the final output has the largest element count."
1299
  },
1300
  "attrs": { "axis": 1 },
1301
  "inputs": {
 
1315
  {
1316
  "name": "block_vec4_four_outputs_empty_y3",
1317
  "provenance": {
1318
+ "notes": "A four-output vectorized block split uses lengths [4,4,4,0]. ONNX permits the zero-length final chunk, which must be emitted without a copy or out-of-bounds input read. A 13-value cycle over 12-wide rows avoids alignment between values, rows, and chunk boundaries."
1319
  },
1320
  "attrs": { "axis": 1 },
1321
  "inputs": {