Xenova HF Staff commited on
Commit
9b3a220
·
verified ·
1 Parent(s): f4636d9

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,69 @@
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.Mod
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 13
12
+
13
+ ## Description
14
+
15
+ Performs elementwise binary modulo on tensors `A` and `B` with multidirectional broadcasting. When `fmod` is `0` (default), applies Python-style `%` with the sign of the divisor; when `fmod` is `1`, applies C-style `fmod` with the sign of the dividend.
16
+
17
+ See the [ONNX `Mod` spec](https://onnx.ai/onnx/operators/onnx__Mod.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `A` | `a` | `T` | — | — | Dividend tensor. | required |
24
+ | `B` | `b` | `T` | — | — | Divisor tensor. | required |
25
+
26
+ ## Outputs
27
+
28
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
29
+ | --- | --- | --- | --- | --- | --- | --- |
30
+ | `C` | `c` | `T` | derived | broadcast result of `A` and `B` | Remainder tensor; same shape as the broadcast result of A and B. | required |
31
+
32
+ ## Attributes
33
+
34
+ Default values (overridable per request):
35
+
36
+ | Attribute | Default | Description |
37
+ | --- | --- | --- |
38
+ | `fmod` | `0` | Controls the modulo mode: `0` (default) uses Python-style integer mod (sign of divisor); `1` uses C-style `fmod` (sign of dividend, floating-point types only). |
39
+
40
+ ## Type constraints
41
+
42
+ | Variable | Allowed dtypes |
43
+ | --- | --- |
44
+ | `T` | `float32`, `float16`, `int32`, `uint32`, `int16`, `int8`, `uint8` |
45
+
46
+ ## Files
47
+
48
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
49
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
50
+ - [`test.json`](build/webgpu/test.json) — correctness cases
51
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
52
+ - [`mod-vec4.wgsl.jinja`](build/webgpu/mod-vec4.wgsl.jinja)
53
+ - [`mod.wgsl.jinja`](build/webgpu/mod.wgsl.jinja)
54
+
55
+ ## Use with `@huggingface/kernels`
56
+
57
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
58
+ It then allocates the result tensors automatically.
59
+
60
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
61
+
62
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
63
+
64
+ ```js
65
+ import { getKernel } from "@huggingface/kernels";
66
+
67
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.Mod", { version: 1 });
68
+ const { c } = await kernel({ a: { data: aData, shape: [3] }, b: { data: bData, shape: [3] } });
69
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.Mod",
3
+ "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
4
+ "cases": [
5
+ {
6
+ "name": "8m_u32_scalar_div",
7
+ "preset": "smoke",
8
+ "inputs": {
9
+ "a": { "dtype": "uint32", "shape": [8388608] },
10
+ "b": { "dtype": "uint32", "shape": [1], "data": { "kind": "constant", "value": 17 } }
11
+ },
12
+ "outputs": { "c": { "dtype": "uint32", "shape": [8388608] } },
13
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "8388608 * 4 * 2" }] }
14
+ },
15
+ {
16
+ "name": "1m_u32",
17
+ "inputs": {
18
+ "a": { "dtype": "uint32", "shape": [1048576] },
19
+ "b": { "dtype": "uint32", "shape": [1], "data": { "kind": "constant", "value": 17 } }
20
+ },
21
+ "outputs": { "c": { "dtype": "uint32", "shape": [1048576] } }
22
+ },
23
+ {
24
+ "name": "1m_f32_fmod_same_shape",
25
+ "attrs": { "fmod": 1 },
26
+ "inputs": {
27
+ "a": { "dtype": "float32", "shape": [1048576] },
28
+ "b": { "dtype": "float32", "shape": [1048576], "data": { "kind": "constant", "value": 3.7 } }
29
+ },
30
+ "outputs": { "c": { "dtype": "float32", "shape": [1048576] } }
31
+ },
32
+ {
33
+ "name": "f32_broadcast_per_channel_fallback",
34
+ "preset": "smoke",
35
+ "vars": { "dtype": "float32" },
36
+ "attrs": { "fmod": 1 },
37
+ "inputs": {
38
+ "a": { "dtype": "float32", "shape": [2048, 2049], "dist": "normal", "seed": 711, "scale": 2 },
39
+ "b": { "dtype": "float32", "shape": [2049], "data": { "kind": "constant", "value": 3.0 } }
40
+ },
41
+ "outputs": { "c": { "dtype": "float32", "shape": [2048, 2049] } },
42
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "2048 * 2049 * 2 * dtypeBytes(args.dtype)" }] }
43
+ },
44
+ {
45
+ "name": "1m_f16_fmod_same_shape",
46
+ "preset": "smoke",
47
+ "vars": { "dtype": "float16", "count": 1048576 },
48
+ "attrs": { "fmod": 1 },
49
+ "inputs": {
50
+ "a": { "dtype": "float16", "shape": [1048576], "dist": "normal", "seed": 712, "scale": 8 },
51
+ "b": { "dtype": "float16", "shape": [1048576], "data": { "kind": "constant", "value": 3.5 } }
52
+ },
53
+ "outputs": { "c": { "dtype": "float16", "shape": [1048576] } },
54
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 3" }] }
55
+ },
56
+ {
57
+ "name": "1m_i32_floor_mod_same_shape",
58
+ "preset": "smoke",
59
+ "vars": { "dtype": "int32", "count": 1048576 },
60
+ "inputs": {
61
+ "a": { "dtype": "int32", "shape": [1048576], "dist": "normal", "seed": 713, "scale": 1000 },
62
+ "b": { "dtype": "int32", "shape": [1048576], "data": { "kind": "constant", "value": 7 } }
63
+ },
64
+ "outputs": { "c": { "dtype": "int32", "shape": [1048576] } },
65
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "args.count * dtypeBytes(args.dtype) * 3" }] }
66
+ },
67
+ {
68
+ "name": "i32_floor_mod_broadcast_non_vec4_shape",
69
+ "preset": "stress",
70
+ "attrs": { "fmod": 0 },
71
+ "inputs": {
72
+ "a": { "dtype": "int32", "shape": [2049, 2051], "dist": "normal", "seed": 801, "scale": 1000 },
73
+ "b": { "dtype": "int32", "shape": [2051], "data": { "kind": "constant", "value": 7 } }
74
+ },
75
+ "outputs": { "c": { "dtype": "int32", "shape": [2049, 2051] } },
76
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "2049 * 2051 * 4 * 2" }] }
77
+ }
78
+ ]
79
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "Mod",
4
+ "sinceVersion": 13,
5
+ "description": "Performs elementwise binary modulo on tensors `A` and `B` with multidirectional broadcasting. When `fmod` is `0` (default), applies Python-style `%` with the sign of the divisor; when `fmod` is `1`, applies C-style `fmod` with the sign of the dividend.",
6
+ "inputs": [
7
+ { "role": "A", "dtype": "T", "description": "Dividend tensor." },
8
+ { "role": "B", "dtype": "T", "description": "Divisor tensor." }
9
+ ],
10
+ "outputs": [
11
+ {
12
+ "role": "C",
13
+ "dtype": "T",
14
+ "rank": "max(ranks.A, ranks.B)",
15
+ "description": "Remainder tensor; same shape as the broadcast result of A and B.",
16
+ "shape": "broadcastShape(shapes.A, shapes.B)"
17
+ }
18
+ ],
19
+ "attributes": { "fmod": 0 },
20
+ "attributeDescriptions": {
21
+ "fmod": "Controls the modulo mode: `0` (default) uses Python-style integer mod (sign of divisor); `1` uses C-style `fmod` (sign of dividend, floating-point types only)."
22
+ },
23
+ "attributeConstraints": { "fmod": { "values": [0, 1] } },
24
+ "typeConstraints": { "T": ["float32", "float16", "int32", "uint32", "int16", "int8", "uint8"] },
25
+ "args": {
26
+ "a": { "kind": "tensor", "semantic": "A", "role": "input" },
27
+ "b": { "kind": "tensor", "semantic": "B", "role": "input" },
28
+ "c": { "kind": "tensor", "semantic": "C", "role": "output" }
29
+ },
30
+ "tunables": { "WORKGROUP_SIZE": 256 },
31
+ "derive": { "floatFmodContract": "attrs.fmod != 0 or not (dtypes.T == \"f32\" or dtypes.T == \"f16\")" },
32
+ "constants": {
33
+ "scalar": "dtypes.T",
34
+ "usesF16": "dtypes.T == \"f16\"",
35
+ "isU32": "dtypes.T == \"u32\"",
36
+ "isI32": "dtypes.T == \"i32\"",
37
+ "fmod": "attrs.fmod != 0"
38
+ },
39
+ "variants": [
40
+ {
41
+ "id": "last_axis_broadcast_vec4",
42
+ "priority": 25,
43
+ "when": ["ranks.C >= 1", "sameShape(shapes.A, shapes.C)", "ranks.B == 1", "dim(shapes.B, 0) == dim(shapes.C, ranks.C - 1)", "numel(shapes.C) > 0", "numel(shapes.C) % 4 == 0", "f16Ok(dtypes.T)", "floatFmodContract"],
44
+ "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
45
+ "passes": [
46
+ {
47
+ "id": "main",
48
+ "name": "Mod.lastAxisBroadcastVec4",
49
+ "source": { "shader": "mod-vec4.wgsl.jinja", "inputs": { "perAxisB": true, "axisDim": "dim(shapes.B, 0)" } },
50
+ "bindings": [
51
+ {
52
+ "name": "a",
53
+ "arg": "a",
54
+ "semantic": "A",
55
+ "buffer": { "type": "read-only-storage" },
56
+ "elementType": "$vectorScalar"
57
+ },
58
+ {
59
+ "name": "b",
60
+ "arg": "b",
61
+ "semantic": "B",
62
+ "buffer": { "type": "read-only-storage" },
63
+ "elementType": "$scalar"
64
+ },
65
+ {
66
+ "name": "c",
67
+ "arg": "c",
68
+ "semantic": "C",
69
+ "buffer": { "type": "storage" },
70
+ "elementType": "$vectorScalar"
71
+ },
72
+ {
73
+ "name": "params",
74
+ "semantic": "kernel.params",
75
+ "buffer": { "type": "uniform" },
76
+ "struct": {
77
+ "name": "Params",
78
+ "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.C) / 4" }]
79
+ }
80
+ }
81
+ ],
82
+ "dispatch": { "threads": "numel(shapes.C) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
83
+ }
84
+ ]
85
+ },
86
+ {
87
+ "id": "scalar_b_vec4",
88
+ "priority": 30,
89
+ "when": ["sameShape(shapes.A, shapes.C)", "numel(shapes.B) == 1", "numel(shapes.C) > 0", "numel(shapes.C) % 4 == 0", "f16Ok(dtypes.T)", "floatFmodContract"],
90
+ "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
91
+ "passes": [
92
+ {
93
+ "id": "main",
94
+ "name": "Mod.scalarBVec4",
95
+ "source": { "shader": "mod-vec4.wgsl.jinja", "inputs": { "scalarB": true } },
96
+ "bindings": [
97
+ {
98
+ "name": "a",
99
+ "arg": "a",
100
+ "semantic": "A",
101
+ "buffer": { "type": "read-only-storage" },
102
+ "elementType": "$vectorScalar"
103
+ },
104
+ {
105
+ "name": "b",
106
+ "arg": "b",
107
+ "semantic": "B",
108
+ "buffer": { "type": "read-only-storage" },
109
+ "elementType": "$scalar",
110
+ "length": 1
111
+ },
112
+ {
113
+ "name": "c",
114
+ "arg": "c",
115
+ "semantic": "C",
116
+ "buffer": { "type": "storage" },
117
+ "elementType": "$vectorScalar"
118
+ },
119
+ {
120
+ "name": "params",
121
+ "semantic": "kernel.params",
122
+ "buffer": { "type": "uniform" },
123
+ "struct": {
124
+ "name": "Params",
125
+ "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.C) / 4" }]
126
+ }
127
+ }
128
+ ],
129
+ "dispatch": { "threads": "numel(shapes.C) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
130
+ }
131
+ ]
132
+ },
133
+ {
134
+ "id": "same_shape_vec4",
135
+ "priority": 26,
136
+ "when": ["sameShape(shapes.A, shapes.C)", "sameShape(shapes.B, shapes.C)", "numel(shapes.C) > 0", "numel(shapes.C) % 4 == 0", "f16Ok(dtypes.T)", "floatFmodContract"],
137
+ "constants": { "vectorScalar": "\"vec4<\" ~ dtypes.T ~ \">\"" },
138
+ "passes": [
139
+ {
140
+ "id": "main",
141
+ "name": "Mod.vec4",
142
+ "source": { "shader": "mod-vec4.wgsl.jinja", "inputs": {} },
143
+ "bindings": [
144
+ {
145
+ "name": "a",
146
+ "arg": "a",
147
+ "semantic": "A",
148
+ "buffer": { "type": "read-only-storage" },
149
+ "elementType": "$vectorScalar"
150
+ },
151
+ {
152
+ "name": "b",
153
+ "arg": "b",
154
+ "semantic": "B",
155
+ "buffer": { "type": "read-only-storage" },
156
+ "elementType": "$vectorScalar"
157
+ },
158
+ {
159
+ "name": "c",
160
+ "arg": "c",
161
+ "semantic": "C",
162
+ "buffer": { "type": "storage" },
163
+ "elementType": "$vectorScalar"
164
+ },
165
+ {
166
+ "name": "params",
167
+ "semantic": "kernel.params",
168
+ "buffer": { "type": "uniform" },
169
+ "struct": {
170
+ "name": "Params",
171
+ "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.C) / 4" }]
172
+ }
173
+ }
174
+ ],
175
+ "dispatch": { "threads": "numel(shapes.C) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
176
+ }
177
+ ]
178
+ },
179
+ {
180
+ "id": "broadcast",
181
+ "when": ["ranks.A >= 0", "ranks.B >= 0", "ranks.C >= 0", "ranks.A <= ranks.C", "ranks.B <= ranks.C", "f16Ok(dtypes.T)", "floatFmodContract"],
182
+ "passes": [
183
+ {
184
+ "id": "main",
185
+ "name": "Mod",
186
+ "source": {
187
+ "shader": "mod.wgsl.jinja",
188
+ "inputs": {
189
+ "aShape": "shapes.A",
190
+ "bShape": "shapes.B",
191
+ "cShape": "shapes.C",
192
+ "aRank": "ranks.A",
193
+ "bRank": "ranks.B",
194
+ "cRank": "ranks.C"
195
+ }
196
+ },
197
+ "bindings": [
198
+ {
199
+ "name": "a",
200
+ "arg": "a",
201
+ "semantic": "A",
202
+ "buffer": { "type": "read-only-storage" },
203
+ "elementType": "$scalar"
204
+ },
205
+ {
206
+ "name": "b",
207
+ "arg": "b",
208
+ "semantic": "B",
209
+ "buffer": { "type": "read-only-storage" },
210
+ "elementType": "$scalar"
211
+ },
212
+ { "name": "c", "arg": "c", "semantic": "C", "buffer": { "type": "storage" }, "elementType": "$scalar" },
213
+ {
214
+ "name": "params",
215
+ "semantic": "kernel.params",
216
+ "buffer": { "type": "uniform" },
217
+ "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.C)" }] }
218
+ }
219
+ ],
220
+ "dispatch": { "threads": "numel(shapes.C)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
221
+ }
222
+ ]
223
+ }
224
+ ]
225
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.Mod",
3
+ "id": "_ai_onnx_mod_webgpu_4a7f8df",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "QdfVC09wyipoUAu6hPHgQsOE7mtlr2T9OZJffvCh8YQ=",
11
+ "manifest.json": "EWrTl0E4Q3pRgojKEehDCETQSaGUYd6LvDy+6DtWFCI=",
12
+ "mod-vec4.wgsl.jinja": "OoOWti3TFUtnneaDtonReshC71gzRLiUQOOK4IZ9P7w=",
13
+ "mod.wgsl.jinja": "R3tnz/Y9NdD2Ut/jujKAUl2H2Y2CYSu9LpKXdUYJsz4=",
14
+ "test.json": "3XigfrMBzVhWpcZHr5WWptcT2N637Wb7iEjzEValb4A="
15
+ }
16
+ },
17
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
18
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.Mod" }
19
+ }
build/webgpu/mod-vec4.wgsl.jinja ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ {% if usesF16 %}
35
+ enable f16;
36
+ {% endif %}
37
+ {{ env.wgsl.resourceDeclarations }}
38
+
39
+ {% if not isU32 and not isI32 %}
40
+ // Truncated floating remainder (C fmod / sign of dividend). The naive
41
+ // `a - b*trunc(a/b)` loses all precision once |a| is large enough that the f32
42
+ // product `b*trunc(a/b)` rounds back to `a` (e.g. fmod(1e20, 3) cancels to 0).
43
+ // This uses an exact Sterbenz scaled-subtraction reduction: each
44
+ // `x - scaled` is exact because `scaled <= x < 2*scaled`, and scaling by powers
45
+ // of two is exact, so the final remainder carries no rounding error.
46
+ fn fmod_trunc(a: f32, b: f32) -> f32 {
47
+ let aa = abs(a);
48
+ let ab = abs(b);
49
+ // b == 0, or a non-finite: defer to the hardware formula for IEEE NaN/Inf.
50
+ if (!(ab > 0.0) || bitcast<u32>(aa) >= 0x7f800000u) {
51
+ return a - b * trunc(a / b);
52
+ }
53
+ if (aa < ab) {
54
+ return a;
55
+ }
56
+ var scaled = ab;
57
+ loop {
58
+ let nxt = scaled + scaled;
59
+ if (nxt > aa) { break; }
60
+ scaled = nxt;
61
+ }
62
+ var x = aa;
63
+ loop {
64
+ if (x >= scaled) { x = x - scaled; }
65
+ if (scaled == ab) { break; }
66
+ scaled = scaled * 0.5;
67
+ }
68
+ return select(x, -x, a < 0.0);
69
+ }
70
+ {% endif %}
71
+
72
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
73
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
74
+ {{ flat_index_2d(note="vec4-limit") }}
75
+ {% if source.perAxisB is defined and source.perAxisB %}
76
+ // B broadcasts over the innermost axis. A/C stay 128-bit packed even when
77
+ // an odd row width makes vec4 groups cross a row boundary; derive each
78
+ // lane's wrapped B coordinate from the flat scalar position.
79
+ let b0 = (i * 4u) % {{ source.axisDim }}u;
80
+ let b1 = (b0 + 1u) % {{ source.axisDim }}u;
81
+ let b2 = (b0 + 2u) % {{ source.axisDim }}u;
82
+ let b3 = (b0 + 3u) % {{ source.axisDim }}u;
83
+ {% endif %}
84
+ {% if isU32 %}
85
+ c[i] = a[i] % {% if source.scalarB is defined and source.scalarB %}vec4<u32>(b[0]){% elif source.perAxisB is defined and source.perAxisB %}vec4<u32>(b[b0], b[b1], b[b2], b[b3]){% else %}b[i]{% endif %};
86
+ {% elif isI32 %}
87
+ let av = a[i];
88
+ let bv = {% if source.scalarB is defined and source.scalarB %}vec4<i32>(b[0]){% elif source.perAxisB is defined and source.perAxisB %}vec4<i32>(b[b0], b[b1], b[b2], b[b3]){% else %}b[i]{% endif %};
89
+ let rt = av % bv;
90
+ {% if fmod %}
91
+ c[i] = rt;
92
+ {% else %}
93
+ c[i] = select(rt, rt + bv, (rt != vec4<i32>(0)) & ((rt < vec4<i32>(0)) != (bv < vec4<i32>(0))));
94
+ {% endif %}
95
+ {% else %}
96
+ let av = vec4<f32>(a[i]);
97
+ {% if source.perAxisB is defined and source.perAxisB %}
98
+ let bv = vec4<f32>(f32(b[b0]), f32(b[b1]), f32(b[b2]), f32(b[b3]));
99
+ {% else %}
100
+ let bv = {% if source.scalarB is defined and source.scalarB %}vec4<f32>(f32(b[0])){% else %}vec4<f32>(b[i]){% endif %};
101
+ {% endif %}
102
+ let rt = vec4<f32>(
103
+ fmod_trunc(av.x, bv.x),
104
+ fmod_trunc(av.y, bv.y),
105
+ fmod_trunc(av.z, bv.z),
106
+ fmod_trunc(av.w, bv.w)
107
+ );
108
+ c[i] = vec4<{{ scalar }}>(rt);
109
+ {% endif %}
110
+ }
build/webgpu/mod.wgsl.jinja ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ {% macro offset_fn(fn_name, opShape, opRank, op_same, op_numel, outShape, outRank, out_numel) %}
35
+ fn {{ fn_name }}({% if out_numel != 0 and op_numel != 1 %}out_index: u32{% endif %}) -> u32 {
36
+ {% if out_numel == 0 %}
37
+ return 0u;
38
+ {% elif op_numel == 1 %}
39
+ return 0u;
40
+ {% elif op_same %}
41
+ return out_index;
42
+ {% else %}
43
+ var offset = 0u;
44
+ {% for axis in range(outRank) %}
45
+ {% set op_axis = axis - (outRank - opRank) %}
46
+ {% if op_axis >= 0 and opShape[op_axis] != 1 %}
47
+ {% set c_stride = namespace(value=1) %}
48
+ {% for j in range(axis + 1, outRank) %}
49
+ {% set c_stride.value = c_stride.value * outShape[j] %}
50
+ {% endfor %}
51
+ {% set op_stride = namespace(value=1) %}
52
+ {% for j in range(op_axis + 1, opRank) %}
53
+ {% set op_stride.value = op_stride.value * opShape[j] %}
54
+ {% endfor %}
55
+ {% if c_stride.value == 1 %}
56
+ let coord{{ axis }} = out_index % {{ outShape[axis] }}u;
57
+ {% else %}
58
+ let coord{{ axis }} = (out_index / {{ c_stride.value }}u) % {{ outShape[axis] }}u;
59
+ {% endif %}
60
+ {% if op_stride.value == 1 %}
61
+ offset = offset + coord{{ axis }};
62
+ {% else %}
63
+ offset = offset + coord{{ axis }} * {{ op_stride.value }}u;
64
+ {% endif %}
65
+ {% endif %}
66
+ {% endfor %}
67
+ return offset;
68
+ {% endif %}
69
+ }
70
+ {%- endmacro %}{% macro broadcast_offset_call(fn_name, opShape, outShape, out_index) %}
71
+ {% set op_numel = namespace(value=1) %}
72
+ {% for d in opShape %}{% set op_numel.value = op_numel.value * d %}{% endfor %}
73
+ {% set out_numel = namespace(value=1) %}
74
+ {% for d in outShape %}{% set out_numel.value = out_numel.value * d %}{% endfor %}
75
+ {{ fn_name }}({% if out_numel.value != 0 and op_numel.value != 1 %}{{ out_index }}{% endif %})
76
+ {%- endmacro %}{% macro broadcast_offset_fn(fn_name, opShape, opRank, outShape, outRank) %}
77
+ {% set op_numel = namespace(value=1) %}
78
+ {% for d in opShape %}
79
+ {% set op_numel.value = op_numel.value * d %}
80
+ {% endfor %}
81
+ {% set out_numel = namespace(value=1) %}
82
+ {% for d in outShape %}
83
+ {% set out_numel.value = out_numel.value * d %}
84
+ {% endfor %}
85
+ {% set op_same = namespace(value=(opRank == outRank)) %}
86
+ {% if op_same.value %}
87
+ {% for axis in range(outRank) %}
88
+ {% if opShape[axis] != outShape[axis] %}
89
+ {% set op_same.value = false %}
90
+ {% endif %}
91
+ {% endfor %}
92
+ {% endif %}
93
+ {{ offset_fn(fn_name, opShape, opRank, op_same.value, op_numel.value, outShape, outRank, out_numel.value) }}
94
+ {%- endmacro %}{% macro binary_broadcast_offsets() %}
95
+ {{ broadcast_offset_fn("a_offset", source.aShape, source.aRank, source.cShape, source.cRank) }}
96
+
97
+ {{ broadcast_offset_fn("b_offset", source.bShape, source.bRank, source.cShape, source.cRank) }}
98
+ {%- endmacro %}
99
+
100
+ {% if usesF16 %}
101
+ enable f16;
102
+ {% endif %}
103
+ {{ env.wgsl.resourceDeclarations }}
104
+
105
+
106
+ {{ binary_broadcast_offsets() }}
107
+
108
+ {% if not isU32 and not isI32 %}
109
+ // Truncated floating remainder (C fmod / sign of dividend). The naive
110
+ // `a - b*trunc(a/b)` loses all precision once |a| is large enough that the f32
111
+ // product `b*trunc(a/b)` rounds back to `a` (e.g. fmod(1e20, 3) cancels to 0).
112
+ // This uses an exact Sterbenz scaled-subtraction reduction: each
113
+ // `x - scaled` is exact because `scaled <= x < 2*scaled`, and scaling by powers
114
+ // of two is exact, so the final remainder carries no rounding error.
115
+ fn fmod_trunc(a: f32, b: f32) -> f32 {
116
+ let aa = abs(a);
117
+ let ab = abs(b);
118
+ // b == 0, or a non-finite: defer to the hardware formula for IEEE NaN/Inf.
119
+ if (!(ab > 0.0) || bitcast<u32>(aa) >= 0x7f800000u) {
120
+ return a - b * trunc(a / b);
121
+ }
122
+ if (aa < ab) {
123
+ return a;
124
+ }
125
+ var scaled = ab;
126
+ loop {
127
+ let nxt = scaled + scaled;
128
+ if (nxt > aa) { break; }
129
+ scaled = nxt;
130
+ }
131
+ var x = aa;
132
+ loop {
133
+ if (x >= scaled) { x = x - scaled; }
134
+ if (scaled == ab) { break; }
135
+ scaled = scaled * 0.5;
136
+ }
137
+ return select(x, -x, a < 0.0);
138
+ }
139
+ {% endif %}
140
+
141
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
142
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
143
+ {{ flat_index_2d() }}
144
+ {% if isU32 %}
145
+ c[i] = a[{{ broadcast_offset_call("a_offset", source.aShape, source.cShape, "i") }}] % b[{{ broadcast_offset_call("b_offset", source.bShape, source.cShape, "i") }}];
146
+ {% elif isI32 %}
147
+ let av = a[{{ broadcast_offset_call("a_offset", source.aShape, source.cShape, "i") }}];
148
+ let bv = b[{{ broadcast_offset_call("b_offset", source.bShape, source.cShape, "i") }}];
149
+ let rt = av % bv;
150
+ {% if fmod %}
151
+ c[i] = rt;
152
+ {% else %}
153
+ c[i] = select(rt, rt + bv, rt != 0 && (rt < 0) != (bv < 0));
154
+ {% endif %}
155
+ {% else %}
156
+ let av = f32(a[{{ broadcast_offset_call("a_offset", source.aShape, source.cShape, "i") }}]);
157
+ let bv = f32(b[{{ broadcast_offset_call("b_offset", source.bShape, source.cShape, "i") }}]);
158
+ let rt = fmod_trunc(av, bv);
159
+ c[i] = {{ scalar }}(rt);
160
+ {% endif %}
161
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,773 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.Mod",
3
+ "fixtureArrays": {
4
+ "ort_int32_rank3_scalar_divisor_broadcast_input_a": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
5
+ },
6
+ "cases": [
7
+ {
8
+ "name": "int16_floor_mod_boundaries",
9
+ "inputs": {
10
+ "a": { "dtype": "int16", "shape": [4], "data": { "kind": "values", "values": [-32768, -32768, 32767, -32767] } },
11
+ "b": { "dtype": "int16", "shape": [4], "data": { "kind": "values", "values": [-1, 32767, -32768, -32768] } }
12
+ },
13
+ "outputs": {
14
+ "c": {
15
+ "dtype": "int16",
16
+ "shape": [4],
17
+ "tolerance": 0,
18
+ "data": { "kind": "values", "values": [0, 32766, -1, -32767] }
19
+ }
20
+ }
21
+ },
22
+ {
23
+ "name": "dispatch_cliff_broadcast",
24
+ "attrs": { "fmod": 1 },
25
+ "inputs": {
26
+ "a": {
27
+ "dtype": "float32",
28
+ "shape": [16776961],
29
+ "data": { "kind": "cycle", "values": [5.5, 6.5, 7.5, 8.5, 9.5] }
30
+ },
31
+ "b": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [3.0] } }
32
+ },
33
+ "outputs": { "c": { "dtype": "float32", "shape": [16776961], "tolerance": 0.0001 } }
34
+ },
35
+ {
36
+ "name": "u32_broadcast",
37
+ "inputs": {
38
+ "a": { "dtype": "uint32", "shape": [2, 3], "data": { "kind": "values", "values": [5, 6, 7, 8, 9, 10] } },
39
+ "b": { "dtype": "uint32", "shape": [3], "data": { "kind": "values", "values": [2, 4, 5] } }
40
+ },
41
+ "outputs": { "c": { "dtype": "uint32", "shape": [2, 3] } }
42
+ },
43
+ {
44
+ "name": "f32_broadcast",
45
+ "attrs": { "fmod": 1 },
46
+ "inputs": {
47
+ "a": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [5.5, 6.5, 7.5, 8.5] } },
48
+ "b": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [2.0] } }
49
+ },
50
+ "outputs": { "c": { "dtype": "float32", "shape": [4] } }
51
+ },
52
+ {
53
+ "name": "f32_fmod_subnormal_dividend_scalar_b_vec4",
54
+ "provenance": {
55
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
56
+ "test": "ModOpTest.Fmod_float_mixed_sign",
57
+ "notes": "For |x| < |y|, fmod(x, y) returns x; finite subnormal dividends must not be flushed to zero."
58
+ },
59
+ "attrs": { "fmod": 1 },
60
+ "inputs": {
61
+ "a": {
62
+ "dtype": "float32",
63
+ "shape": [4],
64
+ "data": { "kind": "values", "values": [1e-40, -1e-40, 4e-39, -4e-39] }
65
+ },
66
+ "b": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [2.0] } }
67
+ },
68
+ "outputs": {
69
+ "c": {
70
+ "dtype": "float32",
71
+ "shape": [4],
72
+ "tolerance": 0,
73
+ "data": { "kind": "values", "values": [1e-40, -1e-40, 4e-39, -4e-39] }
74
+ }
75
+ }
76
+ },
77
+ {
78
+ "name": "f32_fmod_subnormal_dividend_scalar_path",
79
+ "provenance": {
80
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
81
+ "test": "ModOpTest.Fmod_float_mixed_sign",
82
+ "notes": "Scalar broadcast companion: subnormal dividends smaller than the divisor are valid nonzero remainders."
83
+ },
84
+ "attrs": { "fmod": 1 },
85
+ "inputs": {
86
+ "a": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [1e-40, -1e-40, 4e-39] } },
87
+ "b": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [2.0] } }
88
+ },
89
+ "outputs": {
90
+ "c": {
91
+ "dtype": "float32",
92
+ "shape": [3],
93
+ "tolerance": 0,
94
+ "data": { "kind": "values", "values": [1e-40, -1e-40, 4e-39] }
95
+ }
96
+ }
97
+ },
98
+ {
99
+ "name": "f32_fmod_negative_operands",
100
+ "attrs": { "fmod": 1 },
101
+ "inputs": {
102
+ "a": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [-5.5, 5.5, -5.5, 5.5] } },
103
+ "b": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [2.0, 2.0, -2.0, -2.0] } }
104
+ },
105
+ "outputs": { "c": { "dtype": "float32", "shape": [4], "tolerance": 0.000001 } }
106
+ },
107
+ {
108
+ "name": "rank0_scalar_fmod_negative_dividend",
109
+ "attrs": { "fmod": 1 },
110
+ "inputs": {
111
+ "a": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [-5.5] } },
112
+ "b": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [2.0] } }
113
+ },
114
+ "outputs": { "c": { "dtype": "float32", "shape": [], "tolerance": 0.000001 } }
115
+ },
116
+ {
117
+ "name": "f32_fmod_large_dividend_cancellation",
118
+ "attrs": { "fmod": 1 },
119
+ "inputs": {
120
+ "a": {
121
+ "dtype": "float32",
122
+ "shape": [6],
123
+ "data": {
124
+ "kind": "values",
125
+ "values": [100000000000000000000.0, 10000000000000.0, -100000000000000000000.0, 100000000000000000000.0, 2500000000.0, 123456789.0]
126
+ }
127
+ },
128
+ "b": {
129
+ "dtype": "float32",
130
+ "shape": [6],
131
+ "data": { "kind": "values", "values": [3.0, 7.0, 3.0, 7.0, 100.0, 1000.0] }
132
+ }
133
+ },
134
+ "outputs": { "c": { "dtype": "float32", "shape": [6], "tolerance": 0.001 } }
135
+ },
136
+ {
137
+ "name": "ort_f32_fmod_mixed_sign",
138
+ "provenance": {
139
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
140
+ "test": "ModOpTest.Fmod_float_mixed_sign"
141
+ },
142
+ "attrs": { "fmod": 1 },
143
+ "inputs": {
144
+ "a": {
145
+ "dtype": "float32",
146
+ "shape": [6],
147
+ "data": { "kind": "values", "values": [-4.3, 7.2, 5.0, 4.3, -7.2, 8.0] }
148
+ },
149
+ "b": {
150
+ "dtype": "float32",
151
+ "shape": [6],
152
+ "data": { "kind": "values", "values": [2.1, -3.4, 8.0, -2.1, 3.4, 5.0] }
153
+ }
154
+ },
155
+ "outputs": { "c": { "dtype": "float32", "shape": [6], "tolerance": 0.000001 } }
156
+ },
157
+ {
158
+ "name": "ort_f16_fmod_mixed_sign",
159
+ "provenance": {
160
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
161
+ "test": "ModOpTest.Fmod_float16_mixed_sign"
162
+ },
163
+ "attrs": { "fmod": 1 },
164
+ "inputs": {
165
+ "a": {
166
+ "dtype": "float16",
167
+ "shape": [6],
168
+ "data": { "kind": "values", "values": [-4.3, 7.2, 5.0, 4.3, -7.2, 8.0] }
169
+ },
170
+ "b": {
171
+ "dtype": "float16",
172
+ "shape": [6],
173
+ "data": { "kind": "values", "values": [2.1, -3.4, 8.0, -2.1, 3.4, 5.0] }
174
+ }
175
+ },
176
+ "outputs": { "c": { "dtype": "float16", "shape": [6], "tolerance": 0.002 } }
177
+ },
178
+ {
179
+ "name": "ort_uint32_mod",
180
+ "provenance": {
181
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
182
+ "test": "ModOpTest.UInt32_mod"
183
+ },
184
+ "inputs": {
185
+ "a": { "dtype": "uint32", "shape": [6], "data": { "kind": "values", "values": [4, 7, 5, 4, 7, 8] } },
186
+ "b": { "dtype": "uint32", "shape": [6], "data": { "kind": "values", "values": [2, 3, 8, 2, 3, 5] } }
187
+ },
188
+ "outputs": { "c": { "dtype": "uint32", "shape": [6], "tolerance": 0 } }
189
+ },
190
+ {
191
+ "name": "ort_int32_mixed_sign_floor_mod",
192
+ "provenance": {
193
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
194
+ "test": "ModOpTest.Int32_mixed_sign"
195
+ },
196
+ "inputs": {
197
+ "a": { "dtype": "int32", "shape": [6], "data": { "kind": "values", "values": [-4, 7, 5, 4, -7, 8] } },
198
+ "b": { "dtype": "int32", "shape": [6], "data": { "kind": "values", "values": [2, -3, 8, -2, 3, 5] } }
199
+ },
200
+ "outputs": { "c": { "dtype": "int32", "shape": [6], "tolerance": 0 } }
201
+ },
202
+ {
203
+ "name": "ort_int32_mixed_sign_fmod",
204
+ "provenance": {
205
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
206
+ "test": "ModOpTest.Int32_mixed_sign_fmod"
207
+ },
208
+ "attrs": { "fmod": 1 },
209
+ "inputs": {
210
+ "a": { "dtype": "int32", "shape": [6], "data": { "kind": "values", "values": [-4, 7, 5, 4, -7, 8] } },
211
+ "b": { "dtype": "int32", "shape": [6], "data": { "kind": "values", "values": [2, -3, 8, -2, 3, 5] } }
212
+ },
213
+ "outputs": { "c": { "dtype": "int32", "shape": [6], "tolerance": 0 } }
214
+ },
215
+ {
216
+ "name": "ort_int32_rank3_scalar_divisor_broadcast",
217
+ "provenance": {
218
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
219
+ "test": "ModOpTest.Int32_mod_bcast"
220
+ },
221
+ "inputs": {
222
+ "a": {
223
+ "dtype": "int32",
224
+ "shape": [3, 2, 5],
225
+ "data": {
226
+ "kind": "values",
227
+ "values": { "$ref": "#/fixtureArrays/ort_int32_rank3_scalar_divisor_broadcast_input_a" }
228
+ }
229
+ },
230
+ "b": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [7] } }
231
+ },
232
+ "outputs": {
233
+ "c": {
234
+ "dtype": "int32",
235
+ "shape": [3, 2, 5],
236
+ "tolerance": 0,
237
+ "data": {
238
+ "kind": "values",
239
+ "values": [0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1]
240
+ }
241
+ }
242
+ }
243
+ },
244
+ {
245
+ "name": "ort_int8_mixed_sign_floor_mod",
246
+ "provenance": {
247
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
248
+ "test": "ModOpTest.Int8_mixed_sign"
249
+ },
250
+ "inputs": {
251
+ "a": { "dtype": "int8", "shape": [6], "data": { "kind": "values", "values": [-4, 7, 5, 4, -7, 8] } },
252
+ "b": { "dtype": "int8", "shape": [6], "data": { "kind": "values", "values": [2, -3, 8, -2, 3, 5] } }
253
+ },
254
+ "outputs": {
255
+ "c": {
256
+ "dtype": "int8",
257
+ "shape": [6],
258
+ "data": { "kind": "values", "values": [0, -2, 5, 0, 2, 3] },
259
+ "tolerance": 0
260
+ }
261
+ }
262
+ },
263
+ {
264
+ "name": "ort_int8_mixed_sign_fmod",
265
+ "provenance": {
266
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
267
+ "test": "ModOpTest.Int8_mixed_sign_fmod"
268
+ },
269
+ "attrs": { "fmod": 1 },
270
+ "inputs": {
271
+ "a": { "dtype": "int8", "shape": [6], "data": { "kind": "values", "values": [-4, 7, 5, 4, -7, 8] } },
272
+ "b": { "dtype": "int8", "shape": [6], "data": { "kind": "values", "values": [2, -3, 8, -2, 3, 5] } }
273
+ },
274
+ "outputs": {
275
+ "c": {
276
+ "dtype": "int8",
277
+ "shape": [6],
278
+ "data": { "kind": "values", "values": [0, 1, 5, 0, -1, 3] },
279
+ "tolerance": 0
280
+ }
281
+ }
282
+ },
283
+ {
284
+ "name": "ort_uint8_mod",
285
+ "provenance": {
286
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
287
+ "test": "ModOpTest.UInt8_mod"
288
+ },
289
+ "inputs": {
290
+ "a": { "dtype": "uint8", "shape": [6], "data": { "kind": "values", "values": [4, 7, 5, 4, 7, 8] } },
291
+ "b": { "dtype": "uint8", "shape": [6], "data": { "kind": "values", "values": [2, 3, 8, 2, 3, 5] } }
292
+ },
293
+ "outputs": {
294
+ "c": {
295
+ "dtype": "uint8",
296
+ "shape": [6],
297
+ "data": { "kind": "values", "values": [0, 1, 5, 0, 1, 3] },
298
+ "tolerance": 0
299
+ }
300
+ }
301
+ },
302
+ {
303
+ "name": "ort_int8_mixed_sign_floor_mod_vec4",
304
+ "provenance": {
305
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
306
+ "test": "ModOpTest.Int8_mixed_sign",
307
+ "notes": "Extended to 8 elements to exercise the vectorized WebGPU path while preserving ORT's mixed-sign floor-mod semantics."
308
+ },
309
+ "inputs": {
310
+ "a": { "dtype": "int8", "shape": [8], "data": { "kind": "values", "values": [-4, 7, 5, 4, -7, 8, -9, 9] } },
311
+ "b": { "dtype": "int8", "shape": [8], "data": { "kind": "values", "values": [2, -3, 8, -2, 3, 5, 4, -4] } }
312
+ },
313
+ "outputs": {
314
+ "c": {
315
+ "dtype": "int8",
316
+ "shape": [8],
317
+ "data": { "kind": "values", "values": [0, -2, 5, 0, 2, 3, 3, -3] },
318
+ "tolerance": 0
319
+ }
320
+ }
321
+ },
322
+ {
323
+ "name": "ort_uint8_mod_vec4",
324
+ "provenance": {
325
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
326
+ "test": "ModOpTest.UInt8_mod",
327
+ "notes": "Extended to 8 elements to exercise the vectorized WebGPU path while preserving ORT's unsigned mod semantics."
328
+ },
329
+ "inputs": {
330
+ "a": { "dtype": "uint8", "shape": [8], "data": { "kind": "values", "values": [4, 7, 5, 4, 7, 8, 255, 128] } },
331
+ "b": { "dtype": "uint8", "shape": [8], "data": { "kind": "values", "values": [2, 3, 8, 2, 3, 5, 10, 127] } }
332
+ },
333
+ "outputs": {
334
+ "c": {
335
+ "dtype": "uint8",
336
+ "shape": [8],
337
+ "data": { "kind": "values", "values": [0, 1, 5, 0, 1, 3, 5, 1] },
338
+ "tolerance": 0
339
+ }
340
+ }
341
+ },
342
+ {
343
+ "name": "ort_int16_mixed_sign_floor_mod",
344
+ "provenance": {
345
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
346
+ "test": "ModOpTest.Int16_mixed_sign"
347
+ },
348
+ "inputs": {
349
+ "a": { "dtype": "int16", "shape": [6], "data": { "kind": "values", "values": [-4, 7, 5, 4, -7, 8] } },
350
+ "b": { "dtype": "int16", "shape": [6], "data": { "kind": "values", "values": [2, -3, 8, -2, 3, 5] } }
351
+ },
352
+ "outputs": { "c": { "dtype": "int16", "shape": [6], "tolerance": 0 } }
353
+ },
354
+ {
355
+ "name": "ort_int16_mixed_sign_fmod",
356
+ "provenance": {
357
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
358
+ "test": "ModOpTest.Int16_mixed_sign_fmod"
359
+ },
360
+ "attrs": { "fmod": 1 },
361
+ "inputs": {
362
+ "a": { "dtype": "int16", "shape": [6], "data": { "kind": "values", "values": [-4, 7, 5, 4, -7, 8] } },
363
+ "b": { "dtype": "int16", "shape": [6], "data": { "kind": "values", "values": [2, -3, 8, -2, 3, 5] } }
364
+ },
365
+ "outputs": { "c": { "dtype": "int16", "shape": [6], "tolerance": 0 } }
366
+ },
367
+ {
368
+ "name": "ort_int32_scalar_divisor_broadcast",
369
+ "provenance": {
370
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
371
+ "test": "ModOpTest.Int32_mod_bcast"
372
+ },
373
+ "inputs": {
374
+ "a": {
375
+ "dtype": "int32",
376
+ "shape": [3, 2, 5],
377
+ "data": {
378
+ "kind": "values",
379
+ "values": { "$ref": "#/fixtureArrays/ort_int32_rank3_scalar_divisor_broadcast_input_a" }
380
+ }
381
+ },
382
+ "b": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [7] } }
383
+ },
384
+ "outputs": { "c": { "dtype": "int32", "shape": [3, 2, 5], "tolerance": 0 } }
385
+ },
386
+ {
387
+ "name": "int32_large_values_no_float_cast",
388
+ "inputs": {
389
+ "a": {
390
+ "dtype": "int32",
391
+ "shape": [6],
392
+ "data": {
393
+ "kind": "values",
394
+ "values": [2147483647, 2000000001, 16777217, -2147483647, -2000000001, -16777217]
395
+ }
396
+ },
397
+ "b": { "dtype": "int32", "shape": [6], "data": { "kind": "values", "values": [3, 97, 3, 3, 97, 3] } }
398
+ },
399
+ "outputs": { "c": { "dtype": "int32", "shape": [6], "tolerance": 0 } }
400
+ },
401
+ {
402
+ "name": "u32_large_values_no_float_cast",
403
+ "inputs": {
404
+ "a": {
405
+ "dtype": "uint32",
406
+ "shape": [5],
407
+ "data": { "kind": "values", "values": [4294967295, 4294967294, 4000000001, 16777217, 16777216] }
408
+ },
409
+ "b": { "dtype": "uint32", "shape": [5], "data": { "kind": "values", "values": [97, 65535, 4000000000, 3, 5] } }
410
+ },
411
+ "outputs": { "c": { "dtype": "uint32", "shape": [5], "tolerance": 0 } }
412
+ },
413
+ {
414
+ "name": "zero_sized_broadcast_noop",
415
+ "inputs": {
416
+ "a": { "dtype": "uint32", "shape": [0, 3], "data": { "kind": "values", "values": [] } },
417
+ "b": { "dtype": "uint32", "shape": [1, 3], "data": { "kind": "values", "values": [2, 3, 5] } }
418
+ },
419
+ "outputs": { "c": { "dtype": "uint32", "shape": [0, 3], "tolerance": 0 } }
420
+ },
421
+ {
422
+ "name": "ort_dim_zero_broadcast_loop_span",
423
+ "provenance": {
424
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
425
+ "test": "MathOpTest.DimWithZeroHandling",
426
+ "notes": "Int32 projection of ORT's zero-dimension Mod BroadcastLoopSpan case."
427
+ },
428
+ "inputs": {
429
+ "a": { "dtype": "int32", "shape": [2, 2, 0], "data": { "kind": "values", "values": [] } },
430
+ "b": { "dtype": "int32", "shape": [2, 1], "data": { "kind": "values", "values": [1, 2] } }
431
+ },
432
+ "outputs": { "c": { "dtype": "int32", "shape": [2, 2, 0], "tolerance": 0 } }
433
+ },
434
+ {
435
+ "name": "onnx_backend_mod_broadcast",
436
+ "inputs": {
437
+ "a": {
438
+ "dtype": "int32",
439
+ "shape": [3, 2, 5],
440
+ "data": {
441
+ "kind": "values",
442
+ "values": { "$ref": "#/fixtureArrays/ort_int32_rank3_scalar_divisor_broadcast_input_a" }
443
+ }
444
+ },
445
+ "b": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [7] } }
446
+ },
447
+ "outputs": { "c": { "dtype": "int32", "shape": [3, 2, 5] } },
448
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_mod_broadcast" }
449
+ },
450
+ {
451
+ "name": "onnx_backend_mod_int64_fmod",
452
+ "attrs": { "fmod": 1 },
453
+ "inputs": {
454
+ "a": { "dtype": "int32", "shape": [6], "data": { "kind": "values", "values": [-4, 7, 5, 4, -7, 8] } },
455
+ "b": { "dtype": "int32", "shape": [6], "data": { "kind": "values", "values": [2, -3, 8, -2, 3, 5] } }
456
+ },
457
+ "outputs": { "c": { "dtype": "int32", "shape": [6] } },
458
+ "provenance": {
459
+ "source": "cmake/external/onnx/onnx/backend/test/data/node/test_mod_int64_fmod",
460
+ "notes": "ONNX int64 tensors use framework int32/uint32 slots where representable."
461
+ }
462
+ },
463
+ {
464
+ "name": "onnx_backend_mod_mixed_sign_float16",
465
+ "attrs": { "fmod": 1 },
466
+ "inputs": {
467
+ "a": {
468
+ "dtype": "float16",
469
+ "shape": [6],
470
+ "data": { "kind": "values", "values": [-4.30078125, 7.19921875, 5.0, 4.30078125, -7.19921875, 8.0] }
471
+ },
472
+ "b": {
473
+ "dtype": "float16",
474
+ "shape": [6],
475
+ "data": { "kind": "values", "values": [2.099609375, -3.400390625, 8.0, -2.099609375, 3.400390625, 5.0] }
476
+ }
477
+ },
478
+ "outputs": { "c": { "dtype": "float16", "shape": [6] } },
479
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_mod_mixed_sign_float16" }
480
+ },
481
+ {
482
+ "name": "onnx_backend_mod_mixed_sign_float32",
483
+ "attrs": { "fmod": 1 },
484
+ "inputs": {
485
+ "a": {
486
+ "dtype": "float32",
487
+ "shape": [6],
488
+ "data": {
489
+ "kind": "values",
490
+ "values": [-4.300000190734863, 7.199999809265137, 5.0, 4.300000190734863, -7.199999809265137, 8.0]
491
+ }
492
+ },
493
+ "b": {
494
+ "dtype": "float32",
495
+ "shape": [6],
496
+ "data": {
497
+ "kind": "values",
498
+ "values": [2.0999999046325684, -3.4000000953674316, 8.0, -2.0999999046325684, 3.4000000953674316, 5.0]
499
+ }
500
+ }
501
+ },
502
+ "outputs": { "c": { "dtype": "float32", "shape": [6] } },
503
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_mod_mixed_sign_float32" }
504
+ },
505
+ {
506
+ "name": "onnx_backend_mod_mixed_sign_int32",
507
+ "inputs": {
508
+ "a": { "dtype": "int32", "shape": [6], "data": { "kind": "values", "values": [-4, 7, 5, 4, -7, 8] } },
509
+ "b": { "dtype": "int32", "shape": [6], "data": { "kind": "values", "values": [2, -3, 8, -2, 3, 5] } }
510
+ },
511
+ "outputs": { "c": { "dtype": "int32", "shape": [6] } },
512
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_mod_mixed_sign_int32" }
513
+ },
514
+ {
515
+ "name": "onnx_backend_mod_mixed_sign_int8",
516
+ "inputs": {
517
+ "a": { "dtype": "int8", "shape": [6], "data": { "kind": "values", "values": [-4, 7, 5, 4, -7, 8] } },
518
+ "b": { "dtype": "int8", "shape": [6], "data": { "kind": "values", "values": [2, -3, 8, -2, 3, 5] } }
519
+ },
520
+ "outputs": { "c": { "dtype": "int8", "shape": [6] } },
521
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_mod_mixed_sign_int8" }
522
+ },
523
+ {
524
+ "name": "onnx_backend_mod_uint32",
525
+ "inputs": {
526
+ "a": { "dtype": "uint32", "shape": [3], "data": { "kind": "values", "values": [4, 7, 5] } },
527
+ "b": { "dtype": "uint32", "shape": [3], "data": { "kind": "values", "values": [2, 3, 8] } }
528
+ },
529
+ "outputs": { "c": { "dtype": "uint32", "shape": [3] } },
530
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_mod_uint32" }
531
+ },
532
+ {
533
+ "name": "onnx_backend_mod_uint8",
534
+ "inputs": {
535
+ "a": { "dtype": "uint8", "shape": [3], "data": { "kind": "values", "values": [4, 7, 5] } },
536
+ "b": { "dtype": "uint8", "shape": [3], "data": { "kind": "values", "values": [2, 3, 8] } }
537
+ },
538
+ "outputs": { "c": { "dtype": "uint8", "shape": [3] } },
539
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_mod_uint8" }
540
+ },
541
+ {
542
+ "name": "vec4_u32_same_shape",
543
+ "inputs": {
544
+ "a": { "dtype": "uint32", "shape": [8], "data": { "kind": "values", "values": [4, 7, 5, 4, 7, 8, 9, 10] } },
545
+ "b": { "dtype": "uint32", "shape": [8], "data": { "kind": "values", "values": [2, 3, 8, 3, 4, 5, 7, 6] } }
546
+ },
547
+ "outputs": {
548
+ "c": {
549
+ "dtype": "uint32",
550
+ "shape": [8],
551
+ "data": { "kind": "values", "values": [0, 1, 5, 1, 3, 3, 2, 4] },
552
+ "tolerance": 0
553
+ }
554
+ }
555
+ },
556
+ {
557
+ "name": "i32_scalar_divisor_vec4_floor_mod",
558
+ "provenance": {
559
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
560
+ "test": "ModOpTest.Int32_mod_bcast",
561
+ "notes": "A vec4-aligned int32 tensor with a scalar negative divisor exercises scalar-divisor broadcasting and drives floor-mod correction on dividends on both sides of zero."
562
+ },
563
+ "inputs": {
564
+ "a": { "dtype": "int32", "shape": [8], "data": { "kind": "values", "values": [-9, 7, -7, 15, 0, -1, 22, -22] } },
565
+ "b": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [-7] } }
566
+ },
567
+ "outputs": {
568
+ "c": {
569
+ "dtype": "int32",
570
+ "shape": [8],
571
+ "tolerance": 0,
572
+ "data": { "kind": "values", "values": [-2, 0, 0, -6, 0, -1, -6, -1] }
573
+ }
574
+ }
575
+ },
576
+ {
577
+ "name": "i32_scalar_divisor_vec4_fmod",
578
+ "attrs": { "fmod": 1 },
579
+ "provenance": {
580
+ "source": "onnxruntime/test/providers/cpu/math/element_wise_ops_test.cc",
581
+ "test": "ModOpTest.Int32_mod_bcast",
582
+ "notes": "The fmod=1 half of the same route: truncated remainder, so the sign follows the dividend and the floor-mod adjust must NOT fire. Left unpinned so the reference is exercised on the arm its pinned sibling covers only in the kernel."
583
+ },
584
+ "inputs": {
585
+ "a": { "dtype": "int32", "shape": [8], "data": { "kind": "values", "values": [-9, 7, -7, 15, 0, -1, 22, -22] } },
586
+ "b": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [-7] } }
587
+ },
588
+ "outputs": { "c": { "dtype": "int32", "shape": [8], "tolerance": 0 } }
589
+ },
590
+ {
591
+ "name": "vec4_i32_floor_mod_mixed_sign",
592
+ "inputs": {
593
+ "a": { "dtype": "int32", "shape": [8], "data": { "kind": "values", "values": [-4, 7, 5, -7, 8, -9, 10, -3] } },
594
+ "b": { "dtype": "int32", "shape": [8], "data": { "kind": "values", "values": [3, -3, 3, 3, -5, 5, 7, -2] } }
595
+ },
596
+ "outputs": {
597
+ "c": {
598
+ "dtype": "int32",
599
+ "shape": [8],
600
+ "data": { "kind": "values", "values": [2, -2, 2, 2, -2, 1, 3, -1] },
601
+ "tolerance": 0
602
+ }
603
+ }
604
+ },
605
+ {
606
+ "name": "vec4_i32_fmod_mixed_sign",
607
+ "attrs": { "fmod": 1 },
608
+ "inputs": {
609
+ "a": { "dtype": "int32", "shape": [8], "data": { "kind": "values", "values": [-4, 7, 5, -7, 8, -9, 10, -3] } },
610
+ "b": { "dtype": "int32", "shape": [8], "data": { "kind": "values", "values": [3, -3, 3, 3, -5, 5, 7, -2] } }
611
+ },
612
+ "outputs": {
613
+ "c": {
614
+ "dtype": "int32",
615
+ "shape": [8],
616
+ "data": { "kind": "values", "values": [-1, 1, 2, -1, 3, -4, 3, -1] },
617
+ "tolerance": 0
618
+ }
619
+ }
620
+ },
621
+ {
622
+ "name": "vec4_f16_fmod_mixed_sign",
623
+ "attrs": { "fmod": 1 },
624
+ "inputs": {
625
+ "a": {
626
+ "dtype": "float16",
627
+ "shape": [8],
628
+ "data": { "kind": "values", "values": [5.5, -5.5, 7.25, -7.25, 9.5, -9.5, 3.75, -3.75] }
629
+ },
630
+ "b": {
631
+ "dtype": "float16",
632
+ "shape": [8],
633
+ "data": { "kind": "values", "values": [2.0, 2.0, 3.0, 3.0, 4.0, -4.0, 1.5, 1.5] }
634
+ }
635
+ },
636
+ "outputs": {
637
+ "c": {
638
+ "dtype": "float16",
639
+ "shape": [8],
640
+ "data": { "kind": "values", "values": [1.5, -1.5, 1.25, -1.25, 1.5, -1.5, 0.75, -0.75] },
641
+ "tolerance": 0.001
642
+ }
643
+ }
644
+ },
645
+ {
646
+ "name": "vec4_f32_fmod_large_dividend_cancellation",
647
+ "attrs": { "fmod": 1 },
648
+ "inputs": {
649
+ "a": {
650
+ "dtype": "float32",
651
+ "shape": [8],
652
+ "data": {
653
+ "kind": "values",
654
+ "values": [100000000000000000000.0, 10000000000000.0, -100000000000000000000.0, 100000000000000000000.0, 2500000000.0, 123456789.0, -10000000000000.0, 999999999.0]
655
+ }
656
+ },
657
+ "b": {
658
+ "dtype": "float32",
659
+ "shape": [8],
660
+ "data": { "kind": "values", "values": [3.0, 7.0, 3.0, 7.0, 100.0, 1000.0, 7.0, 10.0] }
661
+ }
662
+ },
663
+ "outputs": { "c": { "dtype": "float32", "shape": [8], "tolerance": 0.001 } }
664
+ },
665
+ {
666
+ "name": "i32_floor_mod_non_vec4_numel_negative_divisor",
667
+ "inputs": {
668
+ "a": { "dtype": "int32", "shape": [3], "data": { "kind": "values", "values": [5, -5, 7] } },
669
+ "b": { "dtype": "int32", "shape": [3], "data": { "kind": "values", "values": [-3, -3, -3] } }
670
+ },
671
+ "outputs": { "c": { "dtype": "int32", "shape": [3], "tolerance": 0 } }
672
+ },
673
+ {
674
+ "name": "f16_fmod_values_near_f16_max",
675
+ "attrs": { "fmod": 1 },
676
+ "inputs": {
677
+ "a": {
678
+ "dtype": "float16",
679
+ "shape": [4],
680
+ "data": { "kind": "values", "values": [65504.0, 65504.0, -65504.0, 1024.0] }
681
+ },
682
+ "b": { "dtype": "float16", "shape": [4], "data": { "kind": "values", "values": [3.0, 0.5, 3.0, 0.125] } }
683
+ },
684
+ "outputs": { "c": { "dtype": "float16", "shape": [4], "tolerance": 0.5 } }
685
+ },
686
+ {
687
+ "name": "u32_scalar_b_non_vec4_large_values",
688
+ "inputs": {
689
+ "a": {
690
+ "dtype": "uint32",
691
+ "shape": [5],
692
+ "data": { "kind": "values", "values": [4294967295, 4294967294, 100, 7, 3] }
693
+ },
694
+ "b": { "dtype": "uint32", "shape": [1], "data": { "kind": "values", "values": [4294967293] } }
695
+ },
696
+ "outputs": {
697
+ "c": {
698
+ "dtype": "uint32",
699
+ "shape": [5],
700
+ "tolerance": 0,
701
+ "data": { "kind": "values", "values": [2, 1, 100, 7, 3] }
702
+ }
703
+ }
704
+ },
705
+ {
706
+ "name": "i8_floor_mod_divisor_equals_dividend",
707
+ "inputs": {
708
+ "a": { "dtype": "int8", "shape": [4], "data": { "kind": "values", "values": [-128, -128, -1, 127] } },
709
+ "b": { "dtype": "int8", "shape": [4], "data": { "kind": "values", "values": [-128, 127, -1, -127] } }
710
+ },
711
+ "outputs": {
712
+ "c": { "dtype": "int8", "shape": [4], "tolerance": 0, "data": { "kind": "values", "values": [0, 126, 0, 0] } }
713
+ }
714
+ },
715
+ {
716
+ "name": "rank7_broadcast_scalar_tail",
717
+ "inputs": {
718
+ "a": {
719
+ "dtype": "uint32",
720
+ "shape": [2, 1, 2, 1, 2, 1, 3],
721
+ "data": { "kind": "cycle", "values": [5, 7, 11, 13] }
722
+ },
723
+ "b": { "dtype": "uint32", "shape": [1, 2, 1, 2, 1, 2, 1], "data": { "kind": "cycle", "values": [2, 3, 4] } }
724
+ },
725
+ "outputs": { "c": { "dtype": "uint32", "shape": [2, 2, 2, 2, 2, 2, 3], "tolerance": 0 } }
726
+ },
727
+ {
728
+ "name": "last_axis_broadcast_vec4_odd_row_crossing",
729
+ "provenance": {
730
+ "notes": "Four vec4 groups cross odd-width row boundaries, exercising wrapped per-lane B indices. Expected values come from the TypeScript Mod reference."
731
+ },
732
+ "attrs": { "fmod": 1 },
733
+ "inputs": {
734
+ "a": {
735
+ "dtype": "float32",
736
+ "shape": [4, 5],
737
+ "data": { "kind": "fillFloat32", "sinStep": 0.41, "scale": 17.0, "offset": -2.0 }
738
+ },
739
+ "b": { "dtype": "float32", "shape": [5], "data": { "kind": "values", "values": [2.0, 3.0, 4.0, 5.0, 6.0] } }
740
+ },
741
+ "outputs": { "c": { "dtype": "float32", "shape": [4, 5], "tolerance": 0.000001 } }
742
+ },
743
+ {
744
+ "name": "last_axis_broadcast_vec4_f16_odd_row_crossing",
745
+ "provenance": {
746
+ "notes": "f16 companion for the per-lane broadcast path; locks explicit widening of scalar B lanes before fmod arithmetic."
747
+ },
748
+ "attrs": { "fmod": 1 },
749
+ "inputs": {
750
+ "a": {
751
+ "dtype": "float16",
752
+ "shape": [4, 5],
753
+ "data": { "kind": "fillFloat32", "sinStep": 0.41, "scale": 17.0, "offset": -2.0 }
754
+ },
755
+ "b": { "dtype": "float16", "shape": [5], "data": { "kind": "values", "values": [2.0, 3.0, 4.0, 5.0, 6.0] } }
756
+ },
757
+ "outputs": { "c": { "dtype": "float16", "shape": [4, 5], "tolerance": 0.01 } }
758
+ },
759
+ {
760
+ "name": "rank8_broadcast_alternating",
761
+ "attrs": {},
762
+ "inputs": {
763
+ "a": {
764
+ "dtype": "uint32",
765
+ "shape": [2, 1, 2, 1, 2, 1, 2, 3],
766
+ "data": { "kind": "cycle", "values": [7, 9, 13, 20] }
767
+ },
768
+ "b": { "dtype": "uint32", "shape": [1, 2, 1, 2, 1, 2, 1, 1], "data": { "kind": "cycle", "values": [3, 5] } }
769
+ },
770
+ "outputs": { "c": { "dtype": "uint32", "shape": [2, 2, 2, 2, 2, 2, 2, 3], "tolerance": 0 } }
771
+ }
772
+ ]
773
+ }