Xenova HF Staff commited on
Commit
62b03c9
·
verified ·
1 Parent(s): fb9dcfe

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,80 @@
1
  ---
 
2
  license: apache-2.0
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ library_name: kernels
3
  license: apache-2.0
4
+ tags:
5
+ - kernel
6
+ - webgpu
7
+ - wgsl
8
  ---
9
+ # com.microsoft.GatherBlockQuantized
10
+
11
+ `com.microsoft` · ONNX Runtime contrib operator · contrib since_version 1
12
+
13
+ ## Description
14
+
15
+ Gathers rows from a block-wise quantized weight matrix and dequantizes them. This inference implementation supports the standard `gather_axis = 0`, `quantize_axis = 1` matrix subset with uint8 `data`, 4-bit packed or 8-bit values, rank-1 non-negative in-bounds int64 `indices` projected to uint32 WebGPU storage, and float32 scales/output. Higher-rank gathers, negative indices, int32 indices, int4/uint4 data, 2-bit data, float16/bfloat16 output, and non-default axes are not implemented.
16
+
17
+ See the [ONNX Runtime `GatherBlockQuantized` contrib-operator spec](https://github.com/microsoft/onnxruntime/blob/main/docs/ContribOperators.md#com.microsoft.GatherBlockQuantized) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | WebGPU storage | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- | --- |
23
+ | `data` | `dataT` | `T1` | runtime-selected; narrow integers and bool use 32-bit slots | `2` | — | Constant uint8 weight matrix. With `bits = 4`, each byte stores two values low-nibble first; with `bits = 8`, each byte stores one value. | required |
24
+ | `indices` | `indicesT` | `Tind` | `uint32` | `1` | — | Non-negative logical int64 indices selecting rows from axis 0 of `data`. Every index must be less than the row count; values use checked uint32 WebGPU storage. | required |
25
+ | `scales` | `scalesT` | `T2` | same as logical dtype | `2` | — | Per-block dequantization scale factors of shape `(rows, ceil(output_columns / block_size))`. | required |
26
+ | `zero_points` | `zeroPointsT` | `T1` | runtime-selected; narrow integers and bool use 32-bit slots | `2` | — | Optional uint8 zero points. At 4 bits two zero points are packed per byte along the quantized axis, low-nibble first; at 8 bits the shape matches `scales`. If absent, uint8 data uses 2^(bits-1). | optional |
27
+
28
+ ## Outputs
29
+
30
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
31
+ | --- | --- | --- | --- | --- | --- | --- |
32
+ | `output` | `outputT` | `T2` | `2` | derived; see description | Dequantized floating-point output rows corresponding to the gathered indices. | required |
33
+
34
+ ## Attributes
35
+
36
+ Default values (overridable per request):
37
+
38
+ | Attribute | Default | Description |
39
+ | --- | --- | --- |
40
+ | `bits` | `4` | Bits per quantized value. The schema default is 4; this implementation supports 4 or 8. |
41
+ | `block_size` | `128` | Number of values sharing a scale. Defaults to 128 and must be a power of two at least 16. |
42
+ | `gather_axis` | `0` | Axis from which values are gathered. This matrix implementation supports the standard default, axis 0. |
43
+ | `quantize_axis` | `1` | Axis split into quantization blocks. This matrix implementation supports the standard default, axis 1. |
44
+
45
+ ## Type constraints
46
+
47
+ | Variable | Allowed dtypes |
48
+ | --- | --- |
49
+ | `T1` | `uint8` |
50
+ | `T2` | `float32` |
51
+ | `Tind` | `int64` |
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
59
+ - [`gather-block-quantized-q4-pair.wgsl.jinja`](build/webgpu/gather-block-quantized-q4-pair.wgsl.jinja)
60
+ - [`gather-block-quantized-q8-vec4.wgsl.jinja`](build/webgpu/gather-block-quantized-q8-vec4.wgsl.jinja)
61
+
62
+ ## Use with `@huggingface/kernels`
63
+
64
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
65
+ It then allocates the result tensors automatically.
66
+
67
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
68
+
69
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
70
+
71
+ ```js
72
+ import { getKernel } from "@huggingface/kernels";
73
+
74
+ const kernel = await getKernel("webgpu-kernels/com.microsoft.GatherBlockQuantized", { version: 1 });
75
+ const { outputT } = await kernel({
76
+ dataT: { data: dataTData, shape: [4, 8] },
77
+ indicesT: { data: indicesTData, shape: [2] },
78
+ scalesT: { data: scalesTData, shape: [4, 1] },
79
+ });
80
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,386 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "com.microsoft.GatherBlockQuantized",
3
+ "tunableSpace": { "workgroupSize": [64, 128, 256] },
4
+ "cases": [
5
+ {
6
+ "name": "gather-block-q8-4096x1024-idx1024",
7
+ "preset": "smoke",
8
+ "vars": { "rows": 4096, "cols": 1024, "indexCount": 1024, "bits": 8, "blockSize": 32 },
9
+ "attrs": { "bits": 8, "block_size": 32 },
10
+ "inputs": {
11
+ "dataT": { "shape": [4096, 1024], "dtype": "uint8", "dist": "randint", "seed": 205, "min": 0, "max": 255 },
12
+ "indicesT": { "shape": [1024], "dtype": "uint32", "dist": "linearMod", "seed": 205, "step": 37, "mod": 4096 },
13
+ "scalesT": {
14
+ "shape": [4096, 32],
15
+ "dtype": "float32",
16
+ "dist": "uniform",
17
+ "seed": 206,
18
+ "offset": 0.04,
19
+ "scale": 0.01,
20
+ "signed": false
21
+ }
22
+ },
23
+ "outputs": { "outputT": { "shape": [1024, 1024], "dtype": "float32" } },
24
+ "bench": {
25
+ "metrics": [
26
+ {
27
+ "type": "bandwidth",
28
+ "value": "args.indexCount * (args.cols * args.bits / 8 + dim(shapes.scalesT, 1) * 4 + args.cols * 4 + 4)"
29
+ }
30
+ ]
31
+ }
32
+ },
33
+ {
34
+ "name": "gather-block-q4-512x128-idx512",
35
+ "preset": "smoke",
36
+ "vars": { "rows": 512, "cols": 128, "indexCount": 512, "bits": 4, "blockSize": 32 },
37
+ "attrs": { "bits": 4, "block_size": 32 },
38
+ "inputs": {
39
+ "dataT": { "shape": [512, 64], "dtype": "uint8", "dist": "q4pair", "seed": 207 },
40
+ "indicesT": { "shape": [512], "dtype": "uint32", "dist": "linearMod", "seed": 207, "step": 17, "mod": 512 },
41
+ "scalesT": {
42
+ "shape": [512, 4],
43
+ "dtype": "float32",
44
+ "dist": "uniform",
45
+ "seed": 208,
46
+ "offset": 0.04,
47
+ "scale": 0.01,
48
+ "signed": false
49
+ }
50
+ },
51
+ "outputs": { "outputT": { "shape": [512, 128], "dtype": "float32" } },
52
+ "bench": {
53
+ "primary": true,
54
+ "metrics": [
55
+ {
56
+ "type": "bandwidth",
57
+ "value": "args.indexCount * (args.cols * args.bits / 8 + dim(shapes.scalesT, 1) * 4 + args.cols * 4 + 4)"
58
+ }
59
+ ]
60
+ }
61
+ },
62
+ {
63
+ "name": "gather-block-q4-zero-512x128-idx512",
64
+ "preset": "smoke",
65
+ "vars": { "rows": 512, "cols": 128, "indexCount": 512, "bits": 4, "blockSize": 32 },
66
+ "attrs": { "bits": 4, "block_size": 32 },
67
+ "inputs": {
68
+ "dataT": { "shape": [512, 64], "dtype": "uint8", "dist": "q4pair", "seed": 208 },
69
+ "indicesT": { "shape": [512], "dtype": "uint32", "dist": "linearMod", "seed": 208, "step": 17, "mod": 512 },
70
+ "scalesT": {
71
+ "shape": [512, 4],
72
+ "dtype": "float32",
73
+ "dist": "uniform",
74
+ "seed": 209,
75
+ "offset": 0.04,
76
+ "scale": 0.01,
77
+ "signed": false
78
+ },
79
+ "zeroPointsT": { "shape": [512, 2], "dtype": "uint8", "dist": "q4pair", "seed": 210 }
80
+ },
81
+ "outputs": { "outputT": { "shape": [512, 128], "dtype": "float32" } },
82
+ "bench": {
83
+ "primary": true,
84
+ "metrics": [
85
+ {
86
+ "type": "bandwidth",
87
+ "value": "args.indexCount * (args.cols * args.bits / 8 + dim(shapes.scalesT, 1) * 4 + dim(shapes.zeroPointsT, 1) + args.cols * 4 + 4)"
88
+ }
89
+ ]
90
+ }
91
+ },
92
+ {
93
+ "name": "gather-block-q4-4096x1024-idx1024",
94
+ "preset": "smoke",
95
+ "vars": { "rows": 4096, "cols": 1024, "indexCount": 1024, "bits": 4, "blockSize": 32 },
96
+ "attrs": { "bits": 4, "block_size": 32 },
97
+ "inputs": {
98
+ "dataT": { "shape": [4096, 512], "dtype": "uint8", "dist": "q4pair", "seed": 211 },
99
+ "indicesT": { "shape": [1024], "dtype": "uint32", "dist": "linearMod", "seed": 211, "step": 37, "mod": 4096 },
100
+ "scalesT": {
101
+ "shape": [4096, 32],
102
+ "dtype": "float32",
103
+ "dist": "uniform",
104
+ "seed": 212,
105
+ "offset": 0.04,
106
+ "scale": 0.01,
107
+ "signed": false
108
+ }
109
+ },
110
+ "outputs": { "outputT": { "shape": [1024, 1024], "dtype": "float32" } },
111
+ "bench": {
112
+ "metrics": [
113
+ {
114
+ "type": "bandwidth",
115
+ "value": "args.indexCount * (args.cols * args.bits / 8 + dim(shapes.scalesT, 1) * 4 + args.cols * 4 + 4)"
116
+ }
117
+ ]
118
+ }
119
+ },
120
+ {
121
+ "name": "gather-block-q4-zero-4096x1024-idx1024",
122
+ "preset": "smoke",
123
+ "vars": { "rows": 4096, "cols": 1024, "indexCount": 1024, "bits": 4, "blockSize": 32 },
124
+ "attrs": { "bits": 4, "block_size": 32 },
125
+ "inputs": {
126
+ "dataT": { "shape": [4096, 512], "dtype": "uint8", "dist": "q4pair", "seed": 212 },
127
+ "indicesT": { "shape": [1024], "dtype": "uint32", "dist": "linearMod", "seed": 212, "step": 37, "mod": 4096 },
128
+ "scalesT": {
129
+ "shape": [4096, 32],
130
+ "dtype": "float32",
131
+ "dist": "uniform",
132
+ "seed": 213,
133
+ "offset": 0.04,
134
+ "scale": 0.01,
135
+ "signed": false
136
+ },
137
+ "zeroPointsT": { "shape": [4096, 16], "dtype": "uint8", "dist": "q4pair", "seed": 214 }
138
+ },
139
+ "outputs": { "outputT": { "shape": [1024, 1024], "dtype": "float32" } },
140
+ "bench": {
141
+ "metrics": [
142
+ {
143
+ "type": "bandwidth",
144
+ "value": "args.indexCount * (args.cols * args.bits / 8 + dim(shapes.scalesT, 1) * 4 + dim(shapes.zeroPointsT, 1) + args.cols * 4 + 4)"
145
+ }
146
+ ]
147
+ }
148
+ },
149
+ {
150
+ "name": "gather-block-q8-alignment-cliff-cols1023-scalar-fallback",
151
+ "preset": "smoke",
152
+ "vars": { "rows": 4096, "cols": 1023, "indexCount": 1024, "bits": 8, "blockSize": 32 },
153
+ "attrs": { "bits": 8, "block_size": 32 },
154
+ "inputs": {
155
+ "dataT": { "shape": [4096, 1023], "dtype": "uint8", "dist": "randint", "seed": 215, "min": 0, "max": 255 },
156
+ "indicesT": { "shape": [1024], "dtype": "uint32", "dist": "linearMod", "seed": 215, "step": 37, "mod": 4096 },
157
+ "scalesT": {
158
+ "shape": [4096, 32],
159
+ "dtype": "float32",
160
+ "dist": "uniform",
161
+ "seed": 216,
162
+ "offset": 0.04,
163
+ "scale": 0.01,
164
+ "signed": false
165
+ }
166
+ },
167
+ "outputs": { "outputT": { "shape": [1024, 1023], "dtype": "float32" } },
168
+ "bench": {
169
+ "metrics": [
170
+ {
171
+ "type": "bandwidth",
172
+ "value": "args.indexCount * (args.cols * args.bits / 8 + dim(shapes.scalesT, 1) * 4 + args.cols * 4 + 4)"
173
+ }
174
+ ]
175
+ }
176
+ },
177
+ {
178
+ "name": "gather-block-q8-alignment-healthy-cols1024-vec4",
179
+ "preset": "smoke",
180
+ "vars": { "rows": 4096, "cols": 1024, "indexCount": 1024, "bits": 8, "blockSize": 32 },
181
+ "attrs": { "bits": 8, "block_size": 32 },
182
+ "inputs": {
183
+ "dataT": { "shape": [4096, 1024], "dtype": "uint8", "dist": "randint", "seed": 215, "min": 0, "max": 255 },
184
+ "indicesT": { "shape": [1024], "dtype": "uint32", "dist": "linearMod", "seed": 215, "step": 37, "mod": 4096 },
185
+ "scalesT": {
186
+ "shape": [4096, 32],
187
+ "dtype": "float32",
188
+ "dist": "uniform",
189
+ "seed": 216,
190
+ "offset": 0.04,
191
+ "scale": 0.01,
192
+ "signed": false
193
+ }
194
+ },
195
+ "outputs": { "outputT": { "shape": [1024, 1024], "dtype": "float32" } },
196
+ "bench": {
197
+ "metrics": [
198
+ {
199
+ "type": "bandwidth",
200
+ "value": "args.indexCount * (args.cols * args.bits / 8 + dim(shapes.scalesT, 1) * 4 + args.cols * 4 + 4)"
201
+ }
202
+ ]
203
+ }
204
+ },
205
+ {
206
+ "name": "gather-block-q8-dispatch-cliff-vocab-idx8192-cols4096-2dfold",
207
+ "preset": "stress",
208
+ "provenance": {
209
+ "notes": "Stress-only dispatch/capacity case: widened uint8 GPU storage brings the declared footprint to 392 MiB."
210
+ },
211
+ "vars": { "rows": 16384, "cols": 4096, "indexCount": 8192, "bits": 8, "blockSize": 32 },
212
+ "attrs": { "bits": 8, "block_size": 32 },
213
+ "inputs": {
214
+ "dataT": { "shape": [16384, 4096], "dtype": "uint8", "dist": "randint", "seed": 217, "min": 0, "max": 255 },
215
+ "indicesT": { "shape": [8192], "dtype": "uint32", "dist": "linearMod", "seed": 217, "step": 37, "mod": 16384 },
216
+ "scalesT": {
217
+ "shape": [16384, 128],
218
+ "dtype": "float32",
219
+ "dist": "uniform",
220
+ "seed": 218,
221
+ "offset": 0.04,
222
+ "scale": 0.01,
223
+ "signed": false
224
+ }
225
+ },
226
+ "outputs": { "outputT": { "shape": [8192, 4096], "dtype": "float32" } },
227
+ "bench": {
228
+ "metrics": [
229
+ {
230
+ "type": "bandwidth",
231
+ "value": "args.indexCount * (args.cols * args.bits / 8 + dim(shapes.scalesT, 1) * 4 + args.cols * 4 + 4)"
232
+ }
233
+ ]
234
+ }
235
+ },
236
+ {
237
+ "name": "gather-block-q8-dispatch-healthy-idx1024-cols4096-1d",
238
+ "preset": "stress",
239
+ "provenance": {
240
+ "notes": "Stress-only capacity sibling: widened uint8 GPU storage brings the declared footprint to 280 MiB."
241
+ },
242
+ "vars": { "rows": 16384, "cols": 4096, "indexCount": 1024, "bits": 8, "blockSize": 32 },
243
+ "attrs": { "bits": 8, "block_size": 32 },
244
+ "inputs": {
245
+ "dataT": { "shape": [16384, 4096], "dtype": "uint8", "dist": "randint", "seed": 217, "min": 0, "max": 255 },
246
+ "indicesT": { "shape": [1024], "dtype": "uint32", "dist": "linearMod", "seed": 217, "step": 37, "mod": 16384 },
247
+ "scalesT": {
248
+ "shape": [16384, 128],
249
+ "dtype": "float32",
250
+ "dist": "uniform",
251
+ "seed": 218,
252
+ "offset": 0.04,
253
+ "scale": 0.01,
254
+ "signed": false
255
+ }
256
+ },
257
+ "outputs": { "outputT": { "shape": [1024, 4096], "dtype": "float32" } },
258
+ "bench": {
259
+ "metrics": [
260
+ {
261
+ "type": "bandwidth",
262
+ "value": "args.indexCount * (args.cols * args.bits / 8 + dim(shapes.scalesT, 1) * 4 + args.cols * 4 + 4)"
263
+ }
264
+ ]
265
+ }
266
+ },
267
+ {
268
+ "name": "gather-block-q4-llama-v32000-h4096-idx1",
269
+ "preset": "model",
270
+ "provenance": {
271
+ "notes": "Llama class defaults (vocab_size 32000, hidden_size 4096) with a 4-bit block-quantized embedding table, gathering the single token of a decode step."
272
+ },
273
+ "vars": { "rows": 32000, "cols": 4096, "indexCount": 1, "bits": 4, "blockSize": 128 },
274
+ "attrs": { "bits": 4, "block_size": 128 },
275
+ "inputs": {
276
+ "dataT": { "shape": [32000, 2048], "dtype": "uint8", "dist": "uniform", "seed": 6200, "min": 0, "max": 255 },
277
+ "indicesT": { "shape": [1], "dtype": "uint32", "dist": "randint", "seed": 6201, "min": 0, "max": 31999 },
278
+ "scalesT": {
279
+ "shape": [32000, 32],
280
+ "dtype": "float32",
281
+ "dist": "uniform",
282
+ "seed": 6202,
283
+ "min": 0.005,
284
+ "max": 0.05
285
+ }
286
+ },
287
+ "outputs": { "outputT": { "shape": [1, 4096], "dtype": "float32" } },
288
+ "bench": {
289
+ "metrics": [
290
+ {
291
+ "type": "bandwidth",
292
+ "value": "args.indexCount * (args.cols * args.bits / 8 + dim(shapes.scalesT, 1) * 4 + args.cols * 4 + 4)"
293
+ }
294
+ ]
295
+ }
296
+ },
297
+ {
298
+ "name": "gather-block-q4-llama-v32000-h4096-idx512",
299
+ "preset": "model",
300
+ "provenance": { "notes": "Llama class defaults gathering a 512-token prefill chunk." },
301
+ "vars": { "rows": 32000, "cols": 4096, "indexCount": 512, "bits": 4, "blockSize": 128 },
302
+ "attrs": { "bits": 4, "block_size": 128 },
303
+ "inputs": {
304
+ "dataT": { "shape": [32000, 2048], "dtype": "uint8", "dist": "uniform", "seed": 6300, "min": 0, "max": 255 },
305
+ "indicesT": { "shape": [512], "dtype": "uint32", "dist": "randint", "seed": 6301, "min": 0, "max": 31999 },
306
+ "scalesT": {
307
+ "shape": [32000, 32],
308
+ "dtype": "float32",
309
+ "dist": "uniform",
310
+ "seed": 6302,
311
+ "min": 0.005,
312
+ "max": 0.05
313
+ }
314
+ },
315
+ "outputs": { "outputT": { "shape": [512, 4096], "dtype": "float32" } },
316
+ "bench": {
317
+ "metrics": [
318
+ {
319
+ "type": "bandwidth",
320
+ "value": "args.indexCount * (args.cols * args.bits / 8 + dim(shapes.scalesT, 1) * 4 + args.cols * 4 + 4)"
321
+ }
322
+ ]
323
+ }
324
+ },
325
+ {
326
+ "name": "gather-block-q4-qwen3-v151936-h2048-idx512",
327
+ "preset": "model",
328
+ "provenance": {
329
+ "notes": "Qwen3-MoE class defaults (vocab_size 151936, hidden_size 2048) -- the large-vocabulary case, where the gather is scattered over a much taller table."
330
+ },
331
+ "vars": { "rows": 151936, "cols": 2048, "indexCount": 512, "bits": 4, "blockSize": 128 },
332
+ "attrs": { "bits": 4, "block_size": 128 },
333
+ "inputs": {
334
+ "dataT": { "shape": [151936, 1024], "dtype": "uint8", "dist": "uniform", "seed": 6400, "min": 0, "max": 255 },
335
+ "indicesT": { "shape": [512], "dtype": "uint32", "dist": "randint", "seed": 6401, "min": 0, "max": 151935 },
336
+ "scalesT": {
337
+ "shape": [151936, 16],
338
+ "dtype": "float32",
339
+ "dist": "uniform",
340
+ "seed": 6402,
341
+ "min": 0.005,
342
+ "max": 0.05
343
+ }
344
+ },
345
+ "outputs": { "outputT": { "shape": [512, 2048], "dtype": "float32" } },
346
+ "bench": {
347
+ "metrics": [
348
+ {
349
+ "type": "bandwidth",
350
+ "value": "args.indexCount * (args.cols * args.bits / 8 + dim(shapes.scalesT, 1) * 4 + args.cols * 4 + 4)"
351
+ }
352
+ ]
353
+ }
354
+ },
355
+ {
356
+ "name": "gather-block-q8-whisper-v51865-h384-idx448",
357
+ "preset": "model",
358
+ "provenance": {
359
+ "notes": "Whisper class defaults (vocab_size 51865, d_model 384, max_target_positions 448) with an 8-bit table, gathering a full decoder context."
360
+ },
361
+ "vars": { "rows": 51865, "cols": 384, "indexCount": 448, "bits": 8, "blockSize": 32 },
362
+ "attrs": { "bits": 8, "block_size": 32 },
363
+ "inputs": {
364
+ "dataT": { "shape": [51865, 384], "dtype": "uint8", "dist": "uniform", "seed": 6500, "min": 0, "max": 255 },
365
+ "indicesT": { "shape": [448], "dtype": "uint32", "dist": "randint", "seed": 6501, "min": 0, "max": 51864 },
366
+ "scalesT": {
367
+ "shape": [51865, 12],
368
+ "dtype": "float32",
369
+ "dist": "uniform",
370
+ "seed": 6502,
371
+ "min": 0.005,
372
+ "max": 0.05
373
+ }
374
+ },
375
+ "outputs": { "outputT": { "shape": [448, 384], "dtype": "float32" } },
376
+ "bench": {
377
+ "metrics": [
378
+ {
379
+ "type": "bandwidth",
380
+ "value": "args.indexCount * (args.cols * args.bits / 8 + dim(shapes.scalesT, 1) * 4 + args.cols * 4 + 4)"
381
+ }
382
+ ]
383
+ }
384
+ }
385
+ ]
386
+ }
build/webgpu/gather-block-quantized-q4-pair.wgsl.jinja ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+
3
+ const WG: u32 = {{ workgroupSize }}u;
4
+
5
+ @compute @workgroup_size(WG, 1, 1)
6
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
7
+ @builtin(num_workgroups) nwg: vec3<u32>) {
8
+ // 2D-folded flat index: gid.y carries the high bits past the maxComputeWorkgroupsPerDimension
9
+ // workgroup-per-dimension dispatch limit. Reduces to gid.x when nwg.y == 1.
10
+ let pair_index = gid.x + gid.y * nwg.x * WG;
11
+ let total = params.indexCount * params.packedCols;
12
+ if (pair_index >= total) {
13
+ return;
14
+ }
15
+
16
+ let out_row = pair_index / params.packedCols;
17
+ let packed_col = pair_index % params.packedCols;
18
+ let data_row = indices[out_row];
19
+ if (data_row >= params.rows) {
20
+ return;
21
+ }
22
+ let packed = data[data_row * params.packedCols + packed_col];
23
+ let col0 = packed_col * 2u;
24
+ let block = col0 / params.blockSize;
25
+ let scale = scales[data_row * params.blocks + block];
26
+ {% if hasZero %}
27
+ let packed_zero = zero_points[data_row * params.zeroPointCols + block / 2u];
28
+ let zero = f32((packed_zero >> ((block % 2u) * 4u)) & 15u);
29
+ {% else %}
30
+ // uint8 data with bits=4 defaults to zero point 2^(bits-1) = 8.
31
+ let zero = 8.0;
32
+ {% endif %}
33
+ let q0 = f32(packed & 15u);
34
+ let q1 = f32((packed >> 4u) & 15u);
35
+ output[pair_index] = vec2<f32>((q0 - zero) * scale, (q1 - zero) * scale);
36
+ }
build/webgpu/gather-block-quantized-q8-vec4.wgsl.jinja ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{ env.wgsl.resourceDeclarations }}
2
+
3
+ const WG: u32 = {{ workgroupSize }}u;
4
+
5
+ @compute @workgroup_size(WG, 1, 1)
6
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>,
7
+ @builtin(num_workgroups) nwg: vec3<u32>) {
8
+ // 2D-folded flat index: gid.y carries the high bits past the maxComputeWorkgroupsPerDimension
9
+ // workgroup-per-dimension dispatch limit. Reduces to gid.x when nwg.y == 1.
10
+ let vec_index = gid.x + gid.y * nwg.x * WG;
11
+ {% if scalarTail %}
12
+ let row_vecs = (params.cols + 3u) / 4u;
13
+ {% else %}
14
+ let row_vecs = params.cols / 4u;
15
+ {% endif %}
16
+ let total = params.indexCount * row_vecs;
17
+ if (vec_index >= total) {
18
+ return;
19
+ }
20
+
21
+ let out_row = vec_index / row_vecs;
22
+ let vec_col = vec_index % row_vecs;
23
+ let data_row = indices[out_row];
24
+ let col0 = vec_col * 4u;
25
+ if (data_row >= params.rows) {
26
+ return;
27
+ }
28
+ {% if scalarTail %}
29
+ {% for lane in range(4) %}
30
+ if (col0 + {{ lane }}u < params.cols) {
31
+ let col{{ lane }} = col0 + {{ lane }}u;
32
+ let block{{ lane }} = col{{ lane }} / params.blockSize;
33
+ let scale{{ lane }} = scales[data_row * params.blocks + block{{ lane }}];
34
+ let q{{ lane }} = data[data_row * params.packedCols + col{{ lane }}] & 255u;
35
+ {% if hasZero %}
36
+ let zero{{ lane }} = f32(zero_points[data_row * params.blocks + block{{ lane }}]);
37
+ output[out_row * params.cols + col{{ lane }}] = (f32(q{{ lane }}) - zero{{ lane }}) * scale{{ lane }};
38
+ {% else %}
39
+ // zero_points omitted: the default is 2^(bits-1) = 128 at 8 bits, because `data`
40
+ // is unsigned storage for signed values offset by the midpoint. Dropping the term
41
+ // would shift every dequantized value by 128 * scale.
42
+ output[out_row * params.cols + col{{ lane }}] = (f32(q{{ lane }}) - 128.0) * scale{{ lane }};
43
+ {% endif %}
44
+ }
45
+ {% endfor %}
46
+ {% else %}
47
+ let block = col0 / params.blockSize;
48
+ let scale = scales[data_row * params.blocks + block];
49
+ let q = data[data_row * row_vecs + vec_col] & vec4<u32>(255u);
50
+ // blockSize % 4 == 0 (gated) guarantees the 4 packed columns share one block,
51
+ // so one scale and one zero point apply to the whole vec4.
52
+ {% if hasZero %}
53
+ let zero = f32(zero_points[data_row * params.blocks + block]);
54
+ output[vec_index] = (vec4<f32>(q) - vec4<f32>(zero)) * vec4<f32>(scale);
55
+ {% else %}
56
+ // zero_points omitted: default 2^(bits-1) = 128 at 8 bits (see the scalar arm).
57
+ output[vec_index] = (vec4<f32>(q) - vec4<f32>(128.0)) * vec4<f32>(scale);
58
+ {% endif %}
59
+ {% endif %}
60
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,596 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "com.microsoft",
3
+ "name": "GatherBlockQuantized",
4
+ "sinceVersion": 1,
5
+ "description": "Gathers rows from a block-wise quantized weight matrix and dequantizes them. This inference implementation supports the standard `gather_axis = 0`, `quantize_axis = 1` matrix subset with uint8 `data`, 4-bit packed or 8-bit values, rank-1 non-negative in-bounds int64 `indices` projected to uint32 WebGPU storage, and float32 scales/output. Higher-rank gathers, negative indices, int32 indices, int4/uint4 data, 2-bit data, float16/bfloat16 output, and non-default axes are not implemented.",
6
+ "inputs": [
7
+ {
8
+ "role": "data",
9
+ "dtype": "T1",
10
+ "rank": 2,
11
+ "description": "Constant uint8 weight matrix. With `bits = 4`, each byte stores two values low-nibble first; with `bits = 8`, each byte stores one value."
12
+ },
13
+ {
14
+ "role": "indices",
15
+ "dtype": "Tind",
16
+ "rank": 1,
17
+ "description": "Non-negative logical int64 indices selecting rows from axis 0 of `data`. Every index must be less than the row count; values use checked uint32 WebGPU storage."
18
+ },
19
+ {
20
+ "role": "scales",
21
+ "dtype": "T2",
22
+ "rank": 2,
23
+ "description": "Per-block dequantization scale factors of shape `(rows, ceil(output_columns / block_size))`."
24
+ },
25
+ {
26
+ "role": "zero_points",
27
+ "dtype": "T1",
28
+ "rank": 2,
29
+ "optional": true,
30
+ "description": "Optional uint8 zero points. At 4 bits two zero points are packed per byte along the quantized axis, low-nibble first; at 8 bits the shape matches `scales`. If absent, uint8 data uses 2^(bits-1)."
31
+ }
32
+ ],
33
+ "outputs": [
34
+ {
35
+ "role": "output",
36
+ "dtype": "T2",
37
+ "rank": 2,
38
+ "shape": "[dim(shapes.indices, 0), dim(shapes.data, 1) * (8 / attrs.bits)]",
39
+ "description": "Dequantized floating-point output rows corresponding to the gathered indices."
40
+ }
41
+ ],
42
+ "attributes": { "bits": 4, "block_size": 128, "gather_axis": 0, "quantize_axis": 1 },
43
+ "attributeConstraints": {
44
+ "bits": { "values": [4, 8] },
45
+ "gather_axis": { "values": [0] },
46
+ "quantize_axis": { "values": [1] }
47
+ },
48
+ "attributeDescriptions": {
49
+ "bits": "Bits per quantized value. The schema default is 4; this implementation supports 4 or 8.",
50
+ "block_size": "Number of values sharing a scale. Defaults to 128 and must be a power of two at least 16.",
51
+ "gather_axis": "Axis from which values are gathered. This matrix implementation supports the standard default, axis 0.",
52
+ "quantize_axis": "Axis split into quantization blocks. This matrix implementation supports the standard default, axis 1."
53
+ },
54
+ "typeConstraints": { "T1": ["uint8"], "T2": ["float32"], "Tind": ["int64"] },
55
+ "args": {
56
+ "dataT": { "kind": "tensor", "semantic": "data", "role": "input" },
57
+ "indicesT": { "kind": "tensor", "semantic": "indices", "role": "input", "dtype": "uint32", "narrowing": "checked" },
58
+ "scalesT": { "kind": "tensor", "semantic": "scales", "role": "input" },
59
+ "zeroPointsT": { "kind": "tensor", "semantic": "zero_points", "role": "input", "required": false },
60
+ "outputT": { "kind": "tensor", "semantic": "output", "role": "output" }
61
+ },
62
+ "derive": {
63
+ "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
64
+ "bits": "attrs.bits",
65
+ "blockSize": "attrs.block_size",
66
+ "blockSizeOk": "blockSize >= 16 and pow2ceil(blockSize) == blockSize",
67
+ "outBlocks": "ceilDiv(dim(shapes.output, 1), blockSize)",
68
+ "zeroPointCols": "ceilDiv(outBlocks, 2) if bits == 4 else outBlocks",
69
+ "workgroupSize": "min(tunables.WORKGROUP_SIZE, deviceWorkgroupCap)",
70
+ "commonShapeValid": "blockSizeOk and ranks.data == 2 and ranks.indices == 1 and ranks.scales == 2 and ranks.output == 2 and tensorDtypes.indices == \"uint32\" and tensorDtypes.scales == \"float32\" and tensorDtypes.output == \"float32\" and dim(shapes.output, 0) == dim(shapes.indices, 0) and dim(shapes.scales, 0) == dim(shapes.data, 0) and dim(shapes.scales, 1) == outBlocks",
71
+ "q4ShapeValid": "commonShapeValid and tensorDtypes.data == \"uint8\" and dim(shapes.output, 1) == dim(shapes.data, 1) * 2",
72
+ "q8ShapeValid": "commonShapeValid and tensorDtypes.data == \"uint8\" and dim(shapes.output, 1) == dim(shapes.data, 1)",
73
+ "zeroPointsValid": "present.zeroPointsT and ranks.zero_points == 2 and tensorDtypes.zero_points == \"uint8\" and dim(shapes.zero_points, 0) == dim(shapes.data, 0) and dim(shapes.zero_points, 1) == zeroPointCols",
74
+ "noZeroMode": "not present.zeroPointsT",
75
+ "zeroMode": "zeroPointsValid",
76
+ "workgroupFits": "workgroupSize > 0",
77
+ "foldedDispatchFits": "ceil(ceil(numel(shapes.output) / device.limits.maxComputeWorkgroupsPerDimension) / workgroupSize) <= device.limits.maxComputeWorkgroupsPerDimension"
78
+ },
79
+ "tunables": { "WORKGROUP_SIZE": 64 },
80
+ "bindingSets": {
81
+ "noZero": [
82
+ {
83
+ "name": "data",
84
+ "arg": "dataT",
85
+ "semantic": "data",
86
+ "buffer": { "type": "read-only-storage" },
87
+ "elementType": "$dataElement"
88
+ },
89
+ {
90
+ "name": "indices",
91
+ "arg": "indicesT",
92
+ "semantic": "indices",
93
+ "buffer": { "type": "read-only-storage" },
94
+ "elementType": "$indexScalar"
95
+ },
96
+ {
97
+ "name": "scales",
98
+ "arg": "scalesT",
99
+ "semantic": "scales",
100
+ "buffer": { "type": "read-only-storage" },
101
+ "elementType": "$scaleScalar"
102
+ },
103
+ {
104
+ "name": "output",
105
+ "arg": "outputT",
106
+ "semantic": "output",
107
+ "buffer": { "type": "storage" },
108
+ "elementType": "$outputElement"
109
+ },
110
+ {
111
+ "name": "params",
112
+ "semantic": "kernel.params",
113
+ "buffer": { "type": "uniform" },
114
+ "struct": {
115
+ "name": "Params",
116
+ "fields": [
117
+ { "name": "indexCount", "type": "u32", "value": "dim(shapes.indices, 0)" },
118
+ { "name": "cols", "type": "u32", "value": "dim(shapes.output, 1)" },
119
+ { "name": "packedCols", "type": "u32", "value": "dim(shapes.data, 1)" },
120
+ { "name": "blocks", "type": "u32", "value": "dim(shapes.scales, 1)" },
121
+ { "name": "blockSize", "type": "u32", "value": "blockSize" },
122
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" }
123
+ ]
124
+ }
125
+ }
126
+ ],
127
+ "zero": [
128
+ {
129
+ "name": "data",
130
+ "arg": "dataT",
131
+ "semantic": "data",
132
+ "buffer": { "type": "read-only-storage" },
133
+ "elementType": "$dataElement"
134
+ },
135
+ {
136
+ "name": "indices",
137
+ "arg": "indicesT",
138
+ "semantic": "indices",
139
+ "buffer": { "type": "read-only-storage" },
140
+ "elementType": "$indexScalar"
141
+ },
142
+ {
143
+ "name": "scales",
144
+ "arg": "scalesT",
145
+ "semantic": "scales",
146
+ "buffer": { "type": "read-only-storage" },
147
+ "elementType": "$scaleScalar"
148
+ },
149
+ {
150
+ "name": "zero_points",
151
+ "arg": "zeroPointsT",
152
+ "semantic": "zero_points",
153
+ "buffer": { "type": "read-only-storage" },
154
+ "elementType": "$zeroPointElement"
155
+ },
156
+ {
157
+ "name": "output",
158
+ "arg": "outputT",
159
+ "semantic": "output",
160
+ "buffer": { "type": "storage" },
161
+ "elementType": "$outputElement"
162
+ },
163
+ {
164
+ "name": "params",
165
+ "semantic": "kernel.params",
166
+ "buffer": { "type": "uniform" },
167
+ "struct": {
168
+ "name": "Params",
169
+ "fields": [
170
+ { "name": "indexCount", "type": "u32", "value": "dim(shapes.indices, 0)" },
171
+ { "name": "cols", "type": "u32", "value": "dim(shapes.output, 1)" },
172
+ { "name": "packedCols", "type": "u32", "value": "dim(shapes.data, 1)" },
173
+ { "name": "blocks", "type": "u32", "value": "dim(shapes.scales, 1)" },
174
+ { "name": "blockSize", "type": "u32", "value": "blockSize" },
175
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" }
176
+ ]
177
+ }
178
+ }
179
+ ],
180
+ "noZeroIo": [
181
+ {
182
+ "name": "data",
183
+ "arg": "dataT",
184
+ "semantic": "data",
185
+ "buffer": { "type": "read-only-storage" },
186
+ "elementType": "$dataElement"
187
+ },
188
+ {
189
+ "name": "indices",
190
+ "arg": "indicesT",
191
+ "semantic": "indices",
192
+ "buffer": { "type": "read-only-storage" },
193
+ "elementType": "$indexScalar"
194
+ },
195
+ {
196
+ "name": "scales",
197
+ "arg": "scalesT",
198
+ "semantic": "scales",
199
+ "buffer": { "type": "read-only-storage" },
200
+ "elementType": "$scaleScalar"
201
+ },
202
+ {
203
+ "name": "output",
204
+ "arg": "outputT",
205
+ "semantic": "output",
206
+ "buffer": { "type": "storage" },
207
+ "elementType": "$outputElement"
208
+ }
209
+ ],
210
+ "zeroIo": [
211
+ {
212
+ "name": "data",
213
+ "arg": "dataT",
214
+ "semantic": "data",
215
+ "buffer": { "type": "read-only-storage" },
216
+ "elementType": "$dataElement"
217
+ },
218
+ {
219
+ "name": "indices",
220
+ "arg": "indicesT",
221
+ "semantic": "indices",
222
+ "buffer": { "type": "read-only-storage" },
223
+ "elementType": "$indexScalar"
224
+ },
225
+ {
226
+ "name": "scales",
227
+ "arg": "scalesT",
228
+ "semantic": "scales",
229
+ "buffer": { "type": "read-only-storage" },
230
+ "elementType": "$scaleScalar"
231
+ },
232
+ {
233
+ "name": "zero_points",
234
+ "arg": "zeroPointsT",
235
+ "semantic": "zero_points",
236
+ "buffer": { "type": "read-only-storage" },
237
+ "elementType": "$zeroPointElement"
238
+ },
239
+ {
240
+ "name": "output",
241
+ "arg": "outputT",
242
+ "semantic": "output",
243
+ "buffer": { "type": "storage" },
244
+ "elementType": "$outputElement"
245
+ }
246
+ ],
247
+ "q4NoZero": [
248
+ {
249
+ "name": "data",
250
+ "arg": "dataT",
251
+ "semantic": "data",
252
+ "buffer": { "type": "read-only-storage" },
253
+ "elementType": "$dataElement"
254
+ },
255
+ {
256
+ "name": "indices",
257
+ "arg": "indicesT",
258
+ "semantic": "indices",
259
+ "buffer": { "type": "read-only-storage" },
260
+ "elementType": "$indexScalar"
261
+ },
262
+ {
263
+ "name": "scales",
264
+ "arg": "scalesT",
265
+ "semantic": "scales",
266
+ "buffer": { "type": "read-only-storage" },
267
+ "elementType": "$scaleScalar"
268
+ },
269
+ {
270
+ "name": "output",
271
+ "arg": "outputT",
272
+ "semantic": "output",
273
+ "buffer": { "type": "storage" },
274
+ "elementType": "$outputElement"
275
+ },
276
+ {
277
+ "name": "params",
278
+ "semantic": "kernel.params",
279
+ "buffer": { "type": "uniform" },
280
+ "struct": {
281
+ "name": "Params",
282
+ "fields": [
283
+ { "name": "indexCount", "type": "u32", "value": "dim(shapes.indices, 0)" },
284
+ { "name": "packedCols", "type": "u32", "value": "dim(shapes.data, 1)" },
285
+ { "name": "blocks", "type": "u32", "value": "dim(shapes.scales, 1)" },
286
+ { "name": "blockSize", "type": "u32", "value": "blockSize" },
287
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" }
288
+ ]
289
+ }
290
+ }
291
+ ],
292
+ "q4Zero": [
293
+ {
294
+ "name": "data",
295
+ "arg": "dataT",
296
+ "semantic": "data",
297
+ "buffer": { "type": "read-only-storage" },
298
+ "elementType": "$dataElement"
299
+ },
300
+ {
301
+ "name": "indices",
302
+ "arg": "indicesT",
303
+ "semantic": "indices",
304
+ "buffer": { "type": "read-only-storage" },
305
+ "elementType": "$indexScalar"
306
+ },
307
+ {
308
+ "name": "scales",
309
+ "arg": "scalesT",
310
+ "semantic": "scales",
311
+ "buffer": { "type": "read-only-storage" },
312
+ "elementType": "$scaleScalar"
313
+ },
314
+ {
315
+ "name": "zero_points",
316
+ "arg": "zeroPointsT",
317
+ "semantic": "zero_points",
318
+ "buffer": { "type": "read-only-storage" },
319
+ "elementType": "$zeroPointElement"
320
+ },
321
+ {
322
+ "name": "output",
323
+ "arg": "outputT",
324
+ "semantic": "output",
325
+ "buffer": { "type": "storage" },
326
+ "elementType": "$outputElement"
327
+ },
328
+ {
329
+ "name": "params",
330
+ "semantic": "kernel.params",
331
+ "buffer": { "type": "uniform" },
332
+ "struct": {
333
+ "name": "Params",
334
+ "fields": [
335
+ { "name": "indexCount", "type": "u32", "value": "dim(shapes.indices, 0)" },
336
+ { "name": "packedCols", "type": "u32", "value": "dim(shapes.data, 1)" },
337
+ { "name": "blocks", "type": "u32", "value": "dim(shapes.scales, 1)" },
338
+ { "name": "zeroPointCols", "type": "u32", "value": "zeroPointCols" },
339
+ { "name": "blockSize", "type": "u32", "value": "blockSize" },
340
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" }
341
+ ]
342
+ }
343
+ }
344
+ ],
345
+ "q8Vec4NoZero": [
346
+ {
347
+ "name": "data",
348
+ "arg": "dataT",
349
+ "semantic": "data",
350
+ "buffer": { "type": "read-only-storage" },
351
+ "elementType": "$dataElement"
352
+ },
353
+ {
354
+ "name": "indices",
355
+ "arg": "indicesT",
356
+ "semantic": "indices",
357
+ "buffer": { "type": "read-only-storage" },
358
+ "elementType": "$indexScalar"
359
+ },
360
+ {
361
+ "name": "scales",
362
+ "arg": "scalesT",
363
+ "semantic": "scales",
364
+ "buffer": { "type": "read-only-storage" },
365
+ "elementType": "$scaleScalar"
366
+ },
367
+ {
368
+ "name": "output",
369
+ "arg": "outputT",
370
+ "semantic": "output",
371
+ "buffer": { "type": "storage" },
372
+ "elementType": "$outputElement"
373
+ },
374
+ {
375
+ "name": "params",
376
+ "semantic": "kernel.params",
377
+ "buffer": { "type": "uniform" },
378
+ "struct": {
379
+ "name": "Params",
380
+ "fields": [
381
+ { "name": "indexCount", "type": "u32", "value": "dim(shapes.indices, 0)" },
382
+ { "name": "cols", "type": "u32", "value": "dim(shapes.output, 1)" },
383
+ { "name": "blocks", "type": "u32", "value": "dim(shapes.scales, 1)" },
384
+ { "name": "blockSize", "type": "u32", "value": "blockSize" },
385
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" }
386
+ ]
387
+ }
388
+ }
389
+ ],
390
+ "q8Vec4Zero": [
391
+ {
392
+ "name": "data",
393
+ "arg": "dataT",
394
+ "semantic": "data",
395
+ "buffer": { "type": "read-only-storage" },
396
+ "elementType": "$dataElement"
397
+ },
398
+ {
399
+ "name": "indices",
400
+ "arg": "indicesT",
401
+ "semantic": "indices",
402
+ "buffer": { "type": "read-only-storage" },
403
+ "elementType": "$indexScalar"
404
+ },
405
+ {
406
+ "name": "scales",
407
+ "arg": "scalesT",
408
+ "semantic": "scales",
409
+ "buffer": { "type": "read-only-storage" },
410
+ "elementType": "$scaleScalar"
411
+ },
412
+ {
413
+ "name": "zero_points",
414
+ "arg": "zeroPointsT",
415
+ "semantic": "zero_points",
416
+ "buffer": { "type": "read-only-storage" },
417
+ "elementType": "$zeroPointElement"
418
+ },
419
+ {
420
+ "name": "output",
421
+ "arg": "outputT",
422
+ "semantic": "output",
423
+ "buffer": { "type": "storage" },
424
+ "elementType": "$outputElement"
425
+ },
426
+ {
427
+ "name": "params",
428
+ "semantic": "kernel.params",
429
+ "buffer": { "type": "uniform" },
430
+ "struct": {
431
+ "name": "Params",
432
+ "fields": [
433
+ { "name": "indexCount", "type": "u32", "value": "dim(shapes.indices, 0)" },
434
+ { "name": "cols", "type": "u32", "value": "dim(shapes.output, 1)" },
435
+ { "name": "blocks", "type": "u32", "value": "dim(shapes.scales, 1)" },
436
+ { "name": "blockSize", "type": "u32", "value": "blockSize" },
437
+ { "name": "rows", "type": "u32", "value": "dim(shapes.data, 0)" }
438
+ ]
439
+ }
440
+ }
441
+ ]
442
+ },
443
+ "variants": [
444
+ {
445
+ "id": "q8_no_zero_vec4",
446
+ "priority": 10,
447
+ "when": ["q8ShapeValid", "noZeroMode", "bits == 8", "blockSize % 4 == 0", "dim(shapes.output, 1) % 4 == 0", "workgroupFits", "foldedDispatchFits"],
448
+ "constants": {
449
+ "hasZero": false,
450
+ "scalarTail": false,
451
+ "workgroupSize": "workgroupSize",
452
+ "dataElement": "\"vec4<u32>\"",
453
+ "indexScalar": "\"u32\"",
454
+ "scaleScalar": "\"f32\"",
455
+ "outputElement": "\"vec4<f32>\""
456
+ },
457
+ "passes": [
458
+ {
459
+ "id": "main",
460
+ "shader": "gather-block-quantized-q8-vec4.wgsl.jinja",
461
+ "bindings": "q8Vec4NoZero",
462
+ "dispatch": {
463
+ "threads": "dim(shapes.indices, 0) * (dim(shapes.output, 1) / 4)",
464
+ "workgroupSize": "constants.workgroupSize"
465
+ }
466
+ }
467
+ ]
468
+ },
469
+ {
470
+ "id": "q4_no_zero_pair",
471
+ "priority": 10,
472
+ "when": ["q4ShapeValid", "noZeroMode", "bits == 4", "workgroupFits", "foldedDispatchFits"],
473
+ "constants": {
474
+ "hasZero": false,
475
+ "workgroupSize": "workgroupSize",
476
+ "dataElement": "\"u32\"",
477
+ "indexScalar": "\"u32\"",
478
+ "scaleScalar": "\"f32\"",
479
+ "outputElement": "\"vec2<f32>\""
480
+ },
481
+ "passes": [
482
+ {
483
+ "id": "main",
484
+ "shader": "gather-block-quantized-q4-pair.wgsl.jinja",
485
+ "bindings": "q4NoZero",
486
+ "dispatch": {
487
+ "threads": "dim(shapes.indices, 0) * dim(shapes.data, 1)",
488
+ "workgroupSize": "constants.workgroupSize"
489
+ }
490
+ }
491
+ ]
492
+ },
493
+ {
494
+ "id": "q4_zero_pair",
495
+ "priority": 10,
496
+ "when": ["q4ShapeValid", "zeroMode", "bits == 4", "workgroupFits", "foldedDispatchFits"],
497
+ "constants": {
498
+ "hasZero": true,
499
+ "workgroupSize": "workgroupSize",
500
+ "dataElement": "\"u32\"",
501
+ "indexScalar": "\"u32\"",
502
+ "scaleScalar": "\"f32\"",
503
+ "outputElement": "\"vec2<f32>\"",
504
+ "zeroPointElement": "\"u32\""
505
+ },
506
+ "passes": [
507
+ {
508
+ "id": "main",
509
+ "shader": "gather-block-quantized-q4-pair.wgsl.jinja",
510
+ "bindings": "q4Zero",
511
+ "dispatch": {
512
+ "threads": "dim(shapes.indices, 0) * dim(shapes.data, 1)",
513
+ "workgroupSize": "constants.workgroupSize"
514
+ }
515
+ }
516
+ ]
517
+ },
518
+ {
519
+ "id": "q8_zero_vec4",
520
+ "priority": 10,
521
+ "when": ["q8ShapeValid", "zeroMode", "bits == 8", "blockSize % 4 == 0", "dim(shapes.output, 1) % 4 == 0", "workgroupFits", "foldedDispatchFits"],
522
+ "constants": {
523
+ "hasZero": true,
524
+ "scalarTail": false,
525
+ "workgroupSize": "workgroupSize",
526
+ "dataElement": "\"vec4<u32>\"",
527
+ "zeroPointElement": "\"u32\"",
528
+ "indexScalar": "\"u32\"",
529
+ "scaleScalar": "\"f32\"",
530
+ "outputElement": "\"vec4<f32>\""
531
+ },
532
+ "passes": [
533
+ {
534
+ "id": "main",
535
+ "shader": "gather-block-quantized-q8-vec4.wgsl.jinja",
536
+ "bindings": "q8Vec4Zero",
537
+ "dispatch": {
538
+ "threads": "dim(shapes.indices, 0) * (dim(shapes.output, 1) / 4)",
539
+ "workgroupSize": "constants.workgroupSize"
540
+ }
541
+ }
542
+ ]
543
+ },
544
+ {
545
+ "id": "q8_no_zero_tail4",
546
+ "priority": 5,
547
+ "when": ["q8ShapeValid", "noZeroMode", "bits == 8", "workgroupFits", "foldedDispatchFits"],
548
+ "constants": {
549
+ "hasZero": false,
550
+ "scalarTail": true,
551
+ "workgroupSize": "workgroupSize",
552
+ "dataElement": "\"u32\"",
553
+ "indexScalar": "\"u32\"",
554
+ "scaleScalar": "\"f32\"",
555
+ "outputElement": "\"f32\""
556
+ },
557
+ "passes": [
558
+ {
559
+ "id": "main",
560
+ "shader": "gather-block-quantized-q8-vec4.wgsl.jinja",
561
+ "bindings": "noZero",
562
+ "dispatch": {
563
+ "threads": "dim(shapes.indices, 0) * ceilDiv(dim(shapes.output, 1), 4)",
564
+ "workgroupSize": "constants.workgroupSize"
565
+ }
566
+ }
567
+ ]
568
+ },
569
+ {
570
+ "id": "q8_zero_tail4",
571
+ "priority": 5,
572
+ "when": ["q8ShapeValid", "zeroMode", "bits == 8", "workgroupFits", "foldedDispatchFits"],
573
+ "constants": {
574
+ "hasZero": true,
575
+ "scalarTail": true,
576
+ "workgroupSize": "workgroupSize",
577
+ "dataElement": "\"u32\"",
578
+ "indexScalar": "\"u32\"",
579
+ "scaleScalar": "\"f32\"",
580
+ "outputElement": "\"f32\"",
581
+ "zeroPointElement": "\"u32\""
582
+ },
583
+ "passes": [
584
+ {
585
+ "id": "main",
586
+ "shader": "gather-block-quantized-q8-vec4.wgsl.jinja",
587
+ "bindings": "zero",
588
+ "dispatch": {
589
+ "threads": "dim(shapes.indices, 0) * ceilDiv(dim(shapes.output, 1), 4)",
590
+ "workgroupSize": "constants.workgroupSize"
591
+ }
592
+ }
593
+ ]
594
+ }
595
+ ]
596
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "com.microsoft.GatherBlockQuantized",
3
+ "id": "_com_microsoft_gatherblockquantized_webgpu_88e1761",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "Pi6+cheiVelf+nINEm8o7zLFULpK/8jCmL8+6Jlp9I8=",
11
+ "gather-block-quantized-q4-pair.wgsl.jinja": "U+OSY+xymDWP4cMqTJneiOhIv+Sfazov3A7s3gplCXI=",
12
+ "gather-block-quantized-q8-vec4.wgsl.jinja": "YKplb0sTVxSB/jvuYiQ3HFxNR6sTBp206dp5EEi4TNM=",
13
+ "manifest.json": "On/e3F5KW0FA0mt8SUJXckCZKr4EZrxPKZS9JrQdvKU=",
14
+ "test.json": "XEZF0vZ5ZbO6reeoRXjDL1hLXosLscWnzfZjtHIr9aU="
15
+ }
16
+ },
17
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
18
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/com.microsoft.GatherBlockQuantized" }
19
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "com.microsoft.GatherBlockQuantized",
3
+ "fixtureArrays": {
4
+ "ort_q8_no_zero_input_dataT": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63],
5
+ "q8_no_zero_multiblock_vec4_cols32_input_dataT": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127]
6
+ },
7
+ "cases": [
8
+ {
9
+ "name": "ort_q8_no_zero",
10
+ "provenance": {
11
+ "source": "onnxruntime/test/python/transformers/test_cuda_plugin_ep.py",
12
+ "test": "TestCudaPluginEP.test_op_gather_block_quantized"
13
+ },
14
+ "attrs": { "bits": 8, "block_size": 16 },
15
+ "inputs": {
16
+ "dataT": {
17
+ "dtype": "uint8",
18
+ "shape": [4, 16],
19
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_q8_no_zero_input_dataT" } }
20
+ },
21
+ "indicesT": { "dtype": "uint32", "shape": [2], "data": { "kind": "values", "values": [0, 2] } },
22
+ "scalesT": {
23
+ "dtype": "float32",
24
+ "shape": [4, 1],
25
+ "data": { "kind": "values", "values": [0.01, 0.02, 0.03, 0.04] }
26
+ }
27
+ },
28
+ "outputs": { "outputT": { "dtype": "float32", "shape": [2, 16], "tolerance": 0.000001 } }
29
+ },
30
+ {
31
+ "name": "ort_q8_no_zero_points_default_midpoint",
32
+ "provenance": {
33
+ "source": "onnxruntime/test/python/transformers/test_cuda_plugin_ep.py",
34
+ "test": "TestCudaPluginEP.test_op_gather_block_quantized",
35
+ "notes": "Pinned. With zero_points omitted the default zero point is 2^(bits-1) = 128 at 8 bits, because `data` is unsigned storage for signed values offset by the midpoint; the cited test states this and ORT's kernel applies it in the uint8 branch of contrib_ops/cpu/quantization/gather_block_quantized.cc. Values are chosen so every expected element is exact in f32 and the two rows use different scales, so a wrong default shifts each row by a different amount rather than by a common offset."
36
+ },
37
+ "attrs": { "bits": 8, "block_size": 16 },
38
+ "inputs": {
39
+ "dataT": {
40
+ "dtype": "uint8",
41
+ "shape": [2, 16],
42
+ "data": {
43
+ "kind": "values",
44
+ "values": [120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158]
45
+ }
46
+ },
47
+ "indicesT": { "dtype": "uint32", "shape": [2], "data": { "kind": "values", "values": [0, 1] } },
48
+ "scalesT": { "dtype": "float32", "shape": [2, 1], "data": { "kind": "values", "values": [0.5, 0.25] } }
49
+ },
50
+ "outputs": {
51
+ "outputT": {
52
+ "dtype": "float32",
53
+ "shape": [2, 16],
54
+ "tolerance": 0.000001,
55
+ "data": {
56
+ "kind": "values",
57
+ "values": [-4.0, -3.5, -3.0, -2.5, -2.0, -1.5, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5]
58
+ }
59
+ }
60
+ }
61
+ },
62
+ {
63
+ "name": "ort_projection_q4_zero_points_block16_tail_rows",
64
+ "provenance": {
65
+ "source": "onnxruntime/test/contrib_ops/gather_block_quantized_op_test.cc",
66
+ "test": "GatherBlockQuantizedOpTest.GatherAxis0WithZeroPoints_4Bits",
67
+ "notes": "Rank-2 projection of ORT's uint8 q4 gather-axis-0 case with block_size=16, packed tail block, and three gathered rows."
68
+ },
69
+ "attrs": { "bits": 4, "block_size": 16 },
70
+ "inputs": {
71
+ "dataT": {
72
+ "dtype": "uint8",
73
+ "shape": [6, 9],
74
+ "data": {
75
+ "kind": "values",
76
+ "values": [16, 50, 16, 50, 16, 50, 16, 50, 128, 84, 118, 84, 118, 84, 118, 84, 118, 132, 152, 186, 152, 186, 152, 186, 152, 186, 136, 220, 254, 220, 254, 220, 254, 220, 254, 140, 220, 254, 220, 254, 220, 254, 220, 254, 140, 84, 118, 84, 118, 84, 118, 84, 118, 132]
77
+ }
78
+ },
79
+ "indicesT": { "dtype": "uint32", "shape": [3], "data": { "kind": "values", "values": [3, 4, 5] } },
80
+ "scalesT": {
81
+ "dtype": "float32",
82
+ "shape": [6, 2],
83
+ "data": { "kind": "values", "values": [1.0, 2.0, 1.0, 2.0, 1.0, 2.0, 2.0, 2.0, 1.0, 1.0, 2.0, 1.0] }
84
+ },
85
+ "zeroPointsT": {
86
+ "dtype": "uint8",
87
+ "shape": [6, 1],
88
+ "data": { "kind": "values", "values": [151, 136, 121, 121, 137, 151] }
89
+ }
90
+ },
91
+ "outputs": {
92
+ "outputT": {
93
+ "dtype": "float32",
94
+ "shape": [3, 18],
95
+ "tolerance": 0.000001,
96
+ "data": {
97
+ "kind": "values",
98
+ "values": [6.0, 8.0, 10.0, 12.0, 6.0, 8.0, 10.0, 12.0, 6.0, 8.0, 10.0, 12.0, 6.0, 8.0, 10.0, 12.0, 10.0, 2.0, 3.0, 4.0, 5.0, 6.0, 3.0, 4.0, 5.0, 6.0, 3.0, 4.0, 5.0, 6.0, 3.0, 4.0, 5.0, 6.0, 4.0, 0.0, -6.0, -4.0, -2.0, 0.0, -6.0, -4.0, -2.0, 0.0, -6.0, -4.0, -2.0, 0.0, -6.0, -4.0, -2.0, 0.0, -5.0, -1.0]
99
+ }
100
+ }
101
+ }
102
+ },
103
+ {
104
+ "name": "q8_no_zero_multiblock_vec4_cols32",
105
+ "attrs": { "bits": 8, "block_size": 16 },
106
+ "inputs": {
107
+ "dataT": {
108
+ "dtype": "uint8",
109
+ "shape": [4, 32],
110
+ "data": {
111
+ "kind": "values",
112
+ "values": { "$ref": "#/fixtureArrays/q8_no_zero_multiblock_vec4_cols32_input_dataT" }
113
+ }
114
+ },
115
+ "indicesT": { "dtype": "uint32", "shape": [3], "data": { "kind": "values", "values": [0, 3, 1] } },
116
+ "scalesT": {
117
+ "dtype": "float32",
118
+ "shape": [4, 2],
119
+ "data": { "kind": "values", "values": [0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08] }
120
+ }
121
+ },
122
+ "outputs": { "outputT": { "dtype": "float32", "shape": [3, 32], "tolerance": 0.000001 } }
123
+ },
124
+ {
125
+ "name": "q8_no_zero_wide_vec4_cols256_idx64",
126
+ "provenance": {
127
+ "notes": "Compact sibling for the q8 wide-column gather benchmark; preserves no-zero-point q8, blockSize=32, vec4-aligned columns, many gathered rows, and multiple scale blocks per source row."
128
+ },
129
+ "attrs": { "bits": 8, "block_size": 32 },
130
+ "inputs": {
131
+ "dataT": {
132
+ "dtype": "uint8",
133
+ "shape": [256, 256],
134
+ "data": { "kind": "cycle", "values": [0, 1, 2, 3, 4, 5, 31, 63, 127, 191, 255] }
135
+ },
136
+ "indicesT": {
137
+ "dtype": "uint32",
138
+ "shape": [64],
139
+ "data": { "kind": "cycle", "values": [0, 17, 63, 128, 255, 3, 42, 191] }
140
+ },
141
+ "scalesT": {
142
+ "dtype": "float32",
143
+ "shape": [256, 8],
144
+ "data": { "kind": "cycle", "values": [0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08] }
145
+ }
146
+ },
147
+ "outputs": { "outputT": { "dtype": "float32", "shape": [64, 256], "tolerance": 0.000001 } }
148
+ },
149
+ {
150
+ "name": "q4_zero_points_block16_ort_valid",
151
+ "attrs": { "bits": 4, "block_size": 16 },
152
+ "inputs": {
153
+ "dataT": {
154
+ "dtype": "uint8",
155
+ "shape": [4, 16],
156
+ "data": {
157
+ "kind": "values",
158
+ "values": [16, 50, 84, 118, 152, 186, 220, 254, 16, 33, 136, 119, 102, 85, 17, 34, 200, 152, 104, 56, 8, 216, 168, 120, 72, 24, 232, 184, 136, 88, 40, 248, 35, 70, 105, 140, 175, 210, 245, 21, 56, 91, 126, 161, 196, 231, 11, 46, 81, 116, 151, 186, 221, 0, 35, 70, 105, 140, 175, 210, 245, 21, 56, 91]
159
+ }
160
+ },
161
+ "indicesT": { "dtype": "uint32", "shape": [3], "data": { "kind": "values", "values": [3, 0, 2] } },
162
+ "scalesT": {
163
+ "dtype": "float32",
164
+ "shape": [4, 2],
165
+ "data": { "kind": "values", "values": [0.1, 0.2, 0.05, 0.125, 0.25, 0.15, 0.075, 0.3] }
166
+ },
167
+ "zeroPointsT": {
168
+ "dtype": "uint8",
169
+ "shape": [4, 1],
170
+ "data": { "kind": "values", "values": [120, 105, 135, 149] }
171
+ }
172
+ },
173
+ "outputs": { "outputT": { "dtype": "float32", "shape": [3, 32], "tolerance": 0.000001 } }
174
+ },
175
+ {
176
+ "name": "q8_with_zero_points_vec4_cols32",
177
+ "attrs": { "bits": 8, "block_size": 16 },
178
+ "inputs": {
179
+ "dataT": {
180
+ "dtype": "uint8",
181
+ "shape": [4, 32],
182
+ "data": {
183
+ "kind": "values",
184
+ "values": { "$ref": "#/fixtureArrays/q8_no_zero_multiblock_vec4_cols32_input_dataT" }
185
+ }
186
+ },
187
+ "indicesT": { "dtype": "uint32", "shape": [3], "data": { "kind": "values", "values": [0, 3, 1] } },
188
+ "scalesT": {
189
+ "dtype": "float32",
190
+ "shape": [4, 2],
191
+ "data": { "kind": "values", "values": [0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08] }
192
+ },
193
+ "zeroPointsT": {
194
+ "dtype": "uint8",
195
+ "shape": [4, 2],
196
+ "data": { "kind": "values", "values": [128, 100, 64, 90, 110, 130, 70, 120] }
197
+ }
198
+ },
199
+ "outputs": { "outputT": { "dtype": "float32", "shape": [3, 32], "tolerance": 0.000001 } }
200
+ },
201
+ {
202
+ "name": "empty_input_zero_dim",
203
+ "attrs": { "bits": 8, "block_size": 16 },
204
+ "inputs": {
205
+ "dataT": { "dtype": "uint8", "shape": [0, 16], "data": { "kind": "values", "values": [] } },
206
+ "indicesT": { "dtype": "uint32", "shape": [0], "data": { "kind": "values", "values": [] } },
207
+ "scalesT": { "dtype": "float32", "shape": [0, 1], "data": { "kind": "values", "values": [] } }
208
+ },
209
+ "outputs": { "outputT": { "dtype": "float32", "shape": [0, 16], "tolerance": 0 } }
210
+ },
211
+ {
212
+ "name": "q8_empty_indices_populated_data",
213
+ "attrs": { "bits": 8, "block_size": 16 },
214
+ "inputs": {
215
+ "dataT": {
216
+ "dtype": "uint8",
217
+ "shape": [4, 16],
218
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/ort_q8_no_zero_input_dataT" } }
219
+ },
220
+ "indicesT": { "dtype": "uint32", "shape": [0], "data": { "kind": "values", "values": [] } },
221
+ "scalesT": {
222
+ "dtype": "float32",
223
+ "shape": [4, 1],
224
+ "data": { "kind": "values", "values": [0.01, 0.02, 0.03, 0.04] }
225
+ }
226
+ },
227
+ "outputs": { "outputT": { "dtype": "float32", "shape": [0, 16], "tolerance": 0 } }
228
+ },
229
+ {
230
+ "name": "q8_max_valid_index_and_duplicates_vec4",
231
+ "attrs": { "bits": 8, "block_size": 16 },
232
+ "inputs": {
233
+ "dataT": {
234
+ "dtype": "uint8",
235
+ "shape": [4, 8],
236
+ "data": {
237
+ "kind": "values",
238
+ "values": [0, 1, 2, 3, 4, 5, 6, 7, 10, 20, 30, 40, 50, 60, 70, 80, 255, 128, 64, 32, 16, 8, 4, 2, 90, 100, 110, 120, 130, 140, 150, 160]
239
+ }
240
+ },
241
+ "indicesT": { "dtype": "uint32", "shape": [5], "data": { "kind": "values", "values": [3, 3, 0, 3, 1] } },
242
+ "scalesT": {
243
+ "dtype": "float32",
244
+ "shape": [4, 1],
245
+ "data": { "kind": "values", "values": [0.01, 0.02, 0.03, 0.04] }
246
+ }
247
+ },
248
+ "outputs": { "outputT": { "dtype": "float32", "shape": [5, 8], "tolerance": 0.000001 } }
249
+ },
250
+ {
251
+ "name": "ort_default_bits_and_block_size_q4",
252
+ "provenance": {
253
+ "source": "onnxruntime/core/graph/contrib_ops/contrib_defs.cc",
254
+ "test": "GatherBlockQuantized schema attribute defaults",
255
+ "notes": "Omitting `bits` and `block_size` exercises their standard defaults of 4 and 128."
256
+ },
257
+ "inputs": {
258
+ "dataT": {
259
+ "dtype": "uint8",
260
+ "shape": [4, 8],
261
+ "data": { "kind": "cycle", "values": [7, 12, 3, 9, 14, 1, 5, 11] }
262
+ },
263
+ "scalesT": {
264
+ "dtype": "float32",
265
+ "shape": [4, 1],
266
+ "data": { "kind": "values", "values": [0.5, 0.25, 1.5, 2.0] }
267
+ },
268
+ "indicesT": { "dtype": "uint32", "shape": [2], "data": { "kind": "values", "values": [3, 0] } }
269
+ },
270
+ "outputs": { "outputT": { "dtype": "float32", "shape": [2, 16], "tolerance": 0.0001 } }
271
+ },
272
+ {
273
+ "name": "q8_zero_points_tail_cols18",
274
+ "provenance": {
275
+ "notes": "Explicit q8 zero points with 18 output columns exercise the scalar tail after four complete vec4 groups; the final quantization block is partial and uses its own scale and zero point."
276
+ },
277
+ "attrs": { "bits": 8, "block_size": 16 },
278
+ "inputs": {
279
+ "dataT": {
280
+ "dtype": "uint8",
281
+ "shape": [3, 18],
282
+ "data": { "kind": "cycle", "values": [0, 17, 64, 99, 128, 143, 191, 255, 37, 211] }
283
+ },
284
+ "indicesT": { "dtype": "uint32", "shape": [3], "data": { "kind": "values", "values": [2, 0, 2] } },
285
+ "scalesT": {
286
+ "dtype": "float32",
287
+ "shape": [3, 2],
288
+ "data": { "kind": "values", "values": [0.05, 0.2, 0.025, 0.125, 0.075, 0.3] }
289
+ },
290
+ "zeroPointsT": {
291
+ "dtype": "uint8",
292
+ "shape": [3, 2],
293
+ "data": { "kind": "values", "values": [120, 130, 100, 140, 110, 150] }
294
+ }
295
+ },
296
+ "outputs": { "outputT": { "dtype": "float32", "shape": [3, 18], "tolerance": 0.000001 } }
297
+ }
298
+ ]
299
+ }