Skip to content

[ONNX PTQ] Support for third-party custom ORT/TRT plugins in static calibration #2016

Description

@e-said

Describe the bug

When performing Post-Training Quantization (PTQ) via modelopt.onnx.quantization.quantize(), static calibration fails when the input ONNX graph contains non-standard custom domains or third-party operators (e.g., custom_domain::CustomOp), even when those custom operators are explicitly excluded from quantization via op_types_to_exclude / nodes_to_exclude.

Impact: Blocker for networks containing custom C++/CUDA operations when executing single-stage PTQ workflows under ONNX Runtime execution providers (such as CUDAExecutionProvider).

Technical Root Causes Identified:

  1. Validation Gate Failure (trt_utils.py):
    load_onnx_model invokes get_custom_layers(), which initializes a native trt.OnnxParser. If a node belongs to a custom namespace/domain outside the empty default "" or trt.plugins, the native parser throws an unhandled API error (INVALID_NODE: creator && "Plugin not found..."). Furthermore, set_trt_plugin_domain unconditionally overrides user-defined domains to "trt.plugins".

  2. Session Isolation in Calibrator (ort_patching.py):
    When bypassing initial parser validation, calibration fails during static graph execution. In _create_inference_session_with_ep_config, ModelOpt instantiates an ort.InferenceSession for the temporary augmented_model.onnx graph without providing a mechanism to register external custom operator binaries (e.g., calling sess_options.register_custom_ops_library(plugin_path)). This causes ONNX Runtime to halt execution with:
    [ONNXRuntimeError] : 1 : FAIL : Load model failed: Fatal error: custom_domain:CustomOp(-1) is not a registered function/op

Steps/Code to reproduce bug

import modelopt.onnx.quantization as moq

# Model containing a custom operator (e.g., domain: "custom_domain", op_type: "CustomOp")
onnx_model_path = "model_with_custom_op.onnx"

# Configure PTQ engine settings
# Assume user has a custom ORT operator shared library (.so) for execution
quant_cfg = {
    "quantize_mode": "int8",
    "calibration_method": "max",
    "calibration_eps": ["cuda:0", "cpu"],
    "op_types_to_exclude": ["CustomOp"],
    "nodes_to_exclude": ["/path/to/custom_node"],
}

# Execution halts during initial TRT parsing or internal augmented session instantiation
moq.quantize(
    onnx_path=onnx_model_path,
    quantize_mode="int8",
    calibration_data_reader=data_reader,
    engine_settings=quant_cfg,
)

Expected behavior

ModelOpt should provide an official mechanism (e.g., via engine_settings or extra_options) to pass external custom operator shared libraries (.so / .dll) into both the initial graph inspection pass and all internal ort.InferenceSession instances (such as the augmented model calibrator in ort_patching.py), allowing custom operators to execute seamlessly on CUDA without requiring manual source patching.

Who can help

N/A

System information

  • Container used (if applicable): Custom Docker Development Container
  • OS: Linux 6.14.0-37-generic (Ubuntu x86_64)
  • CPU architecture: x86_64
  • GPU name: NVIDIA RTX 2000 Ada Generation
  • GPU memory size: 15.58 GB
  • Number of GPUs: 1
  • Library versions:
    • Python: 3.12.12
    • ModelOpt version or commit hash: 0.42.0
    • CUDA: 12.4
    • PyTorch: 2.5.0+cu124
    • Transformers: N/A
    • TensorRT-LLM: N/A
    • ONNXRuntime: 1.23.0
    • TensorRT: 10.3.0
  • Any other details that may help: Hard-patching ort_patching.py to invoke sess_options.register_custom_ops_library("/path/to/plugin.so") directly inside _create_inference_session_with_ep_config resolved the issue completely and allowed 100% of quantizable nodes (191 nodes) to calibrate and export successfully.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions