Skip to content

API Reference: Export & Edge Optimization

Graph rewriting, compile safety validation, and multi-target model exporter engines.


CoreExporter

corecv.engine.exporter.CoreExporter(model, target='onnx', opset_version=17, target_hardware='server', input_shape=(1, 3, 640, 640), dynamic_axes=None, output_dir='./exports')

End-to-end model export pipeline for CoreCV.

Orchestrates three stages:

  1. Rewrite – deep-copies the model and applies :class:~corecv.engine.rewriter.TargetRewriter transforms when target_hardware='edge' (GELU -> ReLU, SiLU -> Hardswish, LayerNorm permutation collapse).
  2. Validate – runs :class:~corecv.engine.validator.MetaProber with device='meta' zero-VRAM shape auditing and static-graph dynamic-operation detection.
  3. Export – serialises to ONNX (.onnx) and/or ExecuTorch (.pte) using torch.export / torch.onnx.export.

The exporter handles all three CoreCV task types:

  • Classification – single tensor (B, C) output.
  • Segmentation – single tensor (B, C, H, W) or (B, H, W).
  • Detectiondict with lists of per-level tensors.

Parameters:

Name Type Description Default
model Module

The CoreCV model to export. Can be any nn.Module (backbone + neck + head), e.g. CoreObjectDetector.

required
target str

Export target. One of "onnx", "executorch", or "both".

'onnx'
opset_version int

ONNX opset version. Must be 17 or 18. Defaults to 17.

17
target_hardware str

Hardware target profile. "edge" applies activation rewrites (GELU -> ReLU, SiLU -> Hardswish) and NCHW layout optimisations. "server" skips all rewrites. Defaults to "server".

'server'
input_shape tuple[int, ...]

Input tensor shape (B, C, H, W). Defaults to (1, 3, 640, 640).

(1, 3, 640, 640)
dynamic_axes dict[str, dict[int, str]] | None

ONNX-style dynamic axes dictionary, e.g. {"input": {0: "batch", 2: "height", 3: "width"}}. If provided, the exporter also configures dynamic shapes for the torch.export.export call. None means all shapes are static.

None
output_dir str | Path

Directory for exported model files. Defaults to "./exports".

'./exports'

Raises:

Type Description
ValueError

If target, opset_version, or target_hardware is invalid.

Initialise the CoreExporter.

Parameters:

Name Type Description Default
model Module

The CoreCV model to export.

required
target str

Export target ("onnx", "executorch", or "both").

'onnx'
opset_version int

ONNX opset version (17 or 18).

17
target_hardware str

Hardware target ("edge" or "server").

'server'
input_shape tuple[int, ...]

Input tensor shape (B, C, H, W).

(1, 3, 640, 640)
dynamic_axes dict[str, dict[int, str]] | None

ONNX dynamic axes dict, or None.

None
output_dir str | Path

Output directory for exported files.

'./exports'

Raises:

Type Description
ValueError

If any argument is invalid.

Source code in src/corecv/engine/exporter.py
def __init__(  # noqa: PLR0913
    self,
    model: nn.Module,
    target: str = "onnx",
    opset_version: int = 17,
    target_hardware: str = "server",
    input_shape: tuple[int, ...] = (1, 3, 640, 640),
    dynamic_axes: dict[str, dict[int, str]] | None = None,
    output_dir: str | Path = "./exports",
) -> None:
    """Initialise the CoreExporter.

    Args:
        model: The CoreCV model to export.
        target: Export target (``"onnx"``, ``"executorch"``,
            or ``"both"``).
        opset_version: ONNX opset version (``17`` or ``18``).
        target_hardware: Hardware target (``"edge"`` or ``"server"``).
        input_shape: Input tensor shape ``(B, C, H, W)``.
        dynamic_axes: ONNX dynamic axes dict, or ``None``.
        output_dir: Output directory for exported files.

    Raises:
        ValueError: If any argument is invalid.
    """
    # --- Validate arguments --------------------------------------------
    if target not in _VALID_TARGETS:
        raise ValueError(
            _ERR_INVALID_TARGET.format(target, sorted(_VALID_TARGETS))
        )

    if opset_version not in _VALID_OPSET_VERSIONS:
        raise ValueError(
            _ERR_INVALID_OPSET.format(opset_version, sorted(_VALID_OPSET_VERSIONS))
        )

    target_hw = target_hardware.lower()
    if target_hw not in _VALID_HARDWARE_TARGETS:
        raise ValueError(
            _ERR_INVALID_HARDWARE.format(target_hardware, sorted(_VALID_HARDWARE_TARGETS))
        )
    target_hardware = target_hw

    input_shape_t = tuple(input_shape)
    if len(input_shape_t) != _ALLOWED_INPUT_NDIM:
        raise ValueError(
            _ERR_INVALID_INPUT_SHAPE.format(_ALLOWED_INPUT_NDIM, len(input_shape_t))
        )

    # --- Store attributes ----------------------------------------------
    self.model = model
    self.target = target
    self.opset_version = opset_version
    self.target_hardware = target_hardware
    self.input_shape = input_shape_t
    self.dynamic_axes = dynamic_axes
    self.output_dir = Path(output_dir)

    self.output_dir.mkdir(parents=True, exist_ok=True)

    # Cache the device once
    self._device: torch.device = (
        next(model.parameters()).device
        if list(model.parameters())
        else torch.device("cpu")
    )

