Xenova HF Staff commited on
Commit
1056d1c
·
verified ·
1 Parent(s): d047a99

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,16 +18,16 @@ See the [ONNX `CastLike` spec](https://onnx.ai/onnx/operators/onnx__CastLike.htm
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
- | `input` | `x` | `T1` | — | — | Input tensor whose elements are to be cast. | required |
24
- | `target_type` | `target` | `T2` | — | — | Tensor whose element type defines the destination dtype; its values are not used. | required |
25
 
26
  ## Outputs
27
 
28
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
29
  | --- | --- | --- | --- | --- | --- | --- |
30
- | `output` | `y` | `T2` | same as `input` | same as `input` | Output tensor with the same shape as `input` and the element type of `target_type`. | required |
31
 
32
  ## Attributes
33
 
@@ -47,7 +47,7 @@ Default values (overridable per request):
47
 
48
  ## Files
49
 
50
- - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
51
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
52
  - [`test.json`](build/webgpu/test.json) — correctness cases
53
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
@@ -57,10 +57,14 @@ Default values (overridable per request):
57
 
58
  ## Use with `@huggingface/kernels`
59
 
60
- The loader derives every required output's shape and logical dtype from the manifest contract and this call.
61
- It then allocates the result tensors automatically.
 
 
 
62
 
63
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
64
 
65
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
66
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
+ | `x` | `input` | `T1` | — | — | Input tensor whose elements are to be cast. | required |
24
+ | `target` | `target_type` | `T2` | — | — | Tensor whose element type defines the destination dtype; its values are not used. | required |
25
 
26
  ## Outputs
27
 
28
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
29
  | --- | --- | --- | --- | --- | --- | --- |
30
+ | `y` | `output` | `T2` | same as `x` | same as `x` | Output tensor with the same shape as `input` and the element type of `target_type`. | required |
31
 
32
  ## Attributes
33
 
 
47
 
48
  ## Files
49
 
50
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
51
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
52
  - [`test.json`](build/webgpu/test.json) — correctness cases
53
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
57
 
58
  ## Use with `@huggingface/kernels`
59
 
60
+ ```sh
61
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
62
+ ```
63
+
64
+ Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
65
 
66
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
67
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
68
 
69
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
70
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.CastLike",
3
  "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
4
  "cases": [
5
  {
@@ -53,7 +52,7 @@
53
  "preset": "smoke",
54
  "vars": { "inputDtype": "int8", "outputDtype": "float32", "count": 1048579 },
55
  "inputs": {
56
- "x": { "dtype": "int8", "shape": [1048579], "dist": "uniform", "seed": 915, "scale": 127, "signed": true },
57
  "target": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } }
58
  },
59
  "outputs": { "y": { "dtype": "float32", "shape": [1048579] } },
 
1
  {
 
2
  "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
3
  "cases": [
4
  {
 
52
  "preset": "smoke",
53
  "vars": { "inputDtype": "int8", "outputDtype": "float32", "count": 1048579 },
54
  "inputs": {
55
+ "x": { "dtype": "int8", "shape": [1048579], "dist": "uniform", "seed": 915, "min": -128, "max": 128 },
56
  "target": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } }
57
  },
58
  "outputs": { "y": { "dtype": "float32", "shape": [1048579] } },
build/webgpu/cast-scalar-x4.wgsl.jinja CHANGED
@@ -1,6 +1,3 @@
1
- {% if usesF16 %}
2
- enable f16;
3
- {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
5
  {% if wrapNarrowInt %}
6
 
@@ -13,11 +10,11 @@ fn cast_wrap(v: f32) -> {{ outScalar }} {
13
 
14
  {% endif %}
15
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
16
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
17
  // Four scalar conversions per invocation retain vector-like dispatch density even when the
18
  // logical element count is not vec4 aligned. For a vec4 bulk + scalar tail plan, only lane zero
19
  // of the tail dispatch runs and starts at the first element not covered by the packed pass.
20
- let invocation = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
21
  if (invocation != 0u) { return; }
22
  let base = params.count - params.count % 4u;
23
  {% for lane in range(4) %}
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
  {% if wrapNarrowInt %}
3
 
 
10
 
11
  {% endif %}
12
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
13
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
14
  // Four scalar conversions per invocation retain vector-like dispatch density even when the
15
  // logical element count is not vec4 aligned. For a vec4 bulk + scalar tail plan, only lane zero
16
  // of the tail dispatch runs and starts at the first element not covered by the packed pass.
17
+ let invocation = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
18
  if (invocation != 0u) { return; }
19
  let base = params.count - params.count % 4u;
20
  {% for lane in range(4) %}
build/webgpu/manifest.json CHANGED
@@ -2,87 +2,42 @@
2
  "domain": "ai.onnx",
3
  "name": "CastLike",
4
  "sinceVersion": 25,
5
- "description": "Casts every element of `input` to the same dtype as `target_type`, producing an output with the same shape as `input`. The `target_type` tensor itself is used only for its dtype and is not read elementwise.",
6
- "inputs": [
7
- { "role": "input", "dtype": "T1", "description": "Input tensor whose elements are to be cast." },
8
- {
9
- "role": "target_type",
10
- "dtype": "T2",
11
- "description": "Tensor whose element type defines the destination dtype; its values are not used."
12
- }
13
- ],
14
- "outputs": [
15
- {
16
- "role": "output",
17
- "dtype": "T2",
18
- "rank": "ranks.input",
19
- "description": "Output tensor with the same shape as `input` and the element type of `target_type`.",
20
- "shape": "shapes.input"
21
- }
22
- ],
23
- "attributes": { "round_mode": "up", "saturate": 1 },
24
- "attributeDescriptions": {
25
- "round_mode": "Rounding direction used only when casting to float8e8m0. The implemented non-float8 subset accepts the ONNX default `\"up\"`.",
26
- "saturate": "Whether casts to float8 saturate at the finite range. The implemented non-float8 subset accepts the ONNX default `1`."
27
- },
28
  "attributeConstraints": { "round_mode": { "values": ["up"] }, "saturate": { "values": [1] } },
29
  "typeConstraints": {
30
  "T1": ["float32", "float16", "uint32", "int32", "uint8", "int8", "bool"],
31
  "T2": ["float32", "float16", "uint32", "int32", "uint8", "int8", "bool"]
32
  },
33
- "args": {
34
- "x": { "kind": "tensor", "semantic": "input", "role": "input" },
35
- "target": { "kind": "tensor", "semantic": "target_type", "role": "input" },
36
- "y": { "kind": "tensor", "semantic": "output", "role": "output" }
37
- },
38
- "tunables": { "WORKGROUP_SIZE": 256, "VEC4_TAIL_MIN_ELEMENTS": 4096 },
39
- "bindingSets": {
40
- "vec4": [
41
- {
42
- "name": "x",
43
- "arg": "x",
44
- "semantic": "input",
45
- "buffer": { "type": "read-only-storage" },
46
- "elementType": "$vectorScalar"
47
- },
48
- { "name": "y", "arg": "y", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "$outVec" },
49
- {
50
- "name": "params",
51
- "semantic": "kernel.params",
52
- "buffer": { "type": "uniform" },
53
- "struct": {
54
- "name": "Params",
55
- "fields": [{ "name": "count", "type": "u32", "value": "floor(numel(shapes.y) / 4)" }]
56
- }
57
- }
58
- ],
59
- "scalar": [
60
- {
61
- "name": "x",
62
- "arg": "x",
63
- "semantic": "input",
64
- "buffer": { "type": "read-only-storage" },
65
- "elementType": "$inScalar"
66
- },
67
- { "name": "y", "arg": "y", "semantic": "output", "buffer": { "type": "storage" }, "elementType": "$outScalar" },
68
- {
69
- "name": "params",
70
- "semantic": "kernel.params",
71
- "buffer": { "type": "uniform" },
72
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] }
73
- }
74
- ]
75
- },
76
- "constants": {
77
  "usesF16": "tensorDtypes.x == \"float16\" or tensorDtypes.y == \"float16\"",
78
  "wrapNarrowInt": "(tensorDtypes.x == \"float32\" or tensorDtypes.x == \"float16\") and (tensorDtypes.y == \"int8\" or tensorDtypes.y == \"uint8\")",
79
  "wrapSigned": "tensorDtypes.y == \"int8\""
80
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  "variants": [
82
  {
83
  "id": "same_layout_vec4",
84
- "when": ["numel(shapes.x) > 0", "numel(shapes.x) % 4 == 0", "numel(shapes.x) == numel(shapes.y)", "ranks.y == ranks.x", "tensorDtypes.y != \"bool\"", "f16Ok(dtypes.T1) and f16Ok(dtypes.T2)"],
85
- "constants": {
 
86
  "scalar": "\"f16\" if tensorDtypes.x == \"float16\" else (\"f32\" if tensorDtypes.x == \"float32\" else (\"i32\" if tensorDtypes.x == \"int32\" or tensorDtypes.x == \"int8\" else \"u32\"))",
87
  "vectorScalar": "\"vec4<\" ~ (\"f16\" if tensorDtypes.x == \"float16\" else (\"f32\" if tensorDtypes.x == \"float32\" else (\"i32\" if tensorDtypes.x == \"int32\" or tensorDtypes.x == \"int8\" else \"u32\"))) ~ \">\"",
88
  "outVec": "\"vec4<\" ~ (\"f16\" if tensorDtypes.y == \"float16\" else (\"f32\" if tensorDtypes.y == \"float32\" else (\"i32\" if tensorDtypes.y == \"int32\" or tensorDtypes.y == \"int8\" else \"u32\"))) ~ \">\""
@@ -91,18 +46,25 @@
91
  {
92
  "id": "main",
93
  "name": "CastLike.vec4",
94
- "source": { "shader": "unary-vec4.wgsl.jinja", "inputs": { "op": "\"castlike\"" } },
95
- "bindings": "vec4",
96
- "dispatch": { "threads": "floor(numel(shapes.y) / 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
 
 
 
97
  }
98
- ],
99
- "priority": 20
100
  },
101
  {
102
  "id": "vec4_tail",
103
  "priority": 10,
104
- "when": ["numel(shapes.x) >= tunables.VEC4_TAIL_MIN_ELEMENTS", "numel(shapes.x) % 4 != 0", "ranks.y == ranks.x", "numel(shapes.x) == numel(shapes.y)", "tensorDtypes.y != \"bool\"", "f16Ok(dtypes.T1) and f16Ok(dtypes.T2)"],
105
- "constants": {
106
  "inScalar": "\"f16\" if tensorDtypes.x == \"float16\" else (\"f32\" if tensorDtypes.x == \"float32\" else (\"i32\" if tensorDtypes.x == \"int32\" or tensorDtypes.x == \"int8\" else \"u32\"))",
107
  "outScalar": "\"f16\" if tensorDtypes.y == \"float16\" else (\"f32\" if tensorDtypes.y == \"float32\" else (\"i32\" if tensorDtypes.y == \"int32\" or tensorDtypes.y == \"int8\" else \"u32\"))",
108
  "scalar": "\"f16\" if tensorDtypes.x == \"float16\" else (\"f32\" if tensorDtypes.x == \"float32\" else (\"i32\" if tensorDtypes.x == \"int32\" or tensorDtypes.x == \"int8\" else \"u32\"))",
@@ -113,23 +75,35 @@
113
  {
114
  "id": "bulk",
115
  "name": "CastLike.vec4Bulk",
116
- "source": { "shader": "unary-vec4.wgsl.jinja", "inputs": { "op": "\"castlike\"" } },
117
- "bindings": "vec4",
118
- "dispatch": { "threads": "floor(numel(shapes.y) / 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
 
 
 
119
  },
120
  {
121
  "id": "tail",
122
  "name": "CastLike.scalarTail",
123
- "source": { "shader": "cast-scalar-x4.wgsl.jinja", "inputs": { "tailOnly": true } },
124
- "bindings": "scalar",
125
- "dispatch": { "threads": 1, "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
126
  }
127
  ]
128
  },
129
  {
130
  "id": "elementwise",
131
- "when": ["ranks.y == ranks.x", "numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T1) and f16Ok(dtypes.T2)"],
132
- "constants": {
133
  "inScalar": "\"f16\" if tensorDtypes.x == \"float16\" else (\"f32\" if tensorDtypes.x == \"float32\" else (\"i32\" if tensorDtypes.x == \"int32\" or tensorDtypes.x == \"int8\" else \"u32\"))",
134
  "outScalar": "\"f16\" if tensorDtypes.y == \"float16\" else (\"f32\" if tensorDtypes.y == \"float32\" else (\"i32\" if tensorDtypes.y == \"int32\" or tensorDtypes.y == \"int8\" else \"u32\"))",
135
  "toBool": "tensorDtypes.y == \"bool\""
@@ -138,9 +112,14 @@
138
  {
139
  "id": "main",
140
  "name": "CastLike",
141
- "source": { "shader": "unary-scalar.wgsl.jinja", "inputs": { "op": "\"castlike\"" } },
142
- "bindings": "scalar",
143
- "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
144
  }
145
  ]
146
  }
 
2
  "domain": "ai.onnx",
3
  "name": "CastLike",
4
  "sinceVersion": 25,
5
+ "inputs": { "x": { "onnx": "input", "dtype": "T1" }, "target": { "onnx": "target_type", "dtype": "T2" } },
6
+ "outputs": { "y": { "onnx": "output", "dtype": "T2", "rank": "ranks.x", "shape": "shapes.x" } },
7
+ "attributes": { "round_mode": { "default": "up" }, "saturate": { "default": 1 } },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  "attributeConstraints": { "round_mode": { "values": ["up"] }, "saturate": { "values": [1] } },
9
  "typeConstraints": {
10
  "T1": ["float32", "float16", "uint32", "int32", "uint8", "int8", "bool"],
11
  "T2": ["float32", "float16", "uint32", "int32", "uint8", "int8", "bool"]
12
  },
13
+ "tunables": { "WORKGROUP_SIZE": { "default": 256 }, "VEC4_TAIL_MIN_ELEMENTS": { "default": 4096 } },
14
+ "derive": {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  "usesF16": "tensorDtypes.x == \"float16\" or tensorDtypes.y == \"float16\"",
16
  "wrapNarrowInt": "(tensorDtypes.x == \"float32\" or tensorDtypes.x == \"float16\") and (tensorDtypes.y == \"int8\" or tensorDtypes.y == \"uint8\")",
17
  "wrapSigned": "tensorDtypes.y == \"int8\""
18
  },
19
+ "when": ["numel(shapes.x) == numel(shapes.y)", "ranks.y == ranks.x", "f16Ok(dtypes.T1) and f16Ok(dtypes.T2)"],
20
+ "bindings": {
21
+ "x": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
22
+ "y": { "buffer": "storage", "elementType": "$outVec" },
23
+ "params": {
24
+ "buffer": "uniform",
25
+ "struct": [{ "name": "count", "type": "u32", "value": "floor(numel(shapes.y) / 4)" }]
26
+ },
27
+ "x_2": { "name": "x", "buffer": "read-only-storage", "elementType": "$inScalar" },
28
+ "y_2": { "name": "y", "buffer": "storage", "elementType": "$outScalar" },
29
+ "params_2": {
30
+ "name": "params",
31
+ "buffer": "uniform",
32
+ "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }]
33
+ }
34
+ },
35
  "variants": [
36
  {
37
  "id": "same_layout_vec4",
38
+ "priority": 20,
39
+ "when": ["numel(shapes.x) > 0", "numel(shapes.x) % 4 == 0", "tensorDtypes.y != \"bool\""],
40
+ "derive": {
41
  "scalar": "\"f16\" if tensorDtypes.x == \"float16\" else (\"f32\" if tensorDtypes.x == \"float32\" else (\"i32\" if tensorDtypes.x == \"int32\" or tensorDtypes.x == \"int8\" else \"u32\"))",
42
  "vectorScalar": "\"vec4<\" ~ (\"f16\" if tensorDtypes.x == \"float16\" else (\"f32\" if tensorDtypes.x == \"float32\" else (\"i32\" if tensorDtypes.x == \"int32\" or tensorDtypes.x == \"int8\" else \"u32\"))) ~ \">\"",
43
  "outVec": "\"vec4<\" ~ (\"f16\" if tensorDtypes.y == \"float16\" else (\"f32\" if tensorDtypes.y == \"float32\" else (\"i32\" if tensorDtypes.y == \"int32\" or tensorDtypes.y == \"int8\" else \"u32\"))) ~ \">\""
 
46
  {
47
  "id": "main",
48
  "name": "CastLike.vec4",
49
+ "shader": "unary-vec4.wgsl.jinja",
50
+ "derive": {
51
+ "op": "\"castlike\"",
52
+ "vec4PerThread": "4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1"
53
+ },
54
+ "bindings": ["x", "y", "params"],
55
+ "dispatch": {
56
+ "x": "min(ceilDiv((ceilDiv(floor(numel(shapes.y) / 4), 4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)), (tunables.WORKGROUP_SIZE)), 65535)",
57
+ "y": "ceilDiv(ceilDiv((ceilDiv(floor(numel(shapes.y) / 4), 4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)), (tunables.WORKGROUP_SIZE)), 65535)",
58
+ "z": 1
59
+ }
60
  }
61
+ ]
 
62
  },
63
  {
64
  "id": "vec4_tail",
65
  "priority": 10,
66
+ "when": ["numel(shapes.x) >= tunables.VEC4_TAIL_MIN_ELEMENTS", "numel(shapes.x) % 4 != 0", "tensorDtypes.y != \"bool\""],
67
+ "derive": {
68
  "inScalar": "\"f16\" if tensorDtypes.x == \"float16\" else (\"f32\" if tensorDtypes.x == \"float32\" else (\"i32\" if tensorDtypes.x == \"int32\" or tensorDtypes.x == \"int8\" else \"u32\"))",
69
  "outScalar": "\"f16\" if tensorDtypes.y == \"float16\" else (\"f32\" if tensorDtypes.y == \"float32\" else (\"i32\" if tensorDtypes.y == \"int32\" or tensorDtypes.y == \"int8\" else \"u32\"))",
70
  "scalar": "\"f16\" if tensorDtypes.x == \"float16\" else (\"f32\" if tensorDtypes.x == \"float32\" else (\"i32\" if tensorDtypes.x == \"int32\" or tensorDtypes.x == \"int8\" else \"u32\"))",
 
75
  {
76
  "id": "bulk",
77
  "name": "CastLike.vec4Bulk",
78
+ "shader": "unary-vec4.wgsl.jinja",
79
+ "derive": {
80
+ "op": "\"castlike\"",
81
+ "vec4PerThread": "4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1"
82
+ },
83
+ "bindings": ["x", "y", "params"],
84
+ "dispatch": {
85
+ "x": "min(ceilDiv((ceilDiv(floor(numel(shapes.y) / 4), 4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)), (tunables.WORKGROUP_SIZE)), 65535)",
86
+ "y": "ceilDiv(ceilDiv((ceilDiv(floor(numel(shapes.y) / 4), 4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)), (tunables.WORKGROUP_SIZE)), 65535)",
87
+ "z": 1
88
+ }
89
  },
90
  {
91
  "id": "tail",
92
  "name": "CastLike.scalarTail",
93
+ "shader": "cast-scalar-x4.wgsl.jinja",
94
+ "derive": { "tailOnly": true },
95
+ "bindings": ["x_2", "y_2", "params_2"],
96
+ "dispatch": {
97
+ "x": "min(ceilDiv((1), (tunables.WORKGROUP_SIZE)), 65535)",
98
+ "y": "ceilDiv(ceilDiv((1), (tunables.WORKGROUP_SIZE)), 65535)",
99
+ "z": 1
100
+ }
101
  }
102
  ]
103
  },
104
  {
105
  "id": "elementwise",
106
+ "derive": {
 
107
  "inScalar": "\"f16\" if tensorDtypes.x == \"float16\" else (\"f32\" if tensorDtypes.x == \"float32\" else (\"i32\" if tensorDtypes.x == \"int32\" or tensorDtypes.x == \"int8\" else \"u32\"))",
108
  "outScalar": "\"f16\" if tensorDtypes.y == \"float16\" else (\"f32\" if tensorDtypes.y == \"float32\" else (\"i32\" if tensorDtypes.y == \"int32\" or tensorDtypes.y == \"int8\" else \"u32\"))",
109
  "toBool": "tensorDtypes.y == \"bool\""
 
112
  {
113
  "id": "main",
114
  "name": "CastLike",
115
+ "shader": "unary-scalar.wgsl.jinja",
116
+ "derive": { "op": "\"castlike\"", "itemsPerInvocation": 4 },
117
+ "bindings": ["x_2", "y_2", "params_2"],
118
+ "dispatch": {
119
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
120
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
121
+ "z": 1
122
+ }
123
  }
124
  ]
125
  }
build/webgpu/metadata.json CHANGED
@@ -1,20 +1,27 @@
1
  {
2
  "name": "ai.onnx.CastLike",
3
- "id": "_ai_onnx_castlike_webgpu_ff0741d",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "LJNtd0rYJjdKViAHum941BgDF6evzLmd2ZI+y2rZKLo=",
11
- "cast-scalar-x4.wgsl.jinja": "PrFeLTvZUbu1qrf0L9BWXSct/kRBmcocjA92lM+hgZw=",
12
- "manifest.json": "wafZ0zJmjB3QtZJo7odf1U/4kyIu84s59gKS/h1ItRo=",
13
- "test.json": "t27WIYD7Q5vGZKbB3Z9vswIfpPiC3XMvUQyzhSfdAIE=",
14
- "unary-scalar.wgsl.jinja": "tIRx4X0JJnKcI45N/xWT7E1rwi2vm7nuKvqNE4eFwaE=",
15
- "unary-vec4.wgsl.jinja": "Q77arupT4nkbLU7bJ1Gm43gAtXqjH3roPVo7q7g2txA="
16
  }
17
  },
18
- "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
19
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.CastLike" }
 
 
 
 
 
 
 
20
  }
 
1
  {
2
  "name": "ai.onnx.CastLike",
3
+ "id": "_ai_onnx_castlike_webgpu_b3b5a5c",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "HvPktrO4Hl0z7C35f4x48CIdfhgr5BXEPqDjvZWDBbw=",
11
+ "cast-scalar-x4.wgsl.jinja": "ZEmWwTu9NiMzEKa+PuUlxatkSfJ8WLLYD78x0ay8l/U=",
12
+ "manifest.json": "omTaO2Iw4AaBsEmMGrnaTWCUpxDFQ1mQQtk2ivwV7Yo=",
13
+ "test.json": "uXxmLeBAgMyAvKw77XQjlYD+MzcMu9YPDObTu9xM9CI=",
14
+ "unary-scalar.wgsl.jinja": "zBUAwYvX5TCH+YwAb8iuo9hhR7g51kBVeFGOMjxn8oo=",
15
+ "unary-vec4.wgsl.jinja": "vndf17SxewEvqPOmYVKn4LQTBbVuBNjQ3cDs+olqyTo="
16
  }
17
  },
18
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
19
+ "webgpu": {
20
+ "manifestSpec": "2.0",
21
+ "variants": {
22
+ "same_layout_vec4": ["unary-vec4.wgsl.jinja"],
23
+ "vec4_tail": ["cast-scalar-x4.wgsl.jinja", "unary-vec4.wgsl.jinja"],
24
+ "elementwise": ["unary-scalar.wgsl.jinja"]
25
+ }
26
+ }
27
  }
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.CastLike",
3
  "cases": [
4
  {
5
  "name": "float32_to_uint32_like",
@@ -17,7 +16,7 @@
17
  "name": "uint32_to_float32_like_extremes",
18
  "provenance": {
19
  "source": "onnx/onnx/docs/Operators.md#CastLike-25",
20
- "notes": "Provides executable source-side uint32 coverage, including values beyond signed-i32 and exact-f32 ranges."
21
  },
22
  "inputs": {
23
  "x": {
@@ -33,7 +32,7 @@
33
  "name": "bool_to_int32_like",
34
  "provenance": {
35
  "source": "onnx/onnx/docs/Operators.md#CastLike-25",
36
- "notes": "Provides executable source-side bool coverage while keeping the logical bool contract distinct from widened u32 storage."
37
  },
38
  "inputs": {
39
  "x": { "dtype": "bool", "shape": [4], "data": { "kind": "values", "values": [0, 1, 1, 0] } },
@@ -281,7 +280,7 @@
281
  },
282
  "outputs": { "y": { "dtype": "uint8", "shape": [4099], "tolerance": 0 } },
283
  "provenance": {
284
- "notes": "Float16 source with a uint8 target above the vec4-tail crossover: renders the scalar tail kernel with usesF16 and wrapNarrowInt both set and the UNSIGNED low-byte return. Every cycle value is exactly representable in float16 and lies within uint8's representable range. ONNX defines out-of-range float-to-fixed-point conversion as undefined, so this portable fixture deliberately does not assert wraparound for such values."
285
  }
286
  },
287
  {
@@ -342,7 +341,7 @@
342
  },
343
  "outputs": { "y": { "dtype": "int32", "shape": [1, 1, 1, 1, 1, 1, 8], "tolerance": 0 } },
344
  "provenance": {
345
- "notes": "Rank-7 flat input on the shared vec4 unary cast kernel (indexes by flat numel; sibling Cast has no rank cap). Covers the regime the removed 'ranks.x <= 6' guard rejected."
346
  }
347
  }
348
  ]
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "float32_to_uint32_like",
 
16
  "name": "uint32_to_float32_like_extremes",
17
  "provenance": {
18
  "source": "onnx/onnx/docs/Operators.md#CastLike-25",
19
+ "notes": "Uint32 inputs include values beyond signed-int32 and exact-float32 ranges."
20
  },
21
  "inputs": {
22
  "x": {
 
32
  "name": "bool_to_int32_like",
33
  "provenance": {
34
  "source": "onnx/onnx/docs/Operators.md#CastLike-25",
35
+ "notes": "Logical bool values remain distinct from their widened uint32 storage representation during conversion to int32."
36
  },
37
  "inputs": {
38
  "x": { "dtype": "bool", "shape": [4], "data": { "kind": "values", "values": [0, 1, 1, 0] } },
 
280
  },
281
  "outputs": { "y": { "dtype": "uint8", "shape": [4099], "tolerance": 0 } },
282
  "provenance": {
283
+ "notes": "Casting 4,099 float16 values to uint8 combines vectorized conversion with a three-element scalar tail. Every source value is exactly representable in float16 and lies within the uint8 range."
284
  }
285
  },
286
  {
 
341
  },
342
  "outputs": { "y": { "dtype": "int32", "shape": [1, 1, 1, 1, 1, 1, 8], "tolerance": 0 } },
343
  "provenance": {
344
+ "notes": "A rank-7 flat input exercises float32-to-int32 conversion by flat element count on the shared vec4 path."
345
  }
346
  }
347
  ]
build/webgpu/unary-scalar.wgsl.jinja CHANGED
@@ -1,18 +1,37 @@
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
- let i = invocation;
8
- if (i >= params.count) {
 
 
 
 
 
9
  return;
10
- }{% endmacro %}
 
 
 
 
 
 
 
 
11
  {% macro flat_tail_close() %}
 
12
  {% endmacro %}
13
 
14
- // Scalar unary fallback. Each branch retains the operation's numeric hardening,
15
- // including Payne-Hanek trigonometric range reduction and NaN/overflow guards.
 
 
 
 
 
16
  {% if usesF16 %}
17
  enable f16;
18
  {% 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
+ {% if tailOnly is defined and tailOnly %}
11
+ // Tail of a vec4 bulk pass: lane zero alone covers the elements the packed
12
+ // pass left behind, from the last multiple of the item count to the end.
13
+ if (invocation != 0u) {
14
  return;
15
+ }
16
+ let begin = params.count - params.count % {{ itemsPerInvocation }}u;
17
+ let end = params.count;
18
+ {% else %}
19
+ let begin = invocation * {{ itemsPerInvocation }}u;
20
+ let end = min(begin + {{ itemsPerInvocation }}u, params.count);
21
+ {% endif %}
22
+ for (var i = begin; i < end; i = i + 1u) {
23
+ {%- endmacro %}
24
  {% macro flat_tail_close() %}
25
+ }
26
  {% endmacro %}
27
 
28
+ {% set toBool = toBool is defined and toBool %}
29
+ {% set wrapNarrowInt = wrapNarrowInt is defined and wrapNarrowInt %}
30
+ {% set wrapSigned = wrapSigned is defined and wrapSigned %}
31
+ {% set inScalar = inScalar | default(scalar) %}
32
+ {% set outScalar = outScalar | default(scalar) %}
33
+ // Scalar unary elementwise implementation. Specialization emits only the
34
+ // selected operation and any numerical helper it requires.
35
  {% if usesF16 %}
36
  enable f16;
37
  {% endif %}
build/webgpu/unary-vec4.wgsl.jinja CHANGED
@@ -1,19 +1,38 @@
1
- // Loads and stores vec4<T> (128 bits) while retaining scalar per-component
2
- // arithmetic, including per-component helper calls for guard-heavy operations.
3
- {% if usesF16 %}
4
- enable f16;
5
- {% endif %}
6
  {{ env.wgsl.resourceDeclarations }}
7
 
8
 
 
 
 
 
 
9
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
10
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
11
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
12
- // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills the rest into y).
13
- let i = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
 
 
 
 
 
 
 
 
 
 
 
 
14
  if (i >= params.count) {
15
  return;
16
  }
 
 
17
  let xv = x[i];
18
  {% if wrapNarrowInt %}
19
  // Float-to-int8/uint8 Cast and CastLike wrap modulo 256 rather than saturate:
@@ -23,4 +42,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
23
  {% else %}
24
  y[i] = {{ outVec }}(xv);
25
  {% endif %}
 
 
 
26
  }
 
1
+ {% set wrapNarrowInt = wrapNarrowInt is defined and wrapNarrowInt %}
2
+ {% set wrapSigned = wrapSigned is defined and wrapSigned %}
3
+ {% set outVec = outVec | default(vectorScalar) %}
4
+ // Loads and stores vec4<T> while evaluating the selected unary operation per
5
+ // component.
6
  {{ env.wgsl.resourceDeclarations }}
7
 
8
 
9
+ {% set vec4PerThread = vec4PerThread %}
10
+ {% if vec4PerThread > 1 %}
11
+ const ITEMS: u32 = {{ vec4PerThread }}u;
12
+ {% endif %}
13
+
14
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
15
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
16
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
17
+ // per-axis dispatch fold width (the dispatch caps x and spills the rest into y).
18
+ {% if vec4PerThread > 1 %}
19
+ // Each invocation walks ITEMS vec4 groups a span apart. Consecutive lanes
20
+ // access consecutive words on every step, while each lane can keep several
21
+ // independent loads in flight.
22
+ let tid = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
23
+ let span = (params.count + ITEMS - 1u) / ITEMS;
24
+ for (var j = 0u; j < ITEMS; j = j + 1u) {
25
+ let i = tid + j * span;
26
+ if (i >= params.count) {
27
+ break;
28
+ }
29
+ {% else %}
30
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
31
  if (i >= params.count) {
32
  return;
33
  }
34
+ {% endif %}
35
+
36
  let xv = x[i];
37
  {% if wrapNarrowInt %}
38
  // Float-to-int8/uint8 Cast and CastLike wrap modulo 256 rather than saturate:
 
42
  {% else %}
43
  y[i] = {{ outVec }}(xv);
44
  {% endif %}
45
+ {% if vec4PerThread > 1 %}
46
+ }
47
+ {% endif %}
48
  }