sync 91d990483a17
Browse files- README.md +23 -19
- build/webgpu/bench.json +0 -1
- build/webgpu/conv-int-accumulate-spatial.wgsl.jinja +34 -33
- build/webgpu/conv-int-im2col-spatial.wgsl.jinja +18 -17
- build/webgpu/manifest.json +0 -0
- build/webgpu/metadata.json +35 -13
- build/webgpu/qlinear-conv-dp4a-direct-3x3.wgsl.jinja +23 -23
- build/webgpu/qlinear-conv-nchw-x4.wgsl.jinja +15 -15
- build/webgpu/qlinear-conv-requantize.wgsl.jinja +15 -15
- build/webgpu/quant-dp4a-matmul.wgsl.jinja +29 -98
- build/webgpu/quant-pad-k-matrices.wgsl.jinja +1 -2
- build/webgpu/test.json +103 -10
README.md
CHANGED
|
@@ -12,29 +12,29 @@ tags:
|
|
| 12 |
|
| 13 |
## Description
|
| 14 |
|
| 15 |
-
Performs convolution on a quantized input using quantized weights, producing a quantized output.
|
| 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 |
|
| 22 |
-
| --- | --- | --- | --- | --- | --- |
|
| 23 |
-
| `x` | `
|
| 24 |
-
| `x_scale` | `
|
| 25 |
-
| `x_zero_point` | `
|
| 26 |
-
| `w` | `
|
| 27 |
-
| `w_scale` | `
|
| 28 |
-
| `w_zero_point` | `
|
| 29 |
-
| `y_scale` | `
|
| 30 |
-
| `y_zero_point` | `
|
| 31 |
-
| `B` | `
|
| 32 |
|
| 33 |
## Outputs
|
| 34 |
|
| 35 |
-
| Name |
|
| 36 |
-
| --- | --- | --- | --- | --- | --- |
|
| 37 |
-
| `y` | `
|
| 38 |
|
| 39 |
## Attributes
|
| 40 |
|
|
@@ -43,8 +43,8 @@ Attributes and default values (overridable per request):
|
|
| 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. |
|
|
@@ -60,7 +60,7 @@ Attributes and default values (overridable per request):
|
|
| 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
|
|
@@ -74,10 +74,14 @@ Attributes and default values (overridable per request):
|
|
| 74 |
|
| 75 |
## Use with `@huggingface/kernels`
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
|
|
|
| 81 |
|
| 82 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 83 |
|
|
|
|
| 12 |
|
| 13 |
## Description
|
| 14 |
|
| 15 |
+
Performs convolution on a quantized input using quantized weights, producing a quantized output. Input and output quantization use per-tensor scales and zero points; weight quantization may be 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 | Logical dtype | Rank | Shape | Description | Presence |
|
| 22 |
+
| --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `x` | `TX` | — | — | Quantized input data tensor shaped `(N x C x D1 x ... x Dn)`. | required |
|
| 24 |
+
| `x_scale` | `TF` | — | — | Per-tensor scale for input `x`. | required |
|
| 25 |
+
| `x_zero_point` | `TX` | — | — | Per-tensor zero point for input `x`. | required |
|
| 26 |
+
| `w` | `TW` | — | — | Quantized weight tensor shaped `(M x C/group x k1 x ... x kn)`. | required |
|
| 27 |
+
| `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` | `TW` | — | — | Zero point for weights `w`; scalar or 1-D of length M matching `w_scale`. | required |
|
| 29 |
+
| `y_scale` | `TF` | — | — | Per-tensor scale for output `y`. | required |
|
| 30 |
+
| `y_zero_point` | `TY` | — | — | Per-tensor zero point for output `y`. | required |
|
| 31 |
+
| `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 | Logical dtype | Rank | Shape | Description | Presence |
|
| 36 |
+
| --- | --- | --- | --- | --- | --- |
|
| 37 |
+
| `y` | `TY` | same as `x` | derived | Quantized output tensor; shape is determined by kernel size, strides, and padding. | required |
|
| 38 |
|
| 39 |
## Attributes
|
| 40 |
|
|
|
|
| 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 |
| `dilations` | — | Optional dilation factors, one positive integer per spatial axis. Omission means all ones. |
|
| 47 |
+
| `group` | `1` | Number of groups that input and output channels are split into; defaults to 1. |
|
| 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. |
|
|
|
|
| 60 |
|
| 61 |
## Files
|
| 62 |
|
| 63 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, per-variant templates, 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
|
|
|
|
| 74 |
|
| 75 |
## Use with `@huggingface/kernels`
|
| 76 |
|
| 77 |
+
```sh
|
| 78 |
+
npm install --save-exact @huggingface/kernels@0.0.1-preview.2
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
Required output shapes and logical data types are inferred from the supplied inputs and attributes; result tensors are allocated automatically.
|
| 82 |
|
| 83 |
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 84 |
+
It follows the `v1` branch as fixes land. To pin exact artifact bytes, pass a 40-character commit `revision` instead of `version`.
|
| 85 |
|
| 86 |
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 87 |
|
build/webgpu/bench.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
{
|
| 2 |
-
"op": "ai.onnx.QLinearConv",
|
| 3 |
"tunableSpace": {
|
| 4 |
"DP4A_DIRECT_SPATIAL_TILE": [8, 16],
|
| 5 |
"DP4A_DIRECT_OC_TILE": [8, 16],
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"tunableSpace": {
|
| 3 |
"DP4A_DIRECT_SPATIAL_TILE": [8, 16],
|
| 4 |
"DP4A_DIRECT_OC_TILE": [8, 16],
|
build/webgpu/conv-int-accumulate-spatial.wgsl.jinja
CHANGED
|
@@ -1,37 +1,36 @@
|
|
| 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 |
-
//
|
| 5 |
{% elif note == "limit" %}
|
| 6 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 7 |
-
//
|
| 8 |
{% elif note == "device-axis" %}
|
| 9 |
-
// The flat dispatch is folded across x/y at
|
| 10 |
-
//
|
| 11 |
{% elif note == "vec4-limit" %}
|
| 12 |
-
// 2D-folded flat vec4 index: gid.y carries the high bits past the
|
| 13 |
-
//
|
| 14 |
{% elif note == "element-limit" %}
|
| 15 |
// 2D-folded flat element index: gid.y carries the high bits past the
|
| 16 |
-
//
|
| 17 |
{% elif note == "dispatch" %}
|
| 18 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 19 |
-
//
|
| 20 |
{% endif %}
|
| 21 |
{% if bound == "" %}
|
| 22 |
-
let {{ name }} = gid.x + gid.y *
|
| 23 |
{%- elif guardInline %}
|
| 24 |
-
let {{ name }} = gid.x + gid.y *
|
| 25 |
if ({{ name }} >= {{ bound }}) { return; }
|
| 26 |
{%- else %}
|
| 27 |
-
let {{ name }} = gid.x + gid.y *
|
| 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 {
|
|
@@ -76,13 +75,13 @@ fn read_w_zero({% if wZeroPerChannel %}oc: u32{% endif %}) -> i32 {
|
|
| 76 |
}
|
| 77 |
|
| 78 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 79 |
-
fn main(@builtin(global_invocation_id) gid: 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
|
| 86 |
let od = t % params.outD;
|
| 87 |
t = t / params.outD;
|
| 88 |
{% endif %}
|
|
@@ -94,28 +93,30 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>, @builtin(num_workgroups)
|
|
| 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
|
| 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 |
-
{
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
{
|
| 110 |
-
|
|
|
|
| 111 |
{% else %}
|
| 112 |
-
|
| 113 |
-
|
| 114 |
{% endif %}
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
|
|
|
| 119 |
}
|
| 120 |
{% endif %}
|
| 121 |
}
|
|
|
|
| 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 dispatch's
|
| 4 |
+
// per-axis workgroup fold width (outputs > 16.7M elements).
|
| 5 |
{% elif note == "limit" %}
|
| 6 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 7 |
+
// per-axis workgroup fold width.
|
| 8 |
{% elif note == "device-axis" %}
|
| 9 |
+
// The flat dispatch is folded across x/y at a fixed per-axis workgroup
|
| 10 |
+
// width; 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 dispatch's
|
| 13 |
+
// per-axis workgroup fold width (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 |
+
// dispatch's per-axis workgroup fold width.
|
| 17 |
{% elif note == "dispatch" %}
|
| 18 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 19 |
+
// per-axis workgroup fold width.
|
| 20 |
{% endif %}
|
| 21 |
{% if bound == "" %}
|
| 22 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 23 |
{%- elif guardInline %}
|
| 24 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 25 |
if ({{ name }} >= {{ bound }}) { return; }
|
| 26 |
{%- else %}
|
| 27 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 28 |
if ({{ name }} >= {{ bound }}) {
|
| 29 |
return;
|
| 30 |
}
|
| 31 |
{%- endif %}
|
| 32 |
{% endmacro %}
|
| 33 |
|
|
|
|
| 34 |
{{ env.wgsl.resourceDeclarations }}
|
| 35 |
|
| 36 |
fn read_x(index: u32) -> i32 {
|
|
|
|
| 75 |
}
|
| 76 |
|
| 77 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 78 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 79 |
{{ flat_index_2d("index", guardInline=true) }}
|
| 80 |
let ow = index % params.outW;
|
| 81 |
var t = index / params.outW;
|
| 82 |
let oh = t % params.outH;
|
| 83 |
t = t / params.outH;
|
| 84 |
+
{% if spatialRankSpec == 3 %}
|
| 85 |
let od = t % params.outD;
|
| 86 |
t = t / params.outD;
|
| 87 |
{% endif %}
|
|
|
|
| 93 |
var acc = 0i;
|
| 94 |
for (var ic = 0u; ic < params.weightInChannels; ic = ic + 1u) {
|
| 95 |
let input_channel = group * params.inChannelsPerGroup + ic;
|
| 96 |
+
{% if spatialRankSpec == 3 %}
|
| 97 |
for (var kd = 0u; kd < params.kernelD; kd = kd + 1u) {
|
| 98 |
let id = i32(od * params.strideD + kd * params.dilationD) - params.padD;
|
| 99 |
if (id < 0 || id >= i32(params.inD)) { continue; }
|
| 100 |
{% endif %}
|
| 101 |
+
{% filter indent(2 if spatialRankSpec == 3 else 0, true) %}
|
| 102 |
+
for (var kh = 0u; kh < params.kernelH; kh = kh + 1u) {
|
| 103 |
+
let ih = i32(oh * params.strideH + kh * params.dilationH) - params.padH;
|
| 104 |
+
if (ih < 0 || ih >= i32(params.inH)) { continue; }
|
| 105 |
+
for (var kw = 0u; kw < params.kernelW; kw = kw + 1u) {
|
| 106 |
+
let iw = i32(ow * params.strideW + kw * params.dilationW) - params.padW;
|
| 107 |
+
if (iw < 0 || iw >= i32(params.inW)) { continue; }
|
| 108 |
+
{% if spatialRankSpec == 3 %}
|
| 109 |
+
let x_index = (((batch * params.inChannels + input_channel) * params.inD + u32(id)) * params.inH + u32(ih)) * params.inW + u32(iw);
|
| 110 |
+
let w_index = (((oc * params.weightInChannels + ic) * params.kernelD + kd) * params.kernelH + kh) * params.kernelW + kw;
|
| 111 |
{% else %}
|
| 112 |
+
let x_index = ((batch * params.inChannels + input_channel) * params.inH + u32(ih)) * params.inW + u32(iw);
|
| 113 |
+
let w_index = ((oc * params.weightInChannels + ic) * params.kernelH + kh) * params.kernelW + kw;
|
| 114 |
{% endif %}
|
| 115 |
+
acc = acc + (read_x(x_index) - xzp) * (read_w(w_index) - wzp);
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
{% endfilter %}
|
| 119 |
+
{% if spatialRankSpec == 3 %}
|
| 120 |
}
|
| 121 |
{% endif %}
|
| 122 |
}
|
build/webgpu/conv-int-im2col-spatial.wgsl.jinja
CHANGED
|
@@ -5,27 +5,28 @@
|
|
| 5 |
* Optional extra K rows contain the raw input zero point to align the DP4A
|
| 6 |
* reduction. */
|
| 7 |
{{ env.wgsl.resourceDeclarations }}
|
| 8 |
-
{%
|
|
|
|
| 9 |
|
| 10 |
-
const KERNEL_D: u32 = {{
|
| 11 |
{% endif %}
|
| 12 |
-
const KERNEL_H: u32 = {{
|
| 13 |
-
const KERNEL_W: u32 = {{
|
| 14 |
const KERNEL_HW: u32 = KERNEL_H * KERNEL_W;
|
| 15 |
-
{% if
|
| 16 |
const KSIZE: u32 = KERNEL_D * KERNEL_HW;
|
| 17 |
-
const STRIDE_D: u32 = {{
|
| 18 |
-
const DILATION_D: u32 = {{
|
| 19 |
-
const PAD_FRONT: i32 = {{
|
| 20 |
{% else %}
|
| 21 |
const KSIZE: u32 = KERNEL_HW;
|
| 22 |
{% endif %}
|
| 23 |
-
const STRIDE_H: u32 = {{
|
| 24 |
-
const STRIDE_W: u32 = {{
|
| 25 |
-
const DILATION_H: u32 = {{
|
| 26 |
-
const DILATION_W: u32 = {{
|
| 27 |
-
const PAD_TOP: i32 = {{
|
| 28 |
-
const PAD_LEFT: i32 = {{
|
| 29 |
|
| 30 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 31 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
@@ -35,7 +36,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
| 35 |
}
|
| 36 |
let k = gid.y;
|
| 37 |
let batch = gid.z;
|
| 38 |
-
{% if
|
| 39 |
if (k >= params.sourceKRows) {
|
| 40 |
cols[(batch * params.kRows + k) * params.outCount + pos] = x_zero_point[0];
|
| 41 |
return;
|
|
@@ -43,7 +44,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
| 43 |
{% endif %}
|
| 44 |
let ic = k / KSIZE;
|
| 45 |
let kq = k - ic * KSIZE;
|
| 46 |
-
{% if
|
| 47 |
let kd = kq / KERNEL_HW;
|
| 48 |
let khw = kq - kd * KERNEL_HW;
|
| 49 |
let kh = khw / KERNEL_W;
|
|
@@ -63,7 +64,7 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
| 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
|
| 67 |
if (
|
| 68 |
id >= 0 && id < i32(params.inD)
|
| 69 |
&& ih >= 0 && ih < i32(params.inH)
|
|
|
|
| 5 |
* Optional extra K rows contain the raw input zero point to align the DP4A
|
| 6 |
* reduction. */
|
| 7 |
{{ env.wgsl.resourceDeclarations }}
|
| 8 |
+
{% set padK = padK is defined and padK %}
|
| 9 |
+
{% if spatialRankSpec == 3 %}
|
| 10 |
|
| 11 |
+
const KERNEL_D: u32 = {{ kernelDSpec }}u;
|
| 12 |
{% endif %}
|
| 13 |
+
const KERNEL_H: u32 = {{ kernelHSpec }}u;
|
| 14 |
+
const KERNEL_W: u32 = {{ kernelWSpec }}u;
|
| 15 |
const KERNEL_HW: u32 = KERNEL_H * KERNEL_W;
|
| 16 |
+
{% if spatialRankSpec == 3 %}
|
| 17 |
const KSIZE: u32 = KERNEL_D * KERNEL_HW;
|
| 18 |
+
const STRIDE_D: u32 = {{ strideDSpec }}u;
|
| 19 |
+
const DILATION_D: u32 = {{ dilationDSpec }}u;
|
| 20 |
+
const PAD_FRONT: i32 = {{ padFrontSpec }};
|
| 21 |
{% else %}
|
| 22 |
const KSIZE: u32 = KERNEL_HW;
|
| 23 |
{% endif %}
|
| 24 |
+
const STRIDE_H: u32 = {{ strideHSpec }}u;
|
| 25 |
+
const STRIDE_W: u32 = {{ strideWSpec }}u;
|
| 26 |
+
const DILATION_H: u32 = {{ dilationHSpec }}u;
|
| 27 |
+
const DILATION_W: u32 = {{ dilationWSpec }}u;
|
| 28 |
+
const PAD_TOP: i32 = {{ padTopSpec }};
|
| 29 |
+
const PAD_LEFT: i32 = {{ padLeftSpec }};
|
| 30 |
|
| 31 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 32 |
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
|
|
| 36 |
}
|
| 37 |
let k = gid.y;
|
| 38 |
let batch = gid.z;
|
| 39 |
+
{% if padK %}
|
| 40 |
if (k >= params.sourceKRows) {
|
| 41 |
cols[(batch * params.kRows + k) * params.outCount + pos] = x_zero_point[0];
|
| 42 |
return;
|
|
|
|
| 44 |
{% endif %}
|
| 45 |
let ic = k / KSIZE;
|
| 46 |
let kq = k - ic * KSIZE;
|
| 47 |
+
{% if spatialRankSpec == 3 %}
|
| 48 |
let kd = kq / KERNEL_HW;
|
| 49 |
let khw = kq - kd * KERNEL_HW;
|
| 50 |
let kh = khw / KERNEL_W;
|
|
|
|
| 64 |
let ih = i32(oh * STRIDE_H + kh * DILATION_H) - PAD_TOP;
|
| 65 |
let iw = i32(ow * STRIDE_W + kw * DILATION_W) - PAD_LEFT;
|
| 66 |
var value: {{ bScalar }} = x_zero_point[0];
|
| 67 |
+
{% if spatialRankSpec == 3 %}
|
| 68 |
if (
|
| 69 |
id >= 0 && id < i32(params.inD)
|
| 70 |
&& ih >= 0 && ih < i32(params.inH)
|
build/webgpu/manifest.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/webgpu/metadata.json
CHANGED
|
@@ -1,24 +1,46 @@
|
|
| 1 |
{
|
| 2 |
"name": "ai.onnx.QLinearConv",
|
| 3 |
-
"id": "
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
-
"bench.json": "
|
| 11 |
-
"conv-int-accumulate-spatial.wgsl.jinja": "+
|
| 12 |
-
"conv-int-im2col-spatial.wgsl.jinja": "
|
| 13 |
-
"manifest.json": "
|
| 14 |
-
"qlinear-conv-dp4a-direct-3x3.wgsl.jinja": "
|
| 15 |
-
"qlinear-conv-nchw-x4.wgsl.jinja": "
|
| 16 |
-
"qlinear-conv-requantize.wgsl.jinja": "
|
| 17 |
-
"quant-dp4a-matmul.wgsl.jinja": "
|
| 18 |
-
"quant-pad-k-matrices.wgsl.jinja": "
|
| 19 |
-
"test.json": "
|
| 20 |
}
|
| 21 |
},
|
| 22 |
-
"provenance": { "kernel": { "sha": "
|
| 23 |
-
"webgpu": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"name": "ai.onnx.QLinearConv",
|
| 3 |
+
"id": "_ai_onnx_qlinearconv_webgpu_5dc8940",
|
| 4 |
"version": 1,
|
| 5 |
"license": "Apache-2.0",
|
| 6 |
"backend": { "type": "webgpu" },
|
| 7 |
"digest": {
|
| 8 |
"algorithm": "sha256",
|
| 9 |
"files": {
|
| 10 |
+
"bench.json": "spUQwS0Tr+3Vdfkxd9WepuT6g6ezyeV6aMao4r7OsdY=",
|
| 11 |
+
"conv-int-accumulate-spatial.wgsl.jinja": "TkacF0/fJ1yShdyPJWePK/+8XJFaV+ZDKWT6XvHOCI8=",
|
| 12 |
+
"conv-int-im2col-spatial.wgsl.jinja": "3vFeBqBVtyYtxV1yteSQ3O7skFur08E3vDoVtdwcYAo=",
|
| 13 |
+
"manifest.json": "VInAZzgFiEXRlHexx+MCXfvzy6hnwIGSsbdY6pw6Nas=",
|
| 14 |
+
"qlinear-conv-dp4a-direct-3x3.wgsl.jinja": "xt3DqIwhlzomIiLMuMZ+8F0+Xqg7AWfGCbeZnNT2rLU=",
|
| 15 |
+
"qlinear-conv-nchw-x4.wgsl.jinja": "XT1MIv1iIEOhj0keQUkU5cau6Nc/CTJCAgu+PardWaQ=",
|
| 16 |
+
"qlinear-conv-requantize.wgsl.jinja": "JCGp/e1wWm22EQlsYPNreGtdualHITd5dmvA9kGYZCk=",
|
| 17 |
+
"quant-dp4a-matmul.wgsl.jinja": "x1yY2y4Wb6vlzMVeRw+DbEmfvJdPBHkMF8ATG34ZGis=",
|
| 18 |
+
"quant-pad-k-matrices.wgsl.jinja": "hsLWgSTbpXBnsIOyrIzsmhQzVyIYALaFvx62VN46KyY=",
|
| 19 |
+
"test.json": "z2krhaHYtSozWBIW2J1ZKnlpd1S7dDdTQwYq83sf/7g="
|
| 20 |
}
|
| 21 |
},
|
| 22 |
+
"provenance": { "kernel": { "sha": "91d990483a174128daf7673f3f37a7c890493ae1", "dirty": false } },
|
| 23 |
+
"webgpu": {
|
| 24 |
+
"manifestSpec": "2.0",
|
| 25 |
+
"variants": {
|
| 26 |
+
"dp4a_pointwise_1x1_padded_k_baseline": ["quant-dp4a-matmul.wgsl.jinja", "quant-pad-k-matrices.wgsl.jinja"],
|
| 27 |
+
"dp4a_im2col_nchw_padded_k_baseline": ["conv-int-im2col-spatial.wgsl.jinja", "quant-dp4a-matmul.wgsl.jinja", "quant-pad-k-matrices.wgsl.jinja"],
|
| 28 |
+
"dp4a_direct_3x3_nchw_u8s8": ["qlinear-conv-dp4a-direct-3x3.wgsl.jinja"],
|
| 29 |
+
"dp4a_pointwise_1x1": ["quant-dp4a-matmul.wgsl.jinja"],
|
| 30 |
+
"nchw2d_feature_independent_x4": ["qlinear-conv-nchw-x4.wgsl.jinja"],
|
| 31 |
+
"ncdhw3d_per_channel_quantization": ["conv-int-accumulate-spatial.wgsl.jinja", "qlinear-conv-requantize.wgsl.jinja"],
|
| 32 |
+
"ncw1d_scalar_quantization": ["conv-int-accumulate-spatial.wgsl.jinja", "qlinear-conv-requantize.wgsl.jinja"],
|
| 33 |
+
"ncdhw3d_scalar_quantization": ["conv-int-accumulate-spatial.wgsl.jinja", "qlinear-conv-requantize.wgsl.jinja"],
|
| 34 |
+
"nchw2d_scalar_quantization": ["conv-int-accumulate-spatial.wgsl.jinja", "qlinear-conv-requantize.wgsl.jinja"],
|
| 35 |
+
"ncw1d_scalar_quantization_bias": ["conv-int-accumulate-spatial.wgsl.jinja", "qlinear-conv-requantize.wgsl.jinja"],
|
| 36 |
+
"ncdhw3d_scalar_quantization_bias": ["conv-int-accumulate-spatial.wgsl.jinja", "qlinear-conv-requantize.wgsl.jinja"],
|
| 37 |
+
"ncw1d_per_channel_quantization": ["conv-int-accumulate-spatial.wgsl.jinja", "qlinear-conv-requantize.wgsl.jinja"],
|
| 38 |
+
"dp4a_im2col_ncdhw3d_scalar_quantization": ["conv-int-im2col-spatial.wgsl.jinja", "quant-dp4a-matmul.wgsl.jinja"],
|
| 39 |
+
"dp4a_im2col_nchw_bias_quantization": ["conv-int-im2col-spatial.wgsl.jinja", "quant-dp4a-matmul.wgsl.jinja"],
|
| 40 |
+
"dp4a_im2col_nchw_scalar_quantization": ["conv-int-im2col-spatial.wgsl.jinja", "quant-dp4a-matmul.wgsl.jinja"],
|
| 41 |
+
"nchw2d_per_channel_quantization": ["conv-int-accumulate-spatial.wgsl.jinja", "qlinear-conv-requantize.wgsl.jinja"],
|
| 42 |
+
"nchw2d_per_channel_quantization_bias": ["conv-int-accumulate-spatial.wgsl.jinja", "qlinear-conv-requantize.wgsl.jinja"],
|
| 43 |
+
"nchw2d_scalar_quantization_bias": ["conv-int-accumulate-spatial.wgsl.jinja", "qlinear-conv-requantize.wgsl.jinja"]
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
}
|
build/webgpu/qlinear-conv-dp4a-direct-3x3.wgsl.jinja
CHANGED
|
@@ -13,17 +13,17 @@ fn dot4_packed(a_word: u32, b_word: u32) -> i32 {
|
|
| 13 |
}
|
| 14 |
|
| 15 |
|
| 16 |
-
const IN_C: u32 = {{
|
| 17 |
-
const IN_H: u32 = {{
|
| 18 |
-
const IN_W: u32 = {{
|
| 19 |
-
const OUT_C: u32 = {{
|
| 20 |
-
const OUT_H: u32 = {{
|
| 21 |
-
const OUT_W: u32 = {{
|
| 22 |
-
const BATCH: u32 = {{
|
| 23 |
-
|
| 24 |
-
const SPATIAL_TILE: u32 = {{
|
| 25 |
-
const OC_TILE: u32 = {{
|
| 26 |
-
const IC_TILE: u32 = {{
|
| 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;
|
|
@@ -61,24 +61,24 @@ fn sum4_packed(v: u32) -> i32 {
|
|
| 61 |
+ signed_byte(v, 16u) + signed_byte(v, 24u);
|
| 62 |
}
|
| 63 |
|
| 64 |
-
fn read_w_zero({% if
|
| 65 |
-
{% if
|
| 66 |
return w_zero_point[oc];
|
| 67 |
{% else %}
|
| 68 |
return w_zero_point[0];
|
| 69 |
{% endif %}
|
| 70 |
}
|
| 71 |
|
| 72 |
-
fn read_w_scale({% if
|
| 73 |
-
{% if
|
| 74 |
return w_scale[oc];
|
| 75 |
{% else %}
|
| 76 |
return w_scale[0];
|
| 77 |
{% endif %}
|
| 78 |
}
|
| 79 |
|
| 80 |
-
fn requantize(acc: i32{% if
|
| 81 |
-
let scaled = f32(acc) * x_scale[0] * read_w_scale({% if
|
| 82 |
let q = clamp(
|
| 83 |
i32(clamp(round(scaled), -2.0e9, 2.0e9)) + i32(y_zero_point[0]),
|
| 84 |
0,
|
|
@@ -87,7 +87,7 @@ fn requantize(acc: i32{% if source.wScalePerChannel %}, oc: u32{% endif %}) -> u
|
|
| 87 |
return u32(q);
|
| 88 |
}
|
| 89 |
|
| 90 |
-
@compute @workgroup_size({{
|
| 91 |
fn main(
|
| 92 |
@builtin(workgroup_id) wg: vec3<u32>,
|
| 93 |
@builtin(local_invocation_id) lid: vec3<u32>
|
|
@@ -107,7 +107,7 @@ fn main(
|
|
| 107 |
}
|
| 108 |
workgroupBarrier();
|
| 109 |
|
| 110 |
-
{% for oc in range(
|
| 111 |
var acc{{ oc }} = 0i;
|
| 112 |
{% endfor %}
|
| 113 |
var sumX = 0i;
|
|
@@ -167,7 +167,7 @@ fn main(
|
|
| 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(
|
| 171 |
acc{{ oc }} += dot4_packed(wTile[({{ oc }}u * IC_WORDS + channelWord) * 9u + tap], xp);
|
| 172 |
{% endfor %}
|
| 173 |
}
|
|
@@ -181,17 +181,17 @@ fn main(
|
|
| 181 |
return;
|
| 182 |
}
|
| 183 |
|
| 184 |
-
{% for oc in range(
|
| 185 |
{
|
| 186 |
let outOc = ocBase + {{ oc }}u;
|
| 187 |
if (outOc < OUT_C) {
|
| 188 |
-
let ca = -read_w_zero({% if
|
| 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
|
| 195 |
}
|
| 196 |
}
|
| 197 |
{% endfor %}
|
|
|
|
| 13 |
}
|
| 14 |
|
| 15 |
|
| 16 |
+
const IN_C: u32 = {{ inChannels }}u;
|
| 17 |
+
const IN_H: u32 = {{ inHeight }}u;
|
| 18 |
+
const IN_W: u32 = {{ inWidth }}u;
|
| 19 |
+
const OUT_C: u32 = {{ outChannels }}u;
|
| 20 |
+
const OUT_H: u32 = {{ outHeight }}u;
|
| 21 |
+
const OUT_W: u32 = {{ outWidth }}u;
|
| 22 |
+
const BATCH: u32 = {{ batch }}u;
|
| 23 |
+
|
| 24 |
+
const SPATIAL_TILE: u32 = {{ spatialTile }}u;
|
| 25 |
+
const OC_TILE: u32 = {{ ocTile }}u;
|
| 26 |
+
const IC_TILE: u32 = {{ 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;
|
|
|
|
| 61 |
+ signed_byte(v, 16u) + signed_byte(v, 24u);
|
| 62 |
}
|
| 63 |
|
| 64 |
+
fn read_w_zero({% if wZeroPerChannel %}oc: u32{% endif %}) -> i32 {
|
| 65 |
+
{% if 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 wScalePerChannel %}oc: u32{% endif %}) -> f32 {
|
| 73 |
+
{% if wScalePerChannel %}
|
| 74 |
return w_scale[oc];
|
| 75 |
{% else %}
|
| 76 |
return w_scale[0];
|
| 77 |
{% endif %}
|
| 78 |
}
|
| 79 |
|
| 80 |
+
fn requantize(acc: i32{% if wScalePerChannel %}, oc: u32{% endif %}) -> u32 {
|
| 81 |
+
let scaled = f32(acc) * x_scale[0] * read_w_scale({% if 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,
|
|
|
|
| 87 |
return u32(q);
|
| 88 |
}
|
| 89 |
|
| 90 |
+
@compute @workgroup_size({{ spatialTile }}, {{ spatialTile }}, 1)
|
| 91 |
fn main(
|
| 92 |
@builtin(workgroup_id) wg: vec3<u32>,
|
| 93 |
@builtin(local_invocation_id) lid: vec3<u32>
|
|
|
|
| 107 |
}
|
| 108 |
workgroupBarrier();
|
| 109 |
|
| 110 |
+
{% for oc in range(ocTile) %}
|
| 111 |
var acc{{ oc }} = 0i;
|
| 112 |
{% endfor %}
|
| 113 |
var sumX = 0i;
|
|
|
|
| 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(ocTile) %}
|
| 171 |
acc{{ oc }} += dot4_packed(wTile[({{ oc }}u * IC_WORDS + channelWord) * 9u + tap], xp);
|
| 172 |
{% endfor %}
|
| 173 |
}
|
|
|
|
| 181 |
return;
|
| 182 |
}
|
| 183 |
|
| 184 |
+
{% for oc in range(ocTile) %}
|
| 185 |
{
|
| 186 |
let outOc = ocBase + {{ oc }}u;
|
| 187 |
if (outOc < OUT_C) {
|
| 188 |
+
let ca = -read_w_zero({% if 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 wScalePerChannel %}, outOc{% endif %});
|
| 195 |
}
|
| 196 |
}
|
| 197 |
{% endfor %}
|
build/webgpu/qlinear-conv-nchw-x4.wgsl.jinja
CHANGED
|
@@ -1,30 +1,30 @@
|
|
| 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 |
-
//
|
| 5 |
{% elif note == "limit" %}
|
| 6 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 7 |
-
//
|
| 8 |
{% elif note == "device-axis" %}
|
| 9 |
-
// The flat dispatch is folded across x/y at
|
| 10 |
-
//
|
| 11 |
{% elif note == "vec4-limit" %}
|
| 12 |
-
// 2D-folded flat vec4 index: gid.y carries the high bits past the
|
| 13 |
-
//
|
| 14 |
{% elif note == "element-limit" %}
|
| 15 |
// 2D-folded flat element index: gid.y carries the high bits past the
|
| 16 |
-
//
|
| 17 |
{% elif note == "dispatch" %}
|
| 18 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 19 |
-
//
|
| 20 |
{% endif %}
|
| 21 |
{% if bound == "" %}
|
| 22 |
-
let {{ name }} = gid.x + gid.y *
|
| 23 |
{%- elif guardInline %}
|
| 24 |
-
let {{ name }} = gid.x + gid.y *
|
| 25 |
if ({{ name }} >= {{ bound }}) { return; }
|
| 26 |
{%- else %}
|
| 27 |
-
let {{ name }} = gid.x + gid.y *
|
| 28 |
if ({{ name }} >= {{ bound }}) {
|
| 29 |
return;
|
| 30 |
}
|
|
@@ -97,7 +97,7 @@ fn write_y(i: u32, value: i32) {
|
|
| 97 |
}
|
| 98 |
|
| 99 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 100 |
-
fn main(@builtin(global_invocation_id) gid: 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;
|
|
|
|
| 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 dispatch's
|
| 4 |
+
// per-axis workgroup fold width (outputs > 16.7M elements).
|
| 5 |
{% elif note == "limit" %}
|
| 6 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 7 |
+
// per-axis workgroup fold width.
|
| 8 |
{% elif note == "device-axis" %}
|
| 9 |
+
// The flat dispatch is folded across x/y at a fixed per-axis workgroup
|
| 10 |
+
// width; 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 dispatch's
|
| 13 |
+
// per-axis workgroup fold width (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 |
+
// dispatch's per-axis workgroup fold width.
|
| 17 |
{% elif note == "dispatch" %}
|
| 18 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 19 |
+
// per-axis workgroup fold width.
|
| 20 |
{% endif %}
|
| 21 |
{% if bound == "" %}
|
| 22 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 23 |
{%- elif guardInline %}
|
| 24 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 25 |
if ({{ name }} >= {{ bound }}) { return; }
|
| 26 |
{%- else %}
|
| 27 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 28 |
if ({{ name }} >= {{ bound }}) {
|
| 29 |
return;
|
| 30 |
}
|
|
|
|
| 97 |
}
|
| 98 |
|
| 99 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 100 |
+
fn main(@builtin(global_invocation_id) gid: 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;
|
build/webgpu/qlinear-conv-requantize.wgsl.jinja
CHANGED
|
@@ -1,30 +1,30 @@
|
|
| 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 |
-
//
|
| 5 |
{% elif note == "limit" %}
|
| 6 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 7 |
-
//
|
| 8 |
{% elif note == "device-axis" %}
|
| 9 |
-
// The flat dispatch is folded across x/y at
|
| 10 |
-
//
|
| 11 |
{% elif note == "vec4-limit" %}
|
| 12 |
-
// 2D-folded flat vec4 index: gid.y carries the high bits past the
|
| 13 |
-
//
|
| 14 |
{% elif note == "element-limit" %}
|
| 15 |
// 2D-folded flat element index: gid.y carries the high bits past the
|
| 16 |
-
//
|
| 17 |
{% elif note == "dispatch" %}
|
| 18 |
-
// 2D-folded flat index: gid.y carries the high bits past the
|
| 19 |
-
//
|
| 20 |
{% endif %}
|
| 21 |
{% if bound == "" %}
|
| 22 |
-
let {{ name }} = gid.x + gid.y *
|
| 23 |
{%- elif guardInline %}
|
| 24 |
-
let {{ name }} = gid.x + gid.y *
|
| 25 |
if ({{ name }} >= {{ bound }}) { return; }
|
| 26 |
{%- else %}
|
| 27 |
-
let {{ name }} = gid.x + gid.y *
|
| 28 |
if ({{ name }} >= {{ bound }}) {
|
| 29 |
return;
|
| 30 |
}
|
|
@@ -50,7 +50,7 @@ fn write_y(index: u32, value: i32) {
|
|
| 50 |
}
|
| 51 |
|
| 52 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 53 |
-
fn main(@builtin(global_invocation_id) gid: vec3<u32>
|
| 54 |
{{ flat_index_2d(guardInline=true) }}
|
| 55 |
{% if hasBias or wScalePerChannel %}
|
| 56 |
let oc = (i / params.outImageSize) % params.outChannels;
|
|
|
|
| 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 dispatch's
|
| 4 |
+
// per-axis workgroup fold width (outputs > 16.7M elements).
|
| 5 |
{% elif note == "limit" %}
|
| 6 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 7 |
+
// per-axis workgroup fold width.
|
| 8 |
{% elif note == "device-axis" %}
|
| 9 |
+
// The flat dispatch is folded across x/y at a fixed per-axis workgroup
|
| 10 |
+
// width; 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 dispatch's
|
| 13 |
+
// per-axis workgroup fold width (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 |
+
// dispatch's per-axis workgroup fold width.
|
| 17 |
{% elif note == "dispatch" %}
|
| 18 |
+
// 2D-folded flat index: gid.y carries the high bits past the dispatch's
|
| 19 |
+
// per-axis workgroup fold width.
|
| 20 |
{% endif %}
|
| 21 |
{% if bound == "" %}
|
| 22 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 23 |
{%- elif guardInline %}
|
| 24 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 25 |
if ({{ name }} >= {{ bound }}) { return; }
|
| 26 |
{%- else %}
|
| 27 |
+
let {{ name }} = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 28 |
if ({{ name }} >= {{ bound }}) {
|
| 29 |
return;
|
| 30 |
}
|
|
|
|
| 50 |
}
|
| 51 |
|
| 52 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 53 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 54 |
{{ flat_index_2d(guardInline=true) }}
|
| 55 |
{% if hasBias or wScalePerChannel %}
|
| 56 |
let oc = (i / params.outImageSize) % params.outChannels;
|
build/webgpu/quant-dp4a-matmul.wgsl.jinja
CHANGED
|
@@ -1,24 +1,36 @@
|
|
| 1 |
-
{%
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 %}
|
|
@@ -28,7 +40,6 @@ fn pack_a(v: vec4<{{ aScalar }}>) -> u32 {
|
|
| 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 {
|
|
@@ -39,7 +50,6 @@ fn sum4_a(v: vec4<{{ aScalar }}>) -> i32 {
|
|
| 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);
|
|
@@ -54,7 +64,6 @@ 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 %}
|
|
@@ -64,8 +73,6 @@ fn sum4_b(b0: {{ bScalar }}, b1: {{ bScalar }}, b2: {{ bScalar }}, b3: {{ bScala
|
|
| 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 %}
|
|
@@ -82,25 +89,14 @@ fn read_a_zero({% if aZeroPerRow %}row: u32{% endif %}) -> i32 {
|
|
| 82 |
{% endif %}
|
| 83 |
}
|
| 84 |
|
| 85 |
-
|
| 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 %}
|
|
@@ -131,31 +127,15 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
|
|
| 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);
|
|
@@ -179,49 +159,13 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
|
|
| 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;
|
|
@@ -229,15 +173,12 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
|
|
| 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 |
}
|
|
@@ -262,9 +203,7 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
|
|
| 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 |
}
|
|
@@ -279,9 +218,7 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
|
|
| 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);
|
|
@@ -296,14 +233,11 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
|
|
| 296 |
}
|
| 297 |
|
| 298 |
// Fold bias + zero-point corrections (exact in modular i32 arithmetic).
|
| 299 |
-
let cb: i32 = {{ "128" if bUnsigned else "0" }}
|
| 300 |
{% if not aZeroPerRow %}
|
| 301 |
-
let ca: i32 = {{ "128" if aUnsigned else "0" }}
|
| 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 |
{
|
|
@@ -313,13 +247,9 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
|
|
| 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 }}
|
| 317 |
-
{% if needsASum %}
|
| 318 |
acc = acc + cb * atomicLoad(&a_row_sum[{{ m }}u]);
|
| 319 |
-
|
| 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];
|
|
@@ -327,7 +257,8 @@ fn main(@builtin(workgroup_id) wg: vec3<u32>, @builtin(local_invocation_id) lid:
|
|
| 327 |
{% if requant == "none" %}
|
| 328 |
y[y_base + mr * params.N + col] = acc;
|
| 329 |
{% else %}
|
| 330 |
-
|
|
|
|
| 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).
|
|
|
|
| 1 |
+
{% set yUnsigned = yUnsigned is defined and yUnsigned %}
|
| 2 |
+
{% set qMin = qMin | default(0 if yUnsigned else 0 - 128) %}
|
| 3 |
+
{% set qMax = qMax | default(255 if yUnsigned else 127) %}
|
| 4 |
+
{% if not env.device.wgslLanguageFeatures.has("packed_4x8_integer_dot_product") %}
|
| 5 |
+
// Runtime source-compatible fallbacks for the packed 4x8 integer built-ins used
|
| 6 |
+
// by quantized kernels. Native implementations remain unshadowed when the
|
| 7 |
+
// language feature is present. The user functions retain the built-ins'
|
| 8 |
+
// @must_use contract, but cannot be @const; every call is runtime.
|
| 9 |
+
// Inputs are function parameters, so replacing a built-in call with one of
|
| 10 |
+
// these same-name functions preserves WGSL's single evaluation of each
|
| 11 |
+
// argument. Byte extraction is unsigned. Signed lanes use the exact
|
| 12 |
+
// i32(byte ^ 0x80u) - 128i sign extension, avoiding signed-left-shift overflow
|
| 13 |
+
// while preserving every 8-bit two's-complement value.
|
| 14 |
+
@must_use fn dot4I8Packed(e1: u32, e2: u32) -> i32 {
|
| 15 |
+
let shifts = vec4<u32>(0u, 8u, 16u, 24u);
|
| 16 |
+
let a_bytes = (vec4<u32>(e1) >> shifts) & vec4<u32>(0xffu);
|
| 17 |
+
let b_bytes = (vec4<u32>(e2) >> shifts) & vec4<u32>(0xffu);
|
| 18 |
+
let a = vec4<i32>(a_bytes ^ vec4<u32>(0x80u)) - vec4<i32>(128i);
|
| 19 |
+
let b = vec4<i32>(b_bytes ^ vec4<u32>(0x80u)) - vec4<i32>(128i);
|
| 20 |
+
return dot(a, b);
|
| 21 |
+
}
|
| 22 |
{% endif %}
|
| 23 |
{{ env.wgsl.resourceDeclarations }}
|
| 24 |
|
| 25 |
+
|
| 26 |
const TILE_M: u32 = {{ tileM }}u;
|
| 27 |
const TILE_N: u32 = {{ tileN }}u;
|
| 28 |
const KT_WORDS: u32 = {{ ktWords }}u;
|
| 29 |
const A_TILE_WORDS: u32 = TILE_M * KT_WORDS;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
|
|
|
|
|
|
| 31 |
var<workgroup> a_tile: array<u32, A_TILE_WORDS>;
|
|
|
|
| 32 |
var<workgroup> a_row_sum: array<atomic<i32>, TILE_M>;
|
| 33 |
|
|
|
|
| 34 |
// Pack 4 widened A values into one u32 (byte 0 = lowest k).
|
| 35 |
fn pack_a(v: vec4<{{ aScalar }}>) -> u32 {
|
| 36 |
{% if aUnsigned %}
|
|
|
|
| 40 |
return (u32(v.x) & 0xFFu) | ((u32(v.y) & 0xFFu) << 8u) | ((u32(v.z) & 0xFFu) << 16u) | ((u32(v.w) & 0xFFu) << 24u);
|
| 41 |
{% endif %}
|
| 42 |
}
|
|
|
|
| 43 |
|
| 44 |
// Sum of the 4 bias-shifted A values of one packed word.
|
| 45 |
fn sum4_a(v: vec4<{{ aScalar }}>) -> i32 {
|
|
|
|
| 50 |
{% endif %}
|
| 51 |
}
|
| 52 |
|
|
|
|
| 53 |
fn pack_b(b0: {{ bScalar }}, b1: {{ bScalar }}, b2: {{ bScalar }}, b3: {{ bScalar }}) -> u32 {
|
| 54 |
{% if bUnsigned %}
|
| 55 |
return ((b0 ^ 0x80u) & 0xFFu) | (((b1 ^ 0x80u) & 0xFFu) << 8u) | (((b2 ^ 0x80u) & 0xFFu) << 16u) | (((b3 ^ 0x80u) & 0xFFu) << 24u);
|
|
|
|
| 64 |
return dot4I8Packed(a_word, b_word);
|
| 65 |
}
|
| 66 |
|
|
|
|
| 67 |
|
| 68 |
fn sum4_b(b0: {{ bScalar }}, b1: {{ bScalar }}, b2: {{ bScalar }}, b3: {{ bScalar }}) -> i32 {
|
| 69 |
{% if bUnsigned %}
|
|
|
|
| 73 |
{% endif %}
|
| 74 |
}
|
| 75 |
|
|
|
|
|
|
|
| 76 |
fn read_a_zero({% if aZeroPerRow %}row: u32{% endif %}) -> i32 {
|
| 77 |
{% if aZeroPerRow %}
|
| 78 |
{% if aUnsigned %}
|
|
|
|
| 89 |
{% endif %}
|
| 90 |
}
|
| 91 |
|
| 92 |
+
fn read_b_zero() -> i32 {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
{% if bUnsigned %}
|
| 94 |
return i32(b_zero_point[0]);
|
| 95 |
{% else %}
|
| 96 |
return b_zero_point[0];
|
| 97 |
{% endif %}
|
|
|
|
| 98 |
}
|
| 99 |
|
|
|
|
| 100 |
{% if requant != "none" %}
|
| 101 |
fn read_y_zero() -> i32 {
|
| 102 |
{% if yUnsigned %}
|
|
|
|
| 127 |
let k_words_total = params.K / 4u;
|
| 128 |
{% endif %}
|
| 129 |
|
|
|
|
| 130 |
if (tid < TILE_M) {
|
| 131 |
atomicStore(&a_row_sum[tid], 0);
|
| 132 |
}
|
| 133 |
workgroupBarrier();
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
{% for m in range(tileM) %}
|
| 136 |
var r{{ m }}: i32 = 0;
|
| 137 |
{% endfor %}
|
|
|
|
|
|
|
| 138 |
var sum_b: i32 = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
for (var kw0 = 0u; kw0 < k_words_total; kw0 = kw0 + KT_WORDS) {
|
| 141 |
let words = min(KT_WORDS, k_words_total - kw0);
|
|
|
|
| 159 |
let v = a[a_base4 + src_m * k_words_total + kw0 + ki];
|
| 160 |
{% endif %}
|
| 161 |
packed = pack_a(v);
|
|
|
|
| 162 |
atomicAdd(&a_row_sum[mi], sum4_a(v));
|
|
|
|
| 163 |
}
|
| 164 |
a_tile[mi * KT_WORDS + ki] = packed;
|
| 165 |
}
|
| 166 |
workgroupBarrier();
|
| 167 |
|
| 168 |
if (n_valid) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
for (var kk = 0u; kk < words; kk = kk + 1u) {
|
| 170 |
let k4 = (kw0 + kk) * 4u;
|
| 171 |
let b_row = b_base + k4 * params.N + col;
|
|
|
|
| 173 |
let b1 = b[b_row + params.N];
|
| 174 |
let b2 = b[b_row + 2u * params.N];
|
| 175 |
let b3 = b[b_row + 3u * params.N];
|
|
|
|
| 176 |
sum_b = sum_b + sum4_b(b0, b1, b2, b3);
|
|
|
|
| 177 |
let bp = pack_b(b0, b1, b2, b3);
|
| 178 |
{% for m in range(tileM) %}
|
| 179 |
r{{ m }} = r{{ m }} + dot4_packed(a_tile[{{ m }}u * KT_WORDS + kk], bp);
|
| 180 |
{% endfor %}
|
| 181 |
}
|
|
|
|
| 182 |
}
|
| 183 |
workgroupBarrier();
|
| 184 |
}
|
|
|
|
| 203 |
let a3: {{ aScalar }} = {{ "128u" if aUnsigned else "0" }};
|
| 204 |
let v = vec4<{{ aScalar }}>(a0, a1, a2, a3);
|
| 205 |
packed = pack_a(v);
|
|
|
|
| 206 |
atomicAdd(&a_row_sum[tid], sum4_a(v));
|
|
|
|
| 207 |
}
|
| 208 |
a_tile[tid * KT_WORDS] = packed;
|
| 209 |
}
|
|
|
|
| 218 |
if (tail_lanes > 1u) { b1 = b[b_row + params.N]; }
|
| 219 |
if (tail_lanes > 2u) { b2 = b[b_row + 2u * params.N]; }
|
| 220 |
let b3: {{ bScalar }} = {{ "128u" if bUnsigned else "0" }};
|
|
|
|
| 221 |
sum_b = sum_b + sum4_b(b0, b1, b2, b3);
|
|
|
|
| 222 |
let bp = pack_b(b0, b1, b2, b3);
|
| 223 |
{% for m in range(tileM) %}
|
| 224 |
r{{ m }} = r{{ m }} + dot4_packed(a_tile[{{ m }}u * KT_WORDS], bp);
|
|
|
|
| 233 |
}
|
| 234 |
|
| 235 |
// Fold bias + zero-point corrections (exact in modular i32 arithmetic).
|
| 236 |
+
let cb: i32 = {{ "128" if bUnsigned else "0" }} - read_b_zero();
|
| 237 |
{% if not aZeroPerRow %}
|
| 238 |
+
let ca: i32 = {{ "128" if aUnsigned else "0" }} - read_a_zero();
|
| 239 |
let kcc: i32 = i32(params.K) * ca * cb;
|
| 240 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
{% for m in range(tileM) %}
|
| 243 |
{
|
|
|
|
| 247 |
let ca: i32 = {{ "128" if aUnsigned else "0" }} - read_a_zero(mr);
|
| 248 |
let kcc: i32 = i32(params.K) * ca * cb;
|
| 249 |
{% endif %}
|
| 250 |
+
var acc = r{{ m }};
|
|
|
|
| 251 |
acc = acc + cb * atomicLoad(&a_row_sum[{{ m }}u]);
|
| 252 |
+
acc = acc + ca * sum_b;
|
|
|
|
|
|
|
|
|
|
| 253 |
acc = acc + kcc;
|
| 254 |
{% if hasBias %}
|
| 255 |
acc = acc + bias[mr];
|
|
|
|
| 257 |
{% if requant == "none" %}
|
| 258 |
y[y_base + mr * params.N + col] = acc;
|
| 259 |
{% else %}
|
| 260 |
+
{% set scale_index = "col" if requant == "per_column" else ("mr" if requant == "per_row" else "0u") %}
|
| 261 |
+
let scaled = f32(acc) * f32(a_scale[0]) * f32(b_scale[{{ scale_index }}]) / f32(y_scale[0]);
|
| 262 |
// Clamp the rounded float into an i32-representable range before the cast
|
| 263 |
// so a huge-finite / Inf requantized value saturates (the final clamp pins
|
| 264 |
// it to [qMin,qMax]) instead of invoking undefined i32(huge_float).
|
build/webgpu/quant-pad-k-matrices.wgsl.jinja
CHANGED
|
@@ -16,9 +16,8 @@ fn read_a_zero({% if aZeroPerRow %}row: u32{% endif %}) -> i32 {
|
|
| 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 *
|
| 22 |
|
| 23 |
let a_count = params.M * params.KPadded;
|
| 24 |
if (flat < a_count) {
|
|
|
|
| 16 |
@compute @workgroup_size({{ tunables.WORKGROUP_SIZE }})
|
| 17 |
fn main(
|
| 18 |
@builtin(global_invocation_id) gid: vec3<u32>,
|
|
|
|
| 19 |
) {
|
| 20 |
+
let flat = gid.x + gid.y * {{ DISPATCH_FOLD_WIDTH }}u * {{ tunables.WORKGROUP_SIZE }}u;
|
| 21 |
|
| 22 |
let a_count = params.M * params.KPadded;
|
| 23 |
if (flat < a_count) {
|
build/webgpu/test.json
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 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],
|
|
@@ -83,7 +82,7 @@
|
|
| 83 |
"x": {
|
| 84 |
"dtype": "int8",
|
| 85 |
"shape": [1, 1, 3, 3],
|
| 86 |
-
"data": { "kind": "values", "values": [-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] } },
|
|
@@ -94,7 +93,10 @@
|
|
| 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",
|
|
@@ -881,7 +883,7 @@
|
|
| 881 |
"provenance": {
|
| 882 |
"source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
|
| 883 |
"test": "QLinearConvTest.Conv3D_S8S8",
|
| 884 |
-
"notes": "
|
| 885 |
},
|
| 886 |
"attrs": { "strides": [1, 1, 1] },
|
| 887 |
"inputs": {
|
|
@@ -1229,15 +1231,18 @@
|
|
| 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.
|
| 1237 |
-
"y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "cycle", "values": [
|
| 1238 |
},
|
| 1239 |
"outputs": { "y": { "dtype": "uint8", "shape": [1, 64, 22, 22] } },
|
| 1240 |
-
"attrs": {}
|
|
|
|
|
|
|
|
|
|
| 1241 |
},
|
| 1242 |
{
|
| 1243 |
"name": "im2col_dp4a_requant_huge_finite_saturates_3x3",
|
|
@@ -1327,7 +1332,7 @@
|
|
| 1327 |
"name": "conv3d_depth_dilation2_compact",
|
| 1328 |
"provenance": {
|
| 1329 |
"source": "ONNX QLinearConv-10 volumetric dilation semantics",
|
| 1330 |
-
"notes": "
|
| 1331 |
},
|
| 1332 |
"attrs": { "strides": [1, 1, 1], "dilations": [2, 1, 1] },
|
| 1333 |
"inputs": {
|
|
@@ -1579,7 +1584,7 @@
|
|
| 1579 |
"name": "same_lower_stride2_autopad",
|
| 1580 |
"provenance": {
|
| 1581 |
"source": "ONNX Runtime QLinearConv-10 CPUExecutionProvider",
|
| 1582 |
-
"notes": "ONNX auto_pad SAME_LOWER
|
| 1583 |
},
|
| 1584 |
"attrs": { "auto_pad": "SAME_LOWER", "strides": [2, 2] },
|
| 1585 |
"inputs": {
|
|
@@ -1597,6 +1602,94 @@
|
|
| 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 |
}
|
|
|
|
| 1 |
{
|
|
|
|
| 2 |
"fixtureArrays": {
|
| 3 |
"same_upper_stride2_autopad_input_x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
| 4 |
"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],
|
|
|
|
| 82 |
"x": {
|
| 83 |
"dtype": "int8",
|
| 84 |
"shape": [1, 1, 3, 3],
|
| 85 |
+
"data": { "kind": "values", "values": [-4, 3, -2, 1, 0, -1, 2, -3, 4] }
|
| 86 |
},
|
| 87 |
"x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.5] } },
|
| 88 |
"x_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
|
|
|
|
| 93 |
"y_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [-3] } }
|
| 94 |
},
|
| 95 |
"outputs": { "y": { "dtype": "int8", "shape": [1, 1, 2, 2] } },
|
| 96 |
+
"attrs": {},
|
| 97 |
+
"provenance": {
|
| 98 |
+
"notes": "Signed output storage with a nonzero output zero point. The input is not a ramp, so the 2x2 kernel gives each output position its own response."
|
| 99 |
+
}
|
| 100 |
},
|
| 101 |
{
|
| 102 |
"name": "int8_input_uint8_output_pairwise_route",
|
|
|
|
| 883 |
"provenance": {
|
| 884 |
"source": "onnxruntime/test/providers/cpu/nn/qlinearconv_op_test.cc",
|
| 885 |
"test": "QLinearConvTest.Conv3D_S8S8",
|
| 886 |
+
"notes": "A compact three-dimensional QLinearConv uses int8 input, weights, and output."
|
| 887 |
},
|
| 888 |
"attrs": { "strides": [1, 1, 1] },
|
| 889 |
"inputs": {
|
|
|
|
| 1231 |
"w": {
|
| 1232 |
"dtype": "int8",
|
| 1233 |
"shape": [64, 16, 3, 3],
|
| 1234 |
+
"data": { "kind": "cycle", "values": [2, -1, 3, -2, 1, 4, -3, 2, 0, -4] }
|
| 1235 |
},
|
| 1236 |
"w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.02] } },
|
| 1237 |
"w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "cycle", "values": [1] } },
|
| 1238 |
+
"y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [0.002] } },
|
| 1239 |
+
"y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "cycle", "values": [128] } }
|
| 1240 |
},
|
| 1241 |
"outputs": { "y": { "dtype": "uint8", "shape": [1, 64, 22, 22] } },
|
| 1242 |
+
"attrs": {},
|
| 1243 |
+
"provenance": {
|
| 1244 |
+
"notes": "The dot4 im2col route over 16 input channels. A ten-value weight cycle does not tile the nine taps of a 3x3 kernel, so every output channel carries its own filter, and the output scale resolves the accumulator instead of quantizing every position onto the zero point."
|
| 1245 |
+
}
|
| 1246 |
},
|
| 1247 |
{
|
| 1248 |
"name": "im2col_dp4a_requant_huge_finite_saturates_3x3",
|
|
|
|
| 1332 |
"name": "conv3d_depth_dilation2_compact",
|
| 1333 |
"provenance": {
|
| 1334 |
"source": "ONNX QLinearConv-10 volumetric dilation semantics",
|
| 1335 |
+
"notes": "A three-dimensional quantized convolution uses a nondefault depth dilation."
|
| 1336 |
},
|
| 1337 |
"attrs": { "strides": [1, 1, 1], "dilations": [2, 1, 1] },
|
| 1338 |
"inputs": {
|
|
|
|
| 1584 |
"name": "same_lower_stride2_autopad",
|
| 1585 |
"provenance": {
|
| 1586 |
"source": "ONNX Runtime QLinearConv-10 CPUExecutionProvider",
|
| 1587 |
+
"notes": "ONNX auto_pad SAME_LOWER places the odd padding element at the start of the axis. The nonzero leading pad verifies that execution uses the derived padding rather than unrelated explicit pad attributes."
|
| 1588 |
},
|
| 1589 |
"attrs": { "auto_pad": "SAME_LOWER", "strides": [2, 2] },
|
| 1590 |
"inputs": {
|
|
|
|
| 1602 |
"y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } }
|
| 1603 |
},
|
| 1604 |
"outputs": { "y": { "dtype": "uint8", "shape": [1, 1, 2, 2], "tolerance": 0 } }
|
| 1605 |
+
},
|
| 1606 |
+
{
|
| 1607 |
+
"name": "ncw1d_scalar_bias",
|
| 1608 |
+
"provenance": {
|
| 1609 |
+
"source": "ONNX Operators.md, QLinearConv",
|
| 1610 |
+
"notes": "A one-dimensional quantized convolution carrying a bias, which is the ordinary audio form. The bias is non-zero so the case proves it is applied, not merely that a route exists."
|
| 1611 |
+
},
|
| 1612 |
+
"attrs": { "kernel_shape": [2], "pads": [0, 0] },
|
| 1613 |
+
"inputs": {
|
| 1614 |
+
"x": { "dtype": "uint8", "shape": [1, 1, 4], "data": { "kind": "values", "values": [1, 2, 3, 4] } },
|
| 1615 |
+
"x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
|
| 1616 |
+
"x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
|
| 1617 |
+
"w": { "dtype": "int8", "shape": [1, 1, 2], "data": { "kind": "values", "values": [1, 1] } },
|
| 1618 |
+
"w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
|
| 1619 |
+
"w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
|
| 1620 |
+
"y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
|
| 1621 |
+
"y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
|
| 1622 |
+
"B": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [10] } }
|
| 1623 |
+
},
|
| 1624 |
+
"outputs": {
|
| 1625 |
+
"y": {
|
| 1626 |
+
"dtype": "uint8",
|
| 1627 |
+
"shape": [1, 1, 3],
|
| 1628 |
+
"data": { "kind": "values", "values": [13, 15, 17] },
|
| 1629 |
+
"tolerance": 0
|
| 1630 |
+
}
|
| 1631 |
+
}
|
| 1632 |
+
},
|
| 1633 |
+
{
|
| 1634 |
+
"name": "ncdhw3d_scalar_bias",
|
| 1635 |
+
"provenance": {
|
| 1636 |
+
"source": "ONNX Operators.md, QLinearConv",
|
| 1637 |
+
"notes": "A three-dimensional quantized convolution includes a nonzero bias."
|
| 1638 |
+
},
|
| 1639 |
+
"attrs": { "kernel_shape": [2, 2, 2], "pads": [0, 0, 0, 0, 0, 0] },
|
| 1640 |
+
"inputs": {
|
| 1641 |
+
"x": {
|
| 1642 |
+
"dtype": "uint8",
|
| 1643 |
+
"shape": [1, 1, 2, 2, 2],
|
| 1644 |
+
"data": { "kind": "values", "values": [1, 2, 3, 4, 5, 6, 7, 8] }
|
| 1645 |
+
},
|
| 1646 |
+
"x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
|
| 1647 |
+
"x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
|
| 1648 |
+
"w": {
|
| 1649 |
+
"dtype": "int8",
|
| 1650 |
+
"shape": [1, 1, 2, 2, 2],
|
| 1651 |
+
"data": { "kind": "values", "values": [1, 1, 1, 1, 1, 1, 1, 1] }
|
| 1652 |
+
},
|
| 1653 |
+
"w_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
|
| 1654 |
+
"w_zero_point": { "dtype": "int8", "shape": [1], "data": { "kind": "values", "values": [0] } },
|
| 1655 |
+
"y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
|
| 1656 |
+
"y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
|
| 1657 |
+
"B": { "dtype": "int32", "shape": [1], "data": { "kind": "values", "values": [4] } }
|
| 1658 |
+
},
|
| 1659 |
+
"outputs": {
|
| 1660 |
+
"y": {
|
| 1661 |
+
"dtype": "uint8",
|
| 1662 |
+
"shape": [1, 1, 1, 1, 1],
|
| 1663 |
+
"data": { "kind": "values", "values": [40] },
|
| 1664 |
+
"tolerance": 0
|
| 1665 |
+
}
|
| 1666 |
+
}
|
| 1667 |
+
},
|
| 1668 |
+
{
|
| 1669 |
+
"name": "ncw1d_per_channel_weight_quantization",
|
| 1670 |
+
"provenance": {
|
| 1671 |
+
"source": "ONNX Operators.md, QLinearConv",
|
| 1672 |
+
"notes": "One-dimensional convolution uses weights quantized independently per output channel."
|
| 1673 |
+
},
|
| 1674 |
+
"attrs": { "kernel_shape": [2], "pads": [0, 0] },
|
| 1675 |
+
"inputs": {
|
| 1676 |
+
"x": { "dtype": "uint8", "shape": [1, 1, 4], "data": { "kind": "values", "values": [1, 2, 3, 4] } },
|
| 1677 |
+
"x_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
|
| 1678 |
+
"x_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } },
|
| 1679 |
+
"w": { "dtype": "int8", "shape": [2, 1, 2], "data": { "kind": "values", "values": [1, 1, 2, -1] } },
|
| 1680 |
+
"w_scale": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [1.0, 1.0] } },
|
| 1681 |
+
"w_zero_point": { "dtype": "int8", "shape": [2], "data": { "kind": "values", "values": [0, 0] } },
|
| 1682 |
+
"y_scale": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [1.0] } },
|
| 1683 |
+
"y_zero_point": { "dtype": "uint8", "shape": [1], "data": { "kind": "values", "values": [0] } }
|
| 1684 |
+
},
|
| 1685 |
+
"outputs": {
|
| 1686 |
+
"y": {
|
| 1687 |
+
"dtype": "uint8",
|
| 1688 |
+
"shape": [1, 2, 3],
|
| 1689 |
+
"data": { "kind": "values", "values": [3, 5, 7, 0, 1, 2] },
|
| 1690 |
+
"tolerance": 0
|
| 1691 |
+
}
|
| 1692 |
+
}
|
| 1693 |
}
|
| 1694 |
]
|
| 1695 |
}
|