export_executorch(model, output_path)

Export the model to ExecuTorch format (.pte).

Uses torch.export.export() to obtain an :class:torch.export.ExportedProgram and saves it with torch.export.save().

Optionally applies XNNPACK delegate optimisation when the executorch package is installed.

Parameters:

Name Type Description Default
model Module

The (rewritten) model to export. Must be in eval mode.

required
output_path str

Full path for the output .pte file (e.g. "/tmp/model.pte").

required

Returns:

Type Description
str

The absolute path to the exported .pte file.

Raises:

Type Description
RuntimeError

If torch.export is not available or export fails.

Source code in src/corecv/engine/exporter.py
def export_executorch(
    self,
    model: nn.Module,
    output_path: str,
) -> str:
    """Export the model to ExecuTorch format (``.pte``).

    Uses ``torch.export.export()`` to obtain an
    :class:`torch.export.ExportedProgram` and saves it with
    ``torch.export.save()``.

    Optionally applies XNNPACK delegate optimisation when the
    ``executorch`` package is installed.

    Args:
        model: The (rewritten) model to export.  Must be in eval mode.
        output_path: Full path for the output ``.pte`` file
            (e.g. ``"/tmp/model.pte"``).

    Returns:
        The absolute path to the exported ``.pte`` file.

    Raises:
        RuntimeError: If ``torch.export`` is not available or export
            fails.
    """
    if not TORCH_EXPORT_AVAILABLE or _torch_export is None:
        raise RuntimeError(
            _ERR_EXECUTORCH_UNAVAILABLE.format(torch.__version__)
        )

    output_path = str(Path(output_path).resolve())
    model = model.eval()
    example_input = self._create_example_input()

    # Build dynamic shape specs
    dynamic_shapes = _build_dynamic_shapes(
        self.input_shape, self.dynamic_axes
    )

    try:
        # Step 1: Obtain ExportedProgram
        logger.info("Running torch.export.export for ExecuTorch ...")
        exported_program = _torch_export(
            model,
            (example_input,),
            dynamic_shapes=dynamic_shapes,
        )

        # Step 2: Optionally apply XNNPACK delegate
        exported_program = self._maybe_apply_xnnpack_delegate(
            exported_program
        )

        # Step 3: Save to .pte
        logger.info("Saving ExecuTorch program to %s ...", output_path)
        with _warnings.catch_warnings():
            _warnings.filterwarnings(
                "ignore",
                message="Expect archive file to be a file ending in .pt2",
                module="torch.export",
            )
            torch.export.save(exported_program, output_path)
        logger.info("ExecuTorch export completed: %s", output_path)
    except Exception as exc:
        raise RuntimeError(
            _ERR_EXECUTORCH_EXPORT_FAILED.format(
                exc,
                type(model).__name__,
                self._describe_output(model, example_input),
            )
        ) from exc
    else:
        return output_path

export_onnx(model, output_path)

Export the model to ONNX format.

Uses the modern torch.export.export path when available (PyTorch >= 2.3), which produces cleaner ONNX graphs with better dynamic-shape support. Falls back to torch.onnx.export with FX tracing when the modern path is unavailable.

Detection models returning dict outputs are automatically wrapped with :class:_ONNXCompatModel to flatten outputs into a tuple of tensors.

Parameters:

Name Type Description Default
model Module

The (rewritten) model to export. Must be in eval mode.

required
output_path str

Full path for the output .onnx file (e.g. "/tmp/model.onnx").

required

Returns:

Type Description
str

The absolute path to the exported .onnx file.

Raises:

Type Description
RuntimeError

If ONNX export fails.

Source code in src/corecv/engine/exporter.py
def export_onnx(
    self,
    model: nn.Module,
    output_path: str,
) -> str:
    """Export the model to ONNX format.

    Uses the modern ``torch.export.export`` path when available
    (PyTorch >= 2.3), which produces cleaner ONNX graphs with better
    dynamic-shape support.  Falls back to ``torch.onnx.export`` with
    FX tracing when the modern path is unavailable.

    Detection models returning ``dict`` outputs are automatically
    wrapped with :class:`_ONNXCompatModel` to flatten outputs into a
    tuple of tensors.

    Args:
        model: The (rewritten) model to export.  Must be in eval mode.
        output_path: Full path for the output ``.onnx`` file
            (e.g. ``"/tmp/model.onnx"``).

    Returns:
        The absolute path to the exported ``.onnx`` file.

    Raises:
        RuntimeError: If ONNX export fails.
    """
    output_path = str(Path(output_path).resolve())
    model = model.eval()
    example_input = self._create_example_input()

    # Wrap if the model produces structured (dict/list) output
    wrapped = self._maybe_wrap_for_onnx(model, example_input)

    # Infer output names from the raw model
    try:
        output_names = _infer_output_names(model, example_input)
    except RuntimeError:
        output_names = [_OUTPUT_NAME_DEFAULT]

    # Build dynamic axes for torch.onnx.export
    dynamic_axes = None
    if self.dynamic_axes is not None:
        dynamic_axes = self.dynamic_axes
        # Add dynamic axes for inferred outputs if needed
        wrapped_output_names = _infer_output_names(wrapped, example_input)
        if len(wrapped_output_names) == 1 and len(output_names) > 1:
            # If flattened, assign generic names
            pass

    # Check for onnxscript availability (required by torch.onnx.export
    # in PyTorch >= 2.5)
    if not ONNXSCRIPT_AVAILABLE:
        raise RuntimeError(_ERR_ONNXSCRIPT_MISSING)

    try:
        # --- Modern path: torch.export.export + torch.onnx.export ------
        if TORCH_EXPORT_AVAILABLE and _torch_export is not None:
            logger.info("Using modern torch.export.export path for ONNX ...")
            dynamic_shapes = _build_dynamic_shapes(
                self.input_shape, self.dynamic_axes
            )
            try:
                exported_program = _torch_export(
                    wrapped,
                    (example_input,),
                    dynamic_shapes=dynamic_shapes,
                )
                # Export ExportedProgram to ONNX
                torch.onnx.export(
                    exported_program,
                    example_input,
                    output_path,
                    opset_version=self.opset_version,
                    input_names=["input"],
                    output_names=(
                        wrapped_output_names
                        if (
                            wrapped_output_names := _infer_output_names(
                                wrapped, example_input
                            )
                        )
                        else output_names
                    ),
                    dynamic_axes=dynamic_axes,
                )
                logger.info("ONNX export via torch.export completed: %s", output_path)
            except Exception as modern_exc:  # noqa: BLE001
                logger.warning(
                    "Modern torch.export path failed (%s); "
                    "falling back to torch.onnx.export ...",
                    modern_exc,
                )
            else:
                return output_path  # noqa: TRY300

        # --- Fallback: direct torch.onnx.export ------------------------
        logger.info("Using torch.onnx.export (direct path) ...")
        torch.onnx.export(
            wrapped,
            example_input,
            output_path,
            opset_version=self.opset_version,
            input_names=["input"],
            output_names=(
                _infer_output_names(wrapped, example_input)
                or output_names
            ),
            dynamic_axes=dynamic_axes,
        )
        logger.info("ONNX export completed: %s", output_path)
    except Exception as exc:
        raise RuntimeError(
            _ERR_ONNX_EXPORT_FAILED.format(
                exc,
                type(model).__name__,
                self._describe_output(model, example_input),
            )
        ) from exc
    else:
        return output_path

