/*++ Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. Module Name: qlgavgpool.cpp Abstract: This module implements routines for quantized linear global average pool. --*/ #include "mlasi.h" size_t MLASCALL MlasQLinearSafePaddingElementCount( size_t ElementSize, size_t ElementCount ) { if (!(ElementSize == 1 || ElementSize == 2 || ElementSize == 4 || ElementSize == 8 || ElementSize == 16)) { MLAS_THROW_EX(std::invalid_argument, "ElementSize must be power of 2 and less or equal than 16!"); } return ElementCount + (size_t{256} / ElementSize - 1); } MLAS_FORCEINLINE float CheckQLinearGlobalAveragePoolScaleAndSize( float ScaleInput, float ScaleOutput, size_t ImageSize ) { if (ImageSize >= 0x1000000) { MLAS_THROW_EX(std::invalid_argument, "QLinearGlobalAveragePool ImageSize too large!"); } float scale = ScaleInput / (ScaleOutput * static_cast(ImageSize)); if (scale < 0x1.0p-32f || scale >= 256.0f) { // In first case, the scale is too small, ScaleInput/ScaleOutput < 1/256 no matter what // ImageSize In second case, the scale is too large, ScaleInput/ScaleOutput >= 256 no matter // what Image Size both case make output value constant, and hence not meaningful. MLAS_THROW_EX(std::invalid_argument, "QLinearGlobalAveragePool parameter out of computation range!"); } return scale; } #if defined(MLAS_NEON_INTRINSICS) template void MLASCALL MlasQLinearGlobalAveragePoolNchw( const T8Bits* Input, float ScaleInput, int32_t ZeroPointInput, T8Bits* Output, float ScaleOutput, int32_t ZeroPointOutput, size_t Channels, size_t ImageSize, int32_t* AccumulateBuffer ) { float scale = CheckQLinearGlobalAveragePoolScaleAndSize(ScaleInput, ScaleOutput, ImageSize); int32_t bias[] = {-ZeroPointInput * static_cast(ImageSize), 0, 0, 0}; const int32x4_t vbias = vld1q_s32(bias); const int32x4_t vzero = vmovq_n_s32(0); const uint8_t* InputU8 = (const uint8_t*)(Input); int32_t* sum_buffer = AccumulateBuffer; uint8_t tail_buffer[8] = {0, 0, 0, 0, 0, 0, 0, 0}; for (size_t c = Channels; c > 0; c--) { int32x4_t vacc_lo = vbias; int32x4_t vacc_hi = vzero; auto Len = ImageSize; for (; Len >= 32; Len -= 32) { const uint8x8_t vi0 = vld1_u8(InputU8); const uint8x8_t vi1 = vld1_u8(InputU8 + 8); const uint8x8_t vi2 = vld1_u8(InputU8 + 16); const uint8x8_t vi3 = vld1_u8(InputU8 + 24); int16x8_t vsum; if constexpr (std::is_signed::value) { const int16x8_t vs01 = vaddl_s8(vreinterpret_s8_u8(vi0), vreinterpret_s8_u8(vi1)); const int16x8_t vs23 = vaddl_s8(vreinterpret_s8_u8(vi2), vreinterpret_s8_u8(vi3)); vsum = vaddq_s16(vs01, vs23); } else { const uint16x8_t vs01 = vaddl_u8(vi0, vi1); const uint16x8_t vs23 = vaddl_u8(vi2, vi3); vsum = vreinterpretq_s16_u16(vaddq_u16(vs01, vs23)); } vacc_lo = vaddw_s16(vacc_lo, vget_low_s16(vsum)); vacc_hi = vaddw_s16(vacc_hi, vget_high_s16(vsum)); InputU8 += 32; } for (; Len >= 8; Len -= 8) { int16x8_t vsum; if constexpr (std::is_signed::value) { vsum = vmovl_s8(vreinterpret_s8_u8(vld1_u8(InputU8))); } else { vsum = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(InputU8))); } vacc_lo = vaddw_s16(vacc_lo, vget_low_s16(vsum)); vacc_hi = vaddw_s16(vacc_hi, vget_high_s16(vsum)); InputU8 += 8; } if (Len > 0) { memcpy(tail_buffer, InputU8, Len); int16x8_t vsum; if constexpr (std::is_signed::value) { vsum = vmovl_s8(vreinterpret_s8_u8(vld1_u8(tail_buffer))); } else { vsum = vreinterpretq_s16_u16(vmovl_u8(vld1_u8(tail_buffer))); } vacc_lo = vaddw_s16(vacc_lo, vget_low_s16(vsum)); vacc_hi = vaddw_s16(vacc_hi, vget_high_s16(vsum)); InputU8 += Len; } vacc_lo = vaddq_s32(vacc_lo, vacc_hi); int32x2_t vacc = vadd_s32(vget_high_s32(vacc_lo), vget_low_s32(vacc_lo)); *sum_buffer++ = vget_lane_s32(vpadd_s32(vacc, vacc), 0); } MlasRequantizeOutput(AccumulateBuffer, Channels, Output, Channels, nullptr, &scale, false, static_cast(ZeroPointOutput), 0, 0, 1, Channels); } template MLAS_FORCEINLINE void MlasQLinearGlobalAveragePoolNhwcSingleBatch( const T8Bits* Input, T8Bits* Output, const T8Bits* LastOf8, size_t ImageSize, size_t Channels, size_t Stride, int32_t Bias, float Scale, T8Bits Output_zero_point, int32_t* AccumulateBuffer, const T8Bits* ZeroBuffer ) { #define LOAD_FULL_CHANNELS() \ const uint8x8_t vi0 = vld1_u8(i0); \ i0 += 8; \ const uint8x8_t vi1 = vld1_u8(i1); \ i1 += 8; \ const uint8x8_t vi2 = vld1_u8(i2); \ i2 += 8; \ const uint8x8_t vi3 = vld1_u8(i3); \ i3 += 8; \ const uint8x8_t vi4 = vld1_u8(i4); \ i4 += 8; \ const uint8x8_t vi5 = vld1_u8(i5); \ i5 += 8; \ const uint8x8_t vi6 = vld1_u8(i6); \ i6 += 8 #define CALCULATE_ACCUMULATE_VECTORS() \ int32x4_t vacc_lo = finish_one_pass ? vld1q_s32(acc) : vbias; \ int32x4_t vacc_hi = finish_one_pass ? vld1q_s32(acc + 4) : vbias; \ int16x8_t vsum; \ if constexpr (std::is_signed::value) { \ const int16x8_t vsum01 = vaddl_s8(vreinterpret_s8_u8(vi0), vreinterpret_s8_u8(vi1)); \ const int16x8_t vsum23 = vaddl_s8(vreinterpret_s8_u8(vi2), vreinterpret_s8_u8(vi3)); \ const int16x8_t vsum45 = vaddl_s8(vreinterpret_s8_u8(vi4), vreinterpret_s8_u8(vi5)); \ const int16x8_t vsum016 = vaddw_s8(vsum01, vreinterpret_s8_u8(vi6)); \ const int16x8_t vsum2345 = vaddq_s16(vsum23, vsum45); \ vsum = vaddq_s16(vsum016, vsum2345); \ } else { \ const uint16x8_t vsum01 = vaddl_u8(vi0, vi1); \ const uint16x8_t vsum23 = vaddl_u8(vi2, vi3); \ const uint16x8_t vsum45 = vaddl_u8(vi4, vi5); \ const uint16x8_t vsum016 = vaddw_u8(vsum01, vi6); \ const uint16x8_t vsum2345 = vaddq_u16(vsum23, vsum45); \ vsum = vreinterpretq_s16_u16(vaddq_u16(vsum016, vsum2345)); \ } \ vacc_lo = vaddw_s16(vacc_lo, vget_low_s16(vsum)); \ vacc_hi = vaddw_s16(vacc_hi, vget_high_s16(vsum)) uint8_t tail[8] = {0, 0, 0, 0, 0, 0, 0, 0}; const int32x4_t vbias = vld1q_dup_s32(&Bias); bool finish_one_pass = false; const size_t step_next_group = 7 * Stride - (Channels & ~size_t{7}); const uint8_t* LastOf8U8 = (const uint8_t*)LastOf8; const uint8_t* i0 = (const uint8_t*)Input; const uint8_t* i1 = i0 + Stride; const uint8_t* i4 = i0 + Stride * 4; const uint8_t* i2 = i1 + Stride; const uint8_t* i5 = i4 + Stride; const uint8_t* i3 = i2 + Stride; const uint8_t* i6 = i5 + Stride; for (; ImageSize > 7; ImageSize -= 7) { int32_t* acc = AccumulateBuffer; size_t c = Channels; for (; c >= 8; c -= 8) { LOAD_FULL_CHANNELS(); CALCULATE_ACCUMULATE_VECTORS(); vst1q_s32(acc, vacc_lo); vst1q_s32(acc + 4, vacc_hi); acc += 8; } if (c > 0) { const uint8x8_t vi0 = vld1_u8(((i0 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i0, c) : i0)); const uint8x8_t vi1 = vld1_u8(((i1 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i1, c) : i1)); const uint8x8_t vi2 = vld1_u8(((i2 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i2, c) : i2)); const uint8x8_t vi3 = vld1_u8(((i3 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i3, c) : i3)); const uint8x8_t vi4 = vld1_u8(((i4 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i4, c) : i4)); const uint8x8_t vi5 = vld1_u8(((i5 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i5, c) : i5)); const uint8x8_t vi6 = vld1_u8(((i6 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i6, c) : i6)); CALCULATE_ACCUMULATE_VECTORS(); vst1q_s32(acc, vacc_lo); vst1q_s32(acc + 4, vacc_hi); } finish_one_pass = true; i0 += step_next_group; i1 += step_next_group; i2 += step_next_group; i3 += step_next_group; i4 += step_next_group; i5 += step_next_group; i6 += step_next_group; } if (ImageSize > 0) { switch (ImageSize) { case 1: i1 = (const uint8_t*)ZeroBuffer; /* fall through */ case 2: i2 = (const uint8_t*)ZeroBuffer; /* fall through */ case 3: i3 = (const uint8_t*)ZeroBuffer; /* fall through */ case 4: i4 = (const uint8_t*)ZeroBuffer; /* fall through */ case 5: i5 = (const uint8_t*)ZeroBuffer; /* fall through */ case 6: i6 = (const uint8_t*)ZeroBuffer; /* fall through */ default: break; } int32_t* acc = AccumulateBuffer; size_t c = Channels; for (; c >= 8; c -= 8) { LOAD_FULL_CHANNELS(); CALCULATE_ACCUMULATE_VECTORS(); vst1q_s32(acc, vacc_lo); vst1q_s32(acc + 4, vacc_hi); acc += 8; } if (c > 0) { const uint8x8_t vi0 = vld1_u8(((i0 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i0, c) : i0)); const uint8x8_t vi1 = vld1_u8( ((1 < ImageSize && i1 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i1, c) : i1)); const uint8x8_t vi2 = vld1_u8( ((2 < ImageSize && i2 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i2, c) : i2)); const uint8x8_t vi3 = vld1_u8( ((3 < ImageSize && i3 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i3, c) : i3)); const uint8x8_t vi4 = vld1_u8( ((4 < ImageSize && i4 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i4, c) : i4)); const uint8x8_t vi5 = vld1_u8( ((5 < ImageSize && i5 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i5, c) : i5)); const uint8x8_t vi6 = vld1_u8( ((6 < ImageSize && i6 >= LastOf8U8) ? (const uint8_t*)memcpy(tail, i6, c) : i6)); CALCULATE_ACCUMULATE_VECTORS(); vst1q_s32(acc, vacc_lo); vst1q_s32(acc + 4, vacc_hi); } } MlasRequantizeOutput(AccumulateBuffer, Channels, Output, Channels, nullptr, &Scale, false, Output_zero_point, 0, 0, 1, Channels); } #elif defined(MLAS_SSE2_INTRINSICS) template void MLASCALL MlasQLinearGlobalAveragePoolNchw( const T8Bits* Input, float ScaleInput, int32_t ZeroPointInput, T8Bits* Output, float ScaleOutput, int32_t ZeroPointOutput, size_t Channels, size_t ImageSize, int32_t* AccumulateBuffer ) { float scale = CheckQLinearGlobalAveragePoolScaleAndSize(ScaleInput, ScaleOutput, ImageSize); const int32_t bias[] = {-ZeroPointInput * static_cast(ImageSize), 0, 0, 0}; const auto vbias = _mm_loadu_si128((const __m128i*)&bias); const auto vzero = _mm_setzero_si128(); uint8_t buffer[8] = {0, 0, 0, 0, 0, 0, 0, 0}; int32_t* sum_buffer = AccumulateBuffer; for (size_t c = Channels; c > 0; c--) { __m128i vacc_lo = vbias; __m128i vacc_hi = vzero; auto Len = ImageSize; for (; Len >= 32; Len -= 32) { const __m128i vi0 = _mm_loadl_epi64((const __m128i*)Input); const __m128i vi1 = _mm_loadl_epi64((const __m128i*)(Input + 8)); const __m128i vi2 = _mm_loadl_epi64((const __m128i*)(Input + 16)); const __m128i vi3 = _mm_loadl_epi64((const __m128i*)(Input + 24)); if constexpr (std::is_signed::value) { const __m128i vxi0 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi0), 8); const __m128i vxi1 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi1), 8); const __m128i vxi2 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi2), 8); const __m128i vxi3 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi3), 8); const __m128i vsum = _mm_add_epi16(_mm_add_epi16(vxi0, vxi1), _mm_add_epi16(vxi2, vxi3)); vacc_lo = _mm_add_epi32(vacc_lo, _mm_srai_epi32(_mm_unpacklo_epi16(vzero, vsum), 16)); vacc_hi = _mm_add_epi32(vacc_hi, _mm_srai_epi32(_mm_unpackhi_epi16(vzero, vsum), 16)); } else { const __m128i vxi0 = _mm_unpacklo_epi8(vi0, vzero); const __m128i vxi1 = _mm_unpacklo_epi8(vi1, vzero); const __m128i vxi2 = _mm_unpacklo_epi8(vi2, vzero); const __m128i vxi3 = _mm_unpacklo_epi8(vi3, vzero); const __m128i vsum = _mm_add_epi16(_mm_add_epi16(vxi0, vxi1), _mm_add_epi16(vxi2, vxi3)); vacc_lo = _mm_add_epi32(vacc_lo, _mm_unpacklo_epi16(vsum, vzero)); vacc_hi = _mm_add_epi32(vacc_hi, _mm_unpackhi_epi16(vsum, vzero)); } Input += 32; } for (; Len >= 8; Len -= 8) { if constexpr (std::is_signed::value) { const __m128i vsum = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, _mm_loadl_epi64((const __m128i*)Input)), 8); vacc_lo = _mm_add_epi32(vacc_lo, _mm_srai_epi32(_mm_unpacklo_epi16(vzero, vsum), 16)); vacc_hi = _mm_add_epi32(vacc_hi, _mm_srai_epi32(_mm_unpackhi_epi16(vzero, vsum), 16)); } else { const __m128i vsum = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i*)Input), vzero); vacc_lo = _mm_add_epi32(vacc_lo, _mm_unpacklo_epi16(vsum, vzero)); vacc_hi = _mm_add_epi32(vacc_hi, _mm_unpackhi_epi16(vsum, vzero)); } Input += 8; } if (Len > 0) { memcpy(buffer, Input, Len); if constexpr (std::is_signed::value) { const __m128i vsum = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, _mm_loadl_epi64((const __m128i*)buffer)), 8); vacc_lo = _mm_add_epi32(vacc_lo, _mm_srai_epi32(_mm_unpacklo_epi16(vzero, vsum), 16)); vacc_hi = _mm_add_epi32(vacc_hi, _mm_srai_epi32(_mm_unpackhi_epi16(vzero, vsum), 16)); } else { const __m128i vsum = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i*)buffer), vzero); vacc_lo = _mm_add_epi32(vacc_lo, _mm_unpacklo_epi16(vsum, vzero)); vacc_hi = _mm_add_epi32(vacc_hi, _mm_unpackhi_epi16(vsum, vzero)); } Input += Len; } __m128i vacc = _mm_add_epi32(vacc_lo, vacc_hi); // [ D C | B A ] __m128i vshuf = _mm_shuffle_epi32(vacc, _MM_SHUFFLE(2, 3, 0, 1)); // [ C D | A B ] __m128i vsums = _mm_add_epi32(vacc, vshuf); // [ D+C C+D | B+A A+B ] vshuf = _mm_shuffle_epi32(vsums, _MM_SHUFFLE(1, 0, 3, 2)); // [ B+A A+B | D+C C+D ] vsums = _mm_add_epi32(vsums, vshuf); *sum_buffer++ = _mm_cvtsi128_si32(vsums); } MlasRequantizeOutput(AccumulateBuffer, Channels, Output, Channels, nullptr, &scale, false, static_cast(ZeroPointOutput), 0, 0, 1, Channels); } template MLAS_FORCEINLINE void MlasQLinearGlobalAveragePoolNhwcSingleBatch( const T8Bits* Input, T8Bits* Output, const T8Bits* LastOf8, size_t ImageSize, size_t Channels, size_t Stride, int32_t Bias, float Scale, T8Bits Output_zero_point, int32_t* AccumulateBuffer, const T8Bits* ZeroBuffer ) { #if defined(MLAS_TARGET_IX86) constexpr size_t PixelsPerIteration = 4; #define LOAD_FULL_CHANNELS() \ const __m128i vi0 = _mm_loadl_epi64((const __m128i*)i0); \ i0 += 8; \ const __m128i vi1 = _mm_loadl_epi64((const __m128i*)i1); \ i1 += 8; \ const __m128i vi2 = _mm_loadl_epi64((const __m128i*)i2); \ i2 += 8; \ const __m128i vi3 = _mm_loadl_epi64((const __m128i*)i3); \ i3 += 8; #define CALCULATE_ACCUMULATE_VECTORS() \ __m128i vacc_lo = finish_one_pass ? _mm_loadu_si128((__m128i*)acc) : vbias; \ __m128i vacc_hi = finish_one_pass ? _mm_loadu_si128(((__m128i*)acc) + 1) : vbias; \ __m128i vxi0; \ __m128i vxi1; \ __m128i vxi2; \ __m128i vxi3; \ if constexpr (std::is_signed::value) { \ vxi0 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi0), 8); \ vxi1 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi1), 8); \ vxi2 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi2), 8); \ vxi3 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi3), 8); \ } else { \ vxi0 = _mm_unpacklo_epi8(vi0, vzero); \ vxi1 = _mm_unpacklo_epi8(vi1, vzero); \ vxi2 = _mm_unpacklo_epi8(vi2, vzero); \ vxi3 = _mm_unpacklo_epi8(vi3, vzero); \ } \ __m128i vsum01 = _mm_add_epi16(vxi0, vxi1); \ __m128i vsum23 = _mm_add_epi16(vxi2, vxi3); \ __m128i vsum = _mm_add_epi16(vsum01, vsum23); \ \ if constexpr (std::is_signed::value) { \ vacc_lo = _mm_add_epi32(vacc_lo, _mm_srai_epi32(_mm_unpacklo_epi16(vzero, vsum), 16)); \ vacc_hi = _mm_add_epi32(vacc_hi, _mm_srai_epi32(_mm_unpackhi_epi16(vzero, vsum), 16)); \ } else { \ vacc_lo = _mm_add_epi32(vacc_lo, _mm_unpacklo_epi16(vsum, vzero)); \ vacc_hi = _mm_add_epi32(vacc_hi, _mm_unpackhi_epi16(vsum, vzero)); \ } #else constexpr size_t PixelsPerIteration = 7; #define LOAD_FULL_CHANNELS() \ const __m128i vi0 = _mm_loadl_epi64((const __m128i*)i0); \ i0 += 8; \ const __m128i vi1 = _mm_loadl_epi64((const __m128i*)i1); \ i1 += 8; \ const __m128i vi2 = _mm_loadl_epi64((const __m128i*)i2); \ i2 += 8; \ const __m128i vi3 = _mm_loadl_epi64((const __m128i*)i3); \ i3 += 8; \ const __m128i vi4 = _mm_loadl_epi64((const __m128i*)i4); \ i4 += 8; \ const __m128i vi5 = _mm_loadl_epi64((const __m128i*)i5); \ i5 += 8; \ const __m128i vi6 = _mm_loadl_epi64((const __m128i*)i6); \ i6 += 8 #define CALCULATE_ACCUMULATE_VECTORS() \ __m128i vacc_lo = finish_one_pass ? _mm_loadu_si128((__m128i*)acc) : vbias; \ __m128i vacc_hi = finish_one_pass ? _mm_loadu_si128(((__m128i*)acc) + 1) : vbias; \ __m128i vxi0; \ __m128i vxi1; \ __m128i vxi2; \ __m128i vxi3; \ __m128i vxi4; \ __m128i vxi5; \ __m128i vxi6; \ if constexpr (std::is_signed::value) { \ vxi0 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi0), 8); \ vxi1 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi1), 8); \ vxi2 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi2), 8); \ vxi3 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi3), 8); \ vxi4 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi4), 8); \ vxi5 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi5), 8); \ vxi6 = _mm_srai_epi16(_mm_unpacklo_epi8(vzero, vi6), 8); \ } else { \ vxi0 = _mm_unpacklo_epi8(vi0, vzero); \ vxi1 = _mm_unpacklo_epi8(vi1, vzero); \ vxi2 = _mm_unpacklo_epi8(vi2, vzero); \ vxi3 = _mm_unpacklo_epi8(vi3, vzero); \ vxi4 = _mm_unpacklo_epi8(vi4, vzero); \ vxi5 = _mm_unpacklo_epi8(vi5, vzero); \ vxi6 = _mm_unpacklo_epi8(vi6, vzero); \ } \ const __m128i vsum01 = _mm_add_epi16(vxi0, vxi1); \ const __m128i vsum23 = _mm_add_epi16(vxi2, vxi3); \ const __m128i vsum45 = _mm_add_epi16(vxi4, vxi5); \ const __m128i vsum016 = _mm_add_epi16(vsum01, vxi6); \ const __m128i vsum2345 = _mm_add_epi16(vsum23, vsum45); \ const __m128i vsum = _mm_add_epi16(vsum016, vsum2345); \ if constexpr (std::is_signed::value) { \ vacc_lo = _mm_add_epi32(vacc_lo, _mm_srai_epi32(_mm_unpacklo_epi16(vzero, vsum), 16)); \ vacc_hi = _mm_add_epi32(vacc_hi, _mm_srai_epi32(_mm_unpackhi_epi16(vzero, vsum), 16)); \ } else { \ vacc_lo = _mm_add_epi32(vacc_lo, _mm_unpacklo_epi16(vsum, vzero)); \ vacc_hi = _mm_add_epi32(vacc_hi, _mm_unpackhi_epi16(vsum, vzero)); \ } #endif T8Bits tail[8] = {0, 0, 0, 0, 0, 0, 0, 0}; bool finish_one_pass = false; const __m128i vbias = _mm_set1_epi32(Bias); const __m128i vzero = _mm_setzero_si128(); size_t step_next_group = PixelsPerIteration * Stride - (Channels & ~size_t{7}); const T8Bits* i0 = Input; const T8Bits* i1 = i0 + Stride; const T8Bits* i2 = i1 + Stride; const T8Bits* i3 = i2 + Stride; #if !defined(MLAS_TARGET_IX86) const T8Bits* i4 = i0 + Stride * 4; const T8Bits* i5 = i4 + Stride; const T8Bits* i6 = i5 + Stride; #endif for (; ImageSize > PixelsPerIteration; ImageSize -= PixelsPerIteration) { int32_t* acc = AccumulateBuffer; size_t c = Channels; for (; c >= 8; c -= 8) { LOAD_FULL_CHANNELS(); CALCULATE_ACCUMULATE_VECTORS(); _mm_storeu_si128((__m128i*)acc, vacc_lo); _mm_storeu_si128(((__m128i*)acc) + 1, vacc_hi); acc += 8; } if (c > 0) { const __m128i vi0 = _mm_loadl_epi64((const __m128i*)(i0 >= LastOf8 ? memcpy(tail, i0, c) : i0)); const __m128i vi1 = _mm_loadl_epi64((const __m128i*)(i1 >= LastOf8 ? memcpy(tail, i1, c) : i1)); const __m128i vi2 = _mm_loadl_epi64((const __m128i*)(i2 >= LastOf8 ? memcpy(tail, i2, c) : i2)); const __m128i vi3 = _mm_loadl_epi64((const __m128i*)(i3 >= LastOf8 ? memcpy(tail, i3, c) : i3)); #if !defined(MLAS_TARGET_IX86) const __m128i vi4 = _mm_loadl_epi64((const __m128i*)(i4 >= LastOf8 ? memcpy(tail, i4, c) : i4)); const __m128i vi5 = _mm_loadl_epi64((const __m128i*)(i5 >= LastOf8 ? memcpy(tail, i5, c) : i5)); const __m128i vi6 = _mm_loadl_epi64((const __m128i*)(i6 >= LastOf8 ? memcpy(tail, i6, c) : i6)); #endif CALCULATE_ACCUMULATE_VECTORS(); _mm_storeu_si128((__m128i*)acc, vacc_lo); _mm_storeu_si128(((__m128i*)acc) + 1, vacc_hi); } finish_one_pass = true; i0 += step_next_group; i1 += step_next_group; i2 += step_next_group; i3 += step_next_group; #if !defined(MLAS_TARGET_IX86) i4 += step_next_group; i5 += step_next_group; i6 += step_next_group; #endif } if (ImageSize > 0) { #if defined(MLAS_TARGET_IX86) switch (ImageSize) { case 1: i1 = ZeroBuffer; [[fallthrough]]; case 2: i2 = ZeroBuffer; [[fallthrough]]; case 3: i3 = ZeroBuffer; [[fallthrough]]; default: break; } #else switch (ImageSize) { case 1: i1 = ZeroBuffer; [[fallthrough]]; case 2: i2 = ZeroBuffer; [[fallthrough]]; case 3: i3 = ZeroBuffer; [[fallthrough]]; case 4: i4 = ZeroBuffer; [[fallthrough]]; case 5: i5 = ZeroBuffer; [[fallthrough]]; case 6: i6 = ZeroBuffer; [[fallthrough]]; default: break; } #endif int32_t* acc = AccumulateBuffer; size_t c = Channels; for (; c >= 8; c -= 8) { LOAD_FULL_CHANNELS(); CALCULATE_ACCUMULATE_VECTORS(); _mm_storeu_si128((__m128i*)acc, vacc_lo); _mm_storeu_si128(((__m128i*)acc) + 1, vacc_hi); acc += 8; } if (c > 0) { const __m128i vi0 = _mm_loadl_epi64((const __m128i*)(i0 >= LastOf8 ? memcpy(tail, i0, c) : i0)); const __m128i vi1 = _mm_loadl_epi64( (const __m128i*)(1 < ImageSize && i1 >= LastOf8 ? memcpy(tail, i1, c) : i1)); const __m128i vi2 = _mm_loadl_epi64( (const __m128i*)(2 < ImageSize && i2 >= LastOf8 ? memcpy(tail, i2, c) : i2)); const __m128i vi3 = _mm_loadl_epi64( (const __m128i*)(3 < ImageSize && i3 >= LastOf8 ? memcpy(tail, i3, c) : i3)); #if !defined(MLAS_TARGET_IX86) const __m128i vi4 = _mm_loadl_epi64( (const __m128i*)(4 < ImageSize && i4 >= LastOf8 ? memcpy(tail, i4, c) : i4)); const __m128i vi5 = _mm_loadl_epi64( (const __m128i*)(5 < ImageSize && i5 >= LastOf8 ? memcpy(tail, i5, c) : i5)); const __m128i vi6 = _mm_loadl_epi64( (const __m128i*)(6 < ImageSize && i6 >= LastOf8 ? memcpy(tail, i6, c) : i6)); #endif CALCULATE_ACCUMULATE_VECTORS(); _mm_storeu_si128((__m128i*)acc, vacc_lo); _mm_storeu_si128(((__m128i*)acc) + 1, vacc_hi); } } MlasRequantizeOutput(AccumulateBuffer, Channels, Output, Channels, nullptr, &Scale, false, Output_zero_point, 0, 0, 1, Channels); } #else // Pure C++ Implementation template void MLASCALL MlasQLinearGlobalAveragePoolNchw( const T8Bits* Input, float ScaleInput, int32_t ZeroPointInput, T8Bits* Output, float ScaleOutput, int32_t ZeroPointOutput, size_t Channels, size_t ImageSize, int32_t* /* AccumulateBuffer */ ) { float scale = CheckQLinearGlobalAveragePoolScaleAndSize(ScaleInput, ScaleOutput, ImageSize); int32_t bias = -ZeroPointInput * static_cast(ImageSize); for (; Channels > 0; Channels--) { int32_t acc = bias; for (size_t i = 0; i < ImageSize; ++i) { acc += static_cast(*Input++); } int32_t v = static_cast(std::nearbyintf(acc * scale)) + ZeroPointOutput; v = std::min(static_cast(std::numeric_limits::max()), v); v = std::max(static_cast(std::numeric_limits::lowest()), v); *Output++ = static_cast(v); } } template void MLASCALL MlasQLinearGlobalAveragePoolNhwc( const T8Bits* Input, float ScaleInput, int32_t ZeroPointInput, T8Bits* Output, float ScaleOutput, int32_t ZeroPointOutput, size_t Batch, size_t ImageSize, size_t Stride, size_t Channels, int32_t* AccumulateBuffer, const T8Bits* /*ZeroBuffer*/ ) { float scale = CheckQLinearGlobalAveragePoolScaleAndSize(ScaleInput, ScaleOutput, ImageSize); int32_t bias = -ZeroPointInput * static_cast(ImageSize); for (; Batch > 0; Batch--) { const T8Bits* batch_input = Input; T8Bits* batch_output = Output; Input += Stride * ImageSize; Output += Stride; std::fill_n(AccumulateBuffer, Channels, bias); for (size_t i = 0; i < ImageSize; ++i) { for (size_t c = 0; c < Channels; ++c) { AccumulateBuffer[c] += static_cast(batch_input[c]); } batch_input += Stride; } for (size_t c = 0; c < Channels; ++c) { int32_t v = static_cast(std::nearbyintf(AccumulateBuffer[c] * scale)) + ZeroPointOutput; v = std::min(static_cast(std::numeric_limits::max()), v); v = std::max(static_cast(std::numeric_limits::lowest()), v); *batch_output++ = static_cast(v); } } } #endif #if defined(MLAS_NEON_INTRINSICS) || defined(MLAS_SSE2_INTRINSICS) template void MLASCALL MlasQLinearGlobalAveragePoolNhwc( const T8Bits* Input, float ScaleInput, int32_t ZeroPointInput, T8Bits* Output, float ScaleOutput, int32_t ZeroPointOutput, size_t Batch, size_t ImageSize, size_t Stride, size_t Channels, int32_t* AccumulateBuffer, const T8Bits* ZeroBuffer ) { float scale = CheckQLinearGlobalAveragePoolScaleAndSize(ScaleInput, ScaleOutput, ImageSize); const int32_t bias = -ZeroPointInput * static_cast(ImageSize); const T8Bits* inputLastOf8 = Input + (Batch * ImageSize * Stride - Stride + Channels) - 8; for (; Batch > 0; Batch--) { MlasQLinearGlobalAveragePoolNhwcSingleBatch( Input, Output, inputLastOf8, ImageSize, Channels, Stride, bias, scale, static_cast(ZeroPointOutput), AccumulateBuffer, ZeroBuffer); Input += ImageSize * Stride; Output += Stride; } } #endif template void MLASCALL MlasQLinearGlobalAveragePoolNchw( const int8_t* Input, float ScaleInput, int32_t ZeroPointInput, int8_t* Output, float ScaleOutput, int32_t ZeroPointOutput, size_t Channels, size_t ImageSize, int32_t* AccumulateBuffer ); template void MLASCALL MlasQLinearGlobalAveragePoolNchw( const uint8_t* Input, float ScaleInput, int32_t ZeroPointInput, uint8_t* Output, float ScaleOutput, int32_t ZeroPointOutput, size_t Channels, size_t ImageSize, int32_t* AccumulateBuffer ); template void MLASCALL MlasQLinearGlobalAveragePoolNhwc( const int8_t* Input, float ScaleInput, int32_t ZeroPointInput, int8_t* Output, float ScaleOutput, int32_t ZeroPointOutput, size_t Batch, size_t ImageSize, size_t Stride, size_t Channels, int32_t* AccumulateBuffer, const int8_t* ZeroBuffer ); template void MLASCALL MlasQLinearGlobalAveragePoolNhwc( const uint8_t* Input, float ScaleInput, int32_t ZeroPointInput, uint8_t* Output, float ScaleOutput, int32_t ZeroPointOutput, size_t Batch, size_t ImageSize, size_t Stride, size_t Channels, int32_t* AccumulateBuffer, const uint8_t* ZeroBuffer );