Xenova HF Staff commited on
Commit
63dd0ba
·
verified ·
1 Parent(s): 1f2c04e

sync 91d990483a17

Browse files
README.md CHANGED
@@ -12,24 +12,24 @@ tags:
12
 
13
  ## Description
14
 
15
- Finds unique values or subtensors along an optional `axis`. Without an axis, `X` is flattened; results are sorted or retain first-occurrence order. Sub-32-bit integers and booleans use lossless widened 32-bit storage. Metadata outputs remain logical int64 but use lossless uint32 storage because all values are bounded by an addressable tensor extent. Callers supply exact data-dependent output shapes. ONNX-permitted uint16, 64-bit, string, and complex inputs remain unsupported because the runtime lacks matching WebGPU storage.
16
 
17
  See the [ONNX `Unique` spec](https://onnx.ai/onnx/operators/onnx__Unique.html) for the reference semantics.
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
- | `X` | `x` | `T` | — | — | The N-D input tensor from which unique values or subtensors are extracted. When `axis` is omitted, tensors of any rank are flattened in row-major order. | required |
24
 
25
  ## Outputs
26
 
27
- | Name | Bind key | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
28
  | --- | --- | --- | --- | --- | --- | --- | --- |
29
- | `Y` | `y` | `T` | runtime-selected; narrow integers and bool use 32-bit slots | derived | — | Tensor containing all unique values or subtensors of X, sorted or in first-occurrence order. | required |
30
- | `indices` | `indices` | `I` | `uint32` | `1` | — | Optional logical int64 indices of each `Y` value or slice's first occurrence in `X`; stored as bounded uint32 values by WebGPU. | optional |
31
- | `inverse_indices` | `inverse_indices` | `I` | `uint32` | `1` | — | Optional logical int64 mapping from each flattened input value, or each input-axis slice, to its corresponding index in `Y`; stored as bounded uint32 values by WebGPU. | optional |
32
- | `counts` | `counts` | `I` | `uint32` | `1` | — | Optional logical int64 occurrence count for each unique value or slice in `Y`; stored as bounded uint32 values by WebGPU. | optional |
33
 
34
  ## Attributes
35
 
@@ -37,8 +37,8 @@ Attributes and default values (overridable per request):
37
 
38
  | Attribute | Default | Description |
39
  | --- | --- | --- |
40
- | `sorted` | `1` | Whether to sort unique elements in ascending order before output; 1 (default) sorts, 0 retains first-occurrence order. |
41
  | `axis` | — | Optional axis along which unique subtensors are identified. Negative values count from the back; when omitted, the input is flattened. |
 
42
 
43
  ## Type constraints
44
 
@@ -47,37 +47,95 @@ Attributes and default values (overridable per request):
47
  | `T` | `float32`, `float16`, `uint32`, `int32`, `int16`, `uint8`, `int8`, `bool` |
48
  | `I` | `int64` |
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  ## Files
51
 
52
- - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
53
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
54
  - [`test.json`](build/webgpu/test.json) — correctness cases
55
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
56
  - [`unique-axis-compact-sort.wgsl.jinja`](build/webgpu/unique-axis-compact-sort.wgsl.jinja)
57
  - [`unique-axis-dedup.wgsl.jinja`](build/webgpu/unique-axis-dedup.wgsl.jinja)
58
  - [`unique-axis-hash.wgsl.jinja`](build/webgpu/unique-axis-hash.wgsl.jinja)
 
 
59
  - [`unique-axis-scatter.wgsl.jinja`](build/webgpu/unique-axis-scatter.wgsl.jinja)
60
  - [`unique-axis.wgsl.jinja`](build/webgpu/unique-axis.wgsl.jinja)
61
  - [`unique-compact-sort.wgsl.jinja`](build/webgpu/unique-compact-sort.wgsl.jinja)
62
  - [`unique-dedup.wgsl.jinja`](build/webgpu/unique-dedup.wgsl.jinja)
 
 
 
 
 
63
  - [`unique-hash-build.wgsl.jinja`](build/webgpu/unique-hash-build.wgsl.jinja)
64
  - [`unique-hash-collect.wgsl.jinja`](build/webgpu/unique-hash-collect.wgsl.jinja)
65
  - [`unique-hash-init.wgsl.jinja`](build/webgpu/unique-hash-init.wgsl.jinja)
66
  - [`unique-hash-mark.wgsl.jinja`](build/webgpu/unique-hash-mark.wgsl.jinja)
67
  - [`unique-hash-sort-collected-key-only.wgsl.jinja`](build/webgpu/unique-hash-sort-collected-key-only.wgsl.jinja)
 
 
 
 
68
  - [`unique.wgsl.jinja`](build/webgpu/unique.wgsl.jinja)
69
 
70
  ## Use with `@huggingface/kernels`
71
 
72
- The loader automatically allocates outputs whose metadata it can derive from the manifest contract and this call.
 
 
73
 
74
- The explicit `outputs` entries provide shape and logical dtype metadata for the results listed below:
75
 
76
- - `y`
77
 
78
- Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
79
 
80
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
81
 
82
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
83
 
 
12
 
13
  ## Description
14
 
15
+ Finds unique values or subtensors along an optional `axis`. Without an axis, `X` is flattened; results are sorted or retain first-occurrence order. Sub-32-bit integers and booleans use lossless widened 32-bit storage. Metadata outputs remain logical int64 but use lossless uint32 storage because all values are bounded by an addressable tensor extent. Exact data-dependent output shapes must be supplied. ONNX-permitted uint16, 64-bit, string, and complex inputs are unsupported by this package.
16
 
17
  See the [ONNX `Unique` spec](https://onnx.ai/onnx/operators/onnx__Unique.html) for the reference semantics.
18
 
19
  ## Inputs
20
 
21
+ | Name | Upstream name | Logical dtype | Rank | Shape | Description | Presence |
22
  | --- | --- | --- | --- | --- | --- | --- |
23
+ | `x` | `X` | `T` | — | — | The N-D input tensor from which unique values or subtensors are extracted. When `axis` is omitted, tensors of any rank are flattened in row-major order. | required |
24
 
25
  ## Outputs
26
 
27
+ | Name | Upstream name | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
28
  | --- | --- | --- | --- | --- | --- | --- | --- |
29
+ | `y` | `Y` | `T` | runtime-selected; narrow integers and bool use 32-bit slots | derived | — | Tensor containing all unique values or subtensors of X, sorted or in first-occurrence order. | required |
30
+ | `indices` | | `I` | `uint32` | `1` | — | Optional logical int64 indices of each `Y` value or slice's first occurrence in `X`; stored as bounded uint32 values by WebGPU. | optional |
31
+ | `inverse_indices` | | `I` | `uint32` | `1` | — | Optional logical int64 mapping from each flattened input value, or each input-axis slice, to its corresponding index in `Y`; stored as bounded uint32 values by WebGPU. | optional |
32
+ | `counts` | | `I` | `uint32` | `1` | — | Optional logical int64 occurrence count for each unique value or slice in `Y`; stored as bounded uint32 values by WebGPU. | optional |
33
 
34
  ## Attributes
35
 
 
37
 
38
  | Attribute | Default | Description |
39
  | --- | --- | --- |
 
40
  | `axis` | — | Optional axis along which unique subtensors are identified. Negative values count from the back; when omitted, the input is flattened. |
41
+ | `sorted` | `1` | Whether to sort unique elements in ascending order before output; 1 (default) sorts, 0 retains first-occurrence order. |
42
 
43
  ## Type constraints
44
 
 
47
  | `T` | `float32`, `float16`, `uint32`, `int32`, `int16`, `uint8`, `int8`, `bool` |
48
  | `I` | `int64` |
49
 
50
+ ## Implementation variants
51
+
52
+ One implementation is selected per call from the device capabilities, the request shapes and the dtypes; these notes say what each one covers.
53
+
54
+ - `single_class_y` — Copy the first representative and materialize requested metadata when the exact output contract proves there is one distinct class.
55
+ - `scalar_hash_parallel_y_unsorted` — Hash first occurrences, scan flag blocks in parallel, and scatter scalar representatives in input order with only the requested metadata.
56
+ - `scalar_hash_parallel_y_sorted` — Hash and compact scalar representatives in parallel, sort their order with shared and global bitonic stages, and materialize only the requested metadata.
57
+ - `single_class_indices` — Copy the first representative and materialize requested metadata when the exact output contract proves there is one distinct class.
58
+ - `scalar_hash_parallel_indices_unsorted` — Hash first occurrences, scan flag blocks in parallel, and scatter scalar representatives in input order with only the requested metadata.
59
+ - `scalar_hash_parallel_indices_sorted` — Hash and compact scalar representatives in parallel, sort their order with shared and global bitonic stages, and materialize only the requested metadata.
60
+ - `single_class_inverse` — Copy the first representative and materialize requested metadata when the exact output contract proves there is one distinct class.
61
+ - `scalar_hash_parallel_inverse_unsorted` — Hash first occurrences, scan flag blocks in parallel, and scatter scalar representatives in input order with only the requested metadata.
62
+ - `scalar_hash_parallel_inverse_sorted` — Hash and compact scalar representatives in parallel, sort their order with shared and global bitonic stages, and materialize only the requested metadata.
63
+ - `single_class_indices_inverse` — Copy the first representative and materialize requested metadata when the exact output contract proves there is one distinct class.
64
+ - `scalar_hash_parallel_indices_inverse_unsorted` — Hash first occurrences, scan flag blocks in parallel, and scatter scalar representatives in input order with only the requested metadata.
65
+ - `scalar_hash_parallel_indices_inverse_sorted` — Hash and compact scalar representatives in parallel, sort their order with shared and global bitonic stages, and materialize only the requested metadata.
66
+ - `single_class_counts` — Copy the first representative and materialize requested metadata when the exact output contract proves there is one distinct class.
67
+ - `scalar_hash_parallel_counts_unsorted` — Hash first occurrences, scan flag blocks in parallel, and scatter scalar representatives in input order with only the requested metadata.
68
+ - `scalar_hash_parallel_counts_sorted` — Hash and compact scalar representatives in parallel, sort their order with shared and global bitonic stages, and materialize only the requested metadata.
69
+ - `single_class_indices_counts` — Copy the first representative and materialize requested metadata when the exact output contract proves there is one distinct class.
70
+ - `scalar_hash_parallel_indices_counts_unsorted` — Hash first occurrences, scan flag blocks in parallel, and scatter scalar representatives in input order with only the requested metadata.
71
+ - `scalar_hash_parallel_indices_counts_sorted` — Hash and compact scalar representatives in parallel, sort their order with shared and global bitonic stages, and materialize only the requested metadata.
72
+ - `single_class_inverse_counts` — Copy the first representative and materialize requested metadata when the exact output contract proves there is one distinct class.
73
+ - `scalar_hash_parallel_inverse_counts_unsorted` — Hash first occurrences, scan flag blocks in parallel, and scatter scalar representatives in input order with only the requested metadata.
74
+ - `scalar_hash_parallel_inverse_counts_sorted` — Hash and compact scalar representatives in parallel, sort their order with shared and global bitonic stages, and materialize only the requested metadata.
75
+ - `single_class_indices_inverse_counts` — Copy the first representative and materialize requested metadata when the exact output contract proves there is one distinct class.
76
+ - `scalar_hash_parallel_indices_inverse_counts_unsorted` — Hash first occurrences, scan flag blocks in parallel, and scatter scalar representatives in input order with only the requested metadata.
77
+ - `scalar_hash_parallel_indices_inverse_counts_sorted` — Hash and compact scalar representatives in parallel, sort their order with shared and global bitonic stages, and materialize only the requested metadata.
78
+ - `flat_parallel_metadata_indices` — Builds the unique values with the parallel dedup and compaction passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
79
+ - `flat_parallel_metadata_inverse` — Builds the unique values with the parallel dedup and compaction passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
80
+ - `flat_parallel_metadata_indices_inverse` — Builds the unique values with the parallel dedup and compaction passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
81
+ - `flat_parallel_metadata_counts` — Builds the unique values with the parallel dedup and compaction passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
82
+ - `flat_parallel_metadata_indices_counts` — Builds the unique values with the parallel dedup and compaction passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
83
+ - `flat_parallel_metadata_inverse_counts` — Builds the unique values with the parallel dedup and compaction passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
84
+ - `flat_parallel_metadata_all` — Builds the unique values with the parallel dedup and compaction passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
85
+ - `flat_hash_metadata_indices` — Builds the unique values with the hash-set dedup passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
86
+ - `flat_hash_metadata_inverse` — Builds the unique values with the hash-set dedup passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
87
+ - `flat_hash_metadata_indices_inverse` — Builds the unique values with the hash-set dedup passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
88
+ - `flat_hash_metadata_counts` — Builds the unique values with the hash-set dedup passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
89
+ - `flat_hash_metadata_indices_counts` — Builds the unique values with the hash-set dedup passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
90
+ - `flat_hash_metadata_inverse_counts` — Builds the unique values with the hash-set dedup passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
91
+ - `flat_hash_metadata_all` — Builds the unique values with the hash-set dedup passes, then resolves every metadata output from the finished result: one thread per input element for the bucket and the first-occurrence store, one thread per unique value for the tally.
92
+
93
  ## Files
94
 
95
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
96
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
97
  - [`test.json`](build/webgpu/test.json) — correctness cases
98
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
99
+ - [`scan-block-prefix-u32.wgsl.jinja`](build/webgpu/scan-block-prefix-u32.wgsl.jinja)
100
  - [`unique-axis-compact-sort.wgsl.jinja`](build/webgpu/unique-axis-compact-sort.wgsl.jinja)
101
  - [`unique-axis-dedup.wgsl.jinja`](build/webgpu/unique-axis-dedup.wgsl.jinja)
102
  - [`unique-axis-hash.wgsl.jinja`](build/webgpu/unique-axis-hash.wgsl.jinja)
103
+ - [`unique-axis-scalar-inverse.wgsl.jinja`](build/webgpu/unique-axis-scalar-inverse.wgsl.jinja)
104
+ - [`unique-axis-scalar-ranks.wgsl.jinja`](build/webgpu/unique-axis-scalar-ranks.wgsl.jinja)
105
  - [`unique-axis-scatter.wgsl.jinja`](build/webgpu/unique-axis-scatter.wgsl.jinja)
106
  - [`unique-axis.wgsl.jinja`](build/webgpu/unique-axis.wgsl.jinja)
107
  - [`unique-compact-sort.wgsl.jinja`](build/webgpu/unique-compact-sort.wgsl.jinja)
108
  - [`unique-dedup.wgsl.jinja`](build/webgpu/unique-dedup.wgsl.jinja)
109
+ - [`unique-flag-block-scan.wgsl.jinja`](build/webgpu/unique-flag-block-scan.wgsl.jinja)
110
+ - [`unique-flat-metadata.wgsl.jinja`](build/webgpu/unique-flat-metadata.wgsl.jinja)
111
+ - [`unique-global-sort-exchange.wgsl.jinja`](build/webgpu/unique-global-sort-exchange.wgsl.jinja)
112
+ - [`unique-global-sort-output.wgsl.jinja`](build/webgpu/unique-global-sort-output.wgsl.jinja)
113
+ - [`unique-global-sort-shared.wgsl.jinja`](build/webgpu/unique-global-sort-shared.wgsl.jinja)
114
  - [`unique-hash-build.wgsl.jinja`](build/webgpu/unique-hash-build.wgsl.jinja)
115
  - [`unique-hash-collect.wgsl.jinja`](build/webgpu/unique-hash-collect.wgsl.jinja)
116
  - [`unique-hash-init.wgsl.jinja`](build/webgpu/unique-hash-init.wgsl.jinja)
117
  - [`unique-hash-mark.wgsl.jinja`](build/webgpu/unique-hash-mark.wgsl.jinja)
118
  - [`unique-hash-sort-collected-key-only.wgsl.jinja`](build/webgpu/unique-hash-sort-collected-key-only.wgsl.jinja)
119
+ - [`unique-scalar-compact.wgsl.jinja`](build/webgpu/unique-scalar-compact.wgsl.jinja)
120
+ - [`unique-scalar-metadata.wgsl.jinja`](build/webgpu/unique-scalar-metadata.wgsl.jinja)
121
+ - [`unique-scalar-output.wgsl.jinja`](build/webgpu/unique-scalar-output.wgsl.jinja)
122
+ - [`unique-single-class.wgsl.jinja`](build/webgpu/unique-single-class.wgsl.jinja)
123
  - [`unique.wgsl.jinja`](build/webgpu/unique.wgsl.jinja)
124
 
125
  ## Use with `@huggingface/kernels`
126
 
127
+ ```sh
128
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
129
+ ```
130
 
131
+ Outputs with inferable metadata are allocated automatically. Explicit `outputs` entries request optional results or provide metadata that cannot be inferred from the supplied inputs and attributes.
132
 
133
+ This example supplies explicit metadata for:
134
 
135
+ - `y`
136
 
137
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
138
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
139
 
140
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
141
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.Unique",
3
  "cases": [
4
  {
5
  "name": "int32-64k-moderate-distinct-sorted",
@@ -116,9 +115,12 @@
116
  "preset": "smoke",
117
  "attrs": { "axis": 0, "sorted": 1 },
118
  "inputs": {
119
- "x": { "dtype": "float32", "shape": [4096, 256], "dist": "uniform", "seed": 1531, "scale": 8, "offset": 0 }
120
  },
121
  "outputs": { "y": { "dtype": "float32", "shape": [4096, 256] } },
 
 
 
122
  "bench": {
123
  "primary": true,
124
  "metrics": [
@@ -130,6 +132,27 @@
130
  ]
131
  }
132
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  {
134
  "name": "axis_hash_split_scatter_unsorted_zero_fills_tail",
135
  "preset": "smoke",
@@ -206,7 +229,7 @@
206
  "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + dim(shapes.y, 0))" }] }
207
  },
208
  {
209
- "name": "axis0-f32-70000x1-above-hash-capacity-serial",
210
  "preset": "stress",
211
  "attrs": { "axis": 0, "sorted": 1 },
212
  "inputs": {
@@ -217,7 +240,10 @@
217
  }
218
  },
219
  "outputs": { "y": { "dtype": "float32", "shape": [70000, 1] } },
220
- "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + dim(shapes.y, 0))" }] }
 
 
 
221
  },
222
  {
223
  "name": "int32-262144-Y4096-token-vocab-sorted",
@@ -242,6 +268,1189 @@
242
  "inputs": { "x": { "dtype": "int32", "shape": [8192], "dist": "linearMod", "mod": 8192 } },
243
  "outputs": { "y": { "dtype": "int32", "shape": [8192] } },
244
  "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  }
246
- ]
 
 
 
 
 
247
  }
 
1
  {
 
2
  "cases": [
3
  {
4
  "name": "int32-64k-moderate-distinct-sorted",
 
115
  "preset": "smoke",
116
  "attrs": { "axis": 0, "sorted": 1 },
117
  "inputs": {
118
+ "x": { "dtype": "float32", "shape": [4096, 256], "data": { "kind": "linspace", "start": 8.0, "end": -8.0 } }
119
  },
120
  "outputs": { "y": { "dtype": "float32", "shape": [4096, 256] } },
121
+ "provenance": {
122
+ "notes": "Descending values give 4096 distinct 256-element rows. Lexicographic sorting reverses row order while retaining each row's column order."
123
+ },
124
  "bench": {
125
  "primary": true,
126
  "metrics": [
 
132
  ]
133
  }
134
  },
135
+ {
136
+ "name": "axis0-f32-4096x256-1024-distinct-random-rows",
137
+ "preset": "smoke",
138
+ "attrs": { "axis": 0, "sorted": 1 },
139
+ "inputs": {
140
+ "x": { "dtype": "float32", "shape": [4096, 256], "dist": "uniform", "seed": 1531, "scale": 8, "offset": 0 }
141
+ },
142
+ "outputs": { "y": { "dtype": "float32", "shape": [1024, 256] } },
143
+ "provenance": {
144
+ "notes": "Input contains four copies of each of 1024 distinct 256-element rows. Output contains the distinct rows in lexicographic order."
145
+ },
146
+ "bench": {
147
+ "metrics": [
148
+ {
149
+ "type": "bandwidth",
150
+ "name": "logical input/output bytes",
151
+ "value": "4 * (numel(shapes.x) + numel(shapes.y))"
152
+ }
153
+ ]
154
+ }
155
+ },
156
  {
157
  "name": "axis_hash_split_scatter_unsorted_zero_fills_tail",
158
  "preset": "smoke",
 
229
  "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + dim(shapes.y, 0))" }] }
230
  },
231
  {
232
+ "name": "axis0-f32-70000x1-above-old-hash-capacity",
233
  "preset": "stress",
234
  "attrs": { "axis": 0, "sorted": 1 },
235
  "inputs": {
 
240
  }
241
  },
242
  "outputs": { "y": { "dtype": "float32", "shape": [70000, 1] } },
243
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + dim(shapes.y, 0))" }] },
244
+ "provenance": {
245
+ "notes": "A 70,000-row float tensor with every row distinct exercises large-axis hash deduplication and sorted emission."
246
+ }
247
  },
