Xenova HF Staff commited on
Commit
d047a99
·
verified ·
1 Parent(s): 5d7f694

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,72 @@
1
  ---
 
2
  license: apache-2.0
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ library_name: kernels
3
  license: apache-2.0
4
+ tags:
5
+ - kernel
6
+ - webgpu
7
+ - wgsl
8
  ---
9
+ # ai.onnx.CastLike
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 25
12
+
13
+ ## Description
14
+
15
+ 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.
16
+
17
+ See the [ONNX `CastLike` spec](https://onnx.ai/onnx/operators/onnx__CastLike.html) for the reference semantics.
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
+
34
+ Default values (overridable per request):
35
+
36
+ | Attribute | Default | Description |
37
+ | --- | --- | --- |
38
+ | `round_mode` | `"up"` | Rounding direction used only when casting to float8e8m0. The implemented non-float8 subset accepts the ONNX default `"up"`. |
39
+ | `saturate` | `1` | Whether casts to float8 saturate at the finite range. The implemented non-float8 subset accepts the ONNX default `1`. |
40
+
41
+ ## Type constraints
42
+
43
+ | Variable | Allowed dtypes |
44
+ | --- | --- |
45
+ | `T1` | `float32`, `float16`, `uint32`, `int32`, `uint8`, `int8`, `bool` |
46
+ | `T2` | `float32`, `float16`, `uint32`, `int32`, `uint8`, `int8`, `bool` |
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
54
+ - [`cast-scalar-x4.wgsl.jinja`](build/webgpu/cast-scalar-x4.wgsl.jinja)
55
+ - [`unary-scalar.wgsl.jinja`](build/webgpu/unary-scalar.wgsl.jinja)
56
+ - [`unary-vec4.wgsl.jinja`](build/webgpu/unary-vec4.wgsl.jinja)
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
+
67
+ ```js
68
+ import { getKernel } from "@huggingface/kernels";
69
+
70
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.CastLike", { version: 1 });
71
+ const { y } = await kernel({ x: { data: xData, shape: [] }, target: { data: targetData, shape: [3] } });
72
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.CastLike",
3
+ "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
4
+ "cases": [
5
+ {
6
+ "name": "castlike-f32-to-u32-1m",
7
+ "preset": "smoke",
8
+ "vars": { "inputDtype": "float32", "outputDtype": "uint32", "count": 1048576 },
9
+ "inputs": {
10
+ "x": { "dtype": "float32", "shape": [1048576], "dist": "uniform", "seed": 912, "scale": 255 },
11
+ "target": { "dtype": "uint32", "shape": [1], "data": { "kind": "values", "values": [0] } }
12
+ },
13
+ "outputs": { "y": { "dtype": "uint32", "shape": [1048576] } },
14
+ "bench": {
15
+ "primary": true,
16
+ "metrics": [
17
+ { "type": "bandwidth", "value": "args.count * (dtypeBytes(args.inputDtype) + dtypeBytes(args.outputDtype))" }
18
+ ]
19
+ }
20
+ },
21
+ {
22
+ "name": "castlike-f32-to-u32-1m-plus3",
23
+ "preset": "smoke",
24
+ "vars": { "inputDtype": "float32", "outputDtype": "uint32", "count": 1048579 },
25
+ "inputs": {
26
+ "x": { "dtype": "float32", "shape": [1048579], "dist": "uniform", "seed": 913, "scale": 255 },
27
+ "target": { "dtype": "uint32", "shape": [0], "data": { "kind": "values", "values": [] } }
28
+ },
29
+ "outputs": { "y": { "dtype": "uint32", "shape": [1048579] } },
30
+ "bench": {
31
+ "metrics": [
32
+ { "type": "bandwidth", "value": "args.count * (dtypeBytes(args.inputDtype) + dtypeBytes(args.outputDtype))" }
33
+ ]
34
+ }
35
+ },
36
+ {
37
+ "name": "castlike-f32-to-f16-1m",
38
+ "preset": "smoke",
39
+ "vars": { "inputDtype": "float32", "outputDtype": "float16", "count": 1048576 },
40
+ "inputs": {
41
+ "x": { "dtype": "float32", "shape": [1048576], "dist": "normal", "seed": 914 },
42
+ "target": { "dtype": "float16", "shape": [0], "data": { "kind": "values", "values": [] } }
43
+ },
44
+ "outputs": { "y": { "dtype": "float16", "shape": [1048576] } },
45
+ "bench": {
46
+ "metrics": [
47
+ { "type": "bandwidth", "value": "args.count * (dtypeBytes(args.inputDtype) + dtypeBytes(args.outputDtype))" }
48
+ ]
49
+ }
50
+ },
51
+ {
52
+ "name": "castlike-i8-to-f32-1m-plus3",
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] } },
60
+ "bench": {
61
+ "metrics": [
62
+ { "type": "bandwidth", "value": "args.count * (dtypeBytes(args.inputDtype) + dtypeBytes(args.outputDtype))" }
63
+ ]
64
+ }
65
+ },
66
+ {
67
+ "name": "castlike-f16-to-f32-unaligned-1m-plus1",
68
+ "preset": "edge",
69
+ "vars": { "inputDtype": "float16", "outputDtype": "float32", "count": 1048577 },
70
+ "inputs": {
71
+ "x": { "dtype": "float16", "shape": [1048577], "dist": "normal", "seed": 9231 },
72
+ "target": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } }
73
+ },
74
+ "outputs": { "y": { "dtype": "float32", "shape": [1048577] } },
75
+ "bench": {
76
+ "metrics": [
77
+ { "type": "bandwidth", "value": "args.count * (dtypeBytes(args.inputDtype) + dtypeBytes(args.outputDtype))" }
78
+ ]
79
+ }
80
+ }
81
+ ]
82
+ }
build/webgpu/cast-scalar-x4.wgsl.jinja ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+ {% if wrapNarrowInt %}
6
+
7
+ // ONNX float-to-int8/uint8 narrowing wraps modulo 256 rather than saturating.
8
+ // Preserve the low byte after truncation; the signed path then sign-extends it.
9
+ fn cast_wrap(v: f32) -> {{ outScalar }} {
10
+ let low = i32(v) & 0xFF;
11
+ return {{ outScalar }}({% if wrapSigned %}select(low, low - 256, low > 127){% else %}low{% endif %});
12
+ }
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) %}
24
+ if (base + {{ lane }}u < params.count) {
25
+ y[base + {{ lane }}u] = {% if wrapNarrowInt %}cast_wrap(f32(x[base + {{ lane }}u])){% else %}{{ outScalar }}(x[base + {{ lane }}u]){% endif %};
26
+ }
27
+ {% endfor %}
28
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
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\"))) ~ \">\""
89
+ },
90
+ "passes": [
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\"))",
109
+ "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\"))) ~ \">\"",
110
+ "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\"))) ~ \">\""
111
+ },
112
+ "passes": [
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\""
136
+ },
137
+ "passes": [
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
+ }
147
+ ]
148
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,349 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.CastLike",
3
+ "cases": [
4
+ {
5
+ "name": "float32_to_uint32_like",
6
+ "inputs": {
7
+ "x": {
8
+ "dtype": "float32",
9
+ "shape": [6],
10
+ "data": { "kind": "values", "values": [0.1, 1.9, 2.2, 5.8, 7.0, 9.6] }
11
+ },
12
+ "target": { "dtype": "uint32", "shape": [1], "data": { "kind": "values", "values": [0] } }
13
+ },
14
+ "outputs": { "y": { "dtype": "uint32", "shape": [6] } }
15
+ },
16
+ {
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": {
24
+ "dtype": "uint32",
25
+ "shape": [4],
26
+ "data": { "kind": "values", "values": [0, 16777217, 2147483648, 4294967295] }
27
+ },
28
+ "target": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } }
29
+ },
30
+ "outputs": { "y": { "dtype": "float32", "shape": [4], "tolerance": 0 } }
31
+ },
32
+ {
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] } },
40
+ "target": { "dtype": "int32", "shape": [0], "data": { "kind": "values", "values": [] } }
41
+ },
42
+ "outputs": { "y": { "dtype": "int32", "shape": [4], "tolerance": 0 } }
43
+ },
44
+ {
45
+ "name": "int32_to_float32_like_scalar_target",
46
+ "inputs": {
47
+ "x": { "dtype": "int32", "shape": [2, 3], "data": { "kind": "values", "values": [-3, -1, 0, 1, 7, 12] } },
48
+ "target": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.0] } }
49
+ },
50
+ "outputs": { "y": { "dtype": "float32", "shape": [2, 3] } }
51
+ },
52
+ {
53
+ "name": "uint8_to_float16_like",
54
+ "inputs": {
55
+ "x": { "dtype": "uint8", "shape": [4], "data": { "kind": "values", "values": [0, 1, 127, 255] } },
56
+ "target": { "dtype": "float16", "shape": [2, 2], "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0] } }
57
+ },
58
+ "outputs": { "y": { "dtype": "float16", "shape": [4] } },
59
+ "tolerance": 0.001
60
+ },
61
+ {
62
+ "name": "float16_to_int32_like",
63
+ "inputs": {
64
+ "x": { "dtype": "float16", "shape": [5], "data": { "kind": "values", "values": [-2.75, -1.1, 0.0, 1.9, 4.5] } },
65
+ "target": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [0] } }
66
+ },
67
+ "outputs": { "y": { "dtype": "int32", "shape": [5] } }
68
+ },
69
+ {
70
+ "name": "ort_function_scalar_float_to_ranked_float_like",
71
+ "provenance": {
72
+ "source": "onnxruntime/test/framework/function_test.cc",
73
+ "test": "FunctionTest.AttrWithDefault",
74
+ "notes": "Covers ORT's use of CastLike inside a function body; the target tensor contributes only the output dtype, not the output shape."
75
+ },
76
+ "inputs": {
77
+ "x": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [2.0] } },
78
+ "target": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [0.0, 0.0, 0.0] } }
79
+ },
80
+ "outputs": { "y": { "dtype": "float32", "shape": [], "tolerance": 0 } }
81
+ },
82
+ {
83
+ "name": "ort_scatternd_float32_to_float16_like_initializer",
84
+ "provenance": {
85
+ "source": "onnxruntime/test/python/onnxruntime_test_scatternd.py",
86
+ "test": "TestScatterND.common_scatter",
87
+ "notes": "Adapts ORT's ScatterND helper pattern where CastLike converts float input to the dtype of a float16 initializer before later graph ops."
88
+ },
89
+ "inputs": {
90
+ "x": { "dtype": "float32", "shape": [2, 2], "data": { "kind": "values", "values": [-3.5, -0.25, 1.5, 8.0] } },
91
+ "target": { "dtype": "float16", "shape": [1], "data": { "kind": "values", "values": [0.0] } }
92
+ },
93
+ "outputs": { "y": { "dtype": "float16", "shape": [2, 2], "tolerance": 0.001 } }
94
+ },
95
+ {
96
+ "name": "onnx_backend_empty_like_float32_to_int8",
97
+ "provenance": {
98
+ "source": "cmake/external/onnx/onnx/backend/test/case/node/castlike.py",
99
+ "test": "CastLike.export",
100
+ "notes": "Uses the ONNX backend generator pattern where the like tensor is empty and only supplies the target dtype."
101
+ },
102
+ "inputs": {
103
+ "x": { "dtype": "float32", "shape": [5], "data": { "kind": "values", "values": [-3.9, -1.1, 0.0, 1.9, 127.9] } },
104
+ "target": { "dtype": "int8", "shape": [0], "data": { "kind": "values", "values": [] } }
105
+ },
106
+ "outputs": { "y": { "dtype": "int8", "shape": [5], "tolerance": 0 } }
107
+ },
108
+ {
109
+ "name": "onnx_backend_empty_like_float32_to_uint8",
110
+ "provenance": {
111
+ "source": "cmake/external/onnx/onnx/backend/test/case/node/castlike.py",
112
+ "test": "CastLike.export",
113
+ "notes": "Uses the ONNX backend generator pattern where the like tensor is empty and only supplies the target dtype."
114
+ },
115
+ "inputs": {
116
+ "x": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.9, 1.9, 127.9, 255.9] } },
117
+ "target": { "dtype": "uint8", "shape": [0], "data": { "kind": "values", "values": [] } }
118
+ },
119
+ "outputs": { "y": { "dtype": "uint8", "shape": [4], "tolerance": 0 } }
120
+ },
121
+ {
122
+ "name": "onnx_backend_empty_like_int8_to_float32",
123
+ "provenance": {
124
+ "source": "cmake/external/onnx/onnx/backend/test/case/node/castlike.py",
125
+ "test": "CastLike.export",
126
+ "notes": "Uses the ONNX backend generator pattern where the like tensor is empty and only supplies the target dtype."
127
+ },
128
+ "inputs": {
129
+ "x": { "dtype": "int8", "shape": [4], "data": { "kind": "values", "values": [-128, -1, 0, 127] } },
130
+ "target": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } }
131
+ },
132
+ "outputs": { "y": { "dtype": "float32", "shape": [4], "tolerance": 0 } }
133
+ },
134
+ {
135
+ "name": "onnx_backend_empty_like_uint8_to_int32",
136
+ "provenance": {
137
+ "source": "cmake/external/onnx/onnx/backend/test/case/node/castlike.py",
138
+ "test": "CastLike.export",
139
+ "notes": "Uses the ONNX backend generator pattern where the like tensor is empty and only supplies the target dtype."
140
+ },
141
+ "inputs": {
142
+ "x": { "dtype": "uint8", "shape": [4], "data": { "kind": "values", "values": [0, 1, 127, 255] } },
143
+ "target": { "dtype": "int32", "shape": [0], "data": { "kind": "values", "values": [] } }
144
+ },
145
+ "outputs": { "y": { "dtype": "int32", "shape": [4], "tolerance": 0 } }
146
+ },
147
+ {
148
+ "name": "onnx_backend_castlike_float_to_float16",
149
+ "provenance": {
150
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_castlike_FLOAT_to_FLOAT16",
151
+ "test": "test_castlike_FLOAT_to_FLOAT16"
152
+ },
153
+ "inputs": {
154
+ "x": {
155
+ "dtype": "float32",
156
+ "shape": [3, 4],
157
+ "data": {
158
+ "kind": "values",
159
+ "values": [0.4789254665374756, 0.48033666610717773, 0.4996848702430725, 0.8191054463386536, 0.4703124761581421, 0.8164680004119873, 0.21087194979190826, 0.7229037880897522, "NaN", "Infinity", "Infinity", "-Infinity"]
160
+ }
161
+ },
162
+ "target": { "dtype": "float16", "shape": [0], "data": { "kind": "values", "values": [] } }
163
+ },
164
+ "outputs": { "y": { "dtype": "float16", "shape": [3, 4], "tolerance": 0.001, "allowNaN": true } }
165
+ },
166
+ {
167
+ "name": "onnx_backend_castlike_float16_to_float",
168
+ "provenance": {
169
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_castlike_FLOAT16_to_FLOAT",
170
+ "test": "test_castlike_FLOAT16_to_FLOAT"
171
+ },
172
+ "inputs": {
173
+ "x": {
174
+ "dtype": "float16",
175
+ "shape": [3, 4],
176
+ "data": {
177
+ "kind": "values",
178
+ "values": [0.47900390625, 0.480224609375, 0.499755859375, 0.8193359375, 0.47021484375, 0.81640625, 0.2108154296875, 0.72314453125, "NaN", "Infinity", "Infinity", "-Infinity"]
179
+ }
180
+ },
181
+ "target": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } }
182
+ },
183
+ "outputs": { "y": { "dtype": "float32", "shape": [3, 4], "tolerance": 0, "allowNaN": true } }
184
+ },
185
+ {
186
+ "name": "vec4_i32_to_f32_lanes",
187
+ "inputs": {
188
+ "x": {
189
+ "dtype": "int32",
190
+ "shape": [8],
191
+ "data": { "kind": "values", "values": [-100, -3, -1, 0, 1, 7, 12, 100] }
192
+ },
193
+ "target": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.0] } }
194
+ },
195
+ "outputs": { "y": { "dtype": "float32", "shape": [8], "tolerance": 0 } }
196
+ },
197
+ {
198
+ "name": "vec4_f32_to_i32_like_truncates_toward_zero",
199
+ "inputs": {
200
+ "x": {
201
+ "dtype": "float32",
202
+ "shape": [8],
203
+ "data": { "kind": "values", "values": [1.0, 2.9, -3.0, 0.0, 100.0, -0.5, -2.9, 127.75] }
204
+ },
205
+ "target": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [0] } }
206
+ },
207
+ "outputs": { "y": { "dtype": "int32", "shape": [8], "tolerance": 0 } }
208
+ },
209
+ {
210
+ "name": "vec4_f16_to_i32_like_truncates_toward_zero",
211
+ "inputs": {
212
+ "x": {
213
+ "dtype": "float16",
214
+ "shape": [8],
215
+ "data": { "kind": "values", "values": [1.0, 2.5, -3.0, 0.0, 100.0, -0.5, -2.5, 7.5] }
216
+ },
217
+ "target": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [0] } }
218
+ },
219
+ "outputs": { "y": { "dtype": "int32", "shape": [8], "tolerance": 0 } }
220
+ },
221
+ {
222
+ "name": "vec4_f32_to_i8_like_in_range_truncates",
223
+ "inputs": {
224
+ "x": {
225
+ "dtype": "float32",
226
+ "shape": [8],
227
+ "data": { "kind": "values", "values": [-128.0, -1.9, -0.5, 0.0, 1.9, 127.0, -127.75, 126.5] }
228
+ },
229
+ "target": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } }
230
+ },
231
+ "outputs": { "y": { "dtype": "int8", "shape": [8], "tolerance": 0 } }
232
+ },
233
+ {
234
+ "name": "scalar_x4_f32_to_u32_like_tail",
235
+ "inputs": {
236
+ "x": {
237
+ "dtype": "float32",
238
+ "shape": [17],
239
+ "data": {
240
+ "kind": "values",
241
+ "values": [0.0, 1.9, 2.2, 3.8, 4.0, 5.6, 6.1, 7.9, 8.0, 9.2, 10.7, 11.0, 12.4, 13.8, 14.0, 15.9, 16.2]
242
+ }
243
+ },
244
+ "target": { "dtype": "uint32", "shape": [0], "data": { "kind": "values", "values": [] } }
245
+ },
246
+ "outputs": { "y": { "dtype": "uint32", "shape": [17], "tolerance": 0 } }
247
+ },
248
+ {
249
+ "name": "vec4_tail_i8_to_f32_like_4097",
250
+ "inputs": {
251
+ "x": { "dtype": "int8", "shape": [4097], "data": { "kind": "constant", "value": -3 } },
252
+ "target": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0] } }
253
+ },
254
+ "outputs": { "y": { "dtype": "float32", "shape": [4097], "tolerance": 0 } },
255
+ "provenance": { "notes": "Exercises a packed vec4 bulk followed by a scalar tail in the same dispatch." }
256
+ },
257
+ {
258
+ "name": "vec4_tail_f32_to_i8_like_4099",
259
+ "inputs": {
260
+ "x": {
261
+ "dtype": "float32",
262
+ "shape": [4099],
263
+ "data": { "kind": "cycle", "values": [1.5, -2.5, 126.25, -127.75, 63.5] }
264
+ },
265
+ "target": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } }
266
+ },
267
+ "outputs": { "y": { "dtype": "int8", "shape": [4099], "tolerance": 0 } },
268
+ "provenance": {
269
+ "notes": "CastLike to an int8 target above the vec4-tail crossover (4099 = 4096 packed elements plus a 3-element scalar tail), selecting the vec4 bulk plus scalar-tail path with its signed int8 storage conversion. Every value is within int8's representable range, so the fixture asserts ONNX-defined truncation toward zero without depending on undefined out-of-range float-to-fixed-point behavior."
270
+ }
271
+ },
272
+ {
273
+ "name": "vec4_tail_f16_to_u8_like_4099",
274
+ "inputs": {
275
+ "x": {
276
+ "dtype": "float16",
277
+ "shape": [4099],
278
+ "data": { "kind": "cycle", "values": [0.5, 200.75, 255.0, 254.25, 1.5, 44.5, 127.5] }
279
+ },
280
+ "target": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } }
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
+ {
288
+ "name": "int32_to_bool_like",
289
+ "inputs": {
290
+ "x": { "dtype": "int32", "shape": [2, 3], "data": { "kind": "values", "values": [-3, 1, 0, 42, -1, 0] } },
291
+ "target": { "dtype": "bool", "shape": [1], "data": { "kind": "values", "values": [0] } }
292
+ },
293
+ "outputs": {
294
+ "y": {
295
+ "dtype": "bool",
296
+ "shape": [2, 3],
297
+ "tolerance": 0,
298
+ "data": { "kind": "values", "values": [1, 1, 0, 1, 1, 0] }
299
+ }
300
+ }
301
+ },
302
+ {
303
+ "name": "vec4_f32_finite_overflow_to_float16_like_saturate_vs_inf",
304
+ "inputs": {
305
+ "x": {
306
+ "dtype": "float32",
307
+ "shape": [8],
308
+ "data": { "kind": "values", "values": [70000.0, -1e+30, 65504.0, 65600.0, 1.0, -2.5, 0.0, -70000.0] }
309
+ },
310
+ "target": { "dtype": "float16", "shape": [1], "data": { "kind": "values", "values": [0.0] } }
311
+ },
312
+ "outputs": { "y": { "dtype": "float16", "shape": [8], "tolerance": 0.001 } }
313
+ },
314
+ {
315
+ "name": "vec4_f32_nonzero_special_to_bool_like",
316
+ "inputs": {
317
+ "x": {
318
+ "dtype": "float32",
319
+ "shape": [8],
320
+ "data": { "kind": "values", "values": ["NaN", "Infinity", "-Infinity", 0.0, 0.0, 1.0, -2.5, 1e-30] }
321
+ },
322
+ "target": { "dtype": "bool", "shape": [1], "data": { "kind": "values", "values": [0] } }
323
+ },
324
+ "outputs": {
325
+ "y": {
326
+ "dtype": "bool",
327
+ "shape": [8],
328
+ "tolerance": 0,
329
+ "data": { "kind": "values", "values": [1, 1, 1, 0, 0, 1, 1, 1] }
330
+ }
331
+ }
332
+ },
333
+ {
334
+ "name": "rank7_vec4_f32_to_i32",
335
+ "inputs": {
336
+ "x": {
337
+ "dtype": "float32",
338
+ "shape": [1, 1, 1, 1, 1, 1, 8],
339
+ "data": { "kind": "values", "values": [0.1, 1.9, 2.2, 5.8, 7.0, 9.6, -3.7, -1.2] }
340
+ },
341
+ "target": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [0] } }
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
+ ]
349
+ }
build/webgpu/unary-scalar.wgsl.jinja ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 %}
19
+ {{ env.wgsl.resourceDeclarations }}
20
+ {{ flat_tail_open() }}
21
+ {% if toBool %}
22
+ {% if inScalar == "f16" or inScalar == "f32" %}
23
+ // Bool cast on the raw bits with the sign cleared: NaN != 0 must be true,
24
+ // but no-NaN fast math can fold the ordered float comparison. Both zeros
25
+ // clear to 0; everything
26
+ // else — NaN, infinities, subnormals — is nonzero.
27
+ y[i] = select(0u, 1u, (bitcast<u32>(f32(x[i])) & 0x7fffffffu) != 0u);
28
+ {% else %}
29
+ y[i] = select(0u, 1u, x[i] != {{ inScalar }}(0));
30
+ {% endif %}
31
+ {% elif wrapNarrowInt %}
32
+ // Float-to-int8/uint8 CastLike wraps modulo 256 rather than saturating.
33
+ let low = i32(f32(x[i])) & 0xFF;
34
+ y[i] = {{ outScalar }}({% if wrapSigned %}select(low, low - 256, low > 127){% else %}low{% endif %});
35
+ {% else %}
36
+ y[i] = {{ outScalar }}(x[i]);
37
+ {% endif %}
38
+ {{ flat_tail_close() -}}
39
+ }
build/webgpu/unary-vec4.wgsl.jinja ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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:
20
+ // retain the low byte of the truncated i32, then sign-extend int8.
21
+ let low = vec4<i32>(vec4<f32>(xv)) & vec4<i32>(0xFF);
22
+ y[i] = {{ outVec }}({% if wrapSigned %}select(low, low - vec4<i32>(256), low > vec4<i32>(127)){% else %}low{% endif %});
23
+ {% else %}
24
+ y[i] = {{ outVec }}(xv);
25
+ {% endif %}
26
+ }