Xenova HF Staff commited on
Commit
8b49b15
·
verified ·
1 Parent(s): 40f0ca1

sync 91d990483a17

Browse files
README.md CHANGED
@@ -18,17 +18,17 @@ See the [ONNX `QuantizeLinear` spec](https://onnx.ai/onnx/operators/onnx__Quanti
18
 
19
  ## Inputs
20
 
21
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
- | --- | --- | --- | --- | --- | --- | --- |
23
- | `x` | `x` | `TX` | — | — | N-D full-precision input tensor to be quantized. | required |
24
- | `y_scale` | `y_scale` | `TS` | — | — | Scale factor; scalar for per-tensor, 1-D for per-axis, or same rank as `x` (with one axis blocked) for blocked quantization. | required |
25
- | `y_zero_point` | `y_zero_point` | `TQ` | — | — | Zero point for quantization; must have the same shape as `y_scale`. Defaults to zero if omitted. | optional |
26
 
27
  ## Outputs
28
 
29
- | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
30
- | --- | --- | --- | --- | --- | --- | --- |
31
- | `y` | `y` | `TQ` | same as `x` | same as `x` | N-D quantized output tensor with the same shape as `x`. | required |
32
 
33
  ## Attributes
34
 
@@ -52,7 +52,7 @@ Default values (overridable per request):
52
 
53
  ## Files
54
 
55
- - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
56
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
57
  - [`test.json`](build/webgpu/test.json) — correctness cases
58
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
@@ -62,15 +62,18 @@ Default values (overridable per request):
62
 
63
  ## Use with `@huggingface/kernels`
64
 
65
- The loader automatically allocates outputs whose metadata it can derive from the manifest contract and this call.
 
 
66
 
67
- The explicit `outputs` entries provide shape and logical dtype metadata for the results listed below:
68
 
69
- - `y`
70
 
71
- Each entry either requests an optional result or supplies metadata that cannot be inferred from the inputs.
72
 
73
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
 
74
 
75
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
76
 
 
18
 
19
  ## Inputs
20
 
21
+ | Name | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- |
23
+ | `x` | `TX` | — | — | N-D full-precision input tensor to be quantized. | required |
24
+ | `y_scale` | `TS` | — | — | Scale factor; scalar for per-tensor, 1-D for per-axis, or same rank as `x` (with one axis blocked) for blocked quantization. | required |
25
+ | `y_zero_point` | `TQ` | — | — | Zero point for quantization; must have the same shape as `y_scale`. Defaults to zero if omitted. | optional |
26
 
27
  ## Outputs
28
 
29
+ | Name | Logical dtype | Rank | Shape | Description | Presence |
30
+ | --- | --- | --- | --- | --- | --- |
31
+ | `y` | `TQ` | same as `x` | same as `x` | N-D quantized output tensor with the same shape as `x`. | required |
32
 
33
  ## Attributes
34
 
 
52
 
53
  ## Files
54
 
55
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, provenance)
56
  - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
57
  - [`test.json`](build/webgpu/test.json) — correctness cases
58
  - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
 
62
 
63
  ## Use with `@huggingface/kernels`
64
 
65
+ ```sh
66
+ npm install --save-exact @huggingface/kernels@0.0.1-preview.2
67
+ ```
68
 
69
+ 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.
70
 
71
+ This example supplies explicit metadata for:
72
 
73
+ - `y`
74
 
75
  The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
76
+ It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
77
 
78
  Replace each `*Data` placeholder with a typed array containing the corresponding input data.
79
 
build/webgpu/bench.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.QuantizeLinear",
3
  "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