248
  {
249
  "name": "int32-262144-Y4096-token-vocab-sorted",
 
268
  "inputs": { "x": { "dtype": "int32", "shape": [8192], "dist": "linearMod", "mod": 8192 } },
269
  "outputs": { "y": { "dtype": "int32", "shape": [8192] } },
270
  "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
271
+ },
272
+ {
273
+ "name": "int32-4096-128-distinct-inverse-metadata",
274
+ "provenance": {
275
+ "notes": "A 4,096-token stream with 128 distinct values exercises parallel local deduplication while producing an inverse map for vocabulary compaction."
276
+ },
277
+ "preset": "smoke",
278
+ "attrs": { "sorted": 0 },
279
+ "inputs": { "x": { "dtype": "int32", "shape": [4096], "dist": "linearMod", "mod": 128 } },
280
+ "outputs": {
281
+ "y": { "dtype": "int32", "shape": [128] },
282
+ "inverse_indices": { "dtype": "uint32", "shape": [4096] }
283
+ },
284
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "12 * numel(shapes.x)" }] }
285
+ },
286
+ {
287
+ "name": "int32-65536-512-distinct-inverse-metadata-hash",
288
+ "provenance": {
289
+ "notes": "A 65,536-token stream with 512 distinct values exercises hash-set deduplication while producing an inverse map for vocabulary compaction."
290
+ },
291
+ "preset": "smoke",
292
+ "attrs": { "sorted": 0 },
293
+ "inputs": { "x": { "dtype": "int32", "shape": [65536], "dist": "linearMod", "mod": 512 } },
294
+ "outputs": {
295
+ "y": { "dtype": "int32", "shape": [512] },
296
+ "inverse_indices": { "dtype": "uint32", "shape": [65536] }
297
+ },
298
+ "bench": { "primary": true, "metrics": [{ "type": "bandwidth", "value": "12 * numel(shapes.x)" }] }
299
+ },
300
+ {
301
+ "name": "axis0-int32-512x16-record-table-shared-markers",
302
+ "provenance": {
303
+ "notes": "A fixed-width record table whose shared marker fields sit at the four positions an evenly-spaced four-sample prefilter reads, and whose identifier does not. Every pair of rows therefore agrees on the prefilter and only the exact comparator separates them."
304
+ },
305
+ "attrs": { "axis": 0, "sorted": 0 },
306
+ "inputs": {
307
+ "x": {
308
+ "dtype": "int32",
309
+ "shape": [512, 16],
310
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/axis_record_table_marker_collision" } }
311
+ }
312
+ },
313
+ "outputs": { "y": { "dtype": "int32", "shape": [512, 16], "dist": "empty" } }
314
+ },
315
+ {
316
+ "name": "flat-i32-70000-distinct-above-old-capacity",
317
+ "preset": "stress",
318
+ "provenance": {
319
+ "notes": "A flat 70,000-element float tensor with every value distinct exercises large-output hash deduplication and sorted emission."
320
+ },
321
+ "attrs": { "sorted": 1 },
322
+ "inputs": {
323
+ "x": { "dtype": "int32", "shape": [70000], "data": { "kind": "linspace", "start": -35000, "end": 34999 } }
324
+ },
325
+ "outputs": { "y": { "dtype": "int32", "shape": [70000], "dist": "empty" } },
326
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
327
+ },
328
+ {
329
+ "name": "axis0-f32-16384x1-inverse-metadata",
330
+ "preset": "stress",
331
+ "provenance": {
332
+ "notes": "A float axis request with an inverse map and more distinct rows than the ordered-map head cache holds, so the comparator reads its keys back from the order buffer instead."
333
+ },
334
+ "attrs": { "axis": 0, "sorted": 1 },
335
+ "inputs": {
336
+ "x": {
337
+ "dtype": "float32",
338
+ "shape": [16384, 1],
339
+ "data": { "kind": "linspace", "start": -8192.0, "end": 8191.0 }
340
+ }
341
+ },
342
+ "outputs": {
343
+ "y": { "dtype": "float32", "shape": [16384, 1], "dist": "empty" },
344
+ "inverse_indices": { "dtype": "uint32", "shape": [16384], "dist": "empty" }
345
+ }
346
+ },
347
+ {
348
+ "name": "flat-i32-1049600-1024-classes-label-raster",
349
+ "preset": "stress",
350
+ "provenance": {
351
+ "notes": "A 1,049,600-element class-label raster with 1,024 classes exercises large-input flat deduplication in first-occurrence order."
352
+ },
353
+ "attrs": { "sorted": 0 },
354
+ "inputs": {
355
+ "x": { "dtype": "int32", "shape": [1025, 1024], "data": { "kind": "linspace", "start": 0, "end": 1023 } }
356
+ },
357
+ "outputs": { "y": { "dtype": "int32", "shape": [1024], "dist": "empty" } }
358
+ },
359
+ {
360
+ "name": "flat-i32-70000-distinct-unsorted-first-order",
361
+ "preset": "stress",
362
+ "provenance": {
363
+ "source": "synthetic",
364
+ "notes": "Descending distinct integers measure large-output hash deduplication while preserving first-occurrence order for sorted=0."
365
+ },
366
+ "attrs": { "sorted": 0 },
367
+ "inputs": {
368
+ "x": { "dtype": "int32", "shape": [70000], "data": { "kind": "linspace", "start": 34999, "end": -35000 } }
369
+ },
370
+ "outputs": { "y": { "dtype": "int32", "shape": [70000], "dist": "empty" } },
371
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
372
+ },
373
+ {
374
+ "name": "axis-scalar-f32-inverse-descending",
375
+ "preset": "stress",
376
+ "provenance": {
377
+ "notes": "Scalar-axis inverse map: descending. Checks the hash/rank route with non-identity ordering, duplicate-rich or history-dependent NaN inputs."
378
+ },
379
+ "attrs": { "axis": 0, "sorted": 1 },
380
+ "inputs": {
381
+ "x": {
382
+ "dtype": "float32",
383
+ "shape": [16384, 1],
384
+ "data": { "kind": "linspace", "start": 8191.0, "end": -8192.0 }
385
+ }
386
+ },
387
+ "outputs": {
388
+ "y": { "dtype": "float32", "shape": [16384, 1], "dist": "empty" },
389
+ "inverse_indices": { "dtype": "uint32", "shape": [16384], "dist": "empty" }
390
+ }
391
+ },
392
+ {
393
+ "name": "axis-scalar-f32-inverse-duplicate64",
394
+ "preset": "stress",
395
+ "provenance": {
396
+ "notes": "Scalar-axis inverse map: duplicate64. Checks the hash/rank route with non-identity ordering, duplicate-rich or history-dependent NaN inputs."
397
+ },
398
+ "attrs": { "axis": 0, "sorted": 1 },
399
+ "inputs": {
400
+ "x": {
401
+ "dtype": "float32",
402
+ "shape": [4096, 1],
403
+ "data": {
404
+ "kind": "cycle",
405
+ "values": { "$ref": "#/fixtureArrays/axis-scalar-f32-inverse-duplicate64_input_x" }
406
+ }
407
+ }
408
+ },
409
+ "outputs": {
410
+ "y": { "dtype": "float32", "shape": [64, 1], "dist": "empty" },
411
+ "inverse_indices": { "dtype": "uint32", "shape": [4096], "dist": "empty" }
412
+ }
413
+ },
414
+ {
415
+ "name": "axis-scalar-f32-inverse-duplicate64-unsorted",
416
+ "preset": "stress",
417
+ "provenance": {
418
+ "notes": "Scalar-axis inverse map: duplicate64-unsorted. Checks the hash/rank route with non-identity ordering, duplicate-rich or history-dependent NaN inputs."
419
+ },
420
+ "attrs": { "axis": 0, "sorted": 0 },
421
+ "inputs": {
422
+ "x": {
423
+ "dtype": "float32",
424
+ "shape": [4096, 1],
425
+ "data": {
426
+ "kind": "cycle",
427
+ "values": { "$ref": "#/fixtureArrays/axis-scalar-f32-inverse-duplicate64_input_x" }
428
+ }
429
+ }
430
+ },
431
+ "outputs": {
432
+ "y": { "dtype": "float32", "shape": [64, 1], "dist": "empty" },
433
+ "inverse_indices": { "dtype": "uint32", "shape": [4096], "dist": "empty" }
434
+ }
435
+ },
436
+ {
437
+ "name": "axis-scalar-f32-inverse-nan-prefix",
438
+ "preset": "stress",
439
+ "provenance": {
440
+ "notes": "Scalar-axis inverse map: nan-prefix. Checks the hash/rank route with non-identity ordering, duplicate-rich or history-dependent NaN inputs."
441
+ },
442
+ "attrs": { "axis": 0, "sorted": 1 },
443
+ "inputs": {
444
+ "x": {
445
+ "dtype": "float32",
446
+ "shape": [8192, 1],
447
+ "data": { "kind": "cycle", "values": [5.0, "NaN", 1.0, "NaN", -2.0, "NaN", 3.0, 5.0] }
448
+ }
449
+ },
450
+ "outputs": {
451
+ "y": { "dtype": "float32", "shape": [4, 1], "dist": "empty" },
452
+ "inverse_indices": { "dtype": "uint32", "shape": [8192], "dist": "empty" }
453
+ }
454
+ },
455
+ {
456
+ "name": "axis-scalar-f32-inverse-nan-prefix-unsorted",
457
+ "preset": "stress",
458
+ "provenance": {
459
+ "notes": "Scalar-axis inverse map: nan-prefix-unsorted. Checks the hash/rank route with non-identity ordering, duplicate-rich or history-dependent NaN inputs."
460
+ },
461
+ "attrs": { "axis": 0, "sorted": 0 },
462
+ "inputs": {
463
+ "x": {
464
+ "dtype": "float32",
465
+ "shape": [8192, 1],
466
+ "data": { "kind": "cycle", "values": [5.0, "NaN", 1.0, "NaN", -2.0, "NaN", 3.0, 5.0] }
467
+ }
468
+ },
469
+ "outputs": {
470
+ "y": { "dtype": "float32", "shape": [4, 1], "dist": "empty" },
471
+ "inverse_indices": { "dtype": "uint32", "shape": [8192], "dist": "empty" }
472
+ }
473
+ },
474
+ {
475
+ "name": "axis-scalar-f32-inverse-hybrid-floor",
476
+ "preset": "stress",
477
+ "provenance": {
478
+ "notes": "Scalar-axis inverse map: hybrid-floor. Checks the hash/rank route with non-identity ordering, duplicate-rich or history-dependent NaN inputs."
479
+ },
480
+ "attrs": { "axis": 0, "sorted": 1 },
481
+ "inputs": {
482
+ "x": {
483
+ "dtype": "float32",
484
+ "shape": [4096, 1],
485
+ "data": {
486
+ "kind": "cycle",
487
+ "values": { "$ref": "#/fixtureArrays/axis-scalar-f32-inverse-hybrid-floor_input_x" }
488
+ }
489
+ }
490
+ },
491
+ "outputs": {
492
+ "y": { "dtype": "float32", "shape": [2049, 1], "dist": "empty" },
493
+ "inverse_indices": { "dtype": "uint32", "shape": [4096], "dist": "empty" }
494
+ }
495
+ },
496
+ {
497
+ "name": "r4_scalar_scan_n32767_u13_sorted0",
498
+ "attrs": { "axis": -2, "sorted": 0 },
499
+ "inputs": {
500
+ "x": {
501
+ "dtype": "float32",
502
+ "shape": [1, 32767, 1],
503
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
504
+ }
505
+ },
506
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] } },
507
+ "provenance": {
508
+ "notes": "Neighboring natural scan sizes at dispatch boundaries with bounded distinct-count oracle."
509
+ },
510
+ "bench": {
511
+ "metrics": [
512
+ {
513
+ "type": "bandwidth",
514
+ "name": "logical input/output bytes",
515
+ "value": "4 * (numel(shapes.x) + numel(shapes.y))"
516
+ }
517
+ ]
518
+ },
519
+ "preset": "stress"
520
+ },
521
+ {
522
+ "name": "r4_scalar_scan_n32767_u13_sorted1",
523
+ "attrs": { "axis": -2, "sorted": 1 },
524
+ "inputs": {
525
+ "x": {
526
+ "dtype": "float32",
527
+ "shape": [1, 32767, 1],
528
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
529
+ }
530
+ },
531
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] } },
532
+ "provenance": {
533
+ "notes": "Neighboring natural scan sizes at dispatch boundaries with bounded distinct-count oracle."
534
+ },
535
+ "bench": {
536
+ "metrics": [
537
+ {
538
+ "type": "bandwidth",
539
+ "name": "logical input/output bytes",
540
+ "value": "4 * (numel(shapes.x) + numel(shapes.y))"
541
+ }
542
+ ]
543
+ },
544
+ "preset": "stress"
545
+ },
546
+ {
547
+ "name": "r4_scalar_scan_n32768_u13_sorted0",
548
+ "attrs": { "axis": -2, "sorted": 0 },
549
+ "inputs": {
550
+ "x": {
551
+ "dtype": "float32",
552
+ "shape": [1, 32768, 1],
553
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
554
+ }
555
+ },
556
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] } },
557
+ "provenance": {
558
+ "notes": "Neighboring natural scan sizes at dispatch boundaries with bounded distinct-count oracle."
559
+ },
560
+ "bench": {
561
+ "metrics": [
562
+ {
563
+ "type": "bandwidth",
564
+ "name": "logical input/output bytes",
565
+ "value": "4 * (numel(shapes.x) + numel(shapes.y))"
566
+ }
567
+ ]
568
+ },
569
+ "preset": "stress"
570
+ },
571
+ {
572
+ "name": "r4_scalar_scan_n32768_u13_sorted1",
573
+ "attrs": { "axis": -2, "sorted": 1 },
574
+ "inputs": {
575
+ "x": {
576
+ "dtype": "float32",
577
+ "shape": [1, 32768, 1],
578
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
579
+ }
580
+ },
581
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] } },
582
+ "provenance": {
583
+ "notes": "Neighboring natural scan sizes at dispatch boundaries with bounded distinct-count oracle."
584
+ },
585
+ "bench": {
586
+ "metrics": [
587
+ {
588
+ "type": "bandwidth",
589
+ "name": "logical input/output bytes",
590
+ "value": "4 * (numel(shapes.x) + numel(shapes.y))"
591
+ }
592
+ ]
593
+ },
594
+ "preset": "stress"
595
+ },
596
+ {
597
+ "name": "r4_scalar_scan_n32769_u13_sorted0",
598
+ "attrs": { "axis": -2, "sorted": 0 },
599
+ "inputs": {
600
+ "x": {
601
+ "dtype": "float32",
602
+ "shape": [1, 32769, 1],
603
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
604
+ }
605
+ },
606
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] } },
607
+ "provenance": {
608
+ "notes": "Neighboring natural scan sizes at dispatch boundaries with bounded distinct-count oracle."
609
+ },
610
+ "bench": {
611
+ "metrics": [
612
+ {
613
+ "type": "bandwidth",
614
+ "name": "logical input/output bytes",
615
+ "value": "4 * (numel(shapes.x) + numel(shapes.y))"
616
+ }
617
+ ]
618
+ },
619
+ "preset": "stress"
620
+ },
621
+ {
622
+ "name": "r4_scalar_scan_n32769_u13_sorted1",
623
+ "attrs": { "axis": -2, "sorted": 1 },
624
+ "inputs": {
625
+ "x": {
626
+ "dtype": "float32",
627
+ "shape": [1, 32769, 1],
628
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
629
+ }
630
+ },
631
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] } },
632
+ "provenance": {
633
+ "notes": "Neighboring natural scan sizes at dispatch boundaries with bounded distinct-count oracle."
634
+ },
635
+ "bench": {
636
+ "metrics": [
637
+ {
638
+ "type": "bandwidth",
639
+ "name": "logical input/output bytes",
640
+ "value": "4 * (numel(shapes.x) + numel(shapes.y))"
641
+ }
642
+ ]
643
+ },
644
+ "preset": "stress"
645
+ },
646
+ {
647
+ "name": "r4_scalar_scan_n65537_u13_sorted0",
648
+ "attrs": { "axis": -2, "sorted": 0 },
649
+ "inputs": {
650
+ "x": {
651
+ "dtype": "float32",
652
+ "shape": [1, 65537, 1],
653
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
654
+ }
655
+ },
656
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] } },
657
+ "provenance": {
658
+ "notes": "Neighboring natural scan sizes at dispatch boundaries with bounded distinct-count oracle."
659
+ },
660
+ "bench": {
661
+ "metrics": [
662
+ {
663
+ "type": "bandwidth",
664
+ "name": "logical input/output bytes",
665
+ "value": "4 * (numel(shapes.x) + numel(shapes.y))"
666
+ }
667
+ ]
668
+ },
669
+ "preset": "stress"
670
+ },
671
+ {
672
+ "name": "r4_scalar_scan_n65537_u13_sorted1",
673
+ "attrs": { "axis": -2, "sorted": 1 },
674
+ "inputs": {
675
+ "x": {
676
+ "dtype": "float32",
677
+ "shape": [1, 65537, 1],
678
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
679
+ }
680
+ },
681
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] } },
682
+ "provenance": {
683
+ "notes": "Neighboring natural scan sizes at dispatch boundaries with bounded distinct-count oracle."
684
+ },
685
+ "bench": {
686
+ "metrics": [
687
+ {
688
+ "type": "bandwidth",
689
+ "name": "logical input/output bytes",
690
+ "value": "4 * (numel(shapes.x) + numel(shapes.y))"
691
+ }
692
+ ]
693
+ },
694
+ "preset": "stress"
695
+ },
696
+ {
697
+ "name": "scalar-f32-metadata-mask0-sorted0-n16385-u13",
698
+ "preset": "stress",
699
+ "attrs": { "axis": 1, "sorted": 0 },
700
+ "inputs": {
701
+ "x": {
702
+ "dtype": "float32",
703
+ "shape": [1, 16385, 1],
704
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
705
+ }
706
+ },
707
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] } }
708
+ },
709
+ {
710
+ "name": "scalar-f32-metadata-mask0-sorted1-n16385-u13",
711
+ "preset": "stress",
712
+ "attrs": { "axis": 1, "sorted": 1 },
713
+ "inputs": {
714
+ "x": {
715
+ "dtype": "float32",
716
+ "shape": [1, 16385, 1],
717
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
718
+ }
719
+ },
720
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] } }
721
+ },
722
+ {
723
+ "name": "scalar-f32-metadata-mask1-sorted0-n16385-u13",
724
+ "preset": "stress",
725
+ "attrs": { "axis": 1, "sorted": 0 },
726
+ "inputs": {
727
+ "x": {
728
+ "dtype": "float32",
729
+ "shape": [1, 16385, 1],
730
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
731
+ }
732
+ },
733
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] }, "indices": { "dtype": "uint32", "shape": [13] } }
734
+ },
735
+ {
736
+ "name": "scalar-f32-metadata-mask1-sorted1-n16385-u13",
737
+ "preset": "stress",
738
+ "attrs": { "axis": 1, "sorted": 1 },
739
+ "inputs": {
740
+ "x": {
741
+ "dtype": "float32",
742
+ "shape": [1, 16385, 1],
743
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
744
+ }
745
+ },
746
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] }, "indices": { "dtype": "uint32", "shape": [13] } }
747
+ },
748
+ {
749
+ "name": "scalar-f32-metadata-mask2-sorted0-n16385-u13",
750
+ "preset": "stress",
751
+ "attrs": { "axis": 1, "sorted": 0 },
752
+ "inputs": {
753
+ "x": {
754
+ "dtype": "float32",
755
+ "shape": [1, 16385, 1],
756
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
757
+ }
758
+ },
759
+ "outputs": {
760
+ "y": { "dtype": "float32", "shape": [1, 13, 1] },
761
+ "inverse_indices": { "dtype": "uint32", "shape": [16385] }
762
+ }
763
+ },
764
+ {
765
+ "name": "scalar-f32-metadata-mask2-sorted1-n16385-u13",
766
+ "preset": "stress",
767
+ "attrs": { "axis": 1, "sorted": 1 },
768
+ "inputs": {
769
+ "x": {
770
+ "dtype": "float32",
771
+ "shape": [1, 16385, 1],
772
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
773
+ }
774
+ },
775
+ "outputs": {
776
+ "y": { "dtype": "float32", "shape": [1, 13, 1] },
777
+ "inverse_indices": { "dtype": "uint32", "shape": [16385] }
778
+ }
779
+ },
780
+ {
781
+ "name": "scalar-f32-metadata-mask3-sorted0-n16385-u13",
782
+ "preset": "stress",
783
+ "attrs": { "axis": 1, "sorted": 0 },
784
+ "inputs": {
785
+ "x": {
786
+ "dtype": "float32",
787
+ "shape": [1, 16385, 1],
788
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
789
+ }
790
+ },
791
+ "outputs": {
792
+ "y": { "dtype": "float32", "shape": [1, 13, 1] },
793
+ "indices": { "dtype": "uint32", "shape": [13] },
794
+ "inverse_indices": { "dtype": "uint32", "shape": [16385] }
795
+ }
796
+ },
797
+ {
798
+ "name": "scalar-f32-metadata-mask3-sorted1-n16385-u13",
799
+ "preset": "stress",
800
+ "attrs": { "axis": 1, "sorted": 1 },
801
+ "inputs": {
802
+ "x": {
803
+ "dtype": "float32",
804
+ "shape": [1, 16385, 1],
805
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
806
+ }
807
+ },
808
+ "outputs": {
809
+ "y": { "dtype": "float32", "shape": [1, 13, 1] },
810
+ "indices": { "dtype": "uint32", "shape": [13] },
811
+ "inverse_indices": { "dtype": "uint32", "shape": [16385] }
812
+ }
813
+ },
814
+ {
815
+ "name": "scalar-f32-metadata-mask4-sorted0-n16385-u13",
816
+ "preset": "stress",
817
+ "attrs": { "axis": 1, "sorted": 0 },
818
+ "inputs": {
819
+ "x": {
820
+ "dtype": "float32",
821
+ "shape": [1, 16385, 1],
822
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
823
+ }
824
+ },
825
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] }, "counts": { "dtype": "uint32", "shape": [13] } }
826
+ },
827
+ {
828
+ "name": "scalar-f32-metadata-mask4-sorted1-n16385-u13",
829
+ "preset": "stress",
830
+ "attrs": { "axis": 1, "sorted": 1 },
831
+ "inputs": {
832
+ "x": {
833
+ "dtype": "float32",
834
+ "shape": [1, 16385, 1],
835
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
836
+ }
837
+ },
838
+ "outputs": { "y": { "dtype": "float32", "shape": [1, 13, 1] }, "counts": { "dtype": "uint32", "shape": [13] } }
839
+ },
840
+ {
841
+ "name": "scalar-f32-metadata-mask5-sorted0-n16385-u13",
842
+ "preset": "stress",
843
+ "attrs": { "axis": 1, "sorted": 0 },
844
+ "inputs": {
845
+ "x": {
846
+ "dtype": "float32",
847
+ "shape": [1, 16385, 1],
848
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
849
+ }
850
+ },
851
+ "outputs": {
852
+ "y": { "dtype": "float32", "shape": [1, 13, 1] },
853
+ "indices": { "dtype": "uint32", "shape": [13] },
854
+ "counts": { "dtype": "uint32", "shape": [13] }
855
+ }
856
+ },
857
+ {
858
+ "name": "scalar-f32-metadata-mask5-sorted1-n16385-u13",
859
+ "preset": "stress",
860
+ "attrs": { "axis": 1, "sorted": 1 },
861
+ "inputs": {
862
+ "x": {
863
+ "dtype": "float32",
864
+ "shape": [1, 16385, 1],
865
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
866
+ }
867
+ },
868
+ "outputs": {
869
+ "y": { "dtype": "float32", "shape": [1, 13, 1] },
870
+ "indices": { "dtype": "uint32", "shape": [13] },
871
+ "counts": { "dtype": "uint32", "shape": [13] }
872
+ }
873
+ },
874
+ {
875
+ "name": "scalar-f32-metadata-mask6-sorted0-n16385-u13",
876
+ "preset": "stress",
877
+ "attrs": { "axis": 1, "sorted": 0 },
878
+ "inputs": {
879
+ "x": {
880
+ "dtype": "float32",
881
+ "shape": [1, 16385, 1],
882
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
883
+ }
884
+ },
885
+ "outputs": {
886
+ "y": { "dtype": "float32", "shape": [1, 13, 1] },
887
+ "inverse_indices": { "dtype": "uint32", "shape": [16385] },
888
+ "counts": { "dtype": "uint32", "shape": [13] }
889
+ }
890
+ },
891
+ {
892
+ "name": "scalar-f32-metadata-mask6-sorted1-n16385-u13",
893
+ "preset": "stress",
894
+ "attrs": { "axis": 1, "sorted": 1 },
895
+ "inputs": {
896
+ "x": {
897
+ "dtype": "float32",
898
+ "shape": [1, 16385, 1],
899
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
900
+ }
901
+ },
902
+ "outputs": {
903
+ "y": { "dtype": "float32", "shape": [1, 13, 1] },
904
+ "inverse_indices": { "dtype": "uint32", "shape": [16385] },
905
+ "counts": { "dtype": "uint32", "shape": [13] }
906
+ }
907
+ },
908
+ {
909
+ "name": "scalar-f32-metadata-mask7-sorted0-n16385-u13",
910
+ "preset": "stress",
911
+ "attrs": { "axis": 1, "sorted": 0 },
912
+ "inputs": {
913
+ "x": {
914
+ "dtype": "float32",
915
+ "shape": [1, 16385, 1],
916
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
917
+ }
918
+ },
919
+ "outputs": {
920
+ "y": { "dtype": "float32", "shape": [1, 13, 1] },
921
+ "indices": { "dtype": "uint32", "shape": [13] },
922
+ "inverse_indices": { "dtype": "uint32", "shape": [16385] },
923
+ "counts": { "dtype": "uint32", "shape": [13] }
924
+ }
925
+ },
926
+ {
927
+ "name": "scalar-f32-metadata-mask7-sorted1-n16385-u13",
928
+ "preset": "stress",
929
+ "attrs": { "axis": 1, "sorted": 1 },
930
+ "inputs": {
931
+ "x": {
932
+ "dtype": "float32",
933
+ "shape": [1, 16385, 1],
934
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0, 11.0, 0.0, 4.0, 6.0, 8.0, 9.0, 10.0, 12.0] }
935
+ }
936
+ },
937
+ "outputs": {
938
+ "y": { "dtype": "float32", "shape": [1, 13, 1] },
939
+ "indices": { "dtype": "uint32", "shape": [13] },
940
+ "inverse_indices": { "dtype": "uint32", "shape": [16385] },
941
+ "counts": { "dtype": "uint32", "shape": [13] }
942
+ }
943
+ },
944
+ {
945
+ "name": "single-class-f32-n65537-mask0",
946
+ "preset": "stress",
947
+ "inputs": { "x": { "dtype": "float32", "shape": [65537], "data": { "kind": "constant", "value": 3.0 } } },
948
+ "outputs": { "y": { "dtype": "float32", "shape": [1] } }
949
+ },
950
+ {
951
+ "name": "single-class-f32-n65537-mask2",
952
+ "preset": "stress",
953
+ "inputs": { "x": { "dtype": "float32", "shape": [65537], "data": { "kind": "constant", "value": 3.0 } } },
954
+ "outputs": {
955
+ "y": { "dtype": "float32", "shape": [1] },
956
+ "inverse_indices": { "dtype": "uint32", "shape": [65537] }
957
+ }
958
+ },
959
+ {
960
+ "name": "single-class-f32-n65537-mask4",
961
+ "preset": "stress",
962
+ "inputs": { "x": { "dtype": "float32", "shape": [65537], "data": { "kind": "constant", "value": 3.0 } } },
963
+ "outputs": { "y": { "dtype": "float32", "shape": [1] }, "counts": { "dtype": "uint32", "shape": [1] } }
964
+ },
965
+ {
966
+ "name": "single-class-f32-n65537-mask7",
967
+ "preset": "stress",
968
+ "inputs": { "x": { "dtype": "float32", "shape": [65537], "data": { "kind": "constant", "value": 3.0 } } },
969
+ "outputs": {
970
+ "y": { "dtype": "float32", "shape": [1] },
971
+ "indices": { "dtype": "uint32", "shape": [1] },
972
+ "inverse_indices": { "dtype": "uint32", "shape": [65537] },
973
+ "counts": { "dtype": "uint32", "shape": [1] }
974
+ }
975
+ },
976
+ {
977
+ "name": "single-class-axis1-f32-outer3-n4097-inner5-all-metadata",
978
+ "preset": "stress",
979
+ "attrs": { "axis": 1, "sorted": 1 },
980
+ "inputs": {
981
+ "x": {
982
+ "dtype": "float32",
983
+ "shape": [3, 4097, 5],
984
+ "data": { "kind": "cycle", "values": [5.0, 2.0, 7.0, 1.0, 3.0] }
985
+ }
986
+ },
987
+ "outputs": {
988
+ "y": { "dtype": "float32", "shape": [3, 1, 5] },
989
+ "indices": { "dtype": "uint32", "shape": [1] },
990
+ "inverse_indices": { "dtype": "uint32", "shape": [4097] },
991
+ "counts": { "dtype": "uint32", "shape": [1] }
992
+ }
993
+ },
994
+ {
995
+ "name": "flat-global-crossover-i32-n2048-u2048-sorted0",
996
+ "preset": "stress",
997
+ "attrs": { "sorted": 0 },
998
+ "inputs": { "x": { "dtype": "int32", "shape": [2048], "dist": "linearMod", "mod": 2048 } },
999
+ "outputs": { "y": { "dtype": "int32", "shape": [2048] } },
1000
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1001
+ },
1002
+ {
1003
+ "name": "flat-global-crossover-i32-n2048-u2048-sorted1",
1004
+ "preset": "stress",
1005
+ "attrs": { "sorted": 1 },
1006
+ "inputs": { "x": { "dtype": "int32", "shape": [2048], "dist": "linearMod", "mod": 2048 } },
1007
+ "outputs": { "y": { "dtype": "int32", "shape": [2048] } },
1008
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1009
+ },
1010
+ {
1011
+ "name": "flat-global-crossover-i32-n2049-u2049-sorted0",
1012
+ "preset": "stress",
1013
+ "attrs": { "sorted": 0 },
1014
+ "inputs": { "x": { "dtype": "int32", "shape": [2049], "dist": "linearMod", "mod": 2049 } },
1015
+ "outputs": { "y": { "dtype": "int32", "shape": [2049] } },
1016
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1017
+ },
1018
+ {
1019
+ "name": "flat-global-crossover-i32-n2049-u2049-sorted1",
1020
+ "preset": "stress",
1021
+ "attrs": { "sorted": 1 },
1022
+ "inputs": { "x": { "dtype": "int32", "shape": [2049], "dist": "linearMod", "mod": 2049 } },
1023
+ "outputs": { "y": { "dtype": "int32", "shape": [2049] } },
1024
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1025
+ },
1026
+ {
1027
+ "name": "flat-global-crossover-i32-n4097-u4097-sorted0",
1028
+ "preset": "stress",
1029
+ "attrs": { "sorted": 0 },
1030
+ "inputs": { "x": { "dtype": "int32", "shape": [4097], "dist": "linearMod", "mod": 4097 } },
1031
+ "outputs": { "y": { "dtype": "int32", "shape": [4097] } },
1032
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1033
+ },
1034
+ {
1035
+ "name": "flat-global-crossover-i32-n4097-u4097-sorted1",
1036
+ "preset": "stress",
1037
+ "attrs": { "sorted": 1 },
1038
+ "inputs": { "x": { "dtype": "int32", "shape": [4097], "dist": "linearMod", "mod": 4097 } },
1039
+ "outputs": { "y": { "dtype": "int32", "shape": [4097] } },
1040
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1041
+ },
1042
+ {
1043
+ "name": "flat-global-crossover-i32-n8193-u8193-sorted0",
1044
+ "preset": "stress",
1045
+ "attrs": { "sorted": 0 },
1046
+ "inputs": { "x": { "dtype": "int32", "shape": [8193], "dist": "linearMod", "mod": 8193 } },
1047
+ "outputs": { "y": { "dtype": "int32", "shape": [8193] } },
1048
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1049
+ },
1050
+ {
1051
+ "name": "flat-global-crossover-i32-n8193-u8193-sorted1",
1052
+ "preset": "stress",
1053
+ "attrs": { "sorted": 1 },
1054
+ "inputs": { "x": { "dtype": "int32", "shape": [8193], "dist": "linearMod", "mod": 8193 } },
1055
+ "outputs": { "y": { "dtype": "int32", "shape": [8193] } },
1056
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1057
+ },
1058
+ {
1059
+ "name": "flat-global-crossover-int32-n4097-u2049-sorted0",
1060
+ "preset": "stress",
1061
+ "attrs": { "sorted": 0 },
1062
+ "inputs": {
1063
+ "x": {
1064
+ "dtype": "int32",
1065
+ "shape": [4097],
1066
+ "data": {
1067
+ "kind": "cycle",
1068
+ "values": { "$ref": "#/fixtureArrays/axis-scalar-f32-inverse-hybrid-floor_input_x" }
1069
+ }
1070
+ }
1071
+ },
1072
+ "outputs": { "y": { "dtype": "int32", "shape": [2049] } }
1073
+ },
1074
+ {
1075
+ "name": "flat-global-crossover-int32-n4097-u2049-sorted1",
1076
+ "preset": "stress",
1077
+ "attrs": { "sorted": 1 },
1078
+ "inputs": {
1079
+ "x": {
1080
+ "dtype": "int32",
1081
+ "shape": [4097],
1082
+ "data": {
1083
+ "kind": "cycle",
1084
+ "values": { "$ref": "#/fixtureArrays/axis-scalar-f32-inverse-hybrid-floor_input_x" }
1085
+ }
1086
+ }
1087
+ },
1088
+ "outputs": { "y": { "dtype": "int32", "shape": [2049] } }
1089
+ },
1090
+ {
1091
+ "name": "flat-global-crossover-uint32-n8193-u2049-sorted0",
1092
+ "preset": "stress",
1093
+ "attrs": { "sorted": 0 },
1094
+ "inputs": {
1095
+ "x": {
1096
+ "dtype": "uint32",
1097
+ "shape": [8193],
1098
+ "data": {
1099
+ "kind": "cycle",
1100
+ "values": { "$ref": "#/fixtureArrays/axis-scalar-f32-inverse-hybrid-floor_input_x" }
1101
+ }
1102
+ }
1103
+ },
1104
+ "outputs": { "y": { "dtype": "uint32", "shape": [2049] } }
1105
+ },
1106
+ {
1107
+ "name": "flat-global-crossover-uint32-n8193-u2049-sorted1",
1108
+ "preset": "stress",
1109
+ "attrs": { "sorted": 1 },
1110
+ "inputs": {
1111
+ "x": {
1112
+ "dtype": "uint32",
1113
+ "shape": [8193],
1114
+ "data": {
1115
+ "kind": "cycle",
1116
+ "values": { "$ref": "#/fixtureArrays/axis-scalar-f32-inverse-hybrid-floor_input_x" }
1117
+ }
1118
+ }
1119
+ },
1120
+ "outputs": { "y": { "dtype": "uint32", "shape": [2049] } }
1121
+ },
1122
+ {
1123
+ "name": "output-span-int32-n257-sorted0",
1124
+ "preset": "stress",
1125
+ "attrs": { "sorted": 0 },
1126
+ "inputs": { "x": { "dtype": "int32", "shape": [257], "dist": "linearMod", "mod": 257 } },
1127
+ "outputs": { "y": { "dtype": "int32", "shape": [257] } },
1128
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1129
+ },
1130
+ {
1131
+ "name": "output-span-int32-n257-sorted1",
1132
+ "preset": "stress",
1133
+ "attrs": { "sorted": 1 },
1134
+ "inputs": { "x": { "dtype": "int32", "shape": [257], "dist": "linearMod", "mod": 257 } },
1135
+ "outputs": { "y": { "dtype": "int32", "shape": [257] } },
1136
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1137
+ },
1138
+ {
1139
+ "name": "output-span-float32-n257-sorted0",
1140
+ "preset": "stress",
1141
+ "attrs": { "sorted": 0 },
1142
+ "inputs": { "x": { "dtype": "float32", "shape": [257], "dist": "linearMod", "mod": 257 } },
1143
+ "outputs": { "y": { "dtype": "float32", "shape": [257] } },
1144
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1145
+ },
1146
+ {
1147
+ "name": "output-span-float32-n257-sorted1",
1148
+ "preset": "stress",
1149
+ "attrs": { "sorted": 1 },
1150
+ "inputs": { "x": { "dtype": "float32", "shape": [257], "dist": "linearMod", "mod": 257 } },
1151
+ "outputs": { "y": { "dtype": "float32", "shape": [257] } },
1152
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1153
+ },
1154
+ {
1155
+ "name": "output-span-int32-n1025-sorted0",
1156
+ "preset": "stress",
1157
+ "attrs": { "sorted": 0 },
1158
+ "inputs": { "x": { "dtype": "int32", "shape": [1025], "dist": "linearMod", "mod": 1025 } },
1159
+ "outputs": { "y": { "dtype": "int32", "shape": [1025] } },
1160
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1161
+ },
1162
+ {
1163
+ "name": "output-span-int32-n1025-sorted1",
1164
+ "preset": "stress",
1165
+ "attrs": { "sorted": 1 },
1166
+ "inputs": { "x": { "dtype": "int32", "shape": [1025], "dist": "linearMod", "mod": 1025 } },
1167
+ "outputs": { "y": { "dtype": "int32", "shape": [1025] } },
1168
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1169
+ },
1170
+ {
1171
+ "name": "output-span-float32-n1025-sorted0",
1172
+ "preset": "stress",
1173
+ "attrs": { "sorted": 0 },
1174
+ "inputs": { "x": { "dtype": "float32", "shape": [1025], "dist": "linearMod", "mod": 1025 } },
1175
+ "outputs": { "y": { "dtype": "float32", "shape": [1025] } },
1176
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1177
+ },
1178
+ {
1179
+ "name": "output-span-float32-n1025-sorted1",
1180
+ "preset": "stress",
1181
+ "attrs": { "sorted": 1 },
1182
+ "inputs": { "x": { "dtype": "float32", "shape": [1025], "dist": "linearMod", "mod": 1025 } },
1183
+ "outputs": { "y": { "dtype": "float32", "shape": [1025] } },
1184
+ "bench": { "metrics": [{ "type": "bandwidth", "value": "4 * (numel(shapes.x) + numel(shapes.y))" }] }
1185
+ },
1186
+ {
1187
+ "name": "float_hash_work_float32_n15_u15_sorted0",
1188
+ "preset": "stress",
1189
+ "attrs": { "sorted": 0 },
1190
+ "inputs": { "x": { "dtype": "float32", "shape": [15], "dist": "linearMod", "mod": 15 } },
1191
+ "outputs": { "y": { "dtype": "float32", "shape": [15] } },
1192
+ "bench": {
1193
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1194
+ },
1195
+ "vars": { "dtype": "float32" }
1196
+ },
1197
+ {
1198
+ "name": "float_hash_work_float32_n15_u15_sorted1",
1199
+ "preset": "stress",
1200
+ "attrs": { "sorted": 1 },
1201
+ "inputs": { "x": { "dtype": "float32", "shape": [15], "dist": "linearMod", "mod": 15 } },
1202
+ "outputs": { "y": { "dtype": "float32", "shape": [15] } },
1203
+ "bench": {
1204
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1205
+ },
1206
+ "vars": { "dtype": "float32" }
1207
+ },
1208
+ {
1209
+ "name": "float_hash_work_float32_n16_u16_sorted0",
1210
+ "preset": "stress",
1211
+ "attrs": { "sorted": 0 },
1212
+ "inputs": { "x": { "dtype": "float32", "shape": [16], "dist": "linearMod", "mod": 16 } },
1213
+ "outputs": { "y": { "dtype": "float32", "shape": [16] } },
1214
+ "bench": {
1215
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1216
+ },
1217
+ "vars": { "dtype": "float32" }
1218
+ },
1219
+ {
1220
+ "name": "float_hash_work_float32_n16_u16_sorted1",
1221
+ "preset": "stress",
1222
+ "attrs": { "sorted": 1 },
1223
+ "inputs": { "x": { "dtype": "float32", "shape": [16], "dist": "linearMod", "mod": 16 } },
1224
+ "outputs": { "y": { "dtype": "float32", "shape": [16] } },
1225
+ "bench": {
1226
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1227
+ },
1228
+ "vars": { "dtype": "float32" }
1229
+ },
1230
+ {
1231
+ "name": "float_hash_work_float32_n32_u8_sorted0",
1232
+ "preset": "stress",
1233
+ "attrs": { "sorted": 0 },
1234
+ "inputs": { "x": { "dtype": "float32", "shape": [32], "dist": "linearMod", "mod": 8 } },
1235
+ "outputs": { "y": { "dtype": "float32", "shape": [8] } },
1236
+ "bench": {
1237
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1238
+ },
1239
+ "vars": { "dtype": "float32" }
1240
+ },
1241
+ {
1242
+ "name": "float_hash_work_float32_n32_u8_sorted1",
1243
+ "preset": "stress",
1244
+ "attrs": { "sorted": 1 },
1245
+ "inputs": { "x": { "dtype": "float32", "shape": [32], "dist": "linearMod", "mod": 8 } },
1246
+ "outputs": { "y": { "dtype": "float32", "shape": [8] } },
1247
+ "bench": {
1248
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1249
+ },
1250
+ "vars": { "dtype": "float32" }
1251
+ },
1252
+ {
1253
+ "name": "float_hash_work_float32_n64_u64_sorted0",
1254
+ "preset": "stress",
1255
+ "attrs": { "sorted": 0 },
1256
+ "inputs": { "x": { "dtype": "float32", "shape": [64], "dist": "linearMod", "mod": 64 } },
1257
+ "outputs": { "y": { "dtype": "float32", "shape": [64] } },
1258
+ "bench": {
1259
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1260
+ },
1261
+ "vars": { "dtype": "float32" }
1262
+ },
1263
+ {
1264
+ "name": "float_hash_work_float32_n64_u64_sorted1",
1265
+ "preset": "stress",
1266
+ "attrs": { "sorted": 1 },
1267
+ "inputs": { "x": { "dtype": "float32", "shape": [64], "dist": "linearMod", "mod": 64 } },
1268
+ "outputs": { "y": { "dtype": "float32", "shape": [64] } },
1269
+ "bench": {
1270
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1271
+ },
1272
+ "vars": { "dtype": "float32" }
1273
+ },
1274
+ {
1275
+ "name": "float_hash_work_float32_n128_u8_sorted0",
1276
+ "preset": "stress",
1277
+ "attrs": { "sorted": 0 },
1278
+ "inputs": { "x": { "dtype": "float32", "shape": [128], "dist": "linearMod", "mod": 8 } },
1279
+ "outputs": { "y": { "dtype": "float32", "shape": [8] } },
1280
+ "bench": {
1281
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1282
+ },
1283
+ "vars": { "dtype": "float32" }
1284
+ },
1285
+ {
1286
+ "name": "float_hash_work_float32_n128_u8_sorted1",
1287
+ "preset": "stress",
1288
+ "attrs": { "sorted": 1 },
1289
+ "inputs": { "x": { "dtype": "float32", "shape": [128], "dist": "linearMod", "mod": 8 } },
1290
+ "outputs": { "y": { "dtype": "float32", "shape": [8] } },
1291
+ "bench": {
1292
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1293
+ },
1294
+ "vars": { "dtype": "float32" }
1295
+ },
1296
+ {
1297
+ "name": "float_hash_work_float32_n512_u512_sorted0",
1298
+ "preset": "stress",
1299
+ "attrs": { "sorted": 0 },
1300
+ "inputs": { "x": { "dtype": "float32", "shape": [512], "dist": "linearMod", "mod": 512 } },
1301
+ "outputs": { "y": { "dtype": "float32", "shape": [512] } },
1302
+ "bench": {
1303
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1304
+ },
1305
+ "vars": { "dtype": "float32" }
1306
+ },
1307
+ {
1308
+ "name": "float_hash_work_float32_n512_u512_sorted1",
1309
+ "preset": "stress",
1310
+ "attrs": { "sorted": 1 },
1311
+ "inputs": { "x": { "dtype": "float32", "shape": [512], "dist": "linearMod", "mod": 512 } },
1312
+ "outputs": { "y": { "dtype": "float32", "shape": [512] } },
1313
+ "bench": {
1314
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1315
+ },
1316
+ "vars": { "dtype": "float32" }
1317
+ },
1318
+ {
1319
+ "name": "float_hash_work_float16_n15_u15_sorted0",
1320
+ "preset": "stress",
1321
+ "attrs": { "sorted": 0 },
1322
+ "inputs": { "x": { "dtype": "float16", "shape": [15], "dist": "linearMod", "mod": 15 } },
1323
+ "outputs": { "y": { "dtype": "float16", "shape": [15] } },
1324
+ "bench": {
1325
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1326
+ },
1327
+ "vars": { "dtype": "float16" }
1328
+ },
1329
+ {
1330
+ "name": "float_hash_work_float16_n15_u15_sorted1",
1331
+ "preset": "stress",
1332
+ "attrs": { "sorted": 1 },
1333
+ "inputs": { "x": { "dtype": "float16", "shape": [15], "dist": "linearMod", "mod": 15 } },
1334
+ "outputs": { "y": { "dtype": "float16", "shape": [15] } },
1335
+ "bench": {
1336
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1337
+ },
1338
+ "vars": { "dtype": "float16" }
1339
+ },
1340
+ {
1341
+ "name": "float_hash_work_float16_n16_u16_sorted0",
1342
+ "preset": "stress",
1343
+ "attrs": { "sorted": 0 },
1344
+ "inputs": { "x": { "dtype": "float16", "shape": [16], "dist": "linearMod", "mod": 16 } },
1345
+ "outputs": { "y": { "dtype": "float16", "shape": [16] } },
1346
+ "bench": {
1347
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1348
+ },
1349
+ "vars": { "dtype": "float16" }
1350
+ },
1351
+ {
1352
+ "name": "float_hash_work_float16_n16_u16_sorted1",
1353
+ "preset": "stress",
1354
+ "attrs": { "sorted": 1 },
1355
+ "inputs": { "x": { "dtype": "float16", "shape": [16], "dist": "linearMod", "mod": 16 } },
1356
+ "outputs": { "y": { "dtype": "float16", "shape": [16] } },
1357
+ "bench": {
1358
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1359
+ },
1360
+ "vars": { "dtype": "float16" }
1361
+ },
1362
+ {
1363
+ "name": "float_hash_work_float16_n32_u8_sorted0",
1364
+ "preset": "stress",
1365
+ "attrs": { "sorted": 0 },
1366
+ "inputs": { "x": { "dtype": "float16", "shape": [32], "dist": "linearMod", "mod": 8 } },
1367
+ "outputs": { "y": { "dtype": "float16", "shape": [8] } },
1368
+ "bench": {
1369
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1370
+ },
1371
+ "vars": { "dtype": "float16" }
1372
+ },
1373
+ {
1374
+ "name": "float_hash_work_float16_n32_u8_sorted1",
1375
+ "preset": "stress",
1376
+ "attrs": { "sorted": 1 },
1377
+ "inputs": { "x": { "dtype": "float16", "shape": [32], "dist": "linearMod", "mod": 8 } },
1378
+ "outputs": { "y": { "dtype": "float16", "shape": [8] } },
1379
+ "bench": {
1380
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1381
+ },
1382
+ "vars": { "dtype": "float16" }
1383
+ },
1384
+ {
1385
+ "name": "float_hash_work_float16_n64_u64_sorted0",
1386
+ "preset": "stress",
1387
+ "attrs": { "sorted": 0 },
1388
+ "inputs": { "x": { "dtype": "float16", "shape": [64], "dist": "linearMod", "mod": 64 } },
1389
+ "outputs": { "y": { "dtype": "float16", "shape": [64] } },
1390
+ "bench": {
1391
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1392
+ },
1393
+ "vars": { "dtype": "float16" }
1394
+ },
1395
+ {
1396
+ "name": "float_hash_work_float16_n64_u64_sorted1",
1397
+ "preset": "stress",
1398
+ "attrs": { "sorted": 1 },
1399
+ "inputs": { "x": { "dtype": "float16", "shape": [64], "dist": "linearMod", "mod": 64 } },
1400
+ "outputs": { "y": { "dtype": "float16", "shape": [64] } },
1401
+ "bench": {
1402
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1403
+ },
1404
+ "vars": { "dtype": "float16" }
1405
+ },
1406
+ {
1407
+ "name": "float_hash_work_float16_n128_u8_sorted0",
1408
+ "preset": "stress",
1409
+ "attrs": { "sorted": 0 },
1410
+ "inputs": { "x": { "dtype": "float16", "shape": [128], "dist": "linearMod", "mod": 8 } },
1411
+ "outputs": { "y": { "dtype": "float16", "shape": [8] } },
1412
+ "bench": {
1413
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1414
+ },
1415
+ "vars": { "dtype": "float16" }
1416
+ },
1417
+ {
1418
+ "name": "float_hash_work_float16_n128_u8_sorted1",
1419
+ "preset": "stress",
1420
+ "attrs": { "sorted": 1 },
1421
+ "inputs": { "x": { "dtype": "float16", "shape": [128], "dist": "linearMod", "mod": 8 } },
1422
+ "outputs": { "y": { "dtype": "float16", "shape": [8] } },
1423
+ "bench": {
1424
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1425
+ },
1426
+ "vars": { "dtype": "float16" }
1427
+ },
1428
+ {
1429
+ "name": "float_hash_work_float16_n512_u512_sorted0",
1430
+ "preset": "stress",
1431
+ "attrs": { "sorted": 0 },
1432
+ "inputs": { "x": { "dtype": "float16", "shape": [512], "dist": "linearMod", "mod": 512 } },
1433
+ "outputs": { "y": { "dtype": "float16", "shape": [512] } },
1434
+ "bench": {
1435
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1436
+ },
1437
+ "vars": { "dtype": "float16" }
1438
+ },
1439
+ {
1440
+ "name": "float_hash_work_float16_n512_u512_sorted1",
1441
+ "preset": "stress",
1442
+ "attrs": { "sorted": 1 },
1443
+ "inputs": { "x": { "dtype": "float16", "shape": [512], "dist": "linearMod", "mod": 512 } },
1444
+ "outputs": { "y": { "dtype": "float16", "shape": [512] } },
1445
+ "bench": {
1446
+ "metrics": [{ "type": "bandwidth", "value": "dtypeBytes(args.dtype) * (numel(shapes.x) + numel(shapes.y))" }]
1447
+ },
1448
+ "vars": { "dtype": "float16" }
1449
  }
