From fccf26755fe26d37c70755eabadb58f57dfe335d Mon Sep 17 00:00:00 2001 From: Uri Goren Date: Fri, 26 Nov 2021 23:53:32 +0200 Subject: [PATCH 01/19] Create hnswlib.py Added `LazyIndex` with the same API as `Index`. Useful when having multiple indices with lazy initialization. --- python_bindings/hnswlib.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 python_bindings/hnswlib.py diff --git a/python_bindings/hnswlib.py b/python_bindings/hnswlib.py new file mode 100644 index 00000000..60488f36 --- /dev/null +++ b/python_bindings/hnswlib.py @@ -0,0 +1,38 @@ +import hnswlib + +class LazyIndex(hnswlib.Index): + def __init__(self, space, dim,max_elements=1024, ef_construction=200, M=16): + super().__init__(space, dim) + self.init_max_elements=max_elements + self.init_ef_construction=ef_construction + self.init_M=M + def init(self, max_elements=0): + if max_elements==0: + max_elements=self.init_max_elements + super().init_index(max_elements, self.init_M, self.init_ef_construction) + def add_items(self, data, ids=None, num_threads=-1): + if self.max_elements==0: + self.init() + return super().add_items(data,ids, num_threads) + def get_items(self, ids=None): + if self.max_elements==0: + return [] + return super().get_items(ids) + def knn_query(self, data,k=1, num_threads=-1): + if self.max_elements==0: + return [], [] + return super().knn_query(data, k, num_threads) + def resize_index(self, size): + if self.max_elements==0: + return self.init(size) + else: + return super().resize_index(size) + def set_ef(self, ef): + if self.max_elements==0: + self.init_ef_construction=ef + return + super().set_ef(ef) + def get_max_elements(self): + return self.max_elements + def get_current_count(self): + return self.element_count From a4490a0b2e051f80acf21eaf0e10990f29965ef4 Mon Sep 17 00:00:00 2001 From: Uri Goren Date: Tue, 30 Nov 2021 10:14:24 +0200 Subject: [PATCH 02/19] Rename hnswlib.py to LazyIndex.py --- python_bindings/{hnswlib.py => LazyIndex.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename python_bindings/{hnswlib.py => LazyIndex.py} (100%) diff --git a/python_bindings/hnswlib.py b/python_bindings/LazyIndex.py similarity index 100% rename from python_bindings/hnswlib.py rename to python_bindings/LazyIndex.py From 3e158d9f3a23d9a86e03c974dc57cf472537b276 Mon Sep 17 00:00:00 2001 From: Uri Goren Date: Tue, 30 Nov 2021 23:21:45 +0200 Subject: [PATCH 03/19] Update LazyIndex.py Added a short description --- python_bindings/LazyIndex.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/python_bindings/LazyIndex.py b/python_bindings/LazyIndex.py index 60488f36..4b8f078c 100644 --- a/python_bindings/LazyIndex.py +++ b/python_bindings/LazyIndex.py @@ -1,18 +1,24 @@ import hnswlib - +""" + A python wrapper for lazy indexing, preserves the same api as hnswlib.Index but initializes the index only after adding items for the first time with `add_items`. +""" class LazyIndex(hnswlib.Index): def __init__(self, space, dim,max_elements=1024, ef_construction=200, M=16): super().__init__(space, dim) self.init_max_elements=max_elements self.init_ef_construction=ef_construction self.init_M=M - def init(self, max_elements=0): + def init_index(self, max_elements=0,M=None,ef_construction=None): if max_elements==0: max_elements=self.init_max_elements + if ef_construction: + self.init_ef_construction=ef_construction + if M: + self.init_M=M super().init_index(max_elements, self.init_M, self.init_ef_construction) def add_items(self, data, ids=None, num_threads=-1): if self.max_elements==0: - self.init() + self.init_index() return super().add_items(data,ids, num_threads) def get_items(self, ids=None): if self.max_elements==0: @@ -24,7 +30,7 @@ def knn_query(self, data,k=1, num_threads=-1): return super().knn_query(data, k, num_threads) def resize_index(self, size): if self.max_elements==0: - return self.init(size) + return self.init_index(size) else: return super().resize_index(size) def set_ef(self, ef): From 97a980ec67341afe6b1e9136b03377a054a4b314 Mon Sep 17 00:00:00 2001 From: Uri Goren Date: Tue, 30 Nov 2021 23:24:14 +0200 Subject: [PATCH 04/19] Update LazyIndex.py --- python_bindings/LazyIndex.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/python_bindings/LazyIndex.py b/python_bindings/LazyIndex.py index 4b8f078c..dbaa4673 100644 --- a/python_bindings/LazyIndex.py +++ b/python_bindings/LazyIndex.py @@ -8,14 +8,14 @@ def __init__(self, space, dim,max_elements=1024, ef_construction=200, M=16): self.init_max_elements=max_elements self.init_ef_construction=ef_construction self.init_M=M - def init_index(self, max_elements=0,M=None,ef_construction=None): - if max_elements==0: - max_elements=self.init_max_elements - if ef_construction: + def init_index(self, max_elements=0,M=0,ef_construction=0): + if max_elements>0: + self.init_max_elements=max_elements + if ef_construction>0: self.init_ef_construction=ef_construction - if M: + if M>0: self.init_M=M - super().init_index(max_elements, self.init_M, self.init_ef_construction) + super().init_index(self.init_max_elements, self.init_M, self.init_ef_construction) def add_items(self, data, ids=None, num_threads=-1): if self.max_elements==0: self.init_index() From b8010809949ae5a021997ed0e8906c319582bb0d Mon Sep 17 00:00:00 2001 From: Tony Kuo Date: Mon, 20 Dec 2021 12:07:31 -0800 Subject: [PATCH 05/19] avx runtime check --- hnswlib/hnswlib.h | 81 +++++++++++++++++++++++++++++++++++++++++++++- hnswlib/space_ip.h | 57 +++++++++++++++++++++++++++----- hnswlib/space_l2.h | 36 +++++++++++++++++---- 3 files changed, 159 insertions(+), 15 deletions(-) diff --git a/hnswlib/hnswlib.h b/hnswlib/hnswlib.h index d95d6f88..0c219749 100644 --- a/hnswlib/hnswlib.h +++ b/hnswlib/hnswlib.h @@ -37,6 +37,86 @@ #include #include +// Adapted from https://github.com/Mysticial/FeatureDetector +#define _XCR_XFEATURE_ENABLED_MASK 0 +#ifdef _WIN32 +void cpuid(int32_t out[4], int32_t eax, int32_t ecx){ + __cpuidex(out, eax, ecx); +} +__int64 xgetbv(unsigned int x){ + return _xgetbv(x); +} +#else +#include +#include +void cpuid(int32_t cpuInfo[4], int32_t eax, int32_t ecx) { + __cpuid_count(eax, ecx, cpuInfo[0], cpuInfo[1], cpuInfo[2], cpuInfo[3]); +} + +uint64_t xgetbv(unsigned int index) { + uint32_t eax, edx; + __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index)); + return ((uint64_t)edx << 32) | eax; +} +#endif + +bool AVXCapable() { + int cpuInfo[4]; + + // CPU support + cpuid(cpuInfo, 0, 0); + int nIds = cpuInfo[0]; + + bool HW_AVX = false; + if (nIds >= 0x00000001) { + cpuid(cpuInfo, 0x00000001, 0); + HW_AVX = (cpuInfo[2] & ((int)1 << 28)) != 0; + } + + // OS support + cpuid(cpuInfo, 1, 0); + + bool osUsesXSAVE_XRSTORE = (cpuInfo[2] & (1 << 27)) != 0; + bool cpuAVXSuport = (cpuInfo[2] & (1 << 28)) != 0; + + bool avxSupported = false; + if (osUsesXSAVE_XRSTORE && cpuAVXSuport) { + uint64_t xcrFeatureMask = xgetbv(_XCR_XFEATURE_ENABLED_MASK); + avxSupported = (xcrFeatureMask & 0x6) == 0x6; + } + return avxSupported; +} + +bool AVX512Capable() { + if (!AVXCapable()) return false; + + int cpuInfo[4]; + + // CPU support + cpuid(cpuInfo, 0, 0); + int nIds = cpuInfo[0]; + + bool HW_AVX512F = false; // AVX512 Foundation + if (nIds >= 0x00000007) { + cpuid(cpuInfo, 0x00000007, 0); + HW_AVX512F = (cpuInfo[1] & ((int)1 << 16)) != 0; + } + + // OS support + cpuid(cpuInfo, 1, 0); + + bool osUsesXSAVE_XRSTORE = (cpuInfo[2] & (1 << 27)) != 0; + bool cpuAVXSuport = (cpuInfo[2] & (1 << 28)) != 0; + + bool avxSupported = false; + bool avx512Supported = false; + if (osUsesXSAVE_XRSTORE && cpuAVXSuport) { + uint64_t xcrFeatureMask = xgetbv(_XCR_XFEATURE_ENABLED_MASK); + avx512Supported = (xcrFeatureMask & 0xe6) == 0xe6; + } + return avx512Supported; +} + namespace hnswlib { typedef size_t labeltype; @@ -108,7 +188,6 @@ namespace hnswlib { return result; } - } #include "space_l2.h" diff --git a/hnswlib/space_ip.h b/hnswlib/space_ip.h index c0029bde..e8efafa1 100644 --- a/hnswlib/space_ip.h +++ b/hnswlib/space_ip.h @@ -18,7 +18,7 @@ namespace hnswlib { // Favor using AVX if available. static float - InnerProductSIMD4Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { + InnerProductSIMD4ExtAVX(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { float PORTABLE_ALIGN32 TmpRes[8]; float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; @@ -64,10 +64,12 @@ namespace hnswlib { return 1.0f - sum; } -#elif defined(USE_SSE) +#endif + +#if defined(USE_SSE) static float - InnerProductSIMD4Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { + InnerProductSIMD4ExtSSE(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { float PORTABLE_ALIGN32 TmpRes[8]; float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; @@ -128,7 +130,7 @@ namespace hnswlib { #if defined(USE_AVX512) static float - InnerProductSIMD16Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { + InnerProductSIMD16ExtAVX512(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { float PORTABLE_ALIGN64 TmpRes[16]; float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; @@ -157,10 +159,12 @@ namespace hnswlib { return 1.0f - sum; } -#elif defined(USE_AVX) +#endif + +#if defined(USE_AVX) static float - InnerProductSIMD16Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { + InnerProductSIMD16ExtAVX(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { float PORTABLE_ALIGN32 TmpRes[8]; float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; @@ -195,10 +199,12 @@ namespace hnswlib { return 1.0f - sum; } -#elif defined(USE_SSE) +#endif + +#if defined(USE_SSE) static float - InnerProductSIMD16Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { + InnerProductSIMD16ExtSSE(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { float PORTABLE_ALIGN32 TmpRes[8]; float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; @@ -245,6 +251,41 @@ namespace hnswlib { #endif #if defined(USE_SSE) || defined(USE_AVX) || defined(USE_AVX512) + static float + InnerProductSIMD4Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { + DISTFUNC simdfunc_; +#if defined(USE_AVX) + if (AVXCapable()) + simdfunc_ = InnerProductSIMD4ExtAVX; + else + simdfunc_ = InnerProductSIMD4ExtSSE; +#else + simdfunc_ = InnerProductSIMD4ExtSSE; +#endif + return simdfunc_(pVect1v, pVect2v, qty_ptr); + } + + static float + InnerProductSIMD16Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { + DISTFUNC simdfunc_; +#if defined(USE_AVX512) + if (AVX512Capable()) + simdfunc_ = InnerProductSIMD16ExtAVX512; + else if (AVXCapable()) + simdfunc_ = InnerProductSIMD16ExtAVX; + else + simdfunc_ = InnerProductSIMD16ExtSSE; +#elif defined(USE_AVX) + if (AVXCapable()) + simdfunc_ = InnerProductSIMD16ExtAVX; + else + simdfunc_ = InnerProductSIMD16ExtSSE; +#else + simdfunc_ = InnerProductSIMD16ExtSSE; +#endif + return simdfunc_(pVect1v, pVect2v, qty_ptr); + } + static float InnerProductSIMD16ExtResiduals(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { size_t qty = *((size_t *) qty_ptr); diff --git a/hnswlib/space_l2.h b/hnswlib/space_l2.h index 3b6a49ef..9949c342 100644 --- a/hnswlib/space_l2.h +++ b/hnswlib/space_l2.h @@ -23,7 +23,7 @@ namespace hnswlib { // Favor using AVX512 if available. static float - L2SqrSIMD16Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { + L2SqrSIMD16ExtAVX512(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; size_t qty = *((size_t *) qty_ptr); @@ -52,12 +52,13 @@ namespace hnswlib { return (res); } +#endif -#elif defined(USE_AVX) +#if defined(USE_AVX) // Favor using AVX if available. static float - L2SqrSIMD16Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { + L2SqrSIMD16ExtAVX(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; size_t qty = *((size_t *) qty_ptr); @@ -89,10 +90,12 @@ namespace hnswlib { return TmpRes[0] + TmpRes[1] + TmpRes[2] + TmpRes[3] + TmpRes[4] + TmpRes[5] + TmpRes[6] + TmpRes[7]; } -#elif defined(USE_SSE) +#endif + +#if defined(USE_SSE) static float - L2SqrSIMD16Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { + L2SqrSIMD16ExtSSE(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { float *pVect1 = (float *) pVect1v; float *pVect2 = (float *) pVect2v; size_t qty = *((size_t *) qty_ptr); @@ -141,6 +144,27 @@ namespace hnswlib { #endif #if defined(USE_SSE) || defined(USE_AVX) || defined(USE_AVX512) + static float + L2SqrSIMD16Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { + DISTFUNC simdfunc_; +#if defined(USE_AVX512) + if (AVX512Capable()) + simdfunc_ = L2SqrSIMD16ExtAVX512; + else if (AVXCapable()) + simdfunc_ = L2SqrSIMD16ExtAVX; + else + simdfunc_ = L2SqrSIMD16ExtSSE; +#elif defined(USE_AVX) + if (AVXCapable()) + simdfunc_ = L2SqrSIMD16ExtAVX; + else + simdfunc_ = L2SqrSIMD16ExtSSE; +#else + simdfunc_ = L2SqrSIMD16ExtSSE; +#endif + return simdfunc_(pVect1v, pVect2v, qty_ptr); + } + static float L2SqrSIMD16ExtResiduals(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { size_t qty = *((size_t *) qty_ptr); @@ -156,7 +180,7 @@ namespace hnswlib { #endif -#ifdef USE_SSE +#if defined(USE_SSE) static float L2SqrSIMD4Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { float PORTABLE_ALIGN32 TmpRes[8]; From ce9685ca854442ddfd9fea4e90515d171e30f5d9 Mon Sep 17 00:00:00 2001 From: Tony Kuo Date: Mon, 20 Dec 2021 13:48:32 -0800 Subject: [PATCH 06/19] cleanup --- hnswlib/hnswlib.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/hnswlib/hnswlib.h b/hnswlib/hnswlib.h index 0c219749..0853d5e8 100644 --- a/hnswlib/hnswlib.h +++ b/hnswlib/hnswlib.h @@ -84,7 +84,7 @@ bool AVXCapable() { uint64_t xcrFeatureMask = xgetbv(_XCR_XFEATURE_ENABLED_MASK); avxSupported = (xcrFeatureMask & 0x6) == 0x6; } - return avxSupported; + return HW_AVX && avxSupported; } bool AVX512Capable() { @@ -96,8 +96,8 @@ bool AVX512Capable() { cpuid(cpuInfo, 0, 0); int nIds = cpuInfo[0]; - bool HW_AVX512F = false; // AVX512 Foundation - if (nIds >= 0x00000007) { + bool HW_AVX512F = false; + if (nIds >= 0x00000007) { // AVX512 Foundation cpuid(cpuInfo, 0x00000007, 0); HW_AVX512F = (cpuInfo[1] & ((int)1 << 16)) != 0; } @@ -108,13 +108,12 @@ bool AVX512Capable() { bool osUsesXSAVE_XRSTORE = (cpuInfo[2] & (1 << 27)) != 0; bool cpuAVXSuport = (cpuInfo[2] & (1 << 28)) != 0; - bool avxSupported = false; bool avx512Supported = false; if (osUsesXSAVE_XRSTORE && cpuAVXSuport) { uint64_t xcrFeatureMask = xgetbv(_XCR_XFEATURE_ENABLED_MASK); avx512Supported = (xcrFeatureMask & 0xe6) == 0xe6; } - return avx512Supported; + return HW_AVX512F && avx512Supported; } namespace hnswlib { From f069830cc1f962d1b05af9f6a9ed9978cd09c933 Mon Sep 17 00:00:00 2001 From: Tony Kuo Date: Tue, 21 Dec 2021 08:14:48 -0800 Subject: [PATCH 07/19] indent --- hnswlib/space_ip.h | 14 +++++++------- hnswlib/space_l2.h | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/hnswlib/space_ip.h b/hnswlib/space_ip.h index e8efafa1..4cd5dc09 100644 --- a/hnswlib/space_ip.h +++ b/hnswlib/space_ip.h @@ -254,35 +254,35 @@ namespace hnswlib { static float InnerProductSIMD4Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { DISTFUNC simdfunc_; -#if defined(USE_AVX) + #if defined(USE_AVX) if (AVXCapable()) simdfunc_ = InnerProductSIMD4ExtAVX; else simdfunc_ = InnerProductSIMD4ExtSSE; -#else + #else simdfunc_ = InnerProductSIMD4ExtSSE; -#endif + #endif return simdfunc_(pVect1v, pVect2v, qty_ptr); } static float InnerProductSIMD16Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { DISTFUNC simdfunc_; -#if defined(USE_AVX512) + #if defined(USE_AVX512) if (AVX512Capable()) simdfunc_ = InnerProductSIMD16ExtAVX512; else if (AVXCapable()) simdfunc_ = InnerProductSIMD16ExtAVX; else simdfunc_ = InnerProductSIMD16ExtSSE; -#elif defined(USE_AVX) + #elif defined(USE_AVX) if (AVXCapable()) simdfunc_ = InnerProductSIMD16ExtAVX; else simdfunc_ = InnerProductSIMD16ExtSSE; -#else + #else simdfunc_ = InnerProductSIMD16ExtSSE; -#endif + #endif return simdfunc_(pVect1v, pVect2v, qty_ptr); } diff --git a/hnswlib/space_l2.h b/hnswlib/space_l2.h index 9949c342..0871f984 100644 --- a/hnswlib/space_l2.h +++ b/hnswlib/space_l2.h @@ -147,21 +147,21 @@ namespace hnswlib { static float L2SqrSIMD16Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { DISTFUNC simdfunc_; -#if defined(USE_AVX512) + #if defined(USE_AVX512) if (AVX512Capable()) simdfunc_ = L2SqrSIMD16ExtAVX512; else if (AVXCapable()) simdfunc_ = L2SqrSIMD16ExtAVX; else simdfunc_ = L2SqrSIMD16ExtSSE; -#elif defined(USE_AVX) + #elif defined(USE_AVX) if (AVXCapable()) simdfunc_ = L2SqrSIMD16ExtAVX; else simdfunc_ = L2SqrSIMD16ExtSSE; -#else + #else simdfunc_ = L2SqrSIMD16ExtSSE; -#endif + #endif return simdfunc_(pVect1v, pVect2v, qty_ptr); } From 7cc674df7912e15e0bf8057f599a800a06d566e8 Mon Sep 17 00:00:00 2001 From: Tony Kuo Date: Tue, 21 Dec 2021 09:55:45 -0800 Subject: [PATCH 08/19] no march=native if conda --- setup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 90826dea..69ba060d 100644 --- a/setup.py +++ b/setup.py @@ -74,8 +74,11 @@ class BuildExt(build_ext): """A custom build extension for adding compiler-specific options.""" c_opts = { 'msvc': ['/EHsc', '/openmp', '/O2'], - 'unix': ['-O3', '-march=native'], # , '-w' + #'unix': ['-O3', '-march=native'], # , '-w' + 'unix': ['-O3'], # , '-w' } + if "conda" not in sys.version.lower(): + c_opts['unix'].append("-march=native") link_opts = { 'unix': [], 'msvc': [], From 6da99d678337e16f98f3f0cfeb7e19ebc862692b Mon Sep 17 00:00:00 2001 From: Tony Kuo Date: Tue, 21 Dec 2021 10:50:15 -0800 Subject: [PATCH 09/19] remove march=native --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 69ba060d..68f961b7 100644 --- a/setup.py +++ b/setup.py @@ -77,8 +77,6 @@ class BuildExt(build_ext): #'unix': ['-O3', '-march=native'], # , '-w' 'unix': ['-O3'], # , '-w' } - if "conda" not in sys.version.lower(): - c_opts['unix'].append("-march=native") link_opts = { 'unix': [], 'msvc': [], From e8488c40df67a5cf24cde2b81bbc7fdda6902eed Mon Sep 17 00:00:00 2001 From: Tony Kuo Date: Wed, 22 Dec 2021 07:11:41 -0800 Subject: [PATCH 10/19] use environment flag HNSWLIB_NO_CFLAGS --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index 68f961b7..44675be7 100644 --- a/setup.py +++ b/setup.py @@ -77,6 +77,10 @@ class BuildExt(build_ext): #'unix': ['-O3', '-march=native'], # , '-w' 'unix': ['-O3'], # , '-w' } + if not os.environ.get("HNSWLIB_NO_CFLAGS"): + if "conda" not in sys.version.lower(): + c_opts['unix'].append('-march=native') + link_opts = { 'unix': [], 'msvc': [], From 5fe5e3b7f4d0ebe5f6f39af970c01f24f6d66102 Mon Sep 17 00:00:00 2001 From: Tony Kuo Date: Wed, 22 Dec 2021 09:20:28 -0800 Subject: [PATCH 11/19] change flag name to HNSWLIB_NO_NATIVE --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 44675be7..d9888e42 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ class BuildExt(build_ext): #'unix': ['-O3', '-march=native'], # , '-w' 'unix': ['-O3'], # , '-w' } - if not os.environ.get("HNSWLIB_NO_CFLAGS"): + if not os.environ.get("HNSWLIB_NO_NATIVE"): if "conda" not in sys.version.lower(): c_opts['unix'].append('-march=native') From f7ce7cc0266f419f5cdef569d9105926c21f38e1 Mon Sep 17 00:00:00 2001 From: Tony Kuo Date: Tue, 4 Jan 2022 07:52:22 -0800 Subject: [PATCH 12/19] move decision to constructors --- hnswlib/space_ip.h | 49 ++++++++++++++-------------------------------- hnswlib/space_l2.h | 35 ++++++++++++--------------------- 2 files changed, 28 insertions(+), 56 deletions(-) diff --git a/hnswlib/space_ip.h b/hnswlib/space_ip.h index 4cd5dc09..385751d5 100644 --- a/hnswlib/space_ip.h +++ b/hnswlib/space_ip.h @@ -251,40 +251,8 @@ namespace hnswlib { #endif #if defined(USE_SSE) || defined(USE_AVX) || defined(USE_AVX512) - static float - InnerProductSIMD4Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { - DISTFUNC simdfunc_; - #if defined(USE_AVX) - if (AVXCapable()) - simdfunc_ = InnerProductSIMD4ExtAVX; - else - simdfunc_ = InnerProductSIMD4ExtSSE; - #else - simdfunc_ = InnerProductSIMD4ExtSSE; - #endif - return simdfunc_(pVect1v, pVect2v, qty_ptr); - } - - static float - InnerProductSIMD16Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { - DISTFUNC simdfunc_; - #if defined(USE_AVX512) - if (AVX512Capable()) - simdfunc_ = InnerProductSIMD16ExtAVX512; - else if (AVXCapable()) - simdfunc_ = InnerProductSIMD16ExtAVX; - else - simdfunc_ = InnerProductSIMD16ExtSSE; - #elif defined(USE_AVX) - if (AVXCapable()) - simdfunc_ = InnerProductSIMD16ExtAVX; - else - simdfunc_ = InnerProductSIMD16ExtSSE; - #else - simdfunc_ = InnerProductSIMD16ExtSSE; - #endif - return simdfunc_(pVect1v, pVect2v, qty_ptr); - } + DISTFUNC InnerProductSIMD16Ext = InnerProductSIMD16ExtSSE; + DISTFUNC InnerProductSIMD4Ext = InnerProductSIMD4ExtSSE; static float InnerProductSIMD16ExtResiduals(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { @@ -324,6 +292,19 @@ namespace hnswlib { InnerProductSpace(size_t dim) { fstdistfunc_ = InnerProduct; #if defined(USE_AVX) || defined(USE_SSE) || defined(USE_AVX512) + #if defined(USE_AVX512) + if (AVX512Capable()) + InnerProductSIMD16Ext = InnerProductSIMD16ExtAVX512; + else if (AVXCapable()) + InnerProductSIMD16Ext = InnerProductSIMD16ExtAVX; + #endif + #if defined(USE_AVX) + if (AVXCapable()) { + InnerProductSIMD16Ext = InnerProductSIMD16ExtAVX; + InnerProductSIMD4Ext = InnerProductSIMD4ExtAVX; + } + #endif + if (dim % 16 == 0) fstdistfunc_ = InnerProductSIMD16Ext; else if (dim % 4 == 0) diff --git a/hnswlib/space_l2.h b/hnswlib/space_l2.h index 0871f984..44135370 100644 --- a/hnswlib/space_l2.h +++ b/hnswlib/space_l2.h @@ -144,26 +144,7 @@ namespace hnswlib { #endif #if defined(USE_SSE) || defined(USE_AVX) || defined(USE_AVX512) - static float - L2SqrSIMD16Ext(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { - DISTFUNC simdfunc_; - #if defined(USE_AVX512) - if (AVX512Capable()) - simdfunc_ = L2SqrSIMD16ExtAVX512; - else if (AVXCapable()) - simdfunc_ = L2SqrSIMD16ExtAVX; - else - simdfunc_ = L2SqrSIMD16ExtSSE; - #elif defined(USE_AVX) - if (AVXCapable()) - simdfunc_ = L2SqrSIMD16ExtAVX; - else - simdfunc_ = L2SqrSIMD16ExtSSE; - #else - simdfunc_ = L2SqrSIMD16ExtSSE; - #endif - return simdfunc_(pVect1v, pVect2v, qty_ptr); - } + DISTFUNC L2SqrSIMD16Ext = L2SqrSIMD16ExtSSE; static float L2SqrSIMD16ExtResiduals(const void *pVect1v, const void *pVect2v, const void *qty_ptr) { @@ -232,7 +213,17 @@ namespace hnswlib { public: L2Space(size_t dim) { fstdistfunc_ = L2Sqr; - #if defined(USE_SSE) || defined(USE_AVX) || defined(USE_AVX512) + #if defined(USE_SSE) || defined(USE_AVX) || defined(USE_AVX512) + #if defined(USE_AVX512) + if (AVX512Capable()) + L2SqrSIMD16Ext = L2SqrSIMD16ExtAVX512; + else if (AVXCapable()) + L2SqrSIMD16Ext = L2SqrSIMD16ExtAVX; + #elif defined(USE_AVX) + if (AVXCapable()) + L2SqrSIMD16Ext = L2SqrSIMD16ExtAVX; + #endif + if (dim % 16 == 0) fstdistfunc_ = L2SqrSIMD16Ext; else if (dim % 4 == 0) @@ -241,7 +232,7 @@ namespace hnswlib { fstdistfunc_ = L2SqrSIMD16ExtResiduals; else if (dim > 4) fstdistfunc_ = L2SqrSIMD4ExtResiduals; - #endif + #endif dim_ = dim; data_size_ = dim * sizeof(float); } From 67c869b77b4d2b4e0dd1952f33bae0b006b33804 Mon Sep 17 00:00:00 2001 From: Tony Kuo Date: Thu, 6 Jan 2022 08:01:33 -0800 Subject: [PATCH 13/19] fixed windows build errors --- hnswlib/hnswlib.h | 49 ++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/hnswlib/hnswlib.h b/hnswlib/hnswlib.h index 0853d5e8..58eb7607 100644 --- a/hnswlib/hnswlib.h +++ b/hnswlib/hnswlib.h @@ -15,8 +15,25 @@ #ifdef _MSC_VER #include #include +#include "cpu_x86.h" +void cpu_x86::cpuid(int32_t out[4], int32_t eax, int32_t ecx) { + __cpuidex(out, eax, ecx); +} +__int64 xgetbv(unsigned int x) { + return _xgetbv(x); +} #else #include +#include +#include +void cpuid(int32_t cpuInfo[4], int32_t eax, int32_t ecx) { + __cpuid_count(eax, ecx, cpuInfo[0], cpuInfo[1], cpuInfo[2], cpuInfo[3]); +} +uint64_t xgetbv(unsigned int index) { + uint32_t eax, edx; + __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index)); + return ((uint64_t)edx << 32) | eax; +} #endif #if defined(USE_AVX512) @@ -30,35 +47,9 @@ #define PORTABLE_ALIGN32 __declspec(align(32)) #define PORTABLE_ALIGN64 __declspec(align(64)) #endif -#endif - -#include -#include -#include -#include // Adapted from https://github.com/Mysticial/FeatureDetector #define _XCR_XFEATURE_ENABLED_MASK 0 -#ifdef _WIN32 -void cpuid(int32_t out[4], int32_t eax, int32_t ecx){ - __cpuidex(out, eax, ecx); -} -__int64 xgetbv(unsigned int x){ - return _xgetbv(x); -} -#else -#include -#include -void cpuid(int32_t cpuInfo[4], int32_t eax, int32_t ecx) { - __cpuid_count(eax, ecx, cpuInfo[0], cpuInfo[1], cpuInfo[2], cpuInfo[3]); -} - -uint64_t xgetbv(unsigned int index) { - uint32_t eax, edx; - __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index)); - return ((uint64_t)edx << 32) | eax; -} -#endif bool AVXCapable() { int cpuInfo[4]; @@ -115,6 +106,12 @@ bool AVX512Capable() { } return HW_AVX512F && avx512Supported; } +#endif + +#include +#include +#include +#include namespace hnswlib { typedef size_t labeltype; From 641e62be4ba170baf1b923f0f54562da329e3b92 Mon Sep 17 00:00:00 2001 From: Tony Kuo Date: Thu, 13 Jan 2022 12:22:08 -0800 Subject: [PATCH 14/19] remove checking for conda in os.environment --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index d9888e42..ddf50f75 100644 --- a/setup.py +++ b/setup.py @@ -78,8 +78,7 @@ class BuildExt(build_ext): 'unix': ['-O3'], # , '-w' } if not os.environ.get("HNSWLIB_NO_NATIVE"): - if "conda" not in sys.version.lower(): - c_opts['unix'].append('-march=native') + c_opts['unix'].append('-march=native') link_opts = { 'unix': [], From 324426d9bb4b8514e56cb47b3726fb224ae4bb6c Mon Sep 17 00:00:00 2001 From: Tony Kuo Date: Mon, 17 Jan 2022 08:47:11 -0800 Subject: [PATCH 15/19] fix logic for AVX --- hnswlib/space_ip.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hnswlib/space_ip.h b/hnswlib/space_ip.h index 385751d5..7cd3d020 100644 --- a/hnswlib/space_ip.h +++ b/hnswlib/space_ip.h @@ -297,12 +297,13 @@ namespace hnswlib { InnerProductSIMD16Ext = InnerProductSIMD16ExtAVX512; else if (AVXCapable()) InnerProductSIMD16Ext = InnerProductSIMD16ExtAVX; + #elif defined(USE_AVX) + if (AVXCapable()) + InnerProductSIMD16Ext = InnerProductSIMD16ExtAVX; #endif #if defined(USE_AVX) - if (AVXCapable()) { - InnerProductSIMD16Ext = InnerProductSIMD16ExtAVX; + if (AVXCapable()) InnerProductSIMD4Ext = InnerProductSIMD4ExtAVX; - } #endif if (dim % 16 == 0) From fbe9221a691b105c584b01ef6c52070a78336865 Mon Sep 17 00:00:00 2001 From: Peter Sobot Date: Wed, 19 Jan 2022 15:47:42 -0500 Subject: [PATCH 16/19] Throw exception instead of segfaulting when passing a scalar to get_items. --- python_bindings/bindings.cpp | 13 +++++++++---- python_bindings/tests/bindings_test_getdata.py | 3 +++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/python_bindings/bindings.cpp b/python_bindings/bindings.cpp index 90d26161..d7d015b7 100644 --- a/python_bindings/bindings.cpp +++ b/python_bindings/bindings.cpp @@ -260,11 +260,16 @@ class Index { if (!ids_.is_none()) { py::array_t < size_t, py::array::c_style | py::array::forcecast > items(ids_); auto ids_numpy = items.request(); - std::vector ids1(ids_numpy.shape[0]); - for (size_t i = 0; i < ids1.size(); i++) { - ids1[i] = items.data()[i]; + + if (ids_numpy.ndim != 1) { + throw std::invalid_argument("get_items accepts a list of indices and returns a list of vectors"); + } else { + std::vector ids1(ids_numpy.shape[0]); + for (size_t i = 0; i < ids1.size(); i++) { + ids1[i] = items.data()[i]; + } + ids.swap(ids1); } - ids.swap(ids1); } std::vector> data; diff --git a/python_bindings/tests/bindings_test_getdata.py b/python_bindings/tests/bindings_test_getdata.py index 2985c1dd..515ecebd 100644 --- a/python_bindings/tests/bindings_test_getdata.py +++ b/python_bindings/tests/bindings_test_getdata.py @@ -41,6 +41,9 @@ def testGettingItems(self): print("Adding all elements (%d)" % (len(data))) p.add_items(data, labels) + # Getting data by label should raise an exception if a scalar is passed: + self.assertRaises(ValueError, lambda: p.get_items(labels[0])) + # After adding them, all labels should be retrievable returned_items = p.get_items(labels) self.assertSequenceEqual(data.tolist(), returned_items) From 18fe0c790afadf710328a7a0831ad8f0822edc71 Mon Sep 17 00:00:00 2001 From: Peter Sobot Date: Mon, 24 Jan 2022 21:21:09 -0500 Subject: [PATCH 17/19] Allow passing multidimensional arrays that can be squeezed. --- python_bindings/bindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python_bindings/bindings.cpp b/python_bindings/bindings.cpp index d7d015b7..4bf91c17 100644 --- a/python_bindings/bindings.cpp +++ b/python_bindings/bindings.cpp @@ -261,7 +261,7 @@ class Index { py::array_t < size_t, py::array::c_style | py::array::forcecast > items(ids_); auto ids_numpy = items.request(); - if (ids_numpy.ndim != 1) { + if (ids_numpy.ndim == 0) { throw std::invalid_argument("get_items accepts a list of indices and returns a list of vectors"); } else { std::vector ids1(ids_numpy.shape[0]); From 995831e6b22e113727c3a3f00aaebf19807e225b Mon Sep 17 00:00:00 2001 From: Yury Malkov Date: Sat, 5 Feb 2022 22:41:58 -0800 Subject: [PATCH 18/19] update perfomance test --- examples/git_tester.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/examples/git_tester.py b/examples/git_tester.py index 7891ef20..aaf70c82 100644 --- a/examples/git_tester.py +++ b/examples/git_tester.py @@ -1,16 +1,34 @@ from pydriller import Repository import os import datetime -os.system("cp examples/speedtest.py examples/speedtest2.py") -for commit in Repository('.', from_tag="v0.5.2").traverse_commits(): - print(commit.hash) - print(commit.msg) +os.system("cp examples/speedtest.py examples/speedtest2.py") # the file has to be outside of git +for idx, commit in enumerate(Repository('.', from_tag="v0.6.0").traverse_commits()): + name=commit.msg.replace('\n', ' ').replace('\r', ' ') + print(idx, commit.hash, name) + + + +for commit in Repository('.', from_tag="v0.6.0").traverse_commits(): + + name=commit.msg.replace('\n', ' ').replace('\r', ' ') + print(commit.hash, name) os.system(f"git checkout {commit.hash}; rm -rf build; ") - os.system("python -m pip install .") - os.system(f'python examples/speedtest2.py -n "{commit.msg}" -d 4 -t 1') - os.system(f'python examples/speedtest2.py -n "{commit.msg}" -d 64 -t 1') - os.system(f'python examples/speedtest2.py -n "{commit.msg}" -d 128 -t 1') - os.system(f'python examples/speedtest2.py -n "{commit.msg}" -d 4 -t 24') - os.system(f'python examples/speedtest2.py -n "{commit.msg}" -d 128 -t 24') + print("\n\n--------------------\n\n") + ret=os.system("python -m pip install .") + print(ret) + + if ret != 0: + print ("build failed!!!!") + print ("build failed!!!!") + print ("build failed!!!!") + print ("build failed!!!!") + continue + + os.system(f'python examples/speedtest2.py -n "{name}" -d 4 -t 1') + os.system(f'python examples/speedtest2.py -n "{name}" -d 64 -t 1') + os.system(f'python examples/speedtest2.py -n "{name}" -d 128 -t 1') + os.system(f'python examples/speedtest2.py -n "{name}" -d 4 -t 24') + os.system(f'python examples/speedtest2.py -n "{name}" -d 128 -t 24') + From 2ebbc2cf13d77a2788490b82c2bf9b3b53d82f73 Mon Sep 17 00:00:00 2001 From: Yury Malkov Date: Sun, 6 Feb 2022 14:25:33 -0800 Subject: [PATCH 19/19] bump version --- README.md | 9 ++++++++- setup.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a23a2e57..567d3be8 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,14 @@ Header-only C++ HNSW implementation with python bindings. **NEWS:** -**version 0.6** + +**version 0.6.1** + +* Thanks to ([@tony-kuo](https://github.com/tony-kuo)) hnswlib AVX512 and AVX builds are not backwards-compatible with older SSE and non-AVX512 architectures. +* Thanks to ([@psobot](https://github.com/psobot)) there is now a sencible message instead of segfault when passing a scalar to get_items. +* Thanks to ([@urigoren](https://github.com/urigoren)) hnswlib has a lazy index creation python wrapper. + +**version 0.6.0** * Thanks to ([@dyashuni](https://github.com/dyashuni)) hnswlib now uses github actions for CI, there is a search speedup in some scenarios with deletions. `unmark_deleted(label)` is now also a part of the python interface (note now it throws an exception for double deletions). * Thanks to ([@slice4e](https://github.com/slice4e)) we now support AVX512; thanks to ([@LTLA](https://github.com/LTLA)) the cmake interface for the lib is now updated. * Thanks to ([@alonre24](https://github.com/alonre24)) we now have a python bindings for brute-force (and examples for recall tuning: [TESTING_RECALL.md](TESTING_RECALL.md). diff --git a/setup.py b/setup.py index ddf50f75..e01ce76e 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ from setuptools import Extension, setup from setuptools.command.build_ext import build_ext -__version__ = '0.6.0' +__version__ = '0.6.1' include_dirs = [