aten.bmm, aten.mul, aten._softmax doesn't get quantized/replaced with Cortex-M kernels in Transformer model
I maintainer di solito rispondono entro 1 giorno
@amacharla15 ci sta già lavorando.
Dal 13/9/2026.
Valutazione
Questa issue non è ancora stata valutata.
Descrizione
🐛 Describe the bug
Description:
When quantizing a transformer model with CortexMQuantizer and lowering to the Cortex-M backend, aten.bmm/aten.mul/aten._softmax are not quantized(These are mentioned in CMSIS-NN Supported Operators list).
import argparse
import logging
import torch
import torch.nn as nn
from torch.export import export,default_decompositions
from executorch.exir import EdgeCompileConfig, ExecutorchBackendConfig, to_edge
from executorch.backends.cortex_m.quantizer.quantizer import CortexMQuantizer
from executorch.backends.cortex_m.passes.cortex_m_pass_manager import CortexMPassManager
# Post-training static INT8 quantization (PT2E flow).
from torchao.quantization.pt2e.quantize_pt2e import prepare_pt2e, convert_pt2e
logging.basicConfig(level=logging.INFO, format="%(message)s")
logger = logging.getLogger("simple_transformer")
_SDPA_DECOMP_TABLE = {
op: decomp
for op, decomp in default_decompositions().items()
if "scaled_dot_product" or "_safe_softmax" in str(op) in str(op)
}
def make_calibration_inputs(example_inputs, num_batches: int = 8):
"""Yield representative inputs for static PTQ calibration.
TODO: Replace the synthetic tensors with REAL representative inference
inputs. Static quantization freezes activation scales from whatever is
observed here, so synthetic data yields meaningless ranges and poor
accuracy.
"""
for _ in range(num_batches):
yield tuple(torch.randn_like(t) for t in example_inputs)
def quantize_static_int8(model, example_inputs, calib_batches: int = 8):
"""Apply post-training static INT8 quantization via the PT2E flow."""
model.eval()
# prepared_graph = export(model, example_inputs).run_decompositions(_SDPA_DECOMP_TABLE)
prepared_graph = export(model, example_inputs).module()
from executorch.backends.arm._passes import ConstantFoldingPass
prepared_graph = ConstantFoldingPass().call(prepared_graph).graph_module # Pre-computes and removes the call_function op sitting between the linear nodes and their weights
quantizer = CortexMQuantizer()
prepared = prepare_pt2e(prepared_graph, quantizer)
with torch.no_grad():
for sample in make_calibration_inputs(example_inputs, calib_batches):
prepared(*sample)
return convert_pt2e(prepared)
def main(args):
S, B, D = args.seq_len, 1, args.d_model
# Minimal one-layer encoder-decoder transformer.
model = nn.Transformer(
d_model=D,
nhead=args.nhead,
num_encoder_layers=args.num_layers,
num_decoder_layers=args.num_layers,
dim_feedforward=args.dim_feedforward,
dropout=0.0,
batch_first=False,
)
model.eval()
example_input = (torch.randn(S, B, D), torch.randn(S, B, D))
print("Applying post-training static INT8 quantization")
quantized_model = quantize_static_int8(model, example_input, args.calib_batches)
quantized_exported_program = export(quantized_model, example_input)
config = EdgeCompileConfig(
preserve_ops=[torch.ops.aten.linear.default],
_check_ir_validity=False,
)
edge_program_manager = to_edge(quantized_exported_program, compile_config=config)
pass_manager = CortexMPassManager(edge_program_manager.exported_program())
edge_program_manager._edge_programs["forward"] = pass_manager.transform()
et_program = edge_program_manager.to_executorch(
config=ExecutorchBackendConfig(extract_delegate_segments=False)
)
for op in et_program.executorch_program.execution_plan[0].operators:
print(op.name)
pte_path = "simple_transformer_int8_CortexM.pte"
with open(pte_path, "wb") as f:
f.write(et_program.buffer)
print(f"Wrote {pte_path}")
print(model)
src = torch.rand((S, B, D))
tgt = torch.rand((S, B, D))
out = model(src, tgt)
print("input", src.size())
print("output", out.size())
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Export a minimal quantized encoder-decoder transformer to .pte for the Cortex-M backend"
)
parser.add_argument("--d_model", type=int, help="d_model", default=32)
parser.add_argument("--nhead", type=int, help="nhead", default=2)
parser.add_argument("--num_layers", type=int, help="number of encoder and decoder layers", default=1)
parser.add_argument("--dim_feedforward", type=int, help="dim_feedforward", default=64)
parser.add_argument("--seq_len", type=int, help="sequence length", default=8)
parser.add_argument("--calib_batches", type=int, help="number of calibration batches for static PTQ", default=8)
args = parser.parse_args()
main(args)
Additional context:
Running decomposition before quantizing the model does quantize aten::_softmax and aten::bmm, aten::mul is the one that stays unquantized. (Separately, I also ran into the decomposed .pte not opening in Netron , unrelated to this issue, but flagging in case it's a known/expected
limitation for graphs with this many ops.)
Versions
Versions
PyTorch version: 2.13.0+cpu
OS: Ubuntu 24.04.4 LTS (x86_64)
Python version: 3.11.15 (main, Mar 11 2026, 17:20:07) [GCC 14.3.0] (64-bit runtime)
CPU:
Architecture: x86_64
Versions of relevant libraries:
[pip3] executorch==1.4.0+3dd7ccd
[pip3] flake8==6.1.0
[pip3] flake8-breakpoint==1.1.0
[pip3] flake8-bugbear==24.4.26
[pip3] flake8-comprehensions==3.14.0
[pip3] flake8-plugin-utils==1.3.3
[pip3] flake8-pyi==23.5.0
[pip3] mypy==1.14.1
[pip3] mypy_extensions==1.1.0
[pip3] numpy==2.4.4
[pip3] pytorch_tokenizers==1.4.1
[pip3] torch==2.13.0+cpu
[pip3] torchao==0.18.0
[pip3] torchaudio==2.11.0+cpu
[pip3] torchdata==0.11.0
[pip3] torchsr==1.0.4
[pip3] torchtune==0.0.0
[pip3] torchvision==0.28.0
[pip3] triton==3.7.1
[conda] executorch 1.4.0+3dd7ccd pypi_0 pypi
[conda] numpy 2.4.4 pypi_0 pypi
[conda] pytorch-tokenizers 1.4.1 pypi_0 pypi
[conda] torch 2.13.0+cpu pypi_0 pypi
[conda] torchao 0.18.0 pypi_0 pypi
[conda] torchaudio 2.11.0+cpu pypi_0 pypi
[conda] torchdata 0.11.0 pypi_0 pypi
[conda] torchfix 0.6.0 pypi_0 pypi
[conda] torchsr 1.0.4 pypi_0 pypi
[conda] torchtune 0.0.0 pypi_0 pypi
[conda] torchvision 0.28.0 pypi_0 pypi
[conda] triton 3.7.1 pypi_0 pypi
cc @kimishpatel @jerryzh168 @metascroy @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani
- Lingua principale
- Python
- Stelle
- 5.1k
- Fork
- 1.2k
- Merge medio
- 1g 23h
- PR unite (30g)
- 713
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di pytorch/executorch
-
Portable remainder kernel: wrong sign for integers and float64 rounded to floatForse già presa @JakeStevens l’ha presa 4 giorni fa. Apertabug module: kernels triaged
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
pytorch/executorch#23682 · 2 assegnatari ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement triaged
Difficoltà 2/5 Mezza giornata Idoneità per principianti 68/100
pytorch/executorch#21640 ·
I maintainer di solito rispondono entro 1 giorno
-
bug module: microcontrollers partner: arm
Difficoltà 4/5 3-5 giorni Idoneità per principianti 38/100
pytorch/executorch#23812 · 2 commenti · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
[muse-glimmer] interpolate_pos_emb returns corrupted bf16 values on CPU and can segfaultForse già presa @JakeStevens l’ha presa 2 giorni fa. Apertabug module: examples triaged
pytorch/executorch#23797 · 2 commenti · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
macOS Python wheel _portable_lib has non-relocatable libtorch rpathForse già presa @JacobSzwejbka l’ha presa 1 giorno fa. Aperta
pytorch/executorch#23742 · 1 commento · 2 assegnatari ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di pytorch/executorch
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
UKGovernmentBEIS/control-arena#917 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
debpalash/VoiceStudio#2937 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
aws-samples/sample-per-model-bedrock#261 ·
I maintainer di solito rispondono entro 1 giorno