1450
+ ],
1451
+ "fixtureArrays": {
1452
+ "axis_record_table_marker_collision": [7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 0, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 1, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 2, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 3, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 4, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 5, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 6, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 7, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 8, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 9, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 10, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 11, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 12, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 13, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 14, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 15, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 16, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 17, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 18, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 19, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 20, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 21, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 22, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 23, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 24, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 25, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 26, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 27, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 28, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 29, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 30, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 31, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 32, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 33, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 34, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 35, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 36, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 37, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 38, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 39, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 40, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 41, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 42, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 43, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 44, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 45, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 46, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 47, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 48, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 49, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 50, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 51, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 52, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 53, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 54, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 55, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 56, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 57, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 58, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 59, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 60, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 61, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 62, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 63, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 64, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 65, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 66, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 67, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 68, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 69, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 70, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 71, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 72, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 73, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 74, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 75, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 76, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 77, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 78, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 79, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 80, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 81, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 82, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 83, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 84, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 85, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 86, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 87, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 88, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 89, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 90, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 91, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 92, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 93, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 94, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 95, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 96, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 97, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 98, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 99, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 100, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 101, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 102, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 103, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 104, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 105, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 106, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 107, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 108, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 109, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 110, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 111, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 112, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 113, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 114, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 115, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 116, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 117, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 118, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 119, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 120, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 121, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 122, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 123, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 124, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 125, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 126, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 127, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 128, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 129, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 130, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 131, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 132, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 133, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 134, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 135, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 136, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 137, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 138, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 139, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 140, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 141, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 142, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 143, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 144, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 145, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 146, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 147, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 148, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 149, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 150, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 151, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 152, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 153, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 154, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 155, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 156, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 157, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 158, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 159, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 160, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 161, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 162, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 163, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 164, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 165, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 166, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 167, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 168, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 169, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 170, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 171, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 172, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 173, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 174, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 175, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 176, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 177, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 178, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 179, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 180, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 181, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 182, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 183, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 184, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 185, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 186, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 187, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 188, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 189, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 190, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 191, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 192, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 193, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 194, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 195, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 196, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 197, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 198, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 199, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 200, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 201, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 202, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 203, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 204, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 205, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 206, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 207, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 208, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 209, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 210, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 211, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 212, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 213, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 214, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 215, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 216, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 217, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 218, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 219, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 220, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 221, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 222, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 223, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 224, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 225, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 226, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 227, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 228, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 229, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 230, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 231, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 232, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 233, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 234, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 235, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 236, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 237, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 238, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 239, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 240, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 241, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 242, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 243, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 244, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 245, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 246, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 247, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 248, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 249, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 250, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 251, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 252, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 253, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 254, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 255, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 256, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 257, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 258, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 259, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 260, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 261, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 262, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 263, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 264, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 265, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 266, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 267, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 268, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 269, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 270, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 271, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 272, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 273, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 274, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 275, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 276, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 277, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 278, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 279, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 280, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 281, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 282, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 283, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 284, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 285, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 286, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 287, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 288, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 289, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 290, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 291, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 292, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 293, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 294, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 295, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 296, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 297, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 298, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 299, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 300, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 301, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 302, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 303, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 304, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 305, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 306, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 307, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 308, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 309, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 310, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 311, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 312, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 313, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 314, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 315, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 316, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 317, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 318, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 319, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 320, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 321, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 322, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 323, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 324, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 325, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 326, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 327, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 328, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 329, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 330, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 331, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 332, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 333, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 334, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 335, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 336, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 337, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 338, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 339, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 340, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 341, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 342, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 343, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 344, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 345, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 346, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 347, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 348, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 349, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 350, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 351, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 352, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 353, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 354, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 355, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 356, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 357, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 358, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 359, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 360, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 361, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 362, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 363, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 364, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 365, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 366, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 367, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 368, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 369, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 370, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 371, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 372, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 373, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 374, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 375, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 376, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 377, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 378, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 379, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 380, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 381, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 382, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 383, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 384, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 385, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 386, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 387, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 388, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 389, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 390, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 391, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 392, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 393, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 394, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 395, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 396, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 397, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 398, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 399, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 400, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 401, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 402, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 403, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 404, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 405, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 406, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 407, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 408, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 409, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 410, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 411, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 412, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 413, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 414, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 415, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 416, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 417, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 418, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 419, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 420, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 421, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 422, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 423, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 424, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 425, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 426, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 427, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 428, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 429, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 430, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 431, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 432, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 433, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 434, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 435, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 436, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 437, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 438, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 439, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 440, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 441, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 442, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 443, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 444, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 445, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 446, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 447, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 448, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 449, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 450, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 451, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 452, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 453, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 454, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 455, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 456, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 457, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 458, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 459, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 460, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 461, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 462, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 463, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 464, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 465, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 466, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 467, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 468, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 469, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 470, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 471, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 472, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 473, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 474, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 475, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 476, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 477, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 478, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 479, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 480, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 481, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 482, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 483, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 484, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 485, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 486, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 487, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 488, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 489, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 490, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 491, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 492, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 493, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 494, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 495, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 496, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 497, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 498, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 499, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 500, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 501, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 502, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 503, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 504, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 505, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 506, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 507, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 508, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 509, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 510, 17, 7, 7, 7, 7, 7, 11, 7, 7, 7, 7, 13, 7, 7, 7, 511, 17],
1453
+ "axis-scalar-f32-inverse-duplicate64_input_x": [63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
1454
+ "axis-scalar-f32-inverse-hybrid-floor_input_x": [2048, 2047, 2046, 2045, 2044, 2043, 2042, 2041, 2040, 2039, 2038, 2037, 2036, 2035, 2034, 2033, 2032, 2031, 2030, 2029, 2028, 2027, 2026, 2025, 2024, 2023, 2022, 2021, 2020, 2019, 2018, 2017, 2016, 2015, 2014, 2013, 2012, 2011, 2010, 2009, 2008, 2007, 2006, 2005, 2004, 2003, 2002, 2001, 2000, 1999, 1998, 1997, 1996, 1995, 1994, 1993, 1992, 1991, 1990, 1989, 1988, 1987, 1986, 1985, 1984, 1983, 1982, 1981, 1980, 1979, 1978, 1977, 1976, 1975, 1974, 1973, 1972, 1971, 1970, 1969, 1968, 1967, 1966, 1965, 1964, 1963, 1962, 1961, 1960, 1959, 1958, 1957, 1956, 1955, 1954, 1953, 1952, 1951, 1950, 1949, 1948, 1947, 1946, 1945, 1944, 1943, 1942, 1941, 1940, 1939, 1938, 1937, 1936, 1935, 1934, 1933, 1932, 1931, 1930, 1929, 1928, 1927, 1926, 1925, 1924, 1923, 1922, 1921, 1920, 1919, 1918, 1917, 1916, 1915, 1914, 1913, 1912, 1911, 1910, 1909, 1908, 1907, 1906, 1905, 1904, 1903, 1902, 1901, 1900, 1899, 1898, 1897, 1896, 1895, 1894, 1893, 1892, 1891, 1890, 1889, 1888, 1887, 1886, 1885, 1884, 1883, 1882, 1881, 1880, 1879, 1878, 1877, 1876, 1875, 1874, 1873, 1872, 1871, 1870, 1869, 1868, 1867, 1866, 1865, 1864, 1863, 1862, 1861, 1860, 1859, 1858, 1857, 1856, 1855, 1854, 1853, 1852, 1851, 1850, 1849, 1848, 1847, 1846, 1845, 1844, 1843, 1842, 1841, 1840, 1839, 1838, 1837, 1836, 1835, 1834, 1833, 1832, 1831, 1830, 1829, 1828, 1827, 1826, 1825, 1824, 1823, 1822, 1821, 1820, 1819, 1818, 1817, 1816, 1815, 1814, 1813, 1812, 1811, 1810, 1809, 1808, 1807, 1806, 1805, 1804, 1803, 1802, 1801, 1800, 1799, 1798, 1797, 1796, 1795, 1794, 1793, 1792, 1791, 1790, 1789, 1788, 1787, 1786, 1785, 1784, 1783, 1782, 1781, 1780, 1779, 1778, 1777, 1776, 1775, 1774, 1773, 1772, 1771, 1770, 1769, 1768, 1767, 1766, 1765, 1764, 1763, 1762, 1761, 1760, 1759, 1758, 1757, 1756, 1755, 1754, 1753, 1752, 1751, 1750, 1749, 1748, 1747, 1746, 1745, 1744, 1743, 1742, 1741, 1740, 1739, 1738, 1737, 1736, 1735, 1734, 1733, 1732, 1731, 1730, 1729, 1728, 1727, 1726, 1725, 1724, 1723, 1722, 1721, 1720, 1719, 1718, 1717, 1716, 1715, 1714, 1713, 1712, 1711, 1710, 1709, 1708, 1707, 1706, 1705, 1704, 1703, 1702, 1701, 1700, 1699, 1698, 1697, 1696, 1695, 1694, 1693, 1692, 1691, 1690, 1689, 1688, 1687, 1686, 1685, 1684, 1683, 1682, 1681, 1680, 1679, 1678, 1677, 1676, 1675, 1674, 1673, 1672, 1671, 1670, 1669, 1668, 1667, 1666, 1665, 1664, 1663, 1662, 1661, 1660, 1659, 1658, 1657, 1656, 1655, 1654, 1653, 1652, 1651, 1650, 1649, 1648, 1647, 1646, 1645, 1644, 1643, 1642, 1641, 1640, 1639, 1638, 1637, 1636, 1635, 1634, 1633, 1632, 1631, 1630, 1629, 1628, 1627, 1626, 1625, 1624, 1623, 1622, 1621, 1620, 1619, 1618, 1617, 1616, 1615, 1614, 1613, 1612, 1611, 1610, 1609, 1608, 1607, 1606, 1605, 1604, 1603, 1602, 1601, 1600, 1599, 1598, 1597, 1596, 1595, 1594, 1593, 1592, 1591, 1590, 1589, 1588, 1587, 1586, 1585, 1584, 1583, 1582, 1581, 1580, 1579, 1578, 1577, 1576, 1575, 1574, 1573, 1572, 1571, 1570, 1569, 1568, 1567, 1566, 1565, 1564, 1563, 1562, 1561, 1560, 1559, 1558, 1557, 1556, 1555, 1554, 1553, 1552, 1551, 1550, 1549, 1548, 1547, 1546, 1545, 1544, 1543, 1542, 1541, 1540, 1539, 1538, 1537, 1536, 1535, 1534, 1533, 1532, 1531, 1530, 1529, 1528, 1527, 1526, 1525, 1524, 1523, 1522, 1521, 1520, 1519, 1518, 1517, 1516, 1515, 1514, 1513, 1512, 1511, 1510, 1509, 1508, 1507, 1506, 1505, 1504, 1503, 1502, 1501, 1500, 1499, 1498, 1497, 1496, 1495, 1494, 1493, 1492, 1491, 1490, 1489, 1488, 1487, 1486, 1485, 1484, 1483, 1482, 1481, 1480, 1479, 1478, 1477, 1476, 1475, 1474, 1473, 1472, 1471, 1470, 1469, 1468, 1467, 1466, 1465, 1464, 1463, 1462, 1461, 1460, 1459, 1458, 1457, 1456, 1455, 1454, 1453, 1452, 1451, 1450, 1449, 1448, 1447, 1446, 1445, 1444, 1443, 1442, 1441, 1440, 1439, 1438, 1437, 1436, 1435, 1434, 1433, 1432, 1431, 1430, 1429, 1428, 1427, 1426, 1425, 1424, 1423, 1422, 1421, 1420, 1419, 1418, 1417, 1416, 1415, 1414, 1413, 1412, 1411, 1410, 1409, 1408, 1407, 1406, 1405, 1404, 1403, 1402, 1401, 1400, 1399, 1398, 1397, 1396, 1395, 1394, 1393, 1392, 1391, 1390, 1389, 1388, 1387, 1386, 1385, 1384, 1383, 1382, 1381, 1380, 1379, 1378, 1377, 1376, 1375, 1374, 1373, 1372, 1371, 1370, 1369, 1368, 1367, 1366, 1365, 1364, 1363, 1362, 1361, 1360, 1359, 1358, 1357, 1356, 1355, 1354, 1353, 1352, 1351, 1350, 1349, 1348, 1347, 1346, 1345, 1344, 1343, 1342, 1341, 1340, 1339, 1338, 1337, 1336, 1335, 1334, 1333, 1332, 1331, 1330, 1329, 1328, 1327, 1326, 1325, 1324, 1323, 1322, 1321, 1320, 1319, 1318, 1317, 1316, 1315, 1314, 1313, 1312, 1311, 1310, 1309, 1308, 1307, 1306, 1305, 1304, 1303, 1302, 1301, 1300, 1299, 1298, 1297, 1296, 1295, 1294, 1293, 1292, 1291, 1290, 1289, 1288, 1287, 1286, 1285, 1284, 1283, 1282, 1281, 1280, 1279, 1278, 1277, 1276, 1275, 1274, 1273, 1272, 1271, 1270, 1269, 1268, 1267, 1266, 1265, 1264, 1263, 1262, 1261, 1260, 1259, 1258, 1257, 1256, 1255, 1254, 1253, 1252, 1251, 1250, 1249, 1248, 1247, 1246, 1245, 1244, 1243, 1242, 1241, 1240, 1239, 1238, 1237, 1236, 1235, 1234, 1233, 1232, 1231, 1230, 1229, 1228, 1227, 1226, 1225, 1224, 1223, 1222, 1221, 1220, 1219, 1218, 1217, 1216, 1215, 1214, 1213, 1212, 1211, 1210, 1209, 1208, 1207, 1206, 1205, 1204, 1203, 1202, 1201, 1200, 1199, 1198, 1197, 1196, 1195, 1194, 1193, 1192, 1191, 1190, 1189, 1188, 1187, 1186, 1185, 1184, 1183, 1182, 1181, 1180, 1179, 1178, 1177, 1176, 1175, 1174, 1173, 1172, 1171, 1170, 1169, 1168, 1167, 1166, 1165, 1164, 1163, 1162, 1161, 1160, 1159, 1158, 1157, 1156, 1155, 1154, 1153, 1152, 1151, 1150, 1149, 1148, 1147, 1146, 1145, 1144, 1143, 1142, 1141, 1140, 1139, 1138, 1137, 1136, 1135, 1134, 1133, 1132, 1131, 1130, 1129, 1128, 1127, 1126, 1125, 1124, 1123, 1122, 1121, 1120, 1119, 1118, 1117, 1116, 1115, 1114, 1113, 1112, 1111, 1110, 1109, 1108, 1107, 1106, 1105, 1104, 1103, 1102, 1101, 1100, 1099, 1098, 1097, 1096, 1095, 1094, 1093, 1092, 1091, 1090, 1089, 1088, 1087, 1086, 1085, 1084, 1083, 1082, 1081, 1080, 1079, 1078, 1077, 1076, 1075, 1074, 1073, 1072, 1071, 1070, 1069, 1068, 1067, 1066, 1065, 1064, 1063, 1062, 1061, 1060, 1059, 1058, 1057, 1056, 1055, 1054, 1053, 1052, 1051, 1050, 1049, 1048, 1047, 1046, 1045, 1044, 1043, 1042, 1041, 1040, 1039, 1038, 1037, 1036, 1035, 1034, 1033, 1032, 1031, 1030, 1029, 1028, 1027, 1026, 1025, 1024, 1023, 1022, 1021, 1020, 1019, 1018, 1017, 1016, 1015, 1014, 1013, 1012, 1011, 1010, 1009, 1008, 1007, 1006, 1005, 1004, 1003, 1002, 1001, 1000, 999, 998, 997, 996, 995, 994, 993, 992, 991, 990, 989, 988, 987, 986, 985, 984, 983, 982, 981, 980, 979, 978, 977, 976, 975, 974, 973, 972, 971, 970, 969, 968, 967, 966, 965, 964, 963, 962, 961, 960, 959, 958, 957, 956, 955, 954, 953, 952, 951, 950, 949, 948, 947, 946, 945, 944, 943, 942, 941, 940, 939, 938, 937, 936, 935, 934, 933, 932, 931, 930, 929, 928, 927, 926, 925, 924, 923, 922, 921, 920, 919, 918, 917, 916, 915, 914, 913, 912, 911, 910, 909, 908, 907, 906, 905, 904, 903, 902, 901, 900, 899, 898, 897, 896, 895, 894, 893, 892, 891, 890, 889, 888, 887, 886, 885, 884, 883, 882, 881, 880, 879, 878, 877, 876, 875, 874, 873, 872, 871, 870, 869, 868, 867, 866, 865, 864, 863, 862, 861, 860, 859, 858, 857, 856, 855, 854, 853, 852, 851, 850, 849, 848, 847, 846, 845, 844, 843, 842, 841, 840, 839, 838, 837, 836, 835, 834, 833, 832, 831, 830, 829, 828, 827, 826, 825, 824, 823, 822, 821, 820, 819, 818, 817, 816, 815, 814, 813, 812, 811, 810, 809, 808, 807, 806, 805, 804, 803, 802, 801, 800, 799, 798, 797, 796, 795, 794, 793, 792, 791, 790, 789, 788, 787, 786, 785, 784, 783, 782, 781, 780, 779, 778, 777, 776, 775, 774, 773, 772, 771, 770, 769, 768, 767, 766, 765, 764, 763, 762, 761, 760, 759, 758, 757, 756, 755, 754, 753, 752, 751, 750, 749, 748, 747, 746, 745, 744, 743, 742, 741, 740, 739, 738, 737, 736, 735, 734, 733, 732, 731, 730, 729, 728, 727, 726, 725, 724, 723, 722, 721, 720, 719, 718, 717, 716, 715, 714, 713, 712, 711, 710, 709, 708, 707, 706, 705, 704, 703, 702, 701, 700, 699, 698, 697, 696, 695, 694, 693, 692, 691, 690, 689, 688, 687, 686, 685, 684, 683, 682, 681, 680, 679, 678, 677, 676, 675, 674, 673, 672, 671, 670, 669, 668, 667, 666, 665, 664, 663, 662, 661, 660, 659, 658, 657, 656, 655, 654, 653, 652, 651, 650, 649, 648, 647, 646, 645, 644, 643, 642, 641, 640, 639, 638, 637, 636, 635, 634, 633, 632, 631, 630, 629, 628, 627, 626, 625, 624, 623, 622, 621, 620, 619, 618, 617, 616, 615, 614, 613, 612, 611, 610, 609, 608, 607, 606, 605, 604, 603, 602, 601, 600, 599, 598, 597, 596, 595, 594, 593, 592, 591, 590, 589, 588, 587, 586, 585, 584, 583, 582, 581, 580, 579, 578, 577, 576, 575, 574, 573, 572, 571, 570, 569, 568, 567, 566, 565, 564, 563, 562, 561, 560, 559, 558, 557, 556, 555, 554, 553, 552, 551, 550, 549, 548, 547, 546, 545, 544, 543, 542, 541, 540, 539, 538, 537, 536, 535, 534, 533, 532, 531, 530, 529, 528, 527, 526, 525, 524, 523, 522, 521, 520, 519, 518, 517, 516, 515, 514, 513, 512, 511, 510, 509, 508, 507, 506, 505, 504, 503, 502, 501, 500, 499, 498, 497, 496, 495, 494, 493, 492, 491, 490, 489, 488, 487, 486, 485, 484, 483, 482, 481, 480, 479, 478, 477, 476, 475, 474, 473, 472, 471, 470, 469, 468, 467, 466, 465, 464, 463, 462, 461, 460, 459, 458, 457, 456, 455, 454, 453, 452, 451, 450, 449, 448, 447, 446, 445, 444, 443, 442, 441, 440, 439, 438, 437, 436, 435, 434, 433, 432, 431, 430, 429, 428, 427, 426, 425, 424, 423, 422, 421, 420, 419, 418, 417, 416, 415, 414, 413, 412, 411, 410, 409, 408, 407, 406, 405, 404, 403, 402, 401, 400, 399, 398, 397, 396, 395, 394, 393, 392, 391, 390, 389, 388, 387, 386, 385, 384, 383, 382, 381, 380, 379, 378, 377, 376, 375, 374, 373, 372, 371, 370, 369, 368, 367, 366, 365, 364, 363, 362, 361, 360, 359, 358, 357, 356, 355, 354, 353, 352, 351, 350, 349, 348, 347, 346, 345, 344, 343, 342, 341, 340, 339, 338, 337, 336, 335, 334, 333, 332, 331, 330, 329, 328, 327, 326, 325, 324, 323, 322, 321, 320, 319, 318, 317, 316, 315, 314, 313, 312, 311, 310, 309, 308, 307, 306, 305, 304, 303, 302, 301, 300, 299, 298, 297, 296, 295, 294, 293, 292, 291, 290, 289, 288, 287, 286, 285, 284, 283, 282, 281, 280, 279, 278, 277, 276, 275, 274, 273, 272, 271, 270, 269, 268, 267, 266, 265, 264, 263, 262, 261, 260, 259, 258, 257, 256, 255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, 239, 238, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224, 223, 222, 221, 220, 219, 218, 217, 216, 215, 214, 213, 212, 211, 210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190, 189, 188, 187, 186, 185, 184, 183, 182, 181, 180, 179, 178, 177, 176, 175, 174, 173, 172, 171, 170, 169, 168, 167, 166, 165, 164, 163, 162, 161, 160, 159, 158, 157, 156, 155, 154, 153, 152, 151, 150, 149, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 135, 134, 133, 132, 131, 130, 129, 128, 127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
1455
+ }
1456
  }
build/webgpu/manifest.json CHANGED
The diff for this file is too large to render. See raw diff
 
build/webgpu/metadata.json CHANGED
@@ -1,30 +1,116 @@
1
  {
2
  "name": "ai.onnx.Unique",
3
- "id": "_ai_onnx_unique_webgpu_6e781ae",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "B7+zElIduemQX6xUT7ASYxUA4g57rhMrU3Lt1A761IQ=",
11
- "manifest.json": "/vKIMvGvv1gB51KCCioQsOc43XoxZzBZir/AM+oP7qk=",
12
- "test.json": "GUvkml01vopw45if9L8HSq5uiIzpmkFLGUH8I3StJVQ=",
13
- "unique-axis-compact-sort.wgsl.jinja": "PJRMzhP+QgoHpmF7c9m2pQLOmHUL5aZTX7Ywhxqjrxs=",
14
- "unique-axis-dedup.wgsl.jinja": "O1EWypziZGw+vSmIZWyYo7IBihHTAEEYrUtUSdA+sk4=",
15
- "unique-axis-hash.wgsl.jinja": "Q8xwZ5T74OaJS9TpQURo3QPHXzpmMCA8ZbBfl/bucQA=",
16
- "unique-axis-scatter.wgsl.jinja": "Yjvj7qKzNzVJNKK8NeZ7ooDSetmEl218RIw7ftyt0lk=",
17
- "unique-axis.wgsl.jinja": "fjQxtH8MjP43bhdPOcxya+xVerIi30R4cgs7URqc71s=",
18
- "unique-compact-sort.wgsl.jinja": "jeh9pkkZIYSv2jQA/StxvAaQTVp/sI0eUsekeYSnkvo=",
 
 
 
19
  "unique-dedup.wgsl.jinja": "muTd580L/rfaL/p5NjOwMPp2F3ALTR9S7SzVYl9r+wQ=",
20
- "unique-hash-build.wgsl.jinja": "HkomLl6SIyeWaOI0dRrXbl2chUwav16ClFwxZ+EDZzY=",
21
- "unique-hash-collect.wgsl.jinja": "5+RKxUzJlrcqJ3Z/RfyznHl43WSoVKJSwGlQ/KzwnI4=",
22
- "unique-hash-init.wgsl.jinja": "d4tz0KAxmNaRn4ZvRQemr+7wk4GwRZY9tLQguTuIcGo=",
23
- "unique-hash-mark.wgsl.jinja": "87mwSXE1BpY/3zHxtaOhLpm4IZAF8hSX6bLvr6GfncE=",
24
- "unique-hash-sort-collected-key-only.wgsl.jinja": "Rpuf6QQZwx2XvogNtJoTS5/vsNHbA721YwBqwWt0SLQ=",
25
- "unique.wgsl.jinja": "J4xqWcvx4Ca4uW4ZbJ1xG2pfZbvL3cdnYU33fgHg1eo="
 
 
 
 
 
 
 
 
 
26
  }
27
  },
