diff options
Diffstat (limited to 'library/cpp/dot_product')
| -rw-r--r-- | library/cpp/dot_product/dot_product.cpp | 8 | ||||
| -rw-r--r-- | library/cpp/dot_product/dot_product_ut.cpp | 35 | ||||
| -rw-r--r-- | library/cpp/dot_product/dot_product_vnni.cpp | 125 | ||||
| -rw-r--r-- | library/cpp/dot_product/dot_product_vnni.h | 9 | ||||
| -rw-r--r-- | library/cpp/dot_product/ya.make | 2 |
5 files changed, 176 insertions, 3 deletions
diff --git a/library/cpp/dot_product/dot_product.cpp b/library/cpp/dot_product/dot_product.cpp index 003dbe88335..d3f2b31b2d4 100644 --- a/library/cpp/dot_product/dot_product.cpp +++ b/library/cpp/dot_product/dot_product.cpp @@ -1,6 +1,7 @@ #include "dot_product.h" #include "dot_product_sse.h" #include "dot_product_avx2.h" +#include "dot_product_vnni.h" #include "dot_product_simple.h" #include <library/cpp/sse/sse.h> @@ -31,7 +32,6 @@ namespace NDotProductImpl { namespace { [[maybe_unused]] const int _ = [] { if (!FromYaTest() && GetEnv("Y_NO_AVX_IN_DOT_PRODUCT") == "" && NX86::HaveAVX2() && NX86::HaveFMA()) { - DotProductI8Impl = &DotProductAvx2; DotProductUi8Impl = &DotProductAvx2; DotProductI32Impl = &DotProductAvx2; DotProductFloatImpl = &DotProductAvx2; @@ -40,6 +40,12 @@ namespace NDotProductImpl { TriWayDotProductImpl = &TriWayDotProductAvx2; TriWayDotProductFloatI8Impl = &TriWayDotProductFloatI8Avx2; TriWayDotProductI8Impl = &TriWayDotProductI8Avx2; + + if (GetEnv("Y_NO_VNNI_IN_DOT_PRODUCT") == "" && NX86::HaveAVX512VNNI() && NX86::HaveAVX512BW()) { + DotProductI8Impl = &DotProductVnni; + } else { + DotProductI8Impl = &DotProductAvx2; + } } else { #ifdef ARCADIA_SSE DotProductI8Impl = &DotProductSse; diff --git a/library/cpp/dot_product/dot_product_ut.cpp b/library/cpp/dot_product/dot_product_ut.cpp index 6d07d1a3a31..8ba451f4964 100644 --- a/library/cpp/dot_product/dot_product_ut.cpp +++ b/library/cpp/dot_product/dot_product_ut.cpp @@ -1,6 +1,7 @@ #include "dot_product.h" #include "dot_product_simple.h" #include "dot_product_avx2.h" +#include "dot_product_vnni.h" #include <library/cpp/testing/unittest/registar.h> @@ -48,16 +49,46 @@ Y_UNIT_TEST_SUITE(TDocProductTestSuite) { return sum; } + bool HaveVnniDotProduct() { + return NX86::HaveAVX2() && NX86::HaveFMA() && NX86::HaveAVX512BW() && NX86::HaveAVX512VNNI(); + } + Y_UNIT_TEST(TestDotProduct8) { TVector<i8> a(100); FillWithRandomNumbers(a.data(), 179, 100); TVector<i8> b(100); FillWithRandomNumbers(b.data(), 239, 100); + const bool haveVnni = HaveVnniDotProduct(); for (size_t i = 0; i < 30; ++i) { for (size_t length = 1; length + i + 1 < a.size(); ++length) { - UNIT_ASSERT_EQUAL(DotProduct(a.data() + i, b.data() + i, length), (SimpleDotProduct<i32, i8>(a.data() + i, b.data() + i, length))); - UNIT_ASSERT_EQUAL(DotProductSimple(a.data() + i, b.data() + i, length), (SimpleDotProduct<i32, i8>(a.data() + i, b.data() + i, length))); + const i32 expected = SimpleDotProduct<i32, i8>(a.data() + i, b.data() + i, length); + UNIT_ASSERT_EQUAL(DotProduct(a.data() + i, b.data() + i, length), expected); + UNIT_ASSERT_EQUAL(DotProductSimple(a.data() + i, b.data() + i, length), expected); + if (haveVnni) { + UNIT_ASSERT_EQUAL(DotProductVnni(a.data() + i, b.data() + i, length), expected); + } + } + } + } + + Y_UNIT_TEST(TestDotProduct8VnniEdges) { + if (!HaveVnniDotProduct()) { + return; + } + + TVector<i8> a(512); + TVector<i8> b(512); + for (size_t i = 0; i < a.size(); ++i) { + a[i] = static_cast<i8>((i * 37) % 256 - 128); + b[i] = static_cast<i8>((i * 53 + 17) % 256 - 128); + } + + for (size_t offset = 0; offset < 16; ++offset) { + for (size_t length = 0; length + offset <= a.size(); ++length) { + UNIT_ASSERT_EQUAL( + DotProductVnni(a.data() + offset, b.data() + offset, length), + (SimpleDotProduct<i32, i8>(a.data() + offset, b.data() + offset, length))); } } } diff --git a/library/cpp/dot_product/dot_product_vnni.cpp b/library/cpp/dot_product/dot_product_vnni.cpp new file mode 100644 index 00000000000..4b3c52def49 --- /dev/null +++ b/library/cpp/dot_product/dot_product_vnni.cpp @@ -0,0 +1,125 @@ +#include "dot_product_vnni.h" +#include "dot_product_avx2.h" + +#include <util/system/compiler.h> +#include <util/system/platform.h> + +#if defined(__AVX512VNNI__) && defined(__AVX512BW__) && defined(__AVX512F__) + +#include <immintrin.h> + +namespace { + Y_FORCE_INLINE __m512i Load512i(const void* ptr) { + return _mm512_loadu_si512(ptr); + } + + Y_FORCE_INLINE __m512i CorrectSignedLhsDotProduct( + const __m512i biasedDotProduct, + const __m512i rhsSum) noexcept + { + return _mm512_sub_epi32(biasedDotProduct, _mm512_slli_epi32(rhsSum, 7)); + } + + Y_FORCE_INLINE void AccumulateDotProductI8Vnni( + __m512i& dotProduct, + const i8* lhs, + const i8* rhs, + const __m512i zero, + const __m512i signBit, + const __m512i ones) noexcept + { + const __m512i l = Load512i(lhs); + const __m512i r = Load512i(rhs); + const __m512i unsignedL = _mm512_xor_si512(l, signBit); + + // AVX512 VNNI has u8*i8 dot product. For signed lhs we compute + // (lhs + 128) * rhs and immediately subtract 128 * sum(rhs). + // Correcting each 64-byte chunk keeps the accumulator in the same + // range as a regular signed i8 dot product accumulator instead of + // accumulating a much larger biased intermediate value. + const __m512i biasedDotProduct = _mm512_dpbusd_epi32(zero, unsignedL, r); + const __m512i rhsSum = _mm512_dpbusd_epi32(zero, ones, r); + dotProduct = _mm512_add_epi32(dotProduct, CorrectSignedLhsDotProduct(biasedDotProduct, rhsSum)); + } + + Y_FORCE_INLINE void AccumulateTailDotProductI8Vnni( + __m512i& dotProduct, + const i8* lhs, + const i8* rhs, + const size_t length, + const __m512i zero, + const __m512i signBit, + const __m512i ones) noexcept + { + const __mmask64 mask = (ui64(1) << length) - 1; + const __m512i l = _mm512_maskz_loadu_epi8(mask, lhs); + const __m512i r = _mm512_maskz_loadu_epi8(mask, rhs); + const __m512i unsignedL = _mm512_xor_si512(l, signBit); + + const __m512i biasedDotProduct = _mm512_dpbusd_epi32(zero, unsignedL, r); + const __m512i rhsSum = _mm512_dpbusd_epi32(zero, ones, r); + dotProduct = _mm512_add_epi32(dotProduct, CorrectSignedLhsDotProduct(biasedDotProduct, rhsSum)); + } + + i32 HsumI32(const __m512i v) noexcept { + __m128i sum = _mm512_castsi512_si128(v); + sum = _mm_add_epi32(sum, _mm512_extracti32x4_epi32(v, 1)); + sum = _mm_add_epi32(sum, _mm512_extracti32x4_epi32(v, 2)); + sum = _mm_add_epi32(sum, _mm512_extracti32x4_epi32(v, 3)); + const __m128i hi64 = _mm_unpackhi_epi64(sum, sum); + const __m128i sum64 = _mm_add_epi32(hi64, sum); + const __m128i hi32 = _mm_shuffle_epi32(sum64, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i sum32 = _mm_add_epi32(sum64, hi32); + return _mm_cvtsi128_si32(sum32); + } +} + +i32 DotProductVnni(const i8* lhs, const i8* rhs, size_t length) noexcept { + if (length < 64) { + return DotProductAvx2(lhs, rhs, length); + } + + const __m512i zero = _mm512_setzero_si512(); + const __m512i signBit = _mm512_set1_epi8(-128); + const __m512i ones = _mm512_set1_epi8(1); + + __m512i dotProduct0 = zero; + __m512i dotProduct1 = zero; + __m512i dotProduct2 = zero; + __m512i dotProduct3 = zero; + + while (length >= 256) { + AccumulateDotProductI8Vnni(dotProduct0, lhs, rhs, zero, signBit, ones); + AccumulateDotProductI8Vnni(dotProduct1, lhs + 64, rhs + 64, zero, signBit, ones); + AccumulateDotProductI8Vnni(dotProduct2, lhs + 128, rhs + 128, zero, signBit, ones); + AccumulateDotProductI8Vnni(dotProduct3, lhs + 192, rhs + 192, zero, signBit, ones); + lhs += 256; + rhs += 256; + length -= 256; + } + + while (length >= 64) { + AccumulateDotProductI8Vnni(dotProduct0, lhs, rhs, zero, signBit, ones); + lhs += 64; + rhs += 64; + length -= 64; + } + + if (length > 0) { + AccumulateTailDotProductI8Vnni(dotProduct0, lhs, rhs, length, zero, signBit, ones); + } + + const __m512i dotProduct = _mm512_add_epi32( + _mm512_add_epi32(dotProduct0, dotProduct1), + _mm512_add_epi32(dotProduct2, dotProduct3)); + + return HsumI32(dotProduct); +} + +#else + +i32 DotProductVnni(const i8* lhs, const i8* rhs, size_t length) noexcept { + return DotProductAvx2(lhs, rhs, length); +} + +#endif diff --git a/library/cpp/dot_product/dot_product_vnni.h b/library/cpp/dot_product/dot_product_vnni.h new file mode 100644 index 00000000000..7949c28cd37 --- /dev/null +++ b/library/cpp/dot_product/dot_product_vnni.h @@ -0,0 +1,9 @@ +#pragma once + +#include <util/system/types.h> +#include <util/system/compiler.h> + +#include <stddef.h> + +Y_PURE_FUNCTION +i32 DotProductVnni(const i8* lhs, const i8* rhs, size_t length) noexcept; diff --git a/library/cpp/dot_product/ya.make b/library/cpp/dot_product/ya.make index fea9ba8152c..64d78d7bbdc 100644 --- a/library/cpp/dot_product/ya.make +++ b/library/cpp/dot_product/ya.make @@ -8,8 +8,10 @@ SRCS( IF (USE_SSE4 == "yes" AND OS_LINUX == "yes") SRC_C_AVX2(dot_product_avx2.cpp -mfma) + SRC_C_AVX512(dot_product_vnni.cpp -mavx512vnni) ELSE() SRC(dot_product_avx2.cpp) + SRC(dot_product_vnni.cpp) ENDIF() PEERDIR( |
