sync 2e7068faf55e
Browse files- README.md +61 -0
- build/webgpu/bench.json +177 -0
- build/webgpu/dynamic-quantize-linear-quantize.wgsl.jinja +183 -0
- build/webgpu/dynamic-quantize-linear-reduce.wgsl.jinja +132 -0
- build/webgpu/dynamic-quantize-linear.wgsl.jinja +178 -0
- build/webgpu/manifest.json +404 -0
- build/webgpu/metadata.json +20 -0
- build/webgpu/test.json +489 -0
README.md
CHANGED
|
@@ -1,3 +1,64 @@
|
|
| 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.DynamicQuantizeLinear
|
| 10 |
+
|
| 11 |
+
`ai.onnx` · standard ONNX operator · ONNX opset ≥ 11
|
| 12 |
+
|
| 13 |
+
## Description
|
| 14 |
+
|
| 15 |
+
Computes a per-tensor scale and zero point from the range of floating-point input `x`, extending the range to include zero, then quantizes each value to `uint8` as `saturate(round(x / y_scale) + y_zero_point)`. Uses round-to-nearest-even and clamps results to `[0, 255]`.
|
| 16 |
+
|
| 17 |
+
See the [ONNX `DynamicQuantizeLinear` spec](https://onnx.ai/onnx/operators/onnx__DynamicQuantizeLinear.html) for the reference semantics.
|
| 18 |
+
|
| 19 |
+
## Inputs
|
| 20 |
+
|
| 21 |
+
| Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
|
| 22 |
+
| --- | --- | --- | --- | --- | --- | --- |
|
| 23 |
+
| `x` | `x` | `T` | — | — | Float32 input tensor to quantize. | required |
|
| 24 |
+
|
| 25 |
+
## Outputs
|
| 26 |
+
|
| 27 |
+
| Name | Bind key | Logical dtype | Rank | Shape | Description | Presence |
|
| 28 |
+
| --- | --- | --- | --- | --- | --- | --- |
|
| 29 |
+
| `y` | `y` | `TQ` | same as `x` | same as `x` | Quantized output tensor; same shape as the input. | required |
|
| 30 |
+
| `y_scale` | `y_scale` | `T` | `0` | `[]` | Per-tensor scale factor derived from the input min/max range; scalar. | required |
|
| 31 |
+
| `y_zero_point` | `y_zero_point` | `TQ` | `0` | `[]` | Per-tensor zero point for the quantization; scalar. | required |
|
| 32 |
+
|
| 33 |
+
## Type constraints
|
| 34 |
+
|
| 35 |
+
| Variable | Allowed dtypes |
|
| 36 |
+
| --- | --- |
|
| 37 |
+
| `T` | `float32` |
|
| 38 |
+
| `TQ` | `uint8` |
|
| 39 |
+
|
| 40 |
+
## Files
|
| 41 |
+
|
| 42 |
+
- [`metadata.json`](build/webgpu/metadata.json) — kernel metadata (id, digests, provenance)
|
| 43 |
+
- [`manifest.json`](build/webgpu/manifest.json) — the op contract (source of truth)
|
| 44 |
+
- [`test.json`](build/webgpu/test.json) — correctness cases
|
| 45 |
+
- [`bench.json`](build/webgpu/bench.json) — benchmark + tuning cases
|
| 46 |
+
- [`dynamic-quantize-linear-quantize.wgsl.jinja`](build/webgpu/dynamic-quantize-linear-quantize.wgsl.jinja)
|
| 47 |
+
- [`dynamic-quantize-linear-reduce.wgsl.jinja`](build/webgpu/dynamic-quantize-linear-reduce.wgsl.jinja)
|
| 48 |
+
- [`dynamic-quantize-linear.wgsl.jinja`](build/webgpu/dynamic-quantize-linear.wgsl.jinja)
|
| 49 |
+
|
| 50 |
+
## Use with `@huggingface/kernels`
|
| 51 |
+
|
| 52 |
+
The loader derives every required output's shape and logical dtype from the manifest contract and this call.
|
| 53 |
+
It then allocates the result tensors automatically.
|
| 54 |
+
|
| 55 |
+
The `version: 1` option selects the published kernel contract; it is independent of any operator opset, contrib `since_version`, or model version.
|
| 56 |
+
|
| 57 |
+
Replace each `*Data` placeholder with a typed array containing the corresponding input data.
|
| 58 |
+
|
| 59 |
+
```js
|
| 60 |
+
import { getKernel } from "@huggingface/kernels";
|
| 61 |
+
|
| 62 |
+
const kernel = await getKernel("webgpu-kernels/ai.onnx.DynamicQuantizeLinear", { version: 1 });
|
| 63 |
+
const { y, y_scale, y_zero_point } = await kernel({ x: { data: xData, shape: [1] } });
|
| 64 |
+
```
|
build/webgpu/bench.json
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"op": "ai.onnx.DynamicQuantizeLinear",
|
| 3 |
+
"cases": [
|
| 4 |
+
{
|
| 5 |
+
"name": "f32_64k",
|
| 6 |
+
"inputs": {
|
| 7 |
+
"x": {
|
| 8 |
+
"dtype": "float32",
|
| 9 |
+
"shape": [65536],
|
| 10 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.017, "scale": 3.0 }
|
| 11 |
+
}
|
| 12 |
+
},
|
| 13 |
+
"outputs": {
|
| 14 |
+
"y": { "dtype": "uint8", "shape": [65536] },
|
| 15 |
+
"y_scale": { "dtype": "float32", "shape": [1] },
|
| 16 |
+
"y_zero_point": { "dtype": "uint8", "shape": [1] }
|
| 17 |
+
}
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"name": "f32_4m",
|
| 21 |
+
"preset": "smoke",
|
| 22 |
+
"inputs": {
|
| 23 |
+
"x": {
|
| 24 |
+
"dtype": "float32",
|
| 25 |
+
"shape": [2048, 2048],
|
| 26 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.017, "scale": 3.0 }
|
| 27 |
+
}
|
| 28 |
+
},
|
| 29 |
+
"outputs": {
|
| 30 |
+
"y": { "dtype": "uint8", "shape": [2048, 2048] },
|
| 31 |
+
"y_scale": { "dtype": "float32", "shape": [1] },
|
| 32 |
+
"y_zero_point": { "dtype": "uint8", "shape": [1] }
|
| 33 |
+
},
|
| 34 |
+
"bench": {
|
| 35 |
+
"metrics": [
|
| 36 |
+
{ "type": "bandwidth", "name": "useful_onnx_payload", "value": "5 * numel(shapes.x) + 5" },
|
| 37 |
+
{
|
| 38 |
+
"type": "bandwidth",
|
| 39 |
+
"name": "physical_3pass_storage",
|
| 40 |
+
"value": "12 * numel(shapes.x) + 16 * min(256, ceilDiv(numel(shapes.x), 1024)) + 16"
|
| 41 |
+
}
|
| 42 |
+
]
|
| 43 |
+
}
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"name": "alignment_cliff_non_vec4_4194303",
|
| 47 |
+
"preset": "smoke",
|
| 48 |
+
"inputs": {
|
| 49 |
+
"x": {
|
| 50 |
+
"dtype": "float32",
|
| 51 |
+
"shape": [4194303],
|
| 52 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.017, "scale": 3.0 }
|
| 53 |
+
}
|
| 54 |
+
},
|
| 55 |
+
"outputs": {
|
| 56 |
+
"y": { "dtype": "uint8", "shape": [4194303] },
|
| 57 |
+
"y_scale": { "dtype": "float32", "shape": [1] },
|
| 58 |
+
"y_zero_point": { "dtype": "uint8", "shape": [1] }
|
| 59 |
+
},
|
| 60 |
+
"bench": {
|
| 61 |
+
"primary": true,
|
| 62 |
+
"metrics": [
|
| 63 |
+
{ "type": "bandwidth", "name": "useful_onnx_payload", "value": "5 * numel(shapes.x) + 5" },
|
| 64 |
+
{
|
| 65 |
+
"type": "bandwidth",
|
| 66 |
+
"name": "physical_3pass_storage",
|
| 67 |
+
"value": "12 * numel(shapes.x) + 16 * min(256, ceilDiv(numel(shapes.x), 1024)) + 16"
|
| 68 |
+
}
|
| 69 |
+
]
|
| 70 |
+
}
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"name": "alignment_healthy_vec4_4194304",
|
| 74 |
+
"preset": "smoke",
|
| 75 |
+
"inputs": {
|
| 76 |
+
"x": {
|
| 77 |
+
"dtype": "float32",
|
| 78 |
+
"shape": [4194304],
|
| 79 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.017, "scale": 3.0 }
|
| 80 |
+
}
|
| 81 |
+
},
|
| 82 |
+
"outputs": {
|
| 83 |
+
"y": { "dtype": "uint8", "shape": [4194304] },
|
| 84 |
+
"y_scale": { "dtype": "float32", "shape": [1] },
|
| 85 |
+
"y_zero_point": { "dtype": "uint8", "shape": [1] }
|
| 86 |
+
},
|
| 87 |
+
"bench": {
|
| 88 |
+
"metrics": [
|
| 89 |
+
{ "type": "bandwidth", "name": "useful_onnx_payload", "value": "5 * numel(shapes.x) + 5" },
|
| 90 |
+
{
|
| 91 |
+
"type": "bandwidth",
|
| 92 |
+
"name": "physical_3pass_storage",
|
| 93 |
+
"value": "12 * numel(shapes.x) + 16 * min(256, ceilDiv(numel(shapes.x), 1024)) + 16"
|
| 94 |
+
}
|
| 95 |
+
]
|
| 96 |
+
}
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"name": "serial_finalize_uncapped_non_vec4_2m",
|
| 100 |
+
"preset": "smoke",
|
| 101 |
+
"inputs": {
|
| 102 |
+
"x": {
|
| 103 |
+
"dtype": "float32",
|
| 104 |
+
"shape": [2097153],
|
| 105 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.017, "scale": 3.0 }
|
| 106 |
+
}
|
| 107 |
+
},
|
| 108 |
+
"outputs": {
|
| 109 |
+
"y": { "dtype": "uint8", "shape": [2097153] },
|
| 110 |
+
"y_scale": { "dtype": "float32", "shape": [1] },
|
| 111 |
+
"y_zero_point": { "dtype": "uint8", "shape": [1] }
|
| 112 |
+
},
|
| 113 |
+
"bench": {
|
| 114 |
+
"metrics": [
|
| 115 |
+
{ "type": "bandwidth", "name": "useful_onnx_payload", "value": "5 * numel(shapes.x) + 5" },
|
| 116 |
+
{
|
| 117 |
+
"type": "bandwidth",
|
| 118 |
+
"name": "physical_3pass_storage",
|
| 119 |
+
"value": "12 * numel(shapes.x) + 16 * min(256, ceilDiv(numel(shapes.x), 1024)) + 16"
|
| 120 |
+
}
|
| 121 |
+
]
|
| 122 |
+
}
|
| 123 |
+
},
|
| 124 |
+
{
|
| 125 |
+
"name": "grid_stride_capped_finalize_2m",
|
| 126 |
+
"preset": "smoke",
|
| 127 |
+
"inputs": {
|
| 128 |
+
"x": {
|
| 129 |
+
"dtype": "float32",
|
| 130 |
+
"shape": [2097152],
|
| 131 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.017, "scale": 3.0 }
|
| 132 |
+
}
|
| 133 |
+
},
|
| 134 |
+
"outputs": {
|
| 135 |
+
"y": { "dtype": "uint8", "shape": [2097152] },
|
| 136 |
+
"y_scale": { "dtype": "float32", "shape": [1] },
|
| 137 |
+
"y_zero_point": { "dtype": "uint8", "shape": [1] }
|
| 138 |
+
},
|
| 139 |
+
"bench": {
|
| 140 |
+
"metrics": [
|
| 141 |
+
{ "type": "bandwidth", "name": "useful_onnx_payload", "value": "5 * numel(shapes.x) + 5" },
|
| 142 |
+
{
|
| 143 |
+
"type": "bandwidth",
|
| 144 |
+
"name": "physical_3pass_storage",
|
| 145 |
+
"value": "12 * numel(shapes.x) + 16 * min(256, ceilDiv(numel(shapes.x), 1024)) + 16"
|
| 146 |
+
}
|
| 147 |
+
]
|
| 148 |
+
}
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
"name": "below_gridcap_threshold_non_vec4_262143",
|
| 152 |
+
"preset": "smoke",
|
| 153 |
+
"inputs": {
|
| 154 |
+
"x": {
|
| 155 |
+
"dtype": "float32",
|
| 156 |
+
"shape": [262143],
|
| 157 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.017, "scale": 3.0 }
|
| 158 |
+
}
|
| 159 |
+
},
|
| 160 |
+
"outputs": {
|
| 161 |
+
"y": { "dtype": "uint8", "shape": [262143] },
|
| 162 |
+
"y_scale": { "dtype": "float32", "shape": [1] },
|
| 163 |
+
"y_zero_point": { "dtype": "uint8", "shape": [1] }
|
| 164 |
+
},
|
| 165 |
+
"bench": {
|
| 166 |
+
"metrics": [
|
| 167 |
+
{ "type": "bandwidth", "name": "useful_onnx_payload", "value": "5 * numel(shapes.x) + 5" },
|
| 168 |
+
{
|
| 169 |
+
"type": "bandwidth",
|
| 170 |
+
"name": "physical_3pass_storage",
|
| 171 |
+
"value": "12 * numel(shapes.x) + 16 * min(256, ceilDiv(numel(shapes.x), 1024)) + 16"
|
| 172 |
+
}
|
| 173 |
+
]
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
]
|
| 177 |
+
}
|
build/webgpu/dynamic-quantize-linear-quantize.wgsl.jinja
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Pass 3 of parallel DynamicQuantizeLinear: elementwise quantization using the
|
| 2 |
+
// read-only y_scale/y_zero_point produced by the finalize pass. Each workgroup
|
| 3 |
+
// covers the same WG * EPT contiguous chunk as the reduction pass, so their
|
| 4 |
+
// dispatch counts match. Quantization applies round-to-even and saturates the
|
| 5 |
+
// result to the uint8 range using the common scale and zero point.
|
| 6 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 7 |
+
// ONNX DynamicQuantizeLinear uses correctly rounded f32 division followed by
|
| 8 |
+
// round-half-to-even. Every execution path uses these helpers so their numerical
|
| 9 |
+
// behavior cannot drift apart.
|
| 10 |
+
|
| 11 |
+
// WGSL permits f32 division to differ from the correctly-rounded result by
|
| 12 |
+
// 2.5 ULP, and fma() inherits separate multiply/add accuracy rather than
|
| 13 |
+
// promising a fused residual. Reconstruct the correctly-rounded normal result
|
| 14 |
+
// with integer significand division when the quotient can affect an integer
|
| 15 |
+
// rounding boundary. This is backend-independent and uses only exact u32 ops.
|
| 16 |
+
fn dynamic_quantize_exact_div_normal(numerator: f32, denominator: f32) -> f32 {
|
| 17 |
+
if (numerator == 0.0) {
|
| 18 |
+
return numerator;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
let numerator_bits = bitcast<u32>(numerator);
|
| 22 |
+
let denominator_bits = bitcast<u32>(denominator);
|
| 23 |
+
let sign_bits = (numerator_bits ^ denominator_bits) & 0x80000000u;
|
| 24 |
+
|
| 25 |
+
let numerator_abs = numerator_bits & 0x7fffffffu;
|
| 26 |
+
let denominator_abs = denominator_bits & 0x7fffffffu;
|
| 27 |
+
if (denominator_abs == 0u
|
| 28 |
+
|| (numerator_abs & 0x7f800000u) == 0x7f800000u
|
| 29 |
+
|| (denominator_abs & 0x7f800000u) == 0x7f800000u) {
|
| 30 |
+
return numerator / denominator;
|
| 31 |
+
}
|
| 32 |
+
var numerator_mantissa = numerator_abs & 0x007fffffu;
|
| 33 |
+
var denominator_mantissa = denominator_abs & 0x007fffffu;
|
| 34 |
+
let numerator_biased_exponent = (numerator_abs >> 23u) & 0xffu;
|
| 35 |
+
let denominator_biased_exponent = (denominator_abs >> 23u) & 0xffu;
|
| 36 |
+
var numerator_exponent: i32;
|
| 37 |
+
var denominator_exponent: i32;
|
| 38 |
+
|
| 39 |
+
if (numerator_biased_exponent == 0u) {
|
| 40 |
+
numerator_exponent = -126;
|
| 41 |
+
// Zero returned above. A non-zero subnormal reaches the implicit-bit
|
| 42 |
+
// position in at most 23 exact shifts.
|
| 43 |
+
while ((numerator_mantissa & 0x00800000u) == 0u) {
|
| 44 |
+
numerator_mantissa = numerator_mantissa << 1u;
|
| 45 |
+
numerator_exponent = numerator_exponent - 1;
|
| 46 |
+
}
|
| 47 |
+
} else {
|
| 48 |
+
numerator_mantissa = numerator_mantissa | 0x00800000u;
|
| 49 |
+
numerator_exponent = i32(numerator_biased_exponent) - 127;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
if (denominator_biased_exponent == 0u) {
|
| 53 |
+
denominator_exponent = -126;
|
| 54 |
+
while ((denominator_mantissa & 0x00800000u) == 0u) {
|
| 55 |
+
denominator_mantissa = denominator_mantissa << 1u;
|
| 56 |
+
denominator_exponent = denominator_exponent - 1;
|
| 57 |
+
}
|
| 58 |
+
} else {
|
| 59 |
+
denominator_mantissa = denominator_mantissa | 0x00800000u;
|
| 60 |
+
denominator_exponent = i32(denominator_biased_exponent) - 127;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
var quotient_exponent = numerator_exponent - denominator_exponent;
|
| 64 |
+
var remainder = numerator_mantissa;
|
| 65 |
+
if (remainder < denominator_mantissa) {
|
| 66 |
+
remainder = remainder << 1u;
|
| 67 |
+
quotient_exponent = quotient_exponent - 1;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
// The normalized ratio is now in [1, 2). Emit its implicit bit followed by
|
| 71 |
+
// all 23 stored significand bits using exact binary long division.
|
| 72 |
+
var quotient_mantissa = 0x00800000u;
|
| 73 |
+
remainder = remainder - denominator_mantissa;
|
| 74 |
+
for (var digit = 0u; digit < 23u; digit = digit + 1u) {
|
| 75 |
+
remainder = remainder << 1u;
|
| 76 |
+
if (remainder >= denominator_mantissa) {
|
| 77 |
+
remainder = remainder - denominator_mantissa;
|
| 78 |
+
quotient_mantissa = quotient_mantissa | (1u << (22u - digit));
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
// Round the 24-bit significand to nearest, ties to even. remainder and its
|
| 83 |
+
// doubled value are below 2^25, so no u32 overflow is possible.
|
| 84 |
+
let twice_remainder = remainder << 1u;
|
| 85 |
+
if (twice_remainder > denominator_mantissa
|
| 86 |
+
|| (twice_remainder == denominator_mantissa && (quotient_mantissa & 1u) != 0u)) {
|
| 87 |
+
quotient_mantissa = quotient_mantissa + 1u;
|
| 88 |
+
}
|
| 89 |
+
if (quotient_mantissa == 0x01000000u) {
|
| 90 |
+
quotient_mantissa = quotient_mantissa >> 1u;
|
| 91 |
+
quotient_exponent = quotient_exponent + 1;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
let biased_exponent = quotient_exponent + 127;
|
| 95 |
+
if (biased_exponent <= 0 || biased_exponent >= 255) {
|
| 96 |
+
// DynamicQuantizeLinear's GPU-supported ranges are normal and finite.
|
| 97 |
+
// Preserve the implementation-defined subnormal/overflow behavior outside
|
| 98 |
+
// that contract; the integer quantizer never takes this branch near a
|
| 99 |
+
// half-integer boundary.
|
| 100 |
+
return numerator / denominator;
|
| 101 |
+
}
|
| 102 |
+
let result_bits = sign_bits
|
| 103 |
+
| (u32(biased_exponent) << 23u)
|
| 104 |
+
| (quotient_mantissa & 0x007fffffu);
|
| 105 |
+
return bitcast<f32>(result_bits);
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
fn dynamic_quantize_division_may_cross_half(estimate: f32) -> bool {
|
| 109 |
+
let lower = floor(estimate);
|
| 110 |
+
let fraction = estimate - lower;
|
| 111 |
+
let magnitude = abs(estimate);
|
| 112 |
+
let magnitude_bits = bitcast<u32>(magnitude);
|
| 113 |
+
let adjacent = bitcast<f32>(magnitude_bits + 1u);
|
| 114 |
+
let ulp = adjacent - magnitude;
|
| 115 |
+
// Division is allowed 2.5 ULP error. Eight ULP also covers the factor-of-two
|
| 116 |
+
// ULP change when an estimate straddles the 0.5 exponent boundary.
|
| 117 |
+
return abs(fraction - 0.5) <= ulp * 8.0;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
fn round_dynamic_half_to_even(value: f32, scale: f32) -> i32 {
|
| 121 |
+
let estimate = value / scale;
|
| 122 |
+
var scaled = estimate;
|
| 123 |
+
// `select` evaluates both value operands in WGSL; use control flow so the
|
| 124 |
+
// 23-bit software divide remains a rare boundary fallback, not O(23) work
|
| 125 |
+
// for every quantized element.
|
| 126 |
+
if (dynamic_quantize_division_may_cross_half(estimate)) {
|
| 127 |
+
scaled = dynamic_quantize_exact_div_normal(value, scale);
|
| 128 |
+
}
|
| 129 |
+
let lower = floor(scaled);
|
| 130 |
+
let fraction = scaled - lower;
|
| 131 |
+
if (fraction < 0.5) {
|
| 132 |
+
return i32(lower);
|
| 133 |
+
}
|
| 134 |
+
if (fraction > 0.5) {
|
| 135 |
+
return i32(lower + 1.0);
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
let upper = lower + 1.0;
|
| 139 |
+
let half_lower = floor(lower * 0.5);
|
| 140 |
+
let lower_is_even = (lower - half_lower * 2.0) == 0.0;
|
| 141 |
+
return i32(select(upper, lower, lower_is_even));
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
const WG: u32 = {{ workgroupSize }}u;
|
| 146 |
+
{% if not vec4 %}
|
| 147 |
+
const EPT: u32 = {{ elemsPerThread }}u;
|
| 148 |
+
|
| 149 |
+
{% endif %}
|
| 150 |
+
@compute @workgroup_size(WG, 1, 1)
|
| 151 |
+
fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
| 152 |
+
@builtin(local_invocation_id) lid: vec3<u32>,
|
| 153 |
+
@builtin(num_workgroups) nwg: vec3<u32>) {
|
| 154 |
+
let tid = lid.x;
|
| 155 |
+
let scale = y_scale[0];
|
| 156 |
+
let zp_i32 = i32(y_zero_point[0]);
|
| 157 |
+
// Fold the block grid across x/y at the device's per-axis workgroup limit.
|
| 158 |
+
// Per-element guards discard the over-dispatched tail.
|
| 159 |
+
let blk = wg.x + wg.y * nwg.x;
|
| 160 |
+
{% if vec4 %}
|
| 161 |
+
// The vec4 input load reads four scalars at once. Output storage still uses
|
| 162 |
+
// one u32 element for each quantized value.
|
| 163 |
+
let count4 = params.count / 4u;
|
| 164 |
+
let idx4 = blk * WG + tid;
|
| 165 |
+
if (idx4 < count4) {
|
| 166 |
+
let v = x[idx4];
|
| 167 |
+
let o = idx4 * 4u;
|
| 168 |
+
y[o + 0u] = u32(clamp(round_dynamic_half_to_even(v.x, scale) + zp_i32, 0, 255));
|
| 169 |
+
y[o + 1u] = u32(clamp(round_dynamic_half_to_even(v.y, scale) + zp_i32, 0, 255));
|
| 170 |
+
y[o + 2u] = u32(clamp(round_dynamic_half_to_even(v.z, scale) + zp_i32, 0, 255));
|
| 171 |
+
y[o + 3u] = u32(clamp(round_dynamic_half_to_even(v.w, scale) + zp_i32, 0, 255));
|
| 172 |
+
}
|
| 173 |
+
{% else %}
|
| 174 |
+
let base = blk * WG * EPT;
|
| 175 |
+
for (var e = 0u; e < EPT; e = e + 1u) {
|
| 176 |
+
let idx = base + e * WG + tid;
|
| 177 |
+
if (idx < params.count) {
|
| 178 |
+
let q = clamp(round_dynamic_half_to_even(x[idx], scale) + zp_i32, 0, 255);
|
| 179 |
+
y[idx] = u32(q);
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
{% endif %}
|
| 183 |
+
}
|
build/webgpu/dynamic-quantize-linear-reduce.wgsl.jinja
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Pass 1 of parallel DynamicQuantizeLinear. Regular mode reduces one contiguous
|
| 2 |
+
// WG * EPT block per workgroup. Grid-stride mode caps the partial count and has
|
| 3 |
+
// every lane revisit the tensor at grid-sized strides. Both modes use the same
|
| 4 |
+
// subgroup/tree combine and write one min/max pair per workgroup.
|
| 5 |
+
//
|
| 6 |
+
// The local min/max start at 0.0 because the ONNX DynamicQuantizeLinear range
|
| 7 |
+
// always includes zero; out-of-range lanes contribute the same neutral value.
|
| 8 |
+
// f32 min/max is order-independent, so the result is bit-identical to the serial
|
| 9 |
+
// loop and between subgroup and workgroup-tree combines. Without subgroups, a
|
| 10 |
+
// full workgroup tree combines the per-thread partials.
|
| 11 |
+
{% set gridStride = source.gridStride if source.gridStride is defined else false %}
|
| 12 |
+
{% if useSubgroups %}
|
| 13 |
+
enable subgroups;
|
| 14 |
+
{% endif %}
|
| 15 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 16 |
+
|
| 17 |
+
const WG: u32 = {{ workgroupSize }}u;
|
| 18 |
+
{% if not gridStride and not vec4 %}
|
| 19 |
+
const EPT: u32 = {{ elemsPerThread }}u;
|
| 20 |
+
|
| 21 |
+
{% endif %}
|
| 22 |
+
var<workgroup> wgMin: array<f32, WG>;
|
| 23 |
+
var<workgroup> wgMax: array<f32, WG>;
|
| 24 |
+
|
| 25 |
+
@compute @workgroup_size(WG, 1, 1)
|
| 26 |
+
fn main(@builtin(workgroup_id) wg: vec3<u32>,
|
| 27 |
+
@builtin(local_invocation_id) lid: vec3<u32>,
|
| 28 |
+
@builtin(num_workgroups) nwg: vec3<u32>
|
| 29 |
+
{%- if useSubgroups %},
|
| 30 |
+
@builtin(subgroup_invocation_id) sgLid: u32,
|
| 31 |
+
@builtin(subgroup_size) sgSize: u32
|
| 32 |
+
{%- endif %}) {
|
| 33 |
+
let tid = lid.x;
|
| 34 |
+
{% if gridStride %}
|
| 35 |
+
let blk = wg.x;
|
| 36 |
+
// The capped x-only dispatch is the full logical grid. Each workgroup writes
|
| 37 |
+
// exactly one partial, matching finalize's numPartials uniform.
|
| 38 |
+
let gridThreads = nwg.x * WG;
|
| 39 |
+
{% else %}
|
| 40 |
+
// 2D-folded block index: wg.y carries the high bits past the device's
|
| 41 |
+
// per-dimension dispatch limit. The whole over-dispatched workgroup returns
|
| 42 |
+
// before barriers or scratch stores.
|
| 43 |
+
// blk/numBlocks are workgroup-uniform, so the return can't split the barrier.
|
| 44 |
+
let blk = wg.x + wg.y * nwg.x;
|
| 45 |
+
{% if vec4 %}
|
| 46 |
+
let numBlocks = (params.count / 4u + WG - 1u) / WG;
|
| 47 |
+
{% else %}
|
| 48 |
+
let numBlocks = (params.count + WG * EPT - 1u) / (WG * EPT);
|
| 49 |
+
{% endif %}
|
| 50 |
+
if (blk >= numBlocks) { return; }
|
| 51 |
+
{% endif %}
|
| 52 |
+
var localMin = 0.0;
|
| 53 |
+
var localMax = 0.0;
|
| 54 |
+
{% if gridStride %}
|
| 55 |
+
{% if vec4 %}
|
| 56 |
+
// Adjacent lanes read adjacent vec4s on each grid-stride iteration.
|
| 57 |
+
let count4 = params.count / 4u;
|
| 58 |
+
for (var idx4 = blk * WG + tid; idx4 < count4; idx4 = idx4 + gridThreads) {
|
| 59 |
+
let v = x[idx4];
|
| 60 |
+
localMin = min(localMin, min(min(v.x, v.y), min(v.z, v.w)));
|
| 61 |
+
localMax = max(localMax, max(max(v.x, v.y), max(v.z, v.w)));
|
| 62 |
+
}
|
| 63 |
+
{% else %}
|
| 64 |
+
for (var idx = blk * WG + tid; idx < params.count; idx = idx + gridThreads) {
|
| 65 |
+
let value = x[idx];
|
| 66 |
+
localMin = min(localMin, value);
|
| 67 |
+
localMax = max(localMax, value);
|
| 68 |
+
}
|
| 69 |
+
{% endif %}
|
| 70 |
+
{% else %}
|
| 71 |
+
{% if vec4 %}
|
| 72 |
+
// Each thread reads one contiguous vec4; this path assumes params.count is
|
| 73 |
+
// divisible by 4.
|
| 74 |
+
let count4 = params.count / 4u;
|
| 75 |
+
let idx4 = blk * WG + tid;
|
| 76 |
+
if (idx4 < count4) {
|
| 77 |
+
let v = x[idx4];
|
| 78 |
+
localMin = min(localMin, min(min(v.x, v.y), min(v.z, v.w)));
|
| 79 |
+
localMax = max(localMax, max(max(v.x, v.y), max(v.z, v.w)));
|
| 80 |
+
}
|
| 81 |
+
{% else %}
|
| 82 |
+
let base = blk * WG * EPT;
|
| 83 |
+
for (var e = 0u; e < EPT; e = e + 1u) {
|
| 84 |
+
let idx = base + e * WG + tid;
|
| 85 |
+
if (idx < params.count) {
|
| 86 |
+
let v = x[idx];
|
| 87 |
+
localMin = min(localMin, v);
|
| 88 |
+
localMax = max(localMax, v);
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
{% endif %}
|
| 92 |
+
{% endif %}
|
| 93 |
+
{% if useSubgroups %}
|
| 94 |
+
let sgMin = subgroupMin(localMin);
|
| 95 |
+
let sgMax = subgroupMax(localMax);
|
| 96 |
+
let safeSg = max(sgSize, 1u);
|
| 97 |
+
// Do not assume that the device subgroup size divides the workgroup size.
|
| 98 |
+
let slotCount = max(1u, (WG + safeSg - 1u) / safeSg);
|
| 99 |
+
if (sgLid == 0u) {
|
| 100 |
+
let slot = min(tid / safeSg, WG - 1u);
|
| 101 |
+
wgMin[slot] = sgMin;
|
| 102 |
+
wgMax[slot] = sgMax;
|
| 103 |
+
}
|
| 104 |
+
workgroupBarrier();
|
| 105 |
+
if (tid == 0u) {
|
| 106 |
+
var totalMin = wgMin[0];
|
| 107 |
+
var totalMax = wgMax[0];
|
| 108 |
+
for (var i = 1u; i < slotCount; i = i + 1u) {
|
| 109 |
+
totalMin = min(totalMin, wgMin[i]);
|
| 110 |
+
totalMax = max(totalMax, wgMax[i]);
|
| 111 |
+
}
|
| 112 |
+
partial_min[blk] = totalMin;
|
| 113 |
+
partial_max[blk] = totalMax;
|
| 114 |
+
}
|
| 115 |
+
{% else %}
|
| 116 |
+
// No-subgroups fallback: full workgroup-tree min/max over per-thread partials.
|
| 117 |
+
wgMin[tid] = localMin;
|
| 118 |
+
wgMax[tid] = localMax;
|
| 119 |
+
workgroupBarrier();
|
| 120 |
+
for (var step = WG >> 1u; step > 0u; step = step >> 1u) {
|
| 121 |
+
if (tid < step) {
|
| 122 |
+
wgMin[tid] = min(wgMin[tid], wgMin[tid + step]);
|
| 123 |
+
wgMax[tid] = max(wgMax[tid], wgMax[tid + step]);
|
| 124 |
+
}
|
| 125 |
+
workgroupBarrier();
|
| 126 |
+
}
|
| 127 |
+
if (tid == 0u) {
|
| 128 |
+
partial_min[blk] = wgMin[0];
|
| 129 |
+
partial_max[blk] = wgMax[0];
|
| 130 |
+
}
|
| 131 |
+
{% endif %}
|
| 132 |
+
}
|
build/webgpu/dynamic-quantize-linear.wgsl.jinja
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% if source.fromPartials %}
|
| 2 |
+
// Parallel range finalization folds the guarded min/max partials and computes
|
| 3 |
+
// the output scale and zero point. Each partial already includes zero in its range.
|
| 4 |
+
{% else %}
|
| 5 |
+
// The serial path computes the complete range, scale, zero point, and quantized
|
| 6 |
+
// output in one invocation.
|
| 7 |
+
{% endif %}
|
| 8 |
+
{{ env.wgsl.resourceDeclarations }}
|
| 9 |
+
// ONNX DynamicQuantizeLinear uses correctly rounded f32 division followed by
|
| 10 |
+
// round-half-to-even. Every execution path uses these helpers so their numerical
|
| 11 |
+
// behavior cannot drift apart.
|
| 12 |
+
|
| 13 |
+
// WGSL permits f32 division to differ from the correctly-rounded result by
|
| 14 |
+
// 2.5 ULP, and fma() inherits separate multiply/add accuracy rather than
|
| 15 |
+
// promising a fused residual. Reconstruct the correctly-rounded normal result
|
| 16 |
+
// with integer significand division when the quotient can affect an integer
|
| 17 |
+
// rounding boundary. This is backend-independent and uses only exact u32 ops.
|
| 18 |
+
fn dynamic_quantize_exact_div_normal(numerator: f32, denominator: f32) -> f32 {
|
| 19 |
+
if (numerator == 0.0) {
|
| 20 |
+
return numerator;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
let numerator_bits = bitcast<u32>(numerator);
|
| 24 |
+
let denominator_bits = bitcast<u32>(denominator);
|
| 25 |
+
let sign_bits = (numerator_bits ^ denominator_bits) & 0x80000000u;
|
| 26 |
+
|
| 27 |
+
let numerator_abs = numerator_bits & 0x7fffffffu;
|
| 28 |
+
let denominator_abs = denominator_bits & 0x7fffffffu;
|
| 29 |
+
if (denominator_abs == 0u
|
| 30 |
+
|| (numerator_abs & 0x7f800000u) == 0x7f800000u
|
| 31 |
+
|| (denominator_abs & 0x7f800000u) == 0x7f800000u) {
|
| 32 |
+
return numerator / denominator;
|
| 33 |
+
}
|
| 34 |
+
var numerator_mantissa = numerator_abs & 0x007fffffu;
|
| 35 |
+
var denominator_mantissa = denominator_abs & 0x007fffffu;
|
| 36 |
+
let numerator_biased_exponent = (numerator_abs >> 23u) & 0xffu;
|
| 37 |
+
let denominator_biased_exponent = (denominator_abs >> 23u) & 0xffu;
|
| 38 |
+
var numerator_exponent: i32;
|
| 39 |
+
var denominator_exponent: i32;
|
| 40 |
+
|
| 41 |
+
if (numerator_biased_exponent == 0u) {
|
| 42 |
+
numerator_exponent = -126;
|
| 43 |
+
// Zero returned above. A non-zero subnormal reaches the implicit-bit
|
| 44 |
+
// position in at most 23 exact shifts.
|
| 45 |
+
while ((numerator_mantissa & 0x00800000u) == 0u) {
|
| 46 |
+
numerator_mantissa = numerator_mantissa << 1u;
|
| 47 |
+
numerator_exponent = numerator_exponent - 1;
|
| 48 |
+
}
|
| 49 |
+
} else {
|
| 50 |
+
numerator_mantissa = numerator_mantissa | 0x00800000u;
|
| 51 |
+
numerator_exponent = i32(numerator_biased_exponent) - 127;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
if (denominator_biased_exponent == 0u) {
|
| 55 |
+
denominator_exponent = -126;
|
| 56 |
+
while ((denominator_mantissa & 0x00800000u) == 0u) {
|
| 57 |
+
denominator_mantissa = denominator_mantissa << 1u;
|
| 58 |
+
denominator_exponent = denominator_exponent - 1;
|
| 59 |
+
}
|
| 60 |
+
} else {
|
| 61 |
+
denominator_mantissa = denominator_mantissa | 0x00800000u;
|
| 62 |
+
denominator_exponent = i32(denominator_biased_exponent) - 127;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
var quotient_exponent = numerator_exponent - denominator_exponent;
|
| 66 |
+
var remainder = numerator_mantissa;
|
| 67 |
+
if (remainder < denominator_mantissa) {
|
| 68 |
+
remainder = remainder << 1u;
|
| 69 |
+
quotient_exponent = quotient_exponent - 1;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
// The normalized ratio is now in [1, 2). Emit its implicit bit followed by
|
| 73 |
+
// all 23 stored significand bits using exact binary long division.
|
| 74 |
+
var quotient_mantissa = 0x00800000u;
|
| 75 |
+
remainder = remainder - denominator_mantissa;
|
| 76 |
+
for (var digit = 0u; digit < 23u; digit = digit + 1u) {
|
| 77 |
+
remainder = remainder << 1u;
|
| 78 |
+
if (remainder >= denominator_mantissa) {
|
| 79 |
+
remainder = remainder - denominator_mantissa;
|
| 80 |
+
quotient_mantissa = quotient_mantissa | (1u << (22u - digit));
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
// Round the 24-bit significand to nearest, ties to even. remainder and its
|
| 85 |
+
// doubled value are below 2^25, so no u32 overflow is possible.
|
| 86 |
+
let twice_remainder = remainder << 1u;
|
| 87 |
+
if (twice_remainder > denominator_mantissa
|
| 88 |
+
|| (twice_remainder == denominator_mantissa && (quotient_mantissa & 1u) != 0u)) {
|
| 89 |
+
quotient_mantissa = quotient_mantissa + 1u;
|
| 90 |
+
}
|
| 91 |
+
if (quotient_mantissa == 0x01000000u) {
|
| 92 |
+
quotient_mantissa = quotient_mantissa >> 1u;
|
| 93 |
+
quotient_exponent = quotient_exponent + 1;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
let biased_exponent = quotient_exponent + 127;
|
| 97 |
+
if (biased_exponent <= 0 || biased_exponent >= 255) {
|
| 98 |
+
// DynamicQuantizeLinear's GPU-supported ranges are normal and finite.
|
| 99 |
+
// Preserve the implementation-defined subnormal/overflow behavior outside
|
| 100 |
+
// that contract; the integer quantizer never takes this branch near a
|
| 101 |
+
// half-integer boundary.
|
| 102 |
+
return numerator / denominator;
|
| 103 |
+
}
|
| 104 |
+
let result_bits = sign_bits
|
| 105 |
+
| (u32(biased_exponent) << 23u)
|
| 106 |
+
| (quotient_mantissa & 0x007fffffu);
|
| 107 |
+
return bitcast<f32>(result_bits);
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
fn dynamic_quantize_division_may_cross_half(estimate: f32) -> bool {
|
| 111 |
+
let lower = floor(estimate);
|
| 112 |
+
let fraction = estimate - lower;
|
| 113 |
+
let magnitude = abs(estimate);
|
| 114 |
+
let magnitude_bits = bitcast<u32>(magnitude);
|
| 115 |
+
let adjacent = bitcast<f32>(magnitude_bits + 1u);
|
| 116 |
+
let ulp = adjacent - magnitude;
|
| 117 |
+
// Division is allowed 2.5 ULP error. Eight ULP also covers the factor-of-two
|
| 118 |
+
// ULP change when an estimate straddles the 0.5 exponent boundary.
|
| 119 |
+
return abs(fraction - 0.5) <= ulp * 8.0;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
fn round_dynamic_half_to_even(value: f32, scale: f32) -> i32 {
|
| 123 |
+
let estimate = value / scale;
|
| 124 |
+
var scaled = estimate;
|
| 125 |
+
// `select` evaluates both value operands in WGSL; use control flow so the
|
| 126 |
+
// 23-bit software divide remains a rare boundary fallback, not O(23) work
|
| 127 |
+
// for every quantized element.
|
| 128 |
+
if (dynamic_quantize_division_may_cross_half(estimate)) {
|
| 129 |
+
scaled = dynamic_quantize_exact_div_normal(value, scale);
|
| 130 |
+
}
|
| 131 |
+
let lower = floor(scaled);
|
| 132 |
+
let fraction = scaled - lower;
|
| 133 |
+
if (fraction < 0.5) {
|
| 134 |
+
return i32(lower);
|
| 135 |
+
}
|
| 136 |
+
if (fraction > 0.5) {
|
| 137 |
+
return i32(lower + 1.0);
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
let upper = lower + 1.0;
|
| 141 |
+
let half_lower = floor(lower * 0.5);
|
| 142 |
+
let lower_is_even = (lower - half_lower * 2.0) == 0.0;
|
| 143 |
+
return i32(select(upper, lower, lower_is_even));
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
@compute @workgroup_size(1)
|
| 148 |
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
| 149 |
+
if (gid.x != 0u) { return; }
|
| 150 |
+
{% if source.fromPartials %}
|
| 151 |
+
var min_value = partial_min[0];
|
| 152 |
+
var max_value = partial_max[0];
|
| 153 |
+
for (var i = 1u; i < params.numPartials; i = i + 1u) {
|
| 154 |
+
min_value = min(min_value, partial_min[i]);
|
| 155 |
+
max_value = max(max_value, partial_max[i]);
|
| 156 |
+
}
|
| 157 |
+
{% else %}
|
| 158 |
+
var min_value = 0.0;
|
| 159 |
+
var max_value = 0.0;
|
| 160 |
+
for (var i = 0u; i < params.count; i = i + 1u) {
|
| 161 |
+
min_value = min(min_value, x[i]);
|
| 162 |
+
max_value = max(max_value, x[i]);
|
| 163 |
+
}
|
| 164 |
+
{% endif %}
|
| 165 |
+
var scale = dynamic_quantize_exact_div_normal(max_value - min_value, 255.0);
|
| 166 |
+
if (scale == 0.0) {
|
| 167 |
+
scale = 1.0;
|
| 168 |
+
}
|
| 169 |
+
let zp_i32 = clamp(round_dynamic_half_to_even(-min_value, scale), 0, 255);
|
| 170 |
+
y_scale[0] = scale;
|
| 171 |
+
y_zero_point[0] = u32(zp_i32);
|
| 172 |
+
{% if not source.fromPartials %}
|
| 173 |
+
for (var i = 0u; i < params.count; i = i + 1u) {
|
| 174 |
+
let q = clamp(round_dynamic_half_to_even(x[i], scale) + zp_i32, 0, 255);
|
| 175 |
+
y[i] = u32(q);
|
| 176 |
+
}
|
| 177 |
+
{% endif %}
|
| 178 |
+
}
|
build/webgpu/manifest.json
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"domain": "ai.onnx",
|
| 3 |
+
"name": "DynamicQuantizeLinear",
|
| 4 |
+
"sinceVersion": 11,
|
| 5 |
+
"description": "Computes a per-tensor scale and zero point from the range of floating-point input `x`, extending the range to include zero, then quantizes each value to `uint8` as `saturate(round(x / y_scale) + y_zero_point)`. Uses round-to-nearest-even and clamps results to `[0, 255]`.",
|
| 6 |
+
"inputs": [{ "role": "x", "dtype": "T", "description": "Float32 input tensor to quantize." }],
|
| 7 |
+
"outputs": [
|
| 8 |
+
{
|
| 9 |
+
"role": "y",
|
| 10 |
+
"dtype": "TQ",
|
| 11 |
+
"rank": "ranks.x",
|
| 12 |
+
"description": "Quantized output tensor; same shape as the input.",
|
| 13 |
+
"shape": "shapes.x"
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"role": "y_scale",
|
| 17 |
+
"dtype": "T",
|
| 18 |
+
"rank": 0,
|
| 19 |
+
"description": "Per-tensor scale factor derived from the input min/max range; scalar.",
|
| 20 |
+
"shape": []
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"role": "y_zero_point",
|
| 24 |
+
"dtype": "TQ",
|
| 25 |
+
"rank": 0,
|
| 26 |
+
"description": "Per-tensor zero point for the quantization; scalar.",
|
| 27 |
+
"shape": []
|
| 28 |
+
}
|
| 29 |
+
],
|
| 30 |
+
"typeConstraints": { "T": ["float32"], "TQ": ["uint8"] },
|
| 31 |
+
"args": {
|
| 32 |
+
"x": { "kind": "tensor", "semantic": "x", "role": "input" },
|
| 33 |
+
"y": { "kind": "tensor", "semantic": "y", "role": "output" },
|
| 34 |
+
"y_scale": { "kind": "tensor", "semantic": "y_scale", "role": "output" },
|
| 35 |
+
"y_zero_point": { "kind": "tensor", "semantic": "y_zero_point", "role": "output" }
|
| 36 |
+
},
|
| 37 |
+
"tunables": {
|
| 38 |
+
"WORKGROUP_SIZE": 256,
|
| 39 |
+
"ELEMENTS_PER_THREAD": 4,
|
| 40 |
+
"GRID_STRIDE_MIN_ELEMENTS": 262144,
|
| 41 |
+
"MAX_GRID_PARTIALS": 256,
|
| 42 |
+
"SERIAL_MAX_ELEMENTS": 8192
|
| 43 |
+
},
|
| 44 |
+
"tunableDescriptions": {
|
| 45 |
+
"GRID_STRIDE_MIN_ELEMENTS": "Sets the minimum input element count admitted to the bounded grid-stride reduction.",
|
| 46 |
+
"MAX_GRID_PARTIALS": "Caps the number of min/max partials emitted by the grid-stride reduction, bounding scratch and finalization work.",
|
| 47 |
+
"SERIAL_MAX_ELEMENTS": "Sets the input-size ceiling that prefers the one-invocation route; larger inputs use it only when the parallel reduction cannot fit."
|
| 48 |
+
},
|
| 49 |
+
"derive": {
|
| 50 |
+
"deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)",
|
| 51 |
+
"storageBufferLimit": "min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)",
|
| 52 |
+
"inputCount": "numel(shapes.x)",
|
| 53 |
+
"elementsPerBlock": "tunables.WORKGROUP_SIZE * tunables.ELEMENTS_PER_THREAD",
|
| 54 |
+
"fullPartials": "ceilDiv(inputCount, elementsPerBlock)",
|
| 55 |
+
"gridPartials": "min(tunables.MAX_GRID_PARTIALS, fullPartials)",
|
| 56 |
+
"baseContract": "ranks.x >= 1 and ranks.y == ranks.x and inputCount == numel(shapes.y) and ranks.y_scale == 0 and ranks.y_zero_point == 0",
|
| 57 |
+
"serialContract": "ranks.x >= 0 and ranks.y == ranks.x and inputCount == numel(shapes.y) and ranks.y_scale == 0 and ranks.y_zero_point == 0",
|
| 58 |
+
"parallelDeviceOk": "tunables.WORKGROUP_SIZE <= deviceWorkgroupCap and 8 * tunables.WORKGROUP_SIZE <= device.limits.maxComputeWorkgroupStorageSize",
|
| 59 |
+
"fullScratchFits": "4 * fullPartials <= storageBufferLimit",
|
| 60 |
+
"gridScratchFits": "4 * gridPartials <= storageBufferLimit",
|
| 61 |
+
"fullDispatchFits": "ceilDiv(fullPartials, device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension",
|
| 62 |
+
"gridDispatchFits": "gridPartials <= device.limits.maxComputeWorkgroupsPerDimension",
|
| 63 |
+
"parallelFullFits": "parallelDeviceOk and fullScratchFits and fullDispatchFits",
|
| 64 |
+
"parallelGridFits": "parallelDeviceOk and gridScratchFits and gridDispatchFits and fullDispatchFits",
|
| 65 |
+
"serialFallbackNeeded": "inputCount <= tunables.SERIAL_MAX_ELEMENTS or not parallelFullFits"
|
| 66 |
+
},
|
| 67 |
+
"bindingSets": {
|
| 68 |
+
"reduce": [
|
| 69 |
+
{
|
| 70 |
+
"name": "x",
|
| 71 |
+
"arg": "x",
|
| 72 |
+
"semantic": "x",
|
| 73 |
+
"buffer": { "type": "read-only-storage" },
|
| 74 |
+
"elementType": "$inputElement"
|
| 75 |
+
},
|
| 76 |
+
{ "name": "partial_min", "semantic": "partial_min", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 77 |
+
{ "name": "partial_max", "semantic": "partial_max", "buffer": { "type": "storage" }, "elementType": "f32" },
|
| 78 |
+
{
|
| 79 |
+
"name": "params",
|
| 80 |
+
"semantic": "kernel.params",
|
| 81 |
+
"buffer": { "type": "uniform" },
|
| 82 |
+
"struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.x)" }] }
|
| 83 |
+
}
|
| 84 |
+
],
|
| 85 |
+
"finalizeFull": [
|
| 86 |
+
{
|
| 87 |
+
"name": "partial_min",
|
| 88 |
+
"semantic": "partial_min",
|
| 89 |
+
"buffer": { "type": "read-only-storage" },
|
| 90 |
+
"elementType": "f32"
|
| 91 |
+
},
|
| 92 |
+
{
|
| 93 |
+
"name": "partial_max",
|
| 94 |
+
"semantic": "partial_max",
|
| 95 |
+
"buffer": { "type": "read-only-storage" },
|
| 96 |
+
"elementType": "f32"
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"name": "y_scale",
|
| 100 |
+
"arg": "y_scale",
|
| 101 |
+
"semantic": "y_scale",
|
| 102 |
+
"buffer": { "type": "storage" },
|
| 103 |
+
"elementType": "f32",
|
| 104 |
+
"length": 1
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
"name": "y_zero_point",
|
| 108 |
+
"arg": "y_zero_point",
|
| 109 |
+
"semantic": "y_zero_point",
|
| 110 |
+
"buffer": { "type": "storage" },
|
| 111 |
+
"elementType": "u32",
|
| 112 |
+
"length": 1
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"name": "params",
|
| 116 |
+
"semantic": "kernel.params",
|
| 117 |
+
"buffer": { "type": "uniform" },
|
| 118 |
+
"struct": { "name": "Params", "fields": [{ "name": "numPartials", "type": "u32", "value": "fullPartials" }] }
|
| 119 |
+
}
|
| 120 |
+
],
|
| 121 |
+
"finalizeGrid": [
|
| 122 |
+
{
|
| 123 |
+
"name": "partial_min",
|
| 124 |
+
"semantic": "partial_min",
|
| 125 |
+
"buffer": { "type": "read-only-storage" },
|
| 126 |
+
"elementType": "f32"
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"name": "partial_max",
|
| 130 |
+
"semantic": "partial_max",
|
| 131 |
+
"buffer": { "type": "read-only-storage" },
|
| 132 |
+
"elementType": "f32"
|
| 133 |
+
},
|
| 134 |
+
{
|
| 135 |
+
"name": "y_scale",
|
| 136 |
+
"arg": "y_scale",
|
| 137 |
+
"semantic": "y_scale",
|
| 138 |
+
"buffer": { "type": "storage" },
|
| 139 |
+
"elementType": "f32",
|
| 140 |
+
"length": 1
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"name": "y_zero_point",
|
| 144 |
+
"arg": "y_zero_point",
|
| 145 |
+
"semantic": "y_zero_point",
|
| 146 |
+
"buffer": { "type": "storage" },
|
| 147 |
+
"elementType": "u32",
|
| 148 |
+
"length": 1
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
"name": "params",
|
| 152 |
+
"semantic": "kernel.params",
|
| 153 |
+
"buffer": { "type": "uniform" },
|
| 154 |
+
"struct": { "name": "Params", "fields": [{ "name": "numPartials", "type": "u32", "value": "gridPartials" }] }
|
| 155 |
+
}
|
| 156 |
+
],
|
| 157 |
+
"quantize": [
|
| 158 |
+
{
|
| 159 |
+
"name": "x",
|
| 160 |
+
"arg": "x",
|
| 161 |
+
"semantic": "x",
|
| 162 |
+
"buffer": { "type": "read-only-storage" },
|
| 163 |
+
"elementType": "$inputElement"
|
| 164 |
+
},
|
| 165 |
+
{
|
| 166 |
+
"name": "y_scale",
|
| 167 |
+
"arg": "y_scale",
|
| 168 |
+
"semantic": "y_scale",
|
| 169 |
+
"buffer": { "type": "read-only-storage" },
|
| 170 |
+
"elementType": "f32",
|
| 171 |
+
"length": 1
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
"name": "y_zero_point",
|
| 175 |
+
"arg": "y_zero_point",
|
| 176 |
+
"semantic": "y_zero_point",
|
| 177 |
+
"buffer": { "type": "read-only-storage" },
|
| 178 |
+
"elementType": "u32",
|
| 179 |
+
"length": 1
|
| 180 |
+
},
|
| 181 |
+
{ "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "u32" },
|
| 182 |
+
{
|
| 183 |
+
"name": "params",
|
| 184 |
+
"semantic": "kernel.params",
|
| 185 |
+
"buffer": { "type": "uniform" },
|
| 186 |
+
"struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.x)" }] }
|
| 187 |
+
}
|
| 188 |
+
],
|
| 189 |
+
"serial": [
|
| 190 |
+
{ "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "f32" },
|
| 191 |
+
{ "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "u32" },
|
| 192 |
+
{
|
| 193 |
+
"name": "y_scale",
|
| 194 |
+
"arg": "y_scale",
|
| 195 |
+
"semantic": "y_scale",
|
| 196 |
+
"buffer": { "type": "storage" },
|
| 197 |
+
"elementType": "f32",
|
| 198 |
+
"length": 1
|
| 199 |
+
},
|
| 200 |
+
{
|
| 201 |
+
"name": "y_zero_point",
|
| 202 |
+
"arg": "y_zero_point",
|
| 203 |
+
"semantic": "y_zero_point",
|
| 204 |
+
"buffer": { "type": "storage" },
|
| 205 |
+
"elementType": "u32",
|
| 206 |
+
"length": 1
|
| 207 |
+
},
|
| 208 |
+
{
|
| 209 |
+
"name": "params",
|
| 210 |
+
"semantic": "kernel.params",
|
| 211 |
+
"buffer": { "type": "uniform" },
|
| 212 |
+
"struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.x)" }] }
|
| 213 |
+
}
|
| 214 |
+
]
|
| 215 |
+
},
|
| 216 |
+
"variants": [
|
| 217 |
+
{
|
| 218 |
+
"id": "single_invocation",
|
| 219 |
+
"description": "Uses one invocation to find the range and quantize the tensor, avoiding partial buffers for small inputs. It also provides the fallback when the parallel reduction cannot satisfy device limits.",
|
| 220 |
+
"when": ["serialContract", "serialFallbackNeeded"],
|
| 221 |
+
"passes": [
|
| 222 |
+
{
|
| 223 |
+
"id": "main",
|
| 224 |
+
"name": "DynamicQuantizeLinear",
|
| 225 |
+
"source": { "shader": "dynamic-quantize-linear.wgsl.jinja", "inputs": { "fromPartials": false } },
|
| 226 |
+
"bindings": "serial",
|
| 227 |
+
"dispatch": { "x": 1 }
|
| 228 |
+
}
|
| 229 |
+
]
|
| 230 |
+
},
|
| 231 |
+
{
|
| 232 |
+
"id": "parallel_subgroup_reduce_vec4",
|
| 233 |
+
"description": "Reduces independent input blocks to min/max partials, combines them, and quantizes in a separate pass. The family uses packed reads when the input length is vec4-aligned.",
|
| 234 |
+
"priority": 11,
|
| 235 |
+
"when": ["baseContract", "inputCount > 0", "inputCount % 4 == 0", "parallelFullFits"],
|
| 236 |
+
"constants": {
|
| 237 |
+
"workgroupSize": "tunables.WORKGROUP_SIZE",
|
| 238 |
+
"elemsPerThread": "tunables.ELEMENTS_PER_THREAD",
|
| 239 |
+
"vec4": true,
|
| 240 |
+
"inputElement": "\"vec4<f32>\"",
|
| 241 |
+
"useSubgroups": "device.features.has(\"subgroups\")"
|
| 242 |
+
},
|
| 243 |
+
"intermediates": [
|
| 244 |
+
{ "id": "partial_min", "dtype": "float32", "shape": "[fullPartials]" },
|
| 245 |
+
{ "id": "partial_max", "dtype": "float32", "shape": "[fullPartials]" }
|
| 246 |
+
],
|
| 247 |
+
"passes": [
|
| 248 |
+
{
|
| 249 |
+
"id": "reduce",
|
| 250 |
+
"name": "DynamicQuantizeLinear.ReduceMinMax",
|
| 251 |
+
"shader": "dynamic-quantize-linear-reduce.wgsl.jinja",
|
| 252 |
+
"bindings": "reduce",
|
| 253 |
+
"dispatch": { "workgroups": "fullPartials" }
|
| 254 |
+
},
|
| 255 |
+
{
|
| 256 |
+
"id": "finalize",
|
| 257 |
+
"name": "DynamicQuantizeLinear.Finalize",
|
| 258 |
+
"source": { "shader": "dynamic-quantize-linear.wgsl.jinja", "inputs": { "fromPartials": true } },
|
| 259 |
+
"bindings": "finalizeFull",
|
| 260 |
+
"dispatch": { "x": 1 }
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"id": "quantize",
|
| 264 |
+
"name": "DynamicQuantizeLinear.Quantize",
|
| 265 |
+
"shader": "dynamic-quantize-linear-quantize.wgsl.jinja",
|
| 266 |
+
"bindings": "quantize",
|
| 267 |
+
"dispatch": {
|
| 268 |
+
"threads": "ceilDiv(inputCount, tunables.ELEMENTS_PER_THREAD)",
|
| 269 |
+
"workgroupSize": "tunables.WORKGROUP_SIZE"
|
| 270 |
+
}
|
| 271 |
+
}
|
| 272 |
+
]
|
| 273 |
+
},
|
| 274 |
+
{
|
| 275 |
+
"id": "parallel_subgroup_reduce",
|
| 276 |
+
"description": "Reduces independent input blocks to min/max partials, combines them, and quantizes in a separate pass. The family uses packed reads when the input length is vec4-aligned.",
|
| 277 |
+
"priority": 10,
|
| 278 |
+
"when": ["baseContract", "inputCount > 0", "true", "parallelFullFits"],
|
| 279 |
+
"constants": {
|
| 280 |
+
"workgroupSize": "tunables.WORKGROUP_SIZE",
|
| 281 |
+
"elemsPerThread": "tunables.ELEMENTS_PER_THREAD",
|
| 282 |
+
"vec4": false,
|
| 283 |
+
"inputElement": "\"f32\"",
|
| 284 |
+
"useSubgroups": "device.features.has(\"subgroups\")"
|
| 285 |
+
},
|
| 286 |
+
"intermediates": [
|
| 287 |
+
{ "id": "partial_min", "dtype": "float32", "shape": "[fullPartials]" },
|
| 288 |
+
{ "id": "partial_max", "dtype": "float32", "shape": "[fullPartials]" }
|
| 289 |
+
],
|
| 290 |
+
"passes": [
|
| 291 |
+
{
|
| 292 |
+
"id": "reduce",
|
| 293 |
+
"name": "DynamicQuantizeLinear.ReduceMinMax",
|
| 294 |
+
"shader": "dynamic-quantize-linear-reduce.wgsl.jinja",
|
| 295 |
+
"bindings": "reduce",
|
| 296 |
+
"dispatch": { "workgroups": "fullPartials" }
|
| 297 |
+
},
|
| 298 |
+
{
|
| 299 |
+
"id": "finalize",
|
| 300 |
+
"name": "DynamicQuantizeLinear.Finalize",
|
| 301 |
+
"source": { "shader": "dynamic-quantize-linear.wgsl.jinja", "inputs": { "fromPartials": true } },
|
| 302 |
+
"bindings": "finalizeFull",
|
| 303 |
+
"dispatch": { "x": 1 }
|
| 304 |
+
},
|
| 305 |
+
{
|
| 306 |
+
"id": "quantize",
|
| 307 |
+
"name": "DynamicQuantizeLinear.Quantize",
|
| 308 |
+
"shader": "dynamic-quantize-linear-quantize.wgsl.jinja",
|
| 309 |
+
"bindings": "quantize",
|
| 310 |
+
"dispatch": {
|
| 311 |
+
"threads": "ceilDiv(inputCount, tunables.ELEMENTS_PER_THREAD)",
|
| 312 |
+
"workgroupSize": "tunables.WORKGROUP_SIZE"
|
| 313 |
+
}
|
| 314 |
+
}
|
| 315 |
+
]
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"id": "grid_stride_reduce_vec4",
|
| 319 |
+
"description": "Caps the number of min/max partials and grid-strides each workgroup across the input. This bounds scratch size and finalization work for large tensors.",
|
| 320 |
+
"priority": 12,
|
| 321 |
+
"when": ["baseContract", "inputCount > 0", "inputCount % 4 == 0", "inputCount >= tunables.GRID_STRIDE_MIN_ELEMENTS", "parallelGridFits"],
|
| 322 |
+
"constants": {
|
| 323 |
+
"workgroupSize": "tunables.WORKGROUP_SIZE",
|
| 324 |
+
"elemsPerThread": "tunables.ELEMENTS_PER_THREAD",
|
| 325 |
+
"vec4": true,
|
| 326 |
+
"inputElement": "\"vec4<f32>\"",
|
| 327 |
+
"useSubgroups": "device.features.has(\"subgroups\")"
|
| 328 |
+
},
|
| 329 |
+
"intermediates": [
|
| 330 |
+
{ "id": "partial_min", "dtype": "float32", "shape": "[gridPartials]" },
|
| 331 |
+
{ "id": "partial_max", "dtype": "float32", "shape": "[gridPartials]" }
|
| 332 |
+
],
|
| 333 |
+
"passes": [
|
| 334 |
+
{
|
| 335 |
+
"id": "reduce",
|
| 336 |
+
"name": "DynamicQuantizeLinear.ReduceMinMax",
|
| 337 |
+
"source": { "shader": "dynamic-quantize-linear-reduce.wgsl.jinja", "inputs": { "gridStride": true } },
|
| 338 |
+
"bindings": "reduce",
|
| 339 |
+
"dispatch": { "x": "gridPartials" }
|
| 340 |
+
},
|
| 341 |
+
{
|
| 342 |
+
"id": "finalize",
|
| 343 |
+
"name": "DynamicQuantizeLinear.Finalize",
|
| 344 |
+
"source": { "shader": "dynamic-quantize-linear.wgsl.jinja", "inputs": { "fromPartials": true } },
|
| 345 |
+
"bindings": "finalizeGrid",
|
| 346 |
+
"dispatch": { "x": 1 }
|
| 347 |
+
},
|
| 348 |
+
{
|
| 349 |
+
"id": "quantize",
|
| 350 |
+
"name": "DynamicQuantizeLinear.Quantize",
|
| 351 |
+
"shader": "dynamic-quantize-linear-quantize.wgsl.jinja",
|
| 352 |
+
"bindings": "quantize",
|
| 353 |
+
"dispatch": {
|
| 354 |
+
"threads": "ceilDiv(inputCount, tunables.ELEMENTS_PER_THREAD)",
|
| 355 |
+
"workgroupSize": "tunables.WORKGROUP_SIZE"
|
| 356 |
+
}
|
| 357 |
+
}
|
| 358 |
+
]
|
| 359 |
+
},
|
| 360 |
+
{
|
| 361 |
+
"id": "grid_stride_reduce",
|
| 362 |
+
"description": "Caps the number of min/max partials and grid-strides each workgroup across the input. This bounds scratch size and finalization work for large tensors.",
|
| 363 |
+
"priority": 12,
|
| 364 |
+
"when": ["baseContract", "inputCount > 0", "inputCount % 4 != 0", "inputCount >= tunables.GRID_STRIDE_MIN_ELEMENTS", "parallelGridFits"],
|
| 365 |
+
"constants": {
|
| 366 |
+
"workgroupSize": "tunables.WORKGROUP_SIZE",
|
| 367 |
+
"elemsPerThread": "tunables.ELEMENTS_PER_THREAD",
|
| 368 |
+
"vec4": false,
|
| 369 |
+
"inputElement": "\"f32\"",
|
| 370 |
+
"useSubgroups": "device.features.has(\"subgroups\")"
|
| 371 |
+
},
|
| 372 |
+
"intermediates": [
|
| 373 |
+
{ "id": "partial_min", "dtype": "float32", "shape": "[gridPartials]" },
|
| 374 |
+
{ "id": "partial_max", "dtype": "float32", "shape": "[gridPartials]" }
|
| 375 |
+
],
|
| 376 |
+
"passes": [
|
| 377 |
+
{
|
| 378 |
+
"id": "reduce",
|
| 379 |
+
"name": "DynamicQuantizeLinear.ReduceMinMax",
|
| 380 |
+
"source": { "shader": "dynamic-quantize-linear-reduce.wgsl.jinja", "inputs": { "gridStride": true } },
|
| 381 |
+
"bindings": "reduce",
|
| 382 |
+
"dispatch": { "x": "gridPartials" }
|
| 383 |
+
},
|
| 384 |
+
{
|
| 385 |
+
"id": "finalize",
|
| 386 |
+
"name": "DynamicQuantizeLinear.Finalize",
|
| 387 |
+
"source": { "shader": "dynamic-quantize-linear.wgsl.jinja", "inputs": { "fromPartials": true } },
|
| 388 |
+
"bindings": "finalizeGrid",
|
| 389 |
+
"dispatch": { "x": 1 }
|
| 390 |
+
},
|
| 391 |
+
{
|
| 392 |
+
"id": "quantize",
|
| 393 |
+
"name": "DynamicQuantizeLinear.Quantize",
|
| 394 |
+
"shader": "dynamic-quantize-linear-quantize.wgsl.jinja",
|
| 395 |
+
"bindings": "quantize",
|
| 396 |
+
"dispatch": {
|
| 397 |
+
"threads": "ceilDiv(inputCount, tunables.ELEMENTS_PER_THREAD)",
|
| 398 |
+
"workgroupSize": "tunables.WORKGROUP_SIZE"
|
| 399 |
+
}
|
| 400 |
+
}
|
| 401 |
+
]
|
| 402 |
+
}
|
| 403 |
+
]
|
| 404 |
+
}
|
build/webgpu/metadata.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "ai.onnx.DynamicQuantizeLinear",
|
| 3 |
+
"id": "_ai_onnx_dynamicquantizelinear_webgpu_679d73b",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "Apache-2.0",
|
| 6 |
+
"backend": { "type": "webgpu" },
|
| 7 |
+
"digest": {
|
| 8 |
+
"algorithm": "sha256",
|
| 9 |
+
"files": {
|
| 10 |
+
"bench.json": "ic1d0BkyDcNr1B1KJ9oJN/5mnB04zIaCIW6y43kaEgY=",
|
| 11 |
+
"dynamic-quantize-linear-quantize.wgsl.jinja": "8J/MWQOE+FLfjP/5JCQvU7v0ncgxQcM79GMW6PtSPr8=",
|
| 12 |
+
"dynamic-quantize-linear-reduce.wgsl.jinja": "hbPwzMCANALDD58/USzBkSqY18jQS952IABcmsOy69w=",
|
| 13 |
+
"dynamic-quantize-linear.wgsl.jinja": "ccNGv53bI60K7evqWhmtmZi8ocQUp/zx9X9ICATi03M=",
|
| 14 |
+
"manifest.json": "i7HEkBE8BwjpTprLea26Sqfyb5DM1CVD7V8uRXE0DLI=",
|
| 15 |
+
"test.json": "I3ltf1DnGsVq5fWasOGlLYKfEDY9rtTLSV7xrLiU++M="
|
| 16 |
+
}
|
| 17 |
+
},
|
| 18 |
+
"provenance": { "kernel": { "sha": "2e7068faf55e7f43df740015f6d1ee49391a41c5", "dirty": false } },
|
| 19 |
+
"webgpu": { "manifestSpec": "1.0", "specialized": true, "opPath": "ops/ai.onnx.DynamicQuantizeLinear" }
|
| 20 |
+
}
|
build/webgpu/test.json
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"op": "ai.onnx.DynamicQuantizeLinear",
|
| 3 |
+
"cases": [
|
| 4 |
+
{
|
| 5 |
+
"name": "mixed_sign_values",
|
| 6 |
+
"inputs": {
|
| 7 |
+
"x": {
|
| 8 |
+
"dtype": "float32",
|
| 9 |
+
"shape": [6],
|
| 10 |
+
"data": { "kind": "values", "values": [-1.0, -0.5, 0.0, 0.5, 1.0, 2.0] }
|
| 11 |
+
}
|
| 12 |
+
},
|
| 13 |
+
"outputs": {
|
| 14 |
+
"y": { "dtype": "uint8", "shape": [6] },
|
| 15 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 16 |
+
"y_zero_point": { "dtype": "uint8", "shape": [] }
|
| 17 |
+
}
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"name": "ort_mixed_sign_rounding_formulation",
|
| 21 |
+
"provenance": {
|
| 22 |
+
"source": "onnxruntime/test/providers/cpu/tensor/dynamic_quantize_linear_test.cc",
|
| 23 |
+
"test": "QuantizeLinearOpTest.DynamicQuantizeLinear"
|
| 24 |
+
},
|
| 25 |
+
"inputs": {
|
| 26 |
+
"x": {
|
| 27 |
+
"dtype": "float32",
|
| 28 |
+
"shape": [6],
|
| 29 |
+
"data": { "kind": "values", "values": [0.0, 2.0, -3.0, -2.5, 1.34, 0.5] }
|
| 30 |
+
}
|
| 31 |
+
},
|
| 32 |
+
"outputs": {
|
| 33 |
+
"y": { "dtype": "uint8", "shape": [6] },
|
| 34 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 35 |
+
"y_zero_point": { "dtype": "uint8", "shape": [] }
|
| 36 |
+
}
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"name": "all_positive_includes_zero_range",
|
| 40 |
+
"inputs": {
|
| 41 |
+
"x": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [1.0, 2.0, 3.0, 4.0] } }
|
| 42 |
+
},
|
| 43 |
+
"outputs": {
|
| 44 |
+
"y": { "dtype": "uint8", "shape": [4] },
|
| 45 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 46 |
+
"y_zero_point": { "dtype": "uint8", "shape": [] }
|
| 47 |
+
}
|
| 48 |
+
},
|
| 49 |
+
{
|
| 50 |
+
"name": "constant_zero_scale_fallback",
|
| 51 |
+
"inputs": {
|
| 52 |
+
"x": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [0.0, 0.0, 0.0, 0.0] } }
|
| 53 |
+
},
|
| 54 |
+
"outputs": {
|
| 55 |
+
"y": { "dtype": "uint8", "shape": [4] },
|
| 56 |
+
"y_scale": { "dtype": "float32", "shape": [] },
|
| 57 |
+
"y_zero_point": { "dtype": "uint8", "shape": [] }
|
| 58 |
+
}
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"name": "subnormal_positive_range_preserves_scale_gpu_gap",
|
| 62 |
+
"skipGpu": {
|
| 63 |
+
"category": "permanent",
|
| 64 |
+
"reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal scale values required by this fixture. Backend evidence: DynamicQuantizeLinear computes scale=(max-min)/255 then x/scale; a subnormal input range yields a subnormal scale and Metal flushes denormals in floating-point division (-> NaN/0), so subnormal-range cases remain CPU-reference-only."
|
| 65 |
+
},
|
| 66 |
+
"provenance": {
|
| 67 |
+
"source": "onnxruntime/test/providers/cpu/tensor/dynamic_quantize_linear_test.cc",
|
| 68 |
+
"test": "QuantizeLinearOpTest.DynamicQuantizeLinear",
|
| 69 |
+
"notes": "Tiny but valid positive range: y_scale is a float32 subnormal, so a backend must not flush it to zero and take the constant-input fallback."
|
| 70 |
+
},
|
| 71 |
+
"inputs": { "x": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [0.0, 1e-40] } } },
|
| 72 |
+
"outputs": {
|
| 73 |
+
"y": { "dtype": "uint8", "shape": [2], "tolerance": 0 },
|
| 74 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0 },
|
| 75 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 76 |
+
}
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"name": "subnormal_negative_range_preserves_scale_gpu_gap",
|
| 80 |
+
"skipGpu": {
|
| 81 |
+
"category": "permanent",
|
| 82 |
+
"reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal scale values required by this fixture. Backend evidence: DynamicQuantizeLinear computes scale=(max-min)/255 then x/scale; a subnormal input range yields a subnormal scale and Metal flushes denormals in floating-point division (-> NaN/0), so subnormal-range cases remain CPU-reference-only."
|
| 83 |
+
},
|
| 84 |
+
"provenance": {
|
| 85 |
+
"source": "onnxruntime/test/providers/cpu/tensor/dynamic_quantize_linear_test.cc",
|
| 86 |
+
"test": "QuantizeLinearOpTest.DynamicQuantizeLinear_Max_Adjusted",
|
| 87 |
+
"notes": "Tiny but valid negative range: ONNX adjusts the max to zero, producing a float32 subnormal scale and a high zero point rather than the constant-input fallback."
|
| 88 |
+
},
|
| 89 |
+
"inputs": { "x": { "dtype": "float32", "shape": [2], "data": { "kind": "values", "values": [-1e-40, 0.0] } } },
|
| 90 |
+
"outputs": {
|
| 91 |
+
"y": { "dtype": "uint8", "shape": [2], "tolerance": 0 },
|
| 92 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0 },
|
| 93 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 94 |
+
}
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"name": "subnormal_mixed_sign_range_vec4_reduce_gpu_gap",
|
| 98 |
+
"skipGpu": {
|
| 99 |
+
"category": "permanent",
|
| 100 |
+
"reason": "Portable WGSL floating-point semantics do not guarantee preservation of the subnormal scale values required by this fixture. Backend evidence: DynamicQuantizeLinear computes scale=(max-min)/255 then x/scale; a subnormal input range yields a subnormal scale and Metal flushes denormals in floating-point division (-> NaN/0), so subnormal-range cases remain CPU-reference-only."
|
| 101 |
+
},
|
| 102 |
+
"provenance": {
|
| 103 |
+
"source": "onnxruntime/test/providers/cpu/tensor/dynamic_quantize_linear_test.cc",
|
| 104 |
+
"test": "QuantizeLinearOpTest.DynamicQuantizeLinear",
|
| 105 |
+
"notes": "Tiny mixed-sign range with four elements exercises the vec4 reduce path: ONNX should keep a subnormal scale and nonzero zero point."
|
| 106 |
+
},
|
| 107 |
+
"inputs": {
|
| 108 |
+
"x": {
|
| 109 |
+
"dtype": "float32",
|
| 110 |
+
"shape": [4],
|
| 111 |
+
"data": { "kind": "values", "values": [-1e-40, -5e-41, 5e-41, 1e-40] }
|
| 112 |
+
}
|
| 113 |
+
},
|
| 114 |
+
"outputs": {
|
| 115 |
+
"y": { "dtype": "uint8", "shape": [4], "tolerance": 0 },
|
| 116 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0 },
|
| 117 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 118 |
+
}
|
| 119 |
+
},
|
| 120 |
+
{
|
| 121 |
+
"name": "min_adjusted_positive_2d_ort",
|
| 122 |
+
"provenance": {
|
| 123 |
+
"source": "onnxruntime/test/providers/cpu/tensor/dynamic_quantize_linear_test.cc",
|
| 124 |
+
"test": "QuantizeLinearOpTest.DynamicQuantizeLinear_Min_Adjusted"
|
| 125 |
+
},
|
| 126 |
+
"inputs": {
|
| 127 |
+
"x": {
|
| 128 |
+
"dtype": "float32",
|
| 129 |
+
"shape": [3, 4],
|
| 130 |
+
"data": { "kind": "values", "values": [1.0, 2.1, 1.3, 2.5, 3.34, 4.0, 1.5, 2.6, 3.9, 4.0, 3.0, 2.345] }
|
| 131 |
+
}
|
| 132 |
+
},
|
| 133 |
+
"outputs": {
|
| 134 |
+
"y": { "dtype": "uint8", "shape": [3, 4] },
|
| 135 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 136 |
+
"y_zero_point": { "dtype": "uint8", "shape": [] }
|
| 137 |
+
}
|
| 138 |
+
},
|
| 139 |
+
{
|
| 140 |
+
"name": "max_adjusted_negative_ort",
|
| 141 |
+
"provenance": {
|
| 142 |
+
"source": "onnxruntime/test/providers/cpu/tensor/dynamic_quantize_linear_test.cc",
|
| 143 |
+
"test": "QuantizeLinearOpTest.DynamicQuantizeLinear_Max_Adjusted"
|
| 144 |
+
},
|
| 145 |
+
"inputs": {
|
| 146 |
+
"x": {
|
| 147 |
+
"dtype": "float32",
|
| 148 |
+
"shape": [6],
|
| 149 |
+
"data": { "kind": "values", "values": [-1.0, -2.1, -1.3, -2.5, -3.34, -4.0] }
|
| 150 |
+
}
|
| 151 |
+
},
|
| 152 |
+
"outputs": {
|
| 153 |
+
"y": { "dtype": "uint8", "shape": [6] },
|
| 154 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 155 |
+
"y_zero_point": { "dtype": "uint8", "shape": [] }
|
| 156 |
+
}
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"name": "singleton_positive_adjusts_min_to_zero",
|
| 160 |
+
"inputs": { "x": { "dtype": "float32", "shape": [1], "data": { "kind": "values", "values": [5.0] } } },
|
| 161 |
+
"outputs": {
|
| 162 |
+
"y": { "dtype": "uint8", "shape": [1] },
|
| 163 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 164 |
+
"y_zero_point": { "dtype": "uint8", "shape": [] }
|
| 165 |
+
}
|
| 166 |
+
},
|
| 167 |
+
{
|
| 168 |
+
"name": "onnx_backend_dynamicquantizelinear",
|
| 169 |
+
"provenance": { "source": "cmake/external/onnx/onnx/backend/test/data/node/test_dynamicquantizelinear" },
|
| 170 |
+
"inputs": {
|
| 171 |
+
"x": {
|
| 172 |
+
"dtype": "float32",
|
| 173 |
+
"shape": [6],
|
| 174 |
+
"data": { "kind": "values", "values": [0.0, 2.0, -3.0, -2.5, 1.340000033378601, 0.5] }
|
| 175 |
+
}
|
| 176 |
+
},
|
| 177 |
+
"outputs": {
|
| 178 |
+
"y": { "dtype": "uint8", "shape": [6], "tolerance": 0 },
|
| 179 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.00001 },
|
| 180 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 181 |
+
}
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"name": "onnx_backend_dynamicquantizelinear_max_adjusted",
|
| 185 |
+
"provenance": {
|
| 186 |
+
"source": "cmake/external/onnx/onnx/backend/test/data/node/test_dynamicquantizelinear_max_adjusted"
|
| 187 |
+
},
|
| 188 |
+
"inputs": {
|
| 189 |
+
"x": {
|
| 190 |
+
"dtype": "float32",
|
| 191 |
+
"shape": [6],
|
| 192 |
+
"data": {
|
| 193 |
+
"kind": "values",
|
| 194 |
+
"values": [-1.0, -2.0999999046325684, -1.2999999523162842, -2.5, -3.3399999141693115, -4.0]
|
| 195 |
+
}
|
| 196 |
+
}
|
| 197 |
+
},
|
| 198 |
+
"outputs": {
|
| 199 |
+
"y": { "dtype": "uint8", "shape": [6], "tolerance": 0 },
|
| 200 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.00001 },
|
| 201 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 202 |
+
}
|
| 203 |
+
},
|
| 204 |
+
{
|
| 205 |
+
"name": "onnx_backend_dynamicquantizelinear_min_adjusted",
|
| 206 |
+
"provenance": {
|
| 207 |
+
"source": "cmake/external/onnx/onnx/backend/test/data/node/test_dynamicquantizelinear_min_adjusted"
|
| 208 |
+
},
|
| 209 |
+
"inputs": {
|
| 210 |
+
"x": {
|
| 211 |
+
"dtype": "float32",
|
| 212 |
+
"shape": [3, 4],
|
| 213 |
+
"data": {
|
| 214 |
+
"kind": "values",
|
| 215 |
+
"values": [1.0, 2.0999999046325684, 1.2999999523162842, 2.5, 3.3399999141693115, 4.0, 1.5, 2.5999999046325684, 3.9000000953674316, 4.0, 3.0, 2.3450000286102295]
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
},
|
| 219 |
+
"outputs": {
|
| 220 |
+
"y": { "dtype": "uint8", "shape": [3, 4], "tolerance": 0 },
|
| 221 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.00001 },
|
| 222 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 223 |
+
}
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
"name": "parallel_mixed_sign_5000",
|
| 227 |
+
"inputs": {
|
| 228 |
+
"x": {
|
| 229 |
+
"dtype": "float32",
|
| 230 |
+
"shape": [5000],
|
| 231 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.017, "scale": 3.0 }
|
| 232 |
+
}
|
| 233 |
+
},
|
| 234 |
+
"outputs": {
|
| 235 |
+
"y": { "dtype": "uint8", "shape": [5000], "tolerance": 0 },
|
| 236 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 237 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 238 |
+
}
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"name": "parallel_all_negative_1500_tail",
|
| 242 |
+
"inputs": {
|
| 243 |
+
"x": {
|
| 244 |
+
"dtype": "float32",
|
| 245 |
+
"shape": [1500],
|
| 246 |
+
"data": {
|
| 247 |
+
"kind": "cycle",
|
| 248 |
+
"values": [-1.0, -2.1, -1.3, -2.5, -3.34, -4.0, -0.25, -7.75, -0.001, -6.5, -2.75, -5.125, -3.5]
|
| 249 |
+
}
|
| 250 |
+
}
|
| 251 |
+
},
|
| 252 |
+
"outputs": {
|
| 253 |
+
"y": { "dtype": "uint8", "shape": [1500], "tolerance": 0 },
|
| 254 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 255 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 256 |
+
}
|
| 257 |
+
},
|
| 258 |
+
{
|
| 259 |
+
"name": "parallel_const_zero_1027_tail",
|
| 260 |
+
"inputs": { "x": { "dtype": "float32", "shape": [1027], "data": { "kind": "cycle", "values": [0.0] } } },
|
| 261 |
+
"outputs": {
|
| 262 |
+
"y": { "dtype": "uint8", "shape": [1027], "tolerance": 0 },
|
| 263 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0 },
|
| 264 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 265 |
+
}
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"name": "scalar_rank0_positive",
|
| 269 |
+
"inputs": { "x": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [5.0] } } },
|
| 270 |
+
"outputs": {
|
| 271 |
+
"y": { "dtype": "uint8", "shape": [], "tolerance": 0 },
|
| 272 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 273 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 274 |
+
}
|
| 275 |
+
},
|
| 276 |
+
{
|
| 277 |
+
"name": "scalar_rank0_negative",
|
| 278 |
+
"provenance": {
|
| 279 |
+
"source": "onnxruntime/test/providers/cpu/tensor/dynamic_quantize_linear_test.cc",
|
| 280 |
+
"test": "QuantizeLinearOpTest.DynamicQuantizeLinear_Max_Adjusted",
|
| 281 |
+
"notes": "Rank-0 scalar companion for the all-negative max-adjusted path: max is adjusted to include zero, producing zero_point=255."
|
| 282 |
+
},
|
| 283 |
+
"inputs": { "x": { "dtype": "float32", "shape": [], "data": { "kind": "values", "values": [-5.0] } } },
|
| 284 |
+
"outputs": {
|
| 285 |
+
"y": { "dtype": "uint8", "shape": [], "tolerance": 0 },
|
| 286 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 287 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 288 |
+
}
|
| 289 |
+
},
|
| 290 |
+
{
|
| 291 |
+
"name": "empty_zero_dim",
|
| 292 |
+
"inputs": { "x": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } } },
|
| 293 |
+
"outputs": {
|
| 294 |
+
"y": { "dtype": "uint8", "shape": [0], "tolerance": 0 },
|
| 295 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.0001 },
|
| 296 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 297 |
+
}
|
| 298 |
+
},
|
| 299 |
+
{
|
| 300 |
+
"name": "ort_caseB_empty",
|
| 301 |
+
"inputs": { "x": { "dtype": "float32", "shape": [0], "data": { "kind": "values", "values": [] } } },
|
| 302 |
+
"outputs": {
|
| 303 |
+
"y": { "dtype": "uint8", "shape": [0], "data": { "kind": "values", "values": [] }, "tolerance": 0.001 },
|
| 304 |
+
"y_scale": {
|
| 305 |
+
"dtype": "float32",
|
| 306 |
+
"shape": [],
|
| 307 |
+
"data": { "kind": "values", "values": [1.0] },
|
| 308 |
+
"tolerance": 0.001
|
| 309 |
+
},
|
| 310 |
+
"y_zero_point": {
|
| 311 |
+
"dtype": "uint8",
|
| 312 |
+
"shape": [],
|
| 313 |
+
"data": { "kind": "values", "values": [0] },
|
| 314 |
+
"tolerance": 0.001
|
| 315 |
+
}
|
| 316 |
+
}
|
| 317 |
+
},
|
| 318 |
+
{
|
| 319 |
+
"name": "grid_stride_reduce_1m_mixed_sign",
|
| 320 |
+
"provenance": {
|
| 321 |
+
"notes": "numel = 1048576 >= 262144 selects grid_stride_reduce_vec4: the reduce dispatches a capped min(256, ceil(numel/1024))=256 workgroups, each grid-striding over the whole tensor and writing ONE partial, so the @workgroup_size(1) finalize folds only 256 partials (vs 4096 for the per-block reduce). Validates the grid-stride min/max is bit-identical to the serial reference (y_scale/y_zero_point exact) and that the capped 1D dispatch covers all elements."
|
| 322 |
+
},
|
| 323 |
+
"inputs": {
|
| 324 |
+
"x": {
|
| 325 |
+
"dtype": "float32",
|
| 326 |
+
"shape": [1024, 1024],
|
| 327 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.017, "scale": 3.0 }
|
| 328 |
+
}
|
| 329 |
+
},
|
| 330 |
+
"outputs": {
|
| 331 |
+
"y": { "dtype": "uint8", "shape": [1024, 1024], "tolerance": 0 },
|
| 332 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 333 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 334 |
+
}
|
| 335 |
+
},
|
| 336 |
+
{
|
| 337 |
+
"name": "dispatch_cliff_blocks_65536",
|
| 338 |
+
"provenance": {
|
| 339 |
+
"notes": "numel = 67108864 = 65536*1024 > 65535*1024 forces the folded block dispatch (x=65535, y=2) in the reduce + quantize passes. Validates blk = wg.x + wg.y*nwg.x AND the new `blk >= numBlocks` reduce-pass early-return that protects the partial_min/max[blk] scratch store from an over-dispatched OOB write. linspace puts the global min/max at the first/last block (the last block lives in the wg.y=1 folded region), so a wrong fold corrupts the scale. Before the 2D fold the plan-time dispatch-limit guard throws."
|
| 340 |
+
},
|
| 341 |
+
"inputs": {
|
| 342 |
+
"x": { "dtype": "float32", "shape": [67108864], "data": { "kind": "linspace", "start": -1.0, "end": 1.0 } }
|
| 343 |
+
},
|
| 344 |
+
"outputs": {
|
| 345 |
+
"y": { "dtype": "uint8", "shape": [67108864], "tolerance": 1 },
|
| 346 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 347 |
+
"y_zero_point": { "dtype": "uint8", "shape": [] }
|
| 348 |
+
}
|
| 349 |
+
},
|
| 350 |
+
{
|
| 351 |
+
"name": "grid_stride_reduce_clean_int_262145",
|
| 352 |
+
"inputs": {
|
| 353 |
+
"x": {
|
| 354 |
+
"dtype": "float32",
|
| 355 |
+
"shape": [262145],
|
| 356 |
+
"data": { "kind": "cycle", "values": [0.0, 32.0, 64.0, 96.0, 128.0, 160.0, 192.0, 224.0, 255.0] }
|
| 357 |
+
}
|
| 358 |
+
},
|
| 359 |
+
"outputs": {
|
| 360 |
+
"y": { "dtype": "uint8", "shape": [262145], "tolerance": 0 },
|
| 361 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0 },
|
| 362 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 363 |
+
}
|
| 364 |
+
},
|
| 365 |
+
{
|
| 366 |
+
"name": "non_vec4_large_finalize_fold_mixed_sign_corrected_division",
|
| 367 |
+
"provenance": {
|
| 368 |
+
"notes": "Regression for cross-backend f32 division: raw and fma-residual quotients disagreed with correctly-rounded ORT at half-integer boundaries on Metal and shifted the zero point on Intel D3D. The backend-independent integer significand fallback must keep this odd-sized grid-stride path bit-exact."
|
| 369 |
+
},
|
| 370 |
+
"inputs": {
|
| 371 |
+
"x": {
|
| 372 |
+
"dtype": "float32",
|
| 373 |
+
"shape": [4194305],
|
| 374 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.017, "scale": 3.0 }
|
| 375 |
+
}
|
| 376 |
+
},
|
| 377 |
+
"outputs": {
|
| 378 |
+
"y": { "dtype": "uint8", "shape": [4194305], "tolerance": 0 },
|
| 379 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 380 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 381 |
+
}
|
| 382 |
+
},
|
| 383 |
+
{
|
| 384 |
+
"name": "per_token_activation_2d_vec4",
|
| 385 |
+
"inputs": {
|
| 386 |
+
"x": {
|
| 387 |
+
"dtype": "float32",
|
| 388 |
+
"shape": [3, 4096],
|
| 389 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.011, "cosStep": 0.019, "scale": 2.5 }
|
| 390 |
+
}
|
| 391 |
+
},
|
| 392 |
+
"outputs": {
|
| 393 |
+
"y": { "dtype": "uint8", "shape": [3, 4096], "tolerance": 0 },
|
| 394 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 395 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 396 |
+
}
|
| 397 |
+
},
|
| 398 |
+
{
|
| 399 |
+
"name": "non_vec4_odd_hidden_bridge",
|
| 400 |
+
"inputs": {
|
| 401 |
+
"x": {
|
| 402 |
+
"dtype": "float32",
|
| 403 |
+
"shape": [2049],
|
| 404 |
+
"data": {
|
| 405 |
+
"kind": "cycle",
|
| 406 |
+
"values": [-1.0, -2.1, -1.3, 2.5, 3.34, 4.0, -0.25, 7.75, -0.001, 6.5, -2.75, 5.125, -3.5]
|
| 407 |
+
}
|
| 408 |
+
}
|
| 409 |
+
},
|
| 410 |
+
"outputs": {
|
| 411 |
+
"y": { "dtype": "uint8", "shape": [2049], "tolerance": 0 },
|
| 412 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 413 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 414 |
+
}
|
| 415 |
+
},
|
| 416 |
+
{
|
| 417 |
+
"name": "symmetric_half_step_tie_vec4_boundary",
|
| 418 |
+
"provenance": {
|
| 419 |
+
"notes": "The scale 2/255 puts the zero-point quotient just below 127.5 after correctly-rounded f32 division. Intel D3D exposed an over-correction that changed zero_point 127 to 128."
|
| 420 |
+
},
|
| 421 |
+
"inputs": {
|
| 422 |
+
"x": { "dtype": "float32", "shape": [4], "data": { "kind": "values", "values": [-1.0, -0.5, 0.5, 1.0] } }
|
| 423 |
+
},
|
| 424 |
+
"outputs": {
|
| 425 |
+
"y": { "dtype": "uint8", "shape": [4], "tolerance": 0 },
|
| 426 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 427 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 428 |
+
}
|
| 429 |
+
},
|
| 430 |
+
{
|
| 431 |
+
"name": "intel_d3d_compensated_zero_point_regression",
|
| 432 |
+
"provenance": {
|
| 433 |
+
"notes": "Explicit golden companion to symmetric_half_step_tie_vec4_boundary. It catches backend excess-precision or residual-division compensation shifting the correctly-rounded zero point from 127 to 128."
|
| 434 |
+
},
|
| 435 |
+
"inputs": {
|
| 436 |
+
"x": {
|
| 437 |
+
"dtype": "float32",
|
| 438 |
+
"shape": [8],
|
| 439 |
+
"data": { "kind": "values", "values": [-2.0, -1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0] }
|
| 440 |
+
}
|
| 441 |
+
},
|
| 442 |
+
"outputs": {
|
| 443 |
+
"y": {
|
| 444 |
+
"dtype": "uint8",
|
| 445 |
+
"shape": [8],
|
| 446 |
+
"data": { "kind": "values", "values": [0, 63, 95, 127, 159, 191, 223, 254] },
|
| 447 |
+
"tolerance": 0
|
| 448 |
+
},
|
| 449 |
+
"y_scale": {
|
| 450 |
+
"dtype": "float32",
|
| 451 |
+
"shape": [],
|
| 452 |
+
"data": { "kind": "values", "values": [0.01568627543747425] },
|
| 453 |
+
"tolerance": 0
|
| 454 |
+
},
|
| 455 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "data": { "kind": "values", "values": [127] }, "tolerance": 0 }
|
| 456 |
+
}
|
| 457 |
+
},
|
| 458 |
+
{
|
| 459 |
+
"name": "grid_stride_threshold_boundary_vec4_262144",
|
| 460 |
+
"inputs": {
|
| 461 |
+
"x": {
|
| 462 |
+
"dtype": "float32",
|
| 463 |
+
"shape": [262144],
|
| 464 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.013, "cosStep": 0.017, "scale": 3.0 }
|
| 465 |
+
}
|
| 466 |
+
},
|
| 467 |
+
"outputs": {
|
| 468 |
+
"y": { "dtype": "uint8", "shape": [262144], "tolerance": 0 },
|
| 469 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 470 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 471 |
+
}
|
| 472 |
+
},
|
| 473 |
+
{
|
| 474 |
+
"name": "multidim_non_vec4_parallel_reduce_2d",
|
| 475 |
+
"inputs": {
|
| 476 |
+
"x": {
|
| 477 |
+
"dtype": "float32",
|
| 478 |
+
"shape": [17, 120],
|
| 479 |
+
"data": { "kind": "fillFloat32", "sinStep": 0.031, "cosStep": 0.023, "scale": 2.0 }
|
| 480 |
+
}
|
| 481 |
+
},
|
| 482 |
+
"outputs": {
|
| 483 |
+
"y": { "dtype": "uint8", "shape": [17, 120], "tolerance": 0 },
|
| 484 |
+
"y_scale": { "dtype": "float32", "shape": [], "tolerance": 0.000001 },
|
| 485 |
+
"y_zero_point": { "dtype": "uint8", "shape": [], "tolerance": 0 }
|
| 486 |
+
}
|
| 487 |
+
}
|
| 488 |
+
]
|
| 489 |
+
}
|