/*++ Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. Module Name: convsym.cpp Abstract: This module implements the symmetric quantized integer convolution operation. --*/ #include "mlasi.h" // // Define the prototypes of the platform optimized routines. // typedef void (MLASCALL MLAS_CONV_SYM_KERNEL)( const void* Input, const void* Filter, void* Output, size_t KernelSize, size_t InputChannels, size_t OutputChannels, unsigned ChannelCount, unsigned OutputCount, const struct MLAS_CONV_SYM_POST_PROCESS_PARAMS* PostProcessParams, unsigned KernelFlags ); typedef void (MLASCALL MLAS_CONV_SYM_DEPTHWISE_KERNEL)( const void* Input, const void* Filter, void* Output, size_t KernelSize, size_t Channels, size_t ChannelOffset, unsigned ChannelCount, unsigned OutputCount, const struct MLAS_CONV_SYM_POST_PROCESS_PARAMS* PostProcessParams, unsigned KernelFlags ); // // Processor for common kernel sized (e.g. 3x3, 5x5) // typedef void (MLASCALL MLAS_SYMM_QCONV_DEPTHWISE_FIXFILTER_PROC)( void const* const* InputIndirection, int8_t const* Filter, size_t Channels, void* Output, size_t OutputCount, MLAS_CONV_SYM_POST_PROCESS_PARAMS const* PostProcessParams, unsigned KernelFlags ); extern "C" { #if defined(MLAS_TARGET_AMD64) MLAS_CONV_SYM_KERNEL MlasConvSymKernelAvx2; MLAS_CONV_SYM_DEPTHWISE_KERNEL MlasConvSymDepthwiseKernelAvx2; MLAS_CONV_SYM_KERNEL MlasConvSymKernelAvxVnni; MLAS_CONV_SYM_DEPTHWISE_KERNEL MlasConvSymDepthwiseKernelAvxVnni; MLAS_CONV_SYM_KERNEL MlasConvSymKernelAvx512Core; MLAS_CONV_SYM_DEPTHWISE_KERNEL MlasConvSymDepthwiseKernelAvx512Core; MLAS_CONV_SYM_KERNEL MlasConvSymKernelAvx512Vnni; MLAS_CONV_SYM_DEPTHWISE_KERNEL MlasConvSymDepthwiseKernelAvx512Vnni; #elif defined(MLAS_TARGET_ARM64) MLAS_CONV_SYM_KERNEL MlasConvSymS8KernelNeon; MLAS_CONV_SYM_KERNEL MlasConvSymU8KernelNeon; MLAS_CONV_SYM_KERNEL MlasConvSymS8KernelDot; MLAS_CONV_SYM_KERNEL MlasConvSymS8KernelDotLd64; MLAS_CONV_SYM_KERNEL MlasConvSymU8KernelDot; MLAS_CONV_SYM_DEPTHWISE_KERNEL MlasConvSymDepthwiseU8KernelNeon; MLAS_CONV_SYM_DEPTHWISE_KERNEL MlasConvSymDepthwiseS8KernelNeon; // // Specialized depthwise conv kernels for 3x3 and 5x5 filters // void MLASCALL MlasConvSymDepthwiseKernelSize9Arm64U8S8( void const* const* InputIndirection, int8_t const* Filter, size_t Channels, void* Output, size_t OutputCount, MLAS_CONV_SYM_POST_PROCESS_PARAMS const* PostProcessParams, unsigned KernelFlags ); void MLASCALL MlasConvSymDepthwiseKernelSize9Arm64S8S8( void const* const* InputIndirection, int8_t const* Filter, size_t Channels, void* Output, size_t OutputCount, MLAS_CONV_SYM_POST_PROCESS_PARAMS const* PostProcessParams, unsigned KernelFlags ); void MLASCALL MlasConvSymDepthwiseKernelSize25ArmS8S8( void const* const* InputIndirection, int8_t const* Filter, size_t Channels, void* Output, size_t OutputCount, MLAS_CONV_SYM_POST_PROCESS_PARAMS const* PostProcessParams, unsigned KernelFlags ); void MLASCALL MlasConvSymDepthwiseKernelSize25ArmU8S8( void const* const* InputIndirection, int8_t const* Filter, size_t Channels, void* Output, size_t OutputCount, MLAS_CONV_SYM_POST_PROCESS_PARAMS const* PostProcessParams, unsigned KernelFlags ); #endif } struct MLAS_CONV_SYM_DISPATCH { MLAS_CONV_SYM_KERNEL* Kernel; #if defined(MLAS_TARGET_ARM64) MLAS_CONV_SYM_KERNEL* KernelLittle; // kernel for little core #endif MLAS_CONV_SYM_DEPTHWISE_KERNEL* DepthwiseKernel; MLAS_SYMM_QCONV_DEPTHWISE_FIXFILTER_PROC* Depthwise3x3Proc; MLAS_SYMM_QCONV_DEPTHWISE_FIXFILTER_PROC* Depthwise5x5Proc; uint8_t FilterInputChannelPackCount; uint8_t FilterOutputChannelPackCount; uint8_t KernelChannelCount; uint8_t KernelOutputCount; uint8_t KernelInputChannelAlignment; uint8_t KernelOutputChannelAlignment; uint8_t KernelDepthwiseChannelCount; uint8_t KernelDepthwiseOutputCount; bool FixupInputZeroPoint; }; #if defined(MLAS_TARGET_AMD64) const MLAS_CONV_SYM_DISPATCH MlasConvSymDispatchAvx2 = { MlasConvSymKernelAvx2, MlasConvSymDepthwiseKernelAvx2, nullptr, nullptr, 4, // FilterInputChannelPackCount 16, // FilterOutputChannelPackCount 16, // KernelChannelCount 4, // KernelOutputCount 4, // KernelInputChannelAlignment 8, // KernelOutputChannelAlignment 16, // KernelDepthwiseChannelCount 4, // KernelDepthwiseOutputCount false, // FixupInputZeroPoint }; const MLAS_CONV_SYM_DISPATCH MlasConvSymDispatchAvxVnni = { MlasConvSymKernelAvxVnni, MlasConvSymDepthwiseKernelAvxVnni, nullptr, nullptr, 4, // FilterInputChannelPackCount 16, // FilterOutputChannelPackCount 16, // KernelChannelCount 6, // KernelOutputCount 4, // KernelInputChannelAlignment 8, // KernelOutputChannelAlignment 16, // KernelDepthwiseChannelCount 4, // KernelDepthwiseOutputCount false, // FixupInputZeroPoint }; #if !defined(ORT_MINIMAL_BUILD) const MLAS_CONV_SYM_DISPATCH MlasConvSymDispatchAvx512Core = { MlasConvSymKernelAvx512Core, MlasConvSymDepthwiseKernelAvx512Core, nullptr, nullptr, 4, // FilterInputChannelPackCount 16, // FilterOutputChannelPackCount 64, // KernelChannelCount 6, // KernelOutputCount 4, // KernelInputChannelAlignment 4, // KernelOutputChannelAlignment 64, // KernelDepthwiseChannelCount 6, // KernelDepthwiseOutputCount false, // FixupInputZeroPoint }; const MLAS_CONV_SYM_DISPATCH MlasConvSymDispatchAvx512Vnni = { MlasConvSymKernelAvx512Vnni, MlasConvSymDepthwiseKernelAvx512Vnni, nullptr, nullptr, 4, // FilterInputChannelPackCount 16, // FilterOutputChannelPackCount 64, // KernelChannelCount 6, // KernelOutputCount 4, // KernelInputChannelAlignment 4, // KernelOutputChannelAlignment 64, // KernelDepthwiseChannelCount 6, // KernelDepthwiseOutputCount false, // FixupInputZeroPoint }; #endif // ORT_MINIMAL_BUILD #elif defined(MLAS_TARGET_ARM64) const MLAS_CONV_SYM_DISPATCH MlasConvSymU8DispatchNeon = { MlasConvSymU8KernelNeon, MlasConvSymU8KernelNeon, MlasConvSymDepthwiseU8KernelNeon, MlasConvSymDepthwiseKernelSize9Arm64U8S8, MlasConvSymDepthwiseKernelSize25ArmU8S8, 8, // FilterInputChannelPackCount 8, // FilterOutputChannelPackCount 8, // KernelChannelCount 2, // KernelOutputCount 8, // KernelInputChannelAlignment 8, // KernelOutputChannelAlignment 16, // KernelDepthwiseChannelCount 4, // KernelDepthwiseOutputCount true }; const MLAS_CONV_SYM_DISPATCH MlasConvSymS8DispatchNeon = { MlasConvSymS8KernelNeon, MlasConvSymS8KernelNeon, MlasConvSymDepthwiseS8KernelNeon, MlasConvSymDepthwiseKernelSize9Arm64S8S8, MlasConvSymDepthwiseKernelSize25ArmS8S8, 8, // FilterInputChannelPackCount 8, // FilterOutputChannelPackCount 8, // KernelChannelCount 2, // KernelOutputCount 8, // KernelInputChannelAlignment 8, // KernelOutputChannelAlignment 16, // KernelDepthwiseChannelCount 4, // KernelDepthwiseOutputCount false }; const MLAS_CONV_SYM_DISPATCH MlasConvSymU8DispatchDot = { MlasConvSymU8KernelDot, MlasConvSymU8KernelDot, MlasConvSymDepthwiseU8KernelNeon, MlasConvSymDepthwiseKernelSize9Arm64U8S8, MlasConvSymDepthwiseKernelSize25ArmU8S8, 4, // FilterInputChannelPackCount 16, // FilterOutputChannelPackCount 0, // KernelChannelCount 4, // KernelOutputCount 4, // KernelInputChannelAlignment 16, // KernelOutputChannelAlignment 16, // KernelDepthwiseChannelCount 4, // KernelDepthwiseOutputCount true }; const MLAS_CONV_SYM_DISPATCH MlasConvSymS8DispatchDot = { MlasConvSymS8KernelDot, MlasConvSymS8KernelDotLd64, MlasConvSymDepthwiseS8KernelNeon, MlasConvSymDepthwiseKernelSize9Arm64S8S8, MlasConvSymDepthwiseKernelSize25ArmS8S8, 4, // FilterInputChannelPackCount 16, // FilterOutputChannelPackCount 0, // KernelChannelCount 4, // KernelOutputCount 4, // KernelInputChannelAlignment 16, // KernelOutputChannelAlignment 16, // KernelDepthwiseChannelCount 4, // KernelDepthwiseOutputCount false }; #endif // MLAS_TARGET_AMD64 MLAS_FORCEINLINE void MlasConvSymSetOutputZeroPoint( MLAS_CONV_SYM_POST_PROCESS_PARAMS& PostProcessParams, int32_t OutputZeroPoint, bool InputIsSigned ) { int32_t minimum = InputIsSigned ? std::numeric_limits::lowest() : std::numeric_limits::lowest(); int32_t maximum = InputIsSigned ? std::numeric_limits::max() : std::numeric_limits::max(); PostProcessParams.MinimumValue = static_cast(minimum - OutputZeroPoint); PostProcessParams.MaximumValue = static_cast(maximum - OutputZeroPoint); PostProcessParams.OutputZeroPoint = OutputZeroPoint; } MLAS_FORCEINLINE const MLAS_CONV_SYM_DISPATCH* GetConvSymDispatch(bool InputIsSigned){ return InputIsSigned ? GetMlasPlatform().ConvSymS8S8Dispatch : GetMlasPlatform().ConvSymU8S8Dispatch; } size_t MlasConvSymPackWSize( size_t GroupCount, size_t InputChannels, size_t OutputChannels, size_t KernelSize, bool InputIsSigned ) { const MLAS_CONV_SYM_DISPATCH* ConvSymDispatch = GetConvSymDispatch(InputIsSigned); if (ConvSymDispatch == nullptr) { return 0; } if (GroupCount > 1) { if (ConvSymDispatch->DepthwiseKernel != nullptr && InputChannels == 1 && OutputChannels == 1) { #ifdef MLAS_TARGET_ARM64 constexpr size_t GroupAlign = 8; #else constexpr size_t GroupAlign = 16; #endif size_t AlignedGroupCount = (GroupCount + GroupAlign - 1) & ~(GroupAlign - 1); if (AlignedGroupCount != GroupCount) { return 0; } return AlignedGroupCount * KernelSize; } else { return 0; } } else { #ifdef MLAS_TARGET_ARM64 if (KernelSize <= 1) { // im2col not needed, indirected buffer not needed // just use qgemm path for pointwise return 0; } if (InputChannels < 64) { // Shallow indirect conv runs slower. // TODO!! remove this for functional testing! // TODO!! is there a way to know whether this is called by tests? return 0; } #endif size_t OutputChannelPackCount = ConvSymDispatch->FilterOutputChannelPackCount; if (ConvSymDispatch->Kernel == nullptr || OutputChannels < OutputChannelPackCount || (InputChannels % ConvSymDispatch->KernelInputChannelAlignment) != 0 || (OutputChannels % ConvSymDispatch->KernelOutputChannelAlignment) != 0 ) { return 0; } size_t AlignedOutputChannels = (OutputChannels + OutputChannelPackCount - 1) / OutputChannelPackCount * OutputChannelPackCount; return AlignedOutputChannels * InputChannels * KernelSize; } } void MlasConvSymPackW( size_t GroupCount, size_t InputChannels, size_t OutputChannels, size_t KernelSize, const int8_t* W, int8_t* PackedW, size_t PackedWSize, bool InputIsSigned ) { memset(PackedW, 0, PackedWSize); if (GroupCount > 1) { for (size_t gc = 0; gc < GroupCount; gc++) { for (size_t k = 0; k < KernelSize; k++) { PackedW[k * GroupCount + gc] = W[gc * KernelSize + k]; } } } else { const MLAS_CONV_SYM_DISPATCH* ConvSymDispatch = GetConvSymDispatch(InputIsSigned); size_t InputChannelPackCount = ConvSymDispatch->FilterInputChannelPackCount; size_t OutputChannelPackCount = ConvSymDispatch->FilterOutputChannelPackCount; size_t kernel_dim = InputChannels * KernelSize; for (size_t oc = 0; oc < OutputChannels; oc += OutputChannelPackCount) { const size_t oc_pack_size = std::min(OutputChannels - oc, OutputChannelPackCount); for (size_t ki = 0; ki < KernelSize; ki++) { for (size_t ic = 0; ic < InputChannels; ic += InputChannelPackCount) { const size_t ic_pack_size = std::min(InputChannels - ic, InputChannelPackCount); for (size_t oc_pack = 0; oc_pack < oc_pack_size; oc_pack++) { for (size_t ic_pack = 0; ic_pack < ic_pack_size; ic_pack++) { *(PackedW++) = W[(oc + oc_pack) * kernel_dim + (ic + ic_pack) * KernelSize + ki]; } PackedW += InputChannelPackCount - ic_pack_size; } PackedW += (OutputChannelPackCount - oc_pack_size) * InputChannelPackCount; } } } } } int32_t MlasConvSymFixupInputZeroPoint( int32_t zero_point_value, bool InputIsSigned ) { const MLAS_CONV_SYM_DISPATCH* ConvSymDispatch = GetConvSymDispatch(InputIsSigned); if (ConvSymDispatch != nullptr && ConvSymDispatch->FixupInputZeroPoint) { return zero_point_value - 128; } return zero_point_value; } int32_t MlasConvSymGetKernelOutputCount( bool InputIsSigned ) { const MLAS_CONV_SYM_DISPATCH* ConvSymDispatch = GetConvSymDispatch(InputIsSigned); return ConvSymDispatch->KernelOutputCount; } int32_t MlasConvSymDepthwiseGetKernelOutputCnt( bool InputIsSigned ) { const MLAS_CONV_SYM_DISPATCH* ConvSymDispatch = GetConvSymDispatch(InputIsSigned); return ConvSymDispatch->KernelDepthwiseOutputCount; } void MlasConvSym( const MLAS_CONV_SYM_PARAMS& Params ) { const MLAS_CONV_SYM_DISPATCH* ConvSymDispatch = GetConvSymDispatch(Params.InputIsSigned); // Pick the suitable kernel for current core. Currently we only have specialized core for // s8s8 under ARM64 #if defined(MLAS_TARGET_ARM64) const auto Kernel = (Params.InputIsSigned && MLAS_CPUIDINFO::GetCPUIDInfo().IsCurrentCoreArmv8NarrowLd()) ? ConvSymDispatch->KernelLittle : ConvSymDispatch->Kernel; #else const auto Kernel = ConvSymDispatch->Kernel; #endif int32_t KernelFlags = 0; if (Params.PerChannelScale) { KernelFlags |= MLAS_CONV_SYM_FLAG_PER_CHANNEL_SCALE; } if (Params.InputIndirection == nullptr) { KernelFlags |= MLAS_CONV_SYM_FLAG_INPUT_DIRECT; } MLAS_CONV_SYM_POST_PROCESS_PARAMS PostProcessParams = {}; MlasConvSymSetOutputZeroPoint(PostProcessParams, Params.OutputZeroPoint, Params.InputIsSigned); const size_t KernelChannelCount = (ConvSymDispatch->KernelChannelCount == 0) ? std::numeric_limits::max() : ConvSymDispatch->KernelChannelCount; const size_t KernelOutputCount = ConvSymDispatch->KernelOutputCount; const size_t KernelSize = Params.KernelSize; const size_t InputChannels = Params.InputChannels; const size_t OutputChannels = Params.OutputChannels; for (size_t oc_outside = 0; oc_outside < Params.OutputCount;) { const size_t oc_outside_block_size = std::min(Params.OutputCount - oc_outside, 240); const int8_t* pwb = static_cast(Params.Filter); for (size_t co = 0; co < OutputChannels;) { const size_t ChannelCount = std::min(OutputChannels - co, KernelChannelCount); void* conv_out = static_cast(Params.Output) + (oc_outside * OutputChannels) + co; PostProcessParams.Bias = Params.Bias + co; PostProcessParams.Scale = Params.Scale + (Params.PerChannelScale ? co : 0); for (size_t oc = 0; oc < oc_outside_block_size;) { const void* Input; if (Params.InputIndirection) { Input = Params.InputIndirection + (oc_outside + oc) * KernelSize; } else { Input = static_cast(Params.InputDirect) + (oc_outside + oc) * InputChannels; } size_t OutputCount = std::min(oc_outside_block_size - oc, KernelOutputCount); Kernel( Input, pwb, conv_out, KernelSize, InputChannels, OutputChannels, static_cast(ChannelCount), static_cast(OutputCount), &PostProcessParams, KernelFlags); oc += OutputCount; conv_out = static_cast(conv_out) + OutputCount * OutputChannels; } co += ChannelCount; pwb += ChannelCount * InputChannels * KernelSize; } oc_outside += oc_outside_block_size; } } void MlasConvSymDepthwise( const MLAS_CONV_SYM_PARAMS& Params ) { const MLAS_CONV_SYM_DISPATCH* ConvSymDispatch = GetConvSymDispatch(Params.InputIsSigned); unsigned KernelFlags = 0; if (Params.PerChannelScale) { KernelFlags |= MLAS_CONV_SYM_FLAG_PER_CHANNEL_SCALE; } MLAS_CONV_SYM_POST_PROCESS_PARAMS PostProcessParams = {}; MlasConvSymSetOutputZeroPoint(PostProcessParams, Params.OutputZeroPoint, Params.InputIsSigned); if ((Params.OutputChannels & 15) == 0) { PostProcessParams.Bias = Params.Bias; PostProcessParams.Scale = Params.Scale; if (ConvSymDispatch->Depthwise3x3Proc && Params.KernelSize == 9) { ConvSymDispatch->Depthwise3x3Proc(Params.InputIndirection, (int8_t const*)Params.Filter, Params.OutputChannels, Params.Output, Params.OutputCount, &PostProcessParams, KernelFlags); return; } if (ConvSymDispatch->Depthwise5x5Proc && Params.KernelSize == 25) { ConvSymDispatch->Depthwise5x5Proc(Params.InputIndirection, (int8_t const*)Params.Filter, Params.OutputChannels, Params.Output, Params.OutputCount, &PostProcessParams, KernelFlags); return; } } const size_t KernelChannelCount = ConvSymDispatch->KernelDepthwiseChannelCount; const size_t KernelOutputCount = ConvSymDispatch->KernelDepthwiseOutputCount; const size_t KernelSize = Params.KernelSize; const size_t OutputChannels = Params.OutputChannels; const auto* InputIndirection = Params.InputIndirection; void* Output = Params.Output; for (size_t OutputCountRemaining = Params.OutputCount; OutputCountRemaining > 0;) { const size_t OutputCount = std::min(OutputCountRemaining, KernelOutputCount); for (size_t ChannelOffset = 0; ChannelOffset < OutputChannels;) { const size_t ChannelCount = std::min(OutputChannels - ChannelOffset, KernelChannelCount); PostProcessParams.Bias = Params.Bias + ChannelOffset; PostProcessParams.Scale = Params.Scale + (Params.PerChannelScale ? ChannelOffset : 0); ConvSymDispatch->DepthwiseKernel( InputIndirection, static_cast(Params.Filter) + ChannelOffset, static_cast(Output) + ChannelOffset, KernelSize, OutputChannels, ChannelOffset, static_cast(ChannelCount), static_cast(OutputCount), &PostProcessParams, KernelFlags); ChannelOffset += ChannelCount; } InputIndirection += OutputCount * KernelSize; Output = static_cast(Output) + OutputCount * OutputChannels; OutputCountRemaining -= OutputCount; } }