4
  "cases": [
5
  {
 
1
  {
 
2
  "tunableSpace": { "WORKGROUP_SIZE": [64, 128, 256] },
3
  "cases": [
4
  {
build/webgpu/manifest.json CHANGED
@@ -2,51 +2,26 @@
2
  "domain": "ai.onnx",
3
  "name": "QuantizeLinear",
4
  "sinceVersion": 25,
5
- "description": "Linearly quantizes a high-precision tensor to a lower-precision integer type using the formula `y = saturate((x / y_scale) + y_zero_point)`, with rounding to nearest even. Supports per-tensor, per-axis, and blocked quantization granularities determined by the shape of `y_scale`.",
6
- "inputs": [
7
- { "role": "x", "dtype": "TX", "description": "N-D full-precision input tensor to be quantized." },
8
- {
9
- "role": "y_scale",
10
- "dtype": "TS",
11
- "description": "Scale factor; scalar for per-tensor, 1-D for per-axis, or same rank as `x` (with one axis blocked) for blocked quantization."
12
- },
13
- {
14
- "role": "y_zero_point",
15
- "dtype": "TQ",
16
- "optional": true,
17
- "description": "Zero point for quantization; must have the same shape as `y_scale`. Defaults to zero if omitted."
18
- }
19
- ],
20
- "outputs": [
21
- {
22
- "role": "y",
23
- "dtype": "TQ",
24
- "rank": "ranks.x",
25
- "description": "N-D quantized output tensor with the same shape as `x`.",
26
- "shape": "shapes.x"
27
- }
28
- ],
29
- "attributes": { "axis": 1, "block_size": 0, "output_dtype": 0, "precision": 0, "saturate": 1 },
30
- "attributeDescriptions": {
31
- "axis": "Axis of the quantization dimension in `x`, used for per-axis and blocked quantization; negative values count from the end.",
32
- "block_size": "Number of elements along `axis` that share a single scale value for blocked quantization; 0 means blocked quantization is not used.",
33
- "output_dtype": "ONNX TensorProto element-type code for `y`; 0 infers the type from `y_zero_point`, or uint8 when the zero point is omitted.",
34
- "precision": "ONNX TensorProto element-type code used for `x / y_scale`; `0` uses the dtype of `y_scale`, `1` selects FLOAT, and `10` selects FLOAT16.",
35
- "saturate": "Controls out-of-range conversion for float8 outputs. The implemented int8/uint8 subset accepts the ONNX default `1`."
36
  },
37
  "attributeConstraints": { "precision": { "values": [0, 1, 10] }, "saturate": { "values": [1] } },
38
  "typeConstraints": { "TX": ["float32", "float16"], "TS": ["float32", "float16"], "TQ": ["uint8", "int8"] },
39
- "args": {
40
- "x": { "kind": "tensor", "semantic": "x", "role": "input" },
41
- "y_scale": { "kind": "tensor", "semantic": "y_scale", "role": "input" },
42
- "y_zero_point": { "kind": "tensor", "semantic": "y_zero_point", "role": "input", "required": false },
43
- "y": { "kind": "tensor", "semantic": "y", "role": "output" }
44
- },
45
- "tunables": { "WORKGROUP_SIZE": 256, "VEC4_TAIL_MIN_ELEMENTS": 4096 },
46
  "derive": {
47
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
48
  "workgroupOk": "tunables.WORKGROUP_SIZE <= deviceWorkgroupCap",
49
- "outputDispatchFits": "workgroupOk and ceilDiv(ceilDiv(numel(shapes.y), tunables.WORKGROUP_SIZE), device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension",
50
  "outputDtypeOk": "(attrs.output_dtype == 0 and (present.y_zero_point or tensorDtypes.y == \"uint8\")) or attrs.output_dtype == onnxDtypeCode(logicalDtypes.TQ)",
51
  "quantizeDivisionF16": "attrs.precision == onnxDtypeCode(\"float16\") or (attrs.precision == 0 and tensorDtypes.y_scale == \"float16\")",
52
  "sameShapeOk": "ranks.y == ranks.x and numel(shapes.x) == numel(shapes.y) and outputDispatchFits and outputDtypeOk",
@@ -58,13 +33,8 @@
58
  "scaleInner": "1 if ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1 else inner(shapes.x, attrs.axis)",
59
  "blockedAxisDim": "dim(shapes.x, attrs.axis) if ranks.x >= 2 else 1",
60
  "blockedScaleAxisDim": "dim(shapes.y_scale, attrs.axis) if ranks.y_scale >= 2 else 1",
61
- "blockedInner": "inner(shapes.x, attrs.axis) if ranks.x >= 2 else 1"
62
- },
63
- "constants": {
64
- "usesF16": "dtypes.TX == \"f16\" or dtypes.TS == \"f16\" or quantizeDivisionF16",
65
- "xScalar": "dtypes.TX",
66
  "xVec4": "\"vec4<\" ~ dtypes.TX ~ \">\"",
67
- "scaleScalar": "dtypes.TS",
68
  "scaleVec4": "\"vec4<\" ~ dtypes.TS ~ \">\"",
69
  "yScalar": "dtypes.TQ",
70
  "yVec4": "\"vec4<\" ~ dtypes.TQ ~ \">\"",
@@ -73,767 +43,407 @@
73
  "qMax": "255 if tensorDtypes.y == \"uint8\" else 127",
74
  "divisionF16": "quantizeDivisionF16"
75
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  "variants": [
77
  {
78
  "id": "innermost_axis_vec4_with_zero_point",
79
  "priority": 20,
80
- "when": ["present.y_zero_point", "ranks.x >= 1", "attrs.axis == -1 or attrs.axis == ranks.x - 1", "ranks.y_scale == 1", "dim(shapes.y_scale, 0) == dim(shapes.x, ranks.x - 1)", "dim(shapes.y_scale, 0) % 4 == 0", "ranks.y_zero_point == 1", "dim(shapes.y_zero_point, 0) == dim(shapes.y_scale, 0)", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "sameShapeOk", "quantDtypesOk"],
81
- "constants": { "hasZero": true },
82
  "passes": [
83
  {
84
  "id": "main",
85
  "name": "QuantizeLinear.InnermostAxisVec4",
86
- "source": {
87
- "shader": "quant-linear-vec4.wgsl.jinja",
88
- "inputs": { "op": "\"quantize\"", "perAxis": true, "vectorParams": true }
89
- },
90
- "bindings": "linearInnermostVec4WithZero",
91
- "dispatch": { "threads": "floor(numel(shapes.y) / 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
92
  }
93
  ]
94
  },
95
  {
96
  "id": "innermost_axis_vec4_no_zero_point",
97
  "priority": 20,
98
- "when": ["not present.y_zero_point", "ranks.x >= 1", "attrs.axis == -1 or attrs.axis == ranks.x - 1", "ranks.y_scale == 1", "dim(shapes.y_scale, 0) == dim(shapes.x, ranks.x - 1)", "dim(shapes.y_scale, 0) % 4 == 0", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "sameShapeOk", "quantDtypesOk"],
99
- "constants": { "hasZero": false },
100
  "passes": [
101
  {
102
  "id": "main",
103
  "name": "QuantizeLinear.InnermostAxisVec4NoZero",
104
- "source": {
105
- "shader": "quant-linear-vec4.wgsl.jinja",
106
- "inputs": { "op": "\"quantize\"", "perAxis": true, "vectorParams": true }
107
- },
108
- "bindings": "linearInnermostVec4NoZero",
109
- "dispatch": { "threads": "floor(numel(shapes.y) / 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
110
  }
111
  ]
112
  },
113
  {
114
  "id": "vec4_cross_axis_with_zero_point",
115
  "priority": 16,
116
- "when": ["present.y_zero_point", "attrs.block_size == 0", "ranks.x >= 1", "attrs.axis >= 0", "attrs.axis < ranks.x", "ranks.y_scale == 1", "dim(shapes.y_scale, 0) == dim(shapes.x, attrs.axis)", "inner(shapes.x, attrs.axis) % 4 != 0", "ranks.y_zero_point == 1", "dim(shapes.y_zero_point, 0) == dim(shapes.y_scale, 0)", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "sameShapeOk", "quantDtypesOk"],
117
- "constants": { "hasZero": true },
118
  "passes": [
119
  {
120
  "id": "main",
121
  "name": "QuantizeLinear.Vec4CrossAxis",
122
- "source": {
123
- "shader": "quant-linear-vec4.wgsl.jinja",
124
- "inputs": { "op": "\"quantize\"", "perAxis": true, "crossingParams": true }
125
- },
126
- "bindings": "linearVec4WithZero",
127
- "dispatch": { "threads": "floor(numel(shapes.y) / 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
128
  }
129
  ]
130
  },
131
  {
132
  "id": "vec4_with_zero_point",
133
  "priority": 15,
134
- "when": ["present.y_zero_point", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "(ranks.y_scale == 0 or ranks.y_scale == 1)", "sameShapeOk", "(ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1 or (ranks.x >= 1 and dim(shapes.y_scale, 0) == dim(shapes.x, attrs.axis) and inner(shapes.x, attrs.axis) % 4 == 0))", "(ranks.y_zero_point == 0 or ranks.y_zero_point == 1)", "(ranks.y_zero_point == 0 or dim(shapes.y_zero_point, 0) == 1 or (ranks.x >= 1 and dim(shapes.y_zero_point, 0) == dim(shapes.x, attrs.axis)))", "quantDtypesOk"],
135
- "constants": { "hasZero": true },
136
  "passes": [
137
  {
138
  "id": "main",
139
  "name": "QuantizeLinear.Vec4",
140
- "source": {
141
- "shader": "quant-linear-vec4.wgsl.jinja",
142
- "inputs": { "op": "\"quantize\"", "perAxis": "not (ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1)" }
143
- },
144
- "bindings": "linearVec4WithZero",
145
- "dispatch": { "threads": "floor(numel(shapes.y) / 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
146
  }
147
  ]
148
  },
149
  {
150
  "id": "vec4_no_zero_point",
151
  "priority": 15,
152
- "when": ["not present.y_zero_point", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "(ranks.y_scale == 0 or ranks.y_scale == 1)", "sameShapeOk", "(ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1 or (ranks.x >= 1 and dim(shapes.y_scale, 0) == dim(shapes.x, attrs.axis) and inner(shapes.x, attrs.axis) % 4 == 0))", "quantDtypesOk"],
153
- "constants": { "hasZero": false },
154
  "passes": [
155
  {
156
  "id": "main",
157
  "name": "QuantizeLinear.Vec4",
158
- "source": {
159
- "shader": "quant-linear-vec4.wgsl.jinja",
160
- "inputs": { "op": "\"quantize\"", "perAxis": "not (ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1)" }
161
- },
162
- "bindings": "linearVec4NoZero",
163
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
164
  }
165
  ]
166
  },
167
  {
168
  "id": "vec4_tail_with_zero_point",
169
  "priority": 12,
170
- "when": ["present.y_zero_point", "numel(shapes.y) >= tunables.VEC4_TAIL_MIN_ELEMENTS", "numel(shapes.y) % 4 != 0", "(ranks.y_scale == 0 or (ranks.y_scale == 1 and dim(shapes.y_scale, 0) == 1))", "(ranks.y_zero_point == 0 or (ranks.y_zero_point == 1 and dim(shapes.y_zero_point, 0) == 1))", "sameShapeOk", "quantDtypesOk"],
171
- "constants": { "hasZero": true },
172
  "passes": [
173
  {
174
  "id": "bulk",
175
  "name": "QuantizeLinear.Vec4Bulk",
176
- "source": { "shader": "quant-linear-vec4.wgsl.jinja", "inputs": { "op": "\"quantize\"", "perAxis": false } },
177
- "bindings": "linearVec4PerTensorWithZero",
178
- "dispatch": { "threads": "floor(numel(shapes.y) / 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
179
  },
180
  {
181
  "id": "tail",
182
  "name": "QuantizeLinear.ScalarTail",
183
- "source": {
184
- "shader": "quant-linear-scalar.wgsl.jinja",
185
- "inputs": { "op": "\"quantize\"", "x4": true, "perAxis": false }
186
- },
187
- "bindings": "linearScalarPerTensorWithZero",
188
- "dispatch": { "threads": 1, "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
189
  }
190
  ]
191
  },
192
  {
193
  "id": "vec4_tail_no_zero_point",
194
  "priority": 12,
195
- "when": ["not present.y_zero_point", "numel(shapes.y) >= tunables.VEC4_TAIL_MIN_ELEMENTS", "numel(shapes.y) % 4 != 0", "(ranks.y_scale == 0 or (ranks.y_scale == 1 and dim(shapes.y_scale, 0) == 1))", "sameShapeOk", "quantDtypesOk"],
196
- "constants": { "hasZero": false },
197
  "passes": [
198
  {
199
  "id": "bulk",
200
  "name": "QuantizeLinear.Vec4Bulk",
201
- "source": { "shader": "quant-linear-vec4.wgsl.jinja", "inputs": { "op": "\"quantize\"", "perAxis": false } },
202
- "bindings": "linearVec4PerTensorNoZero",
203
- "dispatch": { "threads": "floor(numel(shapes.y) / 4)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
204
  },
205
  {
206
  "id": "tail",
207
  "name": "QuantizeLinear.ScalarTail",
208
- "source": {
209
- "shader": "quant-linear-scalar.wgsl.jinja",
210
- "inputs": { "op": "\"quantize\"", "x4": true, "perAxis": false }
211
- },
212
- "bindings": "linearScalarPerTensorNoZero",
213
- "dispatch": { "threads": 1, "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
214
  }
215
  ]
216
  },
217
  {
218
  "id": "blocked_last_axis_vec4_with_zero_point",
219
  "priority": 24,
220
- "when": ["present.y_zero_point", "blockedScaleOk", "attrs.block_size % 4 == 0", "ranks.y_zero_point == ranks.x", "sameShapeOk", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "inner(shapes.x, attrs.axis) == 1", "dim(shapes.x, attrs.axis) % attrs.block_size == 0", "dim(shapes.y_scale, attrs.axis) == dim(shapes.x, attrs.axis) / attrs.block_size", "numel(shapes.y_zero_point) == numel(shapes.y_scale)", "tensorDtypes.x == \"float32\"", "tensorDtypes.y_scale == \"float32\""],
221
- "constants": { "hasZero": true, "blockVectors": "attrs.block_size / 4" },
222
  "passes": [
223
  {
224
  "id": "main",
225
  "name": "QuantizeLinear.BlockedLastAxisVec4",
226
- "source": { "shader": "quant-linear-blocked-axis.wgsl.jinja", "inputs": { "lastAxisVectorized": true } },
227
- "bindings": "blockedLastVec4WithZero",
228
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
229
  }
230
  ]
231
  },
232
  {
233
  "id": "blocked_last_axis_vec4_no_zero_point",
234
  "priority": 23,
235
- "when": ["not present.y_zero_point", "blockedScaleOk", "attrs.block_size % 4 == 0", "sameShapeOk", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "inner(shapes.x, attrs.axis) == 1", "dim(shapes.x, attrs.axis) % attrs.block_size == 0", "dim(shapes.y_scale, attrs.axis) == dim(shapes.x, attrs.axis) / attrs.block_size", "tensorDtypes.x == \"float32\"", "tensorDtypes.y_scale == \"float32\""],
236
- "constants": { "hasZero": false, "blockVectors": "attrs.block_size / 4" },
237
  "passes": [
238
  {
239
  "id": "main",
240
  "name": "QuantizeLinear.BlockedLastAxisVec4",
241
- "source": { "shader": "quant-linear-blocked-axis.wgsl.jinja", "inputs": { "lastAxisVectorized": true } },
242
- "bindings": "blockedLastVec4NoZero",
243
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
244
  }
245
  ]
246
  },
247
  {
248
  "id": "blocked_vec4_with_zero_point",
249
  "priority": 22,
250
- "when": ["present.y_zero_point", "blockedScaleOk", "ranks.y_zero_point == ranks.x", "sameShapeOk", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "numel(shapes.y_scale) % 4 == 0", "numel(shapes.y_zero_point) == numel(shapes.y_scale)", "inner(shapes.x, attrs.axis) % 4 == 0", "dim(shapes.y_scale, attrs.axis) == ceil(dim(shapes.x, attrs.axis) / attrs.block_size)", "dim(shapes.y_zero_point, attrs.axis) == dim(shapes.y_scale, attrs.axis)", "tensorDtypes.x == \"float32\"", "tensorDtypes.y_scale == \"float32\""],
251
- "constants": { "hasZero": true },
252
  "passes": [
253
  {
254
  "id": "main",
255
  "name": "QuantizeLinear.BlockedVec4WithZeroPoint",
256
- "source": { "shader": "quant-linear-blocked-axis.wgsl.jinja", "inputs": { "vectorized": true } },
257
- "bindings": "blockedVec4WithZero",
258
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
259
  }
260
  ]
261
  },
262
  {
263
  "id": "blocked_vec4_no_zero_point",
264
  "priority": 21,
265
- "when": ["not present.y_zero_point", "blockedScaleOk", "sameShapeOk", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "numel(shapes.y_scale) % 4 == 0", "inner(shapes.x, attrs.axis) % 4 == 0", "dim(shapes.y_scale, attrs.axis) == ceil(dim(shapes.x, attrs.axis) / attrs.block_size)", "tensorDtypes.x == \"float32\"", "tensorDtypes.y_scale == \"float32\""],
266
- "constants": { "hasZero": false },
267
  "passes": [
268
  {
269
  "id": "main",
270
  "name": "QuantizeLinear.BlockedVec4",
271
- "source": { "shader": "quant-linear-blocked-axis.wgsl.jinja", "inputs": { "vectorized": true } },
272
- "bindings": "blockedVec4NoZero",
273
- "dispatch": { "threads": "numel(shapes.y) / 4", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
 
274
  }
275
  ]
276
  },
277
  {
278
  "id": "blocked_with_zero_point",
279
  "priority": 19,
280
- "when": ["present.y_zero_point", "blockedScaleOk", "ranks.y_zero_point == ranks.x", "sameShapeOk", "dim(shapes.y_scale, attrs.axis) == ceil(dim(shapes.x, attrs.axis) / attrs.block_size)", "dim(shapes.y_zero_point, attrs.axis) == dim(shapes.y_scale, attrs.axis)", "quantDtypesOk"],
281
- "constants": { "hasZero": true },
282
  "passes": [
283
  {
284
  "id": "main",
285
  "name": "QuantizeLinear.Blocked",
286
  "shader": "quant-linear-blocked-axis.wgsl.jinja",
287
- "bindings": "blockedScalarWithZero",
288
- "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
289
  }
290
  ]
291
  },
292
  {
293
  "id": "blocked_no_zero_point",
294
  "priority": 19,
295
- "when": ["not present.y_zero_point", "blockedScaleOk", "sameShapeOk", "dim(shapes.y_scale, attrs.axis) == ceil(dim(shapes.x, attrs.axis) / attrs.block_size)", "quantDtypesOk"],
296
- "constants": { "hasZero": false },
297
  "passes": [
298
  {
299
  "id": "main",
300
  "name": "QuantizeLinear.BlockedNoZero",
301
  "shader": "quant-linear-blocked-axis.wgsl.jinja",
302
- "bindings": "blockedScalarNoZero",
303
- "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
304
  }
305
  ]
306
  },
307
  {
308
  "id": "with_zero_point",
309
  "priority": 10,
310
- "when": ["present.y_zero_point", "(ranks.y_scale == 0 or ranks.y_scale == 1)", "sameShapeOk", "(ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1 or (ranks.x >= 1 and dim(shapes.y_scale, 0) == dim(shapes.x, attrs.axis)))", "(ranks.y_zero_point == 0 or ranks.y_zero_point == 1)", "(ranks.y_zero_point == 0 or dim(shapes.y_zero_point, 0) == 1 or (ranks.x >= 1 and dim(shapes.y_zero_point, 0) == dim(shapes.x, attrs.axis)))", "quantDtypesOk"],
311
- "constants": { "hasZero": true },
312
  "passes": [
313
  {
314
  "id": "main",
315
  "name": "QuantizeLinear",
316
- "source": {
317
- "shader": "quant-linear-scalar.wgsl.jinja",
318
- "inputs": {
319
- "op": "\"quantize\"",
320
- "x4": "false",
321
- "perAxis": "not (ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1)"
322
- }
323
  },
324
- "bindings": "linearScalarWithZero",
325
- "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
326
  }
327
  ]
328
  },
329
  {
330
  "id": "no_zero_point",
331
- "when": ["not present.y_zero_point", "(ranks.y_scale == 0 or ranks.y_scale == 1)", "sameShapeOk", "(ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1 or (ranks.x >= 1 and dim(shapes.y_scale, 0) == dim(shapes.x, attrs.axis)))", "quantDtypesOk"],
332
- "constants": { "hasZero": false },
333
  "passes": [
334
  {
335
  "id": "main",
336
  "name": "QuantizeLinear",
337
- "source": {
338
- "shader": "quant-linear-scalar.wgsl.jinja",
339
- "inputs": {
340
- "op": "\"quantize\"",
341
- "x4": "false",
342
- "perAxis": "not (ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1)"
343
- }
344
  },
345
- "bindings": "linearScalarNoZero",
346
- "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
 
 
 
 
347
  }
348
  ]
349
  }
350
- ],
351
- "bindingSets": {
352
- "linearVec4WithZero": [
353
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xVec4" },
354
- {
355
- "name": "y_scale",
356
- "arg": "y_scale",
357
- "semantic": "y_scale",
358
- "buffer": { "type": "read-only-storage" },
359
- "elementType": "$scaleScalar"
360
- },
361
- {
362
- "name": "y_zero_point",
363
- "arg": "y_zero_point",
364
- "semantic": "y_zero_point",
365
- "buffer": { "type": "read-only-storage" },
366
- "elementType": "$yScalar"
367
- },
368
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yVec4" },
369
- {
370
- "name": "params",
371
- "semantic": "kernel.params",
372
- "buffer": { "type": "uniform" },
373
- "struct": {
374
- "name": "Params",
375
- "fields": [
376
- { "name": "count4", "type": "u32", "value": "elementCount4" },
377
- { "name": "scaleSize", "type": "u32", "value": "scaleSize" },
378
- { "name": "inner", "type": "u32", "value": "scaleInner" }
379
- ]
380
- }
381
- }
382
- ],
383
- "linearInnermostVec4WithZero": [
384
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xVec4" },
385
- {
386
- "name": "y_scale",
387
- "arg": "y_scale",
388
- "semantic": "y_scale",
389
- "buffer": { "type": "read-only-storage" },
390
- "elementType": "$scaleVec4"
391
- },
392
- {
393
- "name": "y_zero_point",
394
- "arg": "y_zero_point",
395
- "semantic": "y_zero_point",
396
- "buffer": { "type": "read-only-storage" },
397
- "elementType": "$yVec4"
398
- },
399
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yVec4" },
400
- {
401
- "name": "params",
402
- "semantic": "kernel.params",
403
- "buffer": { "type": "uniform" },
404
- "struct": {
405
- "name": "Params",
406
- "fields": [
407
- { "name": "count4", "type": "u32", "value": "elementCount4" },
408
- { "name": "scaleSize", "type": "u32", "value": "scaleSize" }
409
- ]
410
- }
411
- }
412
- ],
413
- "linearInnermostVec4NoZero": [
414
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xVec4" },
415
- {
416
- "name": "y_scale",
417
- "arg": "y_scale",
418
- "semantic": "y_scale",
419
- "buffer": { "type": "read-only-storage" },
420
- "elementType": "$scaleVec4"
421
- },
422
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yVec4" },
423
- {
424
- "name": "params",
425
- "semantic": "kernel.params",
426
- "buffer": { "type": "uniform" },
427
- "struct": {
428
- "name": "Params",
429
- "fields": [
430
- { "name": "count4", "type": "u32", "value": "elementCount4" },
431
- { "name": "scaleSize", "type": "u32", "value": "scaleSize" }
432
- ]
433
- }
434
- }
435
- ],
436
- "linearVec4NoZero": [
437
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xVec4" },
438
- {
439
- "name": "y_scale",
440
- "arg": "y_scale",
441
- "semantic": "y_scale",
442
- "buffer": { "type": "read-only-storage" },
443
- "elementType": "$scaleScalar"
444
- },
445
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yVec4" },
446
- {
447
- "name": "params",
448
- "semantic": "kernel.params",
449
- "buffer": { "type": "uniform" },
450
- "struct": {
451
- "name": "Params",
452
- "fields": [
453
- { "name": "count4", "type": "u32", "value": "elementCount4" },
454
- { "name": "scaleSize", "type": "u32", "value": "scaleSize" },
455
- { "name": "inner", "type": "u32", "value": "scaleInner" }
456
- ]
457
- }
458
- }
459
- ],
460
- "linearScalarWithZero": [
461
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
462
- {
463
- "name": "y_scale",
464
- "arg": "y_scale",
465
- "semantic": "y_scale",
466
- "buffer": { "type": "read-only-storage" },
467
- "elementType": "$scaleScalar"
468
- },
469
- {
470
- "name": "y_zero_point",
471
- "arg": "y_zero_point",
472
- "semantic": "y_zero_point",
473
- "buffer": { "type": "read-only-storage" },
474
- "elementType": "$yScalar"
475
- },
476
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
477
- {
478
- "name": "params",
479
- "semantic": "kernel.params",
480
- "buffer": { "type": "uniform" },
481
- "struct": {
482
- "name": "Params",
483
- "fields": [
484
- { "name": "count", "type": "u32", "value": "elementCount" },
485
- { "name": "scaleSize", "type": "u32", "value": "scaleSize" },
486
- { "name": "inner", "type": "u32", "value": "scaleInner" }
487
- ]
488
- }
489
- }
490
- ],
491
- "linearScalarNoZero": [
492
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
493
- {
494
- "name": "y_scale",
495
- "arg": "y_scale",
496
- "semantic": "y_scale",
497
- "buffer": { "type": "read-only-storage" },
498
- "elementType": "$scaleScalar"
499
- },
500
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
501
- {
502
- "name": "params",
503
- "semantic": "kernel.params",
504
- "buffer": { "type": "uniform" },
505
- "struct": {
506
- "name": "Params",
507
- "fields": [
508
- { "name": "count", "type": "u32", "value": "elementCount" },
509
- { "name": "scaleSize", "type": "u32", "value": "scaleSize" },
510
- { "name": "inner", "type": "u32", "value": "scaleInner" }
511
- ]
512
- }
513
- }
514
- ],
515
- "blockedLastVec4WithZero": [
516
- {
517
- "name": "x",
518
- "arg": "x",
519
- "semantic": "x",
520
- "buffer": { "type": "read-only-storage" },
521
- "elementType": "vec4<f32>"
522
- },
523
- {
524
- "name": "y_scale",
525
- "arg": "y_scale",
526
- "semantic": "y_scale",
527
- "buffer": { "type": "read-only-storage" },
528
- "elementType": "f32"
529
- },
530
- {
531
- "name": "y_zero_point",
532
- "arg": "y_zero_point",
533
- "semantic": "y_zero_point",
534
- "buffer": { "type": "read-only-storage" },
535
- "elementType": "$yScalar"
536
- },
537
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yVec4" },
538
- {
539
- "name": "params",
540
- "semantic": "kernel.params",
541
- "buffer": { "type": "uniform" },
542
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "elementCount4" }] }
543
- }
544
- ],
545
- "blockedLastVec4NoZero": [
546
- {
547
- "name": "x",
548
- "arg": "x",
549
- "semantic": "x",
550
- "buffer": { "type": "read-only-storage" },
551
- "elementType": "vec4<f32>"
552
- },
553
- {
554
- "name": "y_scale",
555
- "arg": "y_scale",
556
- "semantic": "y_scale",
557
- "buffer": { "type": "read-only-storage" },
558
- "elementType": "f32"
559
- },
560
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yVec4" },
561
- {
562
- "name": "params",
563
- "semantic": "kernel.params",
564
- "buffer": { "type": "uniform" },
565
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "elementCount4" }] }
566
- }
567
- ],
568
- "blockedVec4WithZero": [
569
- {
570
- "name": "x",
571
- "arg": "x",
572
- "semantic": "x",
573
- "buffer": { "type": "read-only-storage" },
574
- "elementType": "vec4<f32>"
575
- },
576
- {
577
- "name": "y_scale",
578
- "arg": "y_scale",
579
- "semantic": "y_scale",
580
- "buffer": { "type": "read-only-storage" },
581
- "elementType": "vec4<f32>"
582
- },
583
- {
584
- "name": "y_zero_point",
585
- "arg": "y_zero_point",
586
- "semantic": "y_zero_point",
587
- "buffer": { "type": "read-only-storage" },
588
- "elementType": "$yVec4"
589
- },
590
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yVec4" },
591
- {
592
- "name": "params",
593
- "semantic": "kernel.params",
594
- "buffer": { "type": "uniform" },
595
- "struct": {
596
- "name": "Params",
597
- "fields": [
598
- { "name": "count", "type": "u32", "value": "elementCount4" },
599
- { "name": "axisDim", "type": "u32", "value": "blockedAxisDim" },
600
- { "name": "scaleAxisDim", "type": "u32", "value": "blockedScaleAxisDim" },
601
- { "name": "inner", "type": "u32", "value": "blockedInner" },
602
- { "name": "blockSize", "type": "u32", "value": "attrs.block_size" }
603
- ]
604
- }
605
- }
606
- ],
607
- "blockedVec4NoZero": [
608
- {
609
- "name": "x",
610
- "arg": "x",
611
- "semantic": "x",
612
- "buffer": { "type": "read-only-storage" },
613
- "elementType": "vec4<f32>"
614
- },
615
- {
616
- "name": "y_scale",
617
- "arg": "y_scale",
618
- "semantic": "y_scale",
619
- "buffer": { "type": "read-only-storage" },
620
- "elementType": "vec4<f32>"
621
- },
622
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yVec4" },
623
- {
624
- "name": "params",
625
- "semantic": "kernel.params",
626
- "buffer": { "type": "uniform" },
627
- "struct": {
628
- "name": "Params",
629
- "fields": [
630
- { "name": "count", "type": "u32", "value": "elementCount4" },
631
- { "name": "axisDim", "type": "u32", "value": "blockedAxisDim" },
632
- { "name": "scaleAxisDim", "type": "u32", "value": "blockedScaleAxisDim" },
633
- { "name": "inner", "type": "u32", "value": "blockedInner" },
634
- { "name": "blockSize", "type": "u32", "value": "attrs.block_size" }
635
- ]
636
- }
637
- }
638
- ],
639
- "blockedScalarWithZero": [
640
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
641
- {
642
- "name": "y_scale",
643
- "arg": "y_scale",
644
- "semantic": "y_scale",
645
- "buffer": { "type": "read-only-storage" },
646
- "elementType": "$scaleScalar"
647
- },
648
- {
649
- "name": "y_zero_point",
650
- "arg": "y_zero_point",
651
- "semantic": "y_zero_point",
652
- "buffer": { "type": "read-only-storage" },
653
- "elementType": "$yScalar"
654
- },
655
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
656
- {
657
- "name": "params",
658
- "semantic": "kernel.params",
659
- "buffer": { "type": "uniform" },
660
- "struct": {
661
- "name": "Params",
662
- "fields": [
663
- { "name": "count", "type": "u32", "value": "elementCount" },
664
- { "name": "axisDim", "type": "u32", "value": "blockedAxisDim" },
665
- { "name": "scaleAxisDim", "type": "u32", "value": "blockedScaleAxisDim" },
666
- { "name": "inner", "type": "u32", "value": "blockedInner" },
667
- { "name": "blockSize", "type": "u32", "value": "attrs.block_size" }
668
- ]
669
- }
670
- }
671
- ],
672
- "blockedScalarNoZero": [
673
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
674
- {
675
- "name": "y_scale",
676
- "arg": "y_scale",
677
- "semantic": "y_scale",
678
- "buffer": { "type": "read-only-storage" },
679
- "elementType": "$scaleScalar"
680
- },
681
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
682
- {
683
- "name": "params",
684
- "semantic": "kernel.params",
685
- "buffer": { "type": "uniform" },
686
- "struct": {
687
- "name": "Params",
688
- "fields": [
689
- { "name": "count", "type": "u32", "value": "elementCount" },
690
- { "name": "axisDim", "type": "u32", "value": "blockedAxisDim" },
691
- { "name": "scaleAxisDim", "type": "u32", "value": "blockedScaleAxisDim" },
692
- { "name": "inner", "type": "u32", "value": "blockedInner" },
693
- { "name": "blockSize", "type": "u32", "value": "attrs.block_size" }
694
- ]
695
- }
696
- }
697
- ],
698
- "linearVec4WithZeroIo": [
699
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xVec4" },
700
- {
701
- "name": "y_scale",
702
- "arg": "y_scale",
703
- "semantic": "y_scale",
704
- "buffer": { "type": "read-only-storage" },
705
- "elementType": "$scaleScalar"
706
- },
707
- {
708
- "name": "y_zero_point",
709
- "arg": "y_zero_point",
710
- "semantic": "y_zero_point",
711
- "buffer": { "type": "read-only-storage" },
712
- "elementType": "$yScalar"
713
- },
714
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yVec4" }
715
- ],
716
- "linearVec4NoZeroIo": [
717
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xVec4" },
718
- {
719
- "name": "y_scale",
720
- "arg": "y_scale",
721
- "semantic": "y_scale",
722
- "buffer": { "type": "read-only-storage" },
723
- "elementType": "$scaleScalar"
724
- },
725
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yVec4" }
726
- ],
727
- "linearScalarWithZeroIo": [
728
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
729
- {
730
- "name": "y_scale",
731
- "arg": "y_scale",
732
- "semantic": "y_scale",
733
- "buffer": { "type": "read-only-storage" },
734
- "elementType": "$scaleScalar"
735
- },
736
- {
737
- "name": "y_zero_point",
738
- "arg": "y_zero_point",
739
- "semantic": "y_zero_point",
740
- "buffer": { "type": "read-only-storage" },
741
- "elementType": "$yScalar"
742
- },
743
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" }
744
- ],
745
- "linearScalarNoZeroIo": [
746
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
747
- {
748
- "name": "y_scale",
749
- "arg": "y_scale",
750
- "semantic": "y_scale",
751
- "buffer": { "type": "read-only-storage" },
752
- "elementType": "$scaleScalar"
753
- },
754
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" }
755
- ],
756
- "linearVec4PerTensorWithZero": [
757
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xVec4" },
758
- {
759
- "name": "y_scale",
760
- "arg": "y_scale",
761
- "semantic": "y_scale",
762
- "buffer": { "type": "read-only-storage" },
763
- "elementType": "$scaleScalar"
764
- },
765
- {
766
- "name": "y_zero_point",
767
- "arg": "y_zero_point",
768
- "semantic": "y_zero_point",
769
- "buffer": { "type": "read-only-storage" },
770
- "elementType": "$yScalar"
771
- },
772
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yVec4" },
773
- {
774
- "name": "params",
775
- "semantic": "kernel.params",
776
- "buffer": { "type": "uniform" },
777
- "struct": { "name": "Params", "fields": [{ "name": "count4", "type": "u32", "value": "elementCount4" }] }
778
- }
779
- ],
780
- "linearVec4PerTensorNoZero": [
781
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xVec4" },
782
- {
783
- "name": "y_scale",
784
- "arg": "y_scale",
785
- "semantic": "y_scale",
786
- "buffer": { "type": "read-only-storage" },
787
- "elementType": "$scaleScalar"
788
- },
789
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yVec4" },
790
- {
791
- "name": "params",
792
- "semantic": "kernel.params",
793
- "buffer": { "type": "uniform" },
794
- "struct": { "name": "Params", "fields": [{ "name": "count4", "type": "u32", "value": "elementCount4" }] }
795
- }
796
- ],
797
- "linearScalarPerTensorWithZero": [
798
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
799
- {
800
- "name": "y_scale",
801
- "arg": "y_scale",
802
- "semantic": "y_scale",
803
- "buffer": { "type": "read-only-storage" },
804
- "elementType": "$scaleScalar"
805
- },
806
- {
807
- "name": "y_zero_point",
808
- "arg": "y_zero_point",
809
- "semantic": "y_zero_point",
810
- "buffer": { "type": "read-only-storage" },
811
- "elementType": "$yScalar"
812
- },
813
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
814
- {
815
- "name": "params",
816
- "semantic": "kernel.params",
817
- "buffer": { "type": "uniform" },
818
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "elementCount" }] }
819
- }
820
- ],
821
- "linearScalarPerTensorNoZero": [
822
- { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
823
- {
824
- "name": "y_scale",
825
- "arg": "y_scale",
826
- "semantic": "y_scale",
827
- "buffer": { "type": "read-only-storage" },
828
- "elementType": "$scaleScalar"
829
- },
830
- { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
831
- {
832
- "name": "params",
833
- "semantic": "kernel.params",
834
- "buffer": { "type": "uniform" },
835
- "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "elementCount" }] }
836
- }
837
- ]
838
- }
839
  }
 
2
  "domain": "ai.onnx",
3
  "name": "QuantizeLinear",
4
  "sinceVersion": 25,
5
+ "inputs": {
6
+ "x": { "dtype": "TX" },
7
+ "y_scale": { "dtype": "TS" },
8
+ "y_zero_point": { "dtype": "TQ", "optional": true }
9
+ },
10
+ "outputs": { "y": { "dtype": "TQ", "rank": "ranks.x", "shape": "shapes.x" } },
11
+ "attributes": {
12
+ "axis": { "default": 1 },
13
+ "block_size": { "default": 0 },
14
+ "output_dtype": { "default": 0 },
15
+ "precision": { "default": 0 },
16
+ "saturate": { "default": 1 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  },
18
  "attributeConstraints": { "precision": { "values": [0, 1, 10] }, "saturate": { "values": [1] } },
19
  "typeConstraints": { "TX": ["float32", "float16"], "TS": ["float32", "float16"], "TQ": ["uint8", "int8"] },
20
+ "tunables": { "WORKGROUP_SIZE": { "default": 256 }, "VEC4_TAIL_MIN_ELEMENTS": { "default": 4096 } },
 
 
 
 
 
 
21
  "derive": {
22
  "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
23
  "workgroupOk": "tunables.WORKGROUP_SIZE <= deviceWorkgroupCap",
24
+ "outputDispatchFits": "workgroupOk and ceilDiv(ceilDiv(numel(shapes.y), tunables.WORKGROUP_SIZE), min(device.limits.maxComputeWorkgroupsPerDimension, 65535)) <= min(device.limits.maxComputeWorkgroupsPerDimension, 65535)",
25
  "outputDtypeOk": "(attrs.output_dtype == 0 and (present.y_zero_point or tensorDtypes.y == \"uint8\")) or attrs.output_dtype == onnxDtypeCode(logicalDtypes.TQ)",
26
  "quantizeDivisionF16": "attrs.precision == onnxDtypeCode(\"float16\") or (attrs.precision == 0 and tensorDtypes.y_scale == \"float16\")",
27
  "sameShapeOk": "ranks.y == ranks.x and numel(shapes.x) == numel(shapes.y) and outputDispatchFits and outputDtypeOk",
 
33
  "scaleInner": "1 if ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1 else inner(shapes.x, attrs.axis)",
34
  "blockedAxisDim": "dim(shapes.x, attrs.axis) if ranks.x >= 2 else 1",
35
  "blockedScaleAxisDim": "dim(shapes.y_scale, attrs.axis) if ranks.y_scale >= 2 else 1",
36
+ "blockedInner": "inner(shapes.x, attrs.axis) if ranks.x >= 2 else 1",
 
 
 
 
37
  "xVec4": "\"vec4<\" ~ dtypes.TX ~ \">\"",
 
38
  "scaleVec4": "\"vec4<\" ~ dtypes.TS ~ \">\"",
39
  "yScalar": "dtypes.TQ",
40
  "yVec4": "\"vec4<\" ~ dtypes.TQ ~ \">\"",
 
43
  "qMax": "255 if tensorDtypes.y == \"uint8\" else 127",
44
  "divisionF16": "quantizeDivisionF16"
45
  },
46
+ "when": ["sameShapeOk"],
47
+ "bindings": {
48
+ "x": { "buffer": "read-only-storage", "elementType": "$xVec4" },
49
+ "y_scale": { "buffer": "read-only-storage", "elementType": "$scaleVec4" },
50
+ "y_zero_point": { "buffer": "read-only-storage", "elementType": "$yVec4" },
51
+ "y": { "buffer": "storage", "elementType": "$yVec4" },
52
+ "params": {
53
+ "buffer": "uniform",
54
+ "struct": [
55
+ { "name": "count4", "type": "u32", "value": "elementCount4" },
56
+ { "name": "scaleSize", "type": "u32", "value": "scaleSize" }
57
+ ]
58
+ },
59
+ "params_2": {
60
+ "name": "params",
61
+ "buffer": "uniform",
62
+ "struct": [
63
+ { "name": "count4", "type": "u32", "value": "elementCount4" },
64
+ { "name": "scaleSize", "type": "u32", "value": "scaleSize" },
65
+ { "name": "inner", "type": "u32", "value": "scaleInner" }
66
+ ]
67
+ },
68
+ "params_3": {
69
+ "name": "params",
70
+ "buffer": "uniform",
71
+ "struct": [{ "name": "count4", "type": "u32", "value": "elementCount4" }]
72
+ },
73
+ "params_4": {
74
+ "name": "params",
75
+ "buffer": "uniform",
76
+ "struct": [{ "name": "count", "type": "u32", "value": "elementCount" }]
77
+ },
78
+ "x_3": { "name": "x", "buffer": "read-only-storage", "elementType": "vec4<f32>" },
79
+ "y_scale_3": { "name": "y_scale", "buffer": "read-only-storage", "elementType": "f32" },
80
+ "params_5": {
81
+ "name": "params",
82
+ "buffer": "uniform",
83
+ "struct": [{ "name": "count", "type": "u32", "value": "elementCount4" }]
84
+ },
85
+ "y_scale_4": { "name": "y_scale", "buffer": "read-only-storage", "elementType": "vec4<f32>" },
86
+ "params_6": {
87
+ "name": "params",
88
+ "buffer": "uniform",
89
+ "struct": [
90
+ { "name": "count", "type": "u32", "value": "elementCount4" },
91
+ { "name": "axisDim", "type": "u32", "value": "blockedAxisDim" },
92
+ { "name": "scaleAxisDim", "type": "u32", "value": "blockedScaleAxisDim" },
93
+ { "name": "inner", "type": "u32", "value": "blockedInner" },
94
+ { "name": "blockSize", "type": "u32", "value": "attrs.block_size" }
95
+ ]
96
+ },
97
+ "params_7": {
98
+ "name": "params",
99
+ "buffer": "uniform",
100
+ "struct": [
101
+ { "name": "count", "type": "u32", "value": "elementCount" },
102
+ { "name": "axisDim", "type": "u32", "value": "blockedAxisDim" },
103
+ { "name": "scaleAxisDim", "type": "u32", "value": "blockedScaleAxisDim" },
104
+ { "name": "inner", "type": "u32", "value": "blockedInner" },
105
+ { "name": "blockSize", "type": "u32", "value": "attrs.block_size" }
106
+ ]
107
+ },
108
+ "params_8": {
109
+ "name": "params",
110
+ "buffer": "uniform",
111
+ "struct": [
112
+ { "name": "count", "type": "u32", "value": "elementCount" },
113
+ { "name": "scaleSize", "type": "u32", "value": "scaleSize" },
114
+ { "name": "inner", "type": "u32", "value": "scaleInner" }
115
+ ]
116
+ }
117
+ },
118
  "variants": [
119
  {
120
  "id": "innermost_axis_vec4_with_zero_point",
121
  "priority": 20,
122
+ "when": ["present.y_zero_point", "ranks.x >= 1", "attrs.axis == -1 or attrs.axis == ranks.x - 1", "ranks.y_scale == 1", "dim(shapes.y_scale, 0) == dim(shapes.x, ranks.x - 1)", "dim(shapes.y_scale, 0) % 4 == 0", "ranks.y_zero_point == 1", "dim(shapes.y_zero_point, 0) == dim(shapes.y_scale, 0)", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "quantDtypesOk"],
123
+ "derive": { "hasZero": true },
124
  "passes": [
125
  {
126
  "id": "main",
127
  "name": "QuantizeLinear.InnermostAxisVec4",
128
+ "shader": "quant-linear-vec4.wgsl.jinja",
129
+ "derive": { "op": "\"quantize\"", "perAxis": true, "vectorParams": true },
130
+ "bindings": ["x", "y_scale", "y_zero_point", "y", "params"],
131
+ "dispatch": {
132
+ "x": "min(ceilDiv((floor(numel(shapes.y) / 4)), (tunables.WORKGROUP_SIZE)), 65535)",
133
+ "y": "ceilDiv(ceilDiv((floor(numel(shapes.y) / 4)), (tunables.WORKGROUP_SIZE)), 65535)",
134
+ "z": 1
135
+ }
136
  }
137
  ]
138
  },
139
  {
140
  "id": "innermost_axis_vec4_no_zero_point",
141
  "priority": 20,
142
+ "when": ["not present.y_zero_point", "ranks.x >= 1", "attrs.axis == -1 or attrs.axis == ranks.x - 1", "ranks.y_scale == 1", "dim(shapes.y_scale, 0) == dim(shapes.x, ranks.x - 1)", "dim(shapes.y_scale, 0) % 4 == 0", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "quantDtypesOk"],
143
+ "derive": { "hasZero": false },
144
  "passes": [
145
  {
146
  "id": "main",
147
  "name": "QuantizeLinear.InnermostAxisVec4NoZero",
148
+ "shader": "quant-linear-vec4.wgsl.jinja",
149
+ "derive": { "op": "\"quantize\"", "perAxis": true, "vectorParams": true },
150
+ "bindings": ["x", "y_scale", "y", "params"],
151
+ "dispatch": {
152
+ "x": "min(ceilDiv((floor(numel(shapes.y) / 4)), (tunables.WORKGROUP_SIZE)), 65535)",
153
+ "y": "ceilDiv(ceilDiv((floor(numel(shapes.y) / 4)), (tunables.WORKGROUP_SIZE)), 65535)",
154
+ "z": 1
155
+ }
156
  }
157
  ]
158
  },
159
  {
160
  "id": "vec4_cross_axis_with_zero_point",
161
  "priority": 16,
162
+ "when": ["present.y_zero_point", "attrs.block_size == 0", "ranks.x >= 1", "attrs.axis >= 0", "attrs.axis < ranks.x", "ranks.y_scale == 1", "dim(shapes.y_scale, 0) == dim(shapes.x, attrs.axis)", "inner(shapes.x, attrs.axis) % 4 != 0", "ranks.y_zero_point == 1", "dim(shapes.y_zero_point, 0) == dim(shapes.y_scale, 0)", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "quantDtypesOk"],
163
+ "derive": { "hasZero": true },
164
  "passes": [
165
  {
166
  "id": "main",
167
  "name": "QuantizeLinear.Vec4CrossAxis",
168
+ "shader": "quant-linear-vec4.wgsl.jinja",
169
+ "derive": { "op": "\"quantize\"", "perAxis": true, "crossingParams": true },
170
+ "bindings": ["x", { "arg": "y_scale" }, { "arg": "y_zero_point" }, "y", "params_2"],
171
+ "dispatch": {
172
+ "x": "min(ceilDiv((floor(numel(shapes.y) / 4)), (tunables.WORKGROUP_SIZE)), 65535)",
173
+ "y": "ceilDiv(ceilDiv((floor(numel(shapes.y) / 4)), (tunables.WORKGROUP_SIZE)), 65535)",
174
+ "z": 1
175
+ }
176
  }
177
  ]
178
  },
179
  {
180
  "id": "vec4_with_zero_point",
181
  "priority": 15,
182
+ "when": ["present.y_zero_point", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "(ranks.y_scale == 0 or ranks.y_scale == 1)", "(ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1 or (ranks.x >= 1 and dim(shapes.y_scale, 0) == dim(shapes.x, attrs.axis) and inner(shapes.x, attrs.axis) % 4 == 0))", "(ranks.y_zero_point == 0 or ranks.y_zero_point == 1)", "(ranks.y_zero_point == 0 or dim(shapes.y_zero_point, 0) == 1 or (ranks.x >= 1 and dim(shapes.y_zero_point, 0) == dim(shapes.x, attrs.axis)))", "quantDtypesOk"],
183
+ "derive": { "hasZero": true },
184
  "passes": [
185
  {
186
  "id": "main",
187
  "name": "QuantizeLinear.Vec4",
188
+ "shader": "quant-linear-vec4.wgsl.jinja",
189
+ "derive": { "op": "\"quantize\"", "perAxis": "not (ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1)" },
190
+ "bindings": ["x", { "arg": "y_scale" }, { "arg": "y_zero_point" }, "y", "params_2"],
191
+ "dispatch": {
192
+ "x": "min(ceilDiv((floor(numel(shapes.y) / 4)), (tunables.WORKGROUP_SIZE)), 65535)",
193
+ "y": "ceilDiv(ceilDiv((floor(numel(shapes.y) / 4)), (tunables.WORKGROUP_SIZE)), 65535)",
194
+ "z": 1
195
+ }
196
  }
197
  ]
198
  },
199
  {
200
  "id": "vec4_no_zero_point",
201
  "priority": 15,
202
+ "when": ["not present.y_zero_point", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "(ranks.y_scale == 0 or ranks.y_scale == 1)", "(ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1 or (ranks.x >= 1 and dim(shapes.y_scale, 0) == dim(shapes.x, attrs.axis) and inner(shapes.x, attrs.axis) % 4 == 0))", "quantDtypesOk"],
203
+ "derive": { "hasZero": false },
204
  "passes": [
205
  {
206
  "id": "main",
207
  "name": "QuantizeLinear.Vec4",
208
+ "shader": "quant-linear-vec4.wgsl.jinja",
209
+ "derive": { "op": "\"quantize\"", "perAxis": "not (ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1)" },
210
+ "bindings": ["x", { "arg": "y_scale" }, "y", "params_2"],
211
+ "dispatch": {
212
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
213
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
214
+ "z": 1
215
+ }
216
  }
217
  ]
218
  },
219
  {
220
  "id": "vec4_tail_with_zero_point",
221
  "priority": 12,
222
+ "when": ["present.y_zero_point", "numel(shapes.y) >= tunables.VEC4_TAIL_MIN_ELEMENTS", "numel(shapes.y) % 4 != 0", "(ranks.y_scale == 0 or (ranks.y_scale == 1 and dim(shapes.y_scale, 0) == 1))", "(ranks.y_zero_point == 0 or (ranks.y_zero_point == 1 and dim(shapes.y_zero_point, 0) == 1))", "quantDtypesOk"],
223
+ "derive": { "hasZero": true },
224
  "passes": [
225
  {
226
  "id": "bulk",
227
  "name": "QuantizeLinear.Vec4Bulk",
228
+ "shader": "quant-linear-vec4.wgsl.jinja",
229
+ "derive": { "op": "\"quantize\"", "perAxis": false },
230
+ "bindings": ["x", { "arg": "y_scale" }, { "arg": "y_zero_point" }, "y", "params_3"],
231
+ "dispatch": {
232
+ "x": "min(ceilDiv((floor(numel(shapes.y) / 4)), (tunables.WORKGROUP_SIZE)), 65535)",
233
+ "y": "ceilDiv(ceilDiv((floor(numel(shapes.y) / 4)), (tunables.WORKGROUP_SIZE)), 65535)",
234
+ "z": 1
235
+ }
236
  },
237
  {
238
  "id": "tail",
239
  "name": "QuantizeLinear.ScalarTail",
240
+ "shader": "quant-linear-scalar.wgsl.jinja",
241
+ "derive": { "op": "\"quantize\"", "x4": true, "perAxis": false },
242
+ "bindings": [{ "arg": "x" }, { "arg": "y_scale" }, { "arg": "y_zero_point" }, { "arg": "y" }, "params_4"],
243
+ "dispatch": {
244
+ "x": "min(ceilDiv((1), (tunables.WORKGROUP_SIZE)), 65535)",
245
+ "y": "ceilDiv(ceilDiv((1), (tunables.WORKGROUP_SIZE)), 65535)",
246
+ "z": 1
247
+ }
248
  }
249
  ]
250
  },
251
  {
252
  "id": "vec4_tail_no_zero_point",
253
  "priority": 12,
254
+ "when": ["not present.y_zero_point", "numel(shapes.y) >= tunables.VEC4_TAIL_MIN_ELEMENTS", "numel(shapes.y) % 4 != 0", "(ranks.y_scale == 0 or (ranks.y_scale == 1 and dim(shapes.y_scale, 0) == 1))", "quantDtypesOk"],
255
+ "derive": { "hasZero": false },
256
  "passes": [
257
  {
258
  "id": "bulk",
259
  "name": "QuantizeLinear.Vec4Bulk",
260
+ "shader": "quant-linear-vec4.wgsl.jinja",
261
+ "derive": { "op": "\"quantize\"", "perAxis": false },
262
+ "bindings": ["x", { "arg": "y_scale" }, "y", "params_3"],
263
+ "dispatch": {
264
+ "x": "min(ceilDiv((floor(numel(shapes.y) / 4)), (tunables.WORKGROUP_SIZE)), 65535)",
265
+ "y": "ceilDiv(ceilDiv((floor(numel(shapes.y) / 4)), (tunables.WORKGROUP_SIZE)), 65535)",
266
+ "z": 1
267
+ }
268
  },
269
  {
270
  "id": "tail",
271
  "name": "QuantizeLinear.ScalarTail",
272
+ "shader": "quant-linear-scalar.wgsl.jinja",
273
+ "derive": { "op": "\"quantize\"", "x4": true, "perAxis": false },
274
+ "bindings": [{ "arg": "x" }, { "arg": "y_scale" }, { "arg": "y" }, "params_4"],
275
+ "dispatch": {
276
+ "x": "min(ceilDiv((1), (tunables.WORKGROUP_SIZE)), 65535)",
277
+ "y": "ceilDiv(ceilDiv((1), (tunables.WORKGROUP_SIZE)), 65535)",
278
+ "z": 1
279
+ }
280
  }
281
  ]
282
  },
283
  {
284
  "id": "blocked_last_axis_vec4_with_zero_point",
285
  "priority": 24,
286
+ "when": ["present.y_zero_point", "blockedScaleOk", "attrs.block_size % 4 == 0", "ranks.y_zero_point == ranks.x", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "inner(shapes.x, attrs.axis) == 1", "dim(shapes.x, attrs.axis) % attrs.block_size == 0", "dim(shapes.y_scale, attrs.axis) == dim(shapes.x, attrs.axis) / attrs.block_size", "numel(shapes.y_zero_point) == numel(shapes.y_scale)", "tensorDtypes.x == \"float32\"", "tensorDtypes.y_scale == \"float32\""],
287
+ "derive": { "hasZero": true, "blockVectors": "attrs.block_size / 4" },
288
  "passes": [
289
  {
290
  "id": "main",
291
  "name": "QuantizeLinear.BlockedLastAxisVec4",
292
+ "shader": "quant-linear-blocked-axis.wgsl.jinja",
293
+ "derive": { "lastAxisVectorized": true },
294
+ "bindings": ["x_3", "y_scale_3", { "arg": "y_zero_point" }, "y", "params_5"],
295
+ "dispatch": {
296
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
297
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
298
+ "z": 1
299
+ }
300
  }
301
  ]
302
  },
303
  {
304
  "id": "blocked_last_axis_vec4_no_zero_point",
305
  "priority": 23,
306
+ "when": ["not present.y_zero_point", "blockedScaleOk", "attrs.block_size % 4 == 0", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "inner(shapes.x, attrs.axis) == 1", "dim(shapes.x, attrs.axis) % attrs.block_size == 0", "dim(shapes.y_scale, attrs.axis) == dim(shapes.x, attrs.axis) / attrs.block_size", "tensorDtypes.x == \"float32\"", "tensorDtypes.y_scale == \"float32\""],
307
+ "derive": { "hasZero": false, "blockVectors": "attrs.block_size / 4" },
308
  "passes": [
309
  {
310
  "id": "main",
311
  "name": "QuantizeLinear.BlockedLastAxisVec4",
312
+ "shader": "quant-linear-blocked-axis.wgsl.jinja",
313
+ "derive": { "lastAxisVectorized": true },
314
+ "bindings": ["x_3", "y_scale_3", "y", "params_5"],
315
+ "dispatch": {
316
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
317
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
318
+ "z": 1
319
+ }
320
  }
321
  ]
322
  },
323
  {
324
  "id": "blocked_vec4_with_zero_point",
325
  "priority": 22,
326
+ "when": ["present.y_zero_point", "blockedScaleOk", "ranks.y_zero_point == ranks.x", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "numel(shapes.y_scale) % 4 == 0", "numel(shapes.y_zero_point) == numel(shapes.y_scale)", "inner(shapes.x, attrs.axis) % 4 == 0", "dim(shapes.y_scale, attrs.axis) == ceil(dim(shapes.x, attrs.axis) / attrs.block_size)", "dim(shapes.y_zero_point, attrs.axis) == dim(shapes.y_scale, attrs.axis)", "tensorDtypes.x == \"float32\"", "tensorDtypes.y_scale == \"float32\""],
327
+ "derive": { "hasZero": true },
328
  "passes": [
329
  {
330
  "id": "main",
331
  "name": "QuantizeLinear.BlockedVec4WithZeroPoint",
332
+ "shader": "quant-linear-blocked-axis.wgsl.jinja",
333
+ "derive": { "vectorizedSpec": true },
334
+ "bindings": ["x_3", "y_scale_4", "y_zero_point", "y", "params_6"],
335
+ "dispatch": {
336
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
337
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
338
+ "z": 1
339
+ }
340
  }
341
  ]
342
  },
343
  {
344
  "id": "blocked_vec4_no_zero_point",
345
  "priority": 21,
346
+ "when": ["not present.y_zero_point", "blockedScaleOk", "numel(shapes.y) > 0", "numel(shapes.y) % 4 == 0", "numel(shapes.y_scale) % 4 == 0", "inner(shapes.x, attrs.axis) % 4 == 0", "dim(shapes.y_scale, attrs.axis) == ceil(dim(shapes.x, attrs.axis) / attrs.block_size)", "tensorDtypes.x == \"float32\"", "tensorDtypes.y_scale == \"float32\""],
347
+ "derive": { "hasZero": false },
348
  "passes": [
349
  {
350
  "id": "main",
351
  "name": "QuantizeLinear.BlockedVec4",
352
+ "shader": "quant-linear-blocked-axis.wgsl.jinja",
353
+ "derive": { "vectorizedSpec": true },
354
+ "bindings": ["x_3", "y_scale_4", "y", "params_6"],
355
+ "dispatch": {
356
+ "x": "min(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
357
+ "y": "ceilDiv(ceilDiv((numel(shapes.y) / 4), (tunables.WORKGROUP_SIZE)), 65535)",
358
+ "z": 1
359
+ }
360
  }
361
  ]
362
  },
363
  {
364
  "id": "blocked_with_zero_point",
365
  "priority": 19,
366
+ "when": ["present.y_zero_point", "blockedScaleOk", "ranks.y_zero_point == ranks.x", "dim(shapes.y_scale, attrs.axis) == ceil(dim(shapes.x, attrs.axis) / attrs.block_size)", "dim(shapes.y_zero_point, attrs.axis) == dim(shapes.y_scale, attrs.axis)", "quantDtypesOk"],
367
+ "derive": { "hasZero": true },
368
  "passes": [
369
  {
370
  "id": "main",
371
  "name": "QuantizeLinear.Blocked",
372
  "shader": "quant-linear-blocked-axis.wgsl.jinja",
373
+ "bindings": [{ "arg": "x" }, { "arg": "y_scale" }, { "arg": "y_zero_point" }, { "arg": "y" }, "params_7"],
374
+ "dispatch": {
375
+ "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
376
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
377
+ "z": 1
378
+ }
379
  }
380
  ]
381
  },
382
  {
383
  "id": "blocked_no_zero_point",
384
  "priority": 19,
385
+ "when": ["not present.y_zero_point", "blockedScaleOk", "dim(shapes.y_scale, attrs.axis) == ceil(dim(shapes.x, attrs.axis) / attrs.block_size)", "quantDtypesOk"],
386
+ "derive": { "hasZero": false },
387
  "passes": [
388
  {
389
  "id": "main",
390
  "name": "QuantizeLinear.BlockedNoZero",
391
  "shader": "quant-linear-blocked-axis.wgsl.jinja",
392
+ "bindings": [{ "arg": "x" }, { "arg": "y_scale" }, { "arg": "y" }, "params_7"],
393
+ "dispatch": {
394
+ "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
395
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
396
+ "z": 1
397
+ }
398
  }
399
  ]
400
  },
401
  {
402
  "id": "with_zero_point",
403
  "priority": 10,
404
+ "when": ["present.y_zero_point", "(ranks.y_scale == 0 or ranks.y_scale == 1)", "(ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1 or (ranks.x >= 1 and dim(shapes.y_scale, 0) == dim(shapes.x, attrs.axis)))", "(ranks.y_zero_point == 0 or ranks.y_zero_point == 1)", "(ranks.y_zero_point == 0 or dim(shapes.y_zero_point, 0) == 1 or (ranks.x >= 1 and dim(shapes.y_zero_point, 0) == dim(shapes.x, attrs.axis)))", "quantDtypesOk"],
405
+ "derive": { "hasZero": true },
406
  "passes": [
407
  {
408
  "id": "main",
409
  "name": "QuantizeLinear",
410
+ "shader": "quant-linear-scalar.wgsl.jinja",
411
+ "derive": {
412
+ "op": "\"quantize\"",
413
+ "x4": "false",
414
+ "perAxis": "not (ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1)"
 
 
415
  },
416
+ "bindings": [{ "arg": "x" }, { "arg": "y_scale" }, { "arg": "y_zero_point" }, { "arg": "y" }, "params_8"],
417
+ "dispatch": {
418
+ "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
419
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
420
+ "z": 1
421
+ }
422
  }
423
  ]
424
  },
425
  {
426
  "id": "no_zero_point",
427
+ "when": ["not present.y_zero_point", "(ranks.y_scale == 0 or ranks.y_scale == 1)", "(ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1 or (ranks.x >= 1 and dim(shapes.y_scale, 0) == dim(shapes.x, attrs.axis)))", "quantDtypesOk"],
428
+ "derive": { "hasZero": false },
429
  "passes": [
430
  {
431
  "id": "main",
432
  "name": "QuantizeLinear",
433
+ "shader": "quant-linear-scalar.wgsl.jinja",
434
+ "derive": {
435
+ "op": "\"quantize\"",
436
+ "x4": "false",
437
+ "perAxis": "not (ranks.y_scale == 0 or dim(shapes.y_scale, 0) == 1)"
 
 
438
  },
439
+ "bindings": [{ "arg": "x" }, { "arg": "y_scale" }, { "arg": "y" }, "params_8"],
440
+ "dispatch": {
441
+ "x": "min(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
442
+ "y": "ceilDiv(ceilDiv((numel(shapes.y)), (tunables.WORKGROUP_SIZE)), 65535)",
443
+ "z": 1
444
+ }
445
  }
446
  ]
447
  }
448
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
449
  }
build/webgpu/metadata.json CHANGED
@@ -1,20 +1,39 @@
1
  {
2
  "name": "ai.onnx.QuantizeLinear",
3
- "id": "_ai_onnx_quantizelinear_webgpu_a6a6d0c",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
- "bench.json": "dX5NKjv8Gz9VheT2QY/86ZiiTTguwW4MpDCtBw9kMHg=",
11
- "manifest.json": "qDp2PlIs4E2AwjrcPIjItloZ2fWB900UI9LrGCs1PYg=",
12
- "quant-linear-blocked-axis.wgsl.jinja": "964aieHZ1Jm6oeMgBxT1x9i3tEhFRUFJozYikdEIMwg=",
13
- "quant-linear-scalar.wgsl.jinja": "191IHMqakZ9HaVXxPSwNl15B6ec70DLhx40LuSbeu30=",
14
- "quant-linear-vec4.wgsl.jinja": "y48X/TVAaC+b/DD/AQT2ap35JVn7+KrdzlRrxrF8RDA=",
15
- "test.json": "QV/it95sQgUj+dQu4OK/chmXHLuwBAsDJQc6f7RISfM="
16
  }
17
  },
18
- "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
19
- "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.QuantizeLinear" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
 
1
  {
2
  "name": "ai.onnx.QuantizeLinear",
3
+ "id": "_ai_onnx_quantizelinear_webgpu_bf60a95",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "backend": { "type": "webgpu" },
7
  "digest": {
8
  "algorithm": "sha256",
9
  "files": {
10
+ "bench.json": "B6CqLHGCbyXXUAAcatPURKRkCnRMOZCNu7ix44MZbZw=",
11
+ "manifest.json": "X5UX5Db4+s1nUDXg5jez+yaYuFnt+y5vPqCc1mWXy3I=",
12
+ "quant-linear-blocked-axis.wgsl.jinja": "fhTtN3vZ8jl4fTshwThxKYds9QrAZ9U2GzjPtWQs2OE=",
13
+ "quant-linear-scalar.wgsl.jinja": "/OPcCfkXBiT0dEm1wEnImi5EBY8l17n6jpS5KKCjvQ4=",
14
+ "quant-linear-vec4.wgsl.jinja": "qQrnnz6UMx0Pg/d7Cs2cjh1HBaurycYK0H24VIbq+Fs=",
15
+ "test.json": "cniKZLfeclIx3sXUNY1qFbwal5ui2JqFKfODpgZEoCA="
16
  }
17
  },
18
+ "provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
19
+ "webgpu": {
20
+ "manifestSpec": "2.0",
21
+ "variants": {
22
+ "innermost_axis_vec4_with_zero_point": ["quant-linear-vec4.wgsl.jinja"],
23
+ "innermost_axis_vec4_no_zero_point": ["quant-linear-vec4.wgsl.jinja"],
24
+ "vec4_cross_axis_with_zero_point": ["quant-linear-vec4.wgsl.jinja"],
25
+ "vec4_with_zero_point": ["quant-linear-vec4.wgsl.jinja"],
26
+ "vec4_no_zero_point": ["quant-linear-vec4.wgsl.jinja"],
27
+ "vec4_tail_with_zero_point": ["quant-linear-scalar.wgsl.jinja", "quant-linear-vec4.wgsl.jinja"],
28
+ "vec4_tail_no_zero_point": ["quant-linear-scalar.wgsl.jinja", "quant-linear-vec4.wgsl.jinja"],
29
+ "blocked_last_axis_vec4_with_zero_point": ["quant-linear-blocked-axis.wgsl.jinja"],
30
+ "blocked_last_axis_vec4_no_zero_point": ["quant-linear-blocked-axis.wgsl.jinja"],
31
+ "blocked_vec4_with_zero_point": ["quant-linear-blocked-axis.wgsl.jinja"],
32
+ "blocked_vec4_no_zero_point": ["quant-linear-blocked-axis.wgsl.jinja"],
33
+ "blocked_with_zero_point": ["quant-linear-blocked-axis.wgsl.jinja"],
34
+ "blocked_no_zero_point": ["quant-linear-blocked-axis.wgsl.jinja"],
35
+ "with_zero_point": ["quant-linear-scalar.wgsl.jinja"],
36
+ "no_zero_point": ["quant-linear-scalar.wgsl.jinja"]
37
+ }
38
+ }
39
  }
build/webgpu/quant-linear-blocked-axis.wgsl.jinja CHANGED
@@ -1,12 +1,35 @@
1
- // Blocked-axis scale indexing for {{ source.op | default("quantize") }}.
2
- {% set vectorized = source.vectorized if source.vectorized is defined else false %}
3
- {% set lastAxisVectorized = source.lastAxisVectorized if source.lastAxisVectorized is defined else false %}
4
- {% if usesF16 %}
5
- enable f16;
6
- {% endif %}
 
 
 
7
  {{ env.wgsl.resourceDeclarations }}
8
 
9
- {% if not vectorized and not lastAxisVectorized %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  fn read_zero({% if hasZero %}index: u32{% endif %}) -> i32 {
11
  {% if hasZero %}
12
  {% if yUnsigned %}
@@ -37,6 +60,7 @@ fn scale_index(index: u32) -> u32 {
37
  }
38
 
39
  {% endif %}
 
40
  {% set divisionF16 = divisionF16 is defined and divisionF16 %}
41
  // Exact ONNX QuantizeLinear round-to-nearest-even, with identical handling of
42
  // infinities, saturation, and halfway values across every kernel route.
@@ -63,7 +87,8 @@ fn round_scaled_half_to_even(value: f32, scale: f32) -> i32 {
63
  // Clamp before the i32 cast so infinite and huge finite inputs saturate
64
  // instead of invoking undefined conversion behavior. Compare distances in
65
  // the input domain: doing the comparison on value / scale can move a value
66
- // across a half-way boundary because GPU division is not correctly rounded.
 
67
  let v = clamp(value / scale, -2.0e9, 2.0e9);
68
  let fl = floor(v);
69
  let lo = fl;
@@ -83,12 +108,13 @@ fn round_scaled_half_to_even(value: f32, scale: f32) -> i32 {
83
  {% endif %}
84
 
85
 
 
86
  {% if lastAxisVectorized %}
87
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
88
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
89
  // A vec4 cannot cross a block boundary on this route. Map vector indices
90
  // directly to their shared scale/zero-point entry.
91
- let i4 = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
92
  if (i4 >= params.count) { return; }
93
  let scale_index = i4 / {{ blockVectors }}u;
94
  let values = x[i4];
@@ -116,9 +142,9 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
116
  }
117
  {% elif vectorized %}
118
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
119
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
120
  // Four adjacent inner elements map to four adjacent scale entries.
121
- let i4 = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
122
  if (i4 >= params.count) { return; }
123
  let scale4 = scale_index(i4 * 4u) / 4u;
124
  let values = x[i4];
@@ -144,13 +170,18 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
144
  }
145
  {% else %}
146
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
147
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
148
- // The flat dispatch is folded across x/y at the device's per-axis workgroup
149
- // limit; gid.y carries the high portion of the element index.
150
- let index = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
151
  if (index >= params.count) { return; }
152
  let scaleIndex = scale_index(index);
 
153
  let rounded = round_scaled_half_to_even(f32(x[index]), f32(y_scale[scaleIndex])) + read_zero({% if hasZero %}scaleIndex{% endif %});
154
  write_quantized(index, clamp(rounded, {{ qMin }}, {{ qMax }}));
 
 
 
 
155
  }
156
  {% endif %}
 
1
+ // Blocked-axis scale and zero-point indexing for linear quantization.
2
+ {% set operation = op if op is defined else "quantize" %}
3
+ {% set vectorized = vectorizedSpec if vectorizedSpec is defined else false %}
4
+ {% set lastAxisVectorized = lastAxisVectorized if lastAxisVectorized is defined else false %}
5
+ {% set xUnsigned = xUnsigned is defined and xUnsigned %}
6
+ {% set yUnsigned = yUnsigned is defined and yUnsigned %}
7
+ {% set qMin = qMin | default(0 if yUnsigned else 0 - 128) %}
8
+ {% set qMax = qMax | default(255 if yUnsigned else 127) %}
9
+ {% set blockVectors = blockVectors | default(1) %}
10
  {{ env.wgsl.resourceDeclarations }}
11
 
12
+ {% if operation == "dequantize" %}
13
+ fn read_x(index: u32) -> i32 {
14
+ {% if xUnsigned %}
15
+ return i32(x[index]);
16
+ {% else %}
17
+ return x[index];
18
+ {% endif %}
19
+ }
20
+
21
+ fn read_zero({% if hasZero %}index: u32{% endif %}) -> i32 {
22
+ {% if hasZero %}
23
+ {% if xUnsigned %}
24
+ return i32(x_zero_point[index]);
25
+ {% else %}
26
+ return x_zero_point[index];
27
+ {% endif %}
28
+ {% else %}
29
+ return 0;
30
+ {% endif %}
31
+ }
32
+ {% elif not vectorized and not lastAxisVectorized %}
33
  fn read_zero({% if hasZero %}index: u32{% endif %}) -> i32 {
34
  {% if hasZero %}
35
  {% if yUnsigned %}
 
60
  }
61
 
62
  {% endif %}
63
+ {% if operation == "quantize" %}
64
  {% set divisionF16 = divisionF16 is defined and divisionF16 %}
65
  // Exact ONNX QuantizeLinear round-to-nearest-even, with identical handling of
66
  // infinities, saturation, and halfway values across every kernel route.
 
87
  // Clamp before the i32 cast so infinite and huge finite inputs saturate
88
  // instead of invoking undefined conversion behavior. Compare distances in
89
  // the input domain: doing the comparison on value / scale can move a value
90
+ // across a halfway boundary because WGSL division is not required to be
91
+ // correctly rounded.
92
  let v = clamp(value / scale, -2.0e9, 2.0e9);
93
  let fl = floor(v);
94
  let lo = fl;
 
108
  {% endif %}
109
 
110
 
111
+ {% endif %}
112
  {% if lastAxisVectorized %}
113
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
114
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
115
  // A vec4 cannot cross a block boundary on this route. Map vector indices
116
  // directly to their shared scale/zero-point entry.
117
+ let i4 = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
118
  if (i4 >= params.count) { return; }
119
  let scale_index = i4 / {{ blockVectors }}u;
120
  let values = x[i4];
 
142
  }
143
  {% elif vectorized %}
144
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
145
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
146
  // Four adjacent inner elements map to four adjacent scale entries.
147
+ let i4 = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
148
  if (i4 >= params.count) { return; }
149
  let scale4 = scale_index(i4 * 4u) / 4u;
150
  let values = x[i4];
 
170
  }
171
  {% else %}
172
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
173
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
174
+ // The flat dispatch is folded across x/y at a fixed per-axis workgroup
175
+ // width; gid.y carries the high portion of the element index.
176
+ let index = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
177
  if (index >= params.count) { return; }
178
  let scaleIndex = scale_index(index);
179
+ {% if operation == "quantize" %}
180
  let rounded = round_scaled_half_to_even(f32(x[index]), f32(y_scale[scaleIndex])) + read_zero({% if hasZero %}scaleIndex{% endif %});
181
  write_quantized(index, clamp(rounded, {{ qMin }}, {{ qMax }}));
182
+ {% else %}
183
+ let result = f32(read_x(index) - read_zero({% if hasZero %}scaleIndex{% endif %})) * f32(x_scale[scaleIndex]);
184
+ y[index] = {{ yScalar }}(result);
185
+ {% endif %}
186
  }
187
  {% endif %}
build/webgpu/quant-linear-scalar.wgsl.jinja CHANGED
@@ -1,8 +1,8 @@
 
 
 
1
  // The x4 route handles widened int8/uint8 storage with scalar scale parameters;
2
  // the linear route supports both per-tensor and per-axis quantization.
3
- {% if usesF16 %}
4
- enable f16;
5
- {% endif %}
6
  {{ env.wgsl.resourceDeclarations }}
7
 
8
  {% set divisionF16 = divisionF16 is defined and divisionF16 %}
@@ -31,7 +31,8 @@ fn round_scaled_half_to_even(value: f32, scale: f32) -> i32 {
31
  // Clamp before the i32 cast so infinite and huge finite inputs saturate
32
  // instead of invoking undefined conversion behavior. Compare distances in
33
  // the input domain: doing the comparison on value / scale can move a value
34
- // across a half-way boundary because GPU division is not correctly rounded.
 
35
  let v = clamp(value / scale, -2.0e9, 2.0e9);
36
  let fl = floor(v);
37
  let lo = fl;
@@ -77,11 +78,11 @@ fn transform_one(index: u32, scale: f32, zero_point: i32) {
77
  }
78
 
79
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
80
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
81
  // Fold oversized flat dispatches into two dimensions; gid.y carries work
82
  // beyond the device-capped x dimension.
83
- let invocation = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
84
- {% if source.x4 %}
85
  if (invocation != 0u) {
86
  return;
87
  }
@@ -100,7 +101,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
100
  return;
101
  }
102
  var scale_index = 0u;
103
- {% if source.perAxis %}
104
  scale_index = (invocation / params.inner) % params.scaleSize;
105
  {% endif %}
106
  let scale = f32(y_scale[scale_index]);
 
1
+ {% set yUnsigned = yUnsigned is defined and yUnsigned %}
2
+ {% set qMin = qMin | default(0 if yUnsigned else 0 - 128) %}
3
+ {% set qMax = qMax | default(255 if yUnsigned else 127) %}
4
  // The x4 route handles widened int8/uint8 storage with scalar scale parameters;
5
  // the linear route supports both per-tensor and per-axis quantization.
 
 
 
6
  {{ env.wgsl.resourceDeclarations }}
7
 
8
  {% set divisionF16 = divisionF16 is defined and divisionF16 %}
 
31
  // Clamp before the i32 cast so infinite and huge finite inputs saturate
32
  // instead of invoking undefined conversion behavior. Compare distances in
33
  // the input domain: doing the comparison on value / scale can move a value
34
+ // across a halfway boundary because WGSL division is not required to be
35
+ // correctly rounded.
36
  let v = clamp(value / scale, -2.0e9, 2.0e9);
37
  let fl = floor(v);
38
  let lo = fl;
 
78
  }
79
 
80
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
81
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
82
  // Fold oversized flat dispatches into two dimensions; gid.y carries work
83
  // beyond the device-capped x dimension.
84
+ let invocation = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
85
+ {% if x4 %}
86
  if (invocation != 0u) {
87
  return;
88
  }
 
101
  return;
102
  }
103
  var scale_index = 0u;
104
+ {% if perAxis %}
105
  scale_index = (invocation / params.inner) % params.scaleSize;
106
  {% endif %}
107
  let scale = f32(y_scale[scale_index]);
build/webgpu/quant-linear-vec4.wgsl.jinja CHANGED
@@ -1,9 +1,9 @@
 
 
 
1
  // int8/uint8 tensors are stored widened (one u32/i32 per element), so the vec4
2
  // binding gives 128-bit loads/stores of four elements. Per-component arithmetic
3
  // remains identical to the scalar quantize/dequantize paths.
4
- {% if usesF16 %}
5
- enable f16;
6
- {% endif %}
7
  {{ env.wgsl.resourceDeclarations }}
8
 
9
  {% set divisionF16 = divisionF16 is defined and divisionF16 %}
@@ -32,7 +32,8 @@ fn round_scaled_half_to_even(value: f32, scale: f32) -> i32 {
32
  // Clamp before the i32 cast so infinite and huge finite inputs saturate
33
  // instead of invoking undefined conversion behavior. Compare distances in
34
  // the input domain: doing the comparison on value / scale can move a value
35
- // across a half-way boundary because GPU division is not correctly rounded.
 
36
  let v = clamp(value / scale, -2.0e9, 2.0e9);
37
  let fl = floor(v);
38
  let lo = fl;
@@ -52,7 +53,7 @@ fn round_scaled_half_to_even(value: f32, scale: f32) -> i32 {
52
  {% endif %}
53
 
54
 
55
- {% if source.vectorParams is defined and source.vectorParams %}
56
  fn read_zero4({% if hasZero %}index: u32{% endif %}) -> vec4<i32> {
57
  {% if hasZero %}
58
  {% if yUnsigned %}
@@ -79,31 +80,31 @@ fn read_zero({% if hasZero %}index: u32{% endif %}) -> i32 {
79
  {% endif %}
80
 
81
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
82
- fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
83
- // The flat dispatch is folded across x/y at the device's per-axis workgroup
84
- // limit; gid.y carries the high portion of the vector index.
85
- let i = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
86
  if (i >= params.count4) {
87
  return;
88
  }
89
- {% if source.vectorParams is defined and source.vectorParams %}
90
  let scale_index = i % (params.scaleSize / 4u);
91
- {% elif source.crossingParams is defined and source.crossingParams %}
92
  let base_index = i * 4u;
93
  let scale_index0 = (base_index / params.inner) % params.scaleSize;
94
  let scale_index1 = ((base_index + 1u) / params.inner) % params.scaleSize;
95
  let scale_index2 = ((base_index + 2u) / params.inner) % params.scaleSize;
96
  let scale_index3 = ((base_index + 3u) / params.inner) % params.scaleSize;
97
- {% elif source.perAxis %}
98
  // inner % 4 == 0, so all 4 lanes share one scale index.
99
  let scale_index = ((i * 4u) / params.inner) % params.scaleSize;
100
  {% else %}
101
  let scale_index = 0u;
102
  {% endif %}
103
  let xv = x[i];
104
- {% if source.vectorParams is defined and source.vectorParams %}
105
  let zp4 = read_zero4({% if hasZero %}scale_index{% endif %});
106
- {% elif source.crossingParams is defined and source.crossingParams %}
107
  let zp0 = read_zero(scale_index0);
108
  let zp1 = read_zero(scale_index1);
109
  let zp2 = read_zero(scale_index2);
@@ -111,13 +112,13 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
111
  {% else %}
112
  let zp = read_zero({% if hasZero %}scale_index{% endif %});
113
  {% endif %}
114
- {% if source.vectorParams is defined and source.vectorParams %}
115
  let scale = vec4<f32>(y_scale[scale_index]);
116
  let q0 = clamp(round_scaled_half_to_even(f32(xv.x), scale.x) + zp4.x, {{ qMin }}, {{ qMax }});
117
  let q1 = clamp(round_scaled_half_to_even(f32(xv.y), scale.y) + zp4.y, {{ qMin }}, {{ qMax }});
118
  let q2 = clamp(round_scaled_half_to_even(f32(xv.z), scale.z) + zp4.z, {{ qMin }}, {{ qMax }});
119
  let q3 = clamp(round_scaled_half_to_even(f32(xv.w), scale.w) + zp4.w, {{ qMin }}, {{ qMax }});
120
- {% elif source.crossingParams is defined and source.crossingParams %}
121
  let q0 = clamp(round_scaled_half_to_even(f32(xv.x), f32(y_scale[scale_index0])) + zp0, {{ qMin }}, {{ qMax }});
122
  let q1 = clamp(round_scaled_half_to_even(f32(xv.y), f32(y_scale[scale_index1])) + zp1, {{ qMin }}, {{ qMax }});
123
  let q2 = clamp(round_scaled_half_to_even(f32(xv.z), f32(y_scale[scale_index2])) + zp2, {{ qMin }}, {{ qMax }});
 
1
+ {% set yUnsigned = yUnsigned is defined and yUnsigned %}
2
+ {% set qMin = qMin | default(0 if yUnsigned else 0 - 128) %}
3
+ {% set qMax = qMax | default(255 if yUnsigned else 127) %}
4
  // int8/uint8 tensors are stored widened (one u32/i32 per element), so the vec4
5
  // binding gives 128-bit loads/stores of four elements. Per-component arithmetic
6
  // remains identical to the scalar quantize/dequantize paths.
 
 
 
7
  {{ env.wgsl.resourceDeclarations }}
8
 
9
  {% set divisionF16 = divisionF16 is defined and divisionF16 %}
 
32
  // Clamp before the i32 cast so infinite and huge finite inputs saturate
33
  // instead of invoking undefined conversion behavior. Compare distances in
34
  // the input domain: doing the comparison on value / scale can move a value
35
+ // across a halfway boundary because WGSL division is not required to be
36
+ // correctly rounded.
37
  let v = clamp(value / scale, -2.0e9, 2.0e9);
38
  let fl = floor(v);
39
  let lo = fl;
 
53
  {% endif %}
54
 
55
 
56
+ {% if vectorParams is defined and vectorParams %}
57
  fn read_zero4({% if hasZero %}index: u32{% endif %}) -> vec4<i32> {
58
  {% if hasZero %}
59
  {% if yUnsigned %}
 
80
  {% endif %}
81
 
82
  @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
83
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
84
+ // The flat dispatch is folded across x/y at a fixed per-axis workgroup
85
+ // width; gid.y carries the high portion of the vector index.
86
+ let i = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
87
  if (i >= params.count4) {
88
  return;
89
  }
90
+ {% if vectorParams is defined and vectorParams %}
91
  let scale_index = i % (params.scaleSize / 4u);
92
+ {% elif crossingParams is defined and crossingParams %}
93
  let base_index = i * 4u;
94
  let scale_index0 = (base_index / params.inner) % params.scaleSize;
95
  let scale_index1 = ((base_index + 1u) / params.inner) % params.scaleSize;
96
  let scale_index2 = ((base_index + 2u) / params.inner) % params.scaleSize;
97
  let scale_index3 = ((base_index + 3u) / params.inner) % params.scaleSize;
98
+ {% elif perAxis %}
99
  // inner % 4 == 0, so all 4 lanes share one scale index.
100
  let scale_index = ((i * 4u) / params.inner) % params.scaleSize;
101
  {% else %}
102
  let scale_index = 0u;
103
  {% endif %}
104
  let xv = x[i];
105
+ {% if vectorParams is defined and vectorParams %}
106
  let zp4 = read_zero4({% if hasZero %}scale_index{% endif %});
107
+ {% elif crossingParams is defined and crossingParams %}
108
  let zp0 = read_zero(scale_index0);
109
  let zp1 = read_zero(scale_index1);
110
  let zp2 = read_zero(scale_index2);
 
112
  {% else %}
113
  let zp = read_zero({% if hasZero %}scale_index{% endif %});
114
  {% endif %}
115
+ {% if vectorParams is defined and vectorParams %}
116
  let scale = vec4<f32>(y_scale[scale_index]);
117
  let q0 = clamp(round_scaled_half_to_even(f32(xv.x), scale.x) + zp4.x, {{ qMin }}, {{ qMax }});
118
  let q1 = clamp(round_scaled_half_to_even(f32(xv.y), scale.y) + zp4.y, {{ qMin }}, {{ qMax }});
119
  let q2 = clamp(round_scaled_half_to_even(f32(xv.z), scale.z) + zp4.z, {{ qMin }}, {{ qMax }});
120
  let q3 = clamp(round_scaled_half_to_even(f32(xv.w), scale.w) + zp4.w, {{ qMin }}, {{ qMax }});
121
+ {% elif crossingParams is defined and crossingParams %}
122
  let q0 = clamp(round_scaled_half_to_even(f32(xv.x), f32(y_scale[scale_index0])) + zp0, {{ qMin }}, {{ qMax }});
123
  let q1 = clamp(round_scaled_half_to_even(f32(xv.y), f32(y_scale[scale_index1])) + zp1, {{ qMin }}, {{ qMax }});
124
  let q2 = clamp(round_scaled_half_to_even(f32(xv.z), f32(y_scale[scale_index2])) + zp2, {{ qMin }}, {{ qMax }});
build/webgpu/test.json CHANGED
@@ -1,5 +1,4 @@
1
  {
2
- "op": "ai.onnx.QuantizeLinear",
3
  "fixtureArrays": {
4
  "uint8_axis1_rank4_onnx_backend_input_x": [-162, 10, -100, 232, -20, -50, -76, 0, 0, 252, 32, -44, 245, -485, -960, -270, -375, -470]
5
  },
@@ -71,7 +70,7 @@
71
  "provenance": {
72
  "source": "onnxruntime/test/providers/cpu/tensor/quantize_linear_test.cc",
73
  "test": "QuantizeLinearOpTest.Int8",
74
- "notes": "Signed-output subnormal scale companion: distinct one-LSB signed steps should survive instead of collapsing through zero-scale flushing."
75
  },
76
  "inputs": {
77
  "x": {
@@ -100,7 +99,7 @@
100
  "provenance": {
101
  "source": "onnxruntime/test/providers/cpu/tensor/quantize_linear_test.cc",
102
  "test": "QuantizeLinearOpTest.Int8",
103
- "notes": "Vec4 signed-output companion for subnormal QuantizeLinear scale handling."
104
  },
105
  "inputs": {
106
  "x": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [-1e-40, 0.0, 1e-40, 2e-40] } },
@@ -120,7 +119,7 @@
120
  "provenance": {
121
  "source": "onnxruntime/test/providers/cpu/tensor/quantize_linear_test.cc",
122
  "test": "QuantizeLinearOpTest.Per_Channel_Axis_Default",
123
- "notes": "Per-axis companion for subnormal QuantizeLinear scale handling: each channel uses a valid subnormal scale and nonzero zero point."
124
  },
125
  "attrs": { "axis": 1 },
126
  "inputs": {
@@ -154,7 +153,7 @@
154
  "provenance": {
155
  "source": "onnxruntime/test/providers/cpu/tensor/quantize_linear_test.cc",
156
  "test": "QuantizeLinearOpTest.Per_Channel_Axis_0",
157
- "notes": "Vec4 per-axis companion for subnormal QuantizeLinear scale handling: axis=0 has inner size 4, so the vectorized channel path must preserve tiny one-LSB steps."
158
  },
159
  "attrs": { "axis": 0 },
160
  "inputs": {
@@ -989,7 +988,7 @@
989
  "provenance": {
990
  "source": "onnxruntime/test/providers/cpu/tensor/quantize_linear_test.cc",
991
  "test": "QuantizeLinearOp21BlockedTest.UnsignedInt_UseZeroPoint_MiddleAxis",
992
- "notes": "Blocked quantization saturates large finite scaled values after round-to-even, matching the non-blocked QuantizeLinear path (the blocked kernel now clamps value/scale into i32 range before the cast)."
993
  },
994
  "attrs": { "axis": 1, "block_size": 2 },
995
  "inputs": {
@@ -1242,7 +1241,7 @@
1242
  {
1243
  "name": "vec4_cross_axis_rows_width6_no_zero_point",
1244
  "provenance": {
1245
- "notes": "Symmetric per-channel quantization over six-element rows with y_zero_point omitted (ONNX makes it optional and defaults it to 0), so a four-lane vector still crosses each row boundary and may need two different per-axis scale entries while no zero-point binding exists. Twin of vec4_cross_axis_rows_width6_with_zero_point; signed int8 output keeps the negative half of each row representable at zero point 0, and the seven-value input cycle is coprime with the six-wide row so no two rows repeat the same lane pattern. No quotient lands on a .5 tie, so the result is exact for either rounding of a tie."
1246
  },
1247
  "attrs": { "axis": 0, "output_dtype": 3 },
1248
  "inputs": {
@@ -1271,7 +1270,7 @@
1271
  {
1272
  "name": "f16_inputs_explicit_float32_precision",
1273
  "provenance": {
1274
- "notes": "Twin of the default-precision witness with precision=FLOAT (TensorProto code 1), proving that an explicit f32 division remains distinct and rounds to 13."
1275
  },
1276
  "attrs": { "precision": 1 },
1277
  "inputs": {
 
1
  {
 
2
  "fixtureArrays": {
3
  "uint8_axis1_rank4_onnx_backend_input_x": [-162, 10, -100, 232, -20, -50, -76, 0, 0, 252, 32, -44, 245, -485, -960, -270, -375, -470]
4
  },
 
70
  "provenance": {
71
  "source": "onnxruntime/test/providers/cpu/tensor/quantize_linear_test.cc",
72
  "test": "QuantizeLinearOpTest.Int8",
73
+ "notes": "With signed output and a subnormal scale, distinct one-LSB steps must survive instead of collapsing through zero-scale flushing."
74
  },
75
  "inputs": {
76
  "x": {
 
99
  "provenance": {
100
  "source": "onnxruntime/test/providers/cpu/tensor/quantize_linear_test.cc",
101
  "test": "QuantizeLinearOpTest.Int8",
102
+ "notes": "Vectorized signed-output quantization must preserve a valid subnormal scale."
103
  },
104
  "inputs": {
105
  "x": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [-1e-40, 0.0, 1e-40, 2e-40] } },
 
119
  "provenance": {
120
  "source": "onnxruntime/test/providers/cpu/tensor/quantize_linear_test.cc",
121
  "test": "QuantizeLinearOpTest.Per_Channel_Axis_Default",
122
+ "notes": "Each channel uses a valid subnormal scale and a nonzero zero point in per-axis quantization."
123
  },
124
  "attrs": { "axis": 1 },
125
  "inputs": {
 
153
  "provenance": {
154
  "source": "onnxruntime/test/providers/cpu/tensor/quantize_linear_test.cc",
155
  "test": "QuantizeLinearOpTest.Per_Channel_Axis_0",
156
+ "notes": "Axis 0 has inner size 4, so vectorized per-axis quantization must preserve tiny one-LSB steps under subnormal scales."
157
  },
158
  "attrs": { "axis": 0 },
159
  "inputs": {
 
988
  "provenance": {
989
  "source": "onnxruntime/test/providers/cpu/tensor/quantize_linear_test.cc",
990
  "test": "QuantizeLinearOp21BlockedTest.UnsignedInt_UseZeroPoint_MiddleAxis",
991
+ "notes": "Blocked quantization clamps the scaled value to int32 range before conversion, then applies round-to-even and output saturation."
992
  },
993
  "attrs": { "axis": 1, "block_size": 2 },
994
  "inputs": {
 
1241
  {
1242
  "name": "vec4_cross_axis_rows_width6_no_zero_point",
1243
  "provenance": {
1244
+ "notes": "Symmetric per-channel quantization over six-element rows omits y_zero_point, so it defaults to zero. Four-lane vectors cross row boundaries and may require two scale entries; signed int8 output preserves negative values, and a coprime seven-value cycle gives every row a distinct lane pattern. No quotient lands on a half-way tie."
1245
  },
1246
  "attrs": { "axis": 0, "output_dtype": 3 },
1247
  "inputs": {
 
1270
  {
1271
  "name": "f16_inputs_explicit_float32_precision",
1272
  "provenance": {
1273
+ "notes": "With precision=FLOAT (TensorProto code 1), explicit f32 division produces a quotient that rounds to 13."
1274
  },
1275
  "attrs": { "precision": 1 },
1276
  "inputs": {