Summary
quantizeLinear and dequantizeLinear require a zeroPoint operand. Because the argument is mandatory, WebNN has to materialize a zero-point tensor even for a symmetrically quantized model(zero point is omitted), which introduces unnecessary memory consumption. And this makes symmetric quantization inexpressible in WebNN.
Additionally, some backends select more efficient kernels for symmetric quantization by observing that no zero point is attached. Since WebNN always requires one, those kernels are unreachable.
Current behavior
Operation definition:
MLOperand dequantizeLinear(MLOperand input, MLOperand scale, MLOperand zeroPoint,
optional MLOperatorOptions options = {});
MLOperand quantizeLinear(MLOperand input, MLOperand scale, MLOperand zeroPoint,
optional MLOperatorOptions options = {});
zeroPoint's shape is redundant in both operators: method steps 7-8 require it to equal scale's. Its data type is redundant in dequantizeLinear, but quantizeLiear's output data type comes from the data type of zeroPoint (method steps 11).
Why this matters
Measured on Qwen3-4B (4-bit weight-compressed, symmetric) running through WebNN on the ONNX Runtime backend, through WebNN the EP synthesizes 252 zero-point constants that exist in no source file, costing:
- ~13.5 MiB of memory. Each is separately allocated, passed to
MLGraphBuilder.constant(), and copied across the renderer → service boundary.
- 756 nodes that exist only to subtract zero.
- The symmetric kernel becomes unreachable. With a zero point attached, the kernel will takes the asymmetric path. Removing the zero point input will save ~3% kernel time.
Backends status
- ONNX —
DequantizeLinear and QuantizeLinear both take 2–3 inputs with an optional zero point. QuantizeLinear carries an output_dtype attribute.
- CoreML —
dequantize and quantize declare zero_point as optional=True. quantize has a separate, required output_dtype.
- TFLite —
QuantizationParameters always carries a zero-point vector; the format has no "absent" state, only all-zeros. Neutral: it would synthesize zeros, is not blocked, gains nothing directly.
Proposal
Make zeroPoint optional on both operators, and give quantizeLinear an explicit output data type.
dictionary MLQuantizeDequantizeLinearOptions : MLOperatorOptions {
MLOperand zeroPoint; // if omitted, the zero point is 0
};
dictionary MLQuantizeLinearOptions : MLQuantizeDequantizeLinearOptions {
MLOperandDataType outputDataType; // quantizeLinear only
};
partial interface MLGraphBuilder {
MLOperand dequantizeLinear(MLOperand input, MLOperand scale,
optional MLQuantizeDequantizeLinearOptions options = {});
MLOperand quantizeLinear(MLOperand input, MLOperand scale,
optional MLQuantizeLinearOptions options = {});
};
Output type for quantizeLinear: zeroPoint's data type if present; otherwise outputDataType; if both, they must match, else TypeError; if neither, TypeError.
dequantizeLinear does not need an outputDataType; scale remains required.
Summary
quantizeLinearanddequantizeLinearrequire azeroPointoperand. Because the argument is mandatory, WebNN has to materialize a zero-point tensor even for a symmetrically quantized model(zero point is omitted), which introduces unnecessary memory consumption. And this makes symmetric quantization inexpressible in WebNN.Additionally, some backends select more efficient kernels for symmetric quantization by observing that no zero point is attached. Since WebNN always requires one, those kernels are unreachable.
Current behavior
Operation definition:
zeroPoint's shape is redundant in both operators: method steps 7-8 require it to equalscale's. Its data type is redundant indequantizeLinear, butquantizeLiear's output data type comes from the data type ofzeroPoint(method steps 11).Why this matters
Measured on Qwen3-4B (4-bit weight-compressed, symmetric) running through WebNN on the ONNX Runtime backend, through WebNN the EP synthesizes 252 zero-point constants that exist in no source file, costing:
MLGraphBuilder.constant(), and copied across the renderer → service boundary.Backends status
DequantizeLinearandQuantizeLinearboth take 2–3 inputs with an optional zero point.QuantizeLinearcarries anoutput_dtypeattribute.dequantizeandquantizedeclarezero_pointasoptional=True.quantizehas a separate, requiredoutput_dtype.QuantizationParametersalways carries a zero-point vector; the format has no "absent" state, only all-zeros. Neutral: it would synthesize zeros, is not blocked, gains nothing directly.Proposal
Make
zeroPointoptional on both operators, and givequantizeLinearan explicit output data type.dictionary MLQuantizeDequantizeLinearOptions : MLOperatorOptions { MLOperand zeroPoint; // if omitted, the zero point is 0 }; dictionary MLQuantizeLinearOptions : MLQuantizeDequantizeLinearOptions { MLOperandDataType outputDataType; // quantizeLinear only }; partial interface MLGraphBuilder { MLOperand dequantizeLinear(MLOperand input, MLOperand scale, optional MLQuantizeDequantizeLinearOptions options = {}); MLOperand quantizeLinear(MLOperand input, MLOperand scale, optional MLQuantizeLinearOptions options = {}); };Output type for
quantizeLinear:zeroPoint's data type if present; otherwiseoutputDataType; if both, they must match, elseTypeError; if neither,TypeError.dequantizeLineardoes not need anoutputDataType;scaleremains required.