Xenova HF Staff commited on
Commit
fbb37c8
·
verified ·
1 Parent(s): f08c2b8

sync 2e7068faf55e

Browse files
README.md CHANGED
@@ -1,3 +1,98 @@
1
  ---
 
2
  license: apache-2.0
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ library_name: kernels
3
  license: apache-2.0
4
+ tags:
5
+ - kernel
6
+ - webgpu
7
+ - wgsl
8
  ---
9
+ # ai.onnx.QLinearConv
10
+
11
+ `ai.onnx` · standard ONNX operator · ONNX opset ≥ 10
12
+
13
+ ## Description
14
+
15
+ Performs convolution on a quantized input using quantized weights, producing a quantized output. Inputs, weights, and output each carry their own scale and zero-point tensors (per-tensor or per-output-channel); an optional int32 bias must be pre-quantized with `scale = x_scale * w_scale` and zero point 0.
16
+
17
+ See the [ONNX `QLinearConv` spec](https://onnx.ai/onnx/operators/onnx__QLinearConv.html) for the reference semantics.
18
+
19
+ ## Inputs
20
+
21
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
22
+ | --- | --- | --- | --- | --- | --- | --- |
23
+ | `x` | `x` | `TX` | — | — | Quantized input data tensor shaped `(N x C x D1 x ... x Dn)`. | required |
24
+ | `x_scale` | `x_scale` | `TF` | — | — | Per-tensor scale for input `x`. | required |
25
+ | `x_zero_point` | `x_zero_point` | `TX` | — | — | Per-tensor zero point for input `x`. | required |
26
+ | `w` | `w` | `TW` | — | — | Quantized weight tensor shaped `(M x C/group x k1 x ... x kn)`. | required |
27
+ | `w_scale` | `w_scale` | `TF` | — | — | Scale for weights `w`; scalar for per-tensor or 1-D of length M for per-output-channel quantization. | required |
28
+ | `w_zero_point` | `w_zero_point` | `TW` | — | — | Zero point for weights `w`; scalar or 1-D of length M matching `w_scale`. | required |
29
+ | `y_scale` | `y_scale` | `TF` | — | — | Per-tensor scale for output `y`. | required |
30
+ | `y_zero_point` | `y_zero_point` | `TY` | — | — | Per-tensor zero point for output `y`. | required |
31
+ | `B` | `B` | `int32` | `1` | — | Optional 1-D bias of length `M`, pre-quantized with scale `x_scale * w_scale` and zero point 0. | optional |
32
+
33
+ ## Outputs
34
+
35
+ | Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
36
+ | --- | --- | --- | --- | --- | --- | --- |
37
+ | `y` | `y` | `TY` | same as `x` | derived; see description | Quantized output tensor; shape is determined by kernel size, strides, and padding. | required |
38
+
39
+ ## Attributes
40
+
41
+ Attributes and default values (overridable per request):
42
+
43
+ | Attribute | Default | Description |
44
+ | --- | --- | --- |
45
+ | `auto_pad` | `"NOTSET"` | Automatic padding mode. `NOTSET` uses `pads`; `SAME_UPPER` and `SAME_LOWER` choose padding so each output spatial size is `ceil(input / stride)`; `VALID` uses no padding. |
46
+ | `group` | `1` | Number of groups that input and output channels are split into; defaults to 1. |
47
+ | `dilations` | — | Optional dilation factors, one positive integer per spatial axis. Omission means all ones. |
48
+ | `kernel_shape` | — | Optional kernel shape, one positive integer per spatial axis. When present, it must match the spatial dimensions of the weight tensor; omission infers the shape from the weights. |
49
+ | `pads` | — | Optional explicit padding in ONNX order `[begin_axis_0, ..., begin_axis_n, end_axis_0, ..., end_axis_n]`. Omission means all zeros; it cannot be combined with an automatic padding mode. |
50
+ | `strides` | — | Optional stride factors, one positive integer per spatial axis. Omission means all ones. |
51
+
52
+ ## Type constraints
53
+
54
+ | Variable | Allowed dtypes |
55
+ | --- | --- |
56
+ | `TX` | `uint8`, `int8` |
57
+ | `TW` | `uint8`, `int8` |
58
+ | `TY` | `uint8`, `int8` |
59
+ | `TF` | `float32` |
60
+
61
+ ## Files
62
+
63
+ - [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
64
+ - [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
65
+ - [`test.json`](build/webgpu/test.json) — correctness cases
66
+ - [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
67
+ - [`conv-int-accumulate-spatial.wgsl.jinja`](build/webgpu/conv-int-accumulate-spatial.wgsl.jinja)
68
+ - [`conv-int-im2col-spatial.wgsl.jinja`](build/webgpu/conv-int-im2col-spatial.wgsl.jinja)
69
+ - [`qlinear-conv-dp4a-direct-3x3.wgsl.jinja`](build/webgpu/qlinear-conv-dp4a-direct-3x3.wgsl.jinja)
70
+ - [`qlinear-conv-nchw-x4.wgsl.jinja`](build/webgpu/qlinear-conv-nchw-x4.wgsl.jinja)
71
+ - [`qlinear-conv-requantize.wgsl.jinja`](build/webgpu/qlinear-conv-requantize.wgsl.jinja)
72
+ - [`quant-dp4a-matmul.wgsl.jinja`](build/webgpu/quant-dp4a-matmul.wgsl.jinja)
73
+ - [`quant-pad-k-matrices.wgsl.jinja`](build/webgpu/quant-pad-k-matrices.wgsl.jinja)
74
+
75
+ ## Use with `@huggingface/kernels`
76
+
77
+ The loader derives every required output's shape and logical dtype from the manifest contract and this call.
78
+ It then allocates the result tensors automatically.
79
+
80
+ The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
81
+
82
+ Replace each `*Data` placeholder with a typed array containing the corresponding input data.
83
+
84
+ ```js
85
+ import { getKernel } from "@huggingface/kernels";
86
+
87
+ const kernel = await getKernel("webgpu-kernels/ai.onnx.QLinearConv", { version: 1 });
88
+ const { y } = await kernel({
89
+ x: { data: xData, shape: [1, 1, 1] },
90
+ x_scale: { data: x_scaleData, shape: [1] },
91
+ x_zero_point: { data: x_zero_pointData, shape: [1] },
92
+ w: { data: wData, shape: [1, 1, 1] },
93
+ w_scale: { data: w_scaleData, shape: [1] },
94
+ w_zero_point: { data: w_zero_pointData, shape: [1] },
95
+ y_scale: { data: y_scaleData, shape: [1] },
96
+ y_zero_point: { data: y_zero_pointData, shape: [1] },
97
+ });
98
+ ```
build/webgpu/bench.json ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.QLinearConv",
3
+ "tunableSpace": {
4
+ "DP4A_DIRECT_SPATIAL_TILE": [8, 16],
5
+ "DP4A_DIRECT_OC_TILE": [8, 16],
6
+ "DP4A_DIRECT_IC_TILE": [16, 32, 64]
7
+ },
8
+ "cases": [
9
+ {
10
+ "name": "u8_nchw_1x16x32x32",
11
+ "inputs": {
12
+ "x": { "dtype": "uint8", "shape": [1, 16, 32, 32], "data": { "kind": "constant", "value": 127 } },
13
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
14
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
15
+ "w": { "dtype": "uint8", "shape": [16, 16, 3, 3], "data": { "kind": "constant", "value": 129 } },
16
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.03] } },
17
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
18
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.04] } },
19
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
20
+ },
21
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 16, 30, 30] } },
22
+ "attrs": {}
23
+ },
24
+ {
25
+ "name": "u8s8_pointwise_1x64x56x56_oc128",
26
+ "inputs": {
27
+ "x": { "dtype": "uint8", "shape": [1, 64, 56, 56], "data": { "kind": "constant", "value": 127 } },
28
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
29
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
30
+ "w": { "dtype": "int8", "shape": [128, 64, 1, 1], "data": { "kind": "constant", "value": -3 } },
31
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.03] } },
32
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
33
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [2.0] } },
34
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
35
+ },
36
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 128, 56, 56] } },
37
+ "attrs": {}
38
+ },
39
+ {
40
+ "name": "dp4a_pointwise_cin64_aligned_1x56x56_oc128_u8s8",
41
+ "preset": "smoke",
42
+ "vars": { "batch": 1, "inChannels": 64, "outChannels": 128, "inH": 56, "inW": 56, "kernelH": 1, "kernelW": 1 },
43
+ "inputs": {
44
+ "x": { "dtype": "uint8", "shape": [1, 64, 56, 56], "dist": "randint", "seed": 7301, "min": 0, "max": 255 },
45
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
46
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
47
+ "w": { "dtype": "int8", "shape": [128, 64, 1, 1], "dist": "randint", "seed": 7302, "min": -127, "max": 127 },
48
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.015] } },
49
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
50
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [2.0] } },
51
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
52
+ },
53
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 128, 56, 56] } },
54
+ "bench": {
55
+ "primary": true,
56
+ "metrics": [
57
+ {
58
+ "type": "gflops",
59
+ "value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * args.inChannels"
60
+ }
61
+ ]
62
+ },
63
+ "attrs": {}
64
+ },
65
+ {
66
+ "name": "dp4a_pointwise_cin66_unaligned_k4_cliff_1x56x56_oc128_u8s8",
67
+ "preset": "smoke",
68
+ "vars": { "batch": 1, "inChannels": 66, "outChannels": 128, "inH": 56, "inW": 56, "kernelH": 1, "kernelW": 1 },
69
+ "inputs": {
70
+ "x": { "dtype": "uint8", "shape": [1, 66, 56, 56], "dist": "randint", "seed": 7303, "min": 0, "max": 255 },
71
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
72
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
73
+ "w": { "dtype": "int8", "shape": [128, 66, 1, 1], "dist": "randint", "seed": 7304, "min": -127, "max": 127 },
74
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.015] } },
75
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
76
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [2.0] } },
77
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
78
+ },
79
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 128, 56, 56] } },
80
+ "bench": {
81
+ "metrics": [
82
+ {
83
+ "type": "gflops",
84
+ "value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * args.inChannels"
85
+ }
86
+ ]
87
+ },
88
+ "attrs": {}
89
+ },
90
+ {
91
+ "name": "conv3x3_naive_resnet_1x128x56x56_oc128_u8s8",
92
+ "preset": "smoke",
93
+ "vars": { "batch": 1, "inChannels": 128, "outChannels": 128, "inH": 56, "inW": 56, "kernelH": 3, "kernelW": 3 },
94
+ "attrs": { "strides": [1, 1], "pads": [1, 1, 1, 1] },
95
+ "inputs": {
96
+ "x": { "dtype": "uint8", "shape": [1, 128, 56, 56], "dist": "randint", "seed": 7305, "min": 0, "max": 255 },
97
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
98
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
99
+ "w": { "dtype": "int8", "shape": [128, 128, 3, 3], "dist": "randint", "seed": 7306, "min": -127, "max": 127 },
100
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.015] } },
101
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
102
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [4.0] } },
103
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
104
+ },
105
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 128, 56, 56] } },
106
+ "bench": {
107
+ "metrics": [
108
+ {
109
+ "type": "gflops",
110
+ "value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * args.inChannels * args.kernelH * args.kernelW"
111
+ }
112
+ ]
113
+ }
114
+ },
115
+ {
116
+ "name": "depthwise3x3_naive_mobilenet_1x256x28x28_g256_u8s8",
117
+ "preset": "smoke",
118
+ "vars": { "batch": 1, "inChannels": 256, "outChannels": 256, "inH": 28, "inW": 28, "kernelH": 3, "kernelW": 3 },
119
+ "attrs": { "group": 256, "strides": [1, 1], "pads": [1, 1, 1, 1] },
120
+ "inputs": {
121
+ "x": { "dtype": "uint8", "shape": [1, 256, 28, 28], "dist": "randint", "seed": 7307, "min": 0, "max": 255 },
122
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
123
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
124
+ "w": { "dtype": "int8", "shape": [256, 1, 3, 3], "dist": "randint", "seed": 7308, "min": -127, "max": 127 },
125
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.015] } },
126
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
127
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.5] } },
128
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
129
+ },
130
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 256, 28, 28] } },
131
+ "bench": {
132
+ "metrics": [
133
+ {
134
+ "type": "gflops",
135
+ "value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * args.kernelH * args.kernelW"
136
+ }
137
+ ]
138
+ }
139
+ },
140
+ {
141
+ "name": "bias3x3_forces_naive_accumulate_1x128x64x64_oc128_u8s8",
142
+ "preset": "stress",
143
+ "vars": { "batch": 1, "inChannels": 128, "outChannels": 128, "inH": 64, "inW": 64, "kernelH": 3, "kernelW": 3 },
144
+ "attrs": { "strides": [1, 1], "pads": [1, 1, 1, 1] },
145
+ "inputs": {
146
+ "x": { "dtype": "uint8", "shape": [1, 128, 64, 64], "dist": "randint", "seed": 7401, "min": 0, "max": 255 },
147
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
148
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
149
+ "w": { "dtype": "int8", "shape": [128, 128, 3, 3], "dist": "randint", "seed": 7402, "min": -127, "max": 127 },
150
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.015] } },
151
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
152
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [4.0] } },
153
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
154
+ "B": { "dtype": "int32", "shape": [128], "dist": "randint", "seed": 7403, "min": -4096, "max": 4096 }
155
+ },
156
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 128, 64, 64] } },
157
+ "bench": {
158
+ "metrics": [
159
+ {
160
+ "type": "gflops",
161
+ "value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * args.inChannels * args.kernelH * args.kernelW"
162
+ }
163
+ ]
164
+ }
165
+ },
166
+ {
167
+ "name": "unaligned_k_padded_im2col_1x63x48x48_oc128_3x3_u8s8",
168
+ "preset": "stress",
169
+ "vars": { "batch": 1, "inChannels": 63, "outChannels": 128, "inH": 48, "inW": 48, "kernelH": 3, "kernelW": 3 },
170
+ "attrs": { "strides": [1, 1], "pads": [0, 0, 0, 0] },
171
+ "inputs": {
172
+ "x": { "dtype": "uint8", "shape": [1, 63, 48, 48], "dist": "randint", "seed": 7404, "min": 0, "max": 255 },
173
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
174
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
175
+ "w": { "dtype": "int8", "shape": [128, 63, 3, 3], "dist": "randint", "seed": 7405, "min": -127, "max": 127 },
176
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.015] } },
177
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
178
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [4.0] } },
179
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
180
+ },
181
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 128, 46, 46] } },
182
+ "bench": {
183
+ "metrics": [
184
+ {
185
+ "type": "gflops",
186
+ "value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * args.inChannels * args.kernelH * args.kernelW"
187
+ }
188
+ ]
189
+ }
190
+ },
191
+ {
192
+ "name": "bias_unaligned_k_baseline_fallback_1x63x48x48_oc128_3x3_u8s8",
193
+ "preset": "stress",
194
+ "vars": { "batch": 1, "inChannels": 63, "outChannels": 128, "inH": 48, "inW": 48, "kernelH": 3, "kernelW": 3 },
195
+ "attrs": { "strides": [1, 1], "pads": [0, 0, 0, 0] },
196
+ "inputs": {
197
+ "x": { "dtype": "uint8", "shape": [1, 63, 48, 48], "dist": "randint", "seed": 7410, "min": 0, "max": 255 },
198
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
199
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
200
+ "w": { "dtype": "int8", "shape": [128, 63, 3, 3], "dist": "randint", "seed": 7411, "min": -127, "max": 127 },
201
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.015] } },
202
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
203
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [4.0] } },
204
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
205
+ "B": { "dtype": "int32", "shape": [128], "dist": "randint", "seed": 7412, "min": -4096, "max": 4096 }
206
+ },
207
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 128, 46, 46] } },
208
+ "bench": {
209
+ "metrics": [
210
+ {
211
+ "type": "gflops",
212
+ "value": "2 * args.batch * args.outChannels * dim(shapes.y, 2) * dim(shapes.y, 3) * args.inChannels * args.kernelH * args.kernelW"
213
+ }
214
+ ]
215
+ }
216
+ },
217
+ {
218
+ "name": "u8s8_conv3d_dilated_depth_b1c16m32_16x32x32_k3",
219
+ "preset": "stress",
220
+ "attrs": { "strides": [1, 1, 1], "dilations": [2, 1, 1], "pads": [2, 1, 1, 2, 1, 1] },
221
+ "inputs": {
222
+ "x": { "dtype": "uint8", "shape": [1, 16, 16, 32, 32], "dist": "randint", "seed": 7701, "min": 0, "max": 255 },
223
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
224
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
225
+ "w": { "dtype": "int8", "shape": [32, 16, 3, 3, 3], "dist": "randint", "seed": 7702, "min": -127, "max": 127 },
226
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.015] } },
227
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
228
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [4.0] } },
229
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
230
+ },
231
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 32, 16, 32, 32], "dist": "empty" } },
232
+ "bench": {
233
+ "metrics": [
234
+ {
235
+ "type": "gflops",
236
+ "value": "2 * numel(shapes.y) * dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) * dim(shapes.w, 4)"
237
+ }
238
+ ]
239
+ }
240
+ },
241
+ {
242
+ "name": "resnet_perchannel_3x3_b1c64_56x56_oc128_u8s8",
243
+ "preset": "stress",
244
+ "attrs": { "strides": [1, 1], "pads": [1, 1, 1, 1] },
245
+ "inputs": {
246
+ "x": { "dtype": "uint8", "shape": [1, 64, 56, 56], "dist": "randint", "seed": 7801, "min": 0, "max": 255 },
247
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
248
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
249
+ "w": { "dtype": "int8", "shape": [128, 64, 3, 3], "dist": "randint", "seed": 7802, "min": -127, "max": 127 },
250
+ "w_scale": {
251
+ "dtype": "float32",
252
+ "shape": [128],
253
+ "data": { "kind": "cycle", "values": [0.0125, 0.015, 0.0175, 0.02] }
254
+ },
255
+ "w_zero_point": { "dtype": "int8", "shape": [128], "data": { "kind": "cycle", "values": [-3, 0, 2, 5] } },
256
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [4.0] } },
257
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
258
+ },
259
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 128, 56, 56] } },
260
+ "bench": {
261
+ "metrics": [
262
+ { "type": "gflops", "value": "2 * numel(shapes.y) * dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)" }
263
+ ]
264
+ }
265
+ },
266
+ {
267
+ "name": "group2_feature_independent_oc4_cliff_b1c64_56x56_oc128_u8s8",
268
+ "preset": "stress",
269
+ "vars": { "batch": 1, "inChannels": 64, "outChannels": 128, "inH": 56, "inW": 56, "kernelH": 3, "kernelW": 3 },
270
+ "attrs": { "group": 2, "strides": [1, 1], "pads": [1, 1, 1, 1] },
271
+ "inputs": {
272
+ "x": { "dtype": "uint8", "shape": [1, 64, 56, 56], "dist": "randint", "seed": 7811, "min": 0, "max": 255 },
273
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
274
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
275
+ "w": { "dtype": "int8", "shape": [128, 32, 3, 3], "dist": "randint", "seed": 7812, "min": -127, "max": 127 },
276
+ "w_scale": {
277
+ "dtype": "float32",
278
+ "shape": [128],
279
+ "data": { "kind": "cycle", "values": [0.0125, 0.015, 0.0175, 0.02] }
280
+ },
281
+ "w_zero_point": { "dtype": "int8", "shape": [128], "data": { "kind": "cycle", "values": [-3, 0, 2, 5] } },
282
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [4.0] } },
283
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
284
+ },
285
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 128, 56, 56] } },
286
+ "bench": {
287
+ "metrics": [
288
+ { "type": "gflops", "value": "2 * numel(shapes.y) * dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)" }
289
+ ]
290
+ }
291
+ }
292
+ ]
293
+ }
build/webgpu/conv-int-accumulate-spatial.wgsl.jinja ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ {% set depthIndent = " " if source.spatialRank == 3 else "" %}
35
+ {{ env.wgsl.resourceDeclarations }}
36
+
37
+ fn read_x(index: u32) -> i32 {
38
+ {% if xUnsigned %}
39
+ return i32(x[index]);
40
+ {% else %}
41
+ return x[index];
42
+ {% endif %}
43
+ }
44
+
45
+ fn read_w(index: u32) -> i32 {
46
+ {% if wUnsigned %}
47
+ return i32(w[index]);
48
+ {% else %}
49
+ return w[index];
50
+ {% endif %}
51
+ }
52
+
53
+ fn read_x_zero() -> i32 {
54
+ {% if xUnsigned %}
55
+ return i32(x_zero_point[0]);
56
+ {% else %}
57
+ return x_zero_point[0];
58
+ {% endif %}
59
+ }
60
+
61
+ // A per-output-channel weight zero point applies at every spatial rank.
62
+ fn read_w_zero({% if wZeroPerChannel %}oc: u32{% endif %}) -> i32 {
63
+ {% if wZeroPerChannel %}
64
+ {% if wUnsigned %}
65
+ return i32(w_zero_point[oc]);
66
+ {% else %}
67
+ return w_zero_point[oc];
68
+ {% endif %}
69
+ {% else %}
70
+ {% if wUnsigned %}
71
+ return i32(w_zero_point[0]);
72
+ {% else %}
73
+ return w_zero_point[0];
74
+ {% endif %}
75
+ {% endif %}
76
+ }
77
+
78
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
79
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
80
+ {{ flat_index_2d("index", guardInline=true) }}
81
+ let ow = index % params.outW;
82
+ var t = index / params.outW;
83
+ let oh = t % params.outH;
84
+ t = t / params.outH;
85
+ {% if source.spatialRank == 3 %}
86
+ let od = t % params.outD;
87
+ t = t / params.outD;
88
+ {% endif %}
89
+ let oc = t % params.outChannels;
90
+ let batch = t / params.outChannels;
91
+ let group = oc / params.outChannelsPerGroup;
92
+ let xzp = read_x_zero();
93
+ let wzp = read_w_zero({% if wZeroPerChannel %}oc{% endif %});
94
+ var acc = 0i;
95
+ for (var ic = 0u; ic < params.weightInChannels; ic = ic + 1u) {
96
+ let input_channel = group * params.inChannelsPerGroup + ic;
97
+ {% if source.spatialRank == 3 %}
98
+ for (var kd = 0u; kd < params.kernelD; kd = kd + 1u) {
99
+ let id = i32(od * params.strideD + kd * params.dilationD) - params.padD;
100
+ if (id < 0 || id >= i32(params.inD)) { continue; }
101
+ {% endif %}
102
+ {{ depthIndent }} for (var kh = 0u; kh < params.kernelH; kh = kh + 1u) {
103
+ {{ depthIndent }} let ih = i32(oh * params.strideH + kh * params.dilationH) - params.padH;
104
+ {{ depthIndent }} if (ih < 0 || ih >= i32(params.inH)) { continue; }
105
+ {{ depthIndent }} for (var kw = 0u; kw < params.kernelW; kw = kw + 1u) {
106
+ {{ depthIndent }} let iw = i32(ow * params.strideW + kw * params.dilationW) - params.padW;
107
+ {{ depthIndent }} if (iw < 0 || iw >= i32(params.inW)) { continue; }
108
+ {% if source.spatialRank == 3 %}
109
+ {{ depthIndent }} let x_index = (((batch * params.inChannels + input_channel) * params.inD + u32(id)) * params.inH + u32(ih)) * params.inW + u32(iw);
110
+ {{ depthIndent }} let w_index = (((oc * params.weightInChannels + ic) * params.kernelD + kd) * params.kernelH + kh) * params.kernelW + kw;
111
+ {% else %}
112
+ {{ depthIndent }} let x_index = ((batch * params.inChannels + input_channel) * params.inH + u32(ih)) * params.inW + u32(iw);
113
+ {{ depthIndent }} let w_index = ((oc * params.weightInChannels + ic) * params.kernelH + kh) * params.kernelW + kw;
114
+ {% endif %}
115
+ {{ depthIndent }} acc = acc + (read_x(x_index) - xzp) * (read_w(w_index) - wzp);
116
+ {{ depthIndent }} }
117
+ {{ depthIndent }} }
118
+ {% if source.spatialRank == 3 %}
119
+ }
120
+ {% endif %}
121
+ }
122
+ y[index] = acc;
123
+ }
build/webgpu/conv-int-im2col-spatial.wgsl.jinja ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Widened-int8 im2col for convolution DP4A paths. The materialized K order is
2
+ * OIHW for 2D input and OIDHW for 3D input. Spatial padding is filled with the
3
+ * input zero point, so padded taps contribute exactly zero after centering.
4
+ * Adjacent x invocations cover adjacent output positions for coalesced writes.
5
+ * Optional extra K rows contain the raw input zero point to align the DP4A
6
+ * reduction. */
7
+ {{ env.wgsl.resourceDeclarations }}
8
+ {% if source.spatialRank == 3 %}
9
+
10
+ const KERNEL_D: u32 = {{ source.kernelD }}u;
11
+ {% endif %}
12
+ const KERNEL_H: u32 = {{ source.kernelH }}u;
13
+ const KERNEL_W: u32 = {{ source.kernelW }}u;
14
+ const KERNEL_HW: u32 = KERNEL_H * KERNEL_W;
15
+ {% if source.spatialRank == 3 %}
16
+ const KSIZE: u32 = KERNEL_D * KERNEL_HW;
17
+ const STRIDE_D: u32 = {{ source.strideD }}u;
18
+ const DILATION_D: u32 = {{ source.dilationD }}u;
19
+ const PAD_FRONT: i32 = {{ source.padFront }};
20
+ {% else %}
21
+ const KSIZE: u32 = KERNEL_HW;
22
+ {% endif %}
23
+ const STRIDE_H: u32 = {{ source.strideH }}u;
24
+ const STRIDE_W: u32 = {{ source.strideW }}u;
25
+ const DILATION_H: u32 = {{ source.dilationH }}u;
26
+ const DILATION_W: u32 = {{ source.dilationW }}u;
27
+ const PAD_TOP: i32 = {{ source.padTop }};
28
+ const PAD_LEFT: i32 = {{ source.padLeft }};
29
+
30
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
31
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
32
+ let pos = gid.x;
33
+ if (pos >= params.outCount) {
34
+ return;
35
+ }
36
+ let k = gid.y;
37
+ let batch = gid.z;
38
+ {% if source.padK %}
39
+ if (k >= params.sourceKRows) {
40
+ cols[(batch * params.kRows + k) * params.outCount + pos] = x_zero_point[0];
41
+ return;
42
+ }
43
+ {% endif %}
44
+ let ic = k / KSIZE;
45
+ let kq = k - ic * KSIZE;
46
+ {% if source.spatialRank == 3 %}
47
+ let kd = kq / KERNEL_HW;
48
+ let khw = kq - kd * KERNEL_HW;
49
+ let kh = khw / KERNEL_W;
50
+ let kw = khw - kh * KERNEL_W;
51
+ let outHW = params.outH * params.outW;
52
+ let od = pos / outHW;
53
+ let ohw = pos - od * outHW;
54
+ let oh = ohw / params.outW;
55
+ let ow = ohw - oh * params.outW;
56
+ let id = i32(od * STRIDE_D + kd * DILATION_D) - PAD_FRONT;
57
+ {% else %}
58
+ let kh = kq / KERNEL_W;
59
+ let kw = kq - kh * KERNEL_W;
60
+ let oh = pos / params.outW;
61
+ let ow = pos - oh * params.outW;
62
+ {% endif %}
63
+ let ih = i32(oh * STRIDE_H + kh * DILATION_H) - PAD_TOP;
64
+ let iw = i32(ow * STRIDE_W + kw * DILATION_W) - PAD_LEFT;
65
+ var value: {{ bScalar }} = x_zero_point[0];
66
+ {% if source.spatialRank == 3 %}
67
+ if (
68
+ id >= 0 && id < i32(params.inD)
69
+ && ih >= 0 && ih < i32(params.inH)
70
+ && iw >= 0 && iw < i32(params.inW)
71
+ ) {
72
+ value = x[
73
+ (((batch * params.inChannels + ic) * params.inD + u32(id)) * params.inH + u32(ih))
74
+ * params.inW + u32(iw)
75
+ ];
76
+ }
77
+ {% else %}
78
+ if (ih >= 0 && ih < i32(params.inH) && iw >= 0 && iw < i32(params.inW)) {
79
+ value = x[((batch * params.inChannels + ic) * params.inH + u32(ih)) * params.inW + u32(iw)];
80
+ }
81
+ {% endif %}
82
+ cols[(batch * params.kRows + k) * params.outCount + pos] = value;
83
+ }
build/webgpu/manifest.json ADDED
@@ -0,0 +1,1845 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "domain": "ai.onnx",
3
+ "name": "QLinearConv",
4
+ "sinceVersion": 10,
5
+ "description": "Performs convolution on a quantized input using quantized weights, producing a quantized output. Inputs, weights, and output each carry their own scale and zero-point tensors (per-tensor or per-output-channel); an optional int32 bias must be pre-quantized with `scale = x_scale * w_scale` and zero point 0.",
6
+ "inputs": [
7
+ { "role": "x", "dtype": "TX", "description": "Quantized input data tensor shaped `(N x C x D1 x ... x Dn)`." },
8
+ { "role": "x_scale", "dtype": "TF", "description": "Per-tensor scale for input `x`." },
9
+ { "role": "x_zero_point", "dtype": "TX", "description": "Per-tensor zero point for input `x`." },
10
+ { "role": "w", "dtype": "TW", "description": "Quantized weight tensor shaped `(M x C/group x k1 x ... x kn)`." },
11
+ {
12
+ "role": "w_scale",
13
+ "dtype": "TF",
14
+ "description": "Scale for weights `w`; scalar for per-tensor or 1-D of length M for per-output-channel quantization."
15
+ },
16
+ {
17
+ "role": "w_zero_point",
18
+ "dtype": "TW",
19
+ "description": "Zero point for weights `w`; scalar or 1-D of length M matching `w_scale`."
20
+ },
21
+ { "role": "y_scale", "dtype": "TF", "description": "Per-tensor scale for output `y`." },
22
+ { "role": "y_zero_point", "dtype": "TY", "description": "Per-tensor zero point for output `y`." },
23
+ {
24
+ "role": "B",
25
+ "dtype": "int32",
26
+ "rank": 1,
27
+ "optional": true,
28
+ "description": "Optional 1-D bias of length `M`, pre-quantized with scale `x_scale * w_scale` and zero point 0."
29
+ }
30
+ ],
31
+ "outputs": [
32
+ {
33
+ "role": "y",
34
+ "dtype": "TY",
35
+ "description": "Quantized output tensor; shape is determined by kernel size, strides, and padding.",
36
+ "rank": "ranks.x",
37
+ "shape": "[dim(shapes.x, 0), dim(shapes.w, 0), expectedOutputWidth] if ranks.x == 3 else ([dim(shapes.x, 0), dim(shapes.w, 0), expectedOutputHeight, expectedOutputWidth] if ranks.x == 4 else [dim(shapes.x, 0), dim(shapes.w, 0), expectedOutputDepth, expectedOutputHeight, expectedOutputWidth])"
38
+ }
39
+ ],
40
+ "attributes": { "auto_pad": "NOTSET", "group": 1 },
41
+ "attributeDescriptions": {
42
+ "auto_pad": "Automatic padding mode. `NOTSET` uses `pads`; `SAME_UPPER` and `SAME_LOWER` choose padding so each output spatial size is `ceil(input / stride)`; `VALID` uses no padding.",
43
+ "dilations": "Optional dilation factors, one positive integer per spatial axis. Omission means all ones.",
44
+ "group": "Number of groups that input and output channels are split into; defaults to 1.",
45
+ "kernel_shape": "Optional kernel shape, one positive integer per spatial axis. When present, it must match the spatial dimensions of the weight tensor; omission infers the shape from the weights.",
46
+ "pads": "Optional explicit padding in ONNX order `[begin_axis_0, ..., begin_axis_n, end_axis_0, ..., end_axis_n]`. Omission means all zeros; it cannot be combined with an automatic padding mode.",
47
+ "strides": "Optional stride factors, one positive integer per spatial axis. Omission means all ones."
48
+ },
49
+ "attributeConstraints": { "auto_pad": { "values": ["NOTSET", "SAME_UPPER", "SAME_LOWER", "VALID"] } },
50
+ "typeConstraints": { "TX": ["uint8", "int8"], "TW": ["uint8", "int8"], "TY": ["uint8", "int8"], "TF": ["float32"] },
51
+ "args": {
52
+ "x": { "kind": "tensor", "semantic": "x", "role": "input" },
53
+ "x_scale": { "kind": "tensor", "semantic": "x_scale", "role": "input" },
54
+ "x_zero_point": { "kind": "tensor", "semantic": "x_zero_point", "role": "input" },
55
+ "w": { "kind": "tensor", "semantic": "w", "role": "input" },
56
+ "w_scale": { "kind": "tensor", "semantic": "w_scale", "role": "input" },
57
+ "w_zero_point": { "kind": "tensor", "semantic": "w_zero_point", "role": "input" },
58
+ "y_scale": { "kind": "tensor", "semantic": "y_scale", "role": "input" },
59
+ "y_zero_point": { "kind": "tensor", "semantic": "y_zero_point", "role": "input" },
60
+ "B": { "kind": "tensor", "semantic": "B", "role": "input", "required": false },
61
+ "y": { "kind": "tensor", "semantic": "y", "role": "output" }
62
+ },
63
+ "tunables": {
64
+ "WORKGROUP_SIZE": 256,
65
+ "DP4A_DIRECT_SPATIAL_TILE": 8,
66
+ "DP4A_DIRECT_OC_TILE": 8,
67
+ "DP4A_DIRECT_IC_TILE": 16
68
+ },
69
+ "derive": {
70
+ "inputDepth": "dim(shapes.x, 2) if ranks.x == 5 else 1",
71
+ "inputHeight": "dim(shapes.x, 3) if ranks.x == 5 else (dim(shapes.x, 2) if ranks.x == 4 else 1)",
72
+ "inputWidth": "dim(shapes.x, ranks.x - 1) if ranks.x >= 3 else 1",
73
+ "outputDepth": "dim(shapes.y, 2) if ranks.y == 5 else 1",
74
+ "outputHeight": "dim(shapes.y, 3) if ranks.y == 5 else (dim(shapes.y, 2) if ranks.y == 4 else 1)",
75
+ "outputWidth": "dim(shapes.y, ranks.y - 1) if ranks.y >= 3 else 1",
76
+ "kernelDepth": "dim(shapes.w, 2) if ranks.w == 5 else 1",
77
+ "kernelHeight": "dim(shapes.w, 3) if ranks.w == 5 else (dim(shapes.w, 2) if ranks.w == 4 else 1)",
78
+ "kernelWidth": "dim(shapes.w, ranks.w - 1) if ranks.w >= 3 else 1",
79
+ "spatialRank": "ranks.w - 2",
80
+ "kernelShapeLengthOk": "not has(attrs, \"kernel_shape\") or (attrs.kernel_shape | length) == spatialRank",
81
+ "stridesLengthOk": "not has(attrs, \"strides\") or (attrs.strides | length) == spatialRank",
82
+ "dilationsLengthOk": "not has(attrs, \"dilations\") or (attrs.dilations | length) == spatialRank",
83
+ "padsLengthOk": "not has(attrs, \"pads\") or (attrs.pads | length) == 2 * spatialRank",
84
+ "kernelD": "attrs.kernel_shape[0] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank == 3 else 1",
85
+ "kernelH": "attrs.kernel_shape[spatialRank - 2] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank >= 2 else 1",
86
+ "kernelW": "attrs.kernel_shape[spatialRank - 1] if kernelShapeLengthOk and has(attrs, \"kernel_shape\") and spatialRank >= 1 else 1",
87
+ "strideD": "attrs.strides[0] if stridesLengthOk and has(attrs, \"strides\") and spatialRank == 3 else 1",
88
+ "strideH": "attrs.strides[spatialRank - 2] if stridesLengthOk and has(attrs, \"strides\") and spatialRank >= 2 else 1",
89
+ "strideW": "attrs.strides[spatialRank - 1] if stridesLengthOk and has(attrs, \"strides\") and spatialRank >= 1 else 1",
90
+ "dilationD": "attrs.dilations[0] if dilationsLengthOk and has(attrs, \"dilations\") and spatialRank == 3 else 1",
91
+ "dilationH": "attrs.dilations[spatialRank - 2] if dilationsLengthOk and has(attrs, \"dilations\") and spatialRank >= 2 else 1",
92
+ "dilationW": "attrs.dilations[spatialRank - 1] if dilationsLengthOk and has(attrs, \"dilations\") and spatialRank >= 1 else 1",
93
+ "padFront": "attrs.pads[0] if padsLengthOk and has(attrs, \"pads\") and spatialRank == 3 else 0",
94
+ "padTop": "attrs.pads[spatialRank - 2] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 2 else 0",
95
+ "padLeft": "attrs.pads[spatialRank - 1] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 1 else 0",
96
+ "padBack": "attrs.pads[spatialRank] if padsLengthOk and has(attrs, \"pads\") and spatialRank == 3 else 0",
97
+ "padBottom": "attrs.pads[2 * spatialRank - 2] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 2 else 0",
98
+ "padRight": "attrs.pads[2 * spatialRank - 1] if padsLengthOk and has(attrs, \"pads\") and spatialRank >= 1 else 0",
99
+ "narrowSubgroupRange": "has(device.adapterInfo, \"subgroupMinSize\") and has(device.adapterInfo, \"subgroupMaxSize\") and device.adapterInfo.subgroupMinSize < device.adapterInfo.subgroupMaxSize and device.adapterInfo.subgroupMaxSize <= 16",
100
+ "autoPadSame": "attrs.auto_pad == \"SAME_UPPER\" or attrs.auto_pad == \"SAME_LOWER\"",
101
+ "autoPadValid": "attrs.auto_pad == \"VALID\"",
102
+ "samePadDepth": "max(0, (outputDepth - 1) * strideD + (kernelDepth - 1) * dilationD + 1 - inputDepth)",
103
+ "samePadHeight": "max(0, (outputHeight - 1) * strideH + (kernelHeight - 1) * dilationH + 1 - inputHeight)",
104
+ "samePadWidth": "max(0, (outputWidth - 1) * strideW + (kernelWidth - 1) * dilationW + 1 - inputWidth)",
105
+ "samePadFront": "floor(samePadDepth / 2) if attrs.auto_pad == \"SAME_UPPER\" else samePadDepth - floor(samePadDepth / 2)",
106
+ "samePadTop": "floor(samePadHeight / 2) if attrs.auto_pad == \"SAME_UPPER\" else samePadHeight - floor(samePadHeight / 2)",
107
+ "samePadLeft": "floor(samePadWidth / 2) if attrs.auto_pad == \"SAME_UPPER\" else samePadWidth - floor(samePadWidth / 2)",
108
+ "effectivePadFront": "samePadFront if autoPadSame else (0 if autoPadValid else padFront)",
109
+ "effectivePadTop": "samePadTop if autoPadSame else (0 if autoPadValid else padTop)",
110
+ "effectivePadLeft": "samePadLeft if autoPadSame else (0 if autoPadValid else padLeft)",
111
+ "expectedOutputDepth": "ceil(inputDepth / strideD) if autoPadSame else floor((inputDepth + (0 if autoPadValid else padFront + padBack) - ((kernelDepth - 1) * dilationD + 1)) / strideD) + 1",
112
+ "expectedOutputHeight": "ceil(inputHeight / strideH) if autoPadSame else floor((inputHeight + (0 if autoPadValid else padTop + padBottom) - ((kernelHeight - 1) * dilationH + 1)) / strideH) + 1",
113
+ "expectedOutputWidth": "ceil(inputWidth / strideW) if autoPadSame else floor((inputWidth + (0 if autoPadValid else padLeft + padRight) - ((kernelWidth - 1) * dilationW + 1)) / strideW) + 1",
114
+ "spatialAttributeLengthsOk": "kernelShapeLengthOk and stridesLengthOk and dilationsLengthOk and padsLengthOk",
115
+ "kernelShapeMatchesWeights": "not has(attrs, \"kernel_shape\") or (kernelW == kernelWidth and (spatialRank < 2 or kernelH == kernelHeight) and (spatialRank < 3 or kernelD == kernelDepth))",
116
+ "kernelExtentsOk": "kernelWidth >= 1 and (spatialRank < 2 or kernelHeight >= 1) and (spatialRank < 3 or kernelDepth >= 1)",
117
+ "stridesValuesOk": "not has(attrs, \"strides\") or (strideD >= 1 and floor(strideD) == strideD and strideH >= 1 and floor(strideH) == strideH and strideW >= 1 and floor(strideW) == strideW)",
118
+ "dilationsValuesOk": "not has(attrs, \"dilations\") or (dilationD >= 1 and floor(dilationD) == dilationD and dilationH >= 1 and floor(dilationH) == dilationH and dilationW >= 1 and floor(dilationW) == dilationW)",
119
+ "padsValuesOk": "padFront >= 0 and floor(padFront) == padFront and padTop >= 0 and floor(padTop) == padTop and padLeft >= 0 and floor(padLeft) == padLeft and padBack >= 0 and floor(padBack) == padBack and padBottom >= 0 and floor(padBottom) == padBottom and padRight >= 0 and floor(padRight) == padRight",
120
+ "explicitPadsOk": "attrs.auto_pad == \"NOTSET\" or not has(attrs, \"pads\")",
121
+ "spatialAttributesOk": "spatialRank >= 1 and spatialRank <= 3 and spatialAttributeLengthsOk and kernelShapeMatchesWeights and kernelExtentsOk and stridesValuesOk and dilationsValuesOk and padsValuesOk and explicitPadsOk",
122
+ "packedFeature": "device.wgslLanguageFeatures.has(\"packed_4x8_integer_dot_product\")",
123
+ "xyQuantContract": "(ranks.x_scale == 0 or (ranks.x_scale == 1 and dim(shapes.x_scale, 0) == 1)) and (ranks.y_scale == 0 or (ranks.y_scale == 1 and dim(shapes.y_scale, 0) == 1)) and (ranks.x_zero_point == 0 or (ranks.x_zero_point == 1 and dim(shapes.x_zero_point, 0) == 1)) and (ranks.y_zero_point == 0 or (ranks.y_zero_point == 1 and dim(shapes.y_zero_point, 0) == 1))",
124
+ "rank3TensorOk": "spatialAttributesOk and ranks.x == 3 and ranks.w == 3 and ranks.y == 3",
125
+ "rank4TensorOk": "spatialAttributesOk and (ranks.x == 4 and ranks.w == 4 and ranks.y == 4)",
126
+ "rank5TensorOk": "spatialAttributesOk and ranks.x == 5 and ranks.w == 5 and ranks.y == 5",
127
+ "outputBatchChannelsOk": "dim(shapes.y, 0) == dim(shapes.x, 0) and dim(shapes.y, 1) == dim(shapes.w, 0)",
128
+ "groupChannelsOk": "attrs.group >= 1 and dim(shapes.w, 1) * attrs.group == dim(shapes.x, 1) and dim(shapes.w, 0) % attrs.group == 0",
129
+ "output2dShapeOk": "dim(shapes.y, 2) == expectedOutputHeight and dim(shapes.y, 3) == expectedOutputWidth",
130
+ "scalarWeightQuantOk": "(ranks.w_scale == 0 or (ranks.w_scale == 1 and dim(shapes.w_scale, 0) == 1)) and (ranks.w_zero_point == 0 or (ranks.w_zero_point == 1 and dim(shapes.w_zero_point, 0) == 1))",
131
+ "flexibleWeightQuantOk": "(((ranks.w_scale == 0 or (ranks.w_scale == 1 and dim(shapes.w_scale, 0) == 1)) and (ranks.w_zero_point == 0 or (ranks.w_zero_point == 1 and dim(shapes.w_zero_point, 0) == 1))) or (ranks.w_scale == 1 and dim(shapes.w_scale, 0) == dim(shapes.w, 0) and ranks.w_zero_point == 1 and dim(shapes.w_zero_point, 0) == dim(shapes.w, 0)))",
132
+ "dp4a2dDispatchOk": "ceilDiv(dim(shapes.y, 2) * dim(shapes.y, 3), 64) <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(dim(shapes.w, 0), 8) <= device.limits.maxComputeWorkgroupsPerDimension and dim(shapes.y, 0) <= device.limits.maxComputeWorkgroupsPerDimension",
133
+ "convM": "dim(shapes.w, 0)",
134
+ "convN": "inner(shapes.y, 1)",
135
+ "convK": "dim(shapes.w, 1) * inner(shapes.w, 1)",
136
+ "convBStride": "dim(shapes.w, 1) * inner(shapes.w, 1) * inner(shapes.y, 1)",
137
+ "convYStride": "dim(shapes.w, 0) * inner(shapes.y, 1)",
138
+ "paddedK": "ceilDiv(dim(shapes.w, 1) * inner(shapes.w, 1), 4) * 4",
139
+ "paddedBStride": "ceilDiv(dim(shapes.w, 1) * inner(shapes.w, 1), 4) * 4 * inner(shapes.y, 1)",
140
+ "paddedMatricesStorageOk": "dim(shapes.x, 0) * ceilDiv(dim(shapes.w, 1) * inner(shapes.w, 1), 4) * 4 * inner(shapes.y, 1) * 4 <= device.limits.maxStorageBufferBindingSize and dim(shapes.w, 0) * ceilDiv(dim(shapes.w, 1) * inner(shapes.w, 1), 4) * 4 * 4 <= device.limits.maxStorageBufferBindingSize",
141
+ "im2colStorageOk": "dim(shapes.x, 0) * dim(shapes.w, 1) * inner(shapes.w, 1) * inner(shapes.y, 1) * 4 <= device.limits.maxStorageBufferBindingSize",
142
+ "paddedPreparationDispatchOk": "ceilDiv(inner(shapes.y, 1), tunables.WORKGROUP_SIZE) <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(dim(shapes.w, 1) * inner(shapes.w, 1), 4) * 4 <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(dim(shapes.w, 0) * ceilDiv(dim(shapes.w, 1) * inner(shapes.w, 1), 4) * 4, tunables.WORKGROUP_SIZE) <= device.limits.maxComputeWorkgroupsPerDimension",
143
+ "direct3x3SharedBytes": "4 * ((tunables.DP4A_DIRECT_IC_TILE / 4) * (tunables.DP4A_DIRECT_SPATIAL_TILE + 2) * (tunables.DP4A_DIRECT_SPATIAL_TILE + 2) + tunables.DP4A_DIRECT_OC_TILE * (tunables.DP4A_DIRECT_IC_TILE / 4) * 9 + tunables.DP4A_DIRECT_OC_TILE)",
144
+ "direct3x3DispatchOk": "ceilDiv(dim(shapes.y, 3), tunables.DP4A_DIRECT_SPATIAL_TILE) <= device.limits.maxComputeWorkgroupsPerDimension and ceilDiv(dim(shapes.y, 2), tunables.DP4A_DIRECT_SPATIAL_TILE) * ceilDiv(dim(shapes.w, 0), tunables.DP4A_DIRECT_OC_TILE) <= device.limits.maxComputeWorkgroupsPerDimension and dim(shapes.y, 0) <= device.limits.maxComputeWorkgroupsPerDimension",
145
+ "direct3x3DeviceOk": "tunables.DP4A_DIRECT_IC_TILE % 4 == 0 and tunables.DP4A_DIRECT_SPATIAL_TILE * tunables.DP4A_DIRECT_SPATIAL_TILE <= device.limits.maxComputeInvocationsPerWorkgroup and tunables.DP4A_DIRECT_SPATIAL_TILE <= device.limits.maxComputeWorkgroupSizeX and tunables.DP4A_DIRECT_SPATIAL_TILE <= device.limits.maxComputeWorkgroupSizeY and direct3x3SharedBytes <= device.limits.maxComputeWorkgroupStorageSize and direct3x3DispatchOk"
146
+ },
147
+ "bindingSets": {
148
+ "padPointwiseK": [
149
+ { "name": "a", "arg": "w", "semantic": "w", "buffer": { "type": "read-only-storage" }, "elementType": "i32" },
150
+ { "name": "b", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "u32" },
151
+ {
152
+ "name": "a_zero_point",
153
+ "arg": "w_zero_point",
154
+ "semantic": "w_zero_point",
155
+ "buffer": { "type": "read-only-storage" },
156
+ "elementType": "i32"
157
+ },
158
+ {
159
+ "name": "b_zero_point",
160
+ "arg": "x_zero_point",
161
+ "semantic": "x_zero_point",
162
+ "buffer": { "type": "read-only-storage" },
163
+ "elementType": "u32",
164
+ "length": 1
165
+ },
166
+ { "name": "a_padded", "semantic": "w_padded", "buffer": { "type": "storage" }, "elementType": "i32" },
167
+ { "name": "b_padded", "semantic": "x_padded", "buffer": { "type": "storage" }, "elementType": "u32" },
168
+ {
169
+ "name": "params",
170
+ "semantic": "kernel.params",
171
+ "buffer": { "type": "uniform" },
172
+ "struct": {
173
+ "name": "Params",
174
+ "fields": [
175
+ { "name": "batch", "type": "u32", "value": "dim(shapes.x, 0)" },
176
+ { "name": "M", "type": "u32", "value": "dim(shapes.w, 0)" },
177
+ { "name": "N", "type": "u32", "value": "dim(shapes.y, 2) * dim(shapes.y, 3)" },
178
+ { "name": "K", "type": "u32", "value": "dim(shapes.x, 1)" },
179
+ { "name": "KPadded", "type": "u32", "value": "ceilDiv(dim(shapes.x, 1), 4) * 4" }
180
+ ]
181
+ }
182
+ }
183
+ ],
184
+ "dp4aPaddedMain": [
185
+ { "name": "a", "semantic": "w_padded", "buffer": { "type": "read-only-storage" }, "elementType": "vec4<i32>" },
186
+ { "name": "b", "semantic": "x_padded", "buffer": { "type": "read-only-storage" }, "elementType": "u32" },
187
+ {
188
+ "name": "a_zero_point",
189
+ "arg": "w_zero_point",
190
+ "semantic": "w_zero_point",
191
+ "buffer": { "type": "read-only-storage" },
192
+ "elementType": "i32"
193
+ },
194
+ {
195
+ "name": "b_zero_point",
196
+ "arg": "x_zero_point",
197
+ "semantic": "x_zero_point",
198
+ "buffer": { "type": "read-only-storage" },
199
+ "elementType": "u32",
200
+ "length": 1
201
+ },
202
+ {
203
+ "name": "a_scale",
204
+ "arg": "x_scale",
205
+ "semantic": "x_scale",
206
+ "buffer": { "type": "read-only-storage" },
207
+ "elementType": "f32",
208
+ "length": 1
209
+ },
210
+ {
211
+ "name": "b_scale",
212
+ "arg": "w_scale",
213
+ "semantic": "w_scale",
214
+ "buffer": { "type": "read-only-storage" },
215
+ "elementType": "f32"
216
+ },
217
+ {
218
+ "name": "y_scale",
219
+ "arg": "y_scale",
220
+ "semantic": "y_scale",
221
+ "buffer": { "type": "read-only-storage" },
222
+ "elementType": "f32",
223
+ "length": 1
224
+ },
225
+ {
226
+ "name": "y_zero_point",
227
+ "arg": "y_zero_point",
228
+ "semantic": "y_zero_point",
229
+ "buffer": { "type": "read-only-storage" },
230
+ "elementType": "u32",
231
+ "length": 1
232
+ },
233
+ { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "u32" },
234
+ {
235
+ "name": "params",
236
+ "semantic": "kernel.params",
237
+ "buffer": { "type": "uniform" },
238
+ "struct": {
239
+ "name": "Params",
240
+ "fields": [
241
+ { "name": "M", "type": "u32", "value": "convM" },
242
+ { "name": "N", "type": "u32", "value": "convN" },
243
+ { "name": "K", "type": "u32", "value": "paddedK" },
244
+ { "name": "aBatchStride4", "type": "u32", "value": 0 },
245
+ { "name": "bBatchStride", "type": "u32", "value": "paddedBStride" },
246
+ { "name": "yBatchStride", "type": "u32", "value": "convYStride" }
247
+ ]
248
+ }
249
+ }
250
+ ],
251
+ "dp4aPaddedIm2colMain": [
252
+ { "name": "a", "semantic": "w_padded", "buffer": { "type": "read-only-storage" }, "elementType": "vec4<i32>" },
253
+ { "name": "b", "semantic": "cols_padded", "buffer": { "type": "read-only-storage" }, "elementType": "u32" },
254
+ {
255
+ "name": "a_zero_point",
256
+ "arg": "w_zero_point",
257
+ "semantic": "w_zero_point",
258
+ "buffer": { "type": "read-only-storage" },
259
+ "elementType": "i32"
260
+ },
261
+ {
262
+ "name": "b_zero_point",
263
+ "arg": "x_zero_point",
264
+ "semantic": "x_zero_point",
265
+ "buffer": { "type": "read-only-storage" },
266
+ "elementType": "u32",
267
+ "length": 1
268
+ },
269
+ {
270
+ "name": "a_scale",
271
+ "arg": "x_scale",
272
+ "semantic": "x_scale",
273
+ "buffer": { "type": "read-only-storage" },
274
+ "elementType": "f32",
275
+ "length": 1
276
+ },
277
+ {
278
+ "name": "b_scale",
279
+ "arg": "w_scale",
280
+ "semantic": "w_scale",
281
+ "buffer": { "type": "read-only-storage" },
282
+ "elementType": "f32"
283
+ },
284
+ {
285
+ "name": "y_scale",
286
+ "arg": "y_scale",
287
+ "semantic": "y_scale",
288
+ "buffer": { "type": "read-only-storage" },
289
+ "elementType": "f32",
290
+ "length": 1
291
+ },
292
+ {
293
+ "name": "y_zero_point",
294
+ "arg": "y_zero_point",
295
+ "semantic": "y_zero_point",
296
+ "buffer": { "type": "read-only-storage" },
297
+ "elementType": "u32",
298
+ "length": 1
299
+ },
300
+ { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "u32" },
301
+ {
302
+ "name": "params",
303
+ "semantic": "kernel.params",
304
+ "buffer": { "type": "uniform" },
305
+ "struct": {
306
+ "name": "Params",
307
+ "fields": [
308
+ { "name": "M", "type": "u32", "value": "convM" },
309
+ { "name": "N", "type": "u32", "value": "convN" },
310
+ { "name": "K", "type": "u32", "value": "paddedK" },
311
+ { "name": "aBatchStride4", "type": "u32", "value": 0 },
312
+ { "name": "bBatchStride", "type": "u32", "value": "paddedBStride" },
313
+ { "name": "yBatchStride", "type": "u32", "value": "convYStride" }
314
+ ]
315
+ }
316
+ }
317
+ ],
318
+ "im2colNchwPadded": [
319
+ { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "u32" },
320
+ {
321
+ "name": "x_zero_point",
322
+ "arg": "x_zero_point",
323
+ "semantic": "x_zero_point",
324
+ "buffer": { "type": "read-only-storage" },
325
+ "elementType": "u32",
326
+ "length": 1
327
+ },
328
+ { "name": "cols", "semantic": "cols_padded", "buffer": { "type": "storage" }, "elementType": "u32" },
329
+ {
330
+ "name": "params",
331
+ "semantic": "kernel.params",
332
+ "buffer": { "type": "uniform" },
333
+ "struct": {
334
+ "name": "Params",
335
+ "fields": [
336
+ { "name": "outCount", "type": "u32", "value": "dim(shapes.y, 2) * dim(shapes.y, 3)" },
337
+ { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
338
+ { "name": "inH", "type": "u32", "value": "dim(shapes.x, 2)" },
339
+ { "name": "inW", "type": "u32", "value": "dim(shapes.x, 3)" },
340
+ { "name": "sourceKRows", "type": "u32", "value": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)" },
341
+ {
342
+ "name": "kRows",
343
+ "type": "u32",
344
+ "value": "ceilDiv(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3), 4) * 4"
345
+ },
346
+ { "name": "outW", "type": "u32", "value": "dim(shapes.y, 3)" }
347
+ ]
348
+ }
349
+ }
350
+ ],
351
+ "padWeightsK": [
352
+ { "name": "a", "arg": "w", "semantic": "w", "buffer": { "type": "read-only-storage" }, "elementType": "i32" },
353
+ {
354
+ "name": "a_zero_point",
355
+ "arg": "w_zero_point",
356
+ "semantic": "w_zero_point",
357
+ "buffer": { "type": "read-only-storage" },
358
+ "elementType": "i32"
359
+ },
360
+ { "name": "a_padded", "semantic": "w_padded", "buffer": { "type": "storage" }, "elementType": "i32" },
361
+ {
362
+ "name": "params",
363
+ "semantic": "kernel.params",
364
+ "buffer": { "type": "uniform" },
365
+ "struct": {
366
+ "name": "Params",
367
+ "fields": [
368
+ { "name": "M", "type": "u32", "value": "dim(shapes.w, 0)" },
369
+ { "name": "K", "type": "u32", "value": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)" },
370
+ {
371
+ "name": "KPadded",
372
+ "type": "u32",
373
+ "value": "ceilDiv(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3), 4) * 4"
374
+ }
375
+ ]
376
+ }
377
+ }
378
+ ],
379
+ "im2colNcdhw": [
380
+ { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$bScalar" },
381
+ {
382
+ "name": "x_zero_point",
383
+ "arg": "x_zero_point",
384
+ "semantic": "x_zero_point",
385
+ "buffer": { "type": "read-only-storage" },
386
+ "elementType": "$bScalar",
387
+ "length": 1
388
+ },
389
+ { "name": "cols", "semantic": "cols3d", "buffer": { "type": "storage" }, "elementType": "$bScalar" },
390
+ {
391
+ "name": "params",
392
+ "semantic": "kernel.params",
393
+ "buffer": { "type": "uniform" },
394
+ "struct": {
395
+ "name": "Params",
396
+ "fields": [
397
+ { "name": "outCount", "type": "u32", "value": "dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.y, 4)" },
398
+ { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
399
+ { "name": "inD", "type": "u32", "value": "dim(shapes.x, 2)" },
400
+ { "name": "inH", "type": "u32", "value": "dim(shapes.x, 3)" },
401
+ { "name": "inW", "type": "u32", "value": "dim(shapes.x, 4)" },
402
+ {
403
+ "name": "kRows",
404
+ "type": "u32",
405
+ "value": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) * dim(shapes.w, 4)"
406
+ },
407
+ { "name": "outH", "type": "u32", "value": "dim(shapes.y, 3)" },
408
+ { "name": "outW", "type": "u32", "value": "dim(shapes.y, 4)" }
409
+ ]
410
+ }
411
+ }
412
+ ],
413
+ "im2colNchw": [
414
+ { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$bScalar" },
415
+ {
416
+ "name": "x_zero_point",
417
+ "arg": "x_zero_point",
418
+ "semantic": "x_zero_point",
419
+ "buffer": { "type": "read-only-storage" },
420
+ "elementType": "$bScalar",
421
+ "length": 1
422
+ },
423
+ { "name": "cols", "semantic": "cols", "buffer": { "type": "storage" }, "elementType": "$bScalar" },
424
+ {
425
+ "name": "params",
426
+ "semantic": "kernel.params",
427
+ "buffer": { "type": "uniform" },
428
+ "struct": {
429
+ "name": "Params",
430
+ "fields": [
431
+ { "name": "outCount", "type": "u32", "value": "dim(shapes.y, 2) * dim(shapes.y, 3)" },
432
+ { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
433
+ { "name": "inH", "type": "u32", "value": "dim(shapes.x, 2)" },
434
+ { "name": "inW", "type": "u32", "value": "dim(shapes.x, 3)" },
435
+ { "name": "kRows", "type": "u32", "value": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)" },
436
+ { "name": "outW", "type": "u32", "value": "dim(shapes.y, 3)" }
437
+ ]
438
+ }
439
+ }
440
+ ],
441
+ "dp4aDirect3x3": [
442
+ { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "u32" },
443
+ {
444
+ "name": "x_scale",
445
+ "arg": "x_scale",
446
+ "semantic": "x_scale",
447
+ "buffer": { "type": "read-only-storage" },
448
+ "elementType": "f32",
449
+ "length": 1
450
+ },
451
+ {
452
+ "name": "x_zero_point",
453
+ "arg": "x_zero_point",
454
+ "semantic": "x_zero_point",
455
+ "buffer": { "type": "read-only-storage" },
456
+ "elementType": "u32",
457
+ "length": 1
458
+ },
459
+ { "name": "w", "arg": "w", "semantic": "w", "buffer": { "type": "read-only-storage" }, "elementType": "i32" },
460
+ {
461
+ "name": "w_scale",
462
+ "arg": "w_scale",
463
+ "semantic": "w_scale",
464
+ "buffer": { "type": "read-only-storage" },
465
+ "elementType": "f32"
466
+ },
467
+ {
468
+ "name": "w_zero_point",
469
+ "arg": "w_zero_point",
470
+ "semantic": "w_zero_point",
471
+ "buffer": { "type": "read-only-storage" },
472
+ "elementType": "i32"
473
+ },
474
+ {
475
+ "name": "y_scale",
476
+ "arg": "y_scale",
477
+ "semantic": "y_scale",
478
+ "buffer": { "type": "read-only-storage" },
479
+ "elementType": "f32",
480
+ "length": 1
481
+ },
482
+ {
483
+ "name": "y_zero_point",
484
+ "arg": "y_zero_point",
485
+ "semantic": "y_zero_point",
486
+ "buffer": { "type": "read-only-storage" },
487
+ "elementType": "u32",
488
+ "length": 1
489
+ },
490
+ { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "u32" }
491
+ ],
492
+ "dp4aMain": [
493
+ { "name": "a", "arg": "w", "semantic": "w", "buffer": { "type": "read-only-storage" }, "elementType": "$aVec4" },
494
+ {
495
+ "name": "b",
496
+ "arg": "cols",
497
+ "semantic": "cols",
498
+ "buffer": { "type": "read-only-storage" },
499
+ "elementType": "$bScalar"
500
+ },
501
+ {
502
+ "name": "a_zero_point",
503
+ "arg": "w_zero_point",
504
+ "semantic": "w_zero_point",
505
+ "buffer": { "type": "read-only-storage" },
506
+ "elementType": "$aScalar"
507
+ },
508
+ {
509
+ "name": "b_zero_point",
510
+ "arg": "x_zero_point",
511
+ "semantic": "x_zero_point",
512
+ "buffer": { "type": "read-only-storage" },
513
+ "elementType": "$bScalar",
514
+ "length": 1
515
+ },
516
+ {
517
+ "name": "a_scale",
518
+ "arg": "x_scale",
519
+ "semantic": "x_scale",
520
+ "buffer": { "type": "read-only-storage" },
521
+ "elementType": "f32",
522
+ "length": 1
523
+ },
524
+ {
525
+ "name": "b_scale",
526
+ "arg": "w_scale",
527
+ "semantic": "w_scale",
528
+ "buffer": { "type": "read-only-storage" },
529
+ "elementType": "f32"
530
+ },
531
+ {
532
+ "name": "y_scale",
533
+ "arg": "y_scale",
534
+ "semantic": "y_scale",
535
+ "buffer": { "type": "read-only-storage" },
536
+ "elementType": "f32",
537
+ "length": 1
538
+ },
539
+ {
540
+ "name": "y_zero_point",
541
+ "arg": "y_zero_point",
542
+ "semantic": "y_zero_point",
543
+ "buffer": { "type": "read-only-storage" },
544
+ "elementType": "$yScalar",
545
+ "length": 1
546
+ },
547
+ { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
548
+ {
549
+ "name": "params",
550
+ "semantic": "kernel.params",
551
+ "buffer": { "type": "uniform" },
552
+ "struct": {
553
+ "name": "Params",
554
+ "fields": [
555
+ { "name": "M", "type": "u32", "value": "convM" },
556
+ { "name": "N", "type": "u32", "value": "convN" },
557
+ { "name": "K", "type": "u32", "value": "convK" },
558
+ { "name": "aBatchStride4", "type": "u32", "value": 0 },
559
+ { "name": "bBatchStride", "type": "u32", "value": "convBStride" },
560
+ { "name": "yBatchStride", "type": "u32", "value": "convYStride" }
561
+ ]
562
+ }
563
+ }
564
+ ],
565
+ "dp4aMain3d": [
566
+ { "name": "a", "arg": "w", "semantic": "w", "buffer": { "type": "read-only-storage" }, "elementType": "$aVec4" },
567
+ { "name": "b", "semantic": "cols3d", "buffer": { "type": "read-only-storage" }, "elementType": "$bScalar" },
568
+ {
569
+ "name": "a_zero_point",
570
+ "arg": "w_zero_point",
571
+ "semantic": "w_zero_point",
572
+ "buffer": { "type": "read-only-storage" },
573
+ "elementType": "$aScalar"
574
+ },
575
+ {
576
+ "name": "b_zero_point",
577
+ "arg": "x_zero_point",
578
+ "semantic": "x_zero_point",
579
+ "buffer": { "type": "read-only-storage" },
580
+ "elementType": "$bScalar",
581
+ "length": 1
582
+ },
583
+ {
584
+ "name": "a_scale",
585
+ "arg": "x_scale",
586
+ "semantic": "x_scale",
587
+ "buffer": { "type": "read-only-storage" },
588
+ "elementType": "f32",
589
+ "length": 1
590
+ },
591
+ {
592
+ "name": "b_scale",
593
+ "arg": "w_scale",
594
+ "semantic": "w_scale",
595
+ "buffer": { "type": "read-only-storage" },
596
+ "elementType": "f32"
597
+ },
598
+ {
599
+ "name": "y_scale",
600
+ "arg": "y_scale",
601
+ "semantic": "y_scale",
602
+ "buffer": { "type": "read-only-storage" },
603
+ "elementType": "f32",
604
+ "length": 1
605
+ },
606
+ {
607
+ "name": "y_zero_point",
608
+ "arg": "y_zero_point",
609
+ "semantic": "y_zero_point",
610
+ "buffer": { "type": "read-only-storage" },
611
+ "elementType": "$yScalar",
612
+ "length": 1
613
+ },
614
+ { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
615
+ {
616
+ "name": "params",
617
+ "semantic": "kernel.params",
618
+ "buffer": { "type": "uniform" },
619
+ "struct": {
620
+ "name": "Params",
621
+ "fields": [
622
+ { "name": "M", "type": "u32", "value": "convM" },
623
+ { "name": "N", "type": "u32", "value": "convN" },
624
+ { "name": "K", "type": "u32", "value": "convK" },
625
+ { "name": "aBatchStride4", "type": "u32", "value": 0 },
626
+ { "name": "bBatchStride", "type": "u32", "value": "convBStride" },
627
+ { "name": "yBatchStride", "type": "u32", "value": "convYStride" }
628
+ ]
629
+ }
630
+ }
631
+ ],
632
+ "dp4aPointwiseMain": [
633
+ { "name": "a", "arg": "w", "semantic": "w", "buffer": { "type": "read-only-storage" }, "elementType": "$aVec4" },
634
+ { "name": "b", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$bScalar" },
635
+ {
636
+ "name": "a_zero_point",
637
+ "arg": "w_zero_point",
638
+ "semantic": "w_zero_point",
639
+ "buffer": { "type": "read-only-storage" },
640
+ "elementType": "$aScalar"
641
+ },
642
+ {
643
+ "name": "b_zero_point",
644
+ "arg": "x_zero_point",
645
+ "semantic": "x_zero_point",
646
+ "buffer": { "type": "read-only-storage" },
647
+ "elementType": "$bScalar",
648
+ "length": 1
649
+ },
650
+ {
651
+ "name": "a_scale",
652
+ "arg": "x_scale",
653
+ "semantic": "x_scale",
654
+ "buffer": { "type": "read-only-storage" },
655
+ "elementType": "f32",
656
+ "length": 1
657
+ },
658
+ {
659
+ "name": "b_scale",
660
+ "arg": "w_scale",
661
+ "semantic": "w_scale",
662
+ "buffer": { "type": "read-only-storage" },
663
+ "elementType": "f32"
664
+ },
665
+ {
666
+ "name": "y_scale",
667
+ "arg": "y_scale",
668
+ "semantic": "y_scale",
669
+ "buffer": { "type": "read-only-storage" },
670
+ "elementType": "f32",
671
+ "length": 1
672
+ },
673
+ {
674
+ "name": "y_zero_point",
675
+ "arg": "y_zero_point",
676
+ "semantic": "y_zero_point",
677
+ "buffer": { "type": "read-only-storage" },
678
+ "elementType": "$yScalar",
679
+ "length": 1
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": "M", "type": "u32", "value": "convM" },
690
+ { "name": "N", "type": "u32", "value": "convN" },
691
+ { "name": "K", "type": "u32", "value": "convK" },
692
+ { "name": "aBatchStride4", "type": "u32", "value": 0 },
693
+ { "name": "bBatchStride", "type": "u32", "value": "convBStride" },
694
+ { "name": "yBatchStride", "type": "u32", "value": "convYStride" }
695
+ ]
696
+ }
697
+ }
698
+ ],
699
+ "dp4aMainBias": [
700
+ { "name": "a", "arg": "w", "semantic": "w", "buffer": { "type": "read-only-storage" }, "elementType": "$aVec4" },
701
+ {
702
+ "name": "b",
703
+ "arg": "cols",
704
+ "semantic": "cols",
705
+ "buffer": { "type": "read-only-storage" },
706
+ "elementType": "$bScalar"
707
+ },
708
+ {
709
+ "name": "a_zero_point",
710
+ "arg": "w_zero_point",
711
+ "semantic": "w_zero_point",
712
+ "buffer": { "type": "read-only-storage" },
713
+ "elementType": "$aScalar"
714
+ },
715
+ {
716
+ "name": "b_zero_point",
717
+ "arg": "x_zero_point",
718
+ "semantic": "x_zero_point",
719
+ "buffer": { "type": "read-only-storage" },
720
+ "elementType": "$bScalar",
721
+ "length": 1
722
+ },
723
+ {
724
+ "name": "a_scale",
725
+ "arg": "x_scale",
726
+ "semantic": "x_scale",
727
+ "buffer": { "type": "read-only-storage" },
728
+ "elementType": "f32",
729
+ "length": 1
730
+ },
731
+ {
732
+ "name": "b_scale",
733
+ "arg": "w_scale",
734
+ "semantic": "w_scale",
735
+ "buffer": { "type": "read-only-storage" },
736
+ "elementType": "f32"
737
+ },
738
+ {
739
+ "name": "y_scale",
740
+ "arg": "y_scale",
741
+ "semantic": "y_scale",
742
+ "buffer": { "type": "read-only-storage" },
743
+ "elementType": "f32",
744
+ "length": 1
745
+ },
746
+ {
747
+ "name": "y_zero_point",
748
+ "arg": "y_zero_point",
749
+ "semantic": "y_zero_point",
750
+ "buffer": { "type": "read-only-storage" },
751
+ "elementType": "$yScalar",
752
+ "length": 1
753
+ },
754
+ { "name": "bias", "arg": "B", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "i32" },
755
+ { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
756
+ {
757
+ "name": "params",
758
+ "semantic": "kernel.params",
759
+ "buffer": { "type": "uniform" },
760
+ "struct": {
761
+ "name": "Params",
762
+ "fields": [
763
+ { "name": "M", "type": "u32", "value": "convM" },
764
+ { "name": "N", "type": "u32", "value": "convN" },
765
+ { "name": "K", "type": "u32", "value": "convK" },
766
+ { "name": "aBatchStride4", "type": "u32", "value": 0 },
767
+ { "name": "bBatchStride", "type": "u32", "value": "convBStride" },
768
+ { "name": "yBatchStride", "type": "u32", "value": "convYStride" }
769
+ ]
770
+ }
771
+ }
772
+ ],
773
+ "accumulateNcw": [
774
+ { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
775
+ {
776
+ "name": "x_zero_point",
777
+ "arg": "x_zero_point",
778
+ "semantic": "x_zero_point",
779
+ "buffer": { "type": "read-only-storage" },
780
+ "elementType": "$xScalar",
781
+ "length": 1
782
+ },
783
+ { "name": "w", "arg": "w", "semantic": "w", "buffer": { "type": "read-only-storage" }, "elementType": "$wScalar" },
784
+ {
785
+ "name": "w_zero_point",
786
+ "arg": "w_zero_point",
787
+ "semantic": "w_zero_point",
788
+ "buffer": { "type": "read-only-storage" },
789
+ "elementType": "$wScalar"
790
+ },
791
+ { "name": "y", "semantic": "accum", "buffer": { "type": "storage" }, "elementType": "i32" },
792
+ {
793
+ "name": "params",
794
+ "semantic": "kernel.params",
795
+ "buffer": { "type": "uniform" },
796
+ "struct": {
797
+ "name": "Params",
798
+ "fields": [
799
+ { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
800
+ { "name": "inH", "type": "u32", "value": 1 },
801
+ { "name": "inW", "type": "u32", "value": "dim(shapes.x, 2)" },
802
+ { "name": "outChannels", "type": "u32", "value": "dim(shapes.w, 0)" },
803
+ { "name": "weightInChannels", "type": "u32", "value": "dim(shapes.w, 1)" },
804
+ { "name": "inChannelsPerGroup", "type": "u32", "value": "dim(shapes.x, 1) / attrs.group" },
805
+ { "name": "outChannelsPerGroup", "type": "u32", "value": "dim(shapes.w, 0) / attrs.group" },
806
+ { "name": "kernelH", "type": "u32", "value": 1 },
807
+ { "name": "kernelW", "type": "u32", "value": "dim(shapes.w, 2)" },
808
+ { "name": "outH", "type": "u32", "value": 1 },
809
+ { "name": "outW", "type": "u32", "value": "dim(shapes.y, 2)" },
810
+ { "name": "strideH", "type": "u32", "value": 1 },
811
+ { "name": "strideW", "type": "u32", "value": "strideW" },
812
+ { "name": "dilationH", "type": "u32", "value": 1 },
813
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
814
+ { "name": "padH", "type": "i32", "value": 0 },
815
+ { "name": "padW", "type": "i32", "value": "effectivePadLeft" },
816
+ { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
817
+ ]
818
+ }
819
+ }
820
+ ],
821
+ "accumulateNcdhw": [
822
+ { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
823
+ {
824
+ "name": "x_zero_point",
825
+ "arg": "x_zero_point",
826
+ "semantic": "x_zero_point",
827
+ "buffer": { "type": "read-only-storage" },
828
+ "elementType": "$xScalar",
829
+ "length": 1
830
+ },
831
+ { "name": "w", "arg": "w", "semantic": "w", "buffer": { "type": "read-only-storage" }, "elementType": "$wScalar" },
832
+ {
833
+ "name": "w_zero_point",
834
+ "arg": "w_zero_point",
835
+ "semantic": "w_zero_point",
836
+ "buffer": { "type": "read-only-storage" },
837
+ "elementType": "$wScalar"
838
+ },
839
+ { "name": "y", "semantic": "accum", "buffer": { "type": "storage" }, "elementType": "i32" },
840
+ {
841
+ "name": "params",
842
+ "semantic": "kernel.params",
843
+ "buffer": { "type": "uniform" },
844
+ "struct": {
845
+ "name": "Params",
846
+ "fields": [
847
+ { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
848
+ { "name": "inD", "type": "u32", "value": "dim(shapes.x, 2)" },
849
+ { "name": "inH", "type": "u32", "value": "dim(shapes.x, 3)" },
850
+ { "name": "inW", "type": "u32", "value": "dim(shapes.x, 4)" },
851
+ { "name": "outChannels", "type": "u32", "value": "dim(shapes.w, 0)" },
852
+ { "name": "weightInChannels", "type": "u32", "value": "dim(shapes.w, 1)" },
853
+ { "name": "inChannelsPerGroup", "type": "u32", "value": "dim(shapes.x, 1) / attrs.group" },
854
+ { "name": "outChannelsPerGroup", "type": "u32", "value": "dim(shapes.w, 0) / attrs.group" },
855
+ { "name": "kernelD", "type": "u32", "value": "dim(shapes.w, 2)" },
856
+ { "name": "kernelH", "type": "u32", "value": "dim(shapes.w, 3)" },
857
+ { "name": "kernelW", "type": "u32", "value": "dim(shapes.w, 4)" },
858
+ { "name": "outD", "type": "u32", "value": "dim(shapes.y, 2)" },
859
+ { "name": "outH", "type": "u32", "value": "dim(shapes.y, 3)" },
860
+ { "name": "outW", "type": "u32", "value": "dim(shapes.y, 4)" },
861
+ { "name": "strideD", "type": "u32", "value": "strideD" },
862
+ { "name": "strideH", "type": "u32", "value": "strideH" },
863
+ { "name": "strideW", "type": "u32", "value": "strideW" },
864
+ { "name": "dilationD", "type": "u32", "value": "dilationD" },
865
+ { "name": "dilationH", "type": "u32", "value": "dilationH" },
866
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
867
+ { "name": "padD", "type": "i32", "value": "effectivePadFront" },
868
+ { "name": "padH", "type": "i32", "value": "effectivePadTop" },
869
+ { "name": "padW", "type": "i32", "value": "effectivePadLeft" },
870
+ { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
871
+ ]
872
+ }
873
+ }
874
+ ],
875
+ "fusedNchwX4": [
876
+ { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
877
+ {
878
+ "name": "x_scale",
879
+ "arg": "x_scale",
880
+ "semantic": "x_scale",
881
+ "buffer": { "type": "read-only-storage" },
882
+ "elementType": "f32",
883
+ "length": 1
884
+ },
885
+ {
886
+ "name": "x_zero_point",
887
+ "arg": "x_zero_point",
888
+ "semantic": "x_zero_point",
889
+ "buffer": { "type": "read-only-storage" },
890
+ "elementType": "$xScalar",
891
+ "length": 1
892
+ },
893
+ { "name": "w", "arg": "w", "semantic": "w", "buffer": { "type": "read-only-storage" }, "elementType": "$wScalar" },
894
+ {
895
+ "name": "w_scale",
896
+ "arg": "w_scale",
897
+ "semantic": "w_scale",
898
+ "buffer": { "type": "read-only-storage" },
899
+ "elementType": "f32"
900
+ },
901
+ {
902
+ "name": "w_zero_point",
903
+ "arg": "w_zero_point",
904
+ "semantic": "w_zero_point",
905
+ "buffer": { "type": "read-only-storage" },
906
+ "elementType": "$wScalar"
907
+ },
908
+ {
909
+ "name": "y_scale",
910
+ "arg": "y_scale",
911
+ "semantic": "y_scale",
912
+ "buffer": { "type": "read-only-storage" },
913
+ "elementType": "f32",
914
+ "length": 1
915
+ },
916
+ {
917
+ "name": "y_zero_point",
918
+ "arg": "y_zero_point",
919
+ "semantic": "y_zero_point",
920
+ "buffer": { "type": "read-only-storage" },
921
+ "elementType": "$yScalar",
922
+ "length": 1
923
+ },
924
+ { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
925
+ {
926
+ "name": "params",
927
+ "semantic": "kernel.params",
928
+ "buffer": { "type": "uniform" },
929
+ "struct": {
930
+ "name": "Params",
931
+ "fields": [
932
+ { "name": "batch", "type": "u32", "value": "dim(shapes.x, 0)" },
933
+ { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
934
+ { "name": "inH", "type": "u32", "value": "dim(shapes.x, 2)" },
935
+ { "name": "inW", "type": "u32", "value": "dim(shapes.x, 3)" },
936
+ { "name": "outChannels", "type": "u32", "value": "dim(shapes.w, 0)" },
937
+ { "name": "weightInChannels", "type": "u32", "value": "dim(shapes.w, 1)" },
938
+ { "name": "inChannelsPerGroup", "type": "u32", "value": "dim(shapes.x, 1) / attrs.group" },
939
+ { "name": "outChannelsPerGroup", "type": "u32", "value": "dim(shapes.w, 0) / attrs.group" },
940
+ { "name": "kernelH", "type": "u32", "value": "dim(shapes.w, 2)" },
941
+ { "name": "kernelW", "type": "u32", "value": "dim(shapes.w, 3)" },
942
+ { "name": "outH", "type": "u32", "value": "dim(shapes.y, 2)" },
943
+ { "name": "outW", "type": "u32", "value": "dim(shapes.y, 3)" },
944
+ { "name": "strideH", "type": "u32", "value": "strideH" },
945
+ { "name": "strideW", "type": "u32", "value": "strideW" },
946
+ { "name": "dilationH", "type": "u32", "value": "dilationH" },
947
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
948
+ { "name": "padH", "type": "i32", "value": "effectivePadTop" },
949
+ { "name": "padW", "type": "i32", "value": "effectivePadLeft" }
950
+ ]
951
+ }
952
+ }
953
+ ],
954
+ "accumulateNchw": [
955
+ { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "$xScalar" },
956
+ {
957
+ "name": "x_zero_point",
958
+ "arg": "x_zero_point",
959
+ "semantic": "x_zero_point",
960
+ "buffer": { "type": "read-only-storage" },
961
+ "elementType": "$xScalar",
962
+ "length": 1
963
+ },
964
+ { "name": "w", "arg": "w", "semantic": "w", "buffer": { "type": "read-only-storage" }, "elementType": "$wScalar" },
965
+ {
966
+ "name": "w_zero_point",
967
+ "arg": "w_zero_point",
968
+ "semantic": "w_zero_point",
969
+ "buffer": { "type": "read-only-storage" },
970
+ "elementType": "$wScalar"
971
+ },
972
+ { "name": "y", "semantic": "accum", "buffer": { "type": "storage" }, "elementType": "i32" },
973
+ {
974
+ "name": "params",
975
+ "semantic": "kernel.params",
976
+ "buffer": { "type": "uniform" },
977
+ "struct": {
978
+ "name": "Params",
979
+ "fields": [
980
+ { "name": "inChannels", "type": "u32", "value": "dim(shapes.x, 1)" },
981
+ { "name": "inH", "type": "u32", "value": "dim(shapes.x, 2)" },
982
+ { "name": "inW", "type": "u32", "value": "dim(shapes.x, 3)" },
983
+ { "name": "outChannels", "type": "u32", "value": "dim(shapes.w, 0)" },
984
+ { "name": "weightInChannels", "type": "u32", "value": "dim(shapes.w, 1)" },
985
+ { "name": "inChannelsPerGroup", "type": "u32", "value": "dim(shapes.x, 1) / attrs.group" },
986
+ { "name": "outChannelsPerGroup", "type": "u32", "value": "dim(shapes.w, 0) / attrs.group" },
987
+ { "name": "kernelH", "type": "u32", "value": "dim(shapes.w, 2)" },
988
+ { "name": "kernelW", "type": "u32", "value": "dim(shapes.w, 3)" },
989
+ { "name": "outH", "type": "u32", "value": "dim(shapes.y, 2)" },
990
+ { "name": "outW", "type": "u32", "value": "dim(shapes.y, 3)" },
991
+ { "name": "strideH", "type": "u32", "value": "strideH" },
992
+ { "name": "strideW", "type": "u32", "value": "strideW" },
993
+ { "name": "dilationH", "type": "u32", "value": "dilationH" },
994
+ { "name": "dilationW", "type": "u32", "value": "dilationW" },
995
+ { "name": "padH", "type": "i32", "value": "effectivePadTop" },
996
+ { "name": "padW", "type": "i32", "value": "effectivePadLeft" },
997
+ { "name": "count", "type": "u32", "value": "numel(shapes.y)" }
998
+ ]
999
+ }
1000
+ }
1001
+ ],
1002
+ "requantizeScalar": [
1003
+ { "name": "accum", "semantic": "accum", "buffer": { "type": "read-only-storage" }, "elementType": "i32" },
1004
+ {
1005
+ "name": "x_scale",
1006
+ "arg": "x_scale",
1007
+ "semantic": "x_scale",
1008
+ "buffer": { "type": "read-only-storage" },
1009
+ "elementType": "f32",
1010
+ "length": 1
1011
+ },
1012
+ {
1013
+ "name": "w_scale",
1014
+ "arg": "w_scale",
1015
+ "semantic": "w_scale",
1016
+ "buffer": { "type": "read-only-storage" },
1017
+ "elementType": "f32"
1018
+ },
1019
+ {
1020
+ "name": "y_scale",
1021
+ "arg": "y_scale",
1022
+ "semantic": "y_scale",
1023
+ "buffer": { "type": "read-only-storage" },
1024
+ "elementType": "f32",
1025
+ "length": 1
1026
+ },
1027
+ {
1028
+ "name": "y_zero_point",
1029
+ "arg": "y_zero_point",
1030
+ "semantic": "y_zero_point",
1031
+ "buffer": { "type": "read-only-storage" },
1032
+ "elementType": "$yScalar",
1033
+ "length": 1
1034
+ },
1035
+ { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
1036
+ {
1037
+ "name": "params",
1038
+ "semantic": "kernel.params",
1039
+ "buffer": { "type": "uniform" },
1040
+ "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.y)" }] }
1041
+ }
1042
+ ],
1043
+ "requantizePerChannel": [
1044
+ { "name": "accum", "semantic": "accum", "buffer": { "type": "read-only-storage" }, "elementType": "i32" },
1045
+ {
1046
+ "name": "x_scale",
1047
+ "arg": "x_scale",
1048
+ "semantic": "x_scale",
1049
+ "buffer": { "type": "read-only-storage" },
1050
+ "elementType": "f32",
1051
+ "length": 1
1052
+ },
1053
+ {
1054
+ "name": "w_scale",
1055
+ "arg": "w_scale",
1056
+ "semantic": "w_scale",
1057
+ "buffer": { "type": "read-only-storage" },
1058
+ "elementType": "f32"
1059
+ },
1060
+ {
1061
+ "name": "y_scale",
1062
+ "arg": "y_scale",
1063
+ "semantic": "y_scale",
1064
+ "buffer": { "type": "read-only-storage" },
1065
+ "elementType": "f32",
1066
+ "length": 1
1067
+ },
1068
+ {
1069
+ "name": "y_zero_point",
1070
+ "arg": "y_zero_point",
1071
+ "semantic": "y_zero_point",
1072
+ "buffer": { "type": "read-only-storage" },
1073
+ "elementType": "$yScalar",
1074
+ "length": 1
1075
+ },
1076
+ { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
1077
+ {
1078
+ "name": "params",
1079
+ "semantic": "kernel.params",
1080
+ "buffer": { "type": "uniform" },
1081
+ "struct": {
1082
+ "name": "Params",
1083
+ "fields": [
1084
+ { "name": "count", "type": "u32", "value": "numel(shapes.y)" },
1085
+ { "name": "outChannels", "type": "u32", "value": "dim(shapes.w, 0)" },
1086
+ {
1087
+ "name": "outImageSize",
1088
+ "type": "u32",
1089
+ "value": "numel(shapes.y) / (dim(shapes.y, 0) * dim(shapes.y, 1))"
1090
+ }
1091
+ ]
1092
+ }
1093
+ }
1094
+ ],
1095
+ "requantizeBias": [
1096
+ { "name": "accum", "semantic": "accum", "buffer": { "type": "read-only-storage" }, "elementType": "i32" },
1097
+ {
1098
+ "name": "x_scale",
1099
+ "arg": "x_scale",
1100
+ "semantic": "x_scale",
1101
+ "buffer": { "type": "read-only-storage" },
1102
+ "elementType": "f32",
1103
+ "length": 1
1104
+ },
1105
+ {
1106
+ "name": "w_scale",
1107
+ "arg": "w_scale",
1108
+ "semantic": "w_scale",
1109
+ "buffer": { "type": "read-only-storage" },
1110
+ "elementType": "f32"
1111
+ },
1112
+ {
1113
+ "name": "y_scale",
1114
+ "arg": "y_scale",
1115
+ "semantic": "y_scale",
1116
+ "buffer": { "type": "read-only-storage" },
1117
+ "elementType": "f32",
1118
+ "length": 1
1119
+ },
1120
+ {
1121
+ "name": "y_zero_point",
1122
+ "arg": "y_zero_point",
1123
+ "semantic": "y_zero_point",
1124
+ "buffer": { "type": "read-only-storage" },
1125
+ "elementType": "$yScalar",
1126
+ "length": 1
1127
+ },
1128
+ { "name": "B", "arg": "B", "semantic": "B", "buffer": { "type": "read-only-storage" }, "elementType": "i32" },
1129
+ { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "$yScalar" },
1130
+ {
1131
+ "name": "params",
1132
+ "semantic": "kernel.params",
1133
+ "buffer": { "type": "uniform" },
1134
+ "struct": {
1135
+ "name": "Params",
1136
+ "fields": [
1137
+ { "name": "count", "type": "u32", "value": "numel(shapes.y)" },
1138
+ { "name": "outChannels", "type": "u32", "value": "dim(shapes.w, 0)" },
1139
+ {
1140
+ "name": "outImageSize",
1141
+ "type": "u32",
1142
+ "value": "numel(shapes.y) / (dim(shapes.y, 0) * dim(shapes.y, 1))"
1143
+ }
1144
+ ]
1145
+ }
1146
+ }
1147
+ ]
1148
+ },
1149
+ "variants": [
1150
+ {
1151
+ "id": "dp4a_pointwise_1x1_padded_k_baseline",
1152
+ "priority": 21,
1153
+ "when": ["packedFeature", "not present.B", "not device.features.has(\"shader-f16\")", "rank4TensorOk", "tensorDtypes.x == \"uint8\"", "tensorDtypes.w == \"int8\"", "tensorDtypes.y == \"uint8\"", "xyQuantContract", "flexibleWeightQuantOk", "attrs.group == 1", "dim(shapes.x, 1) % 4 != 0", "dim(shapes.x, 1) >= 4", "dim(shapes.w, 0) >= 8", "dim(shapes.y, 2) * dim(shapes.y, 3) >= 256", "dim(shapes.w, 2) == 1", "dim(shapes.w, 3) == 1", "strideH == 1", "strideW == 1", "padTop == 0 and padBottom == 0", "padLeft == 0 and padRight == 0", "dim(shapes.w, 1) == dim(shapes.x, 1)", "outputBatchChannelsOk", "dim(shapes.y, 2) == dim(shapes.x, 2)", "dim(shapes.y, 3) == dim(shapes.x, 3)", "paddedMatricesStorageOk", "dp4a2dDispatchOk"],
1154
+ "constants": {
1155
+ "aZeroPerRow": "ranks.w_zero_point == 1 and dim(shapes.w_zero_point, 0) == dim(shapes.w, 0)",
1156
+ "aScalar": "\"i32\"",
1157
+ "bScalar": "\"u32\"",
1158
+ "yScalar": "\"u32\"",
1159
+ "aUnsigned": false,
1160
+ "bUnsigned": true,
1161
+ "yUnsigned": true,
1162
+ "hasAZero": true,
1163
+ "hasBZero": true,
1164
+ "aPacked": true,
1165
+ "bZeroPerColumn": false,
1166
+ "requant": "\"per_row\" if ranks.w_scale == 1 and dim(shapes.w_scale, 0) == dim(shapes.w, 0) else \"per_tensor\"",
1167
+ "scaleScalar": "\"f32\"",
1168
+ "qMin": 0,
1169
+ "qMax": 255,
1170
+ "tileM": 8,
1171
+ "tileN": 64,
1172
+ "ktWords": 32,
1173
+ "padB": true
1174
+ },
1175
+ "intermediates": [
1176
+ { "id": "w_padded", "dtype": "int32", "shape": "[dim(shapes.w, 0) * ceilDiv(dim(shapes.x, 1), 4) * 4]" },
1177
+ {
1178
+ "id": "x_padded",
1179
+ "dtype": "uint32",
1180
+ "shape": "[dim(shapes.x, 0) * ceilDiv(dim(shapes.x, 1), 4) * 4 * dim(shapes.y, 2) * dim(shapes.y, 3)]"
1181
+ }
1182
+ ],
1183
+ "passes": [
1184
+ {
1185
+ "id": "pad_k",
1186
+ "name": "QLinearConv.PadPointwiseK",
1187
+ "shader": "quant-pad-k-matrices.wgsl.jinja",
1188
+ "bindings": "padPointwiseK",
1189
+ "dispatch": {
1190
+ "threads": "max(dim(shapes.w, 0) * ceilDiv(dim(shapes.x, 1), 4) * 4, dim(shapes.x, 0) * ceilDiv(dim(shapes.x, 1), 4) * 4 * dim(shapes.y, 2) * dim(shapes.y, 3))",
1191
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
1192
+ }
1193
+ },
1194
+ {
1195
+ "id": "main",
1196
+ "name": "QLinearConv.Dp4aPointwisePaddedK",
1197
+ "shader": "quant-dp4a-matmul.wgsl.jinja",
1198
+ "bindings": "dp4aPaddedMain",
1199
+ "dispatch": {
1200
+ "x": "ceil(dim(shapes.y, 2) * dim(shapes.y, 3) / 64)",
1201
+ "y": "ceil(dim(shapes.w, 0) / 8)",
1202
+ "z": "dim(shapes.y, 0)"
1203
+ }
1204
+ }
1205
+ ]
1206
+ },
1207
+ {
1208
+ "id": "dp4a_im2col_nchw_padded_k_baseline",
1209
+ "priority": 21,
1210
+ "when": ["packedFeature", "not present.B", "not device.features.has(\"shader-f16\")", "rank4TensorOk", "tensorDtypes.x == \"uint8\"", "tensorDtypes.w == \"int8\"", "tensorDtypes.y == \"uint8\"", "xyQuantContract", "flexibleWeightQuantOk", "attrs.group == 1", "strideH >= 1", "strideW >= 1", "dilationH >= 1", "dilationW >= 1", "(dim(shapes.w, 2) > 1 or dim(shapes.w, 3) > 1)", "dim(shapes.w, 1) == dim(shapes.x, 1)", "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) % 4 != 0", "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) >= 64", "dim(shapes.w, 0) >= 8", "dim(shapes.y, 2) * dim(shapes.y, 3) >= 256", "numel(shapes.y) >= 8192", "outputBatchChannelsOk", "output2dShapeOk", "paddedMatricesStorageOk", "paddedPreparationDispatchOk", "dp4a2dDispatchOk"],
1211
+ "constants": {
1212
+ "aZeroPerRow": "ranks.w_zero_point == 1 and dim(shapes.w_zero_point, 0) == dim(shapes.w, 0)",
1213
+ "aScalar": "\"i32\"",
1214
+ "bScalar": "\"u32\"",
1215
+ "yScalar": "\"u32\"",
1216
+ "aUnsigned": false,
1217
+ "bUnsigned": true,
1218
+ "yUnsigned": true,
1219
+ "hasAZero": true,
1220
+ "hasBZero": true,
1221
+ "aPacked": true,
1222
+ "bZeroPerColumn": false,
1223
+ "requant": "\"per_row\" if ranks.w_scale == 1 and dim(shapes.w_scale, 0) == dim(shapes.w, 0) else \"per_tensor\"",
1224
+ "scaleScalar": "\"f32\"",
1225
+ "qMin": 0,
1226
+ "qMax": 255,
1227
+ "tileM": 8,
1228
+ "tileN": 64,
1229
+ "ktWords": 32,
1230
+ "padB": false
1231
+ },
1232
+ "intermediates": [
1233
+ {
1234
+ "id": "w_padded",
1235
+ "dtype": "int32",
1236
+ "shape": "[dim(shapes.w, 0) * ceilDiv(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3), 4) * 4]"
1237
+ },
1238
+ {
1239
+ "id": "cols_padded",
1240
+ "dtype": "uint32",
1241
+ "shape": "[dim(shapes.x, 0) * ceilDiv(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3), 4) * 4 * dim(shapes.y, 2) * dim(shapes.y, 3)]"
1242
+ }
1243
+ ],
1244
+ "passes": [
1245
+ {
1246
+ "id": "im2col",
1247
+ "name": "QLinearConv.Im2ColPaddedK",
1248
+ "source": {
1249
+ "shader": "conv-int-im2col-spatial.wgsl.jinja",
1250
+ "inputs": {
1251
+ "spatialRank": 2,
1252
+ "padK": true,
1253
+ "kernelH": "dim(shapes.w, 2)",
1254
+ "kernelW": "dim(shapes.w, 3)",
1255
+ "strideH": "strideH",
1256
+ "strideW": "strideW",
1257
+ "dilationH": "dilationH",
1258
+ "dilationW": "dilationW",
1259
+ "padTop": "effectivePadTop",
1260
+ "padLeft": "effectivePadLeft"
1261
+ }
1262
+ },
1263
+ "bindings": "im2colNchwPadded",
1264
+ "dispatch": {
1265
+ "x": "ceil(dim(shapes.y, 2) * dim(shapes.y, 3) / tunables.WORKGROUP_SIZE)",
1266
+ "y": "ceilDiv(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3), 4) * 4",
1267
+ "z": "dim(shapes.x, 0)"
1268
+ }
1269
+ },
1270
+ {
1271
+ "id": "pad_w",
1272
+ "name": "QLinearConv.PadIm2ColWeightsK",
1273
+ "shader": "quant-pad-k-matrices.wgsl.jinja",
1274
+ "bindings": "padWeightsK",
1275
+ "dispatch": {
1276
+ "threads": "dim(shapes.w, 0) * ceilDiv(dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3), 4) * 4",
1277
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
1278
+ }
1279
+ },
1280
+ {
1281
+ "id": "main",
1282
+ "name": "QLinearConv.Im2ColPaddedKDp4a",
1283
+ "shader": "quant-dp4a-matmul.wgsl.jinja",
1284
+ "bindings": "dp4aPaddedIm2colMain",
1285
+ "dispatch": {
1286
+ "x": "ceil(dim(shapes.y, 2) * dim(shapes.y, 3) / 64)",
1287
+ "y": "ceil(dim(shapes.w, 0) / 8)",
1288
+ "z": "dim(shapes.y, 0)"
1289
+ }
1290
+ }
1291
+ ]
1292
+ },
1293
+ {
1294
+ "id": "dp4a_direct_3x3_nchw_u8s8",
1295
+ "priority": 30,
1296
+ "demoteWhen": ["narrowSubgroupRange"],
1297
+ "when": ["packedFeature", "not present.B", "rank4TensorOk", "tensorDtypes.x == \"uint8\"", "tensorDtypes.w == \"int8\"", "tensorDtypes.y == \"uint8\"", "xyQuantContract", "flexibleWeightQuantOk", "attrs.group == 1", "dim(shapes.w, 1) == dim(shapes.x, 1)", "dim(shapes.w, 2) == 3", "dim(shapes.w, 3) == 3", "strideH == 1", "strideW == 1", "dilationH == 1", "dilationW == 1", "padTop == 1", "padLeft == 1", "dim(shapes.y, 2) == dim(shapes.x, 2)", "dim(shapes.y, 3) == dim(shapes.x, 3)", "outputBatchChannelsOk", "output2dShapeOk", "dim(shapes.x, 1) % tunables.DP4A_DIRECT_IC_TILE == 0", "dim(shapes.w, 0) >= tunables.DP4A_DIRECT_OC_TILE", "direct3x3DeviceOk"],
1298
+ "passes": [
1299
+ {
1300
+ "id": "main",
1301
+ "name": "QLinearConv.Direct3x3Dp4a",
1302
+ "source": {
1303
+ "shader": "qlinear-conv-dp4a-direct-3x3.wgsl.jinja",
1304
+ "inputs": {
1305
+ "batch": "dim(shapes.x, 0)",
1306
+ "inChannels": "dim(shapes.x, 1)",
1307
+ "inHeight": "dim(shapes.x, 2)",
1308
+ "inWidth": "dim(shapes.x, 3)",
1309
+ "outChannels": "dim(shapes.w, 0)",
1310
+ "outHeight": "dim(shapes.y, 2)",
1311
+ "outWidth": "dim(shapes.y, 3)",
1312
+ "spatialTile": "tunables.DP4A_DIRECT_SPATIAL_TILE",
1313
+ "ocTile": "tunables.DP4A_DIRECT_OC_TILE",
1314
+ "icTile": "tunables.DP4A_DIRECT_IC_TILE",
1315
+ "wScalePerChannel": "ranks.w_scale == 1 and dim(shapes.w_scale, 0) == dim(shapes.w, 0)",
1316
+ "wZeroPerChannel": "ranks.w_zero_point == 1 and dim(shapes.w_zero_point, 0) == dim(shapes.w, 0)"
1317
+ }
1318
+ },
1319
+ "bindings": "dp4aDirect3x3",
1320
+ "dispatch": {
1321
+ "x": "ceilDiv(dim(shapes.y, 3), tunables.DP4A_DIRECT_SPATIAL_TILE)",
1322
+ "y": "ceilDiv(dim(shapes.y, 2), tunables.DP4A_DIRECT_SPATIAL_TILE) * ceilDiv(dim(shapes.w, 0), tunables.DP4A_DIRECT_OC_TILE)",
1323
+ "z": "dim(shapes.y, 0)"
1324
+ }
1325
+ }
1326
+ ]
1327
+ },
1328
+ {
1329
+ "id": "dp4a_pointwise_1x1",
1330
+ "priority": 20,
1331
+ "when": ["packedFeature", "not present.B", "rank4TensorOk", "flexibleWeightQuantOk", "xyQuantContract", "attrs.group == 1", "(device.features.has(\"shader-f16\") or dim(shapes.x, 1) % 4 == 0)", "dim(shapes.w, 2) == 1", "dim(shapes.w, 3) == 1", "strideH == 1", "strideW == 1", "padTop == 0 and padBottom == 0", "padLeft == 0 and padRight == 0", "dim(shapes.w, 1) == dim(shapes.x, 1)", "outputBatchChannelsOk", "dim(shapes.y, 2) == dim(shapes.x, 2)", "dim(shapes.y, 3) == dim(shapes.x, 3)", "dp4a2dDispatchOk"],
1332
+ "constants": {
1333
+ "aScalar": "\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\"",
1334
+ "bScalar": "\"u32\" if tensorDtypes.x == \"uint8\" else \"i32\"",
1335
+ "aVec4": "(\"vec4<u32>\" if tensorDtypes.w == \"uint8\" else \"vec4<i32>\") if dim(shapes.x, 1) % 4 == 0 else (\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\")",
1336
+ "yScalar": "\"u32\" if tensorDtypes.y == \"uint8\" else \"i32\"",
1337
+ "aUnsigned": "tensorDtypes.w == \"uint8\"",
1338
+ "bUnsigned": "tensorDtypes.x == \"uint8\"",
1339
+ "yUnsigned": "tensorDtypes.y == \"uint8\"",
1340
+ "hasAZero": true,
1341
+ "hasBZero": true,
1342
+ "aPacked": "dim(shapes.x, 1) % 4 == 0",
1343
+ "aZeroPerRow": "ranks.w_zero_point == 1 and dim(shapes.w_zero_point, 0) == dim(shapes.w, 0)",
1344
+ "bZeroPerColumn": false,
1345
+ "requant": "\"per_row\" if ranks.w_scale == 1 and dim(shapes.w_scale, 0) == dim(shapes.w, 0) else \"per_tensor\"",
1346
+ "scaleScalar": "\"f32\"",
1347
+ "qMin": "0 if tensorDtypes.y == \"uint8\" else 0 - 128",
1348
+ "qMax": "255 if tensorDtypes.y == \"uint8\" else 127",
1349
+ "tileM": 8,
1350
+ "tileN": 64,
1351
+ "ktWords": 32
1352
+ },
1353
+ "passes": [
1354
+ {
1355
+ "id": "main",
1356
+ "name": "QLinearConv.Dp4aPointwiseFused",
1357
+ "shader": "quant-dp4a-matmul.wgsl.jinja",
1358
+ "bindings": "dp4aPointwiseMain",
1359
+ "dispatch": {
1360
+ "x": "ceil(dim(shapes.y, 2) * dim(shapes.y, 3) / 64)",
1361
+ "y": "ceil(dim(shapes.w, 0) / 8)",
1362
+ "z": "dim(shapes.y, 0)"
1363
+ }
1364
+ }
1365
+ ]
1366
+ },
1367
+ {
1368
+ "id": "nchw2d_feature_independent_x4",
1369
+ "priority": 10,
1370
+ "when": ["not present.B", "rank4TensorOk", "flexibleWeightQuantOk", "xyQuantContract", "groupChannelsOk", "outputBatchChannelsOk", "output2dShapeOk", "dim(shapes.y, 3) >= 4", "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) >= 4", "numel(shapes.y) >= 32"],
1371
+ "constants": {
1372
+ "xScalar": "\"u32\" if tensorDtypes.x == \"uint8\" else \"i32\"",
1373
+ "wScalar": "\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\"",
1374
+ "yScalar": "\"u32\" if tensorDtypes.y == \"uint8\" else \"i32\"",
1375
+ "xUnsigned": "tensorDtypes.x == \"uint8\"",
1376
+ "wUnsigned": "tensorDtypes.w == \"uint8\"",
1377
+ "yUnsigned": "tensorDtypes.y == \"uint8\"",
1378
+ "wScalePerChannel": "ranks.w_scale == 1 and dim(shapes.w_scale, 0) == dim(shapes.w, 0)",
1379
+ "wZeroPerChannel": "ranks.w_zero_point == 1 and dim(shapes.w_zero_point, 0) == dim(shapes.w, 0)",
1380
+ "ocTile": "4 if dim(shapes.w, 0) >= attrs.group * 4 and dim(shapes.w, 0) % (attrs.group * 4) == 0 else 1",
1381
+ "qMin": "0 if tensorDtypes.y == \"uint8\" else 0 - 128",
1382
+ "qMax": "255 if tensorDtypes.y == \"uint8\" else 127"
1383
+ },
1384
+ "passes": [
1385
+ {
1386
+ "id": "main",
1387
+ "name": "QLinearConv.FeatureIndependentX4",
1388
+ "shader": "qlinear-conv-nchw-x4.wgsl.jinja",
1389
+ "bindings": "fusedNchwX4",
1390
+ "dispatch": {
1391
+ "threads": "dim(shapes.y, 0) * ceil(dim(shapes.y, 1) / (4 if dim(shapes.w, 0) >= attrs.group * 4 and dim(shapes.w, 0) % (attrs.group * 4) == 0 else 1)) * dim(shapes.y, 2) * ceil(dim(shapes.y, 3) / 4)",
1392
+ "workgroupSize": "tunables.WORKGROUP_SIZE"
1393
+ }
1394
+ }
1395
+ ]
1396
+ },
1397
+ {
1398
+ "id": "ncdhw3d_per_channel_quantization",
1399
+ "when": ["not present.B", "rank5TensorOk and dim(shapes.y, 2) == expectedOutputDepth and dim(shapes.y, 3) == expectedOutputHeight and dim(shapes.y, 4) == expectedOutputWidth", "not scalarWeightQuantOk and ranks.w_scale == 1 and dim(shapes.w_scale, 0) == dim(shapes.w, 0) and ranks.w_zero_point == 1 and dim(shapes.w_zero_point, 0) == dim(shapes.w, 0)", "xyQuantContract", "groupChannelsOk", "outputBatchChannelsOk"],
1400
+ "constants": {
1401
+ "xScalar": "\"u32\" if tensorDtypes.x == \"uint8\" else \"i32\"",
1402
+ "wScalar": "\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\"",
1403
+ "yScalar": "\"u32\" if tensorDtypes.y == \"uint8\" else \"i32\"",
1404
+ "xUnsigned": "tensorDtypes.x == \"uint8\"",
1405
+ "wUnsigned": "tensorDtypes.w == \"uint8\"",
1406
+ "yUnsigned": "tensorDtypes.y == \"uint8\"",
1407
+ "hasBias": false,
1408
+ "wScalePerChannel": true,
1409
+ "wZeroPerChannel": true,
1410
+ "qMin": "0 if tensorDtypes.y == \"uint8\" else 0 - 128",
1411
+ "qMax": "255 if tensorDtypes.y == \"uint8\" else 127"
1412
+ },
1413
+ "intermediates": [{ "id": "accum", "dtype": "int32", "shape": "shapes.y" }],
1414
+ "passes": [
1415
+ {
1416
+ "id": "accumulate",
1417
+ "name": "QLinearConv.AccumulatePerChannel3d",
1418
+ "source": { "shader": "conv-int-accumulate-spatial.wgsl.jinja", "inputs": { "spatialRank": 3 } },
1419
+ "bindings": "accumulateNcdhw",
1420
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1421
+ },
1422
+ {
1423
+ "id": "requantize",
1424
+ "name": "QLinearConv.Requantize",
1425
+ "shader": "qlinear-conv-requantize.wgsl.jinja",
1426
+ "bindings": "requantizePerChannel",
1427
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1428
+ }
1429
+ ]
1430
+ },
1431
+ {
1432
+ "id": "ncw1d_scalar_quantization",
1433
+ "when": ["not present.B", "rank3TensorOk and dim(shapes.y, 2) == expectedOutputWidth", "scalarWeightQuantOk", "xyQuantContract", "groupChannelsOk", "outputBatchChannelsOk"],
1434
+ "constants": {
1435
+ "xScalar": "\"u32\" if tensorDtypes.x == \"uint8\" else \"i32\"",
1436
+ "wScalar": "\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\"",
1437
+ "yScalar": "\"u32\" if tensorDtypes.y == \"uint8\" else \"i32\"",
1438
+ "xUnsigned": "tensorDtypes.x == \"uint8\"",
1439
+ "wUnsigned": "tensorDtypes.w == \"uint8\"",
1440
+ "yUnsigned": "tensorDtypes.y == \"uint8\"",
1441
+ "hasBias": false,
1442
+ "qMin": "0 if tensorDtypes.y == \"uint8\" else 0 - 128",
1443
+ "qMax": "255 if tensorDtypes.y == \"uint8\" else 127"
1444
+ },
1445
+ "intermediates": [{ "id": "accum", "dtype": "int32", "shape": "shapes.y" }],
1446
+ "passes": [
1447
+ {
1448
+ "id": "accumulate",
1449
+ "name": "QLinearConv.Accumulate1d",
1450
+ "source": { "shader": "conv-int-accumulate-spatial.wgsl.jinja", "inputs": { "spatialRank": 2 } },
1451
+ "bindings": "accumulateNcw",
1452
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1453
+ },
1454
+ {
1455
+ "id": "requantize",
1456
+ "name": "QLinearConv.Requantize",
1457
+ "shader": "qlinear-conv-requantize.wgsl.jinja",
1458
+ "bindings": "requantizeScalar",
1459
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1460
+ }
1461
+ ]
1462
+ },
1463
+ {
1464
+ "id": "ncdhw3d_scalar_quantization",
1465
+ "when": ["not present.B", "rank5TensorOk and dim(shapes.y, 2) == expectedOutputDepth and dim(shapes.y, 3) == expectedOutputHeight and dim(shapes.y, 4) == expectedOutputWidth", "scalarWeightQuantOk", "xyQuantContract", "groupChannelsOk", "outputBatchChannelsOk"],
1466
+ "constants": {
1467
+ "xScalar": "\"u32\" if tensorDtypes.x == \"uint8\" else \"i32\"",
1468
+ "wScalar": "\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\"",
1469
+ "yScalar": "\"u32\" if tensorDtypes.y == \"uint8\" else \"i32\"",
1470
+ "xUnsigned": "tensorDtypes.x == \"uint8\"",
1471
+ "wUnsigned": "tensorDtypes.w == \"uint8\"",
1472
+ "yUnsigned": "tensorDtypes.y == \"uint8\"",
1473
+ "hasBias": false,
1474
+ "qMin": "0 if tensorDtypes.y == \"uint8\" else 0 - 128",
1475
+ "qMax": "255 if tensorDtypes.y == \"uint8\" else 127"
1476
+ },
1477
+ "intermediates": [{ "id": "accum", "dtype": "int32", "shape": "shapes.y" }],
1478
+ "passes": [
1479
+ {
1480
+ "id": "accumulate",
1481
+ "name": "QLinearConv.Accumulate3d",
1482
+ "source": { "shader": "conv-int-accumulate-spatial.wgsl.jinja", "inputs": { "spatialRank": 3 } },
1483
+ "bindings": "accumulateNcdhw",
1484
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1485
+ },
1486
+ {
1487
+ "id": "requantize",
1488
+ "name": "QLinearConv.Requantize",
1489
+ "shader": "qlinear-conv-requantize.wgsl.jinja",
1490
+ "bindings": "requantizeScalar",
1491
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1492
+ }
1493
+ ]
1494
+ },
1495
+ {
1496
+ "id": "nchw2d_scalar_quantization",
1497
+ "when": ["not present.B", "rank4TensorOk and output2dShapeOk", "scalarWeightQuantOk", "xyQuantContract", "groupChannelsOk", "outputBatchChannelsOk"],
1498
+ "constants": {
1499
+ "xScalar": "\"u32\" if tensorDtypes.x == \"uint8\" else \"i32\"",
1500
+ "wScalar": "\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\"",
1501
+ "yScalar": "\"u32\" if tensorDtypes.y == \"uint8\" else \"i32\"",
1502
+ "xUnsigned": "tensorDtypes.x == \"uint8\"",
1503
+ "wUnsigned": "tensorDtypes.w == \"uint8\"",
1504
+ "yUnsigned": "tensorDtypes.y == \"uint8\"",
1505
+ "hasBias": false,
1506
+ "qMin": "0 if tensorDtypes.y == \"uint8\" else 0 - 128",
1507
+ "qMax": "255 if tensorDtypes.y == \"uint8\" else 127"
1508
+ },
1509
+ "intermediates": [{ "id": "accum", "dtype": "int32", "shape": "shapes.y" }],
1510
+ "passes": [
1511
+ {
1512
+ "id": "accumulate",
1513
+ "name": "QLinearConv.Accumulate",
1514
+ "source": { "shader": "conv-int-accumulate-spatial.wgsl.jinja", "inputs": { "spatialRank": 2 } },
1515
+ "bindings": "accumulateNchw",
1516
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1517
+ },
1518
+ {
1519
+ "id": "requantize",
1520
+ "name": "QLinearConv.Requantize",
1521
+ "shader": "qlinear-conv-requantize.wgsl.jinja",
1522
+ "bindings": "requantizeScalar",
1523
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1524
+ }
1525
+ ]
1526
+ },
1527
+ {
1528
+ "id": "dp4a_im2col_ncdhw3d_scalar_quantization",
1529
+ "priority": 20,
1530
+ "when": ["packedFeature", "not present.B", "rank5TensorOk", "attrs.group == 1", "dim(shapes.w, 1) == dim(shapes.x, 1)", "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) * dim(shapes.w, 4) % 4 == 0", "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) * dim(shapes.w, 4) >= 32", "dim(shapes.w, 0) >= 8", "dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.y, 4) >= 256", "flexibleWeightQuantOk", "xyQuantContract", "outputBatchChannelsOk", "dim(shapes.y, 2) == expectedOutputDepth", "dim(shapes.y, 3) == expectedOutputHeight", "dim(shapes.y, 4) == expectedOutputWidth", "im2colStorageOk", "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) * dim(shapes.w, 4) <= device.limits.maxComputeWorkgroupsPerDimension", "ceilDiv(dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.y, 4), tunables.WORKGROUP_SIZE) <= device.limits.maxComputeWorkgroupsPerDimension", "ceilDiv(dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.y, 4), 64) <= device.limits.maxComputeWorkgroupsPerDimension", "ceilDiv(dim(shapes.w, 0), 8) <= device.limits.maxComputeWorkgroupsPerDimension", "dim(shapes.x, 0) <= device.limits.maxComputeWorkgroupsPerDimension"],
1531
+ "constants": {
1532
+ "aScalar": "\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\"",
1533
+ "bScalar": "\"u32\" if tensorDtypes.x == \"uint8\" else \"i32\"",
1534
+ "aVec4": "\"vec4<u32>\" if tensorDtypes.w == \"uint8\" else \"vec4<i32>\"",
1535
+ "yScalar": "\"u32\" if tensorDtypes.y == \"uint8\" else \"i32\"",
1536
+ "aUnsigned": "tensorDtypes.w == \"uint8\"",
1537
+ "bUnsigned": "tensorDtypes.x == \"uint8\"",
1538
+ "yUnsigned": "tensorDtypes.y == \"uint8\"",
1539
+ "hasAZero": true,
1540
+ "hasBZero": true,
1541
+ "aPacked": true,
1542
+ "aZeroPerRow": "ranks.w_zero_point == 1 and dim(shapes.w_zero_point, 0) == dim(shapes.w, 0)",
1543
+ "bZeroPerColumn": false,
1544
+ "requant": "\"per_row\" if ranks.w_scale == 1 and dim(shapes.w_scale, 0) == dim(shapes.w, 0) else \"per_tensor\"",
1545
+ "scaleScalar": "\"f32\"",
1546
+ "qMin": "0 if tensorDtypes.y == \"uint8\" else 0 - 128",
1547
+ "qMax": "255 if tensorDtypes.y == \"uint8\" else 127",
1548
+ "tileM": 8,
1549
+ "tileN": 64,
1550
+ "ktWords": 32,
1551
+ "hasBias": false
1552
+ },
1553
+ "intermediates": [
1554
+ {
1555
+ "id": "cols3d",
1556
+ "dtype": "int32",
1557
+ "shape": "[dim(shapes.x, 0) * dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) * dim(shapes.w, 4) * dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.y, 4)]"
1558
+ }
1559
+ ],
1560
+ "passes": [
1561
+ {
1562
+ "id": "im2col",
1563
+ "name": "QLinearConv.Im2Col3d",
1564
+ "source": {
1565
+ "shader": "conv-int-im2col-spatial.wgsl.jinja",
1566
+ "inputs": {
1567
+ "spatialRank": 3,
1568
+ "kernelD": "dim(shapes.w, 2)",
1569
+ "kernelH": "dim(shapes.w, 3)",
1570
+ "kernelW": "dim(shapes.w, 4)",
1571
+ "strideD": "strideD",
1572
+ "strideH": "strideH",
1573
+ "strideW": "strideW",
1574
+ "dilationD": "dilationD",
1575
+ "dilationH": "dilationH",
1576
+ "dilationW": "dilationW",
1577
+ "padFront": "effectivePadFront",
1578
+ "padTop": "effectivePadTop",
1579
+ "padLeft": "effectivePadLeft"
1580
+ }
1581
+ },
1582
+ "bindings": "im2colNcdhw",
1583
+ "dispatch": {
1584
+ "x": "ceil(dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.y, 4) / tunables.WORKGROUP_SIZE)",
1585
+ "y": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) * dim(shapes.w, 4)",
1586
+ "z": "dim(shapes.x, 0)"
1587
+ }
1588
+ },
1589
+ {
1590
+ "id": "main",
1591
+ "name": "QLinearConv.Im2Col3dDp4a",
1592
+ "shader": "quant-dp4a-matmul.wgsl.jinja",
1593
+ "bindings": "dp4aMain3d",
1594
+ "dispatch": {
1595
+ "x": "ceil(dim(shapes.y, 2) * dim(shapes.y, 3) * dim(shapes.y, 4) / 64)",
1596
+ "y": "ceil(dim(shapes.w, 0) / 8)",
1597
+ "z": "dim(shapes.y, 0)"
1598
+ }
1599
+ }
1600
+ ]
1601
+ },
1602
+ {
1603
+ "id": "dp4a_im2col_nchw_bias_quantization",
1604
+ "priority": 25,
1605
+ "when": ["packedFeature", "present.B", "ranks.B == 1", "dim(shapes.B, 0) == dim(shapes.w, 0)", "rank4TensorOk", "attrs.group == 1", "dim(shapes.w, 1) == dim(shapes.x, 1)", "outputBatchChannelsOk", "flexibleWeightQuantOk", "xyQuantContract"],
1606
+ "constants": {
1607
+ "aScalar": "\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\"",
1608
+ "bScalar": "\"u32\" if tensorDtypes.x == \"uint8\" else \"i32\"",
1609
+ "aVec4": "(\"vec4<u32>\" if tensorDtypes.w == \"uint8\" else \"vec4<i32>\") if dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) % 4 == 0 else (\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\")",
1610
+ "yScalar": "\"u32\" if tensorDtypes.y == \"uint8\" else \"i32\"",
1611
+ "aUnsigned": "tensorDtypes.w == \"uint8\"",
1612
+ "bUnsigned": "tensorDtypes.x == \"uint8\"",
1613
+ "yUnsigned": "tensorDtypes.y == \"uint8\"",
1614
+ "hasAZero": true,
1615
+ "hasBZero": true,
1616
+ "aPacked": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) % 4 == 0",
1617
+ "aZeroPerRow": "ranks.w_zero_point == 1 and dim(shapes.w_zero_point, 0) == dim(shapes.w, 0)",
1618
+ "bZeroPerColumn": false,
1619
+ "requant": "\"per_row\" if ranks.w_scale == 1 and dim(shapes.w_scale, 0) == dim(shapes.w, 0) else \"per_tensor\"",
1620
+ "scaleScalar": "\"f32\"",
1621
+ "qMin": "0 if tensorDtypes.y == \"uint8\" else 0 - 128",
1622
+ "qMax": "255 if tensorDtypes.y == \"uint8\" else 127",
1623
+ "tileM": 8,
1624
+ "tileN": 64,
1625
+ "ktWords": 32,
1626
+ "hasBias": true
1627
+ },
1628
+ "intermediates": [
1629
+ {
1630
+ "id": "cols",
1631
+ "dtype": "int32",
1632
+ "shape": "[dim(shapes.x, 0) * (dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)) * (dim(shapes.y, 2) * dim(shapes.y, 3))]"
1633
+ }
1634
+ ],
1635
+ "passes": [
1636
+ {
1637
+ "id": "im2col",
1638
+ "name": "QLinearConv.Im2Col",
1639
+ "source": {
1640
+ "shader": "conv-int-im2col-spatial.wgsl.jinja",
1641
+ "inputs": {
1642
+ "spatialRank": 2,
1643
+ "kernelH": "dim(shapes.w, 2)",
1644
+ "kernelW": "dim(shapes.w, 3)",
1645
+ "strideH": "strideH",
1646
+ "strideW": "strideW",
1647
+ "dilationH": "dilationH",
1648
+ "dilationW": "dilationW",
1649
+ "padTop": "effectivePadTop",
1650
+ "padLeft": "effectivePadLeft"
1651
+ }
1652
+ },
1653
+ "bindings": "im2colNchw",
1654
+ "dispatch": {
1655
+ "x": "ceil(dim(shapes.y, 2) * dim(shapes.y, 3) / tunables.WORKGROUP_SIZE)",
1656
+ "y": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
1657
+ "z": "dim(shapes.x, 0)"
1658
+ }
1659
+ },
1660
+ {
1661
+ "id": "main",
1662
+ "name": "QLinearConv.Im2ColDp4aBias",
1663
+ "shader": "quant-dp4a-matmul.wgsl.jinja",
1664
+ "bindings": "dp4aMainBias",
1665
+ "dispatch": {
1666
+ "x": "ceil(dim(shapes.y, 2) * dim(shapes.y, 3) / 64)",
1667
+ "y": "ceil(dim(shapes.w, 0) / 8)",
1668
+ "z": "dim(shapes.y, 0)"
1669
+ }
1670
+ }
1671
+ ]
1672
+ },
1673
+ {
1674
+ "id": "dp4a_im2col_nchw_scalar_quantization",
1675
+ "priority": 20,
1676
+ "when": ["packedFeature", "not present.B", "flexibleWeightQuantOk", "xyQuantContract", "rank4TensorOk", "attrs.group == 1", "(device.features.has(\"shader-f16\") or dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) % 4 == 0)", "(dim(shapes.w, 2) > 1 or dim(shapes.w, 3) > 1)", "dim(shapes.w, 1) == dim(shapes.x, 1)", "outputBatchChannelsOk", "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) >= 64", "numel(shapes.y) >= 8192", "dp4a2dDispatchOk"],
1677
+ "constants": {
1678
+ "aScalar": "\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\"",
1679
+ "bScalar": "\"u32\" if tensorDtypes.x == \"uint8\" else \"i32\"",
1680
+ "aVec4": "(\"vec4<u32>\" if tensorDtypes.w == \"uint8\" else \"vec4<i32>\") if dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) % 4 == 0 else (\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\")",
1681
+ "yScalar": "\"u32\" if tensorDtypes.y == \"uint8\" else \"i32\"",
1682
+ "aUnsigned": "tensorDtypes.w == \"uint8\"",
1683
+ "bUnsigned": "tensorDtypes.x == \"uint8\"",
1684
+ "yUnsigned": "tensorDtypes.y == \"uint8\"",
1685
+ "hasAZero": true,
1686
+ "hasBZero": true,
1687
+ "aPacked": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3) % 4 == 0",
1688
+ "aZeroPerRow": "ranks.w_zero_point == 1 and dim(shapes.w_zero_point, 0) == dim(shapes.w, 0)",
1689
+ "bZeroPerColumn": false,
1690
+ "requant": "\"per_row\" if ranks.w_scale == 1 and dim(shapes.w_scale, 0) == dim(shapes.w, 0) else \"per_tensor\"",
1691
+ "scaleScalar": "\"f32\"",
1692
+ "qMin": "0 if tensorDtypes.y == \"uint8\" else 0 - 128",
1693
+ "qMax": "255 if tensorDtypes.y == \"uint8\" else 127",
1694
+ "tileM": 8,
1695
+ "tileN": 64,
1696
+ "ktWords": 32
1697
+ },
1698
+ "intermediates": [
1699
+ {
1700
+ "id": "cols",
1701
+ "dtype": "int32",
1702
+ "shape": "[dim(shapes.x, 0) * (dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)) * (dim(shapes.y, 2) * dim(shapes.y, 3))]"
1703
+ }
1704
+ ],
1705
+ "passes": [
1706
+ {
1707
+ "id": "im2col",
1708
+ "name": "QLinearConv.Im2Col",
1709
+ "source": {
1710
+ "shader": "conv-int-im2col-spatial.wgsl.jinja",
1711
+ "inputs": {
1712
+ "spatialRank": 2,
1713
+ "kernelH": "dim(shapes.w, 2)",
1714
+ "kernelW": "dim(shapes.w, 3)",
1715
+ "strideH": "strideH",
1716
+ "strideW": "strideW",
1717
+ "dilationH": "dilationH",
1718
+ "dilationW": "dilationW",
1719
+ "padTop": "effectivePadTop",
1720
+ "padLeft": "effectivePadLeft"
1721
+ }
1722
+ },
1723
+ "bindings": "im2colNchw",
1724
+ "dispatch": {
1725
+ "x": "ceil(dim(shapes.y, 2) * dim(shapes.y, 3) / tunables.WORKGROUP_SIZE)",
1726
+ "y": "dim(shapes.w, 1) * dim(shapes.w, 2) * dim(shapes.w, 3)",
1727
+ "z": "dim(shapes.x, 0)"
1728
+ }
1729
+ },
1730
+ {
1731
+ "id": "main",
1732
+ "name": "QLinearConv.Im2ColDp4a",
1733
+ "shader": "quant-dp4a-matmul.wgsl.jinja",
1734
+ "bindings": "dp4aMain",
1735
+ "dispatch": {
1736
+ "x": "ceil(dim(shapes.y, 2) * dim(shapes.y, 3) / 64)",
1737
+ "y": "ceil(dim(shapes.w, 0) / 8)",
1738
+ "z": "dim(shapes.y, 0)"
1739
+ }
1740
+ }
1741
+ ]
1742
+ },
1743
+ {
1744
+ "id": "nchw2d_per_channel_quantization",
1745
+ "priority": 5,
1746
+ "when": ["not present.B", "rank4TensorOk", "ranks.w_scale == 1", "dim(shapes.w_scale, 0) == dim(shapes.w, 0)", "ranks.w_zero_point == 1", "dim(shapes.w_zero_point, 0) == dim(shapes.w, 0)", "xyQuantContract", "groupChannelsOk", "outputBatchChannelsOk", "output2dShapeOk"],
1747
+ "constants": {
1748
+ "xScalar": "\"u32\" if tensorDtypes.x == \"uint8\" else \"i32\"",
1749
+ "wScalar": "\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\"",
1750
+ "yScalar": "\"u32\" if tensorDtypes.y == \"uint8\" else \"i32\"",
1751
+ "xUnsigned": "tensorDtypes.x == \"uint8\"",
1752
+ "wUnsigned": "tensorDtypes.w == \"uint8\"",
1753
+ "yUnsigned": "tensorDtypes.y == \"uint8\"",
1754
+ "hasBias": false,
1755
+ "wScalePerChannel": true,
1756
+ "wZeroPerChannel": true,
1757
+ "qMin": "0 if tensorDtypes.y == \"uint8\" else 0 - 128",
1758
+ "qMax": "255 if tensorDtypes.y == \"uint8\" else 127"
1759
+ },
1760
+ "intermediates": [{ "id": "accum", "dtype": "int32", "shape": "shapes.y" }],
1761
+ "passes": [
1762
+ {
1763
+ "id": "accumulate",
1764
+ "name": "QLinearConv.AccumulatePerChannel",
1765
+ "source": { "shader": "conv-int-accumulate-spatial.wgsl.jinja", "inputs": { "spatialRank": 2 } },
1766
+ "bindings": "accumulateNchw",
1767
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1768
+ },
1769
+ {
1770
+ "id": "requantize",
1771
+ "name": "QLinearConv.RequantizePerChannel",
1772
+ "shader": "qlinear-conv-requantize.wgsl.jinja",
1773
+ "bindings": "requantizePerChannel",
1774
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1775
+ }
1776
+ ]
1777
+ },
1778
+ {
1779
+ "id": "nchw2d_per_channel_quantization_bias",
1780
+ "when": ["present.B", "ranks.B == 1", "dim(shapes.B, 0) == dim(shapes.w, 0)", "rank4TensorOk", "ranks.w_scale == 1", "dim(shapes.w_scale, 0) == dim(shapes.w, 0)", "ranks.w_zero_point == 1", "dim(shapes.w_zero_point, 0) == dim(shapes.w, 0)", "xyQuantContract", "groupChannelsOk", "outputBatchChannelsOk", "output2dShapeOk"],
1781
+ "constants": {
1782
+ "xScalar": "\"u32\" if tensorDtypes.x == \"uint8\" else \"i32\"",
1783
+ "wScalar": "\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\"",
1784
+ "yScalar": "\"u32\" if tensorDtypes.y == \"uint8\" else \"i32\"",
1785
+ "xUnsigned": "tensorDtypes.x == \"uint8\"",
1786
+ "wUnsigned": "tensorDtypes.w == \"uint8\"",
1787
+ "yUnsigned": "tensorDtypes.y == \"uint8\"",
1788
+ "hasBias": true,
1789
+ "wScalePerChannel": true,
1790
+ "wZeroPerChannel": true,
1791
+ "qMin": "0 if tensorDtypes.y == \"uint8\" else 0 - 128",
1792
+ "qMax": "255 if tensorDtypes.y == \"uint8\" else 127"
1793
+ },
1794
+ "intermediates": [{ "id": "accum", "dtype": "int32", "shape": "shapes.y" }],
1795
+ "passes": [
1796
+ {
1797
+ "id": "accumulate",
1798
+ "name": "QLinearConv.AccumulatePerChannel",
1799
+ "source": { "shader": "conv-int-accumulate-spatial.wgsl.jinja", "inputs": { "spatialRank": 2 } },
1800
+ "bindings": "accumulateNchw",
1801
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1802
+ },
1803
+ {
1804
+ "id": "requantize",
1805
+ "name": "QLinearConv.RequantizePerChannelBias",
1806
+ "shader": "qlinear-conv-requantize.wgsl.jinja",
1807
+ "bindings": "requantizeBias",
1808
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1809
+ }
1810
+ ]
1811
+ },
1812
+ {
1813
+ "id": "nchw2d_scalar_quantization_bias",
1814
+ "when": ["present.B", "ranks.B == 1", "dim(shapes.B, 0) == dim(shapes.w, 0)", "rank4TensorOk", "scalarWeightQuantOk", "xyQuantContract", "groupChannelsOk", "outputBatchChannelsOk", "output2dShapeOk"],
1815
+ "constants": {
1816
+ "xScalar": "\"u32\" if tensorDtypes.x == \"uint8\" else \"i32\"",
1817
+ "wScalar": "\"u32\" if tensorDtypes.w == \"uint8\" else \"i32\"",
1818
+ "yScalar": "\"u32\" if tensorDtypes.y == \"uint8\" else \"i32\"",
1819
+ "xUnsigned": "tensorDtypes.x == \"uint8\"",
1820
+ "wUnsigned": "tensorDtypes.w == \"uint8\"",
1821
+ "yUnsigned": "tensorDtypes.y == \"uint8\"",
1822
+ "hasBias": true,
1823
+ "qMin": "0 if tensorDtypes.y == \"uint8\" else 0 - 128",
1824
+ "qMax": "255 if tensorDtypes.y == \"uint8\" else 127"
1825
+ },
1826
+ "intermediates": [{ "id": "accum", "dtype": "int32", "shape": "shapes.y" }],
1827
+ "passes": [
1828
+ {
1829
+ "id": "accumulate",
1830
+ "name": "QLinearConv.Accumulate",
1831
+ "source": { "shader": "conv-int-accumulate-spatial.wgsl.jinja", "inputs": { "spatialRank": 2 } },
1832
+ "bindings": "accumulateNchw",
1833
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1834
+ },
1835
+ {
1836
+ "id": "requantize",
1837
+ "name": "QLinearConv.RequantizeBias",
1838
+ "shader": "qlinear-conv-requantize.wgsl.jinja",
1839
+ "bindings": "requantizeBias",
1840
+ "dispatch": { "threads": "numel(shapes.y)", "workgroupSize": "tunables.WORKGROUP_SIZE" }
1841
+ }
1842
+ ]
1843
+ }
1844
+ ]
1845
+ }
build/webgpu/metadata.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ai.onnx.QLinearConv",
3
+ "id": "_ai_onnx_qlinearconv_webgpu_97f4388",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "backend": { "type": "webgpu" },
7
+ "digest": {
8
+ "algorithm": "sha256",
9
+ "files": {
10
+ "bench.json": "7QcaEvhkU7vodLHPSkDnPEs+l3fyHE5M7iPreEs8EBs=",
11
+ "conv-int-accumulate-spatial.wgsl.jinja": "+RLzIvFAtuFaDFjixU0amSlvCZ4sOlphzPpkf+7Klis=",
12
+ "conv-int-im2col-spatial.wgsl.jinja": "fdcyh0E6YDzmlp/uWUnejeervSfidZul2GNQBrZUG+4=",
13
+ "manifest.json": "GD1vHqnoBAsn2BJePTNWNRgtBJUcvPp6xTHgX0xEsLM=",
14
+ "qlinear-conv-dp4a-direct-3x3.wgsl.jinja": "irlapLZkfGOdQZgLgddD34Djo8z+RSVCqUj0OrBH3Yw=",
15
+ "qlinear-conv-nchw-x4.wgsl.jinja": "UZfNJ4L5YVesOPAv/9ugYz8tJOhujdafH/BtcnPjrKQ=",
16
+ "qlinear-conv-requantize.wgsl.jinja": "w4e3cepxUD19iM8XgzpXUN7dyHCzzrxfrWjzrOaPps4=",
17
+ "quant-dp4a-matmul.wgsl.jinja": "yzJ3cN0cvihhQ8P3joswjg12M0p4ZrLQgfxXq9epORo=",
18
+ "quant-pad-k-matrices.wgsl.jinja": "UNPxeSYR99nYYGs6qmsHnO3/uHcDkdQB4jBZwZw2zmw=",
19
+ "test.json": "Q1nMc5F1AYDSVoAvj5w8rpbPrOw+6hZhLvwo5wV9yJ4="
20
+ }
21
+ },
22
+ "provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
23
+ "webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.QLinearConv" }
24
+ }
build/webgpu/qlinear-conv-dp4a-direct-3x3.wgsl.jinja ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Direct tiled u8 x s8 3x3 convolution using packed 4-lane integer dots.
2
+ //
3
+ // An 8x8 output workgroup cooperatively stages a 10x10 halo for sixteen input
4
+ // channels. Four channels are packed into each shared u32, so every staged
5
+ // input word is reused by all output-channel accumulators and neighboring
6
+ // output pixels. Weights are packed in the same channel groups, and the
7
+ // convolution operates directly without a materialized im2col tensor.
8
+ {{ env.wgsl.resourceDeclarations }}
9
+ // Centralize the signed packed-int8 dot expression so unrolled callers cannot
10
+ // collide in lowering-generated temporaries.
11
+ fn dot4_packed(a_word: u32, b_word: u32) -> i32 {
12
+ return dot4I8Packed(a_word, b_word);
13
+ }
14
+
15
+
16
+ const IN_C: u32 = {{ source.inChannels }}u;
17
+ const IN_H: u32 = {{ source.inHeight }}u;
18
+ const IN_W: u32 = {{ source.inWidth }}u;
19
+ const OUT_C: u32 = {{ source.outChannels }}u;
20
+ const OUT_H: u32 = {{ source.outHeight }}u;
21
+ const OUT_W: u32 = {{ source.outWidth }}u;
22
+ const BATCH: u32 = {{ source.batch }}u;
23
+
24
+ const SPATIAL_TILE: u32 = {{ source.spatialTile }}u;
25
+ const OC_TILE: u32 = {{ source.ocTile }}u;
26
+ const IC_TILE: u32 = {{ source.icTile }}u;
27
+ const IC_WORDS: u32 = IC_TILE / 4u;
28
+ const PATCH_EDGE: u32 = SPATIAL_TILE + 2u;
29
+ const PATCH_AREA: u32 = PATCH_EDGE * PATCH_EDGE;
30
+ const X_TILE_WORDS: u32 = IC_WORDS * PATCH_AREA;
31
+ const W_TILE_WORDS: u32 = OC_TILE * IC_WORDS * 9u;
32
+ const WG_SIZE: u32 = SPATIAL_TILE * SPATIAL_TILE;
33
+ const OUT_TILE_ROWS: u32 = (OUT_H + SPATIAL_TILE - 1u) / SPATIAL_TILE;
34
+ const K: i32 = i32(IN_C * 9u);
35
+
36
+ var<workgroup> xTile: array<u32, X_TILE_WORDS>;
37
+ var<workgroup> wTile: array<u32, W_TILE_WORDS>;
38
+ var<workgroup> wSums: array<atomic<i32>, OC_TILE>;
39
+
40
+ fn pack_x(v: vec4<u32>) -> u32 {
41
+ return ((v.x ^ 0x80u) & 0xFFu)
42
+ | (((v.y ^ 0x80u) & 0xFFu) << 8u)
43
+ | (((v.z ^ 0x80u) & 0xFFu) << 16u)
44
+ | (((v.w ^ 0x80u) & 0xFFu) << 24u);
45
+ }
46
+
47
+ fn pack_w(v: vec4<i32>) -> u32 {
48
+ return (u32(v.x) & 0xFFu)
49
+ | ((u32(v.y) & 0xFFu) << 8u)
50
+ | ((u32(v.z) & 0xFFu) << 16u)
51
+ | ((u32(v.w) & 0xFFu) << 24u);
52
+ }
53
+
54
+ fn signed_byte(v: u32, shift: u32) -> i32 {
55
+ let b = i32((v >> shift) & 0xFFu);
56
+ return select(b, b - 256, b >= 128);
57
+ }
58
+
59
+ fn sum4_packed(v: u32) -> i32 {
60
+ return signed_byte(v, 0u) + signed_byte(v, 8u)
61
+ + signed_byte(v, 16u) + signed_byte(v, 24u);
62
+ }
63
+
64
+ fn read_w_zero({% if source.wZeroPerChannel %}oc: u32{% endif %}) -> i32 {
65
+ {% if source.wZeroPerChannel %}
66
+ return w_zero_point[oc];
67
+ {% else %}
68
+ return w_zero_point[0];
69
+ {% endif %}
70
+ }
71
+
72
+ fn read_w_scale({% if source.wScalePerChannel %}oc: u32{% endif %}) -> f32 {
73
+ {% if source.wScalePerChannel %}
74
+ return w_scale[oc];
75
+ {% else %}
76
+ return w_scale[0];
77
+ {% endif %}
78
+ }
79
+
80
+ fn requantize(acc: i32{% if source.wScalePerChannel %}, oc: u32{% endif %}) -> u32 {
81
+ let scaled = f32(acc) * x_scale[0] * read_w_scale({% if source.wScalePerChannel %}oc{% endif %}) / y_scale[0];
82
+ let q = clamp(
83
+ i32(clamp(round(scaled), -2.0e9, 2.0e9)) + i32(y_zero_point[0]),
84
+ 0,
85
+ 255,
86
+ );
87
+ return u32(q);
88
+ }
89
+
90
+ @compute @workgroup_size({{ source.spatialTile }}, {{ source.spatialTile }}, 1)
91
+ fn main(
92
+ @builtin(workgroup_id) wg: vec3<u32>,
93
+ @builtin(local_invocation_id) lid: vec3<u32>
94
+ ) {
95
+ let lane = lid.y * SPATIAL_TILE + lid.x;
96
+ let tileY = wg.y % OUT_TILE_ROWS;
97
+ let ocBlock = wg.y / OUT_TILE_ROWS;
98
+ let ocBase = ocBlock * OC_TILE;
99
+ let oh = tileY * SPATIAL_TILE + lid.y;
100
+ let ow = wg.x * SPATIAL_TILE + lid.x;
101
+ let outputValid = wg.z < BATCH && oh < OUT_H && ow < OUT_W;
102
+ let xzp = x_zero_point[0];
103
+ let cb = 128 - i32(xzp);
104
+
105
+ if (lane < OC_TILE) {
106
+ atomicStore(&wSums[lane], 0);
107
+ }
108
+ workgroupBarrier();
109
+
110
+ {% for oc in range(source.ocTile) %}
111
+ var acc{{ oc }} = 0i;
112
+ {% endfor %}
113
+ var sumX = 0i;
114
+
115
+ for (var icBase = 0u; icBase < IN_C; icBase += IC_TILE) {
116
+ // Pack four NCHW channels for each halo position. Padding uses the input
117
+ // zero point, so the zero-point correction below cancels it exactly.
118
+ for (var item = lane; item < X_TILE_WORDS; item += WG_SIZE) {
119
+ let channelWord = item / PATCH_AREA;
120
+ let patchIndex = item % PATCH_AREA;
121
+ let py = patchIndex / PATCH_EDGE;
122
+ let px = patchIndex % PATCH_EDGE;
123
+ let iy = i32(tileY * SPATIAL_TILE + py) - 1;
124
+ let ix = i32(wg.x * SPATIAL_TILE + px) - 1;
125
+ var xv = vec4<u32>(xzp);
126
+ if (iy >= 0 && iy < i32(IN_H) && ix >= 0 && ix < i32(IN_W)) {
127
+ let spatial = u32(iy) * IN_W + u32(ix);
128
+ let ic0 = icBase + channelWord * 4u;
129
+ let batchBase = wg.z * IN_C * IN_H * IN_W;
130
+ xv = vec4<u32>(
131
+ x[batchBase + (ic0 + 0u) * IN_H * IN_W + spatial],
132
+ x[batchBase + (ic0 + 1u) * IN_H * IN_W + spatial],
133
+ x[batchBase + (ic0 + 2u) * IN_H * IN_W + spatial],
134
+ x[batchBase + (ic0 + 3u) * IN_H * IN_W + spatial],
135
+ );
136
+ }
137
+ xTile[item] = pack_x(xv);
138
+ }
139
+
140
+ // Pack the same four-channel groups for every output channel and tap.
141
+ for (var item = lane; item < W_TILE_WORDS; item += WG_SIZE) {
142
+ let tap = item % 9u;
143
+ let channelWord = (item / 9u) % IC_WORDS;
144
+ let localOc = item / (9u * IC_WORDS);
145
+ let oc = ocBase + localOc;
146
+ var wv = vec4<i32>(0);
147
+ if (oc < OUT_C) {
148
+ let kh = tap / 3u;
149
+ let kw = tap % 3u;
150
+ let ic0 = icBase + channelWord * 4u;
151
+ wv = vec4<i32>(
152
+ w[((oc * IN_C + ic0 + 0u) * 3u + kh) * 3u + kw],
153
+ w[((oc * IN_C + ic0 + 1u) * 3u + kh) * 3u + kw],
154
+ w[((oc * IN_C + ic0 + 2u) * 3u + kh) * 3u + kw],
155
+ w[((oc * IN_C + ic0 + 3u) * 3u + kh) * 3u + kw],
156
+ );
157
+ atomicAdd(&wSums[localOc], wv.x + wv.y + wv.z + wv.w);
158
+ }
159
+ wTile[item] = pack_w(wv);
160
+ }
161
+ workgroupBarrier();
162
+
163
+ if (outputValid) {
164
+ for (var channelWord = 0u; channelWord < IC_WORDS; channelWord++) {
165
+ for (var kh = 0u; kh < 3u; kh++) {
166
+ for (var kw = 0u; kw < 3u; kw++) {
167
+ let tap = kh * 3u + kw;
168
+ let xp = xTile[channelWord * PATCH_AREA + (lid.y + kh) * PATCH_EDGE + lid.x + kw];
169
+ sumX += sum4_packed(xp);
170
+ {% for oc in range(source.ocTile) %}
171
+ acc{{ oc }} += dot4_packed(wTile[({{ oc }}u * IC_WORDS + channelWord) * 9u + tap], xp);
172
+ {% endfor %}
173
+ }
174
+ }
175
+ }
176
+ }
177
+ workgroupBarrier();
178
+ }
179
+
180
+ if (!outputValid) {
181
+ return;
182
+ }
183
+
184
+ {% for oc in range(source.ocTile) %}
185
+ {
186
+ let outOc = ocBase + {{ oc }}u;
187
+ if (outOc < OUT_C) {
188
+ let ca = -read_w_zero({% if source.wZeroPerChannel %}outOc{% endif %});
189
+ let corrected = acc{{ oc }}
190
+ + cb * atomicLoad(&wSums[{{ oc }}u])
191
+ + ca * sumX
192
+ + K * ca * cb;
193
+ let outIndex = ((wg.z * OUT_C + outOc) * OUT_H + oh) * OUT_W + ow;
194
+ y[outIndex] = requantize(corrected{% if source.wScalePerChannel %}, outOc{% endif %});
195
+ }
196
+ }
197
+ {% endfor %}
198
+ }
build/webgpu/qlinear-conv-nchw-x4.wgsl.jinja ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ {{ env.wgsl.resourceDeclarations }}
35
+
36
+ fn x_zero() -> i32 {
37
+ {% if xUnsigned %}
38
+ return i32(x_zero_point[0]);
39
+ {% else %}
40
+ return x_zero_point[0];
41
+ {% endif %}
42
+ }
43
+
44
+ fn w_zero({% if wZeroPerChannel %}oc: u32{% endif %}) -> i32 {
45
+ {% if wZeroPerChannel %}
46
+ let i = oc;
47
+ {% else %}
48
+ let i = 0u;
49
+ {% endif %}
50
+ {% if wUnsigned %}
51
+ return i32(w_zero_point[i]);
52
+ {% else %}
53
+ return w_zero_point[i];
54
+ {% endif %}
55
+ }
56
+
57
+ fn centered_x(i: u32, zp: i32) -> i32 {
58
+ {% if xUnsigned %}
59
+ return i32(x[i]) - zp;
60
+ {% else %}
61
+ return x[i] - zp;
62
+ {% endif %}
63
+ }
64
+
65
+ fn centered_w(i: u32, zp: i32) -> i32 {
66
+ {% if wUnsigned %}
67
+ return i32(w[i]) - zp;
68
+ {% else %}
69
+ return w[i] - zp;
70
+ {% endif %}
71
+ }
72
+
73
+ fn y_zero() -> i32 {
74
+ {% if yUnsigned %}
75
+ return i32(y_zero_point[0]);
76
+ {% else %}
77
+ return y_zero_point[0];
78
+ {% endif %}
79
+ }
80
+
81
+ fn requant(acc: i32{% if wScalePerChannel %}, oc: u32{% endif %}) -> i32 {
82
+ {% if wScalePerChannel %}
83
+ let ws = w_scale[oc];
84
+ {% else %}
85
+ let ws = w_scale[0];
86
+ {% endif %}
87
+ let scaled = f32(acc) * x_scale[0] * ws / y_scale[0];
88
+ return clamp(i32(clamp(round(scaled), -2.0e9, 2.0e9)) + y_zero(), {{ qMin }}, {{ qMax }});
89
+ }
90
+
91
+ fn write_y(i: u32, value: i32) {
92
+ {% if yUnsigned %}
93
+ y[i] = u32(value);
94
+ {% else %}
95
+ y[i] = value;
96
+ {% endif %}
97
+ }
98
+
99
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
100
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
101
+ {{ flat_index_2d("flat", "", note="") }}
102
+ let groups_per_row = (params.outW + 3u) / 4u;
103
+ let oc_blocks = (params.outChannels + {{ ocTile - 1 }}u) / {{ ocTile }}u;
104
+ let group_count = params.batch * oc_blocks * params.outH * groups_per_row;
105
+ if (flat >= group_count) { return; }
106
+
107
+ let ow0 = (flat % groups_per_row) * 4u;
108
+ let row = flat / groups_per_row;
109
+ let oh = row % params.outH;
110
+ let oc0 = ((row / params.outH) % oc_blocks) * {{ ocTile }}u;
111
+ let n = row / (params.outH * oc_blocks);
112
+ let group = oc0 / params.outChannelsPerGroup;
113
+ let in_start = group * params.inChannelsPerGroup;
114
+ let xzp = x_zero();
115
+ {% for ot in range(ocTile) %}
116
+ var acc{{ ot }} = vec4<i32>(0);
117
+ {% endfor %}
118
+
119
+ for (var icg = 0u; icg < params.inChannelsPerGroup; icg = icg + 1u) {
120
+ let ic = in_start + icg;
121
+ for (var kh = 0u; kh < params.kernelH; kh = kh + 1u) {
122
+ let ih = i32(oh * params.strideH + kh * params.dilationH) - params.padH;
123
+ for (var kw = 0u; kw < params.kernelW; kw = kw + 1u) {
124
+ var x0 = 0i;
125
+ var x1 = 0i;
126
+ var x2 = 0i;
127
+ var x3 = 0i;
128
+ if (ih >= 0 && ih < i32(params.inH)) {
129
+ let iw0 = i32(ow0 * params.strideW + kw * params.dilationW) - params.padW;
130
+ let iw1 = i32((ow0 + 1u) * params.strideW + kw * params.dilationW) - params.padW;
131
+ let iw2 = i32((ow0 + 2u) * params.strideW + kw * params.dilationW) - params.padW;
132
+ let iw3 = i32((ow0 + 3u) * params.strideW + kw * params.dilationW) - params.padW;
133
+ let base = (n * params.inChannels + ic) * params.inH * params.inW + u32(ih) * params.inW;
134
+ if (ow0 < params.outW && iw0 >= 0 && iw0 < i32(params.inW)) { x0 = centered_x(base + u32(iw0), xzp); }
135
+ if (ow0 + 1u < params.outW && iw1 >= 0 && iw1 < i32(params.inW)) { x1 = centered_x(base + u32(iw1), xzp); }
136
+ if (ow0 + 2u < params.outW && iw2 >= 0 && iw2 < i32(params.inW)) { x2 = centered_x(base + u32(iw2), xzp); }
137
+ if (ow0 + 3u < params.outW && iw3 >= 0 && iw3 < i32(params.inW)) { x3 = centered_x(base + u32(iw3), xzp); }
138
+ }
139
+ let xv = vec4<i32>(x0, x1, x2, x3);
140
+ {% for ot in range(ocTile) %}
141
+ let oc{{ ot }} = oc0 + {{ ot }}u;
142
+ if (oc{{ ot }} < params.outChannels) {
143
+ let wi{{ ot }} = (((oc{{ ot }} * params.weightInChannels + icg) * params.kernelH + kh) * params.kernelW + kw);
144
+ let wc{{ ot }} = centered_w(wi{{ ot }}, w_zero({% if wZeroPerChannel %}oc{{ ot }}{% endif %}));
145
+ acc{{ ot }} = acc{{ ot }} + xv * vec4<i32>(wc{{ ot }});
146
+ }
147
+ {% endfor %}
148
+ }
149
+ }
150
+ }
151
+
152
+ {% for ot in range(ocTile) %}
153
+ let out_oc{{ ot }} = oc0 + {{ ot }}u;
154
+ if (out_oc{{ ot }} < params.outChannels) {
155
+ let out_base{{ ot }} = ((n * params.outChannels + out_oc{{ ot }}) * params.outH + oh) * params.outW + ow0;
156
+ write_y(out_base{{ ot }}, requant(acc{{ ot }}.x{% if wScalePerChannel %}, out_oc{{ ot }}{% endif %}));
157
+ if (ow0 + 1u < params.outW) { write_y(out_base{{ ot }} + 1u, requant(acc{{ ot }}.y{% if wScalePerChannel %}, out_oc{{ ot }}{% endif %})); }
158
+ if (ow0 + 2u < params.outW) { write_y(out_base{{ ot }} + 2u, requant(acc{{ ot }}.z{% if wScalePerChannel %}, out_oc{{ ot }}{% endif %})); }
159
+ if (ow0 + 3u < params.outW) { write_y(out_base{{ ot }} + 3u, requant(acc{{ ot }}.w{% if wScalePerChannel %}, out_oc{{ ot }}{% endif %})); }
160
+ }
161
+ {% endfor %}
162
+ }
build/webgpu/qlinear-conv-requantize.wgsl.jinja ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% macro flat_index_2d(name="i", bound="params.count", guardInline=false, note="dispatch-limit") %}
2
+ {% if note == "dispatch-limit" %}
3
+ // 2D-folded flat index: gid.y carries the high bits past the
4
+ // maxComputeWorkgroupsPerDimension dispatch limit (outputs > 16.7M elements).
5
+ {% elif note == "limit" %}
6
+ // 2D-folded flat index: gid.y carries the high bits past the
7
+ // maxComputeWorkgroupsPerDimension limit.
8
+ {% elif note == "device-axis" %}
9
+ // The flat dispatch is folded across x/y at the device's per-axis workgroup
10
+ // limit; gid.y carries the high portion of the output index.
11
+ {% elif note == "vec4-limit" %}
12
+ // 2D-folded flat vec4 index: gid.y carries the high bits past the
13
+ // maxComputeWorkgroupsPerDimension limit (the dispatch caps x and spills into y).
14
+ {% elif note == "element-limit" %}
15
+ // 2D-folded flat element index: gid.y carries the high bits past the
16
+ // maxComputeWorkgroupsPerDimension limit.
17
+ {% elif note == "dispatch" %}
18
+ // 2D-folded flat index: gid.y carries the high bits past the
19
+ // maxComputeWorkgroupsPerDimension dispatch limit.
20
+ {% endif %}
21
+ {% if bound == "" %}
22
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
23
+ {%- elif guardInline %}
24
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
25
+ if ({{ name }} >= {{ bound }}) { return; }
26
+ {%- else %}
27
+ let {{ name }} = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
28
+ if ({{ name }} >= {{ bound }}) {
29
+ return;
30
+ }
31
+ {%- endif %}
32
+ {% endmacro %}
33
+
34
+ {{ env.wgsl.resourceDeclarations }}
35
+
36
+ fn read_y_zero() -> i32 {
37
+ {% if yUnsigned %}
38
+ return i32(y_zero_point[0]);
39
+ {% else %}
40
+ return y_zero_point[0];
41
+ {% endif %}
42
+ }
43
+
44
+ fn write_y(index: u32, value: i32) {
45
+ {% if yUnsigned %}
46
+ y[index] = u32(value);
47
+ {% else %}
48
+ y[index] = value;
49
+ {% endif %}
50
+ }
51
+
52
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
53
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups) nwg: vec3<u32>) {
54
+ {{ flat_index_2d(guardInline=true) }}
55
+ {% if hasBias or wScalePerChannel %}
56
+ let oc = (i / params.outImageSize) % params.outChannels;
57
+ {% endif %}
58
+ {% if hasBias %}
59
+ // Optional int32 bias is per output channel, already in (x_scale * w_scale)
60
+ // units, so it is added to the int32 accumulator before requantization.
61
+ let raw = accum[i] + B[oc];
62
+ {% else %}
63
+ let raw = accum[i];
64
+ {% endif %}
65
+ {% if wScalePerChannel %}
66
+ let w_scale_value = w_scale[oc];
67
+ {% else %}
68
+ let w_scale_value = w_scale[0];
69
+ {% endif %}
70
+ let scaled = f32(raw) * x_scale[0] * w_scale_value / y_scale[0];
71
+ // Clamp the rounded float into an i32-representable range before the cast so a
72
+ // huge-finite / Inf requantized value saturates (the final clamp pins it to
73
+ // [qMin,qMax]) instead of invoking undefined behaviour in i32(huge_float).
74
+ let q = clamp(i32(clamp(round(scaled), -2.0e9, 2.0e9)) + read_y_zero(), {{ qMin }}, {{ qMax }});
75
+ write_y(i, q);
76
+ }
build/webgpu/quant-dp4a-matmul.wgsl.jinja ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% if requant != "none" and scaleScalar == "f16" %}
2
+ enable f16;
3
+ {% endif %}
4
+ {{ env.wgsl.resourceDeclarations }}
5
+
6
+ const TILE_M: u32 = {{ tileM }}u;
7
+ const TILE_N: u32 = {{ tileN }}u;
8
+ const KT_WORDS: u32 = {{ ktWords }}u;
9
+ const A_TILE_WORDS: u32 = TILE_M * KT_WORDS;
10
+ {% set GEMV_UNROLL = gemvKUnroll if gemvKUnroll is defined else 4 %}
11
+ {% if tileM == 1 %}
12
+ const GEMV_K_UNROLL: u32 = {{ GEMV_UNROLL }}u;
13
+ {% endif %}
14
+
15
+ {% set needsASum = hasBZero or bUnsigned %}
16
+ {% set needsBSum = hasAZero or aUnsigned %}
17
+ var<workgroup> a_tile: array<u32, A_TILE_WORDS>;
18
+ {% if needsASum %}
19
+ var<workgroup> a_row_sum: array<atomic<i32>, TILE_M>;
20
+
21
+ {% endif %}
22
+ // Pack 4 widened A values into one u32 (byte 0 = lowest k).
23
+ fn pack_a(v: vec4<{{ aScalar }}>) -> u32 {
24
+ {% if aUnsigned %}
25
+ // u8 -> biased i8: (u - 128) two's-complement low byte == u ^ 0x80.
26
+ return ((v.x ^ 0x80u) & 0xFFu) | (((v.y ^ 0x80u) & 0xFFu) << 8u) | (((v.z ^ 0x80u) & 0xFFu) << 16u) | (((v.w ^ 0x80u) & 0xFFu) << 24u);
27
+ {% else %}
28
+ return (u32(v.x) & 0xFFu) | ((u32(v.y) & 0xFFu) << 8u) | ((u32(v.z) & 0xFFu) << 16u) | ((u32(v.w) & 0xFFu) << 24u);
29
+ {% endif %}
30
+ }
31
+ {% if needsASum %}
32
+
33
+ // Sum of the 4 bias-shifted A values of one packed word.
34
+ fn sum4_a(v: vec4<{{ aScalar }}>) -> i32 {
35
+ {% if aUnsigned %}
36
+ return i32(v.x + v.y + v.z + v.w) - 512;
37
+ {% else %}
38
+ return v.x + v.y + v.z + v.w;
39
+ {% endif %}
40
+ }
41
+
42
+ {% endif %}
43
+ fn pack_b(b0: {{ bScalar }}, b1: {{ bScalar }}, b2: {{ bScalar }}, b3: {{ bScalar }}) -> u32 {
44
+ {% if bUnsigned %}
45
+ return ((b0 ^ 0x80u) & 0xFFu) | (((b1 ^ 0x80u) & 0xFFu) << 8u) | (((b2 ^ 0x80u) & 0xFFu) << 16u) | (((b3 ^ 0x80u) & 0xFFu) << 24u);
46
+ {% else %}
47
+ return (u32(b0) & 0xFFu) | ((u32(b1) & 0xFFu) << 8u) | ((u32(b2) & 0xFFu) << 16u) | ((u32(b3) & 0xFFu) << 24u);
48
+ {% endif %}
49
+ }
50
+
51
+ // Centralize the signed packed-int8 dot expression so unrolled callers cannot
52
+ // collide in lowering-generated temporaries.
53
+ fn dot4_packed(a_word: u32, b_word: u32) -> i32 {
54
+ return dot4I8Packed(a_word, b_word);
55
+ }
56
+
57
+ {% if needsBSum %}
58
+
59
+ fn sum4_b(b0: {{ bScalar }}, b1: {{ bScalar }}, b2: {{ bScalar }}, b3: {{ bScalar }}) -> i32 {
60
+ {% if bUnsigned %}
61
+ return i32(b0 + b1 + b2 + b3) - 512;
62
+ {% else %}
63
+ return b0 + b1 + b2 + b3;
64
+ {% endif %}
65
+ }
66
+
67
+ {% endif %}
68
+ {% if hasAZero %}
69
+ fn read_a_zero({% if aZeroPerRow %}row: u32{% endif %}) -> i32 {
70
+ {% if aZeroPerRow %}
71
+ {% if aUnsigned %}
72
+ return i32(a_zero_point[row]);
73
+ {% else %}
74
+ return a_zero_point[row];
75
+ {% endif %}
76
+ {% else %}
77
+ {% if aUnsigned %}
78
+ return i32(a_zero_point[0]);
79
+ {% else %}
80
+ return a_zero_point[0];
81
+ {% endif %}
82
+ {% endif %}
83
+ }
84
+
85
+ {% endif %}
86
+ {% if hasBZero %}
87
+ fn read_b_zero({% if bZeroPerColumn %}col: u32{% endif %}) -> i32 {
88
+ {% if bZeroPerColumn %}
89
+ {% if bUnsigned %}
90
+ return i32(b_zero_point[col]);
91
+ {% else %}
92
+ return b_zero_point[col];
93
+ {% endif %}
94
+ {% else %}
95
+ {% if bUnsigned %}
96
+ return i32(b_zero_point[0]);
97
+ {% else %}
98
+ return b_zero_point[0];
99
+ {% endif %}
100
+ {% endif %}
101
+ }
102
+
103
+ {% endif %}
104
+ {% if requant != "none" %}
105
+ fn read_y_zero() -> i32 {
106
+ {% if yUnsigned %}
107
+ return i32(y_zero_point[0]);
108
+ {% else %}
109
+ return y_zero_point[0];
110
+ {% endif %}
111
+ }
112
+
113
+ {% endif %}
114
+ @compute @workgroup_size(TILE_N, 1, 1)
115
+ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid: vec3<u32>) {
116
+ let batch = wg.z;
117
+ let a_base4 = batch * params.aBatchStride4;
118
+ let b_base = batch * params.bBatchStride;
119
+ let y_base = batch * params.yBatchStride;
120
+ let m_block = wg.y * TILE_M;
121
+ let n_block = wg.x * TILE_N;
122
+ let tid = lid.x;
123
+ let col = n_block + tid;
124
+ let n_valid = col < params.N;
125
+ {% if aPacked == false %}
126
+ // Tail variants keep the hot loop on complete words. Packing the one partial
127
+ // word afterward prevents its bounds checks from infecting every full-word load.
128
+ let k_words_total = params.K / 4u;
129
+ let tail_lanes = params.K - k_words_total * 4u;
130
+ {% else %}
131
+ let k_words_total = params.K / 4u;
132
+ {% endif %}
133
+
134
+ {% if needsASum %}
135
+ if (tid < TILE_M) {
136
+ atomicStore(&a_row_sum[tid], 0);
137
+ }
138
+ workgroupBarrier();
139
+
140
+ {% endif %}
141
+ {% if tileM == 1 %}
142
+ var r0: i32 = 0;
143
+ {% for u in range(1, GEMV_UNROLL) %}
144
+ var r0_{{ u }}: i32 = 0;
145
+ {% endfor %}
146
+ {% else %}
147
+ {% for m in range(tileM) %}
148
+ var r{{ m }}: i32 = 0;
149
+ {% endfor %}
150
+ {% endif %}
151
+ {% if needsBSum %}
152
+ var sum_b: i32 = 0;
153
+ {% if tileM == 1 %}
154
+ {% for u in range(1, GEMV_UNROLL) %}
155
+ var sum_b_{{ u }}: i32 = 0;
156
+ {% endfor %}
157
+ {% endif %}
158
+ {% endif %}
159
+
160
+ for (var kw0 = 0u; kw0 < k_words_total; kw0 = kw0 + KT_WORDS) {
161
+ let words = min(KT_WORDS, k_words_total - kw0);
162
+ // Cooperative vec4 load + pack of the A tile (TILE_M rows x `words` u32).
163
+ for (var i = tid; i < TILE_M * words; i = i + TILE_N) {
164
+ let mi = i / words;
165
+ let ki = i % words;
166
+ let src_m = m_block + mi;
167
+ var packed = 0u;
168
+ if (src_m < params.M) {
169
+ {% if aPacked == false %}
170
+ let k4 = (kw0 + ki) * 4u;
171
+ let a_row = a_base4 + src_m * params.K;
172
+ let v = vec4<{{ aScalar }}>(
173
+ a[a_row + k4],
174
+ a[a_row + k4 + 1u],
175
+ a[a_row + k4 + 2u],
176
+ a[a_row + k4 + 3u],
177
+ );
178
+ {% else %}
179
+ let v = a[a_base4 + src_m * k_words_total + kw0 + ki];
180
+ {% endif %}
181
+ packed = pack_a(v);
182
+ {% if needsASum %}
183
+ atomicAdd(&a_row_sum[mi], sum4_a(v));
184
+ {% endif %}
185
+ }
186
+ a_tile[mi * KT_WORDS + ki] = packed;
187
+ }
188
+ workgroupBarrier();
189
+
190
+ if (n_valid) {
191
+ {% if tileM == 1 %}
192
+ // M=1 has only one output accumulator, so a scalar K loop forms a long
193
+ // dependent DP4A chain. Interleave several words while preserving the
194
+ // coalesced mapping of neighboring threads to neighboring B columns.
195
+ let unrolled_words = words - words % GEMV_K_UNROLL;
196
+ for (var kk = 0u; kk < unrolled_words; kk += GEMV_K_UNROLL) {
197
+ {% for u in range(GEMV_UNROLL) %}
198
+ let k4_{{ u }} = (kw0 + kk + {{ u }}u) * 4u;
199
+ let b_row_{{ u }} = b_base + k4_{{ u }} * params.N + col;
200
+ let b0_{{ u }} = b[b_row_{{ u }}];
201
+ let b1_{{ u }} = b[b_row_{{ u }} + params.N];
202
+ let b2_{{ u }} = b[b_row_{{ u }} + 2u * params.N];
203
+ let b3_{{ u }} = b[b_row_{{ u }} + 3u * params.N];
204
+ {% if needsBSum %}
205
+ sum_b{{ "" if u == 0 else "_" ~ u }} += sum4_b(b0_{{ u }}, b1_{{ u }}, b2_{{ u }}, b3_{{ u }});
206
+ {% endif %}
207
+ let bp_{{ u }} = pack_b(b0_{{ u }}, b1_{{ u }}, b2_{{ u }}, b3_{{ u }});
208
+ r0{{ "" if u == 0 else "_" ~ u }} += dot4_packed(a_tile[kk + {{ u }}u], bp_{{ u }});
209
+ {% endfor %}
210
+ }
211
+ for (var kk = unrolled_words; kk < words; kk++) {
212
+ let k4 = (kw0 + kk) * 4u;
213
+ let b_row = b_base + k4 * params.N + col;
214
+ let b0 = b[b_row];
215
+ let b1 = b[b_row + params.N];
216
+ let b2 = b[b_row + 2u * params.N];
217
+ let b3 = b[b_row + 3u * params.N];
218
+ {% if needsBSum %}
219
+ sum_b += sum4_b(b0, b1, b2, b3);
220
+ {% endif %}
221
+ let bp = pack_b(b0, b1, b2, b3);
222
+ r0 += dot4_packed(a_tile[kk], bp);
223
+ }
224
+ {% else %}
225
+ for (var kk = 0u; kk < words; kk = kk + 1u) {
226
+ let k4 = (kw0 + kk) * 4u;
227
+ let b_row = b_base + k4 * params.N + col;
228
+ let b0 = b[b_row];
229
+ let b1 = b[b_row + params.N];
230
+ let b2 = b[b_row + 2u * params.N];
231
+ let b3 = b[b_row + 3u * params.N];
232
+ {% if needsBSum %}
233
+ sum_b = sum_b + sum4_b(b0, b1, b2, b3);
234
+ {% endif %}
235
+ let bp = pack_b(b0, b1, b2, b3);
236
+ {% for m in range(tileM) %}
237
+ r{{ m }} = r{{ m }} + dot4_packed(a_tile[{{ m }}u * KT_WORDS + kk], bp);
238
+ {% endfor %}
239
+ }
240
+ {% endif %}
241
+ }
242
+ workgroupBarrier();
243
+ }
244
+ {% if aPacked == false %}
245
+
246
+ // Exactly one uniform guarded word handles K % 4. Missing lanes use the raw
247
+ // bias-domain zero (128 for u8, 0 for i8), so their shifted values, sums, and
248
+ // dot-product contributions are all zero while params.K remains unpadded for
249
+ // the zero-point correction below.
250
+ if (tail_lanes != 0u) {
251
+ if (tid < TILE_M) {
252
+ let src_m = m_block + tid;
253
+ var packed = 0u;
254
+ if (src_m < params.M) {
255
+ let k4 = k_words_total * 4u;
256
+ let a_row = a_base4 + src_m * params.K;
257
+ let a0 = a[a_row + k4];
258
+ var a1: {{ aScalar }} = {{ "128u" if aUnsigned else "0" }};
259
+ var a2: {{ aScalar }} = {{ "128u" if aUnsigned else "0" }};
260
+ if (tail_lanes > 1u) { a1 = a[a_row + k4 + 1u]; }
261
+ if (tail_lanes > 2u) { a2 = a[a_row + k4 + 2u]; }
262
+ let a3: {{ aScalar }} = {{ "128u" if aUnsigned else "0" }};
263
+ let v = vec4<{{ aScalar }}>(a0, a1, a2, a3);
264
+ packed = pack_a(v);
265
+ {% if needsASum %}
266
+ atomicAdd(&a_row_sum[tid], sum4_a(v));
267
+ {% endif %}
268
+ }
269
+ a_tile[tid * KT_WORDS] = packed;
270
+ }
271
+ workgroupBarrier();
272
+
273
+ if (n_valid) {
274
+ let k4 = k_words_total * 4u;
275
+ let b_row = b_base + k4 * params.N + col;
276
+ let b0 = b[b_row];
277
+ var b1: {{ bScalar }} = {{ "128u" if bUnsigned else "0" }};
278
+ var b2: {{ bScalar }} = {{ "128u" if bUnsigned else "0" }};
279
+ if (tail_lanes > 1u) { b1 = b[b_row + params.N]; }
280
+ if (tail_lanes > 2u) { b2 = b[b_row + 2u * params.N]; }
281
+ let b3: {{ bScalar }} = {{ "128u" if bUnsigned else "0" }};
282
+ {% if needsBSum %}
283
+ sum_b = sum_b + sum4_b(b0, b1, b2, b3);
284
+ {% endif %}
285
+ let bp = pack_b(b0, b1, b2, b3);
286
+ {% for m in range(tileM) %}
287
+ r{{ m }} = r{{ m }} + dot4_packed(a_tile[{{ m }}u * KT_WORDS], bp);
288
+ {% endfor %}
289
+ }
290
+ workgroupBarrier();
291
+ }
292
+
293
+ {% endif %}
294
+ if (!n_valid) {
295
+ return;
296
+ }
297
+
298
+ // Fold bias + zero-point corrections (exact in modular i32 arithmetic).
299
+ let cb: i32 = {{ "128" if bUnsigned else "0" }}{% if hasBZero %} - read_b_zero({% if bZeroPerColumn %}col{% endif %}){% endif %};
300
+ {% if not aZeroPerRow %}
301
+ let ca: i32 = {{ "128" if aUnsigned else "0" }}{% if hasAZero %} - read_a_zero(){% endif %};
302
+ let kcc: i32 = i32(params.K) * ca * cb;
303
+ {% endif %}
304
+ {% if needsBSum and tileM == 1 %}
305
+ let sum_b_total = sum_b{% for u in range(1, GEMV_UNROLL) %} + sum_b_{{ u }}{% endfor %};
306
+ {% endif %}
307
+
308
+ {% for m in range(tileM) %}
309
+ {
310
+ let mr = m_block + {{ m }}u;
311
+ if (mr < params.M) {
312
+ {% if aZeroPerRow %}
313
+ let ca: i32 = {{ "128" if aUnsigned else "0" }} - read_a_zero(mr);
314
+ let kcc: i32 = i32(params.K) * ca * cb;
315
+ {% endif %}
316
+ var acc = r{{ m }}{% if tileM == 1 %}{% for u in range(1, GEMV_UNROLL) %} + r0_{{ u }}{% endfor %}{% endif %};
317
+ {% if needsASum %}
318
+ acc = acc + cb * atomicLoad(&a_row_sum[{{ m }}u]);
319
+ {% endif %}
320
+ {% if needsBSum %}
321
+ acc = acc + ca * {% if tileM == 1 %}sum_b_total{% else %}sum_b{% endif %};
322
+ {% endif %}
323
+ acc = acc + kcc;
324
+ {% if hasBias %}
325
+ acc = acc + bias[mr];
326
+ {% endif %}
327
+ {% if requant == "none" %}
328
+ y[y_base + mr * params.N + col] = acc;
329
+ {% else %}
330
+ let scaled = f32(acc) * f32(a_scale[0]) * f32(b_scale[{% if requant == "per_column" %}col{% elif requant == "per_row" %}mr{% else %}0u{% endif %}]) / f32(y_scale[0]);
331
+ // Clamp the rounded float into an i32-representable range before the cast
332
+ // so a huge-finite / Inf requantized value saturates (the final clamp pins
333
+ // it to [qMin,qMax]) instead of invoking undefined i32(huge_float).
334
+ let q = clamp(i32(clamp(round(scaled), -2.0e9, 2.0e9)) + read_y_zero(), {{ qMin }}, {{ qMax }});
335
+ {% if yUnsigned %}
336
+ y[y_base + mr * params.N + col] = u32(q);
337
+ {% else %}
338
+ y[y_base + mr * params.N + col] = q;
339
+ {% endif %}
340
+ {% endif %}
341
+ }
342
+ }
343
+ {% endfor %}
344
+ }
build/webgpu/quant-pad-k-matrices.wgsl.jinja ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Pad the K dimension of widened quantized matrices for aligned DP4A. A is
2
+ // [M, K], optional B is [batch, K, N], and outputs use K_PADDED. Padding stores
3
+ // each tensor's raw zero point, so added lanes contribute exactly zero to
4
+ // (a - a_zero) * (b - b_zero). This keeps odd-K matrices on the aligned DP4A
5
+ // path instead of a scalar-tail path.
6
+ {{ env.wgsl.resourceDeclarations }}
7
+
8
+ fn read_a_zero({% if aZeroPerRow %}row: u32{% endif %}) -> i32 {
9
+ {% if aZeroPerRow %}
10
+ return a_zero_point[row];
11
+ {% else %}
12
+ return a_zero_point[0];
13
+ {% endif %}
14
+ }
15
+
16
+ @compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
17
+ fn main(
18
+ @builtin(global_invocation_id) gid: vec3<u32>,
19
+ @builtin(num_workgroups) nwg: vec3<u32>,
20
+ ) {
21
+ let flat = gid.x + gid.y * nwg.x * {{ tunables.WORKGROUP_SIZE }}u;
22
+
23
+ let a_count = params.M * params.KPadded;
24
+ if (flat < a_count) {
25
+ let row = flat / params.KPadded;
26
+ let k = flat - row * params.KPadded;
27
+ var value = read_a_zero({% if aZeroPerRow %}row{% endif %});
28
+ if (k < params.K) {
29
+ value = a[row * params.K + k];
30
+ }
31
+ a_padded[flat] = value;
32
+ }
33
+
34
+ {% if padB %}
35
+ let b_count = params.batch * params.KPadded * params.N;
36
+ if (flat < b_count) {
37
+ let batch_stride_out = params.KPadded * params.N;
38
+ let batch = flat / batch_stride_out;
39
+ let rem = flat - batch * batch_stride_out;
40
+ let k = rem / params.N;
41
+ let col = rem - k * params.N;
42
+ var value = b_zero_point[0];
43
+ if (k < params.K) {
44
+ let src = (batch * params.K + k) * params.N + col;
45
+ value = b[src];
46
+ }
47
+ b_padded[flat] = value;
48
+ }
49
+ {% endif %}
50
+ }
build/webgpu/test.json ADDED
@@ -0,0 +1,1602 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "op": "ai.onnx.QLinearConv",
3
+ "fixtureArrays": {
4
+ "same_upper_stride2_autopad_input_x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
5
+ "backend_qlinearconv_pointwise_u8_input_x": [255, 174, 162, 25, 203, 168, 58, 15, 59, 237, 95, 129, 0, 64, 56, 242, 153, 221, 168, 12, 166, 232, 178, 186, 195, 237, 162, 237, 188, 39, 124, 77, 80, 102, 43, 127, 230, 21, 83, 41, 40, 134, 255, 154, 92, 141, 42, 148, 247],
6
+ "ort_depthwise_per_channel_weight_zero_points_s8s8_input_x": [-8, -4, 0, 4, 8, 12, -12, 16, -16, 3, -3, 6, -6, 9, -9, 12, -12, 15, -20, -10, 0, 10, 20, 30, -30, 40, -40],
7
+ "ort_depthwise_per_channel_weight_zero_points_s8s8_input_w": [-10, -8, -6, -4, -2, 0, 2, 4, 6, 3, 5, 7, 9, 11, 13, 15, 17, 19, -20, -15, -10, -5, 0, 5, 10, 15, 20],
8
+ "dp4a_pointwise_u8s8_c8_batched_input_w": [-77, 3, 100, -100, 42, -5, 19, -64, 88, -33, 7, 125, -90, -128, 127, 0, -1, 56],
9
+ "dp4a_pointwise_s8s8_c12_signed_output_input_x": [-128, 127, 0, -1, 56, -77, 3, 100, -100, 42, -5, 19, -64, 88, -33, 7, 125, -90]
10
+ },
11
+ "cases": [
12
+ {
13
+ "name": "same_upper_stride2_autopad",
14
+ "provenance": {
15
+ "source": "ONNX Runtime QLinearConv-10 CPUExecutionProvider",
16
+ "notes": "QLinearConv inherits Conv auto_pad semantics. This fixture exercises the exact SAME_UPPER spelling and derived asymmetric bottom/right padding; unit scales and zero points make the expected requantized values equal the integer convolution sums."
17
+ },
18
+ "attrs": { "auto_pad": "SAME_UPPER", "kernel_shape": [3, 3], "strides": [2, 2] },
19
+ "inputs": {
20
+ "x": {
21
+ "dtype": "uint8",
22
+ "shape": [1, 1, 4, 4],
23
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/same_upper_stride2_autopad_input_x" } }
24
+ },
25
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
26
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
27
+ "w": { "dtype": "uint8", "shape": [1, 1, 3, 3], "data": { "kind": "constant", "value": 1 } },
28
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
29
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
30
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
31
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } }
32
+ },
33
+ "outputs": {
34
+ "y": {
35
+ "dtype": "uint8",
36
+ "shape": [1, 1, 2, 2],
37
+ "data": { "kind": "values", "values": [54, 45, 72, 54] },
38
+ "tolerance": 0
39
+ }
40
+ }
41
+ },
42
+ {
43
+ "name": "dispatch_cliff_pointwise_u8",
44
+ "inputs": {
45
+ "x": {
46
+ "dtype": "uint8",
47
+ "shape": [1, 1, 4096, 4097],
48
+ "data": { "kind": "cycle", "values": [120, 128, 136, 144] }
49
+ },
50
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.1] } },
51
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
52
+ "w": { "dtype": "uint8", "shape": [1, 1, 1, 1], "data": { "kind": "values", "values": [130] } },
53
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.2] } },
54
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
55
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
56
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [127] } }
57
+ },
58
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 1, 4096, 4097], "tolerance": 0 } },
59
+ "attrs": {}
60
+ },
61
+ {
62
+ "name": "uint8_padding",
63
+ "attrs": { "strides": [1, 1], "pads": [1, 1, 1, 1] },
64
+ "inputs": {
65
+ "x": {
66
+ "dtype": "uint8",
67
+ "shape": [1, 1, 3, 3],
68
+ "data": { "kind": "values", "values": [128, 129, 130, 131, 132, 133, 134, 135, 136] }
69
+ },
70
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.5] } },
71
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
72
+ "w": { "dtype": "uint8", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [127, 128, 129, 130] } },
73
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
74
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
75
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.125] } },
76
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
77
+ },
78
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 1, 4, 4] } }
79
+ },
80
+ {
81
+ "name": "int8_output",
82
+ "inputs": {
83
+ "x": {
84
+ "dtype": "int8",
85
+ "shape": [1, 1, 3, 3],
86
+ "data": { "kind": "values", "values": [-4, -3, -2, -1, 0, 1, 2, 3, 4] }
87
+ },
88
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.5] } },
89
+ "x_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
90
+ "w": { "dtype": "int8", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [1, -1, 2, -2] } },
91
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
92
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
93
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
94
+ "y_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [-3] } }
95
+ },
96
+ "outputs": { "y": { "dtype": "int8", "shape": [1, 1, 2, 2] } },
97
+ "attrs": {}
98
+ },
99
+ {
100
+ "name": "int8_input_uint8_output_pairwise_route",
101
+ "provenance": {
102
+ "notes": "Pairs the signed input route with the independently typed uint8 output on a compact 1-D pointwise convolution."
103
+ },
104
+ "inputs": {
105
+ "x": { "dtype": "int8", "shape": [1, 1, 1], "data": { "kind": "values", "values": [2] } },
106
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.5] } },
107
+ "x_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
108
+ "w": { "dtype": "int8", "shape": [1, 1, 1], "data": { "kind": "values", "values": [3] } },
109
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
110
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
111
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
112
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
113
+ },
114
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 1, 1], "tolerance": 0 } }
115
+ },
116
+ {
117
+ "name": "requant_exact_half_ties_to_even",
118
+ "provenance": {
119
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
120
+ "test": "QLinearConvTest.Conv2D_U8S8_Requantize_NoBias",
121
+ "notes": "ONNX quantized convolution requantization uses ORT's RoundHalfToEven helper; pointwise accumulators +/-1 and +/-5 with y_scale=2 produce exact +/-0.5 and +/-2.5 tie values."
122
+ },
123
+ "inputs": {
124
+ "x": { "dtype": "uint8", "shape": [1, 1, 1, 1], "data": { "kind": "values", "values": [1] } },
125
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
126
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
127
+ "w": { "dtype": "int8", "shape": [4, 1, 1, 1], "data": { "kind": "values", "values": [1, -1, 5, -5] } },
128
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
129
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
130
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [2.0] } },
131
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
132
+ },
133
+ "outputs": {
134
+ "y": {
135
+ "dtype": "uint8",
136
+ "shape": [1, 4, 1, 1],
137
+ "tolerance": 0,
138
+ "data": { "kind": "values", "values": [128, 128, 130, 126] }
139
+ }
140
+ },
141
+ "attrs": {}
142
+ },
143
+ {
144
+ "name": "requant_huge_finite_saturates_before_i32_overflow",
145
+ "provenance": {
146
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
147
+ "test": "QLinearConvTest.Conv2D_U8S8_Pointwise",
148
+ "notes": "Finite requantized outputs far outside uint8 must saturate; the scalar WebGPU requant pass currently converts to i32 before clamping."
149
+ },
150
+ "inputs": {
151
+ "x": { "dtype": "uint8", "shape": [1, 1, 1, 1], "data": { "kind": "values", "values": [1] } },
152
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
153
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
154
+ "w": { "dtype": "int8", "shape": [2, 1, 1, 1], "data": { "kind": "values", "values": [1, -1] } },
155
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
156
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
157
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1e-20] } },
158
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
159
+ },
160
+ "outputs": {
161
+ "y": {
162
+ "dtype": "uint8",
163
+ "shape": [1, 2, 1, 1],
164
+ "tolerance": 0,
165
+ "data": { "kind": "values", "values": [255, 0] }
166
+ }
167
+ },
168
+ "attrs": {}
169
+ },
170
+ {
171
+ "name": "requant_subnormal_y_scale_saturates",
172
+ "provenance": {
173
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
174
+ "test": "QLinearConvTest.Conv2D_U8S8_Pointwise",
175
+ "notes": "A valid positive subnormal output scale can make a one-pixel convolution requantize beyond uint8; the result should saturate before any i32 conversion of an infinite f32."
176
+ },
177
+ "inputs": {
178
+ "x": { "dtype": "uint8", "shape": [1, 1, 1, 1], "data": { "kind": "values", "values": [1] } },
179
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
180
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
181
+ "w": { "dtype": "int8", "shape": [2, 1, 1, 1], "data": { "kind": "values", "values": [1, -1] } },
182
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
183
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
184
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1e-40] } },
185
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
186
+ },
187
+ "outputs": {
188
+ "y": {
189
+ "dtype": "uint8",
190
+ "shape": [1, 2, 1, 1],
191
+ "tolerance": 0,
192
+ "data": { "kind": "values", "values": [255, 0] }
193
+ }
194
+ },
195
+ "attrs": {}
196
+ },
197
+ {
198
+ "name": "dp4a_pointwise_requant_subnormal_y_scale_saturates",
199
+ "provenance": {
200
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
201
+ "test": "QLinearConvTest.Conv2D_U8S8_Pointwise",
202
+ "notes": "Fused DP4A pointwise route with a valid positive subnormal output scale should saturate uint8 results before any i32 conversion of an infinite f32."
203
+ },
204
+ "inputs": {
205
+ "x": { "dtype": "uint8", "shape": [1, 4, 1, 1], "data": { "kind": "values", "values": [1, 0, 0, 0] } },
206
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
207
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
208
+ "w": {
209
+ "dtype": "int8",
210
+ "shape": [2, 4, 1, 1],
211
+ "data": { "kind": "values", "values": [1, 0, 0, 0, -1, 0, 0, 0] }
212
+ },
213
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
214
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
215
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1e-40] } },
216
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
217
+ },
218
+ "outputs": {
219
+ "y": {
220
+ "dtype": "uint8",
221
+ "shape": [1, 2, 1, 1],
222
+ "tolerance": 0,
223
+ "data": { "kind": "values", "values": [255, 0] }
224
+ }
225
+ },
226
+ "attrs": {}
227
+ },
228
+ {
229
+ "name": "uint8_pointwise_subnormal_scale_ratio_gpu_gap",
230
+ "skipGpu": {
231
+ "category": "permanent",
232
+ "reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal scale values required by this fixture. Backend evidence: Requant multiplier x_scale*w_scale/y_scale = 1e-40/1e-40 divides two denormals; Metal flushes denormals in floating-point division (-> NaN), while the CPU reference computes the ratio with denormal support. Subnormal scale-ratio cases remain CPU-reference-only."
233
+ },
234
+ "provenance": {
235
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
236
+ "test": "QLinearConvTest.Conv2DTest",
237
+ "notes": "Valid positive subnormal input/output scales whose ratio is exactly meaningful for scalar quantization."
238
+ },
239
+ "inputs": {
240
+ "x": { "dtype": "uint8", "shape": [1, 4, 1, 1], "data": { "kind": "values", "values": [1, 0, 0, 0] } },
241
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1e-40] } },
242
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
243
+ "w": {
244
+ "dtype": "uint8",
245
+ "shape": [4, 4, 1, 1],
246
+ "data": { "kind": "values", "values": [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0] }
247
+ },
248
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
249
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
250
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1e-40] } },
251
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } }
252
+ },
253
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 4, 1, 1], "tolerance": 0 } },
254
+ "attrs": {}
255
+ },
256
+ {
257
+ "name": "backend_qlinearconv_pointwise_u8",
258
+ "provenance": {
259
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
260
+ "test": "QLinearConvTest.Conv2DTest",
261
+ "notes": "Uses the same quantized tensors as ORT's handwritten pointwise Conv2D test."
262
+ },
263
+ "inputs": {
264
+ "x": {
265
+ "dtype": "uint8",
266
+ "shape": [1, 1, 7, 7],
267
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/backend_qlinearconv_pointwise_u8_input_x" } }
268
+ },
269
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.00369204697] } },
270
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [132] } },
271
+ "w": { "dtype": "uint8", "shape": [1, 1, 1, 1], "data": { "kind": "values", "values": [0] } },
272
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.00172794575] } },
273
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [255] } },
274
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.00162681262] } },
275
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [123] } }
276
+ },
277
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 1, 7, 7], "tolerance": 0 } },
278
+ "attrs": {}
279
+ },
280
+ {
281
+ "name": "group2_depthwise_scalar_quant_u8",
282
+ "attrs": { "group": 2 },
283
+ "inputs": {
284
+ "x": {
285
+ "dtype": "uint8",
286
+ "shape": [1, 2, 2, 2],
287
+ "data": { "kind": "values", "values": [128, 129, 130, 131, 120, 124, 128, 132] }
288
+ },
289
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.1] } },
290
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
291
+ "w": { "dtype": "uint8", "shape": [2, 1, 1, 1], "data": { "kind": "values", "values": [130, 126] } },
292
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.2] } },
293
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
294
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
295
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [127] } }
296
+ },
297
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 2, 2, 2], "tolerance": 0 } }
298
+ },
299
+ {
300
+ "name": "dilated_stride2_padding_saturates_u8",
301
+ "attrs": { "strides": [2, 2], "dilations": [2, 2], "pads": [1, 1, 1, 1] },
302
+ "inputs": {
303
+ "x": {
304
+ "dtype": "uint8",
305
+ "shape": [1, 1, 5, 5],
306
+ "data": {
307
+ "kind": "values",
308
+ "values": [0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 128, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0]
309
+ }
310
+ },
311
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.05] } },
312
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
313
+ "w": { "dtype": "uint8", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [0, 255, 255, 0] } },
314
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.04] } },
315
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
316
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.001] } },
317
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
318
+ },
319
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 1, 3, 3], "tolerance": 0 } }
320
+ },
321
+ {
322
+ "name": "ort_style_multi_channel_four_outputs_pad1_u8",
323
+ "provenance": {
324
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
325
+ "test": "QLinearConvTest.WithBias_2D",
326
+ "notes": "Adapts the handwritten 2D multi-channel/multi-output QLinearConv shape without optional bias."
327
+ },
328
+ "attrs": { "pads": [1, 1, 1, 1] },
329
+ "inputs": {
330
+ "x": {
331
+ "dtype": "uint8",
332
+ "shape": [1, 2, 4, 4],
333
+ "data": { "kind": "cycle", "values": [135, 140, 128, 120, 150, 110, 160, 100] }
334
+ },
335
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
336
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
337
+ "w": {
338
+ "dtype": "uint8",
339
+ "shape": [4, 2, 3, 3],
340
+ "data": { "kind": "cycle", "values": [110, 115, 105, 130, 100, 125] }
341
+ },
342
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.03] } },
343
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [110] } },
344
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
345
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [121] } }
346
+ },
347
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 4, 4, 4], "tolerance": 0 } }
348
+ },
349
+ {
350
+ "name": "ort_style_group3_stride2_pad1_u8",
351
+ "provenance": {
352
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
353
+ "test": "QLinearConvTest.WithGroup_2D",
354
+ "notes": "Covers grouped convolution with group=3 and stride=2 using symmetric padding supported by the WebGPU manifest."
355
+ },
356
+ "attrs": { "group": 3, "strides": [2, 2], "pads": [1, 1, 1, 1] },
357
+ "inputs": {
358
+ "x": {
359
+ "dtype": "uint8",
360
+ "shape": [1, 6, 5, 5],
361
+ "data": { "kind": "cycle", "values": [98, 166, 219, 195, 46, 97, 27, 211, 239, 1] }
362
+ },
363
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.01] } },
364
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [135] } },
365
+ "w": {
366
+ "dtype": "uint8",
367
+ "shape": [6, 2, 3, 3],
368
+ "data": { "kind": "cycle", "values": [220, 111, 73, 254, 235, 151, 6, 156, 129, 204, 234, 198] }
369
+ },
370
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.015] } },
371
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [110] } },
372
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.04] } },
373
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [121] } }
374
+ },
375
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 6, 3, 3], "tolerance": 0 } }
376
+ },
377
+ {
378
+ "name": "ort_with_group_2d_asymmetric_pad_no_bias_u8",
379
+ "provenance": {
380
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
381
+ "test": "QLinearConvTest.WithGroup_2D",
382
+ "notes": "Uses ORT's grouped convolution tensors and asymmetric pads without optional bias."
383
+ },
384
+ "attrs": { "group": 3, "strides": [2, 2], "pads": [0, 0, 1, 1] },
385
+ "inputs": {
386
+ "x": {
387
+ "dtype": "uint8",
388
+ "shape": [1, 6, 3, 5],
389
+ "data": {
390
+ "kind": "values",
391
+ "values": [98, 166, 219, 195, 46, 97, 27, 211, 239, 1, 28, 208, 143, 144, 215, 252, 79, 5, 154, 56, 122, 191, 94, 25, 221, 48, 37, 182, 68, 245, 210, 206, 183, 22, 163, 104, 242, 112, 161, 66, 181, 235, 117, 75, 236, 61, 115, 36, 120, 253, 165, 214, 159, 132, 11, 201, 30, 249, 89, 171, 186, 67, 225, 197, 135, 142, 241, 169, 170, 164, 178, 58, 50, 51, 200, 43, 199, 126, 222, 123, 227, 42, 3, 21, 124, 220, 24, 47, 63, 110]
392
+ }
393
+ },
394
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.01] } },
395
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [135] } },
396
+ "w": {
397
+ "dtype": "uint8",
398
+ "shape": [6, 2, 2, 2],
399
+ "data": {
400
+ "kind": "values",
401
+ "values": [220, 111, 73, 254, 235, 151, 6, 156, 129, 204, 234, 198, 44, 89, 202, 82, 118, 189, 71, 120, 123, 121, 110, 83, 173, 248, 108, 229, 124, 68, 85, 239, 133, 213, 112, 122, 170, 231, 225, 195, 192, 9, 232, 97, 160, 227, 67, 137]
402
+ }
403
+ },
404
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.15] } },
405
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [110] } },
406
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.75] } },
407
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [121] } }
408
+ },
409
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 6, 2, 3], "tolerance": 0 } }
410
+ },
411
+ {
412
+ "name": "mixed_u8_i8_uint8_output_dilated_pad1",
413
+ "provenance": {
414
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
415
+ "test": "QLinearConvTest.Conv2D_U8S8_Dilations",
416
+ "notes": "Exercises the ORT-covered mixed uint8 activation / int8 weight quantization path with dilation."
417
+ },
418
+ "attrs": { "dilations": [2, 2], "pads": [1, 1, 1, 1] },
419
+ "inputs": {
420
+ "x": {
421
+ "dtype": "uint8",
422
+ "shape": [1, 1, 5, 5],
423
+ "data": { "kind": "cycle", "values": [0, 32, 96, 127, 128, 160, 224, 255] }
424
+ },
425
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.04] } },
426
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
427
+ "w": { "dtype": "int8", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [-128, 127, -32, 96] } },
428
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
429
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
430
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.01] } },
431
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
432
+ },
433
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 1, 5, 5], "tolerance": 0 } }
434
+ },
435
+ {
436
+ "name": "ort_u8s8_pointwise_multi_output",
437
+ "provenance": {
438
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
439
+ "test": "QLinearConvTest.Conv2D_U8S8_Pointwise",
440
+ "notes": "Adapts QLinearConvTest.Conv2D_U8S8_Pointwise with multiple input and output channels."
441
+ },
442
+ "inputs": {
443
+ "x": {
444
+ "dtype": "uint8",
445
+ "shape": [1, 2, 2, 3],
446
+ "data": { "kind": "values", "values": [4, 15, 64, 128, 200, 255, 250, 180, 127, 80, 42, 0] }
447
+ },
448
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.05] } },
449
+ "x_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [4] } },
450
+ "w": {
451
+ "dtype": "int8",
452
+ "shape": [3, 2, 1, 1],
453
+ "data": { "kind": "values", "values": [0, 12, -7, 31, 63, -63] }
454
+ },
455
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
456
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [0] } },
457
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.55] } },
458
+ "y_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [54] } }
459
+ },
460
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 3, 2, 3], "tolerance": 0 } },
461
+ "attrs": {}
462
+ },
463
+ {
464
+ "name": "ort_u8s8_stride2_small",
465
+ "provenance": {
466
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
467
+ "test": "QLinearConvTest.Conv2D_U8S8_Strides",
468
+ "notes": "Adapts QLinearConvTest.Conv2D_U8S8_Strides using stride=2 and scalar quantization."
469
+ },
470
+ "attrs": { "strides": [2, 2] },
471
+ "inputs": {
472
+ "x": {
473
+ "dtype": "uint8",
474
+ "shape": [1, 1, 5, 5],
475
+ "data": {
476
+ "kind": "values",
477
+ "values": [4, 18, 32, 47, 63, 79, 95, 111, 127, 143, 159, 175, 191, 207, 223, 239, 255, 231, 199, 167, 135, 103, 71, 39, 7]
478
+ }
479
+ },
480
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.05] } },
481
+ "x_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [4] } },
482
+ "w": { "dtype": "int8", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [-63, -9, 17, 63] } },
483
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
484
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [0] } },
485
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.31] } },
486
+ "y_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [30] } }
487
+ },
488
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 1, 2, 2], "tolerance": 0 } }
489
+ },
490
+ {
491
+ "name": "ort_u8s8_dilation2_small",
492
+ "provenance": {
493
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
494
+ "test": "QLinearConvTest.Conv2D_U8S8_Dilations",
495
+ "notes": "Adapts QLinearConvTest.Conv2D_U8S8_Dilations using dilation=2."
496
+ },
497
+ "attrs": { "dilations": [2, 2] },
498
+ "inputs": {
499
+ "x": {
500
+ "dtype": "uint8",
501
+ "shape": [1, 1, 4, 4],
502
+ "data": {
503
+ "kind": "values",
504
+ "values": [4, 60, 116, 172, 228, 200, 144, 88, 32, 12, 68, 124, 180, 236, 252, 96]
505
+ }
506
+ },
507
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.05] } },
508
+ "x_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [4] } },
509
+ "w": { "dtype": "int8", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [-3, 19, -41, 63] } },
510
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
511
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [0] } },
512
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.24] } },
513
+ "y_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [15] } }
514
+ },
515
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 1, 2, 2], "tolerance": 0 } }
516
+ },
517
+ {
518
+ "name": "ort_u8s8_group2_small",
519
+ "provenance": {
520
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
521
+ "test": "QLinearConvTest.Conv2D_U8S8_Groups",
522
+ "notes": "Adapts QLinearConvTest.Conv2D_U8S8_Groups with two groups and scalar weight quantization."
523
+ },
524
+ "attrs": { "group": 2 },
525
+ "inputs": {
526
+ "x": {
527
+ "dtype": "uint8",
528
+ "shape": [1, 4, 3, 3],
529
+ "data": { "kind": "cycle", "values": [4, 17, 33, 65, 97, 129, 161, 193, 225, 255] }
530
+ },
531
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.05] } },
532
+ "x_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [4] } },
533
+ "w": {
534
+ "dtype": "int8",
535
+ "shape": [4, 2, 2, 2],
536
+ "data": { "kind": "cycle", "values": [-63, -45, -18, -1, 0, 7, 23, 49, 63] }
537
+ },
538
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
539
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [0] } },
540
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.76] } },
541
+ "y_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [88] } }
542
+ },
543
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 4, 2, 2], "tolerance": 0 } }
544
+ },
545
+ {
546
+ "name": "ort_s8s8_stride2_pad1_signed_output",
547
+ "provenance": {
548
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
549
+ "test": "QLinearConvTest.Conv2D_S8S8_Strides",
550
+ "notes": "Adapts QLinearConvTest.Conv2D_S8S8_Strides with signed activations, signed weights, padding, and stride."
551
+ },
552
+ "attrs": { "strides": [2, 2], "pads": [1, 1, 1, 1] },
553
+ "inputs": {
554
+ "x": {
555
+ "dtype": "int8",
556
+ "shape": [1, 1, 5, 5],
557
+ "data": {
558
+ "kind": "values",
559
+ "values": [-128, -96, -64, -32, 0, 32, 64, 96, 127, 95, 63, 31, -1, -33, -65, -97, -127, -80, -40, 8, 48, 88, 120, 72, 24]
560
+ }
561
+ },
562
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.05] } },
563
+ "x_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [4] } },
564
+ "w": {
565
+ "dtype": "int8",
566
+ "shape": [1, 1, 3, 3],
567
+ "data": { "kind": "values", "values": [-63, -31, 0, 17, 45, 63, -12, 29, -48] }
568
+ },
569
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
570
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [0] } },
571
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.31] } },
572
+ "y_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [-30] } }
573
+ },
574
+ "outputs": { "y": { "dtype": "int8", "shape": [1, 1, 3, 3], "tolerance": 0 } }
575
+ },
576
+ {
577
+ "name": "ort_u8u8_pointwise_multi_output",
578
+ "provenance": {
579
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
580
+ "test": "QLinearConvTest.Conv2D_U8U8_Pointwise",
581
+ "notes": "Adapts the ORT pointwise U8/U8 path using scalar quantization and multiple output channels."
582
+ },
583
+ "inputs": {
584
+ "x": {
585
+ "dtype": "uint8",
586
+ "shape": [1, 2, 2, 3],
587
+ "data": { "kind": "values", "values": [4, 15, 64, 128, 200, 255, 250, 180, 127, 80, 42, 0] }
588
+ },
589
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.05] } },
590
+ "x_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [4] } },
591
+ "w": {
592
+ "dtype": "uint8",
593
+ "shape": [3, 2, 1, 1],
594
+ "data": { "kind": "values", "values": [4, 12, 31, 63, 127, 255] }
595
+ },
596
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.03125] } },
597
+ "w_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [4] } },
598
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.55] } },
599
+ "y_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [54] } }
600
+ },
601
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 3, 2, 3], "tolerance": 0 } },
602
+ "attrs": {}
603
+ },
604
+ {
605
+ "name": "ort_s8s8_pointwise_signed_output",
606
+ "provenance": {
607
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
608
+ "test": "QLinearConvTest.Conv2D_S8S8_Pointwise",
609
+ "notes": "Adapts the ORT signed-activation/signed-weight pointwise path with signed output saturation."
610
+ },
611
+ "inputs": {
612
+ "x": {
613
+ "dtype": "int8",
614
+ "shape": [1, 2, 2, 3],
615
+ "data": { "kind": "values", "values": [-128, -96, -64, -32, 0, 31, 63, 95, 127, 64, 16, -16] }
616
+ },
617
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.04] } },
618
+ "x_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [-3] } },
619
+ "w": {
620
+ "dtype": "int8",
621
+ "shape": [3, 2, 1, 1],
622
+ "data": { "kind": "values", "values": [-63, -31, -7, 0, 31, 63] }
623
+ },
624
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.0275] } },
625
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [0] } },
626
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.4] } },
627
+ "y_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [-5] } }
628
+ },
629
+ "outputs": { "y": { "dtype": "int8", "shape": [1, 3, 2, 3], "tolerance": 0 } },
630
+ "attrs": {}
631
+ },
632
+ {
633
+ "name": "ort_u8s8_depthwise_no_bias",
634
+ "provenance": {
635
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
636
+ "test": "QLinearConvTest.Conv2D_U8S8_Depthwise_NoBias",
637
+ "notes": "Adapts ORT's depthwise no-bias coverage using scalar quantization and group=in_channels."
638
+ },
639
+ "attrs": { "group": 3, "pads": [1, 1, 1, 1] },
640
+ "inputs": {
641
+ "x": {
642
+ "dtype": "uint8",
643
+ "shape": [1, 3, 3, 3],
644
+ "data": { "kind": "cycle", "values": [4, 31, 63, 95, 127, 159, 191, 223, 255] }
645
+ },
646
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.04] } },
647
+ "x_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [4] } },
648
+ "w": { "dtype": "int8", "shape": [3, 1, 2, 2], "data": { "kind": "cycle", "values": [-63, -17, 0, 23, 63] } },
649
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
650
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [0] } },
651
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.5] } },
652
+ "y_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [80] } }
653
+ },
654
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 3, 4, 4], "tolerance": 0 } }
655
+ },
656
+ {
657
+ "name": "ort_s8s8_depthwise_no_bias",
658
+ "provenance": {
659
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
660
+ "test": "QLinearConvTest.Conv2D_S8S8_Depthwise_NoBias",
661
+ "notes": "Adapts ORT's signed depthwise no-bias coverage using scalar quantization and group=in_channels."
662
+ },
663
+ "attrs": { "group": 2, "pads": [1, 1, 1, 1] },
664
+ "inputs": {
665
+ "x": {
666
+ "dtype": "int8",
667
+ "shape": [1, 2, 3, 3],
668
+ "data": { "kind": "cycle", "values": [-128, -96, -64, -16, 0, 16, 64, 96, 127] }
669
+ },
670
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.05] } },
671
+ "x_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [-1] } },
672
+ "w": {
673
+ "dtype": "int8",
674
+ "shape": [2, 1, 2, 2],
675
+ "data": { "kind": "values", "values": [-63, -31, 17, 63, 45, -12, -48, 29] }
676
+ },
677
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
678
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [0] } },
679
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.4] } },
680
+ "y_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [-12] } }
681
+ },
682
+ "outputs": { "y": { "dtype": "int8", "shape": [1, 2, 4, 4], "tolerance": 0 } }
683
+ },
684
+ {
685
+ "name": "ort_1d_u8s8_pad1",
686
+ "provenance": {
687
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
688
+ "test": "QLinearConvTest.Conv1D_U8S8",
689
+ "notes": "Compact 1D convolution with symmetric padding."
690
+ },
691
+ "attrs": { "pads": [1, 1] },
692
+ "inputs": {
693
+ "x": { "dtype": "uint8", "shape": [1, 1, 5], "data": { "kind": "values", "values": [10, 11, 12, 13, 14] } },
694
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.5] } },
695
+ "x_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [10] } },
696
+ "w": { "dtype": "int8", "shape": [1, 1, 3], "data": { "kind": "values", "values": [1, 2, 3] } },
697
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.25] } },
698
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [0] } },
699
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
700
+ "y_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [100] } }
701
+ },
702
+ "outputs": {
703
+ "y": {
704
+ "dtype": "uint8",
705
+ "shape": [1, 1, 5],
706
+ "data": { "kind": "values", "values": [103, 108, 114, 120, 111] },
707
+ "tolerance": 0
708
+ }
709
+ }
710
+ },
711
+ {
712
+ "name": "ort_1d_u8s8_stride2_dilation2",
713
+ "provenance": {
714
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
715
+ "test": "QLinearConvTest.Conv1D_U8S8_Dilations",
716
+ "notes": "Compact 1D convolution combining padding, stride, and dilation."
717
+ },
718
+ "attrs": { "strides": [2], "dilations": [2], "pads": [1, 1] },
719
+ "inputs": {
720
+ "x": {
721
+ "dtype": "uint8",
722
+ "shape": [1, 1, 7],
723
+ "data": { "kind": "values", "values": [10, 12, 14, 16, 18, 20, 22] }
724
+ },
725
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.5] } },
726
+ "x_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [10] } },
727
+ "w": { "dtype": "int8", "shape": [1, 1, 3], "data": { "kind": "values", "values": [1, -2, 3] } },
728
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.25] } },
729
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [0] } },
730
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
731
+ "y_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [100] } }
732
+ },
733
+ "outputs": {
734
+ "y": {
735
+ "dtype": "uint8",
736
+ "shape": [1, 1, 3],
737
+ "data": { "kind": "values", "values": [114, 120, 86] },
738
+ "tolerance": 0
739
+ }
740
+ }
741
+ },
742
+ {
743
+ "name": "ort_1d_s8s8_pointwise_signed_output",
744
+ "provenance": {
745
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
746
+ "test": "QLinearConvTest.Conv1D_S8S8_Pointwise",
747
+ "notes": "Compact signed 1D pointwise convolution."
748
+ },
749
+ "inputs": {
750
+ "x": { "dtype": "int8", "shape": [1, 1, 4], "data": { "kind": "values", "values": [-3, -2, -1, 0] } },
751
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.5] } },
752
+ "x_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [-3] } },
753
+ "w": { "dtype": "int8", "shape": [1, 1, 1], "data": { "kind": "values", "values": [4] } },
754
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.25] } },
755
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [0] } },
756
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
757
+ "y_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [-10] } }
758
+ },
759
+ "outputs": {
760
+ "y": {
761
+ "dtype": "int8",
762
+ "shape": [1, 1, 4],
763
+ "data": { "kind": "values", "values": [-10, -6, -2, 2] },
764
+ "tolerance": 0
765
+ }
766
+ },
767
+ "attrs": {}
768
+ },
769
+ {
770
+ "name": "onnx_backend_qlinearconv",
771
+ "provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_qlinearconv" },
772
+ "inputs": {
773
+ "x": {
774
+ "dtype": "uint8",
775
+ "shape": [1, 1, 7, 7],
776
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/backend_qlinearconv_pointwise_u8_input_x" } }
777
+ },
778
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.003692046971991658] } },
779
+ "x_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [132] } },
780
+ "w": { "dtype": "uint8", "shape": [1, 1, 1, 1], "data": { "kind": "values", "values": [0] } },
781
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.0017279457533732057] } },
782
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [255] } },
783
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.001626812620088458] } },
784
+ "y_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [123] } }
785
+ },
786
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 1, 7, 7], "tolerance": 0 } },
787
+ "attrs": {}
788
+ },
789
+ {
790
+ "name": "ort_pointwise_2d_with_bias",
791
+ "provenance": {
792
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
793
+ "test": "QLinearConvTest.WithBias_2D",
794
+ "notes": "Compact ORT-valid pointwise adaptation that preserves optional int32 bias semantics."
795
+ },
796
+ "inputs": {
797
+ "x": { "dtype": "uint8", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [10, 11, 12, 13] } },
798
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.5] } },
799
+ "x_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [10] } },
800
+ "w": { "dtype": "uint8", "shape": [1, 1, 1, 1], "data": { "kind": "values", "values": [14] } },
801
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.25] } },
802
+ "w_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [10] } },
803
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
804
+ "y_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [100] } },
805
+ "B": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [-2] } }
806
+ },
807
+ "outputs": {
808
+ "y": {
809
+ "dtype": "uint8",
810
+ "shape": [1, 1, 2, 2],
811
+ "data": { "kind": "values", "values": [98, 102, 106, 110] },
812
+ "tolerance": 0
813
+ }
814
+ },
815
+ "attrs": {}
816
+ },
817
+ {
818
+ "name": "ort_pointwise_3d_u8u8",
819
+ "provenance": {
820
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
821
+ "test": "QLinearConvTest.Conv3DTest",
822
+ "notes": "Compact ORT-valid pointwise adaptation of the 3D QLinearConv path."
823
+ },
824
+ "inputs": {
825
+ "x": {
826
+ "dtype": "uint8",
827
+ "shape": [1, 1, 2, 2, 2],
828
+ "data": { "kind": "values", "values": [10, 11, 12, 13, 14, 15, 16, 17] }
829
+ },
830
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.5] } },
831
+ "x_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [10] } },
832
+ "w": { "dtype": "uint8", "shape": [1, 1, 1, 1, 1], "data": { "kind": "values", "values": [14] } },
833
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.25] } },
834
+ "w_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [10] } },
835
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
836
+ "y_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [100] } }
837
+ },
838
+ "outputs": {
839
+ "y": {
840
+ "dtype": "uint8",
841
+ "shape": [1, 1, 2, 2, 2],
842
+ "data": { "kind": "values", "values": [100, 104, 108, 112, 116, 120, 124, 128] },
843
+ "tolerance": 0
844
+ }
845
+ },
846
+ "attrs": {}
847
+ },
848
+ {
849
+ "name": "ort_3d_u8s8_compact",
850
+ "provenance": {
851
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
852
+ "test": "QLinearConvTest.Conv3D_U8S8",
853
+ "notes": "Compact deterministic adaptation of ORT's 3D uint8-input/int8-weight QLinearConv coverage."
854
+ },
855
+ "attrs": { "strides": [1, 1, 1] },
856
+ "inputs": {
857
+ "x": {
858
+ "dtype": "uint8",
859
+ "shape": [1, 2, 2, 2, 2],
860
+ "data": { "kind": "values", "values": [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] }
861
+ },
862
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.05] } },
863
+ "x_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [4] } },
864
+ "w": { "dtype": "int8", "shape": [1, 2, 2, 1, 1], "data": { "kind": "values", "values": [2, -3, 4, 1] } },
865
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
866
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [0] } },
867
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.025] } },
868
+ "y_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [54] } }
869
+ },
870
+ "outputs": {
871
+ "y": {
872
+ "dtype": "uint8",
873
+ "shape": [1, 1, 1, 2, 2],
874
+ "tolerance": 0,
875
+ "data": { "kind": "values", "values": [62, 63, 64, 65] }
876
+ }
877
+ }
878
+ },
879
+ {
880
+ "name": "ort_3d_s8s8_compact",
881
+ "provenance": {
882
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
883
+ "test": "QLinearConvTest.Conv3D_S8S8",
884
+ "notes": "Signed companion for compact 3D QLinearConv: int8 input, int8 weights, and int8 output."
885
+ },
886
+ "attrs": { "strides": [1, 1, 1] },
887
+ "inputs": {
888
+ "x": {
889
+ "dtype": "int8",
890
+ "shape": [1, 2, 2, 2, 2],
891
+ "data": { "kind": "values", "values": [-4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] }
892
+ },
893
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.05] } },
894
+ "x_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [-4] } },
895
+ "w": { "dtype": "int8", "shape": [1, 2, 2, 1, 1], "data": { "kind": "values", "values": [2, -3, 4, 1] } },
896
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.125] } },
897
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [0] } },
898
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.025] } },
899
+ "y_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [-54] } }
900
+ },
901
+ "outputs": {
902
+ "y": {
903
+ "dtype": "int8",
904
+ "shape": [1, 1, 1, 2, 2],
905
+ "tolerance": 0,
906
+ "data": { "kind": "values", "values": [-46, -45, -44, -43] }
907
+ }
908
+ }
909
+ },
910
+ {
911
+ "name": "ort_per_channel_weight_zero_points_u8",
912
+ "provenance": {
913
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
914
+ "test": "QLinearConvTest.Conv2D_U8U8_PerChannelZeroPoints",
915
+ "notes": "Compact ORT-validated per-output-channel weight scales and zero-points without optional bias."
916
+ },
917
+ "inputs": {
918
+ "x": {
919
+ "dtype": "uint8",
920
+ "shape": [1, 2, 2, 2],
921
+ "data": { "kind": "values", "values": [120, 128, 136, 144, 100, 110, 130, 150] }
922
+ },
923
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
924
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
925
+ "w": { "dtype": "uint8", "shape": [2, 2, 1, 1], "data": { "kind": "values", "values": [100, 120, 140, 160] } },
926
+ "w_scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.5, 0.25] } },
927
+ "w_zero_point": { "dtype": "uint8", "shape": [2], "data": { "kind": "values", "values": [100, 150] } },
928
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.5] } },
929
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [127] } }
930
+ },
931
+ "outputs": {
932
+ "y": {
933
+ "dtype": "uint8",
934
+ "shape": [1, 2, 2, 2],
935
+ "data": { "kind": "values", "values": [0, 37, 137, 237, 102, 105, 119, 135] },
936
+ "tolerance": 0
937
+ }
938
+ },
939
+ "attrs": {}
940
+ },
941
+ {
942
+ "name": "ort_per_channel_weight_zero_points_u8s8",
943
+ "provenance": {
944
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
945
+ "test": "QLinearConvTest.Conv2D_U8S8_PerChannelZeroPoints",
946
+ "notes": "Compact mixed uint8 activation / int8 weight per-output-channel quantization case without optional bias."
947
+ },
948
+ "inputs": {
949
+ "x": {
950
+ "dtype": "uint8",
951
+ "shape": [1, 2, 2, 2],
952
+ "data": { "kind": "values", "values": [120, 128, 136, 144, 100, 110, 130, 150] }
953
+ },
954
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.2] } },
955
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
956
+ "w": { "dtype": "int8", "shape": [2, 2, 1, 1], "data": { "kind": "values", "values": [-8, 12, 16, -4] } },
957
+ "w_scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.25, 0.5] } },
958
+ "w_zero_point": { "dtype": "int8", "shape": [2], "data": { "kind": "values", "values": [-2, 6] } },
959
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
960
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
961
+ },
962
+ "outputs": {
963
+ "y": {
964
+ "dtype": "uint8",
965
+ "shape": [1, 2, 2, 2],
966
+ "data": { "kind": "values", "values": [59, 78, 124, 170, 208, 200, 152, 104] },
967
+ "tolerance": 0
968
+ }
969
+ },
970
+ "attrs": {}
971
+ },
972
+ {
973
+ "name": "ort_per_channel_weight_zero_points_s8s8_signed_output",
974
+ "provenance": {
975
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
976
+ "test": "QLinearConvTest.Conv2D_S8S8_PerChannelZeroPoints",
977
+ "notes": "Compact signed activation / signed weight projection with per-output-channel weight scales and zero-points."
978
+ },
979
+ "inputs": {
980
+ "x": {
981
+ "dtype": "int8",
982
+ "shape": [1, 2, 2, 2],
983
+ "data": { "kind": "values", "values": [-8, -4, 0, 4, 8, -12, 16, -16] }
984
+ },
985
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
986
+ "x_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [-3] } },
987
+ "w": { "dtype": "int8", "shape": [2, 2, 1, 1], "data": { "kind": "values", "values": [-10, 5, 12, -6] } },
988
+ "w_scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.5, 0.25] } },
989
+ "w_zero_point": { "dtype": "int8", "shape": [2], "data": { "kind": "values", "values": [-8, 4] } },
990
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
991
+ "y_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [-5] } }
992
+ },
993
+ "outputs": {
994
+ "y": {
995
+ "dtype": "int8",
996
+ "shape": [1, 2, 2, 2],
997
+ "data": { "kind": "values", "values": [71, -63, 115, -97, -43, 15, -47, 41] },
998
+ "tolerance": 0
999
+ }
1000
+ },
1001
+ "attrs": {}
1002
+ },
1003
+ {
1004
+ "name": "ort_grouped_per_channel_weight_zero_points_u8",
1005
+ "provenance": {
1006
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
1007
+ "test": "QLinearConvTest.Conv2D_U8U8_Groups_PerChannelZeroPoints",
1008
+ "notes": "Compact grouped 1x1 projection with per-output-channel weight scales and zero-points."
1009
+ },
1010
+ "attrs": { "group": 2 },
1011
+ "inputs": {
1012
+ "x": {
1013
+ "dtype": "uint8",
1014
+ "shape": [1, 4, 2, 2],
1015
+ "data": {
1016
+ "kind": "values",
1017
+ "values": [120, 128, 136, 144, 100, 110, 130, 150, 126, 130, 134, 138, 90, 120, 160, 200]
1018
+ }
1019
+ },
1020
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
1021
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
1022
+ "w": {
1023
+ "dtype": "uint8",
1024
+ "shape": [4, 2, 1, 1],
1025
+ "data": { "kind": "values", "values": [100, 120, 140, 160, 110, 130, 170, 90] }
1026
+ },
1027
+ "w_scale": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.5, 0.25, 0.4, 0.3] } },
1028
+ "w_zero_point": { "dtype": "uint8", "shape": [4], "data": { "kind": "values", "values": [100, 150, 120, 80] } },
1029
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.5] } },
1030
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [127] } }
1031
+ },
1032
+ "outputs": {
1033
+ "y": {
1034
+ "dtype": "uint8",
1035
+ "shape": [1, 4, 2, 2],
1036
+ "data": {
1037
+ "kind": "values",
1038
+ "values": [0, 37, 137, 237, 102, 105, 119, 135, 55, 107, 179, 251, 43, 142, 255, 255]
1039
+ },
1040
+ "tolerance": 0
1041
+ }
1042
+ }
1043
+ },
1044
+ {
1045
+ "name": "ort_depthwise_per_channel_weight_zero_points_s8s8",
1046
+ "provenance": {
1047
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
1048
+ "test": "QLinearConvTest.Conv2D_S8S8_DepthwiseFallback_PerChannelZeroPoints",
1049
+ "notes": "Compact depthwise projection with distinct per-output-channel weight scales and zero-points."
1050
+ },
1051
+ "attrs": { "group": 3, "pads": [1, 1, 1, 1] },
1052
+ "inputs": {
1053
+ "x": {
1054
+ "dtype": "int8",
1055
+ "shape": [1, 3, 3, 3],
1056
+ "data": {
1057
+ "kind": "values",
1058
+ "values": { "$ref": "#/fixtureArrays/ort_depthwise_per_channel_weight_zero_points_s8s8_input_x" }
1059
+ }
1060
+ },
1061
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.05] } },
1062
+ "x_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [4] } },
1063
+ "w": {
1064
+ "dtype": "int8",
1065
+ "shape": [3, 1, 3, 3],
1066
+ "data": {
1067
+ "kind": "values",
1068
+ "values": { "$ref": "#/fixtureArrays/ort_depthwise_per_channel_weight_zero_points_s8s8_input_w" }
1069
+ }
1070
+ },
1071
+ "w_scale": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [0.1, 0.13, 0.16] } },
1072
+ "w_zero_point": { "dtype": "int8", "shape": [3], "data": { "kind": "values", "values": [-10, -4, 6] } },
1073
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
1074
+ "y_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [-8] } }
1075
+ },
1076
+ "outputs": { "y": { "dtype": "int8", "shape": [1, 3, 3, 3], "tolerance": 0 } }
1077
+ },
1078
+ {
1079
+ "name": "ort_depthwise_uniform_per_channel_weight_zero_points_s8s8",
1080
+ "provenance": {
1081
+ "source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
1082
+ "test": "QLinearConvTest.Conv2D_S8S8_Depthwise_PerChannelUniformZeroPoints",
1083
+ "notes": "Uses a per-channel weight zero-point tensor with identical values, while keeping per-output-channel scales."
1084
+ },
1085
+ "attrs": { "group": 3, "pads": [1, 1, 1, 1] },
1086
+ "inputs": {
1087
+ "x": {
1088
+ "dtype": "int8",
1089
+ "shape": [1, 3, 3, 3],
1090
+ "data": {
1091
+ "kind": "values",
1092
+ "values": { "$ref": "#/fixtureArrays/ort_depthwise_per_channel_weight_zero_points_s8s8_input_x" }
1093
+ }
1094
+ },
1095
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.05] } },
1096
+ "x_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [4] } },
1097
+ "w": {
1098
+ "dtype": "int8",
1099
+ "shape": [3, 1, 3, 3],
1100
+ "data": {
1101
+ "kind": "values",
1102
+ "values": { "$ref": "#/fixtureArrays/ort_depthwise_per_channel_weight_zero_points_s8s8_input_w" }
1103
+ }
1104
+ },
1105
+ "w_scale": { "dtype": "float32", "shape": [3], "data": { "kind": "values", "values": [0.1, 0.13, 0.16] } },
1106
+ "w_zero_point": { "dtype": "int8", "shape": [3], "data": { "kind": "values", "values": [-7, -7, -7] } },
1107
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
1108
+ "y_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [-8] } }
1109
+ },
1110
+ "outputs": { "y": { "dtype": "int8", "shape": [1, 3, 3, 3], "tolerance": 0 } }
1111
+ },
1112
+ {
1113
+ "name": "dp4a_pointwise_u8s8_c8_batched",
1114
+ "inputs": {
1115
+ "x": {
1116
+ "dtype": "uint8",
1117
+ "shape": [2, 8, 3, 5],
1118
+ "data": {
1119
+ "kind": "cycle",
1120
+ "values": [0, 255, 128, 127, 56, 77, 3, 100, 200, 42, 5, 19, 64, 88, 33, 7, 125, 90, 211, 16]
1121
+ }
1122
+ },
1123
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
1124
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [113] } },
1125
+ "w": {
1126
+ "dtype": "int8",
1127
+ "shape": [6, 8, 1, 1],
1128
+ "data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/dp4a_pointwise_u8s8_c8_batched_input_w" } }
1129
+ },
1130
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.015] } },
1131
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [-7] } },
1132
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.4] } },
1133
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
1134
+ },
1135
+ "outputs": { "y": { "dtype": "uint8", "shape": [2, 6, 3, 5], "tolerance": 0 } },
1136
+ "attrs": {}
1137
+ },
1138
+ {
1139
+ "name": "dp4a_pointwise_s8s8_c12_signed_output",
1140
+ "inputs": {
1141
+ "x": {
1142
+ "dtype": "int8",
1143
+ "shape": [1, 12, 7, 11],
1144
+ "data": {
1145
+ "kind": "cycle",
1146
+ "values": { "$ref": "#/fixtureArrays/dp4a_pointwise_s8s8_c12_signed_output_input_x" }
1147
+ }
1148
+ },
1149
+ "x_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.025] } },
1150
+ "x_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [5] } },
1151
+ "w": {
1152
+ "dtype": "int8",
1153
+ "shape": [9, 12, 1, 1],
1154
+ "data": {
1155
+ "kind": "cycle",
1156
+ "values": [19, -64, 88, -33, 7, 125, -90, -128, 127, 0, -1, 56, -77, 3, 100, -100, 42, -5]
1157
+ }
1158
+ },
1159
+ "w_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.018] } },
1160
+ "w_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [-3] } },
1161
+ "y_scale": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [0.6] } },
1162
+ "y_zero_point": { "dtype": "int8", "shape": [], "data": { "kind": "values", "values": [3] } }
1163
+ },
1164
+ "outputs": { "y": { "dtype": "int8", "shape": [1, 9, 7, 11], "tolerance": 0 } },
1165
+ "attrs": {}
1166
+ },
1167
+ {
1168
+ "name": "empty_zero_dim",
1169
+ "attrs": { "strides": [1, 1] },
1170
+ "inputs": {
1171
+ "x": { "dtype": "uint8", "shape": [0, 4, 2, 2], "data": { "kind": "values", "values": [] } },
1172
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.5] } },
1173
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
1174
+ "w": { "dtype": "uint8", "shape": [2, 4, 1, 1], "data": { "kind": "constant", "value": 128 } },
1175
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
1176
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
1177
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.125] } },
1178
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
1179
+ },
1180
+ "outputs": { "y": { "dtype": "uint8", "shape": [0, 2, 2, 2], "tolerance": 0 } }
1181
+ },
1182
+ {
1183
+ "name": "dp4a_direct_3x3_u8s8_padding_spatial_oc_tails",
1184
+ "provenance": {
1185
+ "notes": "Compact route lock for the direct tiled 3x3 DP4A kernel. Nonzero zero points exercise exact correction; 10x11 spatial and 12-channel output tails cover partial workgroups."
1186
+ },
1187
+ "attrs": { "strides": [1, 1], "pads": [1, 1, 1, 1] },
1188
+ "inputs": {
1189
+ "x": {
1190
+ "dtype": "uint8",
1191
+ "shape": [1, 16, 10, 11],
1192
+ "data": { "kind": "cycle", "values": [0, 255, 128, 127, 56, 77, 3, 100, 200, 42, 5, 19] }
1193
+ },
1194
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
1195
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [113] } },
1196
+ "w": {
1197
+ "dtype": "int8",
1198
+ "shape": [12, 16, 3, 3],
1199
+ "data": { "kind": "cycle", "values": { "$ref": "#/fixtureArrays/dp4a_pointwise_u8s8_c8_batched_input_w" } }
1200
+ },
1201
+ "w_scale": {
1202
+ "dtype": "float32",
1203
+ "shape": [12],
1204
+ "data": {
1205
+ "kind": "values",
1206
+ "values": [0.011, 0.012, 0.013, 0.014, 0.015, 0.016, 0.017, 0.018, 0.019, 0.02, 0.021, 0.022]
1207
+ }
1208
+ },
1209
+ "w_zero_point": {
1210
+ "dtype": "int8",
1211
+ "shape": [12],
1212
+ "data": { "kind": "values", "values": [-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4] }
1213
+ },
1214
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.6] } },
1215
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [121] } }
1216
+ },
1217
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 12, 10, 11], "tolerance": 0 } }
1218
+ },
1219
+ {
1220
+ "name": "dp4a_im2col_u8s8_oc64_c16_3x3_24x24",
1221
+ "inputs": {
1222
+ "x": {
1223
+ "dtype": "uint8",
1224
+ "shape": [1, 16, 24, 24],
1225
+ "data": { "kind": "cycle", "values": [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 8] }
1226
+ },
1227
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
1228
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "cycle", "values": [2] } },
1229
+ "w": {
1230
+ "dtype": "int8",
1231
+ "shape": [64, 16, 3, 3],
1232
+ "data": { "kind": "cycle", "values": [2, -1, 3, -2, 1, 4, -3, 2, 0] }
1233
+ },
1234
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
1235
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "cycle", "values": [1] } },
1236
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.5] } },
1237
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "cycle", "values": [10] } }
1238
+ },
1239
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 64, 22, 22] } },
1240
+ "attrs": {}
1241
+ },
1242
+ {
1243
+ "name": "im2col_dp4a_requant_huge_finite_saturates_3x3",
1244
+ "attrs": { "strides": [1, 1], "pads": [0, 0, 0, 0] },
1245
+ "inputs": {
1246
+ "x": { "dtype": "uint8", "shape": [1, 16, 24, 24], "data": { "kind": "constant", "value": 255 } },
1247
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
1248
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
1249
+ "w": { "dtype": "int8", "shape": [16, 16, 3, 3], "data": { "kind": "constant", "value": 127 } },
1250
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
1251
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
1252
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1e-20] } },
1253
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
1254
+ },
1255
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 16, 22, 22], "tolerance": 0 } }
1256
+ },
1257
+ {
1258
+ "name": "im2col_dp4a_zero_spatial_output_empty",
1259
+ "attrs": { "strides": [1, 1], "pads": [0, 0, 0, 0] },
1260
+ "inputs": {
1261
+ "x": { "dtype": "uint8", "shape": [1, 1, 2, 2], "data": { "kind": "values", "values": [10, 20, 30, 40] } },
1262
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.5] } },
1263
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
1264
+ "w": { "dtype": "uint8", "shape": [1, 1, 3, 3], "data": { "kind": "constant", "value": 128 } },
1265
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
1266
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
1267
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.125] } },
1268
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
1269
+ },
1270
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 1, 0, 0], "tolerance": 0 } }
1271
+ },
1272
+ {
1273
+ "name": "per_channel_grouped_requant_oc_indexing_g4_i8",
1274
+ "attrs": { "group": 4, "strides": [1, 1], "pads": [1, 1, 1, 1] },
1275
+ "inputs": {
1276
+ "x": {
1277
+ "dtype": "int8",
1278
+ "shape": [1, 8, 4, 4],
1279
+ "data": { "kind": "cycle", "values": [-8, -4, 0, 4, 8, 12, -12, 16, -16, 3, -3, 6, -6, 9, -9, 12, 20, -20] }
1280
+ },
1281
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.05] } },
1282
+ "x_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [4] } },
1283
+ "w": {
1284
+ "dtype": "int8",
1285
+ "shape": [8, 2, 3, 3],
1286
+ "data": { "kind": "cycle", "values": [-10, -8, -6, -4, -2, 0, 2, 4, 6, 3, 5, 7, 9, 11, 13, 15, 17, 19] }
1287
+ },
1288
+ "w_scale": {
1289
+ "dtype": "float32",
1290
+ "shape": [8],
1291
+ "data": { "kind": "values", "values": [0.1, 0.13, 0.16, 0.09, 0.2, 0.07, 0.11, 0.14] }
1292
+ },
1293
+ "w_zero_point": {
1294
+ "dtype": "int8",
1295
+ "shape": [8],
1296
+ "data": { "kind": "values", "values": [-10, -4, 6, 0, 3, -7, 5, -2] }
1297
+ },
1298
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.25] } },
1299
+ "y_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [-5] } }
1300
+ },
1301
+ "outputs": { "y": { "dtype": "int8", "shape": [1, 8, 4, 4], "tolerance": 0 } }
1302
+ },
1303
+ {
1304
+ "name": "dp4a_pointwise_u8_weight_zero_128_bias_shift_edge",
1305
+ "inputs": {
1306
+ "x": {
1307
+ "dtype": "uint8",
1308
+ "shape": [1, 8, 6, 6],
1309
+ "data": { "kind": "cycle", "values": [0, 255, 1, 254, 128, 127, 200, 55, 33, 222, 99, 156] }
1310
+ },
1311
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
1312
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
1313
+ "w": {
1314
+ "dtype": "uint8",
1315
+ "shape": [4, 8, 1, 1],
1316
+ "data": { "kind": "cycle", "values": [0, 255, 128, 64, 200, 30, 129, 127, 1, 254, 100, 156] }
1317
+ },
1318
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.015] } },
1319
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
1320
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.4] } },
1321
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
1322
+ },
1323
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 4, 6, 6], "tolerance": 0 } },
1324
+ "attrs": {}
1325
+ },
1326
+ {
1327
+ "name": "conv3d_depth_dilation2_compact",
1328
+ "provenance": {
1329
+ "source": "ONNX QLinearConv-10 volumetric dilation semantics",
1330
+ "notes": "Covers a non-default depth dilation. Existing 3D fixtures kept dilationD=1 and no 3D performance fixture existed."
1331
+ },
1332
+ "attrs": { "strides": [1, 1, 1], "dilations": [2, 1, 1] },
1333
+ "inputs": {
1334
+ "x": {
1335
+ "dtype": "uint8",
1336
+ "shape": [1, 1, 4, 2, 2],
1337
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/same_upper_stride2_autopad_input_x" } }
1338
+ },
1339
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
1340
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
1341
+ "w": { "dtype": "uint8", "shape": [1, 1, 2, 1, 1], "data": { "kind": "values", "values": [1, 2] } },
1342
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
1343
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
1344
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
1345
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } }
1346
+ },
1347
+ "outputs": {
1348
+ "y": {
1349
+ "dtype": "uint8",
1350
+ "shape": [1, 1, 2, 2, 2],
1351
+ "data": { "kind": "values", "values": [19, 22, 25, 28, 31, 34, 37, 40] }
1352
+ }
1353
+ }
1354
+ },
1355
+ {
1356
+ "name": "dp4a_pointwise_per_channel_bias_compact",
1357
+ "provenance": {
1358
+ "source": "ONNX QLinearConv-10 per-output-channel quantization",
1359
+ "notes": "Locks the common per-channel weight-scale/zero-point plus int32 bias combination on an aligned pointwise DP4A shape."
1360
+ },
1361
+ "inputs": {
1362
+ "x": {
1363
+ "dtype": "uint8",
1364
+ "shape": [1, 4, 3, 3],
1365
+ "data": { "kind": "cycle", "values": [0, 17, 128, 255, 63, 191] }
1366
+ },
1367
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
1368
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
1369
+ "w": {
1370
+ "dtype": "int8",
1371
+ "shape": [4, 4, 1, 1],
1372
+ "data": { "kind": "cycle", "values": [-127, -13, 0, 11, 63, 126] }
1373
+ },
1374
+ "w_scale": {
1375
+ "dtype": "float32",
1376
+ "shape": [4],
1377
+ "data": { "kind": "values", "values": [0.01, 0.015, 0.02, 0.025] }
1378
+ },
1379
+ "w_zero_point": { "dtype": "int8", "shape": [4], "data": { "kind": "values", "values": [-3, 0, 2, 5] } },
1380
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.5] } },
1381
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [127] } },
1382
+ "B": { "dtype": "int32", "shape": [4], "data": { "kind": "values", "values": [-17, 0, 31, 4096] } }
1383
+ },
1384
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 4, 3, 3], "tolerance": 0 } },
1385
+ "attrs": {}
1386
+ },
1387
+ {
1388
+ "name": "feature_independent_x4_odd_width_k5_compact",
1389
+ "provenance": {
1390
+ "source": "ONNX QLinearConv-10 scalar quantization",
1391
+ "notes": "Locks four-column feature-independent accumulation with an odd output-width tail and K not divisible by four."
1392
+ },
1393
+ "inputs": {
1394
+ "x": {
1395
+ "dtype": "uint8",
1396
+ "shape": [1, 5, 3, 5],
1397
+ "data": { "kind": "cycle", "values": [0, 17, 63, 127, 128, 191, 255] }
1398
+ },
1399
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
1400
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [127] } },
1401
+ "w": {
1402
+ "dtype": "int8",
1403
+ "shape": [3, 5, 1, 1],
1404
+ "data": { "kind": "cycle", "values": [-127, -31, -1, 0, 7, 63, 126] }
1405
+ },
1406
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.015] } },
1407
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [-3] } },
1408
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.4] } },
1409
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [129] } }
1410
+ },
1411
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 3, 3, 5], "tolerance": 0 } },
1412
+ "attrs": {}
1413
+ },
1414
+ {
1415
+ "name": "dp4a_pointwise_padded_k66_baseline_16x16",
1416
+ "provenance": {
1417
+ "source": "ONNX QLinearConv-10 pointwise quantized convolution",
1418
+ "notes": "Locks the aligned-padding DP4A fallback for a realistic odd channel count on devices without shader-f16. Padding uses raw zero points and must remain exact."
1419
+ },
1420
+ "inputs": {
1421
+ "x": {
1422
+ "dtype": "uint8",
1423
+ "shape": [1, 66, 16, 16],
1424
+ "data": { "kind": "cycle", "values": [0, 17, 63, 127, 128, 191, 255] }
1425
+ },
1426
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
1427
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [127] } },
1428
+ "w": {
1429
+ "dtype": "int8",
1430
+ "shape": [8, 66, 1, 1],
1431
+ "data": { "kind": "cycle", "values": [-127, -31, -1, 0, 7, 63, 126] }
1432
+ },
1433
+ "w_scale": {
1434
+ "dtype": "float32",
1435
+ "shape": [8],
1436
+ "data": { "kind": "values", "values": [0.01, 0.012, 0.014, 0.016, 0.018, 0.02, 0.022, 0.024] }
1437
+ },
1438
+ "w_zero_point": {
1439
+ "dtype": "int8",
1440
+ "shape": [8],
1441
+ "data": { "kind": "values", "values": [-7, -3, -1, 0, 2, 4, 6, 9] }
1442
+ },
1443
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.4] } },
1444
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [129] } }
1445
+ },
1446
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 8, 16, 16], "tolerance": 0 } },
1447
+ "attrs": {}
1448
+ },
1449
+ {
1450
+ "name": "dp4a_im2col_padded_k81_baseline_exact",
1451
+ "provenance": {
1452
+ "notes": "Exactness and selector lock for no-feature 2-D im2col with odd K=81; im2col writes raw-x-zero-point padded rows and the shared pad pass extends per-row weight zero points."
1453
+ },
1454
+ "attrs": { "strides": [1, 1], "pads": [0, 0, 0, 0] },
1455
+ "inputs": {
1456
+ "x": {
1457
+ "dtype": "uint8",
1458
+ "shape": [1, 9, 18, 18],
1459
+ "data": { "kind": "cycle", "values": [0, 255, 127, 7, 200, 31, 129, 64, 99, 3, 254, 1, 88, 173, 240, 16, 5] }
1460
+ },
1461
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
1462
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [121] } },
1463
+ "w": {
1464
+ "dtype": "int8",
1465
+ "shape": [32, 9, 3, 3],
1466
+ "data": {
1467
+ "kind": "cycle",
1468
+ "values": { "$ref": "#/fixtureArrays/dp4a_pointwise_s8s8_c12_signed_output_input_x" }
1469
+ }
1470
+ },
1471
+ "w_scale": { "dtype": "float32", "shape": [32], "data": { "kind": "constant", "value": 0.03 } },
1472
+ "w_zero_point": { "dtype": "int8", "shape": [32], "data": { "kind": "constant", "value": -4 } },
1473
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [8.0] } },
1474
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } }
1475
+ },
1476
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 32, 16, 16], "tolerance": 0 } }
1477
+ },
1478
+ {
1479
+ "name": "dp4a_im2col_bias_tail_k27_baseline_exact",
1480
+ "provenance": {
1481
+ "notes": "Exact selector lock for the portable DP4A bias path with odd K=27; the shared core packs its final three weight/input lanes without shader-f16."
1482
+ },
1483
+ "attrs": { "strides": [1, 1], "pads": [0, 0, 0, 0] },
1484
+ "inputs": {
1485
+ "x": {
1486
+ "dtype": "uint8",
1487
+ "shape": [1, 3, 5, 5],
1488
+ "data": { "kind": "cycle", "values": [0, 17, 63, 127, 128, 191, 255] }
1489
+ },
1490
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
1491
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [121] } },
1492
+ "w": {
1493
+ "dtype": "int8",
1494
+ "shape": [8, 3, 3, 3],
1495
+ "data": { "kind": "cycle", "values": [-128, -77, -5, 0, 7, 42, 88, 127] }
1496
+ },
1497
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.03] } },
1498
+ "w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [-4] } },
1499
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [4.0] } },
1500
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [128] } },
1501
+ "B": {
1502
+ "dtype": "int32",
1503
+ "shape": [8],
1504
+ "data": { "kind": "values", "values": [-4096, -31, -1, 0, 1, 29, 1024, 4096] }
1505
+ }
1506
+ },
1507
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 8, 3, 3], "tolerance": 0 } }
1508
+ },
1509
+ {
1510
+ "name": "dp4a_im2col_3d_u8s8_b1c4m8_4x8x8_k3",
1511
+ "provenance": {
1512
+ "source": "ONNX QLinearConv-10 volumetric convolution",
1513
+ "notes": "Locks the 3-D im2col-to-DP4A route, including zero-point-filled spatial padding and per-output-channel quantization."
1514
+ },
1515
+ "attrs": { "pads": [1, 1, 1, 1, 1, 1] },
1516
+ "inputs": {
1517
+ "x": {
1518
+ "dtype": "uint8",
1519
+ "shape": [1, 4, 4, 8, 8],
1520
+ "data": { "kind": "cycle", "values": [0, 17, 63, 127, 128, 191, 255] }
1521
+ },
1522
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
1523
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [127] } },
1524
+ "w": {
1525
+ "dtype": "int8",
1526
+ "shape": [8, 4, 3, 3, 3],
1527
+ "data": { "kind": "cycle", "values": [-127, -31, -1, 0, 7, 63, 126] }
1528
+ },
1529
+ "w_scale": {
1530
+ "dtype": "float32",
1531
+ "shape": [8],
1532
+ "data": { "kind": "values", "values": [0.01, 0.012, 0.014, 0.016, 0.018, 0.02, 0.022, 0.024] }
1533
+ },
1534
+ "w_zero_point": {
1535
+ "dtype": "int8",
1536
+ "shape": [8],
1537
+ "data": { "kind": "values", "values": [-7, -3, -1, 0, 2, 4, 6, 9] }
1538
+ },
1539
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [2.0] } },
1540
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [129] } }
1541
+ },
1542
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 8, 4, 8, 8], "tolerance": 0 } }
1543
+ },
1544
+ {
1545
+ "name": "feature_independent_x4_group2_octile4_odd_width_compact",
1546
+ "provenance": {
1547
+ "notes": "Compact selector and exactness lock for grouped 3x3 QLinearConv with four output channels per group. Odd output width also exercises the feature-independent x4 spatial tail."
1548
+ },
1549
+ "attrs": { "group": 2, "strides": [1, 1], "pads": [1, 1, 1, 1] },
1550
+ "inputs": {
1551
+ "x": {
1552
+ "dtype": "uint8",
1553
+ "shape": [1, 4, 5, 5],
1554
+ "data": { "kind": "cycle", "values": [0, 17, 63, 127, 128, 191, 255] }
1555
+ },
1556
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
1557
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [127] } },
1558
+ "w": {
1559
+ "dtype": "int8",
1560
+ "shape": [8, 2, 3, 3],
1561
+ "data": { "kind": "cycle", "values": [-127, -31, -1, 0, 7, 63, 126] }
1562
+ },
1563
+ "w_scale": {
1564
+ "dtype": "float32",
1565
+ "shape": [8],
1566
+ "data": { "kind": "values", "values": [0.01, 0.012, 0.014, 0.016, 0.018, 0.02, 0.022, 0.024] }
1567
+ },
1568
+ "w_zero_point": {
1569
+ "dtype": "int8",
1570
+ "shape": [8],
1571
+ "data": { "kind": "values", "values": [-7, -3, -1, 0, 2, 4, 6, 9] }
1572
+ },
1573
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [2.0] } },
1574
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [129] } }
1575
+ },
1576
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 8, 5, 5], "tolerance": 0 } }
1577
+ },
1578
+ {
1579
+ "name": "same_lower_stride2_autopad",
1580
+ "provenance": {
1581
+ "source": "ONNX Runtime QLinearConv-10 CPUExecutionProvider",
1582
+ "notes": "ONNX auto_pad SAME_LOWER puts the odd padding element at the START of the axis, so unlike its SAME_UPPER twin this case has a non-zero leading pad and fails if the kernel is handed the explicit pad attributes instead of the derived ones."
1583
+ },
1584
+ "attrs": { "auto_pad": "SAME_LOWER", "strides": [2, 2] },
1585
+ "inputs": {
1586
+ "x": {
1587
+ "dtype": "uint8",
1588
+ "shape": [1, 1, 4, 4],
1589
+ "data": { "kind": "values", "values": { "$ref": "#/fixtureArrays/same_upper_stride2_autopad_input_x" } }
1590
+ },
1591
+ "x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
1592
+ "x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
1593
+ "w": { "dtype": "uint8", "shape": [1, 1, 3, 3], "data": { "kind": "constant", "value": 1 } },
1594
+ "w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
1595
+ "w_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
1596
+ "y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
1597
+ "y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } }
1598
+ },
1599
+ "outputs": { "y": { "dtype": "uint8", "shape": [1, 1, 2, 2], "tolerance": 0 } }
1600
+ }
1601
+ ]
1602
+ }