Implement missing pieces for ARM QLinearConv support (#5894)

This commit is contained in:
Tracy Sharpe 2020-11-22 23:19:27 -08:00 committed by GitHub
parent d46dbeafd3
commit 57c92066c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 699 additions and 369 deletions

View file

@ -134,7 +134,7 @@ Status RegisterQuantizationKernels(KernelRegistry& kernel_registry) {
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, float, DynamicQuantizeMatMul)>,
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, uint8_t, MatMulIntegerToFloat)>,
BuildKernelCreateInfo<ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, float, DynamicQuantizeLSTM)>,
#if defined(MLAS_TARGET_AMD64_IX86)
#ifdef MLAS_SUPPORTS_GEMM_U8X8
BuildKernelCreateInfo<ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kMSDomain, 1, QLinearConv)>,
#endif
};

View file

@ -65,11 +65,6 @@ Abstract:
#define MLAS_SUPPORTS_GEMM_U8X8
#endif
#if defined(MLAS_TARGET_AMD64_IX86)
#define MLAS_SUPPORTS_GEMM_U8X8_AND_REQUANTIZE_OUTPUT
#define MLAS_SUPPORTS_TRANSPOSE
#endif
#if defined(MLAS_TARGET_AMD64) || defined(MLAS_TARGET_ARM64) || (defined(MLAS_TARGET_ARM) && !defined(_MSC_VER))
#define MLAS_SUPPORTS_PACKED_GEMM_U8X8
#endif
@ -668,36 +663,13 @@ MlasQuantizeLinear(
void
MLASCALL
MlasRequantizeOutput(
const int32_t* Input,
uint8_t* Output,
const int32_t* Bias,
size_t M,
size_t N,
float Scale,
uint8_t ZeroPoint
);
void
MLASCALL
MlasRequantizeOutputColumn(
const int32_t* Input,
uint8_t* Output,
const int32_t* Bias,
size_t M,
size_t N,
const float Scale,
uint8_t ZeroPoint
);
void
MLASCALL
MlasRequantizeOutputColumn(
const int32_t* Input,
uint8_t* Output,
const int32_t* Bias,
size_t M,
size_t N,
const float* Scale,
bool PerColumnScale,
uint8_t ZeroPoint
);

View file

@ -47,7 +47,7 @@ MLAS_INTERNAL_DATA const struct {
88.7762626647950f,
-88.3762626647949f,
88.3762626647949f,
12582912.f,
MLAS_ROUNDING_BIAS_MAGIC,
1.44269504088896341f,
-6.93145752e-1f,
-1.42860677e-6f,

View file

@ -782,6 +782,47 @@ MlasPartitionWork(
}
}
//
// Define the minimum floating point value (and its bit value equivalent) that
// has no fractional bits. This number can be used for fast rounding of floating
// point numbers to integers.
//
#define MLAS_ROUNDING_BIAS_MAGIC 12582912.f
#define MLAS_ROUNDING_BIAS_MAGIC_BITS 0x4B400000
//
// Helpers to cast a floating point type to and from an integer bit format.
//
MLAS_FORCEINLINE
uint32_t
MlasBitsOfFp32(
float FloatValue
)
{
union {
uint32_t IntegerValue;
float FloatValue;
} u;
u.FloatValue = FloatValue;
return u.IntegerValue;
}
MLAS_FORCEINLINE
float
MlasFp32FromBits(
uint32_t IntegerValue
)
{
union {
uint32_t IntegerValue;
float FloatValue;
} u;
u.IntegerValue = IntegerValue;
return u.FloatValue;
}
//
// Define the missing ARM64 NEON intrinsic macros from arm64_neon.h that enable
// cross-compiler support.

View file

@ -19,35 +19,6 @@ Abstract:
#include "mlasi.h"
union MLAS_FLOAT32BITS
{
uint32_t u32;
float fp32;
};
MLAS_FORCEINLINE
static
uint32_t
MlasBitsOfFp32(
float f
)
{
MLAS_FLOAT32BITS uf;
uf.fp32 = f;
return uf.u32;
}
MLAS_FORCEINLINE
static
float
MlasFp32FromBits(
uint32_t u
)
{
MLAS_FLOAT32BITS uf = { u };
return uf.fp32;
}
MLAS_FORCEINLINE
static
void

View file

@ -289,45 +289,239 @@ MlasQuantizeLinear<uint8_t>(
#if defined(MLAS_SSE2_INTRINSICS)
MLAS_FORCEINLINE
MLAS_INT32X4
MlasRequantizeOutputVector(
MLAS_INT32X4 IntegerVector,
MLAS_INT32X4 BiasVector,
MLAS_FLOAT32X4 ScaleVector,
MLAS_FLOAT32X4 MinimumValueVector,
MLAS_FLOAT32X4 MaximumValueVector,
MLAS_INT32X4 ZeroPointVector
void
MLASCALL
MlasRequantizeOutput(
const int32_t* Input,
uint8_t* Output,
const int32_t* Bias,
size_t M,
size_t N,
const float* Scale,
bool PerColumnScale,
uint8_t ZeroPoint
)
/*++
Routine Description:
This routine requantizes the intermediate buffer to the output buffer
optionally adding the supplied bias.
Arguments:
Input - Supplies the input matrix.
Output - Supplies the output matrix.
Bias - Supplies the optional bias vector to be added to the input buffer
before requantization.
Buffer - Supplies the output matrix.
M - Supplies the number of elements of the bias vector and the number of
rows in the output matrix.
N - Supplies the number of columns of the output matrix.
Scale - Supplies the quantization scale.
PerColumnScale - Supplies true if the quantization scale has per-column
values, else false if a single quantization scale applies to the
entire matrix.
ZeroPoint - Supplies the quantization zero point value.
Return Value:
None.
--*/
{
IntegerVector = _mm_add_epi32(IntegerVector, BiasVector);
MLAS_FLOAT32X4 FloatVector = _mm_cvtepi32_ps(IntegerVector);
const __m128 PerMatrixScaleVector = PerColumnScale ? _mm_setzero_ps() : _mm_load1_ps(Scale);
const __m128 MinimumValueVector = _mm_set1_ps(float(0 - ZeroPoint));
const __m128 MaximumValueVector = _mm_set1_ps(float(255 - ZeroPoint));
const __m128i ZeroPointVector = _mm_set1_epi32(ZeroPoint);
//
// Scale the input vector and clamp the values to the minimum and maximum
// range (adjusted by the zero point value).
// Step through each row of the output matrix.
//
FloatVector = MlasMultiplyFloat32x4(FloatVector, ScaleVector);
while (M-- > 0) {
// N.B. MINPS and MAXPS returns the value from the second vector if the
// value from the first vector is a NaN.
FloatVector = _mm_max_ps(FloatVector, MinimumValueVector);
FloatVector = _mm_min_ps(FloatVector, MaximumValueVector);
const int32_t* bias = Bias;
const float* scale = PerColumnScale ? Scale : nullptr;
size_t n = N;
//
// Convert the float values to integer using "round to nearest even" and
// then shift the output range using the zero point value.
//
//
// Process 16 columns of the matrices at a time.
//
// N.B. Assumes MXCSR has been configured with the default rounding mode of
// "round to nearest even".
IntegerVector = _mm_cvtps_epi32(FloatVector);
IntegerVector = _mm_add_epi32(IntegerVector, ZeroPointVector);
while (n >= 16) {
return IntegerVector;
//
// Load the input data and optionally add the per-column bias.
//
__m128i IntegerVector0 = _mm_loadu_si128((const __m128i *)&Input[0]);
__m128i IntegerVector1 = _mm_loadu_si128((const __m128i *)&Input[4]);
__m128i IntegerVector2 = _mm_loadu_si128((const __m128i *)&Input[8]);
__m128i IntegerVector3 = _mm_loadu_si128((const __m128i *)&Input[12]);
Input += 16;
if (bias != nullptr) {
IntegerVector0 = _mm_add_epi32(IntegerVector0, _mm_loadu_si128((const __m128i *)&bias[0]));
IntegerVector1 = _mm_add_epi32(IntegerVector1, _mm_loadu_si128((const __m128i *)&bias[4]));
IntegerVector2 = _mm_add_epi32(IntegerVector2, _mm_loadu_si128((const __m128i *)&bias[8]));
IntegerVector3 = _mm_add_epi32(IntegerVector3, _mm_loadu_si128((const __m128i *)&bias[12]));
bias += 16;
}
//
// Convert to integer values to float and apply the per-tensor or
// per-column scaling.
//
__m128 FloatVector0 = _mm_cvtepi32_ps(IntegerVector0);
__m128 FloatVector1 = _mm_cvtepi32_ps(IntegerVector1);
__m128 FloatVector2 = _mm_cvtepi32_ps(IntegerVector2);
__m128 FloatVector3 = _mm_cvtepi32_ps(IntegerVector3);
if (scale != nullptr) {
FloatVector0 = _mm_mul_ps(FloatVector0, _mm_loadu_ps(&scale[0]));
FloatVector1 = _mm_mul_ps(FloatVector1, _mm_loadu_ps(&scale[4]));
FloatVector2 = _mm_mul_ps(FloatVector2, _mm_loadu_ps(&scale[8]));
FloatVector3 = _mm_mul_ps(FloatVector3, _mm_loadu_ps(&scale[12]));
scale += 16;
} else {
FloatVector0 = _mm_mul_ps(FloatVector0, PerMatrixScaleVector);
FloatVector1 = _mm_mul_ps(FloatVector1, PerMatrixScaleVector);
FloatVector2 = _mm_mul_ps(FloatVector2, PerMatrixScaleVector);
FloatVector3 = _mm_mul_ps(FloatVector3, PerMatrixScaleVector);
}
FloatVector0 = _mm_max_ps(FloatVector0, MinimumValueVector);
FloatVector1 = _mm_max_ps(FloatVector1, MinimumValueVector);
FloatVector2 = _mm_max_ps(FloatVector2, MinimumValueVector);
FloatVector3 = _mm_max_ps(FloatVector3, MinimumValueVector);
FloatVector0 = _mm_min_ps(FloatVector0, MaximumValueVector);
FloatVector1 = _mm_min_ps(FloatVector1, MaximumValueVector);
FloatVector2 = _mm_min_ps(FloatVector2, MaximumValueVector);
FloatVector3 = _mm_min_ps(FloatVector3, MaximumValueVector);
IntegerVector0 = _mm_cvtps_epi32(FloatVector0);
IntegerVector1 = _mm_cvtps_epi32(FloatVector1);
IntegerVector2 = _mm_cvtps_epi32(FloatVector2);
IntegerVector3 = _mm_cvtps_epi32(FloatVector3);
IntegerVector0 = _mm_add_epi32(IntegerVector0, ZeroPointVector);
IntegerVector1 = _mm_add_epi32(IntegerVector1, ZeroPointVector);
IntegerVector2 = _mm_add_epi32(IntegerVector2, ZeroPointVector);
IntegerVector3 = _mm_add_epi32(IntegerVector3, ZeroPointVector);
__m128i WordVector0 = _mm_packus_epi16(IntegerVector0, IntegerVector1);
__m128i WordVector1 = _mm_packus_epi16(IntegerVector2, IntegerVector3);
__m128i ByteVector = _mm_packus_epi16(WordVector0, WordVector1);
_mm_storeu_si128((__m128i*)Output, ByteVector);
Output += 16;
n -= 16;
}
//
// Process the remaining columns of the matrices.
//
while (n > 0) {
//
// Load the input data and optionally add the per-column bias.
//
__m128i IntegerVector;
if (n >= 4) {
IntegerVector = _mm_loadu_si128((const __m128i*)&Input[0]);
Input += 4;
if (bias != nullptr) {
IntegerVector = _mm_add_epi32(IntegerVector, _mm_loadu_si128((const __m128i*)&bias[0]));
bias += 4;
}
} else {
int32_t IntegerValue = *Input++;
if (bias != nullptr) {
IntegerValue += *bias++;
}
IntegerVector = _mm_cvtsi32_si128(IntegerValue);
}
//
// Convert to integer values to float and apply the per-tensor or
// per-column scaling.
//
__m128 FloatVector = _mm_cvtepi32_ps(IntegerVector);
__m128 ScaleVector;
if (scale != nullptr) {
if (n >= 4) {
ScaleVector = _mm_loadu_ps(scale);
scale += 4;
} else {
ScaleVector = _mm_load_ss(scale);
scale += 1;
}
} else {
ScaleVector = PerMatrixScaleVector;
}
FloatVector = _mm_mul_ps(FloatVector, ScaleVector);
FloatVector = _mm_max_ps(FloatVector, MinimumValueVector);
FloatVector = _mm_min_ps(FloatVector, MaximumValueVector);
IntegerVector = _mm_cvtps_epi32(FloatVector);
IntegerVector = _mm_add_epi32(IntegerVector, ZeroPointVector);
IntegerVector = _mm_packus_epi16(IntegerVector, IntegerVector);
IntegerVector = _mm_packus_epi16(IntegerVector, IntegerVector);
uint32_t OutputValue = uint32_t(_mm_cvtsi128_si32(IntegerVector));
if (n >= 4) {
*reinterpret_cast<uint32_t*>(Output) = OutputValue;
Output += 4;
n -= 4;
} else {
*Output = uint8_t(OutputValue);
Output += 1;
n -= 1;
}
}
}
}
#elif defined(MLAS_NEON64_INTRINSICS)
void
MLASCALL
MlasRequantizeOutput(
@ -336,7 +530,8 @@ MlasRequantizeOutput(
const int32_t* Bias,
size_t M,
size_t N,
float Scale,
const float* Scale,
bool PerColumnScale,
uint8_t ZeroPoint
)
/*++
@ -364,98 +559,9 @@ Arguments:
Scale - Supplies the quantization scale.
ZeroPoint - Supplies the quantization zero point value.
Return Value:
None.
--*/
{
MLAS_FLOAT32X4 ScaleVector = MlasBroadcastFloat32x4(Scale);
MLAS_FLOAT32X4 MinimumValueVector = MlasBroadcastFloat32x4(float(0 - ZeroPoint));
MLAS_FLOAT32X4 MaximumValueVector = MlasBroadcastFloat32x4(float(255 - ZeroPoint));
MLAS_INT32X4 ZeroPointVector = MlasBroadcastInt32x4(ZeroPoint);
MLAS_INT32X4 BiasVector = _mm_setzero_si128();
//
// Step through each row of the output matrix.
//
while (M-- > 0) {
if (Bias != nullptr) {
BiasVector = MlasBroadcastInt32x4(*Bias++);
}
size_t n = N;
while (n >= 4) {
MLAS_INT32X4 IntegerVector = _mm_loadu_si128((const __m128i *)Input);
IntegerVector = MlasRequantizeOutputVector(IntegerVector, BiasVector,
ScaleVector, MinimumValueVector, MaximumValueVector, ZeroPointVector);
IntegerVector = _mm_packus_epi16(IntegerVector, IntegerVector);
IntegerVector = _mm_packus_epi16(IntegerVector, IntegerVector);
*((int32_t*)Output) = _mm_cvtsi128_si32(IntegerVector);
Input += 4;
Output += 4;
n -= 4;
}
while (n > 0) {
MLAS_INT32X4 IntegerVector = _mm_cvtsi32_si128(*Input);
IntegerVector = MlasRequantizeOutputVector(IntegerVector, BiasVector,
ScaleVector, MinimumValueVector, MaximumValueVector, ZeroPointVector);
*Output = (uint8_t)_mm_cvtsi128_si32(IntegerVector);
Input += 1;
Output += 1;
n -= 1;
}
}
}
void
MLASCALL
MlasRequantizeOutputColumn(
const int32_t* Input,
uint8_t* Output,
const int32_t* Bias,
size_t M,
size_t N,
float Scale,
uint8_t ZeroPoint
)
/*++
Routine Description:
This routine requantizes the intermediate buffer to the output buffer
optionally adding the supplied bias.
Arguments:
Input - Supplies the input matrix.
Output - Supplies the output matrix.
Bias - Supplies the optional bias vector to be added to the input buffer
before requantization.
Buffer - Supplies the output matrix.
M - Supplies the number of elements of the bias vector and the number of
rows in the output matrix.
N - Supplies the number of columns of the output matrix.
Scale - Supplies the quantization scale.
PerColumnScale - Supplies true if the quantization scale has per-column
values, else false if a single quantization scale applies to the
entire matrix.
ZeroPoint - Supplies the quantization zero point value.
@ -465,11 +571,8 @@ Return Value:
--*/
{
MLAS_FLOAT32X4 ScaleVector = MlasBroadcastFloat32x4(Scale);
MLAS_FLOAT32X4 MinimumValueVector = MlasBroadcastFloat32x4(float(0 - ZeroPoint));
MLAS_FLOAT32X4 MaximumValueVector = MlasBroadcastFloat32x4(float(255 - ZeroPoint));
MLAS_INT32X4 ZeroPointVector = MlasBroadcastInt32x4(ZeroPoint);
MLAS_INT32X4 BiasVector = _mm_setzero_si128();
const float32x4_t PerMatrixScaleVector = PerColumnScale ? vdupq_n_f32(0) : vld1q_dup_f32(Scale);
const int16x8_t ZeroPointVector = vdupq_n_s16(ZeroPoint);
//
// Step through each row of the output matrix.
@ -478,61 +581,203 @@ Return Value:
while (M-- > 0) {
const int32_t* bias = Bias;
const float* scale = PerColumnScale ? Scale : nullptr;
size_t n = N;
while (n >= 4) {
//
// Process 16 columns of the matrices at a time.
//
MLAS_INT32X4 IntegerVector = _mm_loadu_si128((const __m128i *)Input);
while (n >= 16) {
//
// Load the input data and optionally add the per-column bias.
//
int32x4x4_t IntegerVector;
IntegerVector.val[0] = vld1q_s32(&Input[0]);
IntegerVector.val[1] = vld1q_s32(&Input[4]);
IntegerVector.val[2] = vld1q_s32(&Input[8]);
IntegerVector.val[3] = vld1q_s32(&Input[12]);
Input += 16;
if (bias != nullptr) {
BiasVector = _mm_loadu_si128((const __m128i*)bias);
bias += 4;
IntegerVector.val[0] = vaddq_s32(IntegerVector.val[0], vld1q_s32(&bias[0]));
IntegerVector.val[1] = vaddq_s32(IntegerVector.val[1], vld1q_s32(&bias[4]));
IntegerVector.val[2] = vaddq_s32(IntegerVector.val[2], vld1q_s32(&bias[8]));
IntegerVector.val[3] = vaddq_s32(IntegerVector.val[3], vld1q_s32(&bias[12]));
bias += 16;
}
IntegerVector = MlasRequantizeOutputVector(IntegerVector, BiasVector,
ScaleVector, MinimumValueVector, MaximumValueVector, ZeroPointVector);
//
// Convert to integer values to float and apply the per-tensor or
// per-column scaling.
//
IntegerVector = _mm_packus_epi16(IntegerVector, IntegerVector);
IntegerVector = _mm_packus_epi16(IntegerVector, IntegerVector);
float32x4x4_t FloatVector;
*((int32_t*)Output) = _mm_cvtsi128_si32(IntegerVector);
FloatVector.val[0] = vcvtq_f32_s32(IntegerVector.val[0]);
FloatVector.val[1] = vcvtq_f32_s32(IntegerVector.val[1]);
FloatVector.val[2] = vcvtq_f32_s32(IntegerVector.val[2]);
FloatVector.val[3] = vcvtq_f32_s32(IntegerVector.val[3]);
Input += 4;
Output += 4;
n -= 4;
if (scale != nullptr) {
float32x4x4_t PerColumnScaleVector;
PerColumnScaleVector.val[0] = vld1q_f32(&scale[0]);
PerColumnScaleVector.val[1] = vld1q_f32(&scale[4]);
PerColumnScaleVector.val[2] = vld1q_f32(&scale[8]);
PerColumnScaleVector.val[3] = vld1q_f32(&scale[12]);
scale += 16;
FloatVector.val[0] = vmulq_f32(FloatVector.val[0], PerColumnScaleVector.val[0]);
FloatVector.val[1] = vmulq_f32(FloatVector.val[1], PerColumnScaleVector.val[1]);
FloatVector.val[2] = vmulq_f32(FloatVector.val[2], PerColumnScaleVector.val[2]);
FloatVector.val[3] = vmulq_f32(FloatVector.val[3], PerColumnScaleVector.val[3]);
} else {
FloatVector.val[0] = vmulq_f32(FloatVector.val[0], PerMatrixScaleVector);
FloatVector.val[1] = vmulq_f32(FloatVector.val[1], PerMatrixScaleVector);
FloatVector.val[2] = vmulq_f32(FloatVector.val[2], PerMatrixScaleVector);
FloatVector.val[3] = vmulq_f32(FloatVector.val[3], PerMatrixScaleVector);
}
//
// Convert the float values to integer using "round to nearest even".
// Results are saturated to the range of int32_t.
//
IntegerVector.val[0] = vcvtnq_s32_f32(FloatVector.val[0]);
IntegerVector.val[1] = vcvtnq_s32_f32(FloatVector.val[1]);
IntegerVector.val[2] = vcvtnq_s32_f32(FloatVector.val[2]);
IntegerVector.val[3] = vcvtnq_s32_f32(FloatVector.val[3]);
//
// Pack the integers with saturation to 16-bit values and shift by
// the zero point, then pack the integers again to unsigned bytes.
//
int16x8x2_t WordVector;
WordVector.val[0] = vqmovn_high_s32(vqmovn_s32(IntegerVector.val[0]), IntegerVector.val[1]);
WordVector.val[1] = vqmovn_high_s32(vqmovn_s32(IntegerVector.val[2]), IntegerVector.val[3]);
WordVector.val[0] = vqaddq_s16(WordVector.val[0], ZeroPointVector);
WordVector.val[1] = vqaddq_s16(WordVector.val[1], ZeroPointVector);
vst1q_u8(Output, vqmovun_high_s16(vqmovun_s16(WordVector.val[0]), WordVector.val[1]));
Output += 16;
n -= 16;
}
//
// Process the remaining columns of the matrices.
//
while (n > 0) {
MLAS_INT32X4 IntegerVector = _mm_cvtsi32_si128(*Input);
//
// Load the input data and optionally add the per-column bias.
//
if (bias != nullptr) {
BiasVector = _mm_cvtsi32_si128(*bias);
bias += 1;
int32x4_t IntegerVector;
if (n >= 4) {
IntegerVector = vld1q_s32(&Input[0]);
Input += 4;
if (bias != nullptr) {
IntegerVector = vaddq_s32(IntegerVector, vld1q_s32(&bias[0]));
bias += 4;
}
} else {
IntegerVector = vld1q_dup_s32(Input);
Input += 1;
if (bias != nullptr) {
IntegerVector = vaddq_s32(IntegerVector, vld1q_dup_s32(bias));
bias += 1;
}
}
IntegerVector = MlasRequantizeOutputVector(IntegerVector, BiasVector,
ScaleVector, MinimumValueVector, MaximumValueVector, ZeroPointVector);
//
// Convert to integer values to float and apply the per-tensor or
// per-column scaling.
//
*Output = (uint8_t)_mm_cvtsi128_si32(IntegerVector);
float32x4_t FloatVector = vcvtq_f32_s32(IntegerVector);
float32x4_t ScaleVector;
Input += 1;
Output += 1;
n -= 1;
if (scale != nullptr) {
if (n >= 4) {
ScaleVector = vld1q_f32(scale);
scale += 4;
} else {
ScaleVector = vld1q_dup_f32(scale);
scale += 1;
}
} else {
ScaleVector = PerMatrixScaleVector;
}
FloatVector = vmulq_f32(FloatVector, ScaleVector);
//
// Convert the float values to integer using "round to nearest even".
// Results are saturated to the range of int32_t.
//
IntegerVector = vcvtnq_s32_f32(FloatVector);
//
// Pack the integers with saturation to 16-bit values and shift by
// the zero point, then pack the integers again to unsigned bytes.
//
int16x8_t WordVector = vcombine_s16(vqmovn_s32(IntegerVector), vdup_n_s16(0));
WordVector = vqaddq_s16(WordVector, ZeroPointVector);
uint8x16_t ByteVector = vcombine_u8(vqmovun_s16(WordVector), vdup_n_u8(0));
if (n >= 4) {
vst1q_lane_u32(reinterpret_cast<uint32_t*>(Output), vreinterpretq_u32_u8(ByteVector), 0);
Output += 4;
n -= 4;
} else {
vst1q_lane_u8(Output, ByteVector, 0);
Output += 1;
n -= 1;
}
}
}
}
#else
void
MLASCALL
MlasRequantizeOutputColumn(
MlasRequantizeOutput(
const int32_t* Input,
uint8_t* Output,
const int32_t* Bias,
size_t M,
size_t N,
const float* Scale,
bool PerColumnScale,
uint8_t ZeroPoint
)
/*++
@ -558,7 +803,11 @@ Arguments:
N - Supplies the number of columns of the output matrix.
Scale - Supplies the quantization scale vector.
Scale - Supplies the quantization scale.
PerColumnScale - Supplies true if the quantization scale has per-column
values, else false if a single quantization scale applies to the
entire matrix.
ZeroPoint - Supplies the quantization zero point value.
@ -568,10 +817,9 @@ Return Value:
--*/
{
MLAS_FLOAT32X4 MinimumValueVector = MlasBroadcastFloat32x4(float(0 - ZeroPoint));
MLAS_FLOAT32X4 MaximumValueVector = MlasBroadcastFloat32x4(float(255 - ZeroPoint));
MLAS_INT32X4 ZeroPointVector = MlasBroadcastInt32x4(ZeroPoint);
MLAS_INT32X4 BiasVector = _mm_setzero_si128();
const float PerMatrixScaleValue = PerColumnScale ? 0.0f : *Scale;
const float MinimumValue = float(0 - ZeroPoint);
const float MaximumValue = float(255 - ZeroPoint);
//
// Step through each row of the output matrix.
@ -581,53 +829,36 @@ Return Value:
const int32_t* bias = Bias;
const float* scale = Scale;
size_t n = N;
while (n >= 4) {
MLAS_INT32X4 IntegerVector = _mm_loadu_si128((const __m128i *)Input);
if (bias != nullptr) {
BiasVector = _mm_loadu_si128((const __m128i*)bias);
bias += 4;
}
MLAS_FLOAT32X4 ScaleVector = MlasLoadFloat32x4(scale);
scale += 4;
IntegerVector = MlasRequantizeOutputVector(IntegerVector, BiasVector,
ScaleVector, MinimumValueVector, MaximumValueVector, ZeroPointVector);
IntegerVector = _mm_packus_epi16(IntegerVector, IntegerVector);
IntegerVector = _mm_packus_epi16(IntegerVector, IntegerVector);
*((int32_t*)Output) = _mm_cvtsi128_si32(IntegerVector);
Input += 4;
Output += 4;
n -= 4;
}
while (n > 0) {
MLAS_INT32X4 IntegerVector = _mm_cvtsi32_si128(*Input);
int32_t IntegerValue = *Input++;
if (bias != nullptr) {
BiasVector = _mm_cvtsi32_si128(*bias);
bias += 1;
IntegerValue += *bias++;
}
MLAS_FLOAT32X4 ScaleVector = _mm_load_ss(scale);
scale += 1;
float FloatValue = float(IntegerValue);
float ScaleValue = PerColumnScale ? *scale++ : PerMatrixScaleValue;
IntegerVector = MlasRequantizeOutputVector(IntegerVector, BiasVector,
ScaleVector, MinimumValueVector, MaximumValueVector, ZeroPointVector);
FloatValue *= ScaleValue;
FloatValue = std::max(FloatValue, MinimumValue);
FloatValue = std::min(FloatValue, MaximumValue);
*Output = (uint8_t)_mm_cvtsi128_si32(IntegerVector);
//
// Use the fast rounding trick adapted from XNNPACK: bias the floating
// point value by the first floating point value that has no
// fractional bits. The add operation performs the "round to nearest
// even". Extract the mantissa bits from this floating point value to
// obtain the rounded integer value.
//
IntegerValue = int32_t(MlasBitsOfFp32(FloatValue + MLAS_ROUNDING_BIAS_MAGIC)) -
MLAS_ROUNDING_BIAS_MAGIC_BITS;
*Output++ = uint8_t(IntegerValue + ZeroPoint);
Input += 1;
Output += 1;
n -= 1;
}
}

View file

@ -16,7 +16,177 @@ Abstract:
#include "mlasi.h"
#ifdef MLAS_TARGET_AMD64_IX86
#if defined(MLAS_SSE2_INTRINSICS) || defined(MLAS_NEON_INTRINSICS)
MLAS_FORCEINLINE
void
MlasTranspose8x8Block(
const uint8_t* Input,
size_t InputStride,
uint8_t* Output,
size_t OutputStride
)
/*++
Routine Description:
This routine transposes an eight by eight element block from the input
matrix to the output matrix.
Arguments:
Input - Supplies the input buffer.
InputStride - Supplies the number of elements between rows of the input
matrix.
Output - Supplies the output buffer.
OutputStride - Supplies the number of elements between rows of the output
matrix.
Return Value:
None.
--*/
{
#if defined(MLAS_SSE2_INTRINSICS)
__m128i a0 = _mm_loadl_epi64((const __m128i*)&Input[InputStride * 0]);
__m128i a1 = _mm_loadl_epi64((const __m128i*)&Input[InputStride * 1]);
__m128i b0 = _mm_unpacklo_epi8(a0, a1);
__m128i a2 = _mm_loadl_epi64((const __m128i*)&Input[InputStride * 2]);
__m128i a3 = _mm_loadl_epi64((const __m128i*)&Input[InputStride * 3]);
__m128i b1 = _mm_unpacklo_epi8(a2, a3);
__m128i a4 = _mm_loadl_epi64((const __m128i*)&Input[InputStride * 4]);
__m128i a5 = _mm_loadl_epi64((const __m128i*)&Input[InputStride * 5]);
__m128i b2 = _mm_unpacklo_epi8(a4, a5);
__m128i a6 = _mm_loadl_epi64((const __m128i*)&Input[InputStride * 6]);
__m128i a7 = _mm_loadl_epi64((const __m128i*)&Input[InputStride * 7]);
__m128i b3 = _mm_unpacklo_epi8(a6, a7);
__m128i c0 = _mm_unpacklo_epi16(b0, b1);
__m128i c1 = _mm_unpackhi_epi16(b0, b1);
__m128i c2 = _mm_unpacklo_epi16(b2, b3);
__m128i c3 = _mm_unpackhi_epi16(b2, b3);
__m128 d0 = _mm_castsi128_ps(_mm_unpacklo_epi32(c0, c2));
_mm_storel_pi((__m64*)&Output[OutputStride * 0], d0);
_mm_storeh_pi((__m64*)&Output[OutputStride * 1], d0);
__m128 d1 = _mm_castsi128_ps(_mm_unpackhi_epi32(c0, c2));
_mm_storel_pi((__m64*)&Output[OutputStride * 2], d1);
_mm_storeh_pi((__m64*)&Output[OutputStride * 3], d1);
__m128 d2 = _mm_castsi128_ps(_mm_unpacklo_epi32(c1, c3));
_mm_storel_pi((__m64*)&Output[OutputStride * 4], d2);
_mm_storeh_pi((__m64*)&Output[OutputStride * 5], d2);
__m128 d3 = _mm_castsi128_ps(_mm_unpackhi_epi32(c1, c3));
_mm_storel_pi((__m64*)&Output[OutputStride * 6], d3);
_mm_storeh_pi((__m64*)&Output[OutputStride * 7], d3);
#elif defined(MLAS_NEON_INTRINSICS)
uint8x8_t a0 = vld1_u8(&Input[InputStride * 0]);
uint8x8_t a1 = vld1_u8(&Input[InputStride * 1]);
uint8x8x2_t b0 = vzip_u8(a0, a1);
uint8x8_t a2 = vld1_u8(&Input[InputStride * 2]);
uint8x8_t a3 = vld1_u8(&Input[InputStride * 3]);
uint8x8x2_t b1 = vzip_u8(a2, a3);
uint8x8_t a4 = vld1_u8(&Input[InputStride * 4]);
uint8x8_t a5 = vld1_u8(&Input[InputStride * 5]);
uint8x8x2_t b2 = vzip_u8(a4, a5);
uint8x8_t a6 = vld1_u8(&Input[InputStride * 6]);
uint8x8_t a7 = vld1_u8(&Input[InputStride * 7]);
uint8x8x2_t b3 = vzip_u8(a6, a7);
uint16x4x2_t c0 = vzip_u16(vreinterpret_u16_u8(b0.val[0]), vreinterpret_u16_u8(b1.val[0]));
uint16x4x2_t c1 = vzip_u16(vreinterpret_u16_u8(b0.val[1]), vreinterpret_u16_u8(b1.val[1]));
uint16x4x2_t c2 = vzip_u16(vreinterpret_u16_u8(b2.val[0]), vreinterpret_u16_u8(b3.val[0]));
uint16x4x2_t c3 = vzip_u16(vreinterpret_u16_u8(b2.val[1]), vreinterpret_u16_u8(b3.val[1]));
uint32x2x2_t d0 = vzip_u32(vreinterpret_u32_u16(c0.val[0]), vreinterpret_u32_u16(c2.val[0]));
uint32x2x2_t d1 = vzip_u32(vreinterpret_u32_u16(c0.val[1]), vreinterpret_u32_u16(c2.val[1]));
uint32x2x2_t d2 = vzip_u32(vreinterpret_u32_u16(c1.val[0]), vreinterpret_u32_u16(c3.val[0]));
uint32x2x2_t d3 = vzip_u32(vreinterpret_u32_u16(c1.val[1]), vreinterpret_u32_u16(c3.val[1]));
vst1_u8(&Output[OutputStride * 0], vreinterpret_u8_u32(d0.val[0]));
vst1_u8(&Output[OutputStride * 1], vreinterpret_u8_u32(d0.val[1]));
vst1_u8(&Output[OutputStride * 2], vreinterpret_u8_u32(d1.val[0]));
vst1_u8(&Output[OutputStride * 3], vreinterpret_u8_u32(d1.val[1]));
vst1_u8(&Output[OutputStride * 4], vreinterpret_u8_u32(d2.val[0]));
vst1_u8(&Output[OutputStride * 5], vreinterpret_u8_u32(d2.val[1]));
vst1_u8(&Output[OutputStride * 6], vreinterpret_u8_u32(d3.val[0]));
vst1_u8(&Output[OutputStride * 7], vreinterpret_u8_u32(d3.val[1]));
#endif
}
#endif
MLAS_FORCEINLINE
void
MlasTranspose8xNVector(
const uint8_t* Input,
size_t InputStride,
uint8_t* Output,
size_t OutputStride
)
/*++
Routine Description:
This routine transposes an eight element vector from the input matrix
to the output matrix.
Arguments:
Input - Supplies the input buffer.
InputStride - Supplies the number of elements between rows of the input
matrix.
Output - Supplies the output buffer.
OutputStride - Supplies the number of elements between rows of the output
matrix.
Return Value:
None.
--*/
{
uint8_t a0 = Input[InputStride * 0];
uint8_t a1 = Input[InputStride * 1];
uint8_t a2 = Input[InputStride * 2];
uint8_t a3 = Input[InputStride * 3];
Output[OutputStride * 0] = a0;
Output[OutputStride * 1] = a1;
Output[OutputStride * 2] = a2;
Output[OutputStride * 3] = a3;
uint8_t a4 = Input[InputStride * 4];
uint8_t a5 = Input[InputStride * 5];
uint8_t a6 = Input[InputStride * 6];
uint8_t a7 = Input[InputStride * 7];
Output[OutputStride * 4] = a4;
Output[OutputStride * 5] = a5;
Output[OutputStride * 6] = a6;
Output[OutputStride * 7] = a7;
}
void
MLASCALL
@ -64,60 +234,22 @@ Return Value:
uint8_t* d = Output;
size_t m = M;
#if defined(MLAS_SSE2_INTRINSICS) || defined(MLAS_NEON_INTRINSICS)
while (m >= 8) {
__m128i a0 = _mm_loadl_epi64((const __m128i*)&s[N * 0]);
__m128i a1 = _mm_loadl_epi64((const __m128i*)&s[N * 1]);
__m128i b0 = _mm_unpacklo_epi8(a0, a1);
__m128i a2 = _mm_loadl_epi64((const __m128i*)&s[N * 2]);
__m128i a3 = _mm_loadl_epi64((const __m128i*)&s[N * 3]);
__m128i b1 = _mm_unpacklo_epi8(a2, a3);
__m128i a4 = _mm_loadl_epi64((const __m128i*)&s[N * 4]);
__m128i a5 = _mm_loadl_epi64((const __m128i*)&s[N * 5]);
__m128i b2 = _mm_unpacklo_epi8(a4, a5);
__m128i a6 = _mm_loadl_epi64((const __m128i*)&s[N * 6]);
__m128i a7 = _mm_loadl_epi64((const __m128i*)&s[N * 7]);
__m128i b3 = _mm_unpacklo_epi8(a6, a7);
__m128i c0 = _mm_unpacklo_epi16(b0, b1);
__m128i c1 = _mm_unpackhi_epi16(b0, b1);
__m128i c2 = _mm_unpacklo_epi16(b2, b3);
__m128i c3 = _mm_unpackhi_epi16(b2, b3);
__m128 d0 = _mm_castsi128_ps(_mm_unpacklo_epi32(c0, c2));
_mm_storel_pi((__m64*)&d[M * 0], d0);
_mm_storeh_pi((__m64*)&d[M * 1], d0);
__m128 d1 = _mm_castsi128_ps(_mm_unpackhi_epi32(c0, c2));
_mm_storel_pi((__m64*)&d[M * 2], d1);
_mm_storeh_pi((__m64*)&d[M * 3], d1);
__m128 d2 = _mm_castsi128_ps(_mm_unpacklo_epi32(c1, c3));
_mm_storel_pi((__m64*)&d[M * 4], d2);
_mm_storeh_pi((__m64*)&d[M * 5], d2);
__m128 d3 = _mm_castsi128_ps(_mm_unpackhi_epi32(c1, c3));
_mm_storel_pi((__m64*)&d[M * 6], d3);
_mm_storeh_pi((__m64*)&d[M * 7], d3);
MlasTranspose8x8Block(s, N, d, M);
s += N * 8;
d += 8;
m -= 8;
}
#endif
while (m > 0) {
d[M * 0] = s[0];
d[M * 1] = s[1];
d[M * 2] = s[2];
d[M * 3] = s[3];
d[M * 4] = s[4];
d[M * 5] = s[5];
d[M * 6] = s[6];
d[M * 7] = s[7];
MlasTranspose8xNVector(s, 1, d, M);
s += N;
d += 1;
@ -142,14 +274,7 @@ Return Value:
while (m >= 8) {
d[0] = s[N * 0];
d[1] = s[N * 1];
d[2] = s[N * 2];
d[3] = s[N * 3];
d[4] = s[N * 4];
d[5] = s[N * 5];
d[6] = s[N * 6];
d[7] = s[N * 7];
MlasTranspose8xNVector(s, N, d, 1);
s += N * 8;
d += 8;
@ -170,5 +295,3 @@ Return Value:
n -= 1;
}
}
#endif

View file

@ -168,7 +168,7 @@ std::vector<std::unique_ptr<GraphTransformer>> GenerateTransformers(TransformerL
transformers.emplace_back(onnxruntime::make_unique<NchwcTransformer>());
}
#if defined(MLAS_TARGET_AMD64_IX86)
#ifdef MLAS_SUPPORTS_GEMM_U8X8
transformers.emplace_back(onnxruntime::make_unique<NhwcTransformer>());
#endif
#endif

View file

@ -70,7 +70,7 @@ Status QLinearMatMul::Compute(OpKernelContext* ctx) const {
const float real_multiplier = (a_scale_data * b_scale_data) / y_scale_data;
#ifdef MLAS_SUPPORTS_GEMM_U8X8_AND_REQUANTIZE_OUTPUT
#ifdef MLAS_SUPPORTS_GEMM_U8X8
AllocatorPtr alloc;
ORT_RETURN_IF_ERROR(ctx->GetTempSpaceAllocator(&alloc));
auto gemm_output_data = alloc->Alloc(SafeInt<size_t>(sizeof(int32_t)) *
@ -85,7 +85,7 @@ Status QLinearMatMul::Compute(OpKernelContext* ctx) const {
#endif
for (size_t i = 0; i < helper.OutputOffsets().size(); i++) {
#ifdef MLAS_SUPPORTS_GEMM_U8X8_AND_REQUANTIZE_OUTPUT
#ifdef MLAS_SUPPORTS_GEMM_U8X8
QGemm(static_cast<int>(helper.M()),
static_cast<int>(helper.N()),
static_cast<int>(helper.K()),
@ -105,7 +105,8 @@ Status QLinearMatMul::Compute(OpKernelContext* ctx) const {
nullptr,
static_cast<size_t>(helper.M()),
static_cast<size_t>(helper.N()),
real_multiplier,
&real_multiplier,
false,
*y_offset->template Data<uint8_t>());
#else
GemmlowpMultiplyu8u8_u8(a->template Data<uint8_t>() + helper.LeftOffsets()[i],

View file

@ -13,7 +13,7 @@
namespace onnxruntime {
#if defined(MLAS_TARGET_AMD64_IX86)
#ifdef MLAS_SUPPORTS_GEMM_U8X8
class QLinearConv : public OpKernel {
public:
@ -69,6 +69,8 @@ ONNX_CPU_OPERATOR_KERNEL(
namespace contrib {
// Register an alternate version of this kernel that supports the channels_last
// attribute in order to consume and produce NHWC tensors.
ONNX_OPERATOR_KERNEL_EX(
QLinearConv,
kMSDomain,
@ -525,23 +527,14 @@ Status QLinearConv::Compute(OpKernelContext* context) const {
}
}
if (output_scales.size() == 1) {
MlasRequantizeOutputColumn(worker_gemm_output,
worker_requantize_output,
Bdata,
static_cast<size_t>(output_count),
static_cast<size_t>(M),
output_scales[0],
Y_zero_point_value);
} else {
MlasRequantizeOutputColumn(worker_gemm_output,
worker_requantize_output,
Bdata,
static_cast<size_t>(output_count),
static_cast<size_t>(M),
output_scales.data(),
Y_zero_point_value);
}
MlasRequantizeOutput(worker_gemm_output,
worker_requantize_output,
Bdata,
static_cast<size_t>(output_count),
static_cast<size_t>(M),
output_scales.data(),
output_scales.size() > 1,
Y_zero_point_value);
};
concurrency::ThreadPool::TrySimpleParallelFor(thread_pool, thread_count, conv_worker);
@ -681,18 +674,10 @@ Status QLinearConv::Compute(OpKernelContext* context) const {
const float real_multiplier = (X_scale_value * W_scale_value) / Y_scale_value;
#ifdef MLAS_SUPPORTS_GEMM_U8X8_AND_REQUANTIZE_OUTPUT
// Use an intermediate int32_t buffer for the GEMM computation before
// requantizing to the output type.
auto* gemm_output_data = alloc->Alloc(SafeInt<size_t>(sizeof(int32_t)) * Y_offset);
BufferUniquePtr gemm_output_buffer(gemm_output_data, BufferDeleter(alloc));
auto* gemm_output = static_cast<int32_t*>(gemm_output_buffer.get());
#else
// Compute the fixed point multiplier and shift for requantizing with GEMMLOWP.
int32_t integer_multiplier;
int right_shift;
QuantizeMultiplier(real_multiplier, &integer_multiplier, &right_shift);
#endif
const auto* Xdata = X->template Data<uint8_t>();
const auto* Wdata = W->template Data<uint8_t>();
@ -737,29 +722,6 @@ Status QLinearConv::Compute(OpKernelContext* context) const {
}
}
#ifdef MLAS_SUPPORTS_GEMM_U8X8_AND_REQUANTIZE_OUTPUT
QGemm(static_cast<int>(group_output_channels),
static_cast<int>(output_image_size),
static_cast<int>(kernel_dim),
Wdata + group_id * W_offset,
static_cast<int>(kernel_dim),
W_zero_point_value,
col_buffer_data == nullptr ? Xdata : col_buffer_data,
static_cast<int>(output_image_size),
X_zero_point_value,
false,
gemm_output,
static_cast<int>(output_image_size),
context->GetOperatorThreadPool());
MlasRequantizeOutput(gemm_output,
Ydata,
Bdata != nullptr ? Bdata + group_id * group_output_channels : nullptr,
static_cast<size_t>(group_output_channels),
static_cast<size_t>(output_image_size),
real_multiplier,
Y_zero_point_value);
#else
GemmlowpMultiplyu8u8_u8(Wdata + group_id * W_offset,
col_buffer_data == nullptr ? Xdata : col_buffer_data,
Ydata,
@ -772,7 +734,6 @@ Status QLinearConv::Compute(OpKernelContext* context) const {
integer_multiplier,
right_shift,
Bdata != nullptr ? Bdata + group_id * group_output_channels : nullptr);
#endif
Xdata += X_offset;
Ydata += Y_offset;

View file

@ -301,8 +301,6 @@ static void SimpleTransposeSingleAxisOutwards(const T* input_data, T* output_dat
}
}
#ifdef MLAS_SUPPORTS_TRANSPOSE
static void SimpleTransposeSingleAxisOutwards(const uint8_t* input_data, uint8_t* output_data,
int64_t num_loops, int64_t num_writers,
int64_t writes_per_loop, int64_t writes_per_writer_per_loop) {
@ -316,8 +314,6 @@ static void SimpleTransposeSingleAxisOutwards(const uint8_t* input_data, uint8_t
}
}
#endif
// `input_shape_override` overrides the shape of `input` for compute purposes.
static void TransposeSingleAxisOutwards(const std::vector<size_t>& permutations, const Tensor& input, Tensor& output,
int64_t from, int64_t to, const TensorShape* input_shape_override = nullptr) {
@ -411,8 +407,6 @@ static void SimpleTransposeSingleAxisInwards(const T* input_data, T* output_data
}
}
#ifdef MLAS_SUPPORTS_TRANSPOSE
static void SimpleTransposeSingleAxisInwards(const uint8_t* input_data, uint8_t* output_data,
int64_t num_loops, int64_t num_readers,
int64_t reads_per_loop, int64_t reads_per_reader_per_loop) {
@ -426,8 +420,6 @@ static void SimpleTransposeSingleAxisInwards(const uint8_t* input_data, uint8_t*
}
}
#endif
// moving a single axis inwards where the read/write size is a power of 2 and between 8 and 64 bits.
// `input_shape_override` overrides the shape of `input` for compute purposes.
static void TransposeSingleAxisInwards(const std::vector<size_t>& permutations, const Tensor& input, Tensor& output,

View file

@ -297,7 +297,7 @@ void NhwcTransformerTester(const std::function<void(NhwcTestHelper& helper)>& bu
#ifndef DISABLE_CONTRIB_OPS
#if defined(MLAS_TARGET_AMD64_IX86)
#ifdef MLAS_SUPPORTS_GEMM_U8X8
TEST(NhwcTransformerTests, Conv) {
auto test_case = [&](const std::vector<int64_t>& input_shape, const std::vector<int64_t>& weights_shape) {

View file

@ -260,7 +260,7 @@ TEST(QLinearConvTest, WithGroup_2D) {
{});
}
#if defined(MLAS_TARGET_AMD64_IX86)
#ifdef MLAS_SUPPORTS_GEMM_U8X8
template <typename T1, typename T2>
class QLinearConvOpTester {
@ -764,6 +764,44 @@ TEST(QLinearConvTest, Conv3D_U8S8_Depthwise) {
test.Run();
}
TEST(QLinearConvTest, Conv2D_U8S8_Requantize_NoBias) {
for (int64_t channels = 1; channels <= 32; channels++) {
QLinearConvOpTester<uint8_t, int8_t> test;
test.GenerateRandomInput({1, 8, 5, 5}, .05f, 4);
test.GenerateRandomWeights({channels, 8, 3, 3}, .125f, 0);
test.SetPads({1, 1, 1, 1});
test.SetOutputScaleAndZeroPoint(.55f, 56);
test.Run();
}
}
TEST(QLinearConvTest, Conv2D_U8S8_Requantize_Bias) {
for (int64_t channels = 1; channels <= 32; channels++) {
QLinearConvOpTester<uint8_t, int8_t> test;
test.GenerateRandomInput({1, 8, 5, 5}, .05f, 4);
test.GenerateRandomWeights({channels, 8, 3, 3}, .125f, 0);
test.GenerateRandomBias();
test.SetPads({1, 1, 1, 1});
test.SetOutputScaleAndZeroPoint(.55f, 56);
test.Run();
}
}
TEST(QLinearConvTest, Conv2D_U8S8_Requantize_Bias_PerChannel) {
std::vector<float> weight_scales;
for (int64_t channels = 1; channels <= 32; channels++) {
QLinearConvOpTester<uint8_t, int8_t> test;
test.GenerateRandomInput({1, 8, 5, 5}, .05f, 4);
test.GenerateRandomWeights({channels, 8, 3, 3}, .125f, 0);
weight_scales.push_back(.120f + .002f * static_cast<float>(channels));
test.SetWeightScales(weight_scales);
test.GenerateRandomBias();
test.SetPads({1, 1, 1, 1});
test.SetOutputScaleAndZeroPoint(.55f, 56);
test.Run();
}
}
#endif
} // namespace