Xenova HF Staff commited on
Commit
a87be84
·
verified ·
1 Parent(s): f7581c5

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,17 +18,17 @@ See the [ONNX `Where` spec](https://onnx.ai/onnx/operators/onnx__Where.html) for
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
- | `condition` | `condition` | `C` | — | — | Boolean mask; nonzero entries select from X, zero entries select from Y. | required |
24
- | `X` | `x` | `T` | — | — | Values selected at indices where condition is true. | required |
25
- | `Y` | `y` | `T` | — | — | Values selected at indices where condition is false. | required |
26
 
27
  ## Outputs
28
 
29
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
30
- | --- | --- | --- | --- | --- | --- | --- |
31
- | `output` | `output` | `T` | derived | broadcast result of `condition`, `X`, and `Y` | Tensor of shape equal to the broadcasted shape of condition, X, and Y. | required |
32
 
33
  ## Type constraints
34
 
@@ -39,7 +39,7 @@ See the [ONNX `Where` spec](https://onnx.ai/onnx/operators/onnx__Where.html) for
39
 
40
  ## Files
41
 
42
- - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
43
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
44
  - [`test.json`](build/webgpu/test.json) — correctness cases
45
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
@@ -48,10 +48,14 @@ See the [ONNX `Where` spec](https://onnx.ai/onnx/operators/onnx__Where.html) for
48
 
49
  ## Use with `@huggingface/kernels`
50
 
51
- The loader derives every required output's shape and logical dtype from the manifest contract and this call.
52
- It then allocates the result tensors automatically.
 
 
 
53
 
54
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
55
 
56
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
57
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
+ | `condition` | — | `C` | — | — | Boolean mask; nonzero entries select from X, zero entries select from Y. | required |
24
+ | `x` | `X` | `T` | — | — | Values selected at indices where condition is true. | required |
25
+ | `y` | `Y` | `T` | — | — | Values selected at indices where condition is false. | required |
26
 
27
  ## Outputs
28
 
29
+ | Name | Logical dtype | Rank | Shape | Description | Presence |
30
+ | --- | --- | --- | --- | --- | --- |
31
+ | `output` | `T` | derived | broadcast result of `condition`, `x`, and `y` | Tensor of shape equal to the broadcasted shape of condition, X, and Y. | required |
32
 
33
  ## Type constraints
34
 
 
39
 
40
  ## Files
41
 
42
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
43
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
44
  - [`test.json`](build/webgpu/test.json) — correctness cases
45
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
48
 
49
  ## Use with `@huggingface/kernels`
50
 
51
+ ```sh
52
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
53
+ ```
54
+
55
+ Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
56
 
57
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
58
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
59
 
60
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
61
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.Where",
3
  "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
4
  "cases": [
5
  {
@@ -7,7 +6,7 @@
7
  "preset": "smoke",
8
  "vars": { "dtype": "float32", "count": 1048576 },
9
  "inputs": {
10
- "condition": { "shape": [1048576], "dtype": "bool", "dist": "uniform", "seed": 870, "scale": 1 },
11
  "x": { "shape": [1048576], "dtype": "float32", "dist": "normal", "seed": 871, "scale": 1 },
12
  "y": { "shape": [1048576], "dtype": "float32", "dist": "normal", "seed": 872, "scale": 1 }
13
  },
@@ -59,7 +58,7 @@
59
  "preset": "smoke",
60
  "vars": { "dtype": "float16", "count": 1048576 },
61
  "inputs": {
62
- "condition": { "shape": [1048576], "dtype": "bool", "dist": "uniform", "seed": 970, "scale": 1 },
63
  "x": { "shape": [1048576], "dtype": "float16", "dist": "normal", "seed": 971, "scale": 1 },
64
  "y": { "shape": [1048576], "dtype": "float16", "dist": "normal", "seed": 972, "scale": 1 }
65
  },
@@ -73,7 +72,7 @@
73
  "preset": "smoke",
74
  "vars": { "dtype": "float32", "count": 1048575 },
75
  "inputs": {
76
- "condition": { "shape": [1048575], "dtype": "bool", "dist": "uniform", "seed": 973, "scale": 1 },
77
  "x": { "shape": [1048575], "dtype": "float32", "dist": "normal", "seed": 974, "scale": 1 },
78
  "y": { "shape": [1048575], "dtype": "float32", "dist": "normal", "seed": 975, "scale": 1 }
79
  },
 
1
  {
 
2
  "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
3
  "cases": [
4
  {
 
6
  "preset": "smoke",
7
  "vars": { "dtype": "float32", "count": 1048576 },
8
  "inputs": {
9
+ "condition": { "shape": [1048576], "dtype": "bool", "dist": "uniform", "seed": 870 },
10
  "x": { "shape": [1048576], "dtype": "float32", "dist": "normal", "seed": 871, "scale": 1 },
11
  "y": { "shape": [1048576], "dtype": "float32", "dist": "normal", "seed": 872, "scale": 1 }
12
  },
 
58
  "preset": "smoke",
59
  "vars": { "dtype": "float16", "count": 1048576 },
60
  "inputs": {
61
+ "condition": { "shape": [1048576], "dtype": "bool", "dist": "uniform", "seed": 970 },
62
  "x": { "shape": [1048576], "dtype": "float16", "dist": "normal", "seed": 971, "scale": 1 },
63
  "y": { "shape": [1048576], "dtype": "float16", "dist": "normal", "seed": 972, "scale": 1 }
64
  },
 
72
  "preset": "smoke",
73
  "vars": { "dtype": "float32", "count": 1048575 },
74
  "inputs": {
75
+ "condition": { "shape": [1048575], "dtype": "bool", "dist": "uniform", "seed": 973 },
76
  "x": { "shape": [1048575], "dtype": "float32", "dist": "normal", "seed": 974, "scale": 1 },
77
  "y": { "shape": [1048575], "dtype": "float32", "dist": "normal", "seed": 975, "scale": 1 }
78
  },
build/webgpu/manifest.json CHANGED
@@ -2,268 +2,132 @@
2
  "domain": "ai.onnx",
3
  "name": "Where",
4
  "sinceVersion": 16,
5
- "description": "Selects elements from `X` or `Y` according to a boolean `condition`, following NumPy-style multidirectional broadcasting. Where `condition` is `true`, the output takes the value from `X`; otherwise it takes the value from `Y`.",
6
- "inputs": [
7
- {
8
- "role": "condition",
9
- "dtype": "C",
10
- "description": "Boolean mask; nonzero entries select from X, zero entries select from Y."
11
- },
12
- { "role": "X", "dtype": "T", "description": "Values selected at indices where condition is true." },
13
- { "role": "Y", "dtype": "T", "description": "Values selected at indices where condition is false." }
14
- ],
15
- "outputs": [
16
- {
17
- "role": "output",
18
  "dtype": "T",
19
- "rank": "max(max(ranks.condition, ranks.X), ranks.Y)",
20
- "description": "Tensor of shape equal to the broadcasted shape of condition, X, and Y.",
21
- "shape": "broadcastShape(broadcastShape(shapes.condition, shapes.X), shapes.Y)"
22
  }
23
- ],
24
  "typeConstraints": { "C": ["bool"], "T": ["float32", "float16", "uint32", "int32", "int16", "uint8", "int8", "bool"] },
25
- "args": {
26
- "condition": { "kind": "tensor", "semantic": "condition", "role": "input" },
27
- "x": { "kind": "tensor", "semantic": "X", "role": "input" },
28
- "y": { "kind": "tensor", "semantic": "Y", "role": "input" },
29
- "output": { "kind": "tensor", "semantic": "output", "role": "output" }
 
 
 
 
 
 
 
 
 
30
  },
31
- "tunables": { "WORKGROUP_SIZE": 256 },
32
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
33
  "variants": [
34
  {
35
  "id": "broadcast",
36
- "when": ["ranks.condition <= ranks.output", "ranks.X <= ranks.output", "ranks.Y <= ranks.output", "f16Ok(dtypes.T)"],
37
  "passes": [
38
  {
39
  "id": "main",
40
  "name": "Where",
41
- "source": {
42
- "shader": "where-broadcast.wgsl.jinja",
43
- "inputs": {
44
- "cShape": "shapes.condition",
45
- "xShape": "shapes.X",
46
- "yShape": "shapes.Y",
47
- "oShape": "shapes.output",
48
- "cRank": "ranks.condition",
49
- "xRank": "ranks.X",
50
- "yRank": "ranks.Y",
51
- "oRank": "ranks.output",
52
- "itemsPerInvocation": 4
53
- }
54
  },
55
  "bindings": [
56
- {
57
- "name": "condition",
58
- "arg": "condition",
59
- "semantic": "condition",
60
- "buffer": { "type": "read-only-storage" },
61
- "elementType": "u32"
62
- },
63
- {
64
- "name": "x",
65
- "arg": "x",
66
- "semantic": "X",
67
- "buffer": { "type": "read-only-storage" },
68
- "elementType": "$scalar"
69
- },
70
- {
71
- "name": "y",
72
- "arg": "y",
73
- "semantic": "Y",
74
- "buffer": { "type": "read-only-storage" },
75
- "elementType": "$scalar"
76
- },
77
- {
78
- "name": "output",
79
- "arg": "output",
80
- "semantic": "output",
81
- "buffer": { "type": "storage" },
82
- "elementType": "$scalar"
83
- },
84
- {
85
- "name": "params",
86
- "semantic": "kernel.params",
87
- "buffer": { "type": "uniform" },
88
- "struct": {
89
- "name": "Params",
90
- "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.output)" }]
91
- }
92
- }
93
  ],
94
- "dispatch": { "threads": "ceilDiv(numel(shapes.output), 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
95
  }
96
  ]
97
  },
98
  {
99
  "id": "broadcast_inner_vec4",
100
  "priority": 1,
101
- "when": ["ranks.output >= 1", "numel(shapes.output) > 0", "dim(shapes.output, ranks.output - 1) % 4 == 0", "(ranks.condition == 0 or dim(shapes.condition, ranks.condition - 1) == 1 or dim(shapes.condition, ranks.condition - 1) == dim(shapes.output, ranks.output - 1))", "(ranks.X == 0 or dim(shapes.X, ranks.X - 1) == 1 or dim(shapes.X, ranks.X - 1) == dim(shapes.output, ranks.output - 1))", "(ranks.Y == 0 or dim(shapes.Y, ranks.Y - 1) == 1 or dim(shapes.Y, ranks.Y - 1) == dim(shapes.output, ranks.output - 1))", "ranks.condition <= ranks.output", "ranks.X <= ranks.output", "ranks.Y <= ranks.output", "f16Ok(dtypes.T)"],
102
- "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
103
  "passes": [
104
  {
105
  "id": "main",
106
  "name": "Where",
107
- "source": {
108
- "shader": "where-broadcast.wgsl.jinja",
109
- "inputs": {
110
- "cShape": "shapes.condition",
111
- "xShape": "shapes.X",
112
- "yShape": "shapes.Y",
113
- "oShape": "shapes.output",
114
- "cRank": "ranks.condition",
115
- "xRank": "ranks.X",
116
- "yRank": "ranks.Y",
117
- "oRank": "ranks.output",
118
- "vec4Out": true
119
- }
120
  },
121
- "bindings": [
122
- {
123
- "name": "condition",
124
- "arg": "condition",
125
- "semantic": "condition",
126
- "buffer": { "type": "read-only-storage" },
127
- "elementType": "u32"
128
- },
129
- {
130
- "name": "x",
131
- "arg": "x",
132
- "semantic": "X",
133
- "buffer": { "type": "read-only-storage" },
134
- "elementType": "$scalar"
135
- },
136
- {
137
- "name": "y",
138
- "arg": "y",
139
- "semantic": "Y",
140
- "buffer": { "type": "read-only-storage" },
141
- "elementType": "$scalar"
142
- },
143
- {
144
- "name": "output",
145
- "arg": "output",
146
- "semantic": "output",
147
- "buffer": { "type": "storage" },
148
- "elementType": "$vectorScalar"
149
- },
150
- {
151
- "name": "params",
152
- "semantic": "kernel.params",
153
- "buffer": { "type": "uniform" },
154
- "struct": {
155
- "name": "Params",
156
- "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.output) / 4" }]
157
- }
158
- }
159
- ],
160
- "dispatch": { "threads": "numel(shapes.output) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
161
  }
162
  ]
163
  },
164
  {
165
  "id": "all_same_shape_vec4",
166
  "priority": 30,
167
- "when": ["sameShape(shapes.condition, shapes.output)", "sameShape(shapes.X, shapes.output)", "sameShape(shapes.Y, shapes.output)", "numel(shapes.output) > 0", "numel(shapes.output) % 4 == 0", "f16Ok(dtypes.T)"],
168
- "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
169
  "passes": [
170
  {
171
  "id": "main",
172
  "name": "Where.vec4",
173
- "source": { "shader": "where-vec4.wgsl.jinja", "inputs": { "condMode": "\"vector\"" } },
174
- "bindings": [
175
- {
176
- "name": "condition",
177
- "arg": "condition",
178
- "semantic": "condition",
179
- "buffer": { "type": "read-only-storage" },
180
- "elementType": "vec4<u32>"
181
- },
182
- {
183
- "name": "x",
184
- "arg": "x",
185
- "semantic": "X",
186
- "buffer": { "type": "read-only-storage" },
187
- "elementType": "$vectorScalar"
188
- },
189
- {
190
- "name": "y",
191
- "arg": "y",
192
- "semantic": "Y",
193
- "buffer": { "type": "read-only-storage" },
194
- "elementType": "$vectorScalar"
195
- },
196
- {
197
- "name": "output",
198
- "arg": "output",
199
- "semantic": "output",
200
- "buffer": { "type": "storage" },
201
- "elementType": "$vectorScalar"
202
- },
203
- {
204
- "name": "params",
205
- "semantic": "kernel.params",
206
- "buffer": { "type": "uniform" },
207
- "struct": {
208
- "name": "Params",
209
- "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.output) / 4" }]
210
- }
211
- }
212
- ],
213
- "dispatch": { "threads": "numel(shapes.output) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
214
  }
215
  ]
216
  },
217
  {
218
  "id": "scalar_condition_vec4",
219
  "priority": 25,
220
- "when": ["numel(shapes.condition) == 1", "sameShape(shapes.X, shapes.output)", "sameShape(shapes.Y, shapes.output)", "numel(shapes.output) > 0", "numel(shapes.output) % 4 == 0", "f16Ok(dtypes.T)"],
221
- "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
222
  "passes": [
223
  {
224
  "id": "main",
225
  "name": "Where.scalarCondVec4",
226
- "source": { "shader": "where-vec4.wgsl.jinja", "inputs": { "condMode": "\"scalar\"" } },
227
- "bindings": [
228
- {
229
- "name": "condition",
230
- "arg": "condition",
231
- "semantic": "condition",
232
- "buffer": { "type": "read-only-storage" },
233
- "elementType": "u32"
234
- },
235
- {
236
- "name": "x",
237
- "arg": "x",
238
- "semantic": "X",
239
- "buffer": { "type": "read-only-storage" },
240
- "elementType": "$vectorScalar"
241
- },
242
- {
243
- "name": "y",
244
- "arg": "y",
245
- "semantic": "Y",
246
- "buffer": { "type": "read-only-storage" },
247
- "elementType": "$vectorScalar"
248
- },
249
- {
250
- "name": "output",
251
- "arg": "output",
252
- "semantic": "output",
253
- "buffer": { "type": "storage" },
254
- "elementType": "$vectorScalar"
255
- },
256
- {
257
- "name": "params",
258
- "semantic": "kernel.params",
259
- "buffer": { "type": "uniform" },
260
- "struct": {
261
- "name": "Params",
262
- "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.output) / 4" }]
263
- }
264
- }
265
- ],
266
- "dispatch": { "threads": "numel(shapes.output) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
267
  }
268
  ]
269
  }
 
2
  "domain": "ai.onnx",
3
  "name": "Where",
4
  "sinceVersion": 16,
5
+ "inputs": { "condition": { "dtype": "C" }, "x": { "onnx": "X", "dtype": "T" }, "y": { "onnx": "Y", "dtype": "T" } },
6
+ "outputs": {
7
+ "output": {
 
 
 
 
 
 
 
 
 
 
8
  "dtype": "T",
9
+ "rank": "max(max(ranks.condition, ranks.x), ranks.y)",
10
+ "shape": "broadcastShape(broadcastShape(shapes.condition, shapes.x), shapes.y)"
 
11
  }
12
+ },
13
  "typeConstraints": { "C": ["bool"], "T": ["float32", "float16", "uint32", "int32", "int16", "uint8", "int8", "bool"] },
14
+ "tunables": { "WORKGROUP_SIZE": { "default": 256 } },
15
+ "derive": { "scalar": "dtypes.T" },
16
+ "when": ["f16Ok(dtypes.T)"],
17
+ "bindings": {
18
+ "condition": { "buffer": "read-only-storage", "elementType": "u32" },
19
+ "output_2": { "name": "output", "buffer": "storage", "elementType": "$vectorScalar" },
20
+ "params_2": {
21
+ "name": "params",
22
+ "buffer": "uniform",
23
+ "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.output) / 4" }]
24
+ },
25
+ "condition_2": { "name": "condition", "buffer": "read-only-storage", "elementType": "vec4<u32>" },
26
+ "x_2": { "name": "x", "buffer": "read-only-storage", "elementType": "$vectorScalar" },
27
+ "y_2": { "name": "y", "buffer": "read-only-storage", "elementType": "$vectorScalar" }
28
  },
 
 
29
  "variants": [
30
  {
31
  "id": "broadcast",
32
+ "when": ["ranks.condition <= ranks.output", "ranks.x <= ranks.output", "ranks.y <= ranks.output"],
33
  "passes": [
34
  {
35
  "id": "main",
36
  "name": "Where",
37
+ "shader": "where-broadcast.wgsl.jinja",
38
+ "derive": {
39
+ "cShape": "shapes.condition",
40
+ "xShape": "shapes.x",
41
+ "yShape": "shapes.y",
42
+ "oShape": "shapes.output",
43
+ "cRank": "ranks.condition",
44
+ "xRank": "ranks.x",
45
+ "yRank": "ranks.y",
46
+ "oRank": "ranks.output",
47
+ "itemsPerInvocation": 4
 
 
48
  },
49
  "bindings": [
50
+ "condition",
51
+ "x",
52
+ "y",
53
+ "output",
54
+ { "name": "params", "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.output)" }] }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  ],
56
+ "dispatch": {
57
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.output), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
58
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.output), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
59
+ "z": 1
60
+ }
61
  }
62
  ]
63
  },
64
  {
65
  "id": "broadcast_inner_vec4",
66
  "priority": 1,
67
+ "when": ["ranks.output >= 1", "numel(shapes.output) > 0", "dim(shapes.output, ranks.output - 1) % 4 == 0", "(ranks.condition == 0 or dim(shapes.condition, ranks.condition - 1) == 1 or dim(shapes.condition, ranks.condition - 1) == dim(shapes.output, ranks.output - 1))", "(ranks.x == 0 or dim(shapes.x, ranks.x - 1) == 1 or dim(shapes.x, ranks.x - 1) == dim(shapes.output, ranks.output - 1))", "(ranks.y == 0 or dim(shapes.y, ranks.y - 1) == 1 or dim(shapes.y, ranks.y - 1) == dim(shapes.output, ranks.output - 1))", "ranks.condition <= ranks.output", "ranks.x <= ranks.output", "ranks.y <= ranks.output"],
68
+ "derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
69
  "passes": [
70
  {
71
  "id": "main",
72
  "name": "Where",
73
+ "shader": "where-broadcast.wgsl.jinja",
74
+ "derive": {
75
+ "cShape": "shapes.condition",
76
+ "xShape": "shapes.x",
77
+ "yShape": "shapes.y",
78
+ "oShape": "shapes.output",
79
+ "cRank": "ranks.condition",
80
+ "xRank": "ranks.x",
81
+ "yRank": "ranks.y",
82
+ "oRank": "ranks.output",
83
+ "vec4Out": true
 
 
84
  },
85
+ "bindings": ["condition", "x", "y", "output_2", "params_2"],
86
+ "dispatch": {
87
+ "x": "min(ceilDiv((numel(shapes.output) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
88
+ "y": "ceilDiv(ceilDiv((numel(shapes.output) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
89
+ "z": 1
90
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
92
  ]
93
  },
94
  {
95
  "id": "all_same_shape_vec4",
96
  "priority": 30,
97
+ "when": ["sameShape(shapes.condition, shapes.output)", "sameShape(shapes.x, shapes.output)", "sameShape(shapes.y, shapes.output)", "numel(shapes.output) > 0", "numel(shapes.output) % 4 == 0"],
98
+ "derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
99
  "passes": [
100
  {
101
  "id": "main",
102
  "name": "Where.vec4",
103
+ "shader": "where-vec4.wgsl.jinja",
104
+ "derive": { "condMode": "\"vector\"" },
105
+ "bindings": ["condition_2", "x_2", "y_2", "output_2", "params_2"],
106
+ "dispatch": {
107
+ "x": "min(ceilDiv((numel(shapes.output) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
108
+ "y": "ceilDiv(ceilDiv((numel(shapes.output) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
109
+ "z": 1
110
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  }
112
  ]
113
  },
114
  {
115
  "id": "scalar_condition_vec4",
116
  "priority": 25,
117
+ "when": ["numel(shapes.condition) == 1", "sameShape(shapes.x, shapes.output)", "sameShape(shapes.y, shapes.output)", "numel(shapes.output) > 0", "numel(shapes.output) % 4 == 0"],
118
+ "derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
119
  "passes": [
120
  {
121
  "id": "main",
122
  "name": "Where.scalarCondVec4",
123
+ "shader": "where-vec4.wgsl.jinja",
124
+ "derive": { "condMode": "\"scalar\"" },
125
+ "bindings": ["condition", "x_2", "y_2", "output_2", "params_2"],
126
+ "dispatch": {
127
+ "x": "min(ceilDiv((numel(shapes.output) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
128
+ "y": "ceilDiv(ceilDiv((numel(shapes.output) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
129
+ "z": 1
130
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  }
132
  ]
133
  }
build/webgpu/metadata.json CHANGED
@@ -1,19 +1,27 @@
1
  {
2
  "name": "ai.onnx.Where",
3
- "id": "_ai_onnx_where_webgpu_ffdaf20",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "VyEb3ADRUxJJwyaSc9I4fNcWHPy0yqIKtT8Bm3H1WX8=",
11
- "manifest.json": "q6/trJ9PjIgZtZLhHHnZeBm6zSHhY6xTpkTGJtQmsZY=",
12
- "test.json": "BeLATZd7ICyT+vvjrY04kpYaDrb3BPPBZUo676qxX24=",
13
- "where-broadcast.wgsl.jinja": "xPK1v4t7GBnKJl4YYPF0J6lf3u2pUilCgvefOgKlauE=",
14
- "where-vec4.wgsl.jinja": "EXTMQw4HgDmNJp+QU4QyFhep584Ch0kd4x1OdB7KC8w="
15
  }
16
  },
17
- "provenance": { "kernel": { "sha": "c928d21e6cc1310861cba3bafb75f5f679ecf5f3", "dirty": false } },
18
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.Where" }
 
 
 
 
 
 
 
 
19
  }
 
1
  {
2
  "name": "ai.onnx.Where",
3
+ "id": "_ai_onnx_where_webgpu_5d1b103",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "skxPQI/vUW/oEssq1hdPgFGDyPZYLC0kDHmeyzLR2yY=",
11
+ "manifest.json": "64IxOIxpn6DCKhM59kpc8GKd98yH/HeAA/d/Gp+UZLo=",
12
+ "test.json": "OCgBwQVsPNU0gH93TL5AWMMsMy2aG9EGLiia2+Xi0GA=",
13
+ "where-broadcast.wgsl.jinja": "bYDdm4/2eF9cUf/EGEMys3FeP970NKGKJRafMO+ZxHQ=",
14
+ "where-vec4.wgsl.jinja": "u2PIkiP+3lcTS/Df7xkzylyTNEONTHgEHZSV3c14ZyE="
15
  }
16
  },
17
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
18
+ "webgpu": {
19
+ "manifestSpec": "2.0",
20
+ "variants": {
21
+ "broadcast": ["where-broadcast.wgsl.jinja"],
22
+ "broadcast_inner_vec4": ["where-broadcast.wgsl.jinja"],
23
+ "all_same_shape_vec4": ["where-vec4.wgsl.jinja"],
24
+ "scalar_condition_vec4": ["where-vec4.wgsl.jinja"]
25
+ }
26
+ }
27
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.Where",
3
  "cases": [
4
  {
5
  "name": "int16_same_shape_boundaries",
@@ -144,7 +143,7 @@
144
  "provenance": {
145
  "source": "onnxruntime/test/providers/cpu/tensor/where_op_test.cc",
146
  "test": "WhereOpTest.BasicNumeric",
147
- "notes": "ORT covers float and double; this framework also checks the same branch pattern for float16."
148
  },
149
  "inputs": {
150
  "condition": { "dtype": "bool", "shape": [2, 2], "data": { "kind": "values", "values": [0, 1, 1, 0] } },
@@ -184,7 +183,7 @@
184
  "provenance": {
185
  "source": "onnxruntime/test/providers/cpu/tensor/where_op_test.cc",
186
  "test": "WhereOpTest.BroadcastWithScalar",
187
- "notes": "ORT uses int64 values; this framework covers the same scalar broadcast shape behavior with float32 data."
188
  },
189
  "inputs": {
190
  "condition": { "dtype": "bool", "shape": [3], "data": { "kind": "values", "values": [1, 0, 1] } },
@@ -198,7 +197,7 @@
198
  "provenance": {
199
  "source": "onnxruntime/test/providers/cpu/tensor/where_op_test.cc",
200
  "test": "WhereOpTest.BroadcastWithScalar",
201
- "notes": "ORT uses int64 values; this framework covers the same scalar broadcast shape behavior with int32 data."
202
  },
203
  "inputs": {
204
  "condition": { "dtype": "bool", "shape": [3], "data": { "kind": "values", "values": [1, 0, 1] } },
@@ -308,9 +307,7 @@
308
  },
309
  {
310
  "name": "scalar_condition_true_vec4_4096",
311
- "provenance": {
312
- "notes": "Compact sibling for the scalar-condition Where benchmark; preserves a shape-[1] condition broadcasting over a vec4-aligned f32 payload."
313
- },
314
  "inputs": {
315
  "condition": { "dtype": "bool", "shape": [1], "data": { "kind": "values", "values": [1] } },
316
  "x": {
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "int16_same_shape_boundaries",
 
143
  "provenance": {
144
  "source": "onnxruntime/test/providers/cpu/tensor/where_op_test.cc",
145
  "test": "WhereOpTest.BasicNumeric",
146
+ "notes": "A float16 payload exercises the branch pattern with reduced-precision storage."
147
  },
148
  "inputs": {
149
  "condition": { "dtype": "bool", "shape": [2, 2], "data": { "kind": "values", "values": [0, 1, 1, 0] } },
 
183
  "provenance": {
184
  "source": "onnxruntime/test/providers/cpu/tensor/where_op_test.cc",
185
  "test": "WhereOpTest.BroadcastWithScalar",
186
+ "notes": "A float32 payload exercises scalar broadcast shape behavior."
187
  },
188
  "inputs": {
189
  "condition": { "dtype": "bool", "shape": [3], "data": { "kind": "values", "values": [1, 0, 1] } },
 
197
  "provenance": {
198
  "source": "onnxruntime/test/providers/cpu/tensor/where_op_test.cc",
199
  "test": "WhereOpTest.BroadcastWithScalar",
200
+ "notes": "An int32 payload exercises scalar broadcast shape behavior."
201
  },
202
  "inputs": {
203
  "condition": { "dtype": "bool", "shape": [3], "data": { "kind": "values", "values": [1, 0, 1] } },
 
307
  },
308
  {
309
  "name": "scalar_condition_true_vec4_4096",
310
+ "provenance": { "notes": "A shape-[1] condition broadcasts over a vec4-aligned float32 payload." },
 
 
311
  "inputs": {
312
  "condition": { "dtype": "bool", "shape": [1], "data": { "kind": "values", "values": [1] } },
313
  "x": {
build/webgpu/where-broadcast.wgsl.jinja CHANGED
@@ -1,31 +1,20 @@
1
  {% macro flat_tail_open() %}
2
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
3
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
4
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
5
- // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills the rest into y).
6
- let invocation = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
7
- {% if source.itemsPerInvocation is defined %}
8
  // Tail-safe scalar x4 keeps vector-like dispatch density without requiring
9
  // the logical tensor length (or its storage binding) to be vec4 aligned.
10
- let begin = invocation * {{ source.itemsPerInvocation }}u;
11
- let end = min(begin + {{ source.itemsPerInvocation }}u, params.count);
 
12
  for (var i = begin; i < end; i = i + 1u) {
13
- {%- else %}
14
- let i = invocation;
15
- if (i >= params.count) {
16
- return;
17
- }
18
- {%- endif %}
19
- {% endmacro %}
20
  {% macro flat_tail_close() %}
21
- {% if source.itemsPerInvocation is defined %}
22
  }
23
- {% endif %}
24
  {% endmacro %}
25
 
26
- {% if usesF16 %}
27
- enable f16;
28
- {% endif %}
29
  {{ env.wgsl.resourceDeclarations }}
30
 
31
  {% macro offset_fn(fn_name, opShape, opRank, op_same, op_numel, outShape, outRank, out_numel) %}
@@ -91,33 +80,33 @@ fn {{ fn_name }}({% if out_numel != 0 and op_numel != 1 %}out_index: u32{% endif
91
  {%- endmacro %}
92
 
93
 
94
- {{ broadcast_offset_fn("c_offset", source.cShape, source.cRank, source.oShape, source.oRank) }}
95
 
96
- {{ broadcast_offset_fn("x_offset", source.xShape, source.xRank, source.oShape, source.oRank) }}
97
 
98
- {{ broadcast_offset_fn("y_offset", source.yShape, source.yRank, source.oShape, source.oRank) }}
99
 
100
- {% set vec4Out = source.vec4Out | default(false) %}
101
  {% if vec4Out %}
102
  // One invocation handles one output vec4. Its four elements share every outer
103
  // coordinate. Each input offset either advances by one when its last axis
104
  // matches the output, or remains fixed when that axis is broadcast.
105
- {% set o_last = source.oShape[source.oRank - 1] %}
106
- {% set c_inner = source.cRank > 0 and source.cShape[source.cRank - 1] == o_last %}
107
- {% set x_inner = source.xRank > 0 and source.xShape[source.xRank - 1] == o_last %}
108
- {% set y_inner = source.yRank > 0 and source.yShape[source.yRank - 1] == o_last %}
109
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
110
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
111
  // 2D-folded flat index: gid.y carries the high bits past the
112
- // maxComputeWorkgroupsPerDimension limit.
113
- let vi = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
114
  if (vi >= params.count) {
115
  return;
116
  }
117
  let base = vi * 4u;
118
- let cb = {{ broadcast_offset_call("c_offset", source.cShape, source.oShape, "base") }};
119
- let xb = {{ broadcast_offset_call("x_offset", source.xShape, source.oShape, "base") }};
120
- let yb = {{ broadcast_offset_call("y_offset", source.yShape, source.oShape, "base") }};
121
  {% if c_inner %}
122
  let cond = vec4<u32>(condition[cb], condition[cb + 1u], condition[cb + 2u], condition[cb + 3u]);
123
  {% else %}
@@ -137,7 +126,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
137
  }
138
  {% else %}
139
  {{ flat_tail_open() }}
140
- output[i] = select(y[{{ broadcast_offset_call("y_offset", source.yShape, source.oShape, "i") }}], x[{{ broadcast_offset_call("x_offset", source.xShape, source.oShape, "i") }}], condition[{{ broadcast_offset_call("c_offset", source.cShape, source.oShape, "i") }}] != 0u);
141
  {{ flat_tail_close() -}}
142
  }
143
  {% endif %}
 
1
  {% macro flat_tail_open() %}
2
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
3
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
4
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
5
+ // dispatch's per-axis workgroup fold width (the dispatch caps x and spills the rest into y).
6
+ let invocation = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
 
7
  // Tail-safe scalar x4 keeps vector-like dispatch density without requiring
8
  // the logical tensor length (or its storage binding) to be vec4 aligned.
9
+ {% set itemsPerInvocation = itemsPerInvocation if itemsPerInvocation is defined else 4 %}
10
+ let begin = invocation * {{ itemsPerInvocation }}u;
11
+ let end = min(begin + {{ itemsPerInvocation }}u, params.count);
12
  for (var i = begin; i < end; i = i + 1u) {
13
+ {%- endmacro %}
 
 
 
 
 
 
14
  {% macro flat_tail_close() %}
 
15
  }
 
16
  {% endmacro %}
17
 
 
 
 
18
  {{ env.wgsl.resourceDeclarations }}
19
 
20
  {% macro offset_fn(fn_name, opShape, opRank, op_same, op_numel, outShape, outRank, out_numel) %}
 
80
  {%- endmacro %}
81
 
82
 
83
+ {{ broadcast_offset_fn("c_offset", cShape, cRank, oShape, oRank) }}
84
 
85
+ {{ broadcast_offset_fn("x_offset", xShape, xRank, oShape, oRank) }}
86
 
87
+ {{ broadcast_offset_fn("y_offset", yShape, yRank, oShape, oRank) }}
88
 
89
+ {% set vec4Out = vec4Out | default(false) %}
90
  {% if vec4Out %}
91
  // One invocation handles one output vec4. Its four elements share every outer
92
  // coordinate. Each input offset either advances by one when its last axis
93
  // matches the output, or remains fixed when that axis is broadcast.
94
+ {% set o_last = oShape[oRank - 1] %}
95
+ {% set c_inner = cRank > 0 and cShape[cRank - 1] == o_last %}
96
+ {% set x_inner = xRank > 0 and xShape[xRank - 1] == o_last %}
97
+ {% set y_inner = yRank > 0 and yShape[yRank - 1] == o_last %}
98
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
99
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
100
  // 2D-folded flat index: gid.y carries the high bits past the
101
+ // per-axis dispatch fold width.
102
+ let vi = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
103
  if (vi >= params.count) {
104
  return;
105
  }
106
  let base = vi * 4u;
107
+ let cb = {{ broadcast_offset_call("c_offset", cShape, oShape, "base") }};
108
+ let xb = {{ broadcast_offset_call("x_offset", xShape, oShape, "base") }};
109
+ let yb = {{ broadcast_offset_call("y_offset", yShape, oShape, "base") }};
110
  {% if c_inner %}
111
  let cond = vec4<u32>(condition[cb], condition[cb + 1u], condition[cb + 2u], condition[cb + 3u]);
112
  {% else %}
 
126
  }
127
  {% else %}
128
  {{ flat_tail_open() }}
129
+ output[i] = select(y[{{ broadcast_offset_call("y_offset", yShape, oShape, "i") }}], x[{{ broadcast_offset_call("x_offset", xShape, oShape, "i") }}], condition[{{ broadcast_offset_call("c_offset", cShape, oShape, "i") }}] != 0u);
130
  {{ flat_tail_close() -}}
131
  }
132
  {% endif %}
build/webgpu/where-vec4.wgsl.jinja CHANGED
@@ -1,17 +1,14 @@
1
- {% if usesF16 %}
2
- enable f16;
3
- {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
5
 
6
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
7
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
8
  // 2D-folded flat index: gid.y carries the high bits past the
9
- // maxComputeWorkgroupsPerDimension limit.
10
- let i = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
11
  if (i >= params.count) {
12
  return;
13
  }
14
- {% if source.condMode == "scalar" %}
15
  output[i] = select(y[i], x[i], condition[0] != 0u);
16
  {% else %}
17
  output[i] = select(y[i], x[i], condition[i] != vec4<u32>(0u));
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
4
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
5
  // 2D-folded flat index: gid.y carries the high bits past the
6
+ // per-axis dispatch fold width.
7
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
8
  if (i >= params.count) {
9
  return;
10
  }
11
+ {% if condMode == "scalar" %}
12
  output[i] = select(y[i], x[i], condition[0] != 0u);
13
  {% else %}
14
  output[i] = select(y[i], x[i], condition[i] != vec4<u32>(0u));