rewrite_model()

Apply hardware-specific rewrites on a copy of the model.

When target_hardware == 'edge', the copy is traced through :class:~corecv.engine.rewriter.TargetRewriter which:

  • Replaces nn.GELU / F.gelu with nn.ReLU.
  • Replaces nn.SiLU / F.silu with nn.Hardswish.
  • Collapses redundant NCHW <-> NHWC permutations around LayerNorm.

The original model passed to the constructor is never mutated.

Returns:

Type Description
Module

A rewritten copy of the model (GraphModule when

Module

target_hardware='edge', otherwise a plain nn.Module

Module

deep copy).

Source code in src/corecv/engine/exporter.py
def rewrite_model(self) -> nn.Module:
    """Apply hardware-specific rewrites on a **copy** of the model.

    When ``target_hardware == 'edge'``, the copy is traced through
    :class:`~corecv.engine.rewriter.TargetRewriter` which:

    * Replaces ``nn.GELU`` / ``F.gelu`` with ``nn.ReLU``.
    * Replaces ``nn.SiLU`` / ``F.silu`` with ``nn.Hardswish``.
    * Collapses redundant NCHW <-> NHWC permutations around
      ``LayerNorm``.

    The original model passed to the constructor is **never** mutated.

    Returns:
        A rewritten copy of the model (``GraphModule`` when
        ``target_hardware='edge'``, otherwise a plain ``nn.Module``
        deep copy).
    """
    # Always work on a deep copy to avoid mutating the original
    model_copy = copy.deepcopy(self.model)
    model_copy.eval()

    if self.target_hardware == "edge":
        logger.info("Applying TargetRewriter for edge hardware ...")
        rewriter = TargetRewriter()
        model_copy = rewriter.rewrite_for_edge(model_copy)
        logger.info("Edge rewrites applied successfully.")

    return model_copy

run_export()

Run the full export pipeline: rewrite -> validate -> export.

Convenience method that chains all three stages:

  1. :meth:rewrite_model – deep-copy and apply edge rewrites.
  2. :meth:validate_compatibility – zero-VRAM MetaProber audit.
  3. :meth:export_onnx and/or :meth:export_executorch depending on the target setting.

Exported files are placed in self.output_dir with filenames following the pattern {model_class}_{timestamp}.{ext}.

Returns:

Type Description
dict[str, str]

A dictionary mapping format names to file paths:

dict[str, str]
  • {"onnx": "/path/to/model.onnx"} when target="onnx".
dict[str, str]
  • {"executorch": "/path/to/model.pte"} when target="executorch".
dict[str, str]
  • {"onnx": ..., "executorch": ...} when target="both".

Raises:

Type Description
RuntimeError

If validation fails or any export step errors.

ValueError

If target is invalid (should not happen after constructor validation).

Source code in src/corecv/engine/exporter.py
def run_export(self) -> dict[str, str]:
    """Run the full export pipeline: rewrite -> validate -> export.

    Convenience method that chains all three stages:

    1. :meth:`rewrite_model` – deep-copy and apply edge rewrites.
    2. :meth:`validate_compatibility` – zero-VRAM MetaProber audit.
    3. :meth:`export_onnx` and/or :meth:`export_executorch` depending
       on the ``target`` setting.

    Exported files are placed in ``self.output_dir`` with filenames
    following the pattern ``{model_class}_{timestamp}.{ext}``.

    Returns:
        A dictionary mapping format names to file paths:

        * ``{"onnx": "/path/to/model.onnx"}`` when ``target="onnx"``.
        * ``{"executorch": "/path/to/model.pte"}`` when
            ``target="executorch"``.
        * ``{"onnx": ..., "executorch": ...}`` when ``target="both"``.

    Raises:
        RuntimeError: If validation fails or any export step errors.
        ValueError: If ``target`` is invalid (should not happen after
            constructor validation).
    """
    # ---- Stage 1: Rewrite --------------------------------------------
    logger.info("=" * 60)
    logger.info("Stage 1/3: Rewriting model for %s ...", self.target_hardware)
    rewritten = self.rewrite_model()
    logger.info("Rewrite stage complete.")

    # ---- Stage 2: Validate -------------------------------------------
    logger.info("Stage 2/3: Validating model compatibility ...")
    validation = self.validate_compatibility(rewritten)
    if not validation.passed:
        msg = (
            "Model validation failed. Export aborted.\n"
            f"Errors: {'; '.join(validation.errors)}"
        )
        raise RuntimeError(msg)
    logger.info("Validation stage passed: %s", "; ".join(validation.details))

    # ---- Stage 3: Export ---------------------------------------------
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    model_name = type(self.model).__name__.lower()
    results: dict[str, str] = {}

    if self.target in ("onnx", "both"):
        onnx_path = str(
            self.output_dir / f"{model_name}_{timestamp}.onnx"
        )
        logger.info("Stage 3a/3: Exporting to ONNX: %s ...", onnx_path)
        self.export_onnx(rewritten, onnx_path)
        results["onnx"] = onnx_path
        logger.info("ONNX export complete.")

    if self.target in ("executorch", "both"):
        pte_path = str(
            self.output_dir / f"{model_name}_{timestamp}.pte"
        )
        logger.info("Stage 3b/3: Exporting to ExecuTorch: %s ...", pte_path)
        self.export_executorch(rewritten, pte_path)
        results["executorch"] = pte_path
        logger.info("ExecuTorch export complete.")

    logger.info("Export pipeline finished successfully.")
    logger.info("Results: %s", results)
    return results

