Xenova HF Staff commited on
Commit
1e63f7d
·
verified ·
1 Parent(s): 10eb8e1

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,15 +18,15 @@ See the [ONNX `IsInf` spec](https://onnx.ai/onnx/operators/onnx__IsInf.html) for
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
- | `X` | `x` | `T` | — | — | Input floating-point tensor. | required |
24
 
25
  ## Outputs
26
 
27
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
28
  | --- | --- | --- | --- | --- | --- | --- |
29
- | `Y` | `y` | `B` | same as `X` | same as `X` | Boolean output tensor with the same shape as the input; `true` where the input is infinite. | required |
30
 
31
  ## Attributes
32
 
@@ -46,7 +46,7 @@ Default values (overridable per request):
46
 
47
  ## Files
48
 
49
- - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
50
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
51
  - [`test.json`](build/webgpu/test.json) — correctness cases
52
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
@@ -56,10 +56,14 @@ Default values (overridable per request):
56
 
57
  ## Use with `@huggingface/kernels`
58
 
59
- The loader derives every required output's shape and logical dtype from the manifest contract and this call.
60
- It then allocates the result tensors automatically.
 
 
 
61
 
62
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
63
 
64
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
65
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
+ | `x` | `X` | `T` | — | — | Input floating-point tensor. | required |
24
 
25
  ## Outputs
26
 
27
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
28
  | --- | --- | --- | --- | --- | --- | --- |
29
+ | `y` | `Y` | `B` | same as `x` | same as `x` | Boolean output tensor with the same shape as the input; `true` where the input is infinite. | required |
30
 
31
  ## Attributes
32
 
 
46
 
47
  ## Files
48
 
49
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
50
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
51
  - [`test.json`](build/webgpu/test.json) — correctness cases
52
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
56
 
57
  ## Use with `@huggingface/kernels`
58
 
59
+ ```sh
60
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
61
+ ```
62
+
63
+ Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
64
 
65
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
66
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
67
 
68
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
69
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.IsInf",
3
  "cases": [
4
  {
5
  "name": "isinf-f32-1m",
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "isinf-f32-1m",
build/webgpu/detect-neither.wgsl.jinja CHANGED
@@ -3,11 +3,10 @@
3
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
4
  fn main(
5
  @builtin(global_invocation_id) gid: vec3<u32>,
6
- @builtin(num_workgroups) nwg: vec3<u32>,
7
  ) {
8
  // Four scalar stores retain arbitrary output lengths while sharing the
9
  // folded dispatch index and bounds work.
10
- let item = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
11
  let base = item * 4u;
12
  if (base >= params.count) {
13
  return;
 
3
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
4
  fn main(
5
  @builtin(global_invocation_id) gid: vec3<u32>,
 
6
  ) {
7
  // Four scalar stores retain arbitrary output lengths while sharing the
8
  // folded dispatch index and bounds work.
9
+ let item = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
10
  let base = item * 4u;
11
  if (base >= params.count) {
12
  return;
build/webgpu/manifest.json CHANGED
@@ -2,127 +2,92 @@
2
  "domain": "ai.onnx",
3
  "name": "IsInf",
4
  "sinceVersion": 20,
5
- "description": "Maps each element of a floating-point tensor to `true` if it is infinite and `false` otherwise. The `detect_negative` and `detect_positive` attributes independently control whether negative and positive infinity are considered infinite for this mapping.",
6
- "inputs": [{ "role": "X", "dtype": "T", "description": "Input floating-point tensor." }],
7
- "outputs": [
8
- {
9
- "role": "Y",
10
- "dtype": "B",
11
- "rank": "ranks.X",
12
- "description": "Boolean output tensor with the same shape as the input; `true` where the input is infinite.",
13
- "shape": "shapes.X"
14
- }
15
- ],
16
- "attributes": { "detect_negative": 1, "detect_positive": 1 },
17
- "attributeDescriptions": {
18
- "detect_negative": "When 1 (default), negative infinity maps to true; set to 0 to map negative infinity to false.",
19
- "detect_positive": "When 1 (default), positive infinity maps to true; set to 0 to map positive infinity to false."
20
- },
21
  "attributeConstraints": { "detect_negative": { "values": [0, 1] }, "detect_positive": { "values": [0, 1] } },
22
  "typeConstraints": { "T": ["float32", "float16"], "B": ["bool"] },
23
- "args": {
24
- "x": { "kind": "tensor", "semantic": "X", "role": "input" },
25
- "y": { "kind": "tensor", "semantic": "Y", "role": "output" }
26
- },
27
- "tunables": { "WORKGROUP_SIZE": 256 },
28
- "bindingSets": {
29
- "sameLayoutVec4": [
30
- {
31
- "name": "x",
32
- "arg": "x",
33
- "semantic": "X",
34
- "buffer": { "type": "read-only-storage" },
35
- "elementType": "$vectorScalar"
36
- },
37
- { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "$outVec" },
38
- {
39
- "name": "params",
40
- "semantic": "kernel.params",
41
- "buffer": { "type": "uniform" },
42
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }] }
43
- }
44
- ],
45
- "detectNeither": [
46
- { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "u32" },
47
- {
48
- "name": "params",
49
- "semantic": "kernel.params",
50
- "buffer": { "type": "uniform" },
51
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] }
52
- }
53
- ]
54
- },
55
- "derive": {},
56
- "constants": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
57
  "variants": [
58
  {
59
  "id": "detect_neither",
60
- "when": ["attrs.detect_negative == 0", "attrs.detect_positive == 0", "numel(shapes.x) == numel(shapes.y)", "ranks.y == ranks.x"],
 
61
  "passes": [
62
  {
63
  "id": "main",
64
  "name": "IsInf.detectNeither",
65
- "source": { "shader": "detect-neither.wgsl.jinja", "inputs": {} },
66
- "bindings": "detectNeither",
67
- "dispatch": { "threads": "ceilDiv(numel(shapes.y), 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
 
 
68
  }
69
- ],
70
- "priority": 30
71
  },
72
  {
73
  "id": "same_layout_vec4",
74
- "when": ["attrs.detect_negative == 1 or attrs.detect_positive == 1", "numel(shapes.x) > 0", "numel(shapes.x) % 4 == 0", "numel(shapes.x) == numel(shapes.y)", "ranks.y == ranks.x", "f16Ok(dtypes.T)"],
75
- "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"", "outVec": "\"vec4<\" ~ dtypes.B ~ \">\"" },
 
76
  "passes": [
77
  {
78
  "id": "main",
79
  "name": "IsInf.vec4",
80
- "source": {
81
- "shader": "unary-vec4.wgsl.jinja",
82
- "inputs": {
83
- "op": "\"isinf\"",
84
- "detectNegative": "attrs.detect_negative",
85
- "detectPositive": "attrs.detect_positive"
86
- }
87
  },
88
- "bindings": "sameLayoutVec4",
89
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
 
 
 
90
  }
91
- ],
92
- "priority": 20
93
  },
94
  {
95
  "id": "elementwise",
96
- "when": ["ranks.y == ranks.x", "numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
97
  "passes": [
98
  {
99
  "id": "main",
100
  "name": "IsInf",
101
- "source": { "shader": "unary-scalar.wgsl.jinja", "inputs": { "op": "\"isinf\"", "itemsPerInvocation": 4 } },
 
102
  "bindings": [
103
- {
104
- "name": "x",
105
- "arg": "x",
106
- "semantic": "X",
107
- "buffer": { "type": "read-only-storage" },
108
- "elementType": "$scalar"
109
- },
110
- { "name": "y", "arg": "y", "semantic": "Y", "buffer": { "type": "storage" }, "elementType": "u32" },
111
  {
112
  "name": "params",
113
- "semantic": "kernel.params",
114
- "buffer": { "type": "uniform" },
115
- "struct": {
116
- "name": "Params",
117
- "fields": [
118
- { "name": "count", "type": "u32", "value": "numel(shapes.y)" },
119
- { "name": "detectNegative", "type": "u32", "value": "attrs.detect_negative" },
120
- { "name": "detectPositive", "type": "u32", "value": "attrs.detect_positive" }
121
- ]
122
- }
123
  }
124
  ],
125
- "dispatch": { "threads": "ceilDiv(numel(shapes.y), 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
126
  }
127
  ]
128
  }
 
2
  "domain": "ai.onnx",
3
  "name": "IsInf",
4
  "sinceVersion": 20,
5
+ "inputs": { "x": { "onnx": "X", "dtype": "T" } },
6
+ "outputs": { "y": { "onnx": "Y", "dtype": "B", "rank": "ranks.x", "shape": "shapes.x" } },
7
+ "attributes": { "detect_negative": { "default": 1 }, "detect_positive": { "default": 1 } },
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  "attributeConstraints": { "detect_negative": { "values": [0, 1] }, "detect_positive": { "values": [0, 1] } },
9
  "typeConstraints": { "T": ["float32", "float16"], "B": ["bool"] },
10
+ "tunables": { "WORKGROUP_SIZE": { "default": 256 } },
11
+ "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
12
+ "when": ["numel(shapes.x) == numel(shapes.y)", "ranks.y == ranks.x"],
13
+ "bindings": { "y": { "buffer": "storage", "elementType": "u32" } },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  "variants": [
15
  {
16
  "id": "detect_neither",
17
+ "priority": 30,
18
+ "when": ["attrs.detect_negative == 0", "attrs.detect_positive == 0"],
19
  "passes": [
20
  {
21
  "id": "main",
22
  "name": "IsInf.detectNeither",
23
+ "shader": "detect-neither.wgsl.jinja",
24
+ "bindings": [
25
+ "y",
26
+ { "name": "params", "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] }
27
+ ],
28
+ "dispatch": {
29
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
30
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
31
+ "z": 1
32
+ }
33
  }
34
+ ]
 
35
  },
36
  {
37
  "id": "same_layout_vec4",
38
+ "priority": 20,
39
+ "when": ["attrs.detect_negative == 1 or attrs.detect_positive == 1", "numel(shapes.x) > 0", "numel(shapes.x) % 4 == 0", "f16Ok(dtypes.T)"],
40
+ "derive": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"", "outVec": "\"vec4<\" ~ dtypes.B ~ \">\"" },
41
  "passes": [
42
  {
43
  "id": "main",
44
  "name": "IsInf.vec4",
45
+ "shader": "unary-vec4.wgsl.jinja",
46
+ "derive": {
47
+ "op": "\"isinf\"",
48
+ "detectNegative": "attrs.detect_negative",
49
+ "detectPositive": "attrs.detect_positive",
50
+ "vec4PerThread": "4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1"
 
51
  },
52
+ "bindings": [
53
+ { "arg": "x", "elementType": "$vectorScalar" },
54
+ { "arg": "y", "elementType": "$outVec" },
55
+ { "name": "params", "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }] }
56
+ ],
57
+ "dispatch": {
58
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.y) / 4, 4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)), (tunables.WORKGROUP_SIZE)), 65535)",
59
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y) / 4, 4 if numel(shapes.y) * dtypeBytes(tensorDtypes.y) <= 16777216 else 1)), (tunables.WORKGROUP_SIZE)), 65535)",
60
+ "z": 1
61
+ }
62
  }
63
+ ]
 
64
  },
65
  {
66
  "id": "elementwise",
67
+ "when": ["f16Ok(dtypes.T)"],
68
  "passes": [
69
  {
70
  "id": "main",
71
  "name": "IsInf",
72
+ "shader": "unary-scalar.wgsl.jinja",
73
+ "derive": { "op": "\"isinf\"", "itemsPerInvocation": 4 },
74
  "bindings": [
75
+ "x",
76
+ "y",
 
 
 
 
 
 
77
  {
78
  "name": "params",
79
+ "struct": [
80
+ { "name": "count", "type": "u32", "value": "numel(shapes.y)" },
81
+ { "name": "detectNegative", "type": "u32", "value": "attrs.detect_negative" },
82
+ { "name": "detectPositive", "type": "u32", "value": "attrs.detect_positive" }
83
+ ]
 
 
 
 
 
84
  }
85
  ],
86
+ "dispatch": {
87
+ "x": "min(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
88
+ "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
89
+ "z": 1
90
+ }
91
  }
92
  ]
93
  }
build/webgpu/metadata.json CHANGED
@@ -1,20 +1,27 @@
1
  {
2
  "name": "ai.onnx.IsInf",
3
- "id": "_ai_onnx_isinf_webgpu_f721f3c",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "fpWJYObMRn5yexPegoVWy7o9/p0savu4NWVgbY8dPd4=",
11
- "detect-neither.wgsl.jinja": "Og33gAC0ymZagfIJCnVu4N+uU8W0C+mq+mYg/9P12MQ=",
12
- "manifest.json": "rPgpA/3IjGWEs8AY4VCBvJ4+d4Pe9BWpJ3X2RCCHERU=",
13
- "test.json": "cnx4XDeVNUWmKXbZkFQuTOF/JuVvDXCK9zR+JzjXcI0=",
14
- "unary-scalar.wgsl.jinja": "dewu+DcCE6Ec/dQBnj0eJC/5VIhhRWfvXJbqzZP3mGM=",
15
- "unary-vec4.wgsl.jinja": "s46gUeMNEeKHdLt4//Eb8XKW6lIJU+ZHR9wj1nxqcIM="
16
  }
17
  },
18
- "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
19
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.IsInf" }
 
 
 
 
 
 
 
20
  }
 
1
  {
2
  "name": "ai.onnx.IsInf",
3
+ "id": "_ai_onnx_isinf_webgpu_a2a84e4",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "GwHMLvIe56WI1ZJMsKpWAk+Q1myS7TBHG8aiVFxE3j4=",
11
+ "detect-neither.wgsl.jinja": "0JrGKclRlUw89VVRMFVfRSZ9hCrh2srIwSTAT48xjAM=",
12
+ "manifest.json": "PKpOiTfopy5MSaM8l+xoMZsdeZceGA/cpV917UyQgoE=",
13
+ "test.json": "MQmJcYVSPCKAMfiYgHGwSLwqUY7KumLrp7QsKeEIrqk=",
14
+ "unary-scalar.wgsl.jinja": "5Gu9hbmocQhPBlp4Bqc0XqwgbHsPeNN1GkKzDdSxcT4=",
15
+ "unary-vec4.wgsl.jinja": "1eB8LyA7gHU47DxSGRS/piI/bv4otnBUGTRnUi1X9tI="
16
  }
17
  },
18
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
19
+ "webgpu": {
20
+ "manifestSpec": "2.0",
21
+ "variants": {
22
+ "detect_neither": ["detect-neither.wgsl.jinja"],
23
+ "same_layout_vec4": ["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.IsInf",
3
  "cases": [
4
  {
5
  "name": "f32_detect_both",
@@ -46,7 +45,7 @@
46
  "x": {
47
  "dtype": "float32",
48
  "shape": [5],
49
- "data": { "kind": "values", "values": ["Infinity", "-Infinity", "NaN", 1.0, -1.0] }
50
  }
51
  },
52
  "outputs": { "y": { "dtype": "bool", "shape": [5], "tolerance": 0 } }
@@ -279,7 +278,7 @@
279
  "shape": [8],
280
  "data": {
281
  "kind": "values",
282
- "values": ["Infinity", "-Infinity", "NaN", 0.0, -1.5, 3.6, "Infinity", "-Infinity"]
283
  }
284
  }
285
  },
@@ -337,10 +336,14 @@
337
  {
338
  "name": "f16_max_finite_not_inf",
339
  "inputs": {
340
- "x": { "dtype": "float16", "shape": [4], "data": { "kind": "values", "values": [65504.0, -65504.0, 0.0, 1.0] } }
 
 
 
 
341
  },
342
  "outputs": {
343
- "y": { "dtype": "bool", "shape": [4], "tolerance": 0, "data": { "kind": "values", "values": [0, 0, 0, 0] } }
344
  }
345
  },
346
  {
@@ -357,7 +360,7 @@
357
  },
358
  "outputs": { "y": { "dtype": "bool", "shape": [1, 1, 1, 1, 1, 1, 8], "tolerance": 0 } },
359
  "provenance": {
360
- "notes": "Rank-7 flat input on the shared vec4 unary kernel (indexes by flat numel, so rank is irrelevant; sibling Cast/LeakyRelu impose no rank cap). Covers the regime the removed 'ranks.x <= 6' guard rejected."
361
  }
362
  },
363
  {
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "f32_detect_both",
 
45
  "x": {
46
  "dtype": "float32",
47
  "shape": [5],
48
+ "data": { "kind": "values", "values": ["Infinity", "Infinity", "Infinity", "Infinity", "Infinity"] }
49
  }
50
  },
51
  "outputs": { "y": { "dtype": "bool", "shape": [5], "tolerance": 0 } }
 
278
  "shape": [8],
279
  "data": {
280
  "kind": "values",
281
+ "values": ["Infinity", "Infinity", "Infinity", "Infinity", "Infinity", "Infinity", "Infinity", "Infinity"]
282
  }
283
  }
284
  },
 
336
  {
337
  "name": "f16_max_finite_not_inf",
338
  "inputs": {
339
+ "x": {
340
+ "dtype": "float16",
341
+ "shape": [4],
342
+ "data": { "kind": "values", "values": [65504.0, -65504.0, "Infinity", 1.0] }
343
+ }
344
  },
345
  "outputs": {
346
+ "y": { "dtype": "bool", "shape": [4], "tolerance": 0, "data": { "kind": "values", "values": [0, 0, 1, 0] } }
347
  }
348
  },
349
  {
 
360
  },
361
  "outputs": { "y": { "dtype": "bool", "shape": [1, 1, 1, 1, 1, 1, 8], "tolerance": 0 } },
362
  "provenance": {
363
+ "notes": "A rank-7 flat input exercises the shared vec4 unary path, whose indexing depends only on total element count."
364
  }
365
  },
366
  {
build/webgpu/unary-scalar.wgsl.jinja CHANGED
@@ -1,21 +1,22 @@
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
  // 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
- let begin = invocation * {{ source.itemsPerInvocation }}u;
10
- let end = min(begin + {{ source.itemsPerInvocation }}u, params.count);
 
11
  for (var i = begin; i < end; i = i + 1u) {
12
  {%- endmacro %}
13
  {% macro flat_tail_close() %}
14
  }
15
  {% endmacro %}
16
 
17
- // Scalar unary fallback. Each branch retains the operation's numeric hardening,
18
- // including Payne-Hanek trigonometric range reduction and NaN/overflow guards.
19
  {% if usesF16 %}
20
  enable f16;
21
  {% 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
+ // Scalar unary elementwise implementation. Specialization emits only the
19
+ // selected operation and any numerical helper it requires.
20
  {% if usesF16 %}
21
  enable f16;
22
  {% endif %}
build/webgpu/unary-vec4.wgsl.jinja CHANGED
@@ -1,19 +1,35 @@
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
  let fv = vec4<f32>(xv);
19
  let bits = bitcast<vec4<u32>>(fv);
@@ -21,15 +37,18 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
21
  let mantissa = bits & vec4<u32>(0x007fffffu);
22
  let sgn = bits & vec4<u32>(0x80000000u);
23
  let is_inf = (exponent == vec4<u32>(0x7f800000u)) & (mantissa == vec4<u32>(0u));
24
- {% if source.detectPositive is defined and source.detectPositive %}
25
  let positive = is_inf & (sgn == vec4<u32>(0u));
26
  {% else %}
27
  let positive = vec4<bool>(false);
28
  {% endif %}
29
- {% if source.detectNegative is defined and source.detectNegative %}
30
  let negative = is_inf & (sgn != vec4<u32>(0u));
31
  {% else %}
32
  let negative = vec4<bool>(false);
33
  {% endif %}
34
  y[i] = select(vec4<u32>(0u), vec4<u32>(1u), positive | negative);
 
 
 
35
  }
 
1
+ // Loads and stores vec4<T> while evaluating the selected unary operation per
2
+ // component.
 
 
 
3
  {{ env.wgsl.resourceDeclarations }}
4
 
5
 
6
+ {% set vec4PerThread = vec4PerThread %}
7
+ {% if vec4PerThread > 1 %}
8
+ const ITEMS: u32 = {{ vec4PerThread }}u;
9
+ {% endif %}
10
+
11
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
12
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
13
  // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
14
+ // per-axis dispatch fold width (the dispatch caps x and spills the rest into y).
15
+ {% if vec4PerThread > 1 %}
16
+ // Each invocation walks ITEMS vec4 groups a span apart. Consecutive lanes
17
+ // access consecutive words on every step, while each lane can keep several
18
+ // independent loads in flight.
19
+ let tid = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
20
+ let span = (params.count + ITEMS - 1u) / ITEMS;
21
+ for (var j = 0u; j < ITEMS; j = j + 1u) {
22
+ let i = tid + j * span;
23
+ if (i >= params.count) {
24
+ break;
25
+ }
26
+ {% else %}
27
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
28
  if (i >= params.count) {
29
  return;
30
  }
31
+ {% endif %}
32
+
33
  let xv = x[i];
34
  let fv = vec4<f32>(xv);
35
  let bits = bitcast<vec4<u32>>(fv);
 
37
  let mantissa = bits & vec4<u32>(0x007fffffu);
38
  let sgn = bits & vec4<u32>(0x80000000u);
39
  let is_inf = (exponent == vec4<u32>(0x7f800000u)) & (mantissa == vec4<u32>(0u));
40
+ {% if detectPositive is defined and detectPositive %}
41
  let positive = is_inf & (sgn == vec4<u32>(0u));
42
  {% else %}
43
  let positive = vec4<bool>(false);
44
  {% endif %}
45
+ {% if detectNegative is defined and detectNegative %}
46
  let negative = is_inf & (sgn != vec4<u32>(0u));
47
  {% else %}
48
  let negative = vec4<bool>(false);
49
  {% endif %}
50
  y[i] = select(vec4<u32>(0u), vec4<u32>(1u), positive | negative);
51
+ {% if vec4PerThread > 1 %}
52
+ }
53
+ {% endif %}
54
  }