28
- "provenance": { "kernel": { "sha": "c928d21e6cc1310861cba3bafb75f5f679ecf5f3", "dirty": false } },
29
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.Unique" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  }
 
1
  {
2
  "name": "ai.onnx.Unique",
3
+ "id": "_ai_onnx_unique_webgpu_b3bffe7",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "6IjCP8Xe2mSSuxPK7xy73JMu42BF5ZgUhgwgS3Y2qcM=",
11
+ "manifest.json": "LLZErOf8Z+wJ7nP3zzdL38BwDHrMScCWVqLZ/v/bC/I=",
12
+ "scan-block-prefix-u32.wgsl.jinja": "EDY0dOyl7TX0GjGdfkGRqho+8VaqNTv69XIUAGWwW7w=",
13
+ "test.json": "4n/yg8WUYERuAKaqDlVQLlpguI6LoD/VkjllQSd/r+A=",
14
+ "unique-axis-compact-sort.wgsl.jinja": "USY2zwmM0qLzs5Zu+jeWhvsF/kksOxlMdBCRQyzSeEI=",
15
+ "unique-axis-dedup.wgsl.jinja": "tdp/nGtmgjimm0FRK2Q+nVPJXabpIbsE53/N88b6ZkI=",
16
+ "unique-axis-hash.wgsl.jinja": "KIRzJC48pNLKin6XkQlYmlOdj7UaMvHlHkORtDJBHbg=",
17
+ "unique-axis-scalar-inverse.wgsl.jinja": "8XJ/PidrZqxZ7E4exCzsghcDAnDkBWO6XhKoneojGMo=",
18
+ "unique-axis-scalar-ranks.wgsl.jinja": "MKa09aSgiScgGAke+eaZ8ECSPtZl1DvQRJhNnsGhx9Q=",
19
+ "unique-axis-scatter.wgsl.jinja": "H4nuOCxlxZHOBVm71ht58g4oFZfEwT+pXLXbx0yhjU0=",
20
+ "unique-axis.wgsl.jinja": "xGYbso+QeI9d4zpCXmrwWaueY1/dNII5ton7Nl6qAZQ=",
21
+ "unique-compact-sort.wgsl.jinja": "V1et+Wyn4847Ej1jkpjfPGBfexgbpKk1bk6VIapFU3o=",
22
  "unique-dedup.wgsl.jinja": "muTd580L/rfaL/p5NjOwMPp2F3ALTR9S7SzVYl9r+wQ=",
23
+ "unique-flag-block-scan.wgsl.jinja": "UHiW1pUoMetLcSVFtbb27NBiHyrj1BqnpvsmKHR4eUo=",
24
+ "unique-flat-metadata.wgsl.jinja": "DjjbOABSeqFVcZWMK3tfyBjT+1IxqRaF8IkSb1LbHNI=",
25
+ "unique-global-sort-exchange.wgsl.jinja": "ycyAh+i1Jf8f827QgsW8JFZsyPuTSq8dTe9eID4FtG8=",
26
+ "unique-global-sort-output.wgsl.jinja": "OHzPDIUYaZ71Lb0s+dkXqvYTUyQD5j5WOrb4pl2ehcE=",
27
+ "unique-global-sort-shared.wgsl.jinja": "PrT6/StTV6cAJKH9RsoQ9DL8jMi7xgBiUoxIl5Nhbuo=",
28
+ "unique-hash-build.wgsl.jinja": "R6FsBjCfL8Gs8Obi+UT7TPXH66BM4PlmA7N5zcf6boY=",
29
+ "unique-hash-collect.wgsl.jinja": "QjGT7dXhz4D9ka0Ry6EDJyCk+4ZxqEWZ7CQvaLl00lg=",
30
+ "unique-hash-init.wgsl.jinja": "oijfIva4kwhMMSpHUUWnwAEA3mZZhVSr4wQaXHgqaBI=",
31
+ "unique-hash-mark.wgsl.jinja": "vNqJd6BsN5s1QyOwoZfurA7FMKeSDA7n8+8t9IzvDaA=",
32
+ "unique-hash-sort-collected-key-only.wgsl.jinja": "r3/0IAivXcDWz5je9+0kn+EMahlO7IsQAqggcMPG01g=",
33
+ "unique-scalar-compact.wgsl.jinja": "icYtAk99HoPQMRoKcaDuBKhbiBZQabtx1+EnvM9AXSU=",
34
+ "unique-scalar-metadata.wgsl.jinja": "9K5i3e7rE0hGfnS9y1knf9M/IZNMRztJ2/mijo1uUvw=",
35
+ "unique-scalar-output.wgsl.jinja": "AzH6/sqeVpuLzEA8BN8I9u7eOz3cqvoDqLst5Awh31Q=",
36
+ "unique-single-class.wgsl.jinja": "7eZJTSljWA00bg6rPO5YbebToQuzuhd99IFCXbf96Fs=",
37
+ "unique.wgsl.jinja": "cwLLFAvO05OQZw6C6xpXeQgJPuwQTymG1PjBtHwwBmM="
38
  }
39
  },
40
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
41
+ "webgpu": {
42
+ "manifestSpec": "2.0",
43
+ "variants": {
44
+ "single_class_y": ["unique-single-class.wgsl.jinja"],
45
+ "scalar_hash_parallel_y_unsorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
46
+ "scalar_hash_parallel_y_sorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-global-sort-exchange.wgsl.jinja", "unique-global-sort-shared.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
47
+ "single_class_indices": ["unique-single-class.wgsl.jinja"],
48
+ "scalar_hash_parallel_indices_unsorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
49
+ "scalar_hash_parallel_indices_sorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-global-sort-exchange.wgsl.jinja", "unique-global-sort-shared.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
50
+ "single_class_inverse": ["unique-single-class.wgsl.jinja"],
51
+ "scalar_hash_parallel_inverse_unsorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-metadata.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
52
+ "scalar_hash_parallel_inverse_sorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-global-sort-exchange.wgsl.jinja", "unique-global-sort-shared.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-metadata.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
53
+ "single_class_indices_inverse": ["unique-single-class.wgsl.jinja"],
54
+ "scalar_hash_parallel_indices_inverse_unsorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-metadata.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
55
+ "scalar_hash_parallel_indices_inverse_sorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-global-sort-exchange.wgsl.jinja", "unique-global-sort-shared.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-metadata.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
56
+ "single_class_counts": ["unique-single-class.wgsl.jinja"],
57
+ "scalar_hash_parallel_counts_unsorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-metadata.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
58
+ "scalar_hash_parallel_counts_sorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-global-sort-exchange.wgsl.jinja", "unique-global-sort-shared.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-metadata.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
59
+ "single_class_indices_counts": ["unique-single-class.wgsl.jinja"],
60
+ "scalar_hash_parallel_indices_counts_unsorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-metadata.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
61
+ "scalar_hash_parallel_indices_counts_sorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-global-sort-exchange.wgsl.jinja", "unique-global-sort-shared.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-metadata.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
62
+ "single_class_inverse_counts": ["unique-single-class.wgsl.jinja"],
63
+ "scalar_hash_parallel_inverse_counts_unsorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-metadata.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
64
+ "scalar_hash_parallel_inverse_counts_sorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-global-sort-exchange.wgsl.jinja", "unique-global-sort-shared.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-metadata.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
65
+ "single_class_indices_inverse_counts": ["unique-single-class.wgsl.jinja"],
66
+ "scalar_hash_parallel_indices_inverse_counts_unsorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-metadata.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
67
+ "scalar_hash_parallel_indices_inverse_counts_sorted": ["scan-block-prefix-u32.wgsl.jinja", "unique-flag-block-scan.wgsl.jinja", "unique-global-sort-exchange.wgsl.jinja", "unique-global-sort-shared.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja", "unique-scalar-compact.wgsl.jinja", "unique-scalar-metadata.wgsl.jinja", "unique-scalar-output.wgsl.jinja"],
68
+ "hash_dedup_hybrid_sort_large_y": ["unique-compact-sort.wgsl.jinja", "unique-global-sort-exchange.wgsl.jinja", "unique-global-sort-output.wgsl.jinja", "unique-global-sort-shared.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"],
69
+ "hash_dedup_large_y": ["unique-compact-sort.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"],
70
+ "hash_dedup_large_y_unsorted": ["unique-compact-sort.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"],
71
+ "axis_scalar_hash_hybrid_metadata_inverse": ["unique-axis-compact-sort.wgsl.jinja", "unique-axis-scalar-inverse.wgsl.jinja", "unique-axis-scalar-ranks.wgsl.jinja", "unique-axis-scatter.wgsl.jinja", "unique-global-sort-exchange.wgsl.jinja", "unique-global-sort-shared.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"],
72
+ "axis_scalar_hash_metadata_inverse": ["unique-axis-compact-sort.wgsl.jinja", "unique-axis-scalar-inverse.wgsl.jinja", "unique-axis-scalar-ranks.wgsl.jinja", "unique-axis-scatter.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"],
73
+ "axis_scalar_hash_metadata_inverse_unsorted": ["unique-axis-compact-sort.wgsl.jinja", "unique-axis-scalar-inverse.wgsl.jinja", "unique-axis-scalar-ranks.wgsl.jinja", "unique-axis-scatter.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"],
74
+ "hash_dedup_sorted_collect": ["unique-hash-build.wgsl.jinja", "unique-hash-collect.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-sort-collected-key-only.wgsl.jinja"],
75
+ "hash_dedup": ["unique-compact-sort.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"],
76
+ "rank1_bounded": ["unique.wgsl.jinja"],
77
+ "axis_serial": ["unique-axis.wgsl.jinja"],
78
+ "axis_hash_bounded": ["unique-axis-compact-sort.wgsl.jinja", "unique-axis-hash.wgsl.jinja", "unique-axis-scatter.wgsl.jinja"],
79
+ "axis_hash_bounded_unsorted": ["unique-axis-compact-sort.wgsl.jinja", "unique-axis-hash.wgsl.jinja", "unique-axis-scatter.wgsl.jinja"],
80
+ "axis_hash_bounded_nan_safe": ["unique-axis-compact-sort.wgsl.jinja", "unique-axis-hash.wgsl.jinja", "unique-axis-scatter.wgsl.jinja", "unique-axis.wgsl.jinja"],
81
+ "axis_hash_bounded_unsorted_nan_safe": ["unique-axis-compact-sort.wgsl.jinja", "unique-axis-hash.wgsl.jinja", "unique-axis-scatter.wgsl.jinja", "unique-axis.wgsl.jinja"],
82
+ "parallel_dedup": ["unique-compact-sort.wgsl.jinja", "unique-dedup.wgsl.jinja"],
83
+ "parallel_dedup_large_y": ["unique-compact-sort.wgsl.jinja", "unique-dedup.wgsl.jinja"],
84
+ "parallel_dedup_large_y_unsorted": ["unique-compact-sort.wgsl.jinja", "unique-dedup.wgsl.jinja"],
85
+ "axis_bounded": ["unique-axis-compact-sort.wgsl.jinja", "unique-axis-dedup.wgsl.jinja"],
86
+ "flat_metadata_indices": ["unique.wgsl.jinja"],
87
+ "axis_metadata_indices": ["unique-axis.wgsl.jinja"],
88
+ "flat_metadata_inverse": ["unique.wgsl.jinja"],
89
+ "axis_metadata_inverse": ["unique-axis.wgsl.jinja"],
90
+ "flat_metadata_counts": ["unique.wgsl.jinja"],
91
+ "axis_metadata_counts": ["unique-axis.wgsl.jinja"],
92
+ "flat_metadata_indices_inverse": ["unique.wgsl.jinja"],
93
+ "axis_metadata_indices_inverse": ["unique-axis.wgsl.jinja"],
94
+ "flat_metadata_indices_counts": ["unique.wgsl.jinja"],
95
+ "axis_metadata_indices_counts": ["unique-axis.wgsl.jinja"],
96
+ "flat_metadata_inverse_counts": ["unique.wgsl.jinja"],
97
+ "axis_metadata_inverse_counts": ["unique-axis.wgsl.jinja"],
98
+ "flat_metadata_all": ["unique.wgsl.jinja"],
99
+ "axis_metadata_all": ["unique-axis.wgsl.jinja"],
100
+ "flat_parallel_metadata_indices": ["unique-compact-sort.wgsl.jinja", "unique-dedup.wgsl.jinja", "unique-flat-metadata.wgsl.jinja"],
101
+ "flat_parallel_metadata_inverse": ["unique-compact-sort.wgsl.jinja", "unique-dedup.wgsl.jinja", "unique-flat-metadata.wgsl.jinja"],
102
+ "flat_parallel_metadata_indices_inverse": ["unique-compact-sort.wgsl.jinja", "unique-dedup.wgsl.jinja", "unique-flat-metadata.wgsl.jinja"],
103
+ "flat_parallel_metadata_counts": ["unique-compact-sort.wgsl.jinja", "unique-dedup.wgsl.jinja", "unique-flat-metadata.wgsl.jinja"],
104
+ "flat_parallel_metadata_indices_counts": ["unique-compact-sort.wgsl.jinja", "unique-dedup.wgsl.jinja", "unique-flat-metadata.wgsl.jinja"],
105
+ "flat_parallel_metadata_inverse_counts": ["unique-compact-sort.wgsl.jinja", "unique-dedup.wgsl.jinja", "unique-flat-metadata.wgsl.jinja"],
106
+ "flat_parallel_metadata_all": ["unique-compact-sort.wgsl.jinja", "unique-dedup.wgsl.jinja", "unique-flat-metadata.wgsl.jinja"],
107
+ "flat_hash_metadata_indices": ["unique-compact-sort.wgsl.jinja", "unique-flat-metadata.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"],
108
+ "flat_hash_metadata_inverse": ["unique-compact-sort.wgsl.jinja", "unique-flat-metadata.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"],
109
+ "flat_hash_metadata_indices_inverse": ["unique-compact-sort.wgsl.jinja", "unique-flat-metadata.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"],
110
+ "flat_hash_metadata_counts": ["unique-compact-sort.wgsl.jinja", "unique-flat-metadata.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"],
111
+ "flat_hash_metadata_indices_counts": ["unique-compact-sort.wgsl.jinja", "unique-flat-metadata.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"],
112
+ "flat_hash_metadata_inverse_counts": ["unique-compact-sort.wgsl.jinja", "unique-flat-metadata.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"],
113
+ "flat_hash_metadata_all": ["unique-compact-sort.wgsl.jinja", "unique-flat-metadata.wgsl.jinja", "unique-hash-build.wgsl.jinja", "unique-hash-init.wgsl.jinja", "unique-hash-mark.wgsl.jinja"]
114
+ }
115
+ }
116
  }
build/webgpu/scan-block-prefix-u32.wgsl.jinja ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Single-workgroup exclusive scan over per-block sums for stream compaction.
2
+ // The dispatch is (1, 1, 1). It walks blockSums in workgroup-sized chunks,
3
+ // scans each chunk with Hillis-Steele or subgroup collectives, and links chunks
4
+ // with a running carry. blockPrefix[b] is therefore the number of set flags in
5
+ // all blocks before b. The total count is the last prefix plus the last sum.
6
+ {{ env.wgsl.resourceDeclarations }}
7
+
8
+ const WG: u32 = {{ workgroupSize }}u;
9
+
10
+ var<workgroup> wgScan: array<u32, WG>;
11
+
12
+ @compute @workgroup_size(WG, 1, 1)
13
+ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
14
+ let tid = lid.x;
15
+ var carry = 0u;
16
+ let chunks = (params.numBlocks + WG - 1u) / WG;
17
+ for (var c = 0u; c < chunks; c = c + 1u) {
18
+ let j = c * WG + tid;
19
+ var v = 0u;
20
+ if (j < params.numBlocks) {
21
+ v = blockSums[j];
22
+ }
23
+ workgroupBarrier();
24
+ wgScan[tid] = v;
25
+ for (var step = 1u; step < WG; step = step << 1u) {
26
+ workgroupBarrier();
27
+ var prev = 0u;
28
+ if (tid >= step) {
29
+ prev = wgScan[tid - step];
30
+ }
31
+ workgroupBarrier();
32
+ wgScan[tid] = wgScan[tid] + prev;
33
+ }
34
+ workgroupBarrier();
35
+ if (j < params.numBlocks) {
36
+ var value = carry;
37
+ if (tid > 0u) {
38
+ value = carry + wgScan[tid - 1u];
39
+ }
40
+ blockPrefix[j] = value;
41
+ }
42
+ carry = carry + wgScan[WG - 1u];
43
+ }
44
+ }
build/webgpu/test.json CHANGED
The diff for this file is too large to render. See raw diff
 
build/webgpu/unique-axis-compact-sort.wgsl.jinja CHANGED
@@ -1,16 +1,16 @@
1
- {% if usesF16 %}
2
- enable f16;
3
- {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
5
- {% set emitCount = source.emitCount | default(false) %}
6
 
7
  // Compact every first-occurrence slice index, optionally bitonic-sort them,
8
  // then scatter the exact result. Small results keep slots in workgroup memory;
9
  // larger results use storage-buffer scratch with storage barriers.
10
- const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
11
- const CAP: u32 = {{ source.capacity }}u;
12
- {% if sorted or not source.globalScratch %}
13
- const SORT_N: u32 = {{ source.sortN }}u;
 
 
 
14
 
15
  {% endif %}
16
  {% if sorted %}
@@ -38,6 +38,7 @@ fn canon_key(v: {{ scalar }}) -> u32 {
38
  }
39
 
40
  {% endif %}
 
41
  fn less_value(a: {{ scalar }}, b: {{ scalar }}) -> bool {
42
  {% if isFloat %}
43
  return canon_key(a) < canon_key(b);
@@ -45,9 +46,10 @@ fn less_value(a: {{ scalar }}, b: {{ scalar }}) -> bool {
45
  return a < b;
46
  {% endif %}
47
  }
 
48
 
49
  {% endif %}
50
- {% if sorted or not source.compactOnly %}
51
  fn slice_at(o: u32, k: u32, n: u32) -> {{ scalar }} {
52
  return x[(o * params.axisDim + k) * params.inner + n];
53
  }
@@ -56,6 +58,7 @@ fn slice_at(o: u32, k: u32, n: u32) -> {{ scalar }} {
56
  {% if sorted %}
57
  // True iff slice a < slice b lexicographically over (outer, inner) element order.
58
  // The serial insertion sort uses the same comparator.
 
59
  fn slice_less(a: u32, b: u32) -> bool {
60
  for (var o = 0u; o < params.outer; o = o + 1u) {
61
  for (var n = 0u; n < params.inner; n = n + 1u) {
@@ -68,6 +71,7 @@ fn slice_less(a: u32, b: u32) -> bool {
68
  return false;
69
  }
70
 
 
71
  // Cache the first element's monotonic integer-order key alongside each slot so
72
  // the bitonic network normally compares only threadgroup u32s; equal keys still
73
  // use the exact full-slice comparator.
@@ -83,7 +87,7 @@ fn slice_primary_key(k: u32) -> u32 {
83
  }
84
 
85
  {% endif %}
86
- {% if not source.compactOnly %}
87
  // Keep exact-result axis scatter identical across static- and dynamic-shape
88
  // dispatch strategies. The zero branch is a defensive guard for invalid shapes.
89
  fn unique_axis_zero_value() -> {{ scalar }} {
@@ -91,10 +95,17 @@ fn unique_axis_zero_value() -> {{ scalar }} {
91
  }
92
 
93
  fn unique_axis_scatter_element(g: u32, written: u32) {
 
 
 
 
 
 
94
  let n = g % params.inner;
95
  let tmp = g / params.inner;
96
  let p = tmp % params.outputAxisDim;
97
  let o = tmp / params.outputAxisDim;
 
98
  if (p < written) {
99
  y[g] = slice_at(o, slots[p], n);
100
  } else {
@@ -105,11 +116,11 @@ fn unique_axis_scatter_element(g: u32, written: u32) {
105
 
106
  {% endif %}
107
  var<workgroup> wgScan: array<u32, WG>;
108
- {% if not source.globalScratch %}
109
  var<workgroup> slots: array<u32, SORT_N>; // compacted (then sorted) slice indices
110
  {% endif %}
111
  var<workgroup> wgCarry: u32;
112
- {% if sorted and not source.globalScratch %}
113
  var<workgroup> sortPad: array<u32, SORT_N>; // 1 = padding slot, sorts after every real
114
  var<workgroup> sortKey: array<u32, SORT_N>; // first-element total-order key
115
 
@@ -156,7 +167,7 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
156
  }
157
 
158
  let total = wgCarry;
159
- {% if sorted or not source.compactOnly %}
160
  let written = min(total, CAP);
161
  {% endif %}
162
  {% if emitCount %}
@@ -164,7 +175,7 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
164
  count[0] = total;
165
  }
166
  {% endif %}
167
- {% if source.globalScratch %}
168
  storageBarrier();
169
 
170
  {% endif %}
@@ -181,12 +192,13 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
181
  sortKey[k] = slice_primary_key(slots[k]);
182
  }
183
  }
184
- {% if source.globalScratch %}
185
  storageBarrier();
186
  {% else %}
187
  workgroupBarrier();
188
  {% endif %}
189
 
 
190
  var size = 2u;
191
  loop {
192
  if (size > SORT_N) { break; }
@@ -228,7 +240,7 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
228
  }
229
  }
230
  }
231
- {% if source.globalScratch %}
232
  storageBarrier();
233
  {% else %}
234
  workgroupBarrier();
@@ -238,12 +250,10 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
238
  size = size * 2u;
239
  }
240
  {% endif %}
241
- {% if not source.compactOnly %}
242
- {% if source.globalScratch %}
243
- storageBarrier();
244
- {% else %}
245
- workgroupBarrier();
246
  {% endif %}
 
 
 
247
 
248
  // Scatter the distinct slices into the exact Y allocation: one lane per
249
  // output element, flat index g = (o*outputAxisDim + p)*inner + n.
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
+ {% set emitCount = emitCount | default(false) %}
3
 
4
  // Compact every first-occurrence slice index, optionally bitonic-sort them,
5
  // then scatter the exact result. Small results keep slots in workgroup memory;
6
  // larger results use storage-buffer scratch with storage barriers.
7
+ // Global-scratch variants reserve workgroup memory only for the scan buffer and
8
+ // provide an independent sort width. Workgroup-scratch variants use the shared
9
+ // workgroup-width setting.
10
+ const WG: u32 = {{ sortWg | default(tunables.WORKGROUP_SIZE) }}u;
11
+ const CAP: u32 = {{ capacity }}u;
12
+ {% if sorted or not globalScratch %}
13
+ const SORT_N: u32 = {{ sortN }}u;
14
 
15
  {% endif %}
16
  {% if sorted %}
 
38
  }
39
 
40
  {% endif %}
41
+ {% if not (sortExternally | default(false)) %}
42
  fn less_value(a: {{ scalar }}, b: {{ scalar }}) -> bool {
43
  {% if isFloat %}
44
  return canon_key(a) < canon_key(b);
 
46
  return a < b;
47
  {% endif %}
48
  }
49
+ {% endif %}
50
 
51
  {% endif %}
52
+ {% if sorted or not compactOnly %}
53
  fn slice_at(o: u32, k: u32, n: u32) -> {{ scalar }} {
54
  return x[(o * params.axisDim + k) * params.inner + n];
55
  }
 
58
  {% if sorted %}
59
  // True iff slice a < slice b lexicographically over (outer, inner) element order.
60
  // The serial insertion sort uses the same comparator.
61
+ {% if not (sortExternally | default(false)) %}
62
  fn slice_less(a: u32, b: u32) -> bool {
63
  for (var o = 0u; o < params.outer; o = o + 1u) {
64
  for (var n = 0u; n < params.inner; n = n + 1u) {
 
71
  return false;
72
  }
73
 
74
+ {% endif %}
75
  // Cache the first element's monotonic integer-order key alongside each slot so
76
  // the bitonic network normally compares only threadgroup u32s; equal keys still
77
  // use the exact full-slice comparator.
 
87
  }
88
 
89
  {% endif %}
90
+ {% if not compactOnly %}
91
  // Keep exact-result axis scatter identical across static- and dynamic-shape
92
  // dispatch strategies. The zero branch is a defensive guard for invalid shapes.
93
  fn unique_axis_zero_value() -> {{ scalar }} {
 
95
  }
96
 
97
  fn unique_axis_scatter_element(g: u32, written: u32) {
98
+ {% if staticShape is defined and staticShape %}
99
+ let n = g % INNER;
100
+ let tmp = g / INNER;
101
+ let p = tmp % OUTPUT_AXIS_DIM;
102
+ let o = tmp / OUTPUT_AXIS_DIM;
103
+ {% else %}
104
  let n = g % params.inner;
105
  let tmp = g / params.inner;
106
  let p = tmp % params.outputAxisDim;
107
  let o = tmp / params.outputAxisDim;
108
+ {% endif %}
109
  if (p < written) {
110
  y[g] = slice_at(o, slots[p], n);
111
  } else {
 
116
 
117
  {% endif %}
118
  var<workgroup> wgScan: array<u32, WG>;
119
+ {% if not globalScratch %}
120
  var<workgroup> slots: array<u32, SORT_N>; // compacted (then sorted) slice indices
121
  {% endif %}
122
  var<workgroup> wgCarry: u32;
123
+ {% if sorted and not globalScratch %}
124
  var<workgroup> sortPad: array<u32, SORT_N>; // 1 = padding slot, sorts after every real
125
  var<workgroup> sortKey: array<u32, SORT_N>; // first-element total-order key
126
 
 
167
  }
168
 
169
  let total = wgCarry;
170
+ {% if sorted or not compactOnly %}
171
  let written = min(total, CAP);
172
  {% endif %}
173
  {% if emitCount %}
 
175
  count[0] = total;
176
  }
177
  {% endif %}
178
+ {% if globalScratch %}
179
  storageBarrier();
180
 
181
  {% endif %}
 
192
  sortKey[k] = slice_primary_key(slots[k]);
193
  }
194
  }
195
+ {% if globalScratch %}
196
  storageBarrier();
197
  {% else %}
198
  workgroupBarrier();
199
  {% endif %}
200
 
201
+ {% if not (sortExternally | default(false)) %}
202
  var size = 2u;
203
  loop {
204
  if (size > SORT_N) { break; }
 
240
  }
241
  }
242
  }
243
+ {% if globalScratch %}
244
  storageBarrier();
245
  {% else %}
246
  workgroupBarrier();
 
250
  size = size * 2u;
251
  }
252
  {% endif %}
 
 
 
 
 
253
  {% endif %}
254
+ {% if not compactOnly %}
255
+ // The workgroup-scratch form scatters its compacted slots directly.
256
+ workgroupBarrier();
257
 
258
  // Scatter the distinct slices into the exact Y allocation: one lane per
259
  // output element, flat index g = (o*outputAxisDim + p)*inner + n.
build/webgpu/unique-axis-dedup.wgsl.jinja CHANGED
@@ -7,17 +7,15 @@
7
  // (o in outer, n in inner). This parallel path compares only integer storage
8
  // values.
9
  //
10
- // The quadratic scan's common case only needs to prove that two slices differ.
11
- // Loading the first element of every candidate directly from X made that scan a
12
- // large, cache-hostile gather (the axis stride can be kilobytes). Instead, every
13
- // workgroup stages a chunk of small sampled slice fingerprints in threadgroup
14
- // memory. A fingerprint mismatch proves inequality; collisions fall through to
15
- // the exact element-wise comparator, so this is an optimization only and cannot
16
- // change Unique semantics.
17
  fn slice_at(o: u32, k: u32, n: u32) -> {{ scalar }} {
18
  return x[(o * params.axisDim + k) * params.inner + n];
19
  }
20
 
 
21
  fn value_key(value: {{ scalar }}) -> u32 {
22
  // Integer conversion is injective for the 8/32-bit integer storage types.
23
  return u32(value);
@@ -29,23 +27,26 @@ fn mix_key(hash: u32, value: u32) -> u32 {
29
  return h;
30
  }
31
 
 
 
 
32
  fn slice_key(k: u32) -> u32 {
33
- let elements = params.outer * params.inner;
34
- if (elements == 0u) { return 0u; }
35
-
36
- // Four evenly-spaced samples make the prefilter useful for structured rows
37
- // whose leading value is shared, while keeping the staging pass tiny.
38
- let last = elements - 1u;
39
  var hash = 0x811c9dc5u;
40
- for (var sample = 0u; sample < 4u; sample = sample + 1u) {
41
- let logical = (sample * last) / 3u;
42
- let o = logical / params.inner;
43
- let n = logical % params.inner;
44
- hash = mix_key(hash, value_key(slice_at(o, k, n)));
45
  }
46
  return hash;
47
  }
48
 
 
 
 
 
 
 
 
49
  fn slice_eq(a: u32, b: u32) -> bool {
50
  for (var o = 0u; o < params.outer; o = o + 1u) {
51
  for (var n = 0u; n < params.inner; n = n + 1u) {
@@ -66,7 +67,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
66
  let laneValid = k < params.axisDim;
67
  var kKey = 0u;
68
  if (laneValid) {
69
- kKey = slice_key(k);
70
  }
71
 
72
  var seen = false;
@@ -78,7 +79,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
78
  let loadK = chunkBase + lid.x;
79
  var loadedKey = 0u;
80
  if (loadK < params.axisDim) {
81
- loadedKey = slice_key(loadK);
82
  }
83
  keyCache[lid.x] = loadedKey;
84
  workgroupBarrier();
@@ -103,3 +104,4 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
103
  firstFlag[k] = select(1u, 0u, seen);
104
  }
105
  }
 
 
7
  // (o in outer, n in inner). This parallel path compares only integer storage
8
  // values.
9
  //
10
+ // A separate pass fingerprints each slice into `sliceKeys`, and each workgroup
11
+ // stages a chunk of those fingerprints in shared memory. A mismatch proves
12
+ // inequality; collisions fall through to the exact element-wise comparator, so
13
+ // fingerprints cannot change Unique semantics.
 
 
 
14
  fn slice_at(o: u32, k: u32, n: u32) -> {{ scalar }} {
15
  return x[(o * params.axisDim + k) * params.inner + n];
16
  }
17
 
18
+ {% if stage == "keys" %}
19
  fn value_key(value: {{ scalar }}) -> u32 {
20
  // Integer conversion is injective for the 8/32-bit integer storage types.
21
  return u32(value);
 
27
  return h;
28
  }
29
 
30
+ // The fingerprint covers every element of the slice and is computed once per
31
+ // slice before the pairwise scan. Equal fingerprints still require the exact
32
+ // comparator because collisions are possible.
33
  fn slice_key(k: u32) -> u32 {
 
 
 
 
 
 
34
  var hash = 0x811c9dc5u;
35
+ for (var o = 0u; o < params.outer; o = o + 1u) {
36
+ for (var n = 0u; n < params.inner; n = n + 1u) {
37
+ hash = mix_key(hash, value_key(slice_at(o, k, n)));
38
+ }
 
39
  }
40
  return hash;
41
  }
42
 
43
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
44
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
45
+ let k = gid.x;
46
+ if (k >= params.axisDim) { return; }
47
+ sliceKeys[k] = slice_key(k);
48
+ }
49
+ {% else %}
50
  fn slice_eq(a: u32, b: u32) -> bool {
51
  for (var o = 0u; o < params.outer; o = o + 1u) {
52
  for (var n = 0u; n < params.inner; n = n + 1u) {
 
67
  let laneValid = k < params.axisDim;
68
  var kKey = 0u;
69
  if (laneValid) {
70
+ kKey = sliceKeys[k];
71
  }
72
 
73
  var seen = false;
 
79
  let loadK = chunkBase + lid.x;
80
  var loadedKey = 0u;
81
  if (loadK < params.axisDim) {
82
+ loadedKey = sliceKeys[loadK];
83
  }
84
  keyCache[lid.x] = loadedKey;
85
  workgroupBarrier();
 
104
  firstFlag[k] = select(1u, 0u, seen);
105
  }
106
  }
107
+ {% endif %}
build/webgpu/unique-axis-hash.wgsl.jinja CHANGED
@@ -1,3 +1,4 @@
 
1
  // Hash-backed axis Unique front end. Initialization computes one full-slice
2
  // hash per axis index and clears the open-addressed table. Build inserts
3
  // exact-equality buckets and atomically retains the earliest representative.
@@ -5,19 +6,17 @@
5
  // Hash collisions always use the exact slice comparator, so hashes never
6
  // affect semantics. Floats hash and compare canonical FTZ-safe bits (one NaN
7
  // class, -0 folded to +0), so bucket equality is transitive and exact.
8
- {% if usesF16 %}
9
- enable f16;
10
- {% endif %}
11
  {{ env.wgsl.resourceDeclarations }}
12
 
13
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
14
- const TABLE_SIZE: u32 = {{ source.tableSize }}u;
15
- {% if source.stage == "build" %}
16
  const TABLE_MASK: u32 = TABLE_SIZE - 1u;
17
  {% endif %}
18
  const EMPTY: u32 = 0xffffffffu;
19
 
20
- {% if source.stage == "init" %}
21
  {% if isFloat %}
22
  fn float_bits(v: {{ scalar }}) -> u32 {
23
  {% if usesF16 %}
@@ -53,30 +52,51 @@ fn slice_hash(k: u32) -> u32 {
53
  // FNV-1a over the complete slice. The axis index is deliberately excluded:
54
  // equal slices must hash identically regardless of where they occur.
55
  var hash = 0x811c9dc5u;
 
 
 
56
  for (var o = 0u; o < params.outer; o += 1u) {
57
  for (var n = 0u; n < params.inner; n += 1u) {
 
 
 
 
 
 
58
  hash = (hash ^ value_key(slice_at(o, k, n))) * 0x01000193u;
 
59
  }
60
  }
61
  // One final avalanche reduces clustering for short structured slices.
62
  hash ^= hash >> 16u;
63
  hash *= 0x7feb352du;
64
  hash ^= hash >> 15u;
 
 
 
 
 
65
  return hash;
66
  }
67
 
68
  @compute @workgroup_size(WG)
69
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
70
  let i = gid.x;
 
 
 
 
71
  if (i < TABLE_SIZE) {
72
  atomicStore(&hashSlot[i], EMPTY);
73
  }
74
  if (i < params.axisDim) {
 
75
  firstFlag[i] = 0u;
 
76
  sliceHash[i] = slice_hash(i);
77
  }
78
  }
79
- {% elif source.stage == "build" %}
80
 
81
  {% if isFloat %}
82
  fn float_bits(v: {{ scalar }}) -> u32 {
@@ -118,6 +138,11 @@ fn slice_eq(a: u32, b: u32) -> bool {
118
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
119
  let k = gid.x;
120
  if (k >= params.axisDim) { return; }
 
 
 
 
 
121
  let key = sliceHash[k];
122
  var slot = key & TABLE_MASK;
123
  for (var probe = 0u; probe < TABLE_SIZE; probe += 1u) {
 
1
+ {% set trackNaN = trackNaN | default(false) %}
2
  // Hash-backed axis Unique front end. Initialization computes one full-slice
3
  // hash per axis index and clears the open-addressed table. Build inserts
4
  // exact-equality buckets and atomically retains the earliest representative.
 
6
  // Hash collisions always use the exact slice comparator, so hashes never
7
  // affect semantics. Floats hash and compare canonical FTZ-safe bits (one NaN
8
  // class, -0 folded to +0), so bucket equality is transitive and exact.
9
+ {% set usesF16 = usesF16 and stage != "mark" %}
 
 
10
  {{ env.wgsl.resourceDeclarations }}
11
 
12
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
13
+ const TABLE_SIZE: u32 = {{ tableSize }}u;
14
+ {% if stage == "build" %}
15
  const TABLE_MASK: u32 = TABLE_SIZE - 1u;
16
  {% endif %}
17
  const EMPTY: u32 = 0xffffffffu;
18
 
19
+ {% if stage == "init" %}
20
  {% if isFloat %}
21
  fn float_bits(v: {{ scalar }}) -> u32 {
22
  {% if usesF16 %}
 
52
  // FNV-1a over the complete slice. The axis index is deliberately excluded:
53
  // equal slices must hash identically regardless of where they occur.
54
  var hash = 0x811c9dc5u;
55
+ {% if trackNaN %}
56
+ var containsNaN = false;
57
+ {% endif %}
58
  for (var o = 0u; o < params.outer; o += 1u) {
59
  for (var n = 0u; n < params.inner; n += 1u) {
60
+ {% if trackNaN %}
61
+ let value = slice_at(o, k, n);
62
+ hash = (hash ^ value_key(value)) * 0x01000193u;
63
+ let bits = float_bits(value);
64
+ containsNaN = containsNaN || ((bits & {{ "0x7c00u" if usesF16 else "0x7f800000u" }}) == {{ "0x7c00u" if usesF16 else "0x7f800000u" }} && (bits & {{ "0x03ffu" if usesF16 else "0x007fffffu" }}) != 0u);
65
+ {% else %}
66
  hash = (hash ^ value_key(slice_at(o, k, n))) * 0x01000193u;
67
+ {% endif %}
68
  }
69
  }
70
  // One final avalanche reduces clustering for short structured slices.
71
  hash ^= hash >> 16u;
72
  hash *= 0x7feb352du;
73
  hash ^= hash >> 15u;
74
+ {% if trackNaN %}
75
+ // Each invocation owns its slice flag. Build consumes and clears it in the
76
+ // next dispatch before hash mark uses this same allocation for first flags.
77
+ firstFlag[k] = select(0u, 1u, containsNaN);
78
+ {% endif %}
79
  return hash;
80
  }
81
 
82
  @compute @workgroup_size(WG)
83
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
84
  let i = gid.x;
85
+ {% if trackNaN %}
86
+ // No invocation ORs this flag until the following build dispatch.
87
+ if (i == 0u) { atomicStore(&nanFlag[0], 0u); }
88
+ {% endif %}
89
  if (i < TABLE_SIZE) {
90
  atomicStore(&hashSlot[i], EMPTY);
91
  }
92
  if (i < params.axisDim) {
93
+ {% if not trackNaN %}
94
  firstFlag[i] = 0u;
95
+ {% endif %}
96
  sliceHash[i] = slice_hash(i);
97
  }
98
  }
99
+ {% elif stage == "build" %}
100
 
101
  {% if isFloat %}
102
  fn float_bits(v: {{ scalar }}) -> u32 {
 
138
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
139
  let k = gid.x;
140
  if (k >= params.axisDim) { return; }
141
+ {% if trackNaN %}
142
+ let containsNaN = firstFlag[k] != 0u;
143
+ firstFlag[k] = 0u;
144
+ if (containsNaN) { atomicOr(&nanFlag[0], 1u); }
145
+ {% endif %}
146
  let key = sliceHash[k];
147
  var slot = key & TABLE_MASK;
148
  for (var probe = 0u; probe < TABLE_SIZE; probe += 1u) {
build/webgpu/unique-axis-scalar-inverse.wgsl.jinja ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+ // Open-addressing table constants and key/hash helpers.
6
+ const EMPTY: u32 = 0xffffffffu;
7
+ const MASK: u32 = {{ tableSize }}u - 1u;
8
+
9
+ {% if isFloat %}
10
+ // IEEE total-order comparators for Unique. Floating-point equality and ordering
11
+ // use raw bits because GPUs may flush subnormals in float comparisons, which
12
+ // would otherwise collapse distinct values. Equality canonicalizes -0 to +0;
13
+ // ordering uses a monotonic u32 key over the real line. Integers use == and <.
14
+ {% macro float_bits_def() %}
15
+ fn float_bits(v: {{ scalar }}) -> u32 {
16
+ {% if usesF16 %}
17
+ // WGSL has no scalar u16 type. Packing v into the low component preserves
18
+ // its binary16 representation while producing a bitcast-compatible 32 bits.
19
+ return bitcast<u32>(vec2<f16>(v, 0.0h)) & 0xffffu;
20
+ {% else %}
21
+ return bitcast<u32>(v);
22
+ {% endif %}
23
+ }
24
+ {%- endmacro -%}
25
+ {%- macro is_nan_bits_def() %}
26
+ fn is_nan_bits(v: {{ scalar }}) -> bool {
27
+ let b = float_bits(v);
28
+ {% if usesF16 %}
29
+ return (b & 0x7c00u) == 0x7c00u && (b & 0x03ffu) != 0u;
30
+ {% else %}
31
+ return (b & 0x7f800000u) == 0x7f800000u && (b & 0x007fffffu) != 0u;
32
+ {% endif %}
33
+ }
34
+ {%- endmacro %}
35
+
36
+ {{ float_bits_def() }}
37
+
38
+ {{ is_nan_bits_def() }}
39
+
40
+ {% endif %}
41
+ fn key_bits(v: {{ scalar }}) -> u32 {
42
+ {% if isFloat %}
43
+ let bits = float_bits(v);
44
+ return select(bits, 0u, bits == {{ "0x8000u" if usesF16 else "0x80000000u" }});
45
+ {% elif isUnsigned %}
46
+ return v;
47
+ {% else %}
48
+ return bitcast<u32>(v);
49
+ {% endif %}
50
+ }
51
+ fn hash_key(k: u32) -> u32 {
52
+ var x = k;
53
+ x = x ^ (x >> 16u);
54
+ x = x * 0x7feb352du;
55
+ x = x ^ (x >> 15u);
56
+ x = x * 0x846ca68bu;
57
+ x = x ^ (x >> 16u);
58
+ return x;
59
+ }
60
+
61
+ {% if not sorted %}
62
+ fn ordered_key(key: u32) -> u32 {
63
+ {% if usesF16 %}
64
+ return select(key ^ 0x8000u, (~key) & 0xffffu, (key & 0x8000u) != 0u);
65
+ {% else %}
66
+ return select(key ^ 0x80000000u, ~key, (key & 0x80000000u) != 0u);
67
+ {% endif %}
68
+ }
69
+ {% endif %}
70
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
71
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
72
+ let i = gid.x;
73
+ if (i >= {{ axisDim }}u) { return; }
74
+ if (is_nan_bits(x[0])) { inverse_indices[i] = 0u; return; }
75
+ // Later NaNs joined the smallest representative that already existed.
76
+ // Scan distinct representatives, excluding ones first seen after this input.
77
+ // Sorted slots can stop at their first eligible representative; unsorted
78
+ // slots appear in first-occurrence order, so later slots can be excluded.
79
+ if (is_nan_bits(x[i])) {
80
+ let written = min(count[0], {{ axisOutputDim }}u);
81
+ {% if not sorted %}
82
+ var best = 0u;
83
+ var bestKey = ordered_key(key_bits(x[slots[0]]));
84
+ {% endif %}
85
+ for (var p = 0u; p < written; p += 1u) {
86
+ let first = slots[p];
87
+ {% if sorted %}
88
+ if (first <= i) { inverse_indices[i] = p; return; }
89
+ {% else %}
90
+ if (first > i) { break; }
91
+ let candidate = ordered_key(key_bits(x[first]));
92
+ if (candidate < bestKey) { best = p; bestKey = candidate; }
93
+ {% endif %}
94
+ }
95
+ inverse_indices[i] = {{ "0u" if sorted else "best" }};
96
+ return;
97
+ }
98
+ let key = key_bits(x[i]);
99
+ var slot = hash_key(key) & MASK;
100
+ loop {
101
+ let stored = tableKey[slot];
102
+ if (stored == key) {
103
+ inverse_indices[i] = rankOfFirst[tableIdx[slot]];
104
+ return;
105
+ }
106
+ if (stored == EMPTY) { inverse_indices[i] = 0u; return; }
107
+ slot = (slot + 1u) & MASK;
108
+ }
109
+ }
build/webgpu/unique-axis-scalar-ranks.wgsl.jinja ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
3
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
4
+ let p = gid.x;
5
+ if (p >= min(count[0], {{ axisOutputDim }}u)) { return; }
6
+ rankOfFirst[slots[p]] = p;
7
+ }
build/webgpu/unique-axis-scatter.wgsl.jinja CHANGED
@@ -1,17 +1,14 @@
1
- {% if usesF16 %}
2
- enable f16;
3
- {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
5
 
6
  // Grid-parallel output stage for axis Unique. Compact/sort must remain a single
7
- // globally synchronized workgroup, but copying its selected slice indices into a
8
- // wide Y does not: distribute that bandwidth-heavy tail across the device.
9
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
10
- const CAP: u32 = {{ source.capacity }}u;
11
- const AXIS_DIM: u32 = {{ source.axisDim }}u;
12
- const INNER: u32 = {{ source.inner }}u;
13
- const OUTPUT_AXIS_DIM: u32 = {{ source.outputAxisDim }}u;
14
- const TOTAL_OUT: u32 = {{ source.totalOut }}u;
15
 
16
  fn slice_at(o: u32, k: u32, n: u32) -> {{ scalar }} {
17
  return x[(o * AXIS_DIM + k) * INNER + n];
 
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  // Grid-parallel output stage for axis Unique. Compact/sort must remain a single
4
+ // globally synchronized workgroup, but copying selected slice indices into a
5
+ // wide Y can be distributed across independent workgroups.
6
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
7
+ const CAP: u32 = {{ capacity }}u;
8
+ const AXIS_DIM: u32 = {{ axisDimSpec }}u;
9
+ const INNER: u32 = {{ innerSize }}u;
10
+ const OUTPUT_AXIS_DIM: u32 = {{ outputAxisDim }}u;
11
+ const TOTAL_OUT: u32 = {{ totalOut }}u;
12
 
13
  fn slice_at(o: u32, k: u32, n: u32) -> {{ scalar }} {
14
  return x[(o * AXIS_DIM + k) * INNER + n];
build/webgpu/unique-axis.wgsl.jinja CHANGED
@@ -1,3 +1,4 @@
 
1
  // IEEE total-order comparators for Unique. Floating-point equality and ordering
2
  // use raw bits because GPUs may flush subnormals in float comparisons, which
3
  // would otherwise collapse distinct values. Equality canonicalizes -0 to +0;
@@ -55,12 +56,9 @@ fn zero_value() -> {{ scalar }} {
55
  }
56
  {%- endmacro %}
57
 
58
- {% set emitIndices = source.hasIndices | default(false) %}
59
- {% set emitInverseIndices = source.hasInverseIndices | default(false) %}
60
- {% set emitCounts = source.hasCounts | default(false) %}
61
- {% if usesF16 %}
62
- enable f16;
63
- {% endif %}
64
  {{ env.wgsl.resourceDeclarations }}
65
 
66
  // Axis-mode Unique views the input as outer x axisDim x inner. Two axis slices
@@ -68,14 +66,12 @@ enable f16;
68
  // each distinct slice is retained, optionally sorted lexicographically, then
69
  // scattered into the data-dependent output allocation.
70
  //
71
- // Float equality and ordering use the same bit-preserving rules as the other
72
- // Unique kernels, including canonicalized signed zero and FTZ-safe subnormals.
73
 
74
- const CAP: u32 = {{ source.capacity }}u;
75
- // The walk is one sequential step per slice and every step carries several
76
- // workgroup barriers, so this width is barrier latency, not throughput: the
77
- // head test settles in one round on real data and the chunked scan and shift
78
- // both adapt to any width. Tuned separately from the op-wide WORKGROUP_SIZE.
79
  const WG: u32 = {{ axisSerialWg }}u;
80
  {% if isFloat %}
81
 
@@ -88,7 +84,7 @@ var<workgroup> hitIndex: u32;
88
  var<workgroup> diffAt: atomic<u32>;
89
  var<workgroup> cmpOut: u32;
90
  {% endif %}
91
- {% set headCache = isFloat and (source.headCacheSlots | default(0)) > 0 %}
92
  {% set floatSliceLess = isFloat and (emitInverseIndices or emitCounts) %}
93
  {% set headOf = "headCache[candidate]" if headCache else "head_bits(order[candidate])" %}
94
  {% set signBit = "0x8000u" if usesF16 else "0x80000000u" %}
@@ -114,16 +110,10 @@ fn less_head(ba: u32, bb: u32) -> bool {
114
  {% endif %}
115
  {% if headCache %}
116
 
117
- // Leading element of each written representative, kept beside `order`. The scan
118
- // below compares one slice against every representative, and reading those
119
- // leading elements out of X is a gather whose stride is the slice width, which
120
- // can be a kilobyte, so every candidate lands on its own cache line. Widening
121
- // the workgroup makes that worse rather than better, which is the tell: the
122
- // limit is cache capacity, not latency. Caching the leading elements densely
123
- // means the common case, where two slices already differ at element zero, never
124
- // touches X. The integer dedup path solves the same problem the same way, with
125
- // sampled slice fingerprints.
126
- var<workgroup> headCache: array<u32, {{ source.headCacheSlots }}>;
127
  {% endif %}
128
  {% if isFloat %}
129
 
@@ -154,12 +144,9 @@ fn head_qualifies(hc: u32, hk: u32) -> bool {
154
  // equivalent (identical, or decided at a coordinate holding a NaN, which
155
  // `slice_less` reports as neither-less), 1 when a < b, 2 when b < a.
156
  //
157
- // The serial `slice_less` walks a slice one element at a time on a single lane,
158
- // and its exit is data dependent, so a pair of EQUAL slices -- what a duplicate
159
- // input produces, and the reason this comparison exists -- runs the full slice
160
- // length at one memory latency per element, and that walk dominates the kernel.
161
- // Here every lane takes one coordinate and the first difference falls out of a
162
- // min-reduction, so an equal pair costs one round instead of one per element.
163
  fn slice_cmp_coop(a: u32, b: u32, lane: u32) -> u32 {
164
  if (lane == 0u) { atomicStore(&diffAt, NO_HIT); }
165
  workgroupBarrier();
@@ -225,11 +212,8 @@ fn slice_less(a: u32, b: u32) -> bool {
225
  let baseA = (o * params.axisDim + a) * params.inner;
226
  let baseB = (o * params.axisDim + b) * params.inner;
227
  for (var n = 0u; n < params.inner; n = n + 1u) {
228
- // Same three tests as `is_nan_bits` / `eq_value` / `less_value`, over one
229
- // pair of bit patterns instead of six independent `float_bits` calls --
230
- // which on f16 is a vec2 pack and mask each time. Two equal slices, the
231
- // case a duplicate input hits, walk to the end of the slice, so this loop
232
- // body runs once per element and was the kernel's dominant cost.
233
  let ba = float_bits(x[baseA + n]);
234
  let bb = float_bits(x[baseB + n]);
235
  if (is_nan_head(ba) || is_nan_head(bb)) { return false; }
@@ -328,19 +312,22 @@ fn find_bucket(k: u32, written: u32) -> u32 {
328
  {% endif %}
329
  @compute @workgroup_size(WG)
330
  fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
 
 
 
 
 
 
 
331
  {% if isFloat %}
332
  // Reproduce ordered lower-bound insertion. NaN makes the comparator
333
  // non-transitive, so pairwise equality and conventional hash tables cannot
334
  // preserve its stateful bucket behavior.
335
  //
336
- // Every representative written so far is a lower-bound candidate, so the scan
337
- // over them is the whole cost, and each step is a strided pair of slice loads
338
- // that pays full memory latency. Scanning on one lane left the rest of the
339
- // workgroup idle and issued those loads one at a time. Here the workgroup
340
- // tests a chunk of candidates at once and keeps the SMALLEST index whose
341
- // comparison fails -- the index the serial scan stops at, since it stops at
342
- // the first failure too. Testing the remainder of a chunk is redundant, never
343
- // observable: `slice_less` reads x and writes nothing.
344
  //
345
  // Both inner loops are barrier-free, so their bounds may depend on the live
346
  // count; every barrier sits at the top level of this loop, whose bound comes
@@ -349,10 +336,8 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
349
  for (var k = 0u; k < params.axisDim; k = k + 1u) {
350
  let headK = head_bits(k);
351
  // Walk the representatives for the first one that is not less than this
352
- // slice. The head-only test is exact whenever the leading elements differ,
353
- // so on real data this settles in a single round; a leading-element tie is
354
- // resolved by one exact comparison, and the scan resumes past it only if
355
- // that representative really does sort first.
356
  var searchFrom = 0u;
357
  var lowerBound = written;
358
  var equivalent = false;
@@ -361,8 +346,8 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
361
  workgroupBarrier();
362
  for (var base = searchFrom; base < written; base = base + WG) {
363
  // Chunks ascend, so an index already recorded is smaller than anything
364
- // this chunk holds. Reading the atomic without a barrier can only see
365
- // the hit late, which costs a redundant chunk and never a wrong bound.
366
  if (atomicLoad(&lowerBoundHit) != NO_HIT) { break; }
367
  let candidate = base + lid.x;
368
  if (candidate >= searchFrom && candidate < written
@@ -447,9 +432,8 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
447
  {% endif %}
448
  {% else %}
449
  if (params.axisDim <= CAP) {
450
- // When every input slice can be represented in the order scratch, test
451
- // first-occurrence status independently. Duplicate-heavy inputs usually
452
- // exit after only a handful of comparisons, and all lanes participate.
453
  for (var k = lid.x; k < params.axisDim; k = k + WG) {
454
  var seen = false;
455
  for (var j = 0u; j < k; j = j + 1u) {
@@ -502,8 +486,8 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
502
  // `order` is storage-backed so legal data-dependent capacities are not
503
  // constrained by maxComputeWorkgroupStorageSize.
504
  storageBarrier();
505
- // The public contract requires the caller-provided Y axis dimension to equal
506
- // the exact unique count, so it is also the cross-lane written count.
507
  let writtenCount = params.outputAxisDim;
508
 
509
  // Scattering the selected slices is independent once `order` is ready, so
 
1
+ {% set nanFallback = nanFallback | default(false) %}
2
  // IEEE total-order comparators for Unique. Floating-point equality and ordering
3
  // use raw bits because GPUs may flush subnormals in float comparisons, which
4
  // would otherwise collapse distinct values. Equality canonicalizes -0 to +0;
 
56
  }
57
  {%- endmacro %}
58
 
59
+ {% set emitIndices = hasIndices | default(false) %}
60
+ {% set emitInverseIndices = hasInverseIndices | default(false) %}
61
+ {% set emitCounts = hasCounts | default(false) %}
 
 
 
62
  {{ env.wgsl.resourceDeclarations }}
63
 
64
  // Axis-mode Unique views the input as outer x axisDim x inner. Two axis slices
 
66
  // each distinct slice is retained, optionally sorted lexicographically, then
67
  // scattered into the data-dependent output allocation.
68
  //
69
+ // Float comparisons canonicalize signed zero and preserve subnormal magnitudes
70
+ // when testing equality and lexicographic order.
71
 
72
+ const CAP: u32 = {{ capacity }}u;
73
+ // The slice walk, chunked scan, and shift all use this independently tunable
74
+ // workgroup width.
 
 
75
  const WG: u32 = {{ axisSerialWg }}u;
76
  {% if isFloat %}
77
 
 
84
  var<workgroup> diffAt: atomic<u32>;
85
  var<workgroup> cmpOut: u32;
86
  {% endif %}
87
+ {% set headCache = isFloat and (headCacheSlots | default(0)) > 0 %}
88
  {% set floatSliceLess = isFloat and (emitInverseIndices or emitCounts) %}
89
  {% set headOf = "headCache[candidate]" if headCache else "head_bits(order[candidate])" %}
90
  {% set signBit = "0x8000u" if usesF16 else "0x80000000u" %}
 
110
  {% endif %}
111
  {% if headCache %}
112
 
113
+ // Canonicalized leading-element bits for each written representative, kept
114
+ // beside `order`. A different leading value proves that two slices differ;
115
+ // equal leading values fall through to the exact full-slice comparison.
116
+ var<workgroup> headCache: array<u32, {{ headCacheSlots }}>;
 
 
 
 
 
 
117
  {% endif %}
118
  {% if isFloat %}
119
 
 
144
  // equivalent (identical, or decided at a coordinate holding a NaN, which
145
  // `slice_less` reports as neither-less), 1 when a < b, 2 when b < a.
146
  //
147
+ // Lanes compare separate coordinates and an atomic minimum identifies the first
148
+ // difference, preserving lexicographic order. Equal slices examine every
149
+ // coordinate and return zero.
 
 
 
150
  fn slice_cmp_coop(a: u32, b: u32, lane: u32) -> u32 {
151
  if (lane == 0u) { atomicStore(&diffAt, NO_HIT); }
152
  workgroupBarrier();
 
212
  let baseA = (o * params.axisDim + a) * params.inner;
213
  let baseB = (o * params.axisDim + b) * params.inner;
214
  for (var n = 0u; n < params.inner; n = n + 1u) {
215
+ // Load each bit pattern once so the NaN, equality, and ordering tests use
216
+ // the same representation.
 
 
 
217
  let ba = float_bits(x[baseA + n]);
218
  let bb = float_bits(x[baseB + n]);
219
  if (is_nan_head(ba) || is_nan_head(bb)) { return false; }
 
312
  {% endif %}
313
  @compute @workgroup_size(WG)
314
  fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
315
+ {% if nanFallback %}
316
+ // Publish through workgroup memory so the early return is uniform before
317
+ // entering the cooperative ordered insertion's barrier-containing loops.
318
+ if (lid.x == 0u) { hitIndex = nanFlag[0]; }
319
+ let requiresOrdered = workgroupUniformLoad(&hitIndex);
320
+ if (requiresOrdered == 0u) { return; }
321
+ {% endif %}
322
  {% if isFloat %}
323
  // Reproduce ordered lower-bound insertion. NaN makes the comparator
324
  // non-transitive, so pairwise equality and conventional hash tables cannot
325
  // preserve its stateful bucket behavior.
326
  //
327
+ // Every prior representative is a lower-bound candidate. The workgroup tests
328
+ // candidates in chunks and retains the smallest index whose comparison fails,
329
+ // matching the stopping point of a serial lower-bound scan. Comparisons after
330
+ // that candidate cannot affect output because `slice_less` has no side effects.
 
 
 
 
331
  //
332
  // Both inner loops are barrier-free, so their bounds may depend on the live
333
  // count; every barrier sits at the top level of this loop, whose bound comes
 
336
  for (var k = 0u; k < params.axisDim; k = k + 1u) {
337
  let headK = head_bits(k);
338
  // Walk the representatives for the first one that is not less than this
339
+ // slice. Different leading elements settle the ordering directly; equal
340
+ // leading elements require an exact comparison.
 
 
341
  var searchFrom = 0u;
342
  var lowerBound = written;
343
  var equivalent = false;
 
346
  workgroupBarrier();
347
  for (var base = searchFrom; base < written; base = base + WG) {
348
  // Chunks ascend, so an index already recorded is smaller than anything
349
+ // in this chunk. A late atomic observation may repeat work but cannot
350
+ // change the selected bound.
351
  if (atomicLoad(&lowerBoundHit) != NO_HIT) { break; }
352
  let candidate = base + lid.x;
353
  if (candidate >= searchFrom && candidate < written
 
432
  {% endif %}
433
  {% else %}
434
  if (params.axisDim <= CAP) {
435
+ // When every input slice fits in the order scratch, lanes independently
436
+ // test whether each slice matches an earlier slice.
 
437
  for (var k = lid.x; k < params.axisDim; k = k + WG) {
438
  var seen = false;
439
  for (var j = 0u; j < k; j = j + 1u) {
 
486
  // `order` is storage-backed so legal data-dependent capacities are not
487
  // constrained by maxComputeWorkgroupStorageSize.
488
  storageBarrier();
489
+ // The output axis dimension equals the exact unique count and therefore also
490
+ // supplies the cross-lane written count.
491
  let writtenCount = params.outputAxisDim;
492
 
493
  // Scattering the selected slices is independent once `order` is ready, so
build/webgpu/unique-compact-sort.wgsl.jinja CHANGED
@@ -1,19 +1,26 @@
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  // Pass 2 of parallel Unique: compact flagged first occurrences in appearance
4
  // order, optionally bitonic-sort them, and write the exact result. Small results
5
  // keep scratch in workgroup memory, while larger results use storage buffers
6
  // selected from device limits.
7
- const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
8
- const CAP: u32 = {{ source.capacity }}u;
 
 
 
 
9
  {% if sorted %}
10
  // The power-of-two padded tail sorts after every real value.
11
- const SORT_N: u32 = {{ source.sortN }}u;
12
 
13
  {% endif %}
14
  var<workgroup> wgScan: array<u32, WG>;
15
  var<workgroup> wgCarry: u32;
16
- {% if not source.globalScratch %}
17
  {% if sorted %}
18
  var<workgroup> sortKey: array<u32, SORT_N>;
19
  var<workgroup> sortVal: array<{{ scalar }}, SORT_N>;
@@ -23,20 +30,46 @@ var<workgroup> compacted: array<{{ scalar }}, CAP>;
23
  {% endif %}
24
 
25
  {% endif %}
 
26
  fn zero_value() -> {{ scalar }} {
27
  return {{ scalar }}(0);
28
  }
 
29
  {% if sorted %}
30
 
31
- // Monotonic key matching unsigned or signed integer order. Global scratch
32
- // already stores raw bits.
33
- fn sort_key(v: {% if source.globalScratch %}u32{% else %}{{ scalar }}{% endif %}) -> u32 {
34
- {% if source.globalScratch %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  let b = v;
 
 
36
  {% else %}
37
  let b = bitcast<u32>(v);
38
  {% endif %}
39
- {% if isUnsigned %}
 
 
40
  return b;
41
  {% else %}
42
  return b ^ 0x80000000u;
@@ -45,20 +78,11 @@ fn sort_key(v: {% if source.globalScratch %}u32{% else %}{{ scalar }}{% endif %}
45
 
46
  {% endif %}
47
  {% macro scratch_barrier() %}
48
- {% if source.globalScratch %}storageBarrier();{% else %}workgroupBarrier();{% endif %}
49
  {% endmacro %}
50
 
51
- @compute @workgroup_size(WG)
52
- fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
53
- let tid = lid.x;
54
- if (tid == 0u) {
55
- wgCarry = 0u;
56
- }
57
- workgroupBarrier();
58
-
59
- // Chunked Hillis-Steele exclusive scan maps flags to appearance-order ranks.
60
- let chunks = (params.inputCount + WG - 1u) / WG;
61
- for (var c = 0u; c < chunks; c = c + 1u) {
62
  let i = c * WG + tid;
63
  var f = 0u;
64
  if (i < params.inputCount) {
@@ -81,7 +105,7 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
81
  let excl = wgScan[tid] - f;
82
  let pos = wgCarry + excl;
83
  if (i < params.inputCount && f == 1u && pos < CAP) {
84
- {% if source.globalScratch %}
85
  sortVal[pos] = bitcast<u32>(x[i]);
86
  {% elif sorted %}
87
  sortVal[pos] = x[i];
@@ -94,11 +118,39 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
94
  wgCarry = wgCarry + wgScan[WG - 1u];
95
  }
96
  workgroupBarrier();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  }
 
 
 
 
 
 
98
 
99
  let total = wgCarry;
100
  let written = min(total, CAP);
101
- {% if source.globalScratch %}
102
  storageBarrier();
103
 
104
  {% endif %}
@@ -111,7 +163,7 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
111
  if (isPad == 0u) {
112
  sortKey[k] = sort_key(sortVal[k]);
113
  } else {
114
- {% if source.globalScratch %}
115
  sortVal[k] = 0u;
116
  {% else %}
117
  sortVal[k] = zero_value();
@@ -121,6 +173,7 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
121
  }
122
  {{ scratch_barrier() }}
123
 
 
124
  // Batcher bitonic network. Each lane owns one side of a compare-exchange.
125
  var size = 2u;
126
  loop {
@@ -158,12 +211,14 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
158
  }
159
 
160
  {% endif %}
161
- {% if not source.globalScratch %}
 
 
162
  workgroupBarrier();
163
  {% endif %}
164
  for (var k = tid; k < CAP; k = k + WG) {
165
  if (k < written) {
166
- {% if source.globalScratch %}
167
  y[k] = bitcast<{{ scalar }}>(sortVal[k]);
168
  {% elif sorted %}
169
  y[k] = sortVal[k];
@@ -174,4 +229,5 @@ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
174
  y[k] = zero_value();
175
  }
176
  }
 
177
  }
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
5
 
6
  // Pass 2 of parallel Unique: compact flagged first occurrences in appearance
7
  // order, optionally bitonic-sort them, and write the exact result. Small results
8
  // keep scratch in workgroup memory, while larger results use storage buffers
9
  // selected from device limits.
10
+ // Global-scratch variants reserve workgroup memory only for the scan buffer and
11
+ // provide an independent sort width. Workgroup-scratch variants use the shared
12
+ // workgroup-width setting.
13
+ {% set compactWg = sortWg | default(tunables.WORKGROUP_SIZE) %}
14
+ const WG: u32 = {{ compactWg }}u;
15
+ const CAP: u32 = {{ capacity }}u;
16
  {% if sorted %}
17
  // The power-of-two padded tail sorts after every real value.
18
+ const SORT_N: u32 = {{ sortN }}u;
19
 
20
  {% endif %}
21
  var<workgroup> wgScan: array<u32, WG>;
22
  var<workgroup> wgCarry: u32;
23
+ {% if not globalScratch %}
24
  {% if sorted %}
25
  var<workgroup> sortKey: array<u32, SORT_N>;
26
  var<workgroup> sortVal: array<{{ scalar }}, SORT_N>;
 
30
  {% endif %}
31
 
32
  {% endif %}
33
+ {% if sortExternally is not defined or not sortExternally %}
34
  fn zero_value() -> {{ scalar }} {
35
  return {{ scalar }}(0);
36
  }
37
+ {% endif %}
38
  {% if sorted %}
39
 
40
+ {% if isFloat and not globalScratch %}
41
+ // IEEE total-order comparators for Unique. Floating-point equality and ordering
42
+ // use raw bits because GPUs may flush subnormals in float comparisons, which
43
+ // would otherwise collapse distinct values. Equality canonicalizes -0 to +0;
44
+ // ordering uses a monotonic u32 key over the real line. Integers use == and <.
45
+ {% macro float_bits_def() %}
46
+ fn float_bits(v: {{ scalar }}) -> u32 {
47
+ {% if usesF16 %}
48
+ // WGSL has no scalar u16 type. Packing v into the low component preserves
49
+ // its binary16 representation while producing a bitcast-compatible 32 bits.
50
+ return bitcast<u32>(vec2<f16>(v, 0.0h)) & 0xffffu;
51
+ {% else %}
52
+ return bitcast<u32>(v);
53
+ {% endif %}
54
+ }
55
+ {%- endmacro %}
56
+
57
+ {{ float_bits_def() }}
58
+
59
+ {% endif %}
60
+ // Monotonic raw-bit key preserves finite subnormals on FTZ devices. Global
61
+ // scratch already stores raw bits.
62
+ fn sort_key(v: {% if globalScratch %}u32{% else %}{{ scalar }}{% endif %}) -> u32 {
63
+ {% if globalScratch %}
64
  let b = v;
65
+ {% elif isFloat %}
66
+ let b = float_bits(v);
67
  {% else %}
68
  let b = bitcast<u32>(v);
69
  {% endif %}
70
+ {% if isFloat %}
71
+ return select(b | {{ "0x8000u" if usesF16 else "0x80000000u" }}, (~b) & {{ "0xffffu" if usesF16 else "0xffffffffu" }}, (b & {{ "0x8000u" if usesF16 else "0x80000000u" }}) != 0u);
72
+ {% elif isUnsigned %}
73
  return b;
74
  {% else %}
75
  return b ^ 0x80000000u;
 
78
 
79
  {% endif %}
80
  {% macro scratch_barrier() %}
81
+ {% if globalScratch %}storageBarrier();{% else %}workgroupBarrier();{% endif %}
82
  {% endmacro %}
83
 
84
+ // Compact one input chunk while all lanes keep the scan and carry coherent.
85
+ fn compact_chunk(tid: u32, c: u32) {
 
 
 
 
 
 
 
 
 
86
  let i = c * WG + tid;
87
  var f = 0u;
88
  if (i < params.inputCount) {
 
105
  let excl = wgScan[tid] - f;
106
  let pos = wgCarry + excl;
107
  if (i < params.inputCount && f == 1u && pos < CAP) {
108
+ {% if globalScratch %}
109
  sortVal[pos] = bitcast<u32>(x[i]);
110
  {% elif sorted %}
111
  sortVal[pos] = x[i];
 
118
  wgCarry = wgCarry + wgScan[WG - 1u];
119
  }
120
  workgroupBarrier();
121
+ }
122
+
123
+ @compute @workgroup_size(WG)
124
+ fn main(@builtin(local_invocation_id) lid: vec3<u32>) {
125
+ let tid = lid.x;
126
+ if (tid == 0u) {
127
+ wgCarry = 0u;
128
+ }
129
+ workgroupBarrier();
130
+
131
+ let chunks = (params.inputCount + WG - 1u) / WG;
132
+ {% if capacity <= compactWg %}
133
+ // Test completion once, after the first chunk. If representatives occur
134
+ // later, the remaining scan retains its original barrier-only loop body.
135
+ if (chunks > 0u) {
136
+ compact_chunk(tid, 0u);
137
+ let first_count = workgroupUniformLoad(&wgCarry);
138
+ if (first_count < CAP) {
139
+ for (var c = 1u; c < chunks; c = c + 1u) {
140
+ compact_chunk(tid, c);
141
+ }
142
+ }
143
  }
144
+ {% else %}
145
+ // A chunk cannot fill a capacity larger than its invocation count.
146
+ for (var c = 0u; c < chunks; c = c + 1u) {
147
+ compact_chunk(tid, c);
148
+ }
149
+ {% endif %}
150
 
151
  let total = wgCarry;
152
  let written = min(total, CAP);
153
+ {% if globalScratch %}
154
  storageBarrier();
155
 
156
  {% endif %}
 
163
  if (isPad == 0u) {
164
  sortKey[k] = sort_key(sortVal[k]);
165
  } else {
166
+ {% if globalScratch %}
167
  sortVal[k] = 0u;
168
  {% else %}
169
  sortVal[k] = zero_value();
 
173
  }
174
  {{ scratch_barrier() }}
175
 
176
+ {% if sortExternally is not defined or not sortExternally %}
177
  // Batcher bitonic network. Each lane owns one side of a compare-exchange.
178
  var size = 2u;
179
  loop {
 
211
  }
212
 
213
  {% endif %}
214
+ {% endif %}
215
+ {% if sortExternally is not defined or not sortExternally %}
216
+ {% if not globalScratch %}
217
  workgroupBarrier();
218
  {% endif %}
219
  for (var k = tid; k < CAP; k = k + WG) {
220
  if (k < written) {
221
+ {% if globalScratch %}
222
  y[k] = bitcast<{{ scalar }}>(sortVal[k]);
223
  {% elif sorted %}
224
  y[k] = sortVal[k];
 
229
  y[k] = zero_value();
230
  }
231
  }
232
+ {% endif %}
233
  }
build/webgpu/unique-flag-block-scan.wgsl.jinja ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
3
+ var<workgroup> scan: array<u32, WG>;
4
+ @compute @workgroup_size(WG)
5
+ fn main(@builtin(local_invocation_id) lid: vec3<u32>, @builtin(global_invocation_id) gid: vec3<u32>, @builtin(workgroup_id) wid: vec3<u32>) {
6
+ let tid = lid.x;
7
+ var flag = 0u;
8
+ if (gid.x < {{ scanN }}u) { flag = flags[gid.x]; }
9
+ scan[tid] = flag;
10
+ for (var step = 1u; step < WG; step <<= 1u) {
11
+ workgroupBarrier();
12
+ var previous = 0u;
13
+ if (tid >= step) { previous = scan[tid - step]; }
14
+ workgroupBarrier();
15
+ scan[tid] += previous;
16
+ }
17
+ workgroupBarrier();
18
+ if (gid.x < {{ scanN }}u) { flags[gid.x] = 2u * (scan[tid] - flag) + flag; }
19
+ if (tid == WG - 1u) { blockSums[wid.x] = scan[tid]; }
20
+ }
build/webgpu/unique-flat-metadata.wgsl.jinja ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // IEEE total-order comparators for Unique. Floating-point equality and ordering
2
+ // use raw bits because GPUs may flush subnormals in float comparisons, which
3
+ // would otherwise collapse distinct values. Equality canonicalizes -0 to +0;
4
+ // ordering uses a monotonic u32 key over the real line. Integers use == and <.
5
+ {% macro eq_value_def() %}
6
+ fn eq_value(a: {{ scalar }}, b: {{ scalar }}) -> bool {
7
+ {% if isFloat %}
8
+ var ba = float_bits(a);
9
+ var bb = float_bits(b);
10
+ {% if usesF16 %}
11
+ if (ba == 0x8000u) { ba = 0u; } // -0 -> +0
12
+ if (bb == 0x8000u) { bb = 0u; }
13
+ {% else %}
14
+ if (ba == 0x80000000u) { ba = 0u; } // -0 -> +0
15
+ if (bb == 0x80000000u) { bb = 0u; }
16
+ {% endif %}
17
+ return ba == bb;
18
+ {% else %}
19
+ return a == b;
20
+ {% endif %}
21
+ }
22
+ {%- endmacro -%}
23
+ {%- macro less_value_def() %}
24
+ fn less_value(a: {{ scalar }}, b: {{ scalar }}) -> bool {
25
+ {% if isFloat %}
26
+ let ba = float_bits(a);
27
+ let bb = float_bits(b);
28
+ {% if usesF16 %}
29
+ let ka = select(ba | 0x8000u, (~ba) & 0xffffu, (ba & 0x8000u) != 0u);
30
+ let kb = select(bb | 0x8000u, (~bb) & 0xffffu, (bb & 0x8000u) != 0u);
31
+ {% else %}
32
+ let ka = select(ba | 0x80000000u, ~ba, (ba & 0x80000000u) != 0u);
33
+ let kb = select(bb | 0x80000000u, ~bb, (bb & 0x80000000u) != 0u);
34
+ {% endif %}
35
+ return ka < kb;
36
+ {% else %}
37
+ return a < b;
38
+ {% endif %}
39
+ }
40
+ {%- endmacro %}
41
+
42
+ {% set emitIndices = hasIndices | default(false) %}
43
+ {% set emitInverseIndices = hasInverseIndices | default(false) %}
44
+ {{ env.wgsl.resourceDeclarations }}
45
+
46
+ // Metadata for the parallel integer Unique routes. Y and the first-occurrence
47
+ // flags are already final when this runs, so every output is a pure function of
48
+ // (x, y, flags) and one thread can own one element. The single-invocation kernel
49
+ // this replaces resolved the same buckets by scanning the whole input once per
50
+ // (input, output) pair.
51
+ const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
52
+
53
+ {% if stage != "scatter" or not sorted %}
54
+ {{ eq_value_def() }}
55
+ {% endif %}
56
+ {% if stage == "scatter" %}
57
+ {% if sorted %}
58
+
59
+ {{ less_value_def() }}
60
+
61
+ // Y is ascending on this route, so a bucket is a binary search rather than a
62
+ // walk over every unique value.
63
+ fn find_slot(value: {{ scalar }}) -> u32 {
64
+ var lo = 0u;
65
+ var hi = params.capacity;
66
+ loop {
67
+ if (lo >= hi) { break; }
68
+ let mid = lo + (hi - lo) / 2u;
69
+ if (less_value(y[mid], value)) {
70
+ lo = mid + 1u;
71
+ } else {
72
+ hi = mid;
73
+ }
74
+ }
75
+ return lo;
76
+ }
77
+ {% else %}
78
+
79
+ // Y is in first-appearance order, so a bucket is a scan of the unique values.
80
+ fn find_slot(value: {{ scalar }}) -> u32 {
81
+ for (var k = 0u; k < params.capacity; k = k + 1u) {
82
+ if (eq_value(y[k], value)) { return k; }
83
+ }
84
+ return params.capacity;
85
+ }
86
+ {% endif %}
87
+
88
+ @compute @workgroup_size(WG)
89
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
90
+ let i = gid.x;
91
+ if (i >= params.inputCount) {
92
+ return;
93
+ }
94
+ let slot = find_slot(x[i]);
95
+ if (slot >= params.capacity) {
96
+ return;
97
+ }
98
+ {% if emitInverseIndices %}
99
+ inverse_indices[i] = slot;
100
+ {% endif %}
101
+ {% if emitIndices %}
102
+ // Exactly one input element is the first occurrence of its value, so each
103
+ // slot has a single writer and the store needs no ordering.
104
+ if (flags[i] == 1u) {
105
+ indices[slot] = i;
106
+ }
107
+ {% endif %}
108
+ }
109
+ {% else %}
110
+
111
+ // One thread per unique value counts its own occurrences. Accumulating per
112
+ // output rather than per input keeps the tally free of atomics, and so free of
113
+ // the zero-fill pass an atomic accumulation would need first.
114
+ @compute @workgroup_size(WG)
115
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
116
+ let k = gid.x;
117
+ if (k >= params.capacity) {
118
+ return;
119
+ }
120
+ let value = y[k];
121
+ var total = 0u;
122
+ for (var i = 0u; i < params.inputCount; i = i + 1u) {
123
+ if (eq_value(x[i], value)) {
124
+ total = total + 1u;
125
+ }
126
+ }
127
+ counts[k] = total;
128
+ }
129
+ {% endif %}
build/webgpu/unique-global-sort-exchange.wgsl.jinja ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Integer Unique's existing (padding, monotonic-key) bitonic comparison.
2
+ // One invocation owns a disjoint pair. A dispatch boundary publishes each
3
+ // completed distance to the next distance across every workgroup.
4
+ {{ env.wgsl.resourceDeclarations }}
5
+ {% macro exchange_integer(keys, values, pads, writeState=true) %}
6
+ let pk = {{ pads }}[k];
7
+ let pp = {{ pads }}[partner];
8
+ let kk = {{ keys }}[k];
9
+ let kp = {{ keys }}[partner];
10
+ let kBeforeP = (pk < pp) || (pk == pp && kk <= kp);
11
+ let needSwap = select(kBeforeP, !kBeforeP, ascending);
12
+ if (needSwap) {
13
+ let tVal = {{ values }}[k];
14
+ {{ values }}[k] = {{ values }}[partner];
15
+ {{ values }}[partner] = tVal;
16
+ {% if writeState %}
17
+ {{ keys }}[k] = kp;
18
+ {{ keys }}[partner] = kk;
19
+ {{ pads }}[k] = pp;
20
+ {{ pads }}[partner] = pk;
21
+ {% endif %}
22
+ }
23
+ {% endmacro %}
24
+
25
+ const SIZE: u32 = {{ sortSize }}u;
26
+ const STRIDE: u32 = {{ sortStride }}u;
27
+ const PAIRS: u32 = {{ (sortLength / 2)|int }}u;
28
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
29
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
30
+ let pair = gid.x;
31
+ if (pair >= PAIRS) { return; }
32
+ let k = (pair / STRIDE) * (2u * STRIDE) + pair % STRIDE;
33
+ let partner = k + STRIDE;
34
+ let ascending = (k & SIZE) == 0u;
35
+ {{ exchange_integer("sortKey", "sortVal", "sortPad", writeSortState | default(true)) }}
36
+ }
build/webgpu/unique-global-sort-output.wgsl.jinja ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
3
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
4
+ let k = gid.x;
5
+ if (k >= {{ capacity }}u) { return; }
6
+ var value = {{ scalar }}(0);
7
+ if (k < {{ sortLength }}u && sortPad[k] == 0u) { value = bitcast<{{ scalar }}>(sortVal[k]); }
8
+ y[k] = value;
9
+ }
build/webgpu/unique-global-sort-shared.wgsl.jinja ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Integer-only bitonic stages whose pairs lie in one contiguous shared tile.
2
+ // Global indices, including the tile base, retain the network's direction.
3
+ {{ env.wgsl.resourceDeclarations }}
4
+ {% macro exchange_integer(keys, values, pads, writeState=true) %}
5
+ let pk = {{ pads }}[k];
6
+ let pp = {{ pads }}[partner];
7
+ let kk = {{ keys }}[k];
8
+ let kp = {{ keys }}[partner];
9
+ let kBeforeP = (pk < pp) || (pk == pp && kk <= kp);
10
+ let needSwap = select(kBeforeP, !kBeforeP, ascending);
11
+ if (needSwap) {
12
+ let tVal = {{ values }}[k];
13
+ {{ values }}[k] = {{ values }}[partner];
14
+ {{ values }}[partner] = tVal;
15
+ {% if writeState %}
16
+ {{ keys }}[k] = kp;
17
+ {{ keys }}[partner] = kk;
18
+ {{ pads }}[k] = pp;
19
+ {{ pads }}[partner] = pk;
20
+ {% endif %}
21
+ }
22
+ {% endmacro %}
23
+
24
+ const TILE: u32 = {{ tileSize }}u;
25
+ const WG: u32 = {{ workgroupSize }}u;
26
+ var<workgroup> tileKey: array<u32, TILE>;
27
+ var<workgroup> tileVal: array<u32, TILE>;
28
+ var<workgroup> tilePad: array<u32, TILE>;
29
+ fn exchange_pair(pair: u32, stride: u32, size: u32, base: u32) {
30
+ let k = (pair / stride) * (2u * stride) + pair % stride;
31
+ let partner = k + stride;
32
+ let ascending = ((base + k) & size) == 0u;
33
+ {{ exchange_integer("tileKey", "tileVal", "tilePad") }}
34
+ }
35
+ @compute @workgroup_size(WG)
36
+ fn main(@builtin(workgroup_id) wg: vec3<u32>,
37
+ @builtin(local_invocation_index) tid: u32) {
38
+ let base = wg.x * TILE;
39
+ for (var i = tid; i < TILE; i += WG) {
40
+ tileKey[i] = sortKey[base + i];
41
+ tileVal[i] = sortVal[base + i];
42
+ tilePad[i] = sortPad[base + i];
43
+ }
44
+ workgroupBarrier();
45
+ {% if initialSort %}
46
+ for (var size = 2u; size <= TILE; size *= 2u) {
47
+ for (var stride = size / 2u; stride > 0u; stride /= 2u) {
48
+ for (var pair = tid; pair < TILE / 2u; pair += WG) {
49
+ exchange_pair(pair, stride, size, base);
50
+ }
51
+ workgroupBarrier();
52
+ }
53
+ }
54
+ {% else %}
55
+ for (var stride = TILE / 2u; stride > 0u; stride /= 2u) {
56
+ for (var pair = tid; pair < TILE / 2u; pair += WG) {
57
+ exchange_pair(pair, stride, {{ sortSize }}u, base);
58
+ }
59
+ workgroupBarrier();
60
+ }
61
+ {% endif %}
62
+ for (var i = tid; i < TILE; i += WG) {
63
+ {% if writeSortState | default(true) %}
64
+ sortKey[base + i] = tileKey[i];
65
+ sortPad[base + i] = tilePad[i];
66
+ {% endif %}
67
+ sortVal[base + i] = tileVal[i];
68
+ }
69
+ }
build/webgpu/unique-hash-build.wgsl.jinja CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  // One thread per input element inserts its value into an open-addressing hash
@@ -15,10 +18,45 @@
15
  // its minimum index uses `special`.
16
  // Open-addressing table constants and key/hash helpers.
17
  const EMPTY: u32 = 0xffffffffu;
18
- const MASK: u32 = {{ source.tableSize }}u - 1u;
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  fn key_bits(v: {{ scalar }}) -> u32 {
21
- {% if isUnsigned %}
 
 
 
22
  return v;
23
  {% else %}
24
  return bitcast<u32>(v);
@@ -41,6 +79,11 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
41
  if (i >= params.inputCount) {
42
  return;
43
  }
 
 
 
 
 
44
  let k = key_bits(x[i]);
45
  if (k == EMPTY) {
46
  atomicMin(&special[0], i);
@@ -50,12 +93,14 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
50
  loop {
51
  let res = atomicCompareExchangeWeak(&tableKey[h], EMPTY, k);
52
  if (res.exchanged || res.old_value == k) {
53
- {% if not source.keyOnly %}
54
  // This slot now holds k (we just placed it, or it already held k).
55
  atomicMin(&tableIdx[h], i);
56
  {% endif %}
57
  break;
58
  }
59
- h = (h + 1u) & MASK;
 
 
60
  }
61
  }
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
5
 
6
  // One thread per input element inserts its value into an open-addressing hash
 
18
  // its minimum index uses `special`.
19
  // Open-addressing table constants and key/hash helpers.
20
  const EMPTY: u32 = 0xffffffffu;
21
+ const MASK: u32 = {{ tableSize }}u - 1u;
22
 
23
+ {% if isFloat %}
24
+ // IEEE total-order comparators for Unique. Floating-point equality and ordering
25
+ // use raw bits because GPUs may flush subnormals in float comparisons, which
26
+ // would otherwise collapse distinct values. Equality canonicalizes -0 to +0;
27
+ // ordering uses a monotonic u32 key over the real line. Integers use == and <.
28
+ {% macro float_bits_def() %}
29
+ fn float_bits(v: {{ scalar }}) -> u32 {
30
+ {% if usesF16 %}
31
+ // WGSL has no scalar u16 type. Packing v into the low component preserves
32
+ // its binary16 representation while producing a bitcast-compatible 32 bits.
33
+ return bitcast<u32>(vec2<f16>(v, 0.0h)) & 0xffffu;
34
+ {% else %}
35
+ return bitcast<u32>(v);
36
+ {% endif %}
37
+ }
38
+ {%- endmacro -%}
39
+ {%- macro is_nan_bits_def() %}
40
+ fn is_nan_bits(v: {{ scalar }}) -> bool {
41
+ let b = float_bits(v);
42
+ {% if usesF16 %}
43
+ return (b & 0x7c00u) == 0x7c00u && (b & 0x03ffu) != 0u;
44
+ {% else %}
45
+ return (b & 0x7f800000u) == 0x7f800000u && (b & 0x007fffffu) != 0u;
46
+ {% endif %}
47
+ }
48
+ {%- endmacro %}
49
+
50
+ {{ float_bits_def() }}
51
+
52
+ {{ is_nan_bits_def() }}
53
+
54
+ {% endif %}
55
  fn key_bits(v: {{ scalar }}) -> u32 {
56
+ {% if isFloat %}
57
+ let bits = float_bits(v);
58
+ return select(bits, 0u, bits == {{ "0x8000u" if usesF16 else "0x80000000u" }});
59
+ {% elif isUnsigned %}
60
  return v;
61
  {% else %}
62
  return bitcast<u32>(v);
 
79
  if (i >= params.inputCount) {
80
  return;
81
  }
82
+ {% if isFloat %}
83
+ // A leading NaN is the ordered map's only representative. Otherwise every
84
+ // NaN is equivalent to an already-present bucket and creates no new value.
85
+ if (is_nan_bits(x[0]) || is_nan_bits(x[i])) { return; }
86
+ {% endif %}
87
  let k = key_bits(x[i]);
88
  if (k == EMPTY) {
89
  atomicMin(&special[0], i);
 
93
  loop {
94
  let res = atomicCompareExchangeWeak(&tableKey[h], EMPTY, k);
95
  if (res.exchanged || res.old_value == k) {
96
+ {% if not keyOnly %}
97
  // This slot now holds k (we just placed it, or it already held k).
98
  atomicMin(&tableIdx[h], i);
99
  {% endif %}
100
  break;
101
  }
102
+ // Weak CAS may fail spuriously while the slot is still empty.
103
+ // Keep probing this slot until it is claimed or contains another key.
104
+ if (res.old_value != EMPTY) { h = (h + 1u) & MASK; }
105
  }
106
  }
build/webgpu/unique-hash-collect.wgsl.jinja CHANGED
@@ -1,4 +1,4 @@
1
- {% if source.useSubgroups %}
2
  enable subgroups;
3
  {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
@@ -7,14 +7,14 @@ enable subgroups;
7
  // occupied hash keys directly and reserve output positions once per workgroup.
8
  // This avoids serializing every distinct value through one global atomic.
9
  const EMPTY: u32 = 0xffffffffu;
10
- const TABLE_SIZE: u32 = {{ source.tableSize }}u;
11
- const CAP: u32 = {{ source.capacity }}u;
12
- {% if source.useSubgroups %}
13
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
14
 
15
  {% endif %}
16
  var<workgroup> groupBase: u32;
17
- {% if source.useSubgroups %}
18
  // The first subgroup lane writes its total. Lane zero converts these totals to
19
  // subgroup offsets and performs the workgroup's sole global count reservation.
20
  var<workgroup> subgroupOffsets: array<u32, WG>;
@@ -26,7 +26,7 @@ var<workgroup> localCount: atomic<u32>;
26
 
27
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
28
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
29
- @builtin(local_invocation_id) lid: vec3<u32>{% if source.useSubgroups %},
30
  @builtin(subgroup_invocation_id) sgLane: u32,
31
  @builtin(subgroup_size) sgSize: u32{% endif %}) {
32
  let h = gid.x;
@@ -54,7 +54,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
54
  }
55
  }
56
 
57
- {% if source.useSubgroups %}
58
  let subgroupPrefix = subgroupExclusiveAdd(itemCount);
59
  let subgroupTotal = subgroupAdd(itemCount);
60
  let subgroupId = lid.x / sgSize;
 
1
+ {% if useSubgroups %}
2
  enable subgroups;
3
  {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
 
7
  // occupied hash keys directly and reserve output positions once per workgroup.
8
  // This avoids serializing every distinct value through one global atomic.
9
  const EMPTY: u32 = 0xffffffffu;
10
+ const TABLE_SIZE: u32 = {{ tableSize }}u;
11
+ const CAP: u32 = {{ capacity }}u;
12
+ {% if useSubgroups %}
13
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
14
 
15
  {% endif %}
16
  var<workgroup> groupBase: u32;
17
+ {% if useSubgroups %}
18
  // The first subgroup lane writes its total. Lane zero converts these totals to
19
  // subgroup offsets and performs the workgroup's sole global count reservation.
20
  var<workgroup> subgroupOffsets: array<u32, WG>;
 
26
 
27
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
28
  fn main(@builtin(global_invocation_id) gid: vec3<u32>,
29
+ @builtin(local_invocation_id) lid: vec3<u32>{% if useSubgroups %},
30
  @builtin(subgroup_invocation_id) sgLane: u32,
31
  @builtin(subgroup_size) sgSize: u32{% endif %}) {
32
  let h = gid.x;
 
54
  }
55
  }
56
 
57
+ {% if useSubgroups %}
58
  let subgroupPrefix = subgroupExclusiveAdd(itemCount);
59
  let subgroupTotal = subgroupAdd(itemCount);
60
  let subgroupId = lid.x / sgSize;
build/webgpu/unique-hash-init.wgsl.jinja CHANGED
@@ -6,11 +6,11 @@
6
  // folds each key's minimum input index into tableIdx, and it leaves the output
7
  // counter alone -- only the sorted-collect path counts from zero here.
8
  const EMPTY: u32 = 0xffffffffu;
9
- const TABLE_SIZE: u32 = {{ source.tableSize }}u;
10
 
11
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
12
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
13
- {% if source.keyOnlyVec4 %}
14
  let h4 = gid.x;
15
  if (h4 >= TABLE_SIZE / 4u) {
16
  return;
 
6
  // folds each key's minimum input index into tableIdx, and it leaves the output
7
  // counter alone -- only the sorted-collect path counts from zero here.
8
  const EMPTY: u32 = 0xffffffffu;
9
+ const TABLE_SIZE: u32 = {{ tableSize }}u;
10
 
11
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
12
  fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
13
+ {% if keyOnlyVec4 %}
14
  let h4 = gid.x;
15
  if (h4 >= TABLE_SIZE / 4u) {
16
  return;
build/webgpu/unique-hash-mark.wgsl.jinja CHANGED
@@ -1,6 +1,9 @@
 
 
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
- // Pass 3 of the hash-set parallel Unique (integer dtypes only). One thread per
4
  // input element re-probes the (now fully built) hash table for its value and
5
  // reads the stored minimum index. flags[i] = 1 iff i is that minimum, i.e. i is
6
  // the first occurrence of its value — exactly the predicate the O(n^2) scan
@@ -9,10 +12,45 @@
9
  // hitting an EMPTY slot.
10
  // Open-addressing table constants and key/hash helpers.
11
  const EMPTY: u32 = 0xffffffffu;
12
- const MASK: u32 = {{ source.tableSize }}u - 1u;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
 
 
 
 
 
14
  fn key_bits(v: {{ scalar }}) -> u32 {
15
- {% if isUnsigned %}
 
 
 
16
  return v;
17
  {% else %}
18
  return bitcast<u32>(v);
@@ -34,6 +72,17 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
34
  if (i >= params.inputCount) {
35
  return;
36
  }
 
 
 
 
 
 
 
 
 
 
 
37
  let k = key_bits(x[i]);
38
  if (k == EMPTY) {
39
  flags[i] = select(0u, 1u, atomicLoad(&special[0]) == i);
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
  {{ env.wgsl.resourceDeclarations }}
5
 
6
+ // Pass 3 of the hash-set parallel Unique. One thread per
7
  // input element re-probes the (now fully built) hash table for its value and
8
  // reads the stored minimum index. flags[i] = 1 iff i is that minimum, i.e. i is
9
  // the first occurrence of its value — exactly the predicate the O(n^2) scan
 
12
  // hitting an EMPTY slot.
13
  // Open-addressing table constants and key/hash helpers.
14
  const EMPTY: u32 = 0xffffffffu;
15
+ const MASK: u32 = {{ tableSize }}u - 1u;
16
+
17
+ {% if isFloat %}
18
+ // IEEE total-order comparators for Unique. Floating-point equality and ordering
19
+ // use raw bits because GPUs may flush subnormals in float comparisons, which
20
+ // would otherwise collapse distinct values. Equality canonicalizes -0 to +0;
21
+ // ordering uses a monotonic u32 key over the real line. Integers use == and <.
22
+ {% macro float_bits_def() %}
23
+ fn float_bits(v: {{ scalar }}) -> u32 {
24
+ {% if usesF16 %}
25
+ // WGSL has no scalar u16 type. Packing v into the low component preserves
26
+ // its binary16 representation while producing a bitcast-compatible 32 bits.
27
+ return bitcast<u32>(vec2<f16>(v, 0.0h)) & 0xffffu;
28
+ {% else %}
29
+ return bitcast<u32>(v);
30
+ {% endif %}
31
+ }
32
+ {%- endmacro -%}
33
+ {%- macro is_nan_bits_def() %}
34
+ fn is_nan_bits(v: {{ scalar }}) -> bool {
35
+ let b = float_bits(v);
36
+ {% if usesF16 %}
37
+ return (b & 0x7c00u) == 0x7c00u && (b & 0x03ffu) != 0u;
38
+ {% else %}
39
+ return (b & 0x7f800000u) == 0x7f800000u && (b & 0x007fffffu) != 0u;
40
+ {% endif %}
41
+ }
42
+ {%- endmacro %}
43
 
44
+ {{ float_bits_def() }}
45
+
46
+ {{ is_nan_bits_def() }}
47
+
48
+ {% endif %}
49
  fn key_bits(v: {{ scalar }}) -> u32 {
50
+ {% if isFloat %}
51
+ let bits = float_bits(v);
52
+ return select(bits, 0u, bits == {{ "0x8000u" if usesF16 else "0x80000000u" }});
53
+ {% elif isUnsigned %}
54
  return v;
55
  {% else %}
56
  return bitcast<u32>(v);
 
72
  if (i >= params.inputCount) {
73
  return;
74
  }
75
+ {% if isFloat %}
76
+ // Preserve the leading NaN's exact payload through the original X value.
77
+ if (is_nan_bits(x[0])) {
78
+ flags[i] = select(0u, 1u, i == 0u);
79
+ return;
80
+ }
81
+ if (is_nan_bits(x[i])) {
82
+ flags[i] = 0u;
83
+ return;
84
+ }
85
+ {% endif %}
86
  let k = key_bits(x[i]);
87
  if (k == EMPTY) {
88
  flags[i] = select(0u, 1u, atomicLoad(&special[0]) == i);
build/webgpu/unique-hash-sort-collected-key-only.wgsl.jinja CHANGED
@@ -1,8 +1,8 @@
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
4
- const CAP: u32 = {{ source.capacity }}u;
5
- const SORT_N: u32 = {{ source.sortN }}u;
6
 
7
  // The monotonic key transform is invertible, so the workgroup sort only needs
8
  // one u32 per slot. Padding uses the maximum key; if a real maximum key exists,
 
1
  {{ env.wgsl.resourceDeclarations }}
2
 
3
  const WG: u32 = {{ tunables.WORKGROUP_SIZE }}u;
4
+ const CAP: u32 = {{ capacity }}u;
5
+ const SORT_N: u32 = {{ sortN }}u;
6
 
7
  // The monotonic key transform is invertible, so the workgroup sort only needs
8
  // one u32 per slot. Padding uses the maximum key; if a real maximum key exists,
build/webgpu/unique-scalar-compact.wgsl.jinja ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if sorted and usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+ {% if sorted %}
6
+ {% if isFloat %}
7
+ // IEEE total-order comparators for Unique. Floating-point equality and ordering
8
+ // use raw bits because GPUs may flush subnormals in float comparisons, which
9
+ // would otherwise collapse distinct values. Equality canonicalizes -0 to +0;
10
+ // ordering uses a monotonic u32 key over the real line. Integers use == and <.
11
+ {% macro float_bits_def() %}
12
+ fn float_bits(v: {{ scalar }}) -> u32 {
13
+ {% if usesF16 %}
14
+ // WGSL has no scalar u16 type. Packing v into the low component preserves
15
+ // its binary16 representation while producing a bitcast-compatible 32 bits.
16
+ return bitcast<u32>(vec2<f16>(v, 0.0h)) & 0xffffu;
17
+ {% else %}
18
+ return bitcast<u32>(v);
19
+ {% endif %}
20
+ }
21
+ {%- endmacro %}
22
+
23
+ {{ float_bits_def() }}
24
+ {% endif %}
25
+ fn ordered_key(value: {{ scalar }}) -> u32 {
26
+ {% if isFloat %}
27
+ let bits = float_bits(value);
28
+ return select(bits | {{ "0x8000u" if usesF16 else "0x80000000u" }}, (~bits) & {{ "0xffffu" if usesF16 else "0xffffffffu" }}, (bits & {{ "0x8000u" if usesF16 else "0x80000000u" }}) != 0u);
29
+ {% elif isUnsigned %}
30
+ return value;
31
+ {% else %}
32
+ return bitcast<u32>(value) ^ 0x80000000u;
33
+ {% endif %}
34
+ }
35
+ {% endif %}
36
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
37
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
38
+ let i = gid.x;
39
+ if (i < {{ scanN }}u) {
40
+ let encoded = flags[i];
41
+ if ((encoded & 1u) != 0u) {
42
+ let position = blockPrefix[i / {{ tunables.WORKGROUP_SIZE }}u] + (encoded >> 1u);
43
+ if (position < {{ capacity }}u) {
44
+ sortVal[position] = i;
45
+ {% if sorted %}
46
+ sortKey[position] = ordered_key(x[i]);
47
+ sortPad[position] = 0u;
48
+ {% endif %}
49
+ }
50
+ }
51
+ }
52
+ {% if sorted %}
53
+ // Exact output cardinality is part of Unique's required request contract.
54
+ if (i >= {{ capacity }}u && i < {{ sortN }}u) {
55
+ sortPad[i] = 1u;
56
+ sortKey[i] = 0u;
57
+ sortVal[i] = 0u;
58
+ }
59
+ {% endif %}
60
+ }
build/webgpu/unique-scalar-metadata.wgsl.jinja ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+ // Open-addressing table constants and key/hash helpers.
6
+ const EMPTY: u32 = 0xffffffffu;
7
+ const MASK: u32 = {{ tableSize }}u - 1u;
8
+
9
+ {% if isFloat %}
10
+ // IEEE total-order comparators for Unique. Floating-point equality and ordering
11
+ // use raw bits because GPUs may flush subnormals in float comparisons, which
12
+ // would otherwise collapse distinct values. Equality canonicalizes -0 to +0;
13
+ // ordering uses a monotonic u32 key over the real line. Integers use == and <.
14
+ {% macro float_bits_def() %}
15
+ fn float_bits(v: {{ scalar }}) -> u32 {
16
+ {% if usesF16 %}
17
+ // WGSL has no scalar u16 type. Packing v into the low component preserves
18
+ // its binary16 representation while producing a bitcast-compatible 32 bits.
19
+ return bitcast<u32>(vec2<f16>(v, 0.0h)) & 0xffffu;
20
+ {% else %}
21
+ return bitcast<u32>(v);
22
+ {% endif %}
23
+ }
24
+ {%- endmacro -%}
25
+ {%- macro is_nan_bits_def() %}
26
+ fn is_nan_bits(v: {{ scalar }}) -> bool {
27
+ let b = float_bits(v);
28
+ {% if usesF16 %}
29
+ return (b & 0x7c00u) == 0x7c00u && (b & 0x03ffu) != 0u;
30
+ {% else %}
31
+ return (b & 0x7f800000u) == 0x7f800000u && (b & 0x007fffffu) != 0u;
32
+ {% endif %}
33
+ }
34
+ {%- endmacro %}
35
+
36
+ {{ float_bits_def() }}
37
+
38
+ {{ is_nan_bits_def() }}
39
+
40
+ {% endif %}
41
+ fn key_bits(v: {{ scalar }}) -> u32 {
42
+ {% if isFloat %}
43
+ let bits = float_bits(v);
44
+ return select(bits, 0u, bits == {{ "0x8000u" if usesF16 else "0x80000000u" }});
45
+ {% elif isUnsigned %}
46
+ return v;
47
+ {% else %}
48
+ return bitcast<u32>(v);
49
+ {% endif %}
50
+ }
51
+ fn hash_key(k: u32) -> u32 {
52
+ var x = k;
53
+ x = x ^ (x >> 16u);
54
+ x = x * 0x7feb352du;
55
+ x = x ^ (x >> 15u);
56
+ x = x * 0x846ca68bu;
57
+ x = x ^ (x >> 16u);
58
+ return x;
59
+ }
60
+
61
+ {% if isFloat and not sorted %}
62
+ fn ordered_key(key: u32) -> u32 {
63
+ {% if usesF16 %}
64
+ return select(key ^ 0x8000u, (~key) & 0xffffu, (key & 0x8000u) != 0u);
65
+ {% else %}
66
+ return select(key ^ 0x80000000u, ~key, (key & 0x80000000u) != 0u);
67
+ {% endif %}
68
+ }
69
+ {% endif %}
70
+ fn find_rank(i: u32) -> u32 {
71
+ {% if isFloat %}
72
+ if (is_nan_bits(x[0])) { return 0u; }
73
+ // Later NaNs joined the smallest representative that already existed.
74
+ // Scan distinct representatives, excluding ones first seen after this input.
75
+ // Sorted slots can stop at their first eligible representative; unsorted
76
+ // slots appear in first-occurrence order, so later slots can be excluded.
77
+ if (is_nan_bits(x[i])) {
78
+ let written = {{ capacity }}u;
79
+ {% if not sorted %}
80
+ var best = 0u;
81
+ var bestKey = ordered_key(key_bits(x[slots[0]]));
82
+ {% endif %}
83
+ for (var p = 0u; p < written; p += 1u) {
84
+ let first = slots[p];
85
+ {% if sorted %}
86
+ if (first <= i) { return p; }
87
+ {% else %}
88
+ if (first > i) { break; }
89
+ let candidate = ordered_key(key_bits(x[first]));
90
+ if (candidate < bestKey) { best = p; bestKey = candidate; }
91
+ {% endif %}
92
+ }
93
+ return {{ "0u" if sorted else "best" }};
94
+ }
95
+ {% endif %}
96
+ let key = key_bits(x[i]);
97
+ // The first output class is common in repeated-value streams.
98
+ if (key == key_bits(x[slots[0]])) { return 0u; }
99
+ if (key == EMPTY) { return rankOfFirst[special[0]]; }
100
+ var slot = hash_key(key) & MASK;
101
+ loop {
102
+ let stored = tableKey[slot];
103
+ if (stored == key) {
104
+ return rankOfFirst[tableIdx[slot]];
105
+ }
106
+ if (stored == EMPTY) { return 0u; }
107
+ slot = (slot + 1u) & MASK;
108
+ }
109
+ }
110
+
111
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
112
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
113
+ let i = gid.x;
114
+ if (i >= {{ scanN }}u) { return; }
115
+ let rank = find_rank(i);
116
+ {% if hasInverseIndices %}
117
+ inverse_indices[i] = rank;
118
+ {% endif %}
119
+ {% if hasCounts %}
120
+ atomicAdd(&counts[rank], 1u);
121
+ {% endif %}
122
+ }
build/webgpu/unique-scalar-output.wgsl.jinja ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
6
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
7
+ let p = gid.x;
8
+ if (p >= {{ capacity }}u) { return; }
9
+ let first = slots[p];
10
+ y[p] = x[first];
11
+ {% if hasIndices %}
12
+ indices[p] = first;
13
+ {% endif %}
14
+ {% if needsRanks %}
15
+ rankOfFirst[first] = p;
16
+ {% endif %}
17
+ {% if hasCounts %}
18
+ counts[p] = 0u;
19
+ {% endif %}
20
+ }
build/webgpu/unique-single-class.wgsl.jinja ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if usesF16 %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+ // Exact output cardinality is required by the op contract. One class proves
6
+ // that every input belongs to the first representative, including NaNs and
7
+ // whole axis slices. No hash, scan, sort or atomic count is necessary.
8
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
9
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
10
+ let i = gid.x;
11
+ if (i < {{ outputElements }}u) {
12
+ {% if axisPresent %}
13
+ let source = (i / {{ scalarInner }}u) * {{ inputExtent * scalarInner }}u + i % {{ scalarInner }}u;
14
+ y[i] = x[source];
15
+ {% else %}
16
+ y[i] = x[0];
17
+ {% endif %}
18
+ }
19
+ {% if hasInverseIndices %}
20
+ if (i < {{ inputExtent }}u) { inverse_indices[i] = 0u; }
21
+ {% endif %}
22
+ {% if hasIndices or hasCounts %}
23
+ if (i == 0u) {
24
+ {% if hasIndices %}
25
+ indices[0] = 0u;
26
+ {% endif %}
27
+ {% if hasCounts %}
28
+ counts[0] = {{ inputExtent }}u;
29
+ {% endif %}
30
+ }
31
+ {% endif %}
32
+ }
build/webgpu/unique.wgsl.jinja CHANGED
@@ -65,12 +65,9 @@ fn is_nan_bits(v: {{ scalar }}) -> bool {
65
  }
66
  {%- endmacro %}
67
 
68
- {% set emitIndices = source.hasIndices | default(false) %}
69
- {% set emitInverseIndices = source.hasInverseIndices | default(false) %}
70
- {% set emitCounts = source.hasCounts | default(false) %}
71
- {% if usesF16 %}
72
- enable f16;
73
- {% endif %}
74
  {{ env.wgsl.resourceDeclarations }}
75
 
76
  // Floating-point equality/order use the raw IEEE bit pattern, not the float
@@ -103,6 +100,10 @@ fn ordered_less(a: {{ scalar }}, b: {{ scalar }}) -> bool {
103
  }
104
 
105
  {% if emitIndices or emitInverseIndices or emitCounts %}
 
 
 
 
106
  fn representative_input_index(output_i: u32) -> u32 {
107
  let representative = y[output_i];
108
  for (var input_i = 0u; input_i < params.inputCount; input_i = input_i + 1u) {
@@ -120,7 +121,7 @@ fn find_bucket(input_i: u32, written: u32) -> u32 {
120
  let value = x[input_i];
121
  var candidate = written;
122
  for (var output_i = 0u; output_i < written; output_i = output_i + 1u) {
123
- if (representative_input_index(output_i) > input_i || ordered_less(y[output_i], value)) { continue; }
124
  if (candidate == written || ordered_less(y[output_i], y[candidate])) {
125
  candidate = output_i;
126
  }
@@ -168,9 +169,14 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
168
  for (var i = written; i < params.capacity; i = i + 1u) {
169
  y[i] = zero_value();
170
  }
 
 
 
 
 
171
  {% if emitIndices %}
172
  for (var unique_i = 0u; unique_i < params.capacity; unique_i = unique_i + 1u) {
173
- indices[unique_i] = representative_input_index(unique_i);
174
  }
175
  {% endif %}
176
  {% if emitCounts %}
 
65
  }
66
  {%- endmacro %}
67
 
68
+ {% set emitIndices = hasIndices | default(false) %}
69
+ {% set emitInverseIndices = hasInverseIndices | default(false) %}
70
+ {% set emitCounts = hasCounts | default(false) %}
 
 
 
71
  {{ env.wgsl.resourceDeclarations }}
72
 
73
  // Floating-point equality/order use the raw IEEE bit pattern, not the float
 
100
  }
101
 
102
  {% if emitIndices or emitInverseIndices or emitCounts %}
103
+ // The representative of an output slot is a pure function of the finished Y and
104
+ // of X, but the bucket resolution below asks for it once per (input, output)
105
+ // pair, which makes an O(n) scan the innermost of three nested loops. It is
106
+ // computed once per slot into `representatives` and read from there instead.
107
  fn representative_input_index(output_i: u32) -> u32 {
108
  let representative = y[output_i];
109
  for (var input_i = 0u; input_i < params.inputCount; input_i = input_i + 1u) {
 
121
  let value = x[input_i];
122
  var candidate = written;
123
  for (var output_i = 0u; output_i < written; output_i = output_i + 1u) {
124
+ if (representatives[output_i] > input_i || ordered_less(y[output_i], value)) { continue; }
125
  if (candidate == written || ordered_less(y[output_i], y[candidate])) {
126
  candidate = output_i;
127
  }
 
169
  for (var i = written; i < params.capacity; i = i + 1u) {
170
  y[i] = zero_value();
171
  }
172
+ {% if emitIndices or emitInverseIndices or emitCounts %}
173
+ for (var unique_i = 0u; unique_i < params.capacity; unique_i = unique_i + 1u) {
174
+ representatives[unique_i] = representative_input_index(unique_i);
175
+ }
176
+ {% endif %}
177
  {% if emitIndices %}
178
  for (var unique_i = 0u; unique_i < params.capacity; unique_i = unique_i + 1u) {
179
+ indices[unique_i] = representatives[unique_i];
180
  }
181
  {% endif %}
182
  {% if emitCounts %}