Skip to content

Commit a3d59f0

Browse files
Michael Norrismeta-codesync[bot]
authored andcommitted
faiss DD: use 256-bit fast-scan QBS kernel on AMD Zen 4 (split AVX-512) (#5488)
Summary: Pull Request resolved: #5488 ## TLDR make PQFS faster on AMD Bergamo specifically, without regressing for RaBitQFS, by changing AVX512 --> AVX2 for the FastScan part. The RaBitQ avx512 popcount is still avx512, it is just the FastScan part that stays on AVX2. Under dynamic dispatch (`faiss.dynamic_dispatch=true`) the fast-scan QBS search path picks the 512-bit accumulate kernel whenever the runtime SIMD level is AVX-512. On AMD Zen 4 / Zen 4c ("Bergamo", family 0x19) that is the wrong choice: Zen 4 splits 512-bit ops over two separate 256-bit parts. - This can execute operations in 2 separate operations, so 2 cycles compared to a single cycle in avx2. It can sometimes be slower or faster depending on the operation, because more new instructions are supported in avx512 like vpopcnt which are completely missing in AVX2. - Why split at all? because older CPUs had to downclock for avx512 instructions, while this approach does not, so it can be faster for certain workloads, and it allows support for avx512 instructions for free. Captured from benchmark infra, matched AVX2-static vs AVX-512-DD operating points): - Bergamo `PQ8x4fs`: median AVX-512/AVX2 = 0.861 (nq=1) / 0.860 (batched) across 15 datasets, spread [0.857, 0.867], 14/15 datasets below 0.95 (worst `sift-1M` = 0.60). - Bergamo `PQ16x4fs`: 0.870 (nq=1) / 0.878 (batched) across 14 datasets. The magnitude is tight and reproduces on ~every dataset in both the single-query and batched regimes, well below the Bergamo benchmark noise floor (SVS-Vamana negative control median ~1.05), so it is real signal. It is isolated to the bbs=32 QBS path: `PQ*x4fs_64` (bbs=64) and `IVF*,PQ*x4fs` are unaffected, and on Intel Skylake / Cooper Lake the same factories are FASTER under AVX-512 (1.01-1.71x), so the fix must be keyed to Zen 4 only. Fix (3 files): - `simd_levels.{h,cpp}`: add `SIMDConfig::avx512_split`, set by raw-CPUID detection (vendor == AuthenticAMD && display_family == 0x19), run once at load time from `auto_detect_simd_level()` in both DD and static builds. - `dispatching.h` `ScannerMixIn::accumulate_loop_qbs`: this is the live QBS search dispatch for BOTH PQ fast-scan and RaBitQ fast-scan. When the CPU splits avx512, route to the 256-bit (AVX2) QBS kernel (`pq4_accumulate_loop_qbs_fixed_scaler_256<AVX2>`, the same one the existing unknown-qbs fallback already uses) instead of the 512-bit kernel. Process- constant runtime branch, hoisted out of the inner accumulate loop. Intel AVX-512 keeps the 512-bit kernel unchanged. Note: the fix is placed in `dispatching.h` (the path IndexFastScan / IndexIVFFastScan search actually take, via `ScannerMixIn`), NOT in `decompose_qbs.h` -- the QBS block kernel there is only reached by the `accumulate_to_mem` test utility, not by any search path, so routing it there would be inert for search. Scope: RaBitQ fast-scan shares this same PQ4 QBS accumulate, so it is also routed to 256-bit on Zen 4. That is not expected to regress it -- the 256-bit kernel is >= the 512-bit one on Zen 4 (the whole premise), and RaBitQ's AVX-512 popcount (folded into the query LUT) and multibit FP-refine kernels are untouched. An earlier version of this diff also added a Cooper Lake RaBitQ-popcount cap and a native `vpopcntq` path; the Cooper Lake regression that motivated them did not survive additional benchmark runs, so both were dropped to keep this a single-purpose, evidence-backed fix. Validated on Bergamo (see Test Plan) Reviewed By: junjieqi Differential Revision: D113617130 fbshipit-source-id: c59b522134f40cc7a7daf6abaa3c3f6d5b139fe1
1 parent 4d74915 commit a3d59f0

3 files changed

Lines changed: 85 additions & 2 deletions

File tree

faiss/impl/fast_scan/dispatching.h

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
#include <faiss/impl/fast_scan/accumulate_loops.h>
3535
#include <faiss/impl/fast_scan/fast_scan.h>
36+
#include <faiss/utils/simd_levels.h>
3637

3738
#if defined(COMPILE_SIMD_AVX512) && defined(__AVX512F__)
3839
#include <faiss/impl/fast_scan/accumulate_loops_512.h>
@@ -114,8 +115,40 @@ struct ScannerMixIn : FastScanCodeScanner {
114115
constexpr bool use_avx512_qbs = false;
115116
#endif
116117
if constexpr (use_avx512_qbs) {
117-
// Use 512-bit QBS kernels with properly-leveled scalers.
118-
if (pq2x4_scale) {
118+
// AMD Zen 4 / Zen 4c ("Bergamo", family 0x19) split 512-bit
119+
// ops over a 256-bit datapath, so the 512-bit QBS kernel yields no
120+
// throughput gain but pays extra per-block LUT-assembly and
121+
// cross-lane reduction overhead (measured ~14% search regression
122+
// for PQ8x4fs / PQ16x4fs). Route those CPUs to the 256-bit (AVX2)
123+
// QBS kernel instead -- same output, no downside on Zen 4. This is
124+
// a process-constant runtime branch, hoisted out of the inner
125+
// accumulate loop. Intel AVX-512 keeps the 512-bit kernel.
126+
if (SIMDConfig::avx512_split) {
127+
if (pq2x4_scale) {
128+
NormTableScaler<SIMDLevel::AVX2> scaler(pq2x4_scale);
129+
pq4_accumulate_loop_qbs_fixed_scaler_256<SIMDLevel::AVX2>(
130+
qbs,
131+
nb,
132+
nsq,
133+
codes,
134+
LUT,
135+
handler_,
136+
scaler,
137+
block_stride);
138+
} else {
139+
DummyScaler<SIMDLevel::AVX2> dummy;
140+
pq4_accumulate_loop_qbs_fixed_scaler_256<SIMDLevel::AVX2>(
141+
qbs,
142+
nb,
143+
nsq,
144+
codes,
145+
LUT,
146+
handler_,
147+
dummy,
148+
block_stride);
149+
}
150+
} else if (pq2x4_scale) {
151+
// Use 512-bit QBS kernels with properly-leveled scalers.
119152
NormTableScaler<THE_LEVEL_TO_DISPATCH> scaler(pq2x4_scale);
120153
pq4_accumulate_loop_qbs_fixed_scaler_512(
121154
qbs,

faiss/utils/simd_levels.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ SIMDLevel SIMDConfig::level = SIMDLevel::NONE;
2020
// Bitmask of supported SIMD levels (1 << SIMDLevel)
2121
uint64_t SIMDConfig::supported_simd_levels = 0;
2222

23+
// Microarchitecture flags (x86). Default false; set by
24+
// detect_x86_uarch_flags() at load time.
25+
bool SIMDConfig::avx512_split = false;
26+
2327
// ARM SVE runtime detection
2428
#if defined(__aarch64__) || defined(_M_ARM64)
2529

@@ -53,6 +57,43 @@ static bool has_sve() {
5357
}
5458
#endif
5559

60+
// Detect x86 microarchitecture flags used for kernel routing. Uses raw
61+
// cpuid so it is safe to run on any CPU regardless of compiled SIMD level.
62+
#if defined(__x86_64__)
63+
namespace {
64+
void detect_x86_uarch_flags() {
65+
unsigned int eax, ebx, ecx, edx;
66+
67+
// Vendor string (CPUID.0): "AuthenticAMD" is EBX="Auth", EDX="enti",
68+
// ECX="cAMD".
69+
eax = 0;
70+
ecx = 0;
71+
asm volatile("cpuid"
72+
: "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
73+
: "a"(eax), "c"(ecx));
74+
const bool is_amd =
75+
ebx == 0x68747541u && edx == 0x69746e65u && ecx == 0x444d4163u;
76+
77+
// Family/model (CPUID.1 EAX).
78+
eax = 1;
79+
ecx = 0;
80+
asm volatile("cpuid"
81+
: "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
82+
: "a"(eax), "c"(ecx));
83+
const unsigned int base_family = (eax >> 8) & 0xfu;
84+
const unsigned int display_family =
85+
base_family + (base_family == 0xfu ? ((eax >> 20) & 0xffu) : 0u);
86+
// AMD Zen 4 / Zen 4c (Bergamo) is family 0x19 and splits AVX-512.
87+
// (Zen 5, family 0x1A, has a native 512-bit datapath and is excluded.)
88+
SIMDConfig::avx512_split = is_amd && display_family == 0x19u;
89+
}
90+
} // namespace
91+
#else
92+
namespace {
93+
void detect_x86_uarch_flags() {}
94+
} // namespace
95+
#endif
96+
5697
#ifdef FAISS_ENABLE_DD
5798

5899
// =============================================================================
@@ -101,6 +142,8 @@ bool SIMDConfig::is_simd_level_available(SIMDLevel l) {
101142
SIMDLevel SIMDConfig::auto_detect_simd_level() {
102143
SIMDLevel detected_level = SIMDLevel::NONE;
103144

145+
detect_x86_uarch_flags();
146+
104147
#if defined(__x86_64__) && \
105148
(defined(COMPILE_SIMD_AVX2) || defined(COMPILE_SIMD_AVX512))
106149
unsigned int eax, ebx, ecx, edx;
@@ -264,6 +307,7 @@ bool SIMDConfig::is_simd_level_available(SIMDLevel l) {
264307
}
265308

266309
SIMDLevel SIMDConfig::auto_detect_simd_level() {
310+
detect_x86_uarch_flags();
267311
// In static mode, return the compiled-in level
268312
#if defined(COMPILE_SIMD_AVX512_SPR)
269313
return SIMDLevel::AVX512_SPR;

faiss/utils/simd_levels.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ struct FAISS_API SIMDConfig {
161161
/// Returns bitmask of supported SIMD levels (1 << SIMDLevel).
162162
static uint64_t supported_simd_levels;
163163

164+
/// CPU implements AVX-512 by splitting over a 256-bit datapath
165+
/// (AMD Zen 4 / Zen 4c "Bergamo", family 0x19). On such CPUs 512-bit
166+
/// ops give no throughput gain, so the fast-scan QBS path prefers the
167+
/// 256-bit kernel.
168+
static bool avx512_split;
169+
164170
static SIMDLevel auto_detect_simd_level();
165171

166172
static constexpr bool has_dynamic_dispatch() {

0 commit comments

Comments
 (0)