| { |
| "domain": "ai.onnx", |
| "name": "DynamicQuantizeLinear", |
| "sinceVersion": 11, |
| "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]`.", |
| "inputs": [{ "role": "x", "dtype": "T", "description": "Float32 input tensor to quantize." }], |
| "outputs": [ |
| { |
| "role": "y", |
| "dtype": "TQ", |
| "rank": "ranks.x", |
| "description": "Quantized output tensor; same shape as the input.", |
| "shape": "shapes.x" |
| }, |
| { |
| "role": "y_scale", |
| "dtype": "T", |
| "rank": 0, |
| "description": "Per-tensor scale factor derived from the input min/max range; scalar.", |
| "shape": [] |
| }, |
| { |
| "role": "y_zero_point", |
| "dtype": "TQ", |
| "rank": 0, |
| "description": "Per-tensor zero point for the quantization; scalar.", |
| "shape": [] |
| } |
| ], |
| "typeConstraints": { "T": ["float32"], "TQ": ["uint8"] }, |
| "args": { |
| "x": { "kind": "tensor", "semantic": "x", "role": "input" }, |
| "y": { "kind": "tensor", "semantic": "y", "role": "output" }, |
| "y_scale": { "kind": "tensor", "semantic": "y_scale", "role": "output" }, |
| "y_zero_point": { "kind": "tensor", "semantic": "y_zero_point", "role": "output" } |
| }, |
| "tunables": { |
| "WORKGROUP_SIZE": 256, |
| "ELEMENTS_PER_THREAD": 4, |
| "GRID_STRIDE_MIN_ELEMENTS": 262144, |
| "MAX_GRID_PARTIALS": 256, |
| "SERIAL_MAX_ELEMENTS": 8192 |
| }, |
| "tunableDescriptions": { |
| "GRID_STRIDE_MIN_ELEMENTS": "Sets the minimum input element count admitted to the bounded grid-stride reduction.", |
| "MAX_GRID_PARTIALS": "Caps the number of min/max partials emitted by the grid-stride reduction, bounding scratch and finalization work.", |
| "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." |
| }, |
| "derive": { |
| "deviceWorkgroupCap": "min(device.limits.maxComputeInvocationsPerWorkgroup, device.limits.maxComputeWorkgroupSizeX)", |
| "storageBufferLimit": "min(device.limits.maxStorageBufferBindingSize, device.limits.maxBufferSize)", |
| "inputCount": "numel(shapes.x)", |
| "elementsPerBlock": "tunables.WORKGROUP_SIZE * tunables.ELEMENTS_PER_THREAD", |
| "fullPartials": "ceilDiv(inputCount, elementsPerBlock)", |
| "gridPartials": "min(tunables.MAX_GRID_PARTIALS, fullPartials)", |
| "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", |
| "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", |
| "parallelDeviceOk": "tunables.WORKGROUP_SIZE <= deviceWorkgroupCap and 8 * tunables.WORKGROUP_SIZE <= device.limits.maxComputeWorkgroupStorageSize", |
| "fullScratchFits": "4 * fullPartials <= storageBufferLimit", |
| "gridScratchFits": "4 * gridPartials <= storageBufferLimit", |
| "fullDispatchFits": "ceilDiv(fullPartials, device.limits.maxComputeWorkgroupsPerDimension) <= device.limits.maxComputeWorkgroupsPerDimension", |
| "gridDispatchFits": "gridPartials <= device.limits.maxComputeWorkgroupsPerDimension", |
| "parallelFullFits": "parallelDeviceOk and fullScratchFits and fullDispatchFits", |
| "parallelGridFits": "parallelDeviceOk and gridScratchFits and gridDispatchFits and fullDispatchFits", |
| "serialFallbackNeeded": "inputCount <= tunables.SERIAL_MAX_ELEMENTS or not parallelFullFits" |
| }, |
| "bindingSets": { |
| "reduce": [ |
| { |
| "name": "x", |
| "arg": "x", |
| "semantic": "x", |
| "buffer": { "type": "read-only-storage" }, |
| "elementType": "$inputElement" |
| }, |
| { "name": "partial_min", "semantic": "partial_min", "buffer": { "type": "storage" }, "elementType": "f32" }, |
| { "name": "partial_max", "semantic": "partial_max", "buffer": { "type": "storage" }, "elementType": "f32" }, |
| { |
| "name": "params", |
| "semantic": "kernel.params", |
| "buffer": { "type": "uniform" }, |
| "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.x)" }] } |
| } |
| ], |
| "finalizeFull": [ |
| { |
| "name": "partial_min", |
| "semantic": "partial_min", |
| "buffer": { "type": "read-only-storage" }, |
| "elementType": "f32" |
| }, |
| { |
| "name": "partial_max", |
| "semantic": "partial_max", |
| "buffer": { "type": "read-only-storage" }, |
| "elementType": "f32" |
| }, |
| { |
| "name": "y_scale", |
| "arg": "y_scale", |
| "semantic": "y_scale", |
| "buffer": { "type": "storage" }, |
| "elementType": "f32", |
| "length": 1 |
| }, |
| { |
| "name": "y_zero_point", |
| "arg": "y_zero_point", |
| "semantic": "y_zero_point", |
| "buffer": { "type": "storage" }, |
| "elementType": "u32", |
| "length": 1 |
| }, |
| { |
| "name": "params", |
| "semantic": "kernel.params", |
| "buffer": { "type": "uniform" }, |
| "struct": { "name": "Params", "fields": [{ "name": "numPartials", "type": "u32", "value": "fullPartials" }] } |
| } |
| ], |
| "finalizeGrid": [ |
| { |
| "name": "partial_min", |
| "semantic": "partial_min", |
| "buffer": { "type": "read-only-storage" }, |
| "elementType": "f32" |
| }, |
| { |
| "name": "partial_max", |
| "semantic": "partial_max", |
| "buffer": { "type": "read-only-storage" }, |
| "elementType": "f32" |
| }, |
| { |
| "name": "y_scale", |
| "arg": "y_scale", |
| "semantic": "y_scale", |
| "buffer": { "type": "storage" }, |
| "elementType": "f32", |
| "length": 1 |
| }, |
| { |
| "name": "y_zero_point", |
| "arg": "y_zero_point", |
| "semantic": "y_zero_point", |
| "buffer": { "type": "storage" }, |
| "elementType": "u32", |
| "length": 1 |
| }, |
| { |
| "name": "params", |
| "semantic": "kernel.params", |
| "buffer": { "type": "uniform" }, |
| "struct": { "name": "Params", "fields": [{ "name": "numPartials", "type": "u32", "value": "gridPartials" }] } |
| } |
| ], |
| "quantize": [ |
| { |
| "name": "x", |
| "arg": "x", |
| "semantic": "x", |
| "buffer": { "type": "read-only-storage" }, |
| "elementType": "$inputElement" |
| }, |
| { |
| "name": "y_scale", |
| "arg": "y_scale", |
| "semantic": "y_scale", |
| "buffer": { "type": "read-only-storage" }, |
| "elementType": "f32", |
| "length": 1 |
| }, |
| { |
| "name": "y_zero_point", |
| "arg": "y_zero_point", |
| "semantic": "y_zero_point", |
| "buffer": { "type": "read-only-storage" }, |
| "elementType": "u32", |
| "length": 1 |
| }, |
| { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "u32" }, |
| { |
| "name": "params", |
| "semantic": "kernel.params", |
| "buffer": { "type": "uniform" }, |
| "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.x)" }] } |
| } |
| ], |
| "serial": [ |
| { "name": "x", "arg": "x", "semantic": "x", "buffer": { "type": "read-only-storage" }, "elementType": "f32" }, |
| { "name": "y", "arg": "y", "semantic": "y", "buffer": { "type": "storage" }, "elementType": "u32" }, |
| { |
| "name": "y_scale", |
| "arg": "y_scale", |
| "semantic": "y_scale", |
| "buffer": { "type": "storage" }, |
| "elementType": "f32", |
| "length": 1 |
| }, |
| { |
| "name": "y_zero_point", |
| "arg": "y_zero_point", |
| "semantic": "y_zero_point", |
| "buffer": { "type": "storage" }, |
| "elementType": "u32", |
| "length": 1 |
| }, |
| { |
| "name": "params", |
| "semantic": "kernel.params", |
| "buffer": { "type": "uniform" }, |
| "struct": { "name": "Params", "fields": [{ "name": "count", "type": "u32", "value": "numel(shapes.x)" }] } |
| } |
| ] |
| }, |
| "variants": [ |
| { |
| "id": "single_invocation", |
| "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.", |
| "when": ["serialContract", "serialFallbackNeeded"], |
| "passes": [ |
| { |
| "id": "main", |
| "name": "DynamicQuantizeLinear", |
| "source": { "shader": "dynamic-quantize-linear.wgsl.jinja", "inputs": { "fromPartials": false } }, |
| "bindings": "serial", |
| "dispatch": { "x": 1 } |
| } |
| ] |
| }, |
| { |
| "id": "parallel_subgroup_reduce_vec4", |
| "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.", |
| "priority": 11, |
| "when": ["baseContract", "inputCount > 0", "inputCount % 4 == 0", "parallelFullFits"], |
| "constants": { |
| "workgroupSize": "tunables.WORKGROUP_SIZE", |
| "elemsPerThread": "tunables.ELEMENTS_PER_THREAD", |
| "vec4": true, |
| "inputElement": "\"vec4<f32>\"", |
| "useSubgroups": "device.features.has(\"subgroups\")" |
| }, |
| "intermediates": [ |
| { "id": "partial_min", "dtype": "float32", "shape": "[fullPartials]" }, |
| { "id": "partial_max", "dtype": "float32", "shape": "[fullPartials]" } |
| ], |
| "passes": [ |
| { |
| "id": "reduce", |
| "name": "DynamicQuantizeLinear.ReduceMinMax", |
| "shader": "dynamic-quantize-linear-reduce.wgsl.jinja", |
| "bindings": "reduce", |
| "dispatch": { "workgroups": "fullPartials" } |
| }, |
| { |
| "id": "finalize", |
| "name": "DynamicQuantizeLinear.Finalize", |
| "source": { "shader": "dynamic-quantize-linear.wgsl.jinja", "inputs": { "fromPartials": true } }, |
| "bindings": "finalizeFull", |
| "dispatch": { "x": 1 } |
| }, |
| { |
| "id": "quantize", |
| "name": "DynamicQuantizeLinear.Quantize", |
| "shader": "dynamic-quantize-linear-quantize.wgsl.jinja", |
| "bindings": "quantize", |
| "dispatch": { |
| "threads": "ceilDiv(inputCount, tunables.ELEMENTS_PER_THREAD)", |
| "workgroupSize": "tunables.WORKGROUP_SIZE" |
| } |
| } |
| ] |
| }, |
| { |
| "id": "parallel_subgroup_reduce", |
| "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.", |
| "priority": 10, |
| "when": ["baseContract", "inputCount > 0", "true", "parallelFullFits"], |
| "constants": { |
| "workgroupSize": "tunables.WORKGROUP_SIZE", |
| "elemsPerThread": "tunables.ELEMENTS_PER_THREAD", |
| "vec4": false, |
| "inputElement": "\"f32\"", |
| "useSubgroups": "device.features.has(\"subgroups\")" |
| }, |
| "intermediates": [ |
| { "id": "partial_min", "dtype": "float32", "shape": "[fullPartials]" }, |
| { "id": "partial_max", "dtype": "float32", "shape": "[fullPartials]" } |
| ], |
| "passes": [ |
| { |
| "id": "reduce", |
| "name": "DynamicQuantizeLinear.ReduceMinMax", |
| "shader": "dynamic-quantize-linear-reduce.wgsl.jinja", |
| "bindings": "reduce", |
| "dispatch": { "workgroups": "fullPartials" } |
| }, |
| { |
| "id": "finalize", |
| "name": "DynamicQuantizeLinear.Finalize", |
| "source": { "shader": "dynamic-quantize-linear.wgsl.jinja", "inputs": { "fromPartials": true } }, |
| "bindings": "finalizeFull", |
| "dispatch": { "x": 1 } |
| }, |
| { |
| "id": "quantize", |
| "name": "DynamicQuantizeLinear.Quantize", |
| "shader": "dynamic-quantize-linear-quantize.wgsl.jinja", |
| "bindings": "quantize", |
| "dispatch": { |
| "threads": "ceilDiv(inputCount, tunables.ELEMENTS_PER_THREAD)", |
| "workgroupSize": "tunables.WORKGROUP_SIZE" |
| } |
| } |
| ] |
| }, |
| { |
| "id": "grid_stride_reduce_vec4", |
| "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.", |
| "priority": 12, |
| "when": ["baseContract", "inputCount > 0", "inputCount % 4 == 0", "inputCount >= tunables.GRID_STRIDE_MIN_ELEMENTS", "parallelGridFits"], |
| "constants": { |
| "workgroupSize": "tunables.WORKGROUP_SIZE", |
| "elemsPerThread": "tunables.ELEMENTS_PER_THREAD", |
| "vec4": true, |
| "inputElement": "\"vec4<f32>\"", |
| "useSubgroups": "device.features.has(\"subgroups\")" |
| }, |
| "intermediates": [ |
| { "id": "partial_min", "dtype": "float32", "shape": "[gridPartials]" }, |
| { "id": "partial_max", "dtype": "float32", "shape": "[gridPartials]" } |
| ], |
| "passes": [ |
| { |
| "id": "reduce", |
| "name": "DynamicQuantizeLinear.ReduceMinMax", |
| "source": { "shader": "dynamic-quantize-linear-reduce.wgsl.jinja", "inputs": { "gridStride": true } }, |
| "bindings": "reduce", |
| "dispatch": { "x": "gridPartials" } |
| }, |
| { |
| "id": "finalize", |
| "name": "DynamicQuantizeLinear.Finalize", |
| "source": { "shader": "dynamic-quantize-linear.wgsl.jinja", "inputs": { "fromPartials": true } }, |
| "bindings": "finalizeGrid", |
| "dispatch": { "x": 1 } |
| }, |
| { |
| "id": "quantize", |
| "name": "DynamicQuantizeLinear.Quantize", |
| "shader": "dynamic-quantize-linear-quantize.wgsl.jinja", |
| "bindings": "quantize", |
| "dispatch": { |
| "threads": "ceilDiv(inputCount, tunables.ELEMENTS_PER_THREAD)", |
| "workgroupSize": "tunables.WORKGROUP_SIZE" |
| } |
| } |
| ] |
| }, |
| { |
| "id": "grid_stride_reduce", |
| "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.", |
| "priority": 12, |
| "when": ["baseContract", "inputCount > 0", "inputCount % 4 != 0", "inputCount >= tunables.GRID_STRIDE_MIN_ELEMENTS", "parallelGridFits"], |
| "constants": { |
| "workgroupSize": "tunables.WORKGROUP_SIZE", |
| "elemsPerThread": "tunables.ELEMENTS_PER_THREAD", |
| "vec4": false, |
| "inputElement": "\"f32\"", |
| "useSubgroups": "device.features.has(\"subgroups\")" |
| }, |
| "intermediates": [ |
| { "id": "partial_min", "dtype": "float32", "shape": "[gridPartials]" }, |
| { "id": "partial_max", "dtype": "float32", "shape": "[gridPartials]" } |
| ], |
| "passes": [ |
| { |
| "id": "reduce", |
| "name": "DynamicQuantizeLinear.ReduceMinMax", |
| "source": { "shader": "dynamic-quantize-linear-reduce.wgsl.jinja", "inputs": { "gridStride": true } }, |
| "bindings": "reduce", |
| "dispatch": { "x": "gridPartials" } |
| }, |
| { |
| "id": "finalize", |
| "name": "DynamicQuantizeLinear.Finalize", |
| "source": { "shader": "dynamic-quantize-linear.wgsl.jinja", "inputs": { "fromPartials": true } }, |
| "bindings": "finalizeGrid", |
| "dispatch": { "x": 1 } |
| }, |
| { |
| "id": "quantize", |
| "name": "DynamicQuantizeLinear.Quantize", |
| "shader": "dynamic-quantize-linear-quantize.wgsl.jinja", |
| "bindings": "quantize", |
| "dispatch": { |
| "threads": "ceilDiv(inputCount, tunables.ELEMENTS_PER_THREAD)", |
| "workgroupSize": "tunables.WORKGROUP_SIZE" |
| } |
| } |
| ] |
| } |
| ] |
| } |
|
|