Fix: Windows SIMD detection (#5497)

Summary:
Hi,

while testing on windows x64 systems I've noticed that the `dd` optimization compilation target doesn't work properly. Causing crashes on non-avx2 systems for instance.

This pr fixes the SIMD level detection.
MSVC doesn't define `__x86_64__` but does define `_M_X64`. This is what this patch is based on.

I got some nice [GitHub actions](https://github.com/Binozo/Faiss.NET/actions/runs/30251011773/job/89928775910) and tested the patch on it.

##### Based on https://github.com/facebookresearch/faiss/issues/5476 but reopened because I made a mistake while rebasing

Pull Request resolved: https://github.com/facebookresearch/faiss/pull/5497

Reviewed By: alibeklfc

Differential Revision: D117897685

Pulled By: mnorris11

fbshipit-source-id: 80987895d17945bac9a4c5f0eb14beb67c84cbd5
This commit is contained in:
Binozo
2026-09-04 15:27:56 -07:00
committed by meta-codesync[bot]
parent b4c66ba603
commit 2ed4c106e9
2 changed files with 88 additions and 58 deletions
+86 -57
View File
@@ -7,8 +7,14 @@
#include <faiss/utils/simd_levels.h>
#include <cstdint>
#include <cstdlib>
#if defined(_MSC_VER)
// __cpuidex, _xgetbv
#include <intrin.h>
#endif
#include <faiss/impl/FaissAssert.h>
#include <faiss/impl/simd_dispatch.h>
@@ -67,42 +73,78 @@ static bool has_sve() {
}
#endif
namespace {
#if defined(__x86_64__) || defined(_M_X64)
// MSVC and clang-cl do not support GNU-style
// 64-bit inline assembly, MSVC defines _M_X64 instead of __x86_64__
#if defined(_MSC_VER)
[[maybe_unused]] void cpuid_count(
unsigned int leaf,
unsigned int subleaf,
unsigned int regs[4]) {
int r[4];
__cpuidex(r, static_cast<int>(leaf), static_cast<int>(subleaf));
for (int i = 0; i < 4; i++) {
regs[i] = static_cast<unsigned int>(r[i]);
}
}
[[maybe_unused]] uint64_t xgetbv0() {
return static_cast<uint64_t>(_xgetbv(0));
}
#else // GCC / Clang
[[maybe_unused]] void cpuid_count(
unsigned int leaf,
unsigned int subleaf,
unsigned int regs[4]) {
asm volatile("cpuid"
: "=a"(regs[0]), "=b"(regs[1]), "=c"(regs[2]), "=d"(regs[3])
: "a"(leaf), "c"(subleaf));
}
[[maybe_unused]] uint64_t xgetbv0() {
unsigned int eax, edx;
asm volatile("xgetbv" : "=a"(eax), "=d"(edx) : "c"(0));
return eax | (static_cast<uint64_t>(edx) << 32);
}
#endif // _MSC_VER
// Detect x86 microarchitecture flags used for kernel routing. Uses raw
// cpuid so it is safe to run on any CPU regardless of compiled SIMD level.
#if defined(__x86_64__)
namespace {
void detect_x86_uarch_flags() {
unsigned int eax, ebx, ecx, edx;
unsigned int regs[4];
// Vendor string (CPUID.0): "AuthenticAMD" is EBX="Auth", EDX="enti",
// ECX="cAMD".
eax = 0;
ecx = 0;
asm volatile("cpuid"
: "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
: "a"(eax), "c"(ecx));
const bool is_amd =
ebx == 0x68747541u && edx == 0x69746e65u && ecx == 0x444d4163u;
cpuid_count(0, 0, regs);
const bool is_amd = regs[1] == 0x68747541u && regs[3] == 0x69746e65u &&
regs[2] == 0x444d4163u;
// Family/model (CPUID.1 EAX).
eax = 1;
ecx = 0;
asm volatile("cpuid"
: "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
: "a"(eax), "c"(ecx));
const unsigned int base_family = (eax >> 8) & 0xfu;
cpuid_count(1, 0, regs);
const unsigned int eax1 = regs[0];
const unsigned int base_family = (eax1 >> 8) & 0xfu;
const unsigned int display_family =
base_family + (base_family == 0xfu ? ((eax >> 20) & 0xffu) : 0u);
base_family + (base_family == 0xfu ? ((eax1 >> 20) & 0xffu) : 0u);
// AMD Zen 4 / Zen 4c (Bergamo) is family 0x19 and splits AVX-512.
// (Zen 5, family 0x1A, has a native 512-bit datapath and is excluded.)
SIMDConfig::avx512_split = is_amd && display_family == 0x19u;
}
} // namespace
#else
namespace {
#else // Not x86-64
void detect_x86_uarch_flags() {}
#endif // defined(__x86_64__) || defined(_M_X64)
} // namespace
#endif
#ifdef FAISS_ENABLE_DD
@@ -154,57 +196,49 @@ SIMDLevel SIMDConfig::auto_detect_simd_level() {
detect_x86_uarch_flags();
#if defined(__x86_64__) && \
#if (defined(__x86_64__) || defined(_M_X64)) && \
(defined(COMPILE_SIMD_AVX2) || defined(COMPILE_SIMD_AVX512))
unsigned int eax, ebx, ecx, edx;
unsigned int regs[4];
eax = 1;
ecx = 0;
asm volatile("cpuid"
: "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
: "a"(eax), "c"(ecx));
cpuid_count(1, 0, regs);
unsigned int ecx1 = regs[2];
bool has_avx = (ecx & (1 << 28)) != 0;
bool has_avx = (ecx1 & (1 << 28)) != 0;
bool has_xsave_osxsave =
(ecx & ((1 << 26) | (1 << 27))) == ((1 << 26) | (1 << 27));
(ecx1 & ((1 << 26) | (1 << 27))) == ((1 << 26) | (1 << 27));
bool avx_supported = false;
if (has_avx && has_xsave_osxsave) {
unsigned int xcr0;
asm volatile("xgetbv" : "=a"(xcr0), "=d"(edx) : "c"(0));
avx_supported = (xcr0 & 6) == 6;
avx_supported = (xgetbv0() & 6) == 6;
}
if (avx_supported) {
eax = 7;
ecx = 0;
asm volatile("cpuid"
: "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
: "a"(eax), "c"(ecx));
// Save EDX before xgetbv clobbers it — needed for
// AVX512_FP16 check (bit 23) in the SPR detection below.
unsigned int cpuid7_edx = edx;
cpuid_count(7, 0, regs);
unsigned int ebx7 = regs[1];
// EDX of CPUID leaf 7 subleaf 0 carries AVX512_FP16 (bit 23),
// needed for the SPR detection below. Kept in a local so a later
// xgetbv cannot clobber it.
unsigned int cpuid7_edx = regs[3];
unsigned int xcr0;
asm volatile("xgetbv" : "=a"(xcr0), "=d"(edx) : "c"(0));
uint64_t xcr0 = xgetbv0();
#if defined(COMPILE_SIMD_AVX2) || defined(COMPILE_SIMD_AVX512)
bool has_avx2 = (ebx & (1 << 5)) != 0;
bool has_avx2 = (ebx7 & (1 << 5)) != 0;
if (has_avx2) {
supported_simd_levels |= (1 << static_cast<int>(SIMDLevel::AVX2));
detected_level = SIMDLevel::AVX2;
}
#if defined(COMPILE_SIMD_AVX512)
bool cpu_has_avx512f = (ebx & (1 << 16)) != 0;
bool cpu_has_avx512f = (ebx7 & (1 << 16)) != 0;
bool os_supports_avx512 = (xcr0 & 0xE0) == 0xE0;
bool has_avx512f = cpu_has_avx512f && os_supports_avx512;
if (has_avx512f) {
bool has_avx512cd = (ebx & (1 << 28)) != 0;
bool has_avx512vl = (ebx & (1 << 31)) != 0;
bool has_avx512dq = (ebx & (1 << 17)) != 0;
bool has_avx512bw = (ebx & (1 << 30)) != 0;
bool has_avx512cd = (ebx7 & (1 << 28)) != 0;
bool has_avx512vl = (ebx7 & (1 << 31)) != 0;
bool has_avx512dq = (ebx7 & (1 << 17)) != 0;
bool has_avx512bw = (ebx7 & (1 << 30)) != 0;
if (has_avx512bw && has_avx512cd && has_avx512vl && has_avx512dq) {
detected_level = SIMDLevel::AVX512;
supported_simd_levels |=
@@ -220,13 +254,8 @@ SIMDLevel SIMDConfig::auto_detect_simd_level() {
// CPUID EAX=7, ECX=0: EDX bit 23 = AVX512_FP16
// (Linux: X86_FEATURE_AVX512_FP16 = 18*32+23)
bool has_avx512_fp16 = (cpuid7_edx & (1 << 23)) != 0;
unsigned int eax1, ebx1, ecx1, edx1;
eax1 = 7;
ecx1 = 1;
asm volatile("cpuid"
: "=a"(eax1), "=b"(ebx1), "=c"(ecx1), "=d"(edx1)
: "a"(eax1), "c"(ecx1));
bool has_avx512_bf16 = (eax1 & (1 << 5)) != 0;
cpuid_count(7, 1, regs);
const bool has_avx512_bf16 = (regs[0] & (1 << 5)) != 0;
if (has_avx512_bf16 && has_avx512_fp16) {
detected_level = SIMDLevel::AVX512_SPR;
supported_simd_levels |=
@@ -238,7 +267,7 @@ SIMDLevel SIMDConfig::auto_detect_simd_level() {
#endif // defined(COMPILE_SIMD_AVX512)
#endif // defined(COMPILE_SIMD_AVX2) || defined(COMPILE_SIMD_AVX512)
}
#endif // defined(__x86_64__) && ...
#endif // defined(__x86_64__) || defined(_M_X64)
#ifdef COMPILE_SIMD_ARM_NEON
// ARM NEON is standard on aarch64
+2 -1
View File
@@ -849,7 +849,8 @@ class TestIndexResidualQuantizerSearch(unittest.TestCase):
self.assertLess((Iref != I2).sum(), Iref.size * 0.05)
else:
inter_2 = faiss.eval_intersection(I2, gt)
self.assertGreaterEqual(inter_ref, inter_2)
# many near-ties, quantized norms can marginally beat the reference
self.assertGreaterEqual(inter_2, inter_ref * 0.95)
###########################################################