Skip to content

Conversation

@rivkastroh
Copy link

Description

This change extends the ConvInteger implementation to match the ONNX operator spec, which allows both int8 and uint8 for the input tensors:

  • The ONNX ConvInteger schema defines:
    • T1: tensor(int8) or tensor(uint8)
    • T2: tensor(int8) or tensor(uint8)
    • T3: tensor(int32)
  • Previously, only the uint8 × uint8 combination was supported.
  • This PR adds support for all 8-bit combinations:
    • uint8 × uint8 (existing behavior)
    • uint8 × int8
    • int8 × uint8
    • int8 × int8

Motivation and Context

Fixes #24183
Fixes #15888
Fixes #12558
Fixes #3130
Fixes #12362

The ONNX ConvInteger operator schema allows both int8 and uint8 element types for its inputs, but the current implementation only supports uint8 × uint8. This leads to a gap where valid ONNX models using ConvInteger with int8 tensors cannot be executed.
This PR closes that gap by:
Aligning the implementation with the official ConvInteger type constraints.
Enabling models that use int8 (or mixed int8/uint8) for X and W to run without needing operator rewrites or additional custom kernels.
Keeping existing uint8 behavior unchanged, so the change is backwards compatible for current users.

Implementation details

  1. Templated core implementation (ComputeInner)
    The core logic of ConvInteger::Compute is moved into a templated helper:
class ConvInteger : public OpKernel {
 public:
       ...
 private:
  template <typename XT, typename WT>
  Status ComputeInner(OpKernelContext* context) const
};

XT is the element type of X (uint8_t or int8_t).
WT is the element type of W (uint8_t or int8_t).

  1. Zero-point handling
    Zero points are still treated as per-tensor scalar values, with the same validation,
    The values are read via DataRaw() and stored as 8-bit scalars, preserving the previous behavior.
    Interpretation of these raw bytes as signed or unsigned is delegated to the GEMM implementation via explicit signedness flags (see below).

  2. Im2col templated on XT
    The Im2col call now uses the runtime input type XT.

  3. Quantized GEMM with signedness flags:

gemm_shape.AIsSigned = W->IsDataType<int8_t>();
gemm_shape.BIsSigned = X->IsDataType<int8_t>();

AIsSigned and BIsSigned are derived from the runtime types of W and X.
Data for A and B is passed as raw bytes, the GEMM implementation uses the signedness flags to interpret them correctly (In a manner similar to the implementation in MatMulInteger).

  1. Runtime dispatch in Compute()
    The public Compute method becomes a thin dispatcher that selects the appropriate ComputeInner<XT, WT> instantiation based on the actual input types.

In addition, a small set of unit tests is added on top of the existing ConvInteger tests to cover the new type combinations, including cases where the first input tensor contains negative values (for the int8 × int8 path).

@rivkastroh rivkastroh marked this pull request as ready for review November 17, 2025 10:11
@rivkastroh
Copy link
Author

@skottmckay, @yuslepukhin: could you please review the PR? Thanks.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR extends the ConvInteger operator implementation to support all type combinations allowed by the ONNX specification, adding int8 support alongside the existing uint8 implementation. The changes enable models using int8 or mixed int8/uint8 quantized convolutions to execute without requiring custom kernels or operator rewrites.

  • Templated the core computation logic to handle both uint8_t and int8_t element types for input (X) and weight (W) tensors
  • Added signedness flags to the MLAS GEMM calls to correctly interpret the raw byte data based on runtime types
  • Added explicit template instantiation for Im2col<int8_t> to support int8 input processing

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
onnxruntime/core/providers/cpu/quantization/conv_integer.cc Refactored Compute into a templated ComputeInner method supporting all uint8/int8 combinations, updated kernel registration to accept both types, and added runtime dispatch logic
onnxruntime/core/util/math_cpu.cc Added template instantiation for Im2col<int8_t, StorageOrder::NCHW> to support int8 input data
onnxruntime/test/providers/cpu/nn/conv_integer_test.cc Added comprehensive test cases for u8s8 and s8s8 type combinations covering various scenarios (padding, groups, strides, 2D/3D)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Member

@yuslepukhin yuslepukhin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕐

@yuslepukhin
Copy link
Member

/azp run Linux QNN CI Pipeline, Win_TRT_Minimal_CUDA_Test_CI,Windows ARM64 QNN CI Pipeline,Windows GPU Doc Gen CI Pipeline,Windows x64 QNN CI Pipeline

@azure-pipelines
Copy link

Azure Pipelines successfully started running 4 pipeline(s).

yuslepukhin
yuslepukhin previously approved these changes Nov 19, 2025
Copy link
Member

@yuslepukhin yuslepukhin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@yuslepukhin
Copy link
Member

The documentation pipeline failed. Here is the output:

2025-11-19 20:41:16,441 build [WARNING] - The updated document D:\a_work\1\s\docs\OperatorKernels.md is different from the checked in version. Please regenerate the file with CPU, CUDA and DML execution providers enabled, or copy the updated version from the CI build's published artifacts if applicable.
2025-11-19 20:41:16,441 build [DEBUG] - diff:
diff --git a/docs/OperatorKernels.md b/docs/OperatorKernels.md
index 92093ec546..d7be243323 100644
--- a/docs/OperatorKernels.md
+++ b/docs/OperatorKernels.md
@@ -88,7 +88,7 @@ Do not modify directly.*
|Conv|in X:T
in W:T
in B:T
out Y:T|22+|T = tensor(float)|
|||[11, 21]|T = tensor(float)|
|||[1, 10]|T = tensor(float)|
-|ConvInteger|in x:T1
in w:T2
in x_zero_point:T1
in w_zero_point:T2
out y:T3|10+|T1 = tensor(uint8)
T2 = tensor(uint8)
T3 = tensor(int32)|
+|ConvInteger|in x:T1
in w:T2
in x_zero_point:T1
in w_zero_point:T2
out y:T3|10+|T1 = tensor(int8), tensor(uint8)
T2 = tensor(int8), tensor(uint8)
T3 = tensor(int32)|
|ConvTranspose|in X:T
in W:T
in B:T
out Y:T|22+|T = tensor(float)|
|||[11, 21]|T = tensor(float)|
|||[1, 10]|T = tensor(float)|

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment