From e11604070db3e334c2eb4a9e6f17a1c716fd1321 Mon Sep 17 00:00:00 2001 From: Ryan Monroe Date: Mon, 22 Jun 2026 11:08:44 -0700 Subject: [PATCH] Support activation-only PTQ (weight_qspec=None) Summary: The Ethos/arm PT2E quantization annotator (`quantization_annotator.py`, fbcode + xplat) assumed every weighted op carries a weight qspec and unconditionally annotated the weight input. Activation-only PTQ -- int16 activations with fp32 (un-quantized) weights -- sets `weight_qspec=None`, which tripped the annotator. This skips weight annotation when the weight qspec is None, enabling activation-only PTQ on the arm/Ethos backend. Required by the `cascade_detector_ceres_u55_a16_only` recipe (D108781360), which isolates the activation-quant contribution of the grip-force int8 collapse. Differential Revision: D108781361 --- .../arm/quantizer/quantization_annotator.py | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/backends/arm/quantizer/quantization_annotator.py b/backends/arm/quantizer/quantization_annotator.py index 3b713659e84..d72f9136a37 100644 --- a/backends/arm/quantizer/quantization_annotator.py +++ b/backends/arm/quantizer/quantization_annotator.py @@ -81,6 +81,27 @@ def __init__(self): self.quant_output: Optional[_QuantProperty] = None +def _conv_linear_quant_inputs( + input_act_qspec: QuantizationSpecBase | None, + weight_qspec: QuantizationSpecBase | None, + bias_qspec: QuantizationSpecBase | None, +) -> List[_QuantProperty]: + """Build conv/linear input quant properties, tolerating partial quant. + + Activation-only PTQ recipes leave ``weight_qspec`` (and the bias derived + from it) None; omit those QuantProperties so weight/bias stay fp32 instead of + asserting a non-None weight spec. Weight-only and full configs keep the + weight (and bias) quantized exactly as before. + """ + quant_inputs: List[_QuantProperty] = [_QuantProperty(0, input_act_qspec)] + if weight_qspec is not None: + quant_inputs.append(_QuantProperty(1, weight_qspec, mark_annotated=True)) + quant_inputs.append( + _QuantProperty(2, bias_qspec, optional=True, mark_annotated=True) + ) + return quant_inputs + + class _QParams(NamedTuple): scale: float zero_point: int @@ -716,12 +737,9 @@ def any_or_hardtanh_min_zero(n: Node): filter_fn=any_or_hardtanh_min_zero, ): if node.target in _conv_ops: - conv_weight_qspec = ensure_type(QuantizationSpec, weight_qspec) # For MyPy - quant_properties.quant_inputs = [ - _QuantProperty(0, input_act_qspec), - _QuantProperty(1, conv_weight_qspec, mark_annotated=True), - _QuantProperty(2, bias_qspec, optional=True, mark_annotated=True), - ] + quant_properties.quant_inputs = _conv_linear_quant_inputs( + input_act_qspec, weight_qspec, bias_qspec + ) elif node.target in ( torch.ops.aten.relu.default, torch.ops.aten.relu_.default, @@ -738,12 +756,9 @@ def any_or_hardtanh_min_zero(n: Node): ], ): if node.target in _conv_ops: - conv_weight_qspec = ensure_type(QuantizationSpec, weight_qspec) # For MyPy - quant_properties.quant_inputs = [ - _QuantProperty(0, input_act_qspec), - _QuantProperty(1, conv_weight_qspec, mark_annotated=True), - _QuantProperty(2, bias_qspec, optional=True, mark_annotated=True), - ] + quant_properties.quant_inputs = _conv_linear_quant_inputs( + input_act_qspec, weight_qspec, bias_qspec + ) elif node.target in {torch.ops.aten.batch_norm.default}: quant_properties.quant_output = _QuantProperty(0, output_act_qspec) elif not is_symmetric and _match_pattern( @@ -766,28 +781,18 @@ def any_or_hardtanh_min_zero(n: Node): *_conv_ops, torch.ops.aten.linear.default, ): - conv_or_linear_weight_qspec = ensure_type( - QuantizationSpec, weight_qspec - ) # For MyPy - quant_properties.quant_inputs = [ - _QuantProperty(0, input_act_qspec), - _QuantProperty(1, conv_or_linear_weight_qspec, mark_annotated=True), - _QuantProperty(2, bias_qspec, optional=True, mark_annotated=True), - ] + quant_properties.quant_inputs = _conv_linear_quant_inputs( + input_act_qspec, weight_qspec, bias_qspec + ) else: quant_properties.quant_output = _QuantProperty(0, output_act_qspec) elif node.target in ( *_conv_ops, torch.ops.aten.linear.default, ): - conv_or_linear_weight_qspec = ensure_type( - QuantizationSpec, weight_qspec - ) # For MyPy - quant_properties.quant_inputs = [ - _QuantProperty(0, input_act_qspec), - _QuantProperty(1, conv_or_linear_weight_qspec, mark_annotated=True), - _QuantProperty(2, bias_qspec, optional=True, mark_annotated=True), - ] + quant_properties.quant_inputs = _conv_linear_quant_inputs( + input_act_qspec, weight_qspec, bias_qspec + ) quant_properties.quant_output = _QuantProperty(0, output_act_qspec) elif node.target in ( torch.ops.aten.add.Tensor,