Xenova HF Staff commited on
Commit
a63f51a
·
verified ·
1 Parent(s): 3658569

sync 6fdf6301e2bb

Browse files
README.md CHANGED
@@ -39,14 +39,14 @@ See the [ONNX `Tan` spec](https://onnx.ai/onnx/operators/onnx__Tan.html) for the
39
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
40
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
41
  - [`test.json`](build/webgpu/test.json) — correctness cases
42
- - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
43
  - [`unary-scalar.wgsl.jinja`](build/webgpu/unary-scalar.wgsl.jinja)
44
  - [`unary-vec4.wgsl.jinja`](build/webgpu/unary-vec4.wgsl.jinja)
45
 
46
  ## Use with `@huggingface/kernels`
47
 
48
  ```sh
49
- npm install --save-exact @huggingface/kernels@0.0.1-preview.2
50
  ```
51
 
52
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
 
39
  - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
40
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
41
  - [`test.json`](build/webgpu/test.json) — correctness cases
42
+ - [`bench.json`](build/webgpu/bench.json) — benchmark cases
43
  - [`unary-scalar.wgsl.jinja`](build/webgpu/unary-scalar.wgsl.jinja)
44
  - [`unary-vec4.wgsl.jinja`](build/webgpu/unary-vec4.wgsl.jinja)
45
 
46
  ## Use with `@huggingface/kernels`
47
 
48
  ```sh
49
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.3
50
  ```
51
 
52
  Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
build/webgpu/manifest.json CHANGED
@@ -11,15 +11,11 @@
11
  "id": "same_layout_vec4",
12
  "priority": 20,
13
  "when": ["numel(shapes.x) > 0", "numel(shapes.x) % 4 == 0", "numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
14
- "derive": {
15
- "scalar": "dtypes.T",
16
- "usesF16": "dtypes.T == \"f16\"",
17
- "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\""
18
- },
19
  "passes": [
20
  {
21
  "id": "main",
22
- "name": "Tan.vec4",
23
  "shader": "unary-vec4.wgsl.jinja",
24
  "derive": {
25
  "op": "\"tan\"",
@@ -37,14 +33,14 @@
37
  {
38
  "id": "elementwise",
39
  "when": ["numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
40
- "derive": { "scalar": "dtypes.T", "usesF16": "dtypes.T == \"f16\"" },
41
  "passes": [
42
  {
43
  "id": "main",
44
  "name": "Tan",
45
  "shader": "unary-scalar.wgsl.jinja",
46
  "derive": { "op": "\"tan\"", "itemsPerInvocation": 4 },
47
- "bindings": ["x_2", "y_2", "params_2_unary"],
48
  "dispatch": {
49
  "x": "min(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
50
  "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
@@ -57,17 +53,9 @@
57
  "bindings": {
58
  "x": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
59
  "y": { "buffer": "storage", "elementType": "$vectorScalar" },
60
- "params_unary": {
61
- "buffer": "uniform",
62
- "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }],
63
- "name": "params"
64
- },
65
- "x_2": { "buffer": "read-only-storage", "name": "x", "elementType": "$scalar" },
66
- "y_2": { "buffer": "storage", "name": "y", "elementType": "$scalar" },
67
- "params_2_unary": {
68
- "buffer": "uniform",
69
- "name": "params",
70
- "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }]
71
- }
72
  }
73
  }
 
11
  "id": "same_layout_vec4",
12
  "priority": 20,
13
  "when": ["numel(shapes.x) > 0", "numel(shapes.x) % 4 == 0", "numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
14
+ "derive": { "scalar": "dtypes.T", "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
 
 
 
 
15
  "passes": [
16
  {
17
  "id": "main",
18
+ "name": "Tan.Vec4",
19
  "shader": "unary-vec4.wgsl.jinja",
20
  "derive": {
21
  "op": "\"tan\"",
 
33
  {
34
  "id": "elementwise",
35
  "when": ["numel(shapes.x) == numel(shapes.y)", "f16Ok(dtypes.T)"],
36
+ "derive": { "scalar": "dtypes.T" },
37
  "passes": [
38
  {
39
  "id": "main",
40
  "name": "Tan",
41
  "shader": "unary-scalar.wgsl.jinja",
42
  "derive": { "op": "\"tan\"", "itemsPerInvocation": 4 },
43
+ "bindings": ["x_scalar", "y_scalar", "params_2_unary"],
44
  "dispatch": {
45
  "x": "min(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
46
  "y": "ceilDiv(ceilDiv((ceilDiv(numel(shapes.y), 4)), (tunables.WORKGROUP_SIZE)), 65535)",
 
53
  "bindings": {
54
  "x": { "buffer": "read-only-storage", "elementType": "$vectorScalar" },
55
  "y": { "buffer": "storage", "elementType": "$vectorScalar" },
56
+ "params_unary": { "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y) / 4" }], "name": "params" },
57
+ "x_scalar": { "buffer": "read-only-storage", "name": "x", "elementType": "$scalar" },
58
+ "y_scalar": { "buffer": "storage", "name": "y", "elementType": "$scalar" },
59
+ "params_2_unary": { "name": "params", "struct": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] }
 
 
 
 
 
 
 
 
60
  }
61
  }
build/webgpu/metadata.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "ai.onnx.Tan",
3
- "id": "_ai_onnx_tan_webgpu_ea47a00",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
@@ -8,15 +8,15 @@
8
  "algorithm": "sha256",
9
  "files": {
10
  "bench.json": "UdERmcCeq6Elm16WZSEs0wCOLlz5Gg64dtn0Wviv3DU=",
11
- "manifest.json": "P0urm4KuqNzj8+8CUgeB6Y75va7RIxhENjBqrgu+1FM=",
12
- "test.json": "nr23CSBkeNglZRyckIYhrEX1pE4F9/leWOqRXvPJAPo=",
13
- "unary-scalar.wgsl.jinja": "EfW2qA5VRzSLMb5zpcr5BhhOJA7T9hBytO00uoqt5kE=",
14
- "unary-vec4.wgsl.jinja": "dnEOe8UV0joGHXg9Tl9Ejlycadsg9UxkcGwqtfTFI1c="
15
  }
16
  },
17
- "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
18
  "webgpu": {
19
- "manifestSpec": "2.0",
20
  "variants": { "same_layout_vec4": ["unary-vec4.wgsl.jinja"], "elementwise": ["unary-scalar.wgsl.jinja"] }
21
  }
22
  }
 
1
  {
2
  "name": "ai.onnx.Tan",
3
+ "id": "_ai_onnx_tan_webgpu_60ac5b0",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
 
8
  "algorithm": "sha256",
9
  "files": {
10
  "bench.json": "UdERmcCeq6Elm16WZSEs0wCOLlz5Gg64dtn0Wviv3DU=",
11
+ "manifest.json": "ExZsBKPXQx2r4Ij2LuPuAlwp+XH245/1agGxiZBfrPs=",
12
+ "test.json": "ZWt1K92ZoMAO2Y73HdN+RBDk+F1zgL8uSgKfLSrsjeg=",
13
+ "unary-scalar.wgsl.jinja": "FLarw3ZNNJ8cFjwPvaZgM9cxPxCCwWEga17KNxRl7Zg=",
14
+ "unary-vec4.wgsl.jinja": "CUYHc97trSgiXBBrwy4I+xtoLNSsNxj5qWP+fZi7D4M="
15
  }
16
  },
17
+ "provenance": { "kernel": { "sha": "6fdf6301e2bbcc2f03bf1eaf493b7ad55ef33afc", "dirty": false } },
18
  "webgpu": {
19
+ "manifestSpec": "2.1",
20
  "variants": { "same_layout_vec4": ["unary-vec4.wgsl.jinja"], "elementwise": ["unary-scalar.wgsl.jinja"] }
21
  }
22
  }
build/webgpu/test.json CHANGED
@@ -213,7 +213,7 @@
213
  },
214
  "outputs": { "y": { "dtype": "float16", "shape": [7], "tolerance": 0.05, "relTolerance": 0.01 } },
215
  "provenance": {
216
- "notes": "Float16 arguments at the top of the range force argument reduction in float32; the tolerance is scaled to the result magnitude with headroom over the reduction residue."
217
  }
218
  },
219
  {
@@ -227,7 +227,7 @@
227
  },
228
  "outputs": { "y": { "dtype": "float16", "shape": [8], "tolerance": 0.05, "relTolerance": 0.01 } },
229
  "provenance": {
230
- "notes": "Large float16 arguments on the vec4 lane path force argument reduction in float32; the tolerance is scaled to the result magnitude with headroom over the reduction residue."
231
  }
232
  }
233
  ]
 
213
  },
214
  "outputs": { "y": { "dtype": "float16", "shape": [7], "tolerance": 0.05, "relTolerance": 0.01 } },
215
  "provenance": {
216
+ "notes": "Float16 arguments near the format's maximum (65,504) require argument reduction in float32 before evaluating tan; tolerance is scaled to the result magnitude with headroom for the reduction residue."
217
  }
218
  },
219
  {
 
227
  },
228
  "outputs": { "y": { "dtype": "float16", "shape": [8], "tolerance": 0.05, "relTolerance": 0.01 } },
229
  "provenance": {
230
+ "notes": "Float16 arguments up to 40,000, mixed with values near 1, require argument reduction in float32 before evaluating tan across two vec4 words; tolerance is scaled to the result magnitude with headroom for the reduction residue."
231
  }
232
  }
233
  ]
build/webgpu/unary-scalar.wgsl.jinja CHANGED
@@ -12,29 +12,77 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
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 %}
23
  {{ env.wgsl.resourceDeclarations }}
24
- {% macro emit_is_nan_f32() %}
25
- fn is_nan_f32(value: f32) -> bool {
26
- let bits = bitcast<u32>(value);
27
- return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
28
- }{% endmacro %}
29
- {% macro emit_is_inf_f32() %}
30
  fn is_inf_f32(value: f32) -> bool {
31
  return (bitcast<u32>(value) & 0x7fffffffu) == 0x7f800000u;
32
- }{% endmacro %}
33
- {% macro emit_nan_from_inf_f32() %}
34
  fn nan_from_inf_f32(value: f32) -> f32 {
35
  return bitcast<f32>(bitcast<u32>(value) | 0x00400000u);
36
- }{% endmacro %}
37
- {% macro emit_reduce_pio2() %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  fn reduce_pio2(ax: f32) -> Pio2 {
39
  let ix = bitcast<u32>(ax);
40
  let e = (ix >> 23u) & 0xffu;
@@ -98,97 +146,32 @@ fn reduce_pio2(ax: f32) -> Pio2 {
98
  out.octant = n & 3u;
99
  out.r = phi * PIO2_F;
100
  return out;
101
- }{% endmacro %}
102
- {% macro emit_reduce_pio2_fast() %}
103
- fn reduce_pio2_fast(ax: f32) -> Pio2 {
104
- // Cody-Waite reduction for ordinary magnitudes. Splitting pi/2 keeps the
105
- // residual accurate even when n is thousands; larger inputs use Payne-Hanek.
106
- let nFloat = floor(fma(ax, INV_PIO2_F, 0.5));
107
- var residual = fma(-nFloat, PIO2_HI_F, ax);
108
- residual = fma(-nFloat, PIO2_LO_F, residual);
109
- var out: Pio2;
110
- out.octant = u32(nFloat) & 3u;
111
- out.r = residual;
112
- return out;
113
- }{% endmacro %}
114
- {% macro emit_trig_reduction_support() %}
115
- {% set needPreciseTrigCentered = false %}
116
- {% set needPreciseTrigTwoPi = false %}
117
- // Backend-stable f32 sine/cosine core.
118
- //
119
- // Shader transcendental accuracy is implementation-defined. Each path retains
120
- // its available phase representation, reduces it to [-pi, pi], then evaluates
121
- // explicit polynomials in a fixed order. The half-pi core is degree 13 for sine
122
- // and degree 12 for cosine.
123
- {% if needPreciseTrigCentered %}
124
- const PRECISE_TRIG_PI: f32 = 3.141592653589793;
125
- {% endif %}
126
- {% if needPreciseTrigTwoPi %}
127
- const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
128
- {% endif %}
129
- {% if needPreciseTrigCentered %}
130
- const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
131
- {% endif %}
132
-
133
- // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
134
- fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
135
- let x2 = x * x;
136
-
137
- var sinPolynomial = 1.6059043836821613e-10;
138
- sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
139
- sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
140
- sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
141
- sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
142
- sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
143
- let sine = x * fma(sinPolynomial, x2, 1.0);
144
-
145
- var cosPolynomial = 2.08767569878681e-9;
146
- cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
147
- cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
148
- cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
149
- cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
150
- cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
151
- let cosine = fma(cosPolynomial, x2, 1.0);
152
-
153
- return vec2<f32>(cosine, sine);
154
  }
155
- {% if needPreciseTrigCentered %}
156
-
157
- // Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
158
- fn precise_sincos_centered(x: f32) -> vec2<f32> {
159
- var folded = x;
160
- var cosineSign = 1.0;
161
- if (folded > PRECISE_TRIG_HALF_PI) {
162
- folded = PRECISE_TRIG_PI - folded;
163
- cosineSign = -1.0;
164
- } else if (folded < -PRECISE_TRIG_HALF_PI) {
165
- folded = -PRECISE_TRIG_PI - folded;
166
- cosineSign = -1.0;
 
 
 
 
 
 
 
 
167
  }
168
- let value = precise_sincos_half_pi(folded);
169
- return vec2<f32>(cosineSign * value.x, value.y);
170
  }
171
- {% endif %}
172
-
173
-
174
- // 24-bit words of 2/pi used by the exact Payne-Hanek large-input reducer.
175
- const TWO_OVER_PI: array<u32, 14> = array<u32, 14>(
176
- 0xa2f983u, 0x6e4e44u, 0x1529fcu, 0x2757d1u, 0xf534ddu, 0xc0db62u,
177
- 0x95993cu, 0x439041u, 0xfe5163u, 0xabdebbu, 0xc561b7u, 0x246e3au,
178
- 0x424dd2u, 0xe00649u
179
- );
180
- const PIO2_F: f32 = 1.5707963267948966;
181
- const INV_PIO2_F: f32 = 0.6366197723675814;
182
- const PIO2_HI_F: f32 = 1.570796251296997;
183
- const PIO2_LO_F: f32 = 7.549789415861596e-8;
184
- const REDUCE_THRESHOLD: f32 = 1.0e4;
185
-
186
- struct Pio2 { octant: u32, r: f32 };
187
-
188
- {{ emit_reduce_pio2_fast() }}
189
-
190
- {{ emit_reduce_pio2() }}
191
- {%- endmacro %}
192
  {% macro emit_tan_accurate() %}
193
  fn tan_accurate(x: f32) -> f32 {
194
  if (is_nan_f32(x)) {
@@ -214,17 +197,17 @@ fn tan_accurate(x: f32) -> f32 {
214
  let tn = sn / cs;
215
  // tan is odd: transfer x's sign bit so tan(-0.0) stays -0.0 (x < 0.0 misses it).
216
  return bitcast<f32>(bitcast<u32>(tn) ^ (bitcast<u32>(x) & 0x80000000u));
217
- }{% endmacro %}
 
218
  {{ emit_trig_reduction_support() }}
 
 
 
 
219
 
220
- {{ emit_is_nan_f32() }}
221
-
222
- {{ emit_is_inf_f32() }}
223
-
224
- {{ emit_nan_from_inf_f32() }}
225
-
226
- {{ emit_tan_accurate() }}
227
  {{ flat_tail_open() }}
228
  y[i] = {{ scalar }}(tan_accurate(f32(x[i])));
229
- {{ flat_tail_close() -}}
230
  }
 
12
  for (var i = begin; i < end; i = i + 1u) {
13
  {%- endmacro %}
14
  {% macro flat_tail_close() %}
15
+ }{% endmacro %}
16
+ // Scalar unary elementwise kernel: applies the selected operation, with the
17
+ // numerical helpers it uses, to each element.
 
 
 
 
 
18
  {{ env.wgsl.resourceDeclarations }}
19
+
20
+ {% macro emit_inf_classifiers() %}
 
 
 
 
21
  fn is_inf_f32(value: f32) -> bool {
22
  return (bitcast<u32>(value) & 0x7fffffffu) == 0x7f800000u;
23
+ }
24
+
25
  fn nan_from_inf_f32(value: f32) -> f32 {
26
  return bitcast<f32>(bitcast<u32>(value) | 0x00400000u);
27
+ }
28
+ {% endmacro %}
29
+ {% macro emit_trig_reduction_support() %}
30
+ // Backend-stable f32 sine/cosine core.
31
+ //
32
+ // Shader transcendental accuracy is implementation-defined. Each path retains
33
+ // its available phase representation, reduces it to [-pi, pi], then evaluates
34
+ // explicit polynomials in a fixed order. The half-pi core is degree 13 for sine
35
+ // and degree 12 for cosine.
36
+
37
+ // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
38
+ fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
39
+ let x2 = x * x;
40
+
41
+ var sinPolynomial = 1.6059043836821613e-10;
42
+ sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
43
+ sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
44
+ sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
45
+ sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
46
+ sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
47
+ let sine = x * fma(sinPolynomial, x2, 1.0);
48
+
49
+ var cosPolynomial = 2.08767569878681e-9;
50
+ cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
51
+ cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
52
+ cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
53
+ cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
54
+ cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
55
+ let cosine = fma(cosPolynomial, x2, 1.0);
56
+
57
+ return vec2<f32>(cosine, sine);
58
+ }
59
+
60
+ // 24-bit words of 2/pi used by the exact Payne-Hanek large-input reducer.
61
+ const TWO_OVER_PI: array<u32, 14> = array<u32, 14>(
62
+ 0xa2f983u, 0x6e4e44u, 0x1529fcu, 0x2757d1u, 0xf534ddu, 0xc0db62u,
63
+ 0x95993cu, 0x439041u, 0xfe5163u, 0xabdebbu, 0xc561b7u, 0x246e3au,
64
+ 0x424dd2u, 0xe00649u
65
+ );
66
+ const PIO2_F: f32 = 1.5707963267948966;
67
+ const INV_PIO2_F: f32 = 0.6366197723675814;
68
+ const PIO2_HI_F: f32 = 1.570796251296997;
69
+ const PIO2_LO_F: f32 = 7.549789415861596e-8;
70
+ const REDUCE_THRESHOLD: f32 = 1.0e4;
71
+
72
+ struct Pio2 { octant: u32, r: f32 };
73
+
74
+ fn reduce_pio2_fast(ax: f32) -> Pio2 {
75
+ // Cody-Waite reduction for ordinary magnitudes. Splitting pi/2 keeps the
76
+ // residual accurate even when n is thousands; larger inputs use Payne-Hanek.
77
+ let nFloat = floor(fma(ax, INV_PIO2_F, 0.5));
78
+ var residual = fma(-nFloat, PIO2_HI_F, ax);
79
+ residual = fma(-nFloat, PIO2_LO_F, residual);
80
+ var out: Pio2;
81
+ out.octant = u32(nFloat) & 3u;
82
+ out.r = residual;
83
+ return out;
84
+ }
85
+
86
  fn reduce_pio2(ax: f32) -> Pio2 {
87
  let ix = bitcast<u32>(ax);
88
  let e = (ix >> 23u) & 0xffu;
 
146
  out.octant = n & 3u;
147
  out.r = phi * PIO2_F;
148
  return out;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  }
150
+ {% endmacro %}
151
+ {% macro emit_sin_accurate() %}
152
+ fn sin_accurate(x: f32) -> f32 {
153
+ if (is_nan_f32(x)) {
154
+ return x;
155
+ }
156
+ if (is_inf_f32(x)) {
157
+ return nan_from_inf_f32(x);
158
+ }
159
+ let ax = abs(x);
160
+ var red: Pio2;
161
+ if (ax < REDUCE_THRESHOLD) { red = reduce_pio2_fast(ax); }
162
+ else { red = reduce_pio2(ax); }
163
+ let reduced = precise_sincos_half_pi(red.r);
164
+ var s: f32;
165
+ switch (red.octant) {
166
+ case 0u: { s = reduced.y; }
167
+ case 1u: { s = reduced.x; }
168
+ case 2u: { s = -reduced.y; }
169
+ default: { s = -reduced.x; }
170
  }
171
+ // sin is odd: transfer x's sign bit so sin(-0.0) stays -0.0 (x < 0.0 misses it).
172
+ return bitcast<f32>(bitcast<u32>(s) ^ (bitcast<u32>(x) & 0x80000000u));
173
  }
174
+ {% endmacro %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  {% macro emit_tan_accurate() %}
176
  fn tan_accurate(x: f32) -> f32 {
177
  if (is_nan_f32(x)) {
 
197
  let tn = sn / cs;
198
  // tan is odd: transfer x's sign bit so tan(-0.0) stays -0.0 (x < 0.0 misses it).
199
  return bitcast<f32>(bitcast<u32>(tn) ^ (bitcast<u32>(x) & 0x80000000u));
200
+ }
201
+ {% endmacro %}
202
  {{ emit_trig_reduction_support() }}
203
+ fn is_nan_f32(value: f32) -> bool {
204
+ let bits = bitcast<u32>(value);
205
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
206
+ }
207
 
208
+ {{ emit_inf_classifiers() }}
209
+ {{ emit_sin_accurate() if op == "sin" else emit_tan_accurate() }}
 
 
 
 
 
210
  {{ flat_tail_open() }}
211
  y[i] = {{ scalar }}(tan_accurate(f32(x[i])));
212
+ {{ flat_tail_close() }}
213
  }
build/webgpu/unary-vec4.wgsl.jinja CHANGED
@@ -2,20 +2,72 @@
2
  // component.
3
  {{ env.wgsl.resourceDeclarations }}
4
 
5
- {% macro emit_is_nan_f32() %}
6
- fn is_nan_f32(value: f32) -> bool {
7
- let bits = bitcast<u32>(value);
8
- return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
9
- }{% endmacro %}
10
- {% macro emit_is_inf_f32() %}
11
  fn is_inf_f32(value: f32) -> bool {
12
  return (bitcast<u32>(value) & 0x7fffffffu) == 0x7f800000u;
13
- }{% endmacro %}
14
- {% macro emit_nan_from_inf_f32() %}
15
  fn nan_from_inf_f32(value: f32) -> f32 {
16
  return bitcast<f32>(bitcast<u32>(value) | 0x00400000u);
17
- }{% endmacro %}
18
- {% macro emit_reduce_pio2() %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  fn reduce_pio2(ax: f32) -> Pio2 {
20
  let ix = bitcast<u32>(ax);
21
  let e = (ix >> 23u) & 0xffu;
@@ -79,97 +131,8 @@ fn reduce_pio2(ax: f32) -> Pio2 {
79
  out.octant = n & 3u;
80
  out.r = phi * PIO2_F;
81
  return out;
82
- }{% endmacro %}
83
- {% macro emit_reduce_pio2_fast() %}
84
- fn reduce_pio2_fast(ax: f32) -> Pio2 {
85
- // Cody-Waite reduction for ordinary magnitudes. Splitting pi/2 keeps the
86
- // residual accurate even when n is thousands; larger inputs use Payne-Hanek.
87
- let nFloat = floor(fma(ax, INV_PIO2_F, 0.5));
88
- var residual = fma(-nFloat, PIO2_HI_F, ax);
89
- residual = fma(-nFloat, PIO2_LO_F, residual);
90
- var out: Pio2;
91
- out.octant = u32(nFloat) & 3u;
92
- out.r = residual;
93
- return out;
94
- }{% endmacro %}
95
- {% macro emit_trig_reduction_support() %}
96
- {% set needPreciseTrigCentered = false %}
97
- {% set needPreciseTrigTwoPi = false %}
98
- // Backend-stable f32 sine/cosine core.
99
- //
100
- // Shader transcendental accuracy is implementation-defined. Each path retains
101
- // its available phase representation, reduces it to [-pi, pi], then evaluates
102
- // explicit polynomials in a fixed order. The half-pi core is degree 13 for sine
103
- // and degree 12 for cosine.
104
- {% if needPreciseTrigCentered %}
105
- const PRECISE_TRIG_PI: f32 = 3.141592653589793;
106
- {% endif %}
107
- {% if needPreciseTrigTwoPi %}
108
- const PRECISE_TRIG_TWO_PI: f32 = 6.283185307179586;
109
- {% endif %}
110
- {% if needPreciseTrigCentered %}
111
- const PRECISE_TRIG_HALF_PI: f32 = 1.5707963267948966;
112
- {% endif %}
113
-
114
- // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
115
- fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
116
- let x2 = x * x;
117
-
118
- var sinPolynomial = 1.6059043836821613e-10;
119
- sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
120
- sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
121
- sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
122
- sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
123
- sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
124
- let sine = x * fma(sinPolynomial, x2, 1.0);
125
-
126
- var cosPolynomial = 2.08767569878681e-9;
127
- cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
128
- cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
129
- cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
130
- cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
131
- cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
132
- let cosine = fma(cosPolynomial, x2, 1.0);
133
-
134
- return vec2<f32>(cosine, sine);
135
- }
136
- {% if needPreciseTrigCentered %}
137
-
138
- // Input must be in [-pi, pi]. Returns (cos(x), sin(x)).
139
- fn precise_sincos_centered(x: f32) -> vec2<f32> {
140
- var folded = x;
141
- var cosineSign = 1.0;
142
- if (folded > PRECISE_TRIG_HALF_PI) {
143
- folded = PRECISE_TRIG_PI - folded;
144
- cosineSign = -1.0;
145
- } else if (folded < -PRECISE_TRIG_HALF_PI) {
146
- folded = -PRECISE_TRIG_PI - folded;
147
- cosineSign = -1.0;
148
- }
149
- let value = precise_sincos_half_pi(folded);
150
- return vec2<f32>(cosineSign * value.x, value.y);
151
  }
152
- {% endif %}
153
-
154
-
155
- // 24-bit words of 2/pi used by the exact Payne-Hanek large-input reducer.
156
- const TWO_OVER_PI: array<u32, 14> = array<u32, 14>(
157
- 0xa2f983u, 0x6e4e44u, 0x1529fcu, 0x2757d1u, 0xf534ddu, 0xc0db62u,
158
- 0x95993cu, 0x439041u, 0xfe5163u, 0xabdebbu, 0xc561b7u, 0x246e3au,
159
- 0x424dd2u, 0xe00649u
160
- );
161
- const PIO2_F: f32 = 1.5707963267948966;
162
- const INV_PIO2_F: f32 = 0.6366197723675814;
163
- const PIO2_HI_F: f32 = 1.570796251296997;
164
- const PIO2_LO_F: f32 = 7.549789415861596e-8;
165
- const REDUCE_THRESHOLD: f32 = 1.0e4;
166
-
167
- struct Pio2 { octant: u32, r: f32 };
168
-
169
- {{ emit_reduce_pio2_fast() }}
170
-
171
- {{ emit_reduce_pio2() }}
172
- {%- endmacro %}
173
  {% macro emit_tan_accurate() %}
174
  fn tan_accurate(x: f32) -> f32 {
175
  if (is_nan_f32(x)) {
@@ -195,43 +158,52 @@ fn tan_accurate(x: f32) -> f32 {
195
  let tn = sn / cs;
196
  // tan is odd: transfer x's sign bit so tan(-0.0) stays -0.0 (x < 0.0 misses it).
197
  return bitcast<f32>(bitcast<u32>(tn) ^ (bitcast<u32>(x) & 0x80000000u));
198
- }{% endmacro %}
199
- {{ emit_is_nan_f32() }}
200
- {{ emit_trig_reduction_support() }}
201
-
202
- {{ emit_is_inf_f32() }}
203
-
204
- {{ emit_nan_from_inf_f32() }}
205
 
 
 
206
  {{ emit_tan_accurate() }}
207
-
208
- {% set vec4PerThread = vec4PerThread %}
 
 
 
 
 
 
 
 
209
  {% if vec4PerThread > 1 %}
210
  const ITEMS: u32 = {{ vec4PerThread }}u;
211
- {% endif %}
212
 
 
213
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
214
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
215
- // 2D-folded flat index: gid.y carries the high bits when the element count exceeds the
216
- // per-axis dispatch fold width (the dispatch caps x and spills the rest into y).
217
  {% if vec4PerThread > 1 %}
218
  // Each invocation walks ITEMS vec4 groups a span apart. Consecutive lanes
219
  // access consecutive words on every step, while each lane can keep several
220
  // independent loads in flight.
221
- let tid = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
222
  let span = (params.count + ITEMS - 1u) / ITEMS;
 
 
 
 
 
 
223
  for (var j = 0u; j < ITEMS; j = j + 1u) {
224
  let i = tid + j * span;
225
  if (i >= params.count) {
226
  break;
227
  }
228
  {% else %}
229
- let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
230
- if (i >= params.count) {
231
- return;
232
- }
233
  {% endif %}
234
-
235
  let xv = x[i];
236
  let fv = vec4<f32>(xv);
237
  y[i] = {{ vectorScalar }}(vec4<f32>(tan_accurate(fv.x), tan_accurate(fv.y), tan_accurate(fv.z), tan_accurate(fv.w)));
 
2
  // component.
3
  {{ env.wgsl.resourceDeclarations }}
4
 
5
+ {% macro emit_inf_classifiers() %}
 
 
 
 
 
6
  fn is_inf_f32(value: f32) -> bool {
7
  return (bitcast<u32>(value) & 0x7fffffffu) == 0x7f800000u;
8
+ }
9
+
10
  fn nan_from_inf_f32(value: f32) -> f32 {
11
  return bitcast<f32>(bitcast<u32>(value) | 0x00400000u);
12
+ }
13
+ {% endmacro %}
14
+ {% macro emit_trig_reduction_support() %}
15
+ // Backend-stable f32 sine/cosine core.
16
+ //
17
+ // Shader transcendental accuracy is implementation-defined. Each path retains
18
+ // its available phase representation, reduces it to [-pi, pi], then evaluates
19
+ // explicit polynomials in a fixed order. The half-pi core is degree 13 for sine
20
+ // and degree 12 for cosine.
21
+
22
+ // Input must be in [-pi/2, pi/2]. Returns (cos(x), sin(x)).
23
+ fn precise_sincos_half_pi(x: f32) -> vec2<f32> {
24
+ let x2 = x * x;
25
+
26
+ var sinPolynomial = 1.6059043836821613e-10;
27
+ sinPolynomial = fma(sinPolynomial, x2, -2.505210838544172e-8);
28
+ sinPolynomial = fma(sinPolynomial, x2, 2.7557319223985893e-6);
29
+ sinPolynomial = fma(sinPolynomial, x2, -1.984126984126984e-4);
30
+ sinPolynomial = fma(sinPolynomial, x2, 8.333333333333333e-3);
31
+ sinPolynomial = fma(sinPolynomial, x2, -1.6666666666666666e-1);
32
+ let sine = x * fma(sinPolynomial, x2, 1.0);
33
+
34
+ var cosPolynomial = 2.08767569878681e-9;
35
+ cosPolynomial = fma(cosPolynomial, x2, -2.755731922398589e-7);
36
+ cosPolynomial = fma(cosPolynomial, x2, 2.48015873015873e-5);
37
+ cosPolynomial = fma(cosPolynomial, x2, -1.388888888888889e-3);
38
+ cosPolynomial = fma(cosPolynomial, x2, 4.1666666666666664e-2);
39
+ cosPolynomial = fma(cosPolynomial, x2, -5.0e-1);
40
+ let cosine = fma(cosPolynomial, x2, 1.0);
41
+
42
+ return vec2<f32>(cosine, sine);
43
+ }
44
+
45
+ // 24-bit words of 2/pi used by the exact Payne-Hanek large-input reducer.
46
+ const TWO_OVER_PI: array<u32, 14> = array<u32, 14>(
47
+ 0xa2f983u, 0x6e4e44u, 0x1529fcu, 0x2757d1u, 0xf534ddu, 0xc0db62u,
48
+ 0x95993cu, 0x439041u, 0xfe5163u, 0xabdebbu, 0xc561b7u, 0x246e3au,
49
+ 0x424dd2u, 0xe00649u
50
+ );
51
+ const PIO2_F: f32 = 1.5707963267948966;
52
+ const INV_PIO2_F: f32 = 0.6366197723675814;
53
+ const PIO2_HI_F: f32 = 1.570796251296997;
54
+ const PIO2_LO_F: f32 = 7.549789415861596e-8;
55
+ const REDUCE_THRESHOLD: f32 = 1.0e4;
56
+
57
+ struct Pio2 { octant: u32, r: f32 };
58
+
59
+ fn reduce_pio2_fast(ax: f32) -> Pio2 {
60
+ // Cody-Waite reduction for ordinary magnitudes. Splitting pi/2 keeps the
61
+ // residual accurate even when n is thousands; larger inputs use Payne-Hanek.
62
+ let nFloat = floor(fma(ax, INV_PIO2_F, 0.5));
63
+ var residual = fma(-nFloat, PIO2_HI_F, ax);
64
+ residual = fma(-nFloat, PIO2_LO_F, residual);
65
+ var out: Pio2;
66
+ out.octant = u32(nFloat) & 3u;
67
+ out.r = residual;
68
+ return out;
69
+ }
70
+
71
  fn reduce_pio2(ax: f32) -> Pio2 {
72
  let ix = bitcast<u32>(ax);
73
  let e = (ix >> 23u) & 0xffu;
 
131
  out.octant = n & 3u;
132
  out.r = phi * PIO2_F;
133
  return out;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  }
135
+ {% endmacro %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  {% macro emit_tan_accurate() %}
137
  fn tan_accurate(x: f32) -> f32 {
138
  if (is_nan_f32(x)) {
 
158
  let tn = sn / cs;
159
  // tan is odd: transfer x's sign bit so tan(-0.0) stays -0.0 (x < 0.0 misses it).
160
  return bitcast<f32>(bitcast<u32>(tn) ^ (bitcast<u32>(x) & 0x80000000u));
161
+ }
162
+ {% endmacro %}
163
+ fn is_nan_f32(value: f32) -> bool {
164
+ let bits = bitcast<u32>(value);
165
+ return (bits & 0x7f800000u) == 0x7f800000u && (bits & 0x007fffffu) != 0u;
166
+ }
 
167
 
168
+ {{ emit_trig_reduction_support() }}
169
+ {{ emit_inf_classifiers() }}
170
  {{ emit_tan_accurate() }}
171
+ {% macro flat_index_2d(workgroupSize, name="i", bound="params.count", guardInline=false) %}
172
+ {% set wgTerm = workgroupSize ~ "u" if workgroupSize is number else workgroupSize %}
173
+ // 2D-folded flat index: gid.y carries the high bits past the dispatch's
174
+ // per-axis workgroup fold width.
175
+ {% if bound == "" %}
176
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};{% else %}
177
+ let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ wgTerm }};
178
+ if ({{ name }} >= {{ bound }}) {
179
+ return;
180
+ }{% endif %}{% endmacro %}
181
  {% if vec4PerThread > 1 %}
182
  const ITEMS: u32 = {{ vec4PerThread }}u;
 
183
 
184
+ {% endif %}
185
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
186
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
 
 
187
  {% if vec4PerThread > 1 %}
188
  // Each invocation walks ITEMS vec4 groups a span apart. Consecutive lanes
189
  // access consecutive words on every step, while each lane can keep several
190
  // independent loads in flight.
191
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE, "tid", "") }}
192
  let span = (params.count + ITEMS - 1u) / ITEMS;
193
+ // Lanes at or past span exist only because the dispatch rounds up to whole
194
+ // workgroups. Lane span + k would start on lane k's second group and rewrite
195
+ // up to ITEMS - 1 groups another lane already stored.
196
+ if (tid >= span) {
197
+ return;
198
+ }
199
  for (var j = 0u; j < ITEMS; j = j + 1u) {
200
  let i = tid + j * span;
201
  if (i >= params.count) {
202
  break;
203
  }
204
  {% else %}
205
+ {{ flat_index_2d(tunables.WORKGROUP_SIZE) }}
 
 
 
206
  {% endif %}
 
207
  let xv = x[i];
208
  let fv = vec4<f32>(xv);
209
  y[i] = {{ vectorScalar }}(vec4<f32>(tan_accurate(fv.x), tan_accurate(fv.y), tan_accurate(fv.z), tan_accurate(fv.w)));