AtenToCortexMPass crashes with "unsupported param type, call_function" when aten.linear.default is in preserve_ops

Ouverte
#21,904 5 commentaires 0 réactions 1 personne assignée Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

@AdrianLundell y travaille déjà.

Depuis le 21/8/2026.

Évaluation

Cette issue n'a pas encore été évaluée.

Description

bug module: arm module: microcontrollers partner: arm triaged
🐛 Describe the bug
Description:
When lowering a CortexMQuantizer-quantized model to the Cortex-M backend using the standard EdgeCompileConfig(preserve_ops=[torch.ops.aten.linear.default, ...], _check_ir_validity=False) pattern (as used in the backend's own CortexMToEdge test stage), CortexMPassManager.transform() crashes during AtenToCortexMPass with:
RuntimeError: unsupported param type, call_function.
import argparse
import logging

import torch
import torch.nn as nn
from torch.export import export

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")


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).module()

    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)

    # preserve_ops=[aten.linear.default] keeps `linear` nodes intact so
    # AtenToCortexMPass can substitute them with cortex_m::quantized_linear.
    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)

RuntimeError: unsupported param type, call_function.

Workaround found
Removing torch.ops.aten.linear.default from preserve_ops avoids the crash



### 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 @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani @psiddh @AdrianLundell
Langage dominant
Python
Étoiles
5.1k
Forks
1.2k
Merge moyen
1 j 18 h
PR mergées (30 j)
668

Préparer son environnement

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de pytorch/executorch

Toutes les issues de pytorch/executorch

Issues similaires

Plus d'issues Python

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.