Skip to content
15 changes: 9 additions & 6 deletions backends/qualcomm/quantizer/annotators/htp_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,15 +842,18 @@ def annotate(node: Node, quantization_config: QuantizationConfig) -> None:
input_qspec_map = {}
input_qspec = quantization_config.input_activation
output_qspec = None
if input_qspec is not None:
if input_qspec is not None and _is_float_tensor(value):
input_qspec_map[value] = input_qspec
output_qspec = SharedQuantizationSpec((value, node))

node.meta[Q_ANNOTATION_KEY] = QuantizationAnnotation(
input_qspec_map=input_qspec_map,
output_qspec=output_qspec,
_annotated=True,
)
# A non-float value leaves nothing to quantize; leave the node unannotated
# rather than marking it annotated with an empty spec (its output stays non-float).
if len(input_qspec_map) > 0 or output_qspec is not None:
node.meta[Q_ANNOTATION_KEY] = QuantizationAnnotation(
input_qspec_map=input_qspec_map,
output_qspec=output_qspec,
_annotated=True,
)


@register_annotator(
Expand Down
21 changes: 14 additions & 7 deletions backends/qualcomm/quantizer/annotators/lpai_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,13 +466,20 @@ def annotate(node: Node, quantization_config: QuantizationConfig) -> None:
value = node.args[2]

input_qspec_map = {}
input_qspec_map[value] = quantization_config.input_activation

node.meta[Q_ANNOTATION_KEY] = QuantizationAnnotation(
input_qspec_map=input_qspec_map,
output_qspec=SharedQuantizationSpec((value, node)),
_annotated=True,
)
output_qspec = None
input_qspec = quantization_config.input_activation
if input_qspec is not None and _is_float_tensor(value):
input_qspec_map[value] = input_qspec
output_qspec = SharedQuantizationSpec((value, node))

# A non-float value leaves nothing to quantize; leave the node unannotated
# rather than marking it annotated with an empty spec (its output stays non-float).
if len(input_qspec_map) > 0 or output_qspec is not None:
node.meta[Q_ANNOTATION_KEY] = QuantizationAnnotation(
input_qspec_map=input_qspec_map,
output_qspec=output_qspec,
_annotated=True,
)


@register_annotator(
Expand Down
48 changes: 48 additions & 0 deletions backends/qualcomm/tests/test_passes.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,54 @@ def test_decompose_hardsigmoid_backend_aware(self):
f"hardsigmoid {'should' if should_decompose else 'should NOT'} be decomposed for {backend.name}",
)

def test_index_put_int64_value_not_quantized(self):
"""QNN's IndexPut annotator must skip a non-float (int64) value arg.

Regression for MoE (Mixtral) routing, where index_put's value is an int64
arange: annotating it makes quantize_per_tensor assert a float input, so
to_executorch() fails. Only float tensors may be annotated.
Exercised on both the HTP and LPAI annotators, which share the guard.
"""

class IndexPutInt64Value(torch.nn.Module):
def forward(self, x):
buf = torch.zeros(4, dtype=torch.long)
idx = torch.arange(
4, dtype=torch.int64
) # int64 value written by index_put
buf = buf.index_put((torch.tensor([0, 1, 2, 3]),), idx)
return x + buf.to(torch.float32)

module = IndexPutInt64Value().eval()
sample_input = (torch.randn(4),)

def run_backend(backend):
quantizer = QnnQuantizer(backend=backend)
quantizer.set_default_quant_config(quant_dtype=QuantDtype.use_8a8w)
gm = (
torch.export.export(module, sample_input)
.run_decompositions({})
.module()
)
prepared = prepare_pt2e(gm, quantizer)
prepared(*sample_input)
converted = convert_pt2e(prepared)
# Re-export runs the quantize_per_tensor meta kernel; before the dtype
# guard this raised "Expecting input to have dtype torch.float32" on the
# int64 value.
torch.export.export(converted, sample_input)

# HTP is the core QNN quantizer backend; a failure here is a real regression,
# not something to swallow. LPAI additionally needs quantized_aot_lib, so only
# that missing optional dependency is allowed to skip the LPAI leg.
run_backend(QnnExecuTorchBackendType.kHtpBackend)
try:
run_backend(QnnExecuTorchBackendType.kLpaiBackend)
except Exception as e:
if "quantized_aot_lib" in str(e):
self.skipTest(f"LPAI quantizer unavailable: {e}")
raise


if __name__ == "__main__":
unittest.main()
Loading