Skip to content

Commit 5c8261d

Browse files
Subhadeep Karanfacebook-github-bot
authored andcommitted
SIMDConfig object + SIMD detections (facebookresearch#4552)
Summary: Implementation of: - `SIMDLevel` that can be used as a template parameter to differentiate implementations - `SIMDConfig` object that defines what is the enabled SIMDLevel in the TARGETS all SIMD code is disabled by default. Reviewed By: mnorris11 Differential Revision: D72937710
1 parent 69f1ac0 commit 5c8261d

File tree

3 files changed

+534
-0
lines changed

3 files changed

+534
-0
lines changed

faiss/utils/simd_levels.cpp

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include <faiss/utils/simd_levels.h>
9+
10+
#include <faiss/impl/FaissAssert.h>
11+
#include <cstdlib>
12+
13+
namespace faiss {
14+
15+
SIMDLevel SIMDConfig::level = SIMDLevel::NONE;
16+
std::unordered_set<SIMDLevel>& SIMDConfig::supported_simd_levels() {
17+
static std::unordered_set<SIMDLevel> levels;
18+
return levels;
19+
}
20+
21+
// it is there to make sure the constructor runs
22+
static SIMDConfig dummy_config;
23+
24+
SIMDConfig::SIMDConfig(const char** faiss_simd_level_env) {
25+
// added to support dependency injection
26+
const char* env_var = faiss_simd_level_env ? *faiss_simd_level_env
27+
: getenv("FAISS_SIMD_LEVEL");
28+
29+
// check environment variable for SIMD level is explicitly set
30+
if (!env_var) {
31+
level = auto_detect_simd_level();
32+
} else {
33+
auto matched_level = to_simd_level(env_var);
34+
if (matched_level.has_value()) {
35+
set_level(matched_level.value());
36+
supported_simd_levels().clear();
37+
supported_simd_levels().insert(matched_level.value());
38+
} else {
39+
fprintf(stderr,
40+
"FAISS_SIMD_LEVEL is set to %s, which is unknown\n",
41+
env_var);
42+
exit(1);
43+
}
44+
}
45+
supported_simd_levels().insert(SIMDLevel::NONE);
46+
}
47+
48+
void SIMDConfig::set_level(SIMDLevel l) {
49+
level = l;
50+
}
51+
52+
SIMDLevel SIMDConfig::get_level() {
53+
return level;
54+
}
55+
56+
std::string SIMDConfig::get_level_name() {
57+
return to_string(level).value_or("");
58+
}
59+
60+
bool SIMDConfig::is_simd_level_available(SIMDLevel l) {
61+
return supported_simd_levels().find(l) != supported_simd_levels().end();
62+
}
63+
64+
SIMDLevel SIMDConfig::auto_detect_simd_level() {
65+
SIMDLevel level = SIMDLevel::NONE;
66+
67+
#if defined(__x86_64__) && \
68+
(defined(COMPILE_SIMD_AVX2) || defined(COMPILE_SIMD_AVX512))
69+
unsigned int eax, ebx, ecx, edx;
70+
71+
eax = 1;
72+
ecx = 0;
73+
asm volatile("cpuid"
74+
: "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
75+
: "a"(eax), "c"(ecx));
76+
77+
bool has_avx = (ecx & (1 << 28)) != 0;
78+
79+
bool has_xsave_osxsave =
80+
(ecx & ((1 << 26) | (1 << 27))) == ((1 << 26) | (1 << 27));
81+
82+
bool avx_supported = false;
83+
if (has_avx && has_xsave_osxsave) {
84+
unsigned int xcr0;
85+
asm volatile("xgetbv" : "=a"(xcr0), "=d"(edx) : "c"(0));
86+
avx_supported = (xcr0 & 6) == 6;
87+
}
88+
89+
if (avx_supported) {
90+
eax = 7;
91+
ecx = 0;
92+
asm volatile("cpuid"
93+
: "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
94+
: "a"(eax), "c"(ecx));
95+
96+
unsigned int xcr0;
97+
asm volatile("xgetbv" : "=a"(xcr0), "=d"(edx) : "c"(0));
98+
99+
#if defined(COMPILE_SIMD_AVX2) || defined(COMPILE_SIMD_AVX512)
100+
bool has_avx2 = (ebx & (1 << 5)) != 0;
101+
if (has_avx2) {
102+
SIMDConfig::supported_simd_levels().insert(SIMDLevel::AVX2);
103+
level = SIMDLevel::AVX2;
104+
}
105+
106+
#if defined(COMPILE_SIMD_AVX512)
107+
bool cpu_has_avx512f = (ebx & (1 << 16)) != 0;
108+
bool os_supports_avx512 = (xcr0 & 0xE0) == 0xE0;
109+
bool has_avx512f = cpu_has_avx512f && os_supports_avx512;
110+
if (has_avx512f) {
111+
bool has_avx512cd = (ebx & (1 << 28)) != 0;
112+
bool has_avx512vl = (ebx & (1 << 31)) != 0;
113+
bool has_avx512dq = (ebx & (1 << 17)) != 0;
114+
bool has_avx512bw = (ebx & (1 << 30)) != 0;
115+
if (has_avx512bw && has_avx512cd && has_avx512vl && has_avx512dq) {
116+
level = SIMDLevel::AVX512;
117+
supported_simd_levels().insert(SIMDLevel::AVX512);
118+
}
119+
}
120+
#endif // defined(COMPILE_SIMD_AVX512)
121+
#endif // defined(COMPILE_SIMD_AVX2)|| defined(COMPILE_SIMD_AVX512)
122+
}
123+
#endif // defined(__x86_64__) && (defined(COMPILE_SIMD_AVX2) ||
124+
// defined(COMPILE_SIMD_AVX512))
125+
126+
#if defined(__aarch64__) && defined(__ARM_NEON) && \
127+
defined(COMPILE_SIMD_ARM_NEON)
128+
// ARM NEON is standard on aarch64, so we can assume it's available
129+
supported_simd_levels().insert(SIMDLevel::ARM_NEON);
130+
level = SIMDLevel::ARM_NEON;
131+
132+
// TODO: Add ARM SVE detection when needed
133+
// For now, we default to ARM_NEON as it's universally supported on aarch64
134+
#endif
135+
136+
return level;
137+
}
138+
139+
std::optional<std::string> to_string(SIMDLevel level) {
140+
switch (level) {
141+
case SIMDLevel::NONE:
142+
return "NONE";
143+
case SIMDLevel::AVX2:
144+
return "AVX2";
145+
case SIMDLevel::AVX512:
146+
return "AVX512";
147+
case SIMDLevel::ARM_NEON:
148+
return "ARM_NEON";
149+
default:
150+
return std::nullopt;
151+
}
152+
return std::nullopt;
153+
}
154+
155+
std::optional<SIMDLevel> to_simd_level(const std::string& level_str) {
156+
if (level_str == "NONE") {
157+
return SIMDLevel::NONE;
158+
}
159+
if (level_str == "AVX2") {
160+
return SIMDLevel::AVX2;
161+
}
162+
if (level_str == "AVX512") {
163+
return SIMDLevel::AVX512;
164+
}
165+
if (level_str == "ARM_NEON") {
166+
return SIMDLevel::ARM_NEON;
167+
}
168+
169+
return std::nullopt;
170+
}
171+
172+
} // namespace faiss

faiss/utils/simd_levels.h

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <optional>
11+
#include <string>
12+
#include <unordered_set>
13+
14+
namespace faiss {
15+
16+
#define COMPILE_SIMD_NONE
17+
18+
enum class SIMDLevel {
19+
NONE,
20+
// x86
21+
AVX2,
22+
AVX512,
23+
// arm & aarch64
24+
ARM_NEON,
25+
26+
COUNT
27+
};
28+
29+
std::optional<std::string> to_string(SIMDLevel level);
30+
31+
std::optional<SIMDLevel> to_simd_level(const std::string& level_str);
32+
33+
/* Current SIMD configuration. This static class manages the current SIMD level
34+
* and intializes it from the cpuid and the FAISS_SIMD_LEVEL
35+
* environment variable */
36+
struct SIMDConfig {
37+
static SIMDLevel level;
38+
static std::unordered_set<SIMDLevel>& supported_simd_levels();
39+
40+
typedef SIMDLevel (*DetectSIMDLevelFunc)();
41+
static SIMDLevel auto_detect_simd_level();
42+
43+
SIMDConfig(const char** faiss_simd_level_env = nullptr);
44+
45+
static void set_level(SIMDLevel level);
46+
static SIMDLevel get_level();
47+
static std::string get_level_name();
48+
49+
static bool is_simd_level_available(SIMDLevel level);
50+
};
51+
52+
/*********************** x86 SIMD */
53+
54+
#ifdef COMPILE_SIMD_AVX2
55+
#define DISPATCH_SIMDLevel_AVX2(f, ...) \
56+
case SIMDLevel::AVX2: \
57+
return f<SIMDLevel::AVX2>(__VA_ARGS__)
58+
#else
59+
#define DISPATCH_SIMDLevel_AVX2(f, ...)
60+
#endif
61+
62+
#ifdef COMPILE_SIMD_AVX512
63+
#define DISPATCH_SIMDLevel_AVX512(f, ...) \
64+
case SIMDLevel::AVX512F: \
65+
return f<SIMDLevel::AVX512>(__VA_ARGS__)
66+
#else
67+
#define DISPATCH_SIMDLevel_AVX512(f, ...)
68+
#endif
69+
70+
/* dispatch function f to f<SIMDLevel> */
71+
72+
#define DISPATCH_SIMDLevel(f, ...) \
73+
switch (SIMDConfig::level) { \
74+
case SIMDLevel::NONE: \
75+
return f<SIMDLevel::NONE>(__VA_ARGS__); \
76+
DISPATCH_SIMDLevel_AVX2(f, __VA_ARGS__); \
77+
DISPATCH_SIMDLevel_AVX512(f, __VA_ARGS__); \
78+
default: \
79+
FAISS_ASSERT(!"Invalid SIMD level"); \
80+
}
81+
82+
} // namespace faiss

0 commit comments

Comments
 (0)