validate_compatibility(model)

Validate model compatibility for the target hardware.

Runs :class:~corecv.engine.validator.MetaProber with device='meta' for zero-VRAM shape propagation and static-graph auditing.

The validation strategy is:

  1. Component-aware – If the passed model exposes backbone, neck, and head attributes that satisfy the BaseBackbone interface, the full MetaProber pipeline is used.
  2. Original-model fallback – If FX tracing has erased submodule type information (common after :mod:torch.fx rewriting), the original model passed to the constructor is used for component validation instead.
  3. Shape-only fallback – If neither approach works, a simplified meta-device forward pass is performed to at least verify that shapes are compatible.

Parameters:

Name Type Description Default
model Module

The (possibly rewritten) model to validate.

required

Returns:

Name Type Description
A ValidationResult

class:ValidationResult with pass/fail status and

ValidationResult

descriptive details or errors.

Source code in src/corecv/engine/exporter.py
def validate_compatibility(self, model: nn.Module) -> ValidationResult:
    """Validate model compatibility for the target hardware.

    Runs :class:`~corecv.engine.validator.MetaProber` with
    ``device='meta'`` for zero-VRAM shape propagation and static-graph
    auditing.

    The validation strategy is:

    1. **Component-aware** – If the passed model exposes ``backbone``,
       ``neck``, and ``head`` attributes that satisfy the
       ``BaseBackbone`` interface, the full MetaProber pipeline is used.
    2. **Original-model fallback** – If FX tracing has erased submodule
       type information (common after :mod:`torch.fx` rewriting), the
       *original* model passed to the constructor is used for component
       validation instead.
    3. **Shape-only fallback** – If neither approach works, a simplified
       meta-device forward pass is performed to at least verify that
       shapes are compatible.

    Args:
        model: The (possibly rewritten) model to validate.

    Returns:
        A :class:`ValidationResult` with pass/fail status and
        descriptive details or errors.
    """
    prober = MetaProber()
    details: list[str] = []
    errors: list[str] = []

    # Determine input spatial size from input_shape
    h, w = self.input_shape[_HEIGHT_DIM], self.input_shape[_WIDTH_DIM]

    # ------------------------------------------------------------------
    # Strategy 1: try to extract backbone/neck/head from passed model
    # ------------------------------------------------------------------
    backbone = getattr(model, "backbone", None)
    neck = getattr(model, "neck", None)
    head = getattr(model, "head", None)

    # Check if the backbone has the expected interface; after FX tracing
    # the submodule type may have been erased (e.g. SimpleBackbone ->
    # Module), so we verify with isinstance().
    backbone_valid = (
        backbone is not None
        and isinstance(backbone, BaseBackbone)
    )

    try:
        if backbone_valid and head is not None:
            logger.info(
                "Running MetaProber validation on backbone/neck/head "
                "components ..."
            )
            prober.validate_compatibility(
                backbone=backbone,
                neck=neck,
                head=head,
                input_size=(h, w),
                target_hardware=self.target_hardware,
            )
            details.append(
                "MetaProber component-level validation passed "
                f"(backbone={type(backbone).__name__}, "
                f"head={type(head).__name__})"
            )
        else:
            # ------------------------------------------------------------------
            # Strategy 2: FX tracing may have erased type info on the passed
            # model; try the original model stored at construction time.
            # ------------------------------------------------------------------
            orig_backbone = getattr(self.model, "backbone", None)
            orig_neck = getattr(self.model, "neck", None)
            orig_head = getattr(self.model, "head", None)

            if (
                orig_backbone is not None
                and isinstance(orig_backbone, BaseBackbone)
                and orig_head is not None
            ):
                logger.info(
                    "Passed model submodules lack BaseBackbone type info "
                    "(FX tracing); falling back to original model for "
                    "MetaProber validation ..."
                )
                prober.validate_compatibility(
                    backbone=orig_backbone,
                    neck=orig_neck,
                    head=orig_head,
                    input_size=(h, w),
                    target_hardware=self.target_hardware,
                )
                details.append(
                    "MetaProber validation passed on original model "
                    f"(backbone={type(orig_backbone).__name__}, "
                    f"head={type(orig_head).__name__})"
                )
            else:
                # ------------------------------------------------------------------
                # Strategy 3: simplified meta-device shape propagation
                # ------------------------------------------------------------------
                logger.info(
                    "Model does not expose BaseBackbone backbone/neck/head; "
                    "running meta-device shape propagation ..."
                )
                self._run_meta_shape_propagation(model, h, w)
                details.append(
                    "Meta-device shape propagation passed for "
                    f"{type(model).__name__}"
                )
    except TypeError as exc:
        errors.append(str(exc))
        return ValidationResult(passed=False, details=details, errors=errors)
    except ValueError as exc:
        errors.append(str(exc))
        return ValidationResult(passed=False, details=details, errors=errors)
    except RuntimeError as exc:
        errors.append(str(exc))
        return ValidationResult(passed=False, details=details, errors=errors)
    except Exception as exc:  # noqa: BLE001
        errors.append(f"Unexpected validation error: {exc}")
        return ValidationResult(passed=False, details=details, errors=errors)

    return ValidationResult(passed=True, details=details, errors=errors)

TargetRewriter

corecv.engine.rewriter.TargetRewriter()

Rewrites FX graphs for edge hardware compatibility.

This class performs graph transformations to make models compatible with edge deployment targets like ExecuTorch (XNNPACK) and ONNX by: - Replacing GELU/SiLU activations with ReLU/HardSwish - Collapsing redundant NCHW <-> NHWC permutations around LayerNorm

Initialize the TargetRewriter.

Source code in src/corecv/engine/rewriter.py
def __init__(self) -> None:
    """Initialize the TargetRewriter."""
    pass

rewrite_for_edge(model)

Rewrite a model's FX graph for edge hardware compatibility.

Parameters:

Name Type Description Default
model Module

The PyTorch model to rewrite. Can be a regular nn.Module or an already traced GraphModule.

required

Returns:

Type Description
Module

A new GraphModule with edge-compatible graph transformations applied.

Module

Preserves training/eval mode and gradient flow.

Source code in src/corecv/engine/rewriter.py
def rewrite_for_edge(self, model: nn.Module) -> nn.Module:
    """Rewrite a model's FX graph for edge hardware compatibility.

    Args:
        model: The PyTorch model to rewrite. Can be a regular nn.Module
            or an already traced GraphModule.

    Returns:
        A new GraphModule with edge-compatible graph transformations applied.
        Preserves training/eval mode and gradient flow.
    """
    # Trace the model if it's not already a GraphModule
    if not isinstance(model, GraphModule):
        model = fx.symbolic_trace(model)

    # Apply graph transformations
    self._replace_activations(model)
    self._collapse_layernorm_permutations(model)

    # Recompile the graph to ensure validity
    model.recompile()

    # Preserve the original model's training mode
    model.train(model.training)

    return model

MetaProber

corecv.engine.validator.MetaProber()

Validates model compatibility for edge hardware deployment.

Performs zero-VRAM shape propagation and static graph analysis to ensure models can be compiled to ExecuTorch/ONNX without graph breaks or dynamic operations.

Initialize the MetaProber.

Source code in src/corecv/engine/validator.py
def __init__(self) -> None:
    """Initialize the MetaProber."""
    pass

validate_compatibility(backbone, neck, head, input_size=(224, 224), target_hardware='edge')

Validate end-to-end model compatibility for edge deployment.

Parameters:

Name Type Description Default
backbone Module

Backbone module (must implement BaseBackbone interface).

required
neck Module | None

Optional neck module (FPN, PAN, etc.).

required
head Module

Head module (classification, detection, segmentation).

required
input_size tuple[int, int]

Input image size as (height, width).

(224, 224)
target_hardware str

Target deployment hardware. One of "edge", "xnnpack", "qnn". Defaults to "edge" which runs all common edge validations.

'edge'

Returns:

Type Description
bool

True if the model is compatible with edge hardware.

Raises:

Type Description
TypeError

If backbone does not implement BaseBackbone interface.

ValueError

If compatibility check fails with descriptive message, or hardware-specific constraints are violated.

RuntimeError

If torch.export fails due to graph breaks.

Source code in src/corecv/engine/validator.py
def validate_compatibility(
    self,
    backbone: nn.Module,
    neck: nn.Module | None,
    head: nn.Module,
    input_size: tuple[int, int] = (224, 224),
    target_hardware: str = "edge",
) -> bool:
    """Validate end-to-end model compatibility for edge deployment.

    Args:
        backbone: Backbone module (must implement BaseBackbone interface).
        neck: Optional neck module (FPN, PAN, etc.).
        head: Head module (classification, detection, segmentation).
        input_size: Input image size as (height, width).
        target_hardware: Target deployment hardware. One of
            ``"edge"``, ``"xnnpack"``, ``"qnn"``. Defaults to ``"edge"``
            which runs all common edge validations.

    Returns:
        True if the model is compatible with edge hardware.

    Raises:
        TypeError: If backbone does not implement BaseBackbone interface.
        ValueError: If compatibility check fails with descriptive message,
            or hardware-specific constraints are violated.
        RuntimeError: If torch.export fails due to graph breaks.
    """
    # 1. Validate backbone implements BaseBackbone interface
    if not isinstance(backbone, BaseBackbone):
        raise TypeError(
            ERR_BACKBONE_INTERFACE.format(backbone_type=type(backbone).__name__)
        )

    feature_info: FeatureInfo = backbone.feature_info

    # 2. Propagate shapes on meta device
    try:
        meta_shapes = self._propagate_meta_shapes(
            backbone, neck, head, input_size
        )
    except Exception as e:
        raise ValueError(ERR_META_PROPAGATION_FAILED) from e

    # 3. Validate channel/stride compatibility
    self._validate_channel_stride_compatibility(
        feature_info, neck, head, meta_shapes, input_size
    )

    # 4. Static graph audit + hardware validation on the COMPLETE pipeline
    all_violations: list[str] = []

    # Create a single traced graph for all graph-level validators
    traced = self._create_traced_pipeline(backbone, neck, head, input_size)

    if traced is not None:
        # 4a. Static compatibility audit (dynamic operations check)
        static_violations = self._find_dynamic_operations(
            traced, "CombinedPipeline"
        )
        if static_violations:
            all_violations.append(
                ERR_DYNAMIC_OPS_FOUND.format(dynamic_ops=static_violations)
            )

        # 4b. Memory layout validation (HIGH PRIORITY for edge hardware)
        layout_violations = self._validate_memory_layout(traced)
        all_violations.extend(layout_violations)

        # 4c. Hardware-specific constraints validation
        hw_violations = self._validate_hardware_constraints(
            traced, target_hardware
        )
        all_violations.extend(hw_violations)
    else:
        # If tracing failed entirely (data-dependent control flow), report it
        all_violations.append(
            "Full model pipeline failed FX symbolic tracing. "
            "This may indicate dynamic control flow or "
            "data-dependent operations that prevent static "
            "graph compilation for edge hardware."
        )

    # Raise all violations at once for comprehensive feedback
    if all_violations:
        raise ValueError("; ".join(all_violations))

    # 5. Verify torch.export works (if available)
    if TORCH_EXPORT_AVAILABLE:
        self._verify_export_compatibility(backbone, neck, head, input_size)

    return True