Skip to content

Commit 8bef920

Browse files
authored
New ISA dispatcher implementation (#133)
This ISA dispatcher allows detecting whether the running machine supports none AVX, AVX2, or AVX512, and it dispatches to the appropriate and optimized code path accordingly.
1 parent b30a462 commit 8bef920

File tree

24 files changed

+671
-574
lines changed

24 files changed

+671
-574
lines changed

CMakeLists.txt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ set(SVS_VERSION_MINOR ${PROJECT_VERSION_MINOR})
3535
set(SVS_VERSION_PATCH ${PROJECT_VERSION_PATCH})
3636

3737
add_library(${SVS_LIB} INTERFACE)
38-
set_target_properties(${SVS_LIB} PROPERTIES EXPORT_NAME svs)
39-
add_library(svs::svs ALIAS ${SVS_LIB})
38+
add_library(svs_export INTERFACE)
39+
set_target_properties(svs_export PROPERTIES EXPORT_NAME svs)
40+
target_link_libraries(svs_export INTERFACE ${SVS_LIB})
41+
add_library(svs::svs ALIAS svs_export)
4042

4143
# Runtime include directories are established in the installation logic.
4244
target_include_directories(
@@ -67,6 +69,13 @@ target_compile_options(
6769

6870
include("cmake/options.cmake")
6971

72+
add_library(svs_x86_options_base INTERFACE)
73+
add_library(svs::x86_options_base ALIAS svs_x86_options_base)
74+
if(CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
75+
target_compile_options(svs_x86_options_base INTERFACE -march=nehalem -mtune=nehalem)
76+
include("cmake/multi-arch.cmake")
77+
endif()
78+
7079
include("cmake/clang-tidy.cmake")
7180
include("cmake/eve.cmake")
7281
include("cmake/pthread.cmake")
@@ -112,7 +121,7 @@ set(LIB_CONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/svs")
112121

113122
# Install headers and target information.
114123
install(
115-
TARGETS svs_devel svs_compile_options svs_native_options
124+
TARGETS svs_export svs_devel svs_compile_options svs_native_options
116125
EXPORT svs-targets
117126
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
118127
)

benchmark/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@ endif()
102102
target_link_libraries(
103103
svs_benchmark_library
104104
PUBLIC
105-
${SVS_LIB}
105+
svs::svs
106106
svs_compile_options
107-
svs_native_options
107+
svs_x86_options_base
108108
fmt::fmt
109109
)
110110

bindings/python/CMakeLists.txt

Lines changed: 16 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -24,68 +24,9 @@ FetchContent_Declare(
2424
)
2525
FetchContent_MakeAvailable(pybind11)
2626

27-
# Try to find the Python executable.
28-
#
29-
# If it's given as part of the Cmake arguments given by "scikit build", then use that.
30-
# Otherwise, fall back to using plain old "python".
31-
# If *THAT* doesn't work, give up.
32-
if(DEFINED PYTHON_EXECUTABLE)
33-
set(SVS_PYTHON_EXECUTABLE "${PYTHON_EXECUTABLE}")
34-
else()
35-
set(SVS_PYTHON_EXECUTABLE "python")
36-
endif()
37-
38-
# The micro architectures to compile for.
39-
if(NOT DEFINED SVS_MICROARCHS)
40-
set(SVS_MICROARCHS native)
41-
endif()
42-
4327
# Include the SVS library directly.
4428
add_subdirectory("../.." "${CMAKE_CURRENT_BINARY_DIR}/svs")
4529

46-
# Run the python script to get optimization flags for the desired back-ends.
47-
#
48-
# FLAGS_SCRIPT - Path to the Python script that will take the compiler, compiler version,
49-
# and list of desired microarchitectures and generate optimization flags for each
50-
# microarchitecture.
51-
#
52-
# FLAGS_TEXT_FILE - List of optimization flags for each architecture.
53-
# Expected format:
54-
# -march=arch1,-mtune=arch1
55-
# -march=arch2,-mtune=arch2
56-
# ...
57-
# -march=archN,-mtune=archN
58-
#
59-
# The number of lines should be equal to the number of microarchitectures.
60-
# NOTE: The entries within each line are separated by a comma on purpose to allow CMake
61-
# to read the whole file as a List and then use string replacement on the commas to turn
62-
# each line into a list in its own right.
63-
#
64-
# TEMP_JSON - JSON Manifest file describing the generated binaries. This is meant to be
65-
# included in the Python package to allow the Python code to reason about the packaged
66-
# libraries and select the correct one for loading.
67-
#
68-
set(FLAGS_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/microarch.py")
69-
set(FLAGS_TEXT_FILE "${CMAKE_CURRENT_BINARY_DIR}/optimization_flags.txt")
70-
set(FLAGS_MANIFEST_JSON "${CMAKE_CURRENT_BINARY_DIR}/flags_manifest.json")
71-
72-
execute_process(
73-
COMMAND
74-
${SVS_PYTHON_EXECUTABLE}
75-
${FLAGS_SCRIPT}
76-
${FLAGS_TEXT_FILE}
77-
${FLAGS_MANIFEST_JSON}
78-
--compiler ${CMAKE_CXX_COMPILER_ID}
79-
--compiler-version ${CMAKE_CXX_COMPILER_VERSION}
80-
--microarchitectures ${SVS_MICROARCHS}
81-
COMMAND_ERROR_IS_FATAL ANY
82-
)
83-
84-
file(STRINGS "${FLAGS_TEXT_FILE}" OPTIMIZATION_FLAGS)
85-
message("Flags: ${OPTIMIZATION_FLAGS}")
86-
list(LENGTH OPTIMIZATION_FLAGS OPT_FLAGS_LENGTH)
87-
message("Length of flags: ${OPT_FLAGS_LENGTH}")
88-
8930
# C++ files makind up the python bindings.
9031
set(CPP_FILES
9132
src/allocator.cpp
@@ -98,50 +39,26 @@ set(CPP_FILES
9839
src/svs_mkl.cpp
9940
)
10041

101-
# Generate a shared library for each target microarchitecture.
102-
foreach(MICRO OPT_FLAGS IN ZIP_LISTS SVS_MICROARCHS OPTIMIZATION_FLAGS)
103-
set(LIB_NAME "_svs_${MICRO}")
104-
105-
pybind11_add_module(${LIB_NAME} MODULE ${CPP_FILES})
106-
target_link_libraries(${LIB_NAME} PUBLIC svs::svs)
107-
# Dependency "fmt::fmt" obtained from "svs"
108-
target_link_libraries(${LIB_NAME} PRIVATE svs::compile_options fmt::fmt)
109-
110-
string(REPLACE "," ";" OPT_FLAGS ${OPT_FLAGS})
111-
message("OPT Flags: ${OPT_FLAGS}")
112-
target_compile_options(${LIB_NAME} PRIVATE ${OPT_FLAGS})
113-
114-
# Header files.
115-
target_include_directories(
42+
set(LIB_NAME "_svs")
43+
pybind11_add_module(${LIB_NAME} MODULE ${CPP_FILES})
44+
target_link_libraries(${LIB_NAME} PRIVATE pybind11::module)
45+
target_link_libraries(${LIB_NAME} PUBLIC svs::svs)
46+
# Dependency "fmt::fmt" obtained from "svs"
47+
target_link_libraries(${LIB_NAME} PRIVATE svs::compile_options fmt::fmt svs_x86_options_base)
48+
target_include_directories(
49+
${LIB_NAME}
50+
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>
51+
)
52+
if(DEFINED SKBUILD)
53+
install(TARGETS ${LIB_NAME} DESTINATION .)
54+
set_target_properties(
11655
${LIB_NAME}
117-
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>
56+
PROPERTIES
57+
INSTALL_RPATH "$ORIGIN/${CMAKE_INSTALL_LIBDIR}"
11858
)
119-
120-
# Comunicate to the C++ library the desired name of the library
121-
target_compile_options(${LIB_NAME} PRIVATE "-DSVS_MODULE_NAME=${LIB_NAME}")
122-
123-
# If scikit build is running the compilation process,
124-
if(DEFINED SKBUILD)
125-
install(TARGETS ${LIB_NAME} DESTINATION .)
126-
127-
# The extension module may need to load build or included libraries when loaded.
128-
129-
# Placing build depedencies in the package and using relative RPATHs that
130-
# don't point outside of the package means that the built package is
131-
# relocatable. This allows for safe binary redistribution.
132-
set_target_properties(
133-
${LIB_NAME}
134-
PROPERTIES
135-
INSTALL_RPATH "$ORIGIN/${CMAKE_INSTALL_LIBDIR}"
136-
)
137-
endif()
138-
endforeach()
59+
endif()
13960

14061
if(DEFINED SKBUILD)
141-
# Install the manifest JSON file.
142-
# This is kind of a hack to avoid the needing to explicitly move JSON file into the
143-
# source folder of the python library.
144-
install(FILES ${FLAGS_MANIFEST_JSON} DESTINATION .)
14562

14663
# Install header files.
14764
install(

bindings/python/setup.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
# limitations under the License.
1414

1515
from skbuild import setup
16-
import archspec.cpu as cpu
1716
import os
1817

1918
# If building in a cibuildwheel context, compile multiple versions of the library for
@@ -25,27 +24,6 @@
2524
"-DCMAKE_EXPORT_COMPILE_COMMANDS=YES",
2625
]
2726

28-
# Utility to convert micro-architecture strings to
29-
def target(arch):
30-
return cpu.TARGETS[arch]
31-
32-
# N.B.: cibuildwheel must configure the multi-arch environment variable.
33-
# Also, the micro-architectures defined below should be in order of preference.
34-
if os.environ.get("SVS_MULTIARCH", None) is not None:
35-
svs_microarchs = [
36-
"cascadelake",
37-
"x86_64_v3", # conservative base CPU for x86 CPUs.
38-
]
39-
40-
# Add the current host to the list of micro-architecture if it doesn't already exist.
41-
last_target = target(svs_microarchs[-1])
42-
host_name = cpu.host().name
43-
if host_name not in svs_microarchs and target(host_name) < last_target:
44-
svs_microarchs.append(host_name)
45-
46-
cmake_array = ";".join(svs_microarchs)
47-
cmake_args.append(f"-DSVS_MICROARCHS={cmake_array}")
48-
4927
# Determine the root of the repository
5028
base_dir = os.path.relpath(os.path.join(os.path.dirname(__file__), '..', '..'))
5129

bindings/python/src/python_bindings.cpp

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,6 @@
4444
#include <filesystem>
4545
#include <optional>
4646

47-
// Get the expected name of the library
48-
// Make sure CMake stays up to date with defining this parameter.
49-
//
50-
// The variable allows us to customize the name of the python module to support
51-
// micro-architecture versioning.
52-
#if !defined(SVS_MODULE_NAME)
53-
#define SVS_MODULE_NAME _svs_native
54-
#endif
55-
5647
namespace py = pybind11;
5748

5849
namespace {
@@ -144,7 +135,7 @@ class ScopedModuleNameOverride {
144135

145136
} // namespace
146137

147-
PYBIND11_MODULE(SVS_MODULE_NAME, m) {
138+
PYBIND11_MODULE(_svs, m) {
148139
// Internall, the top level `__init__.py` imports everything from the C++ module named
149140
// `_svs`.
150141
//

bindings/python/src/svs/__init__.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,10 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
# Dynamic loading logic.
16-
from .loader import library, current_backend, available_backends
15+
import svs._svs as _svs
1716

18-
# Reexport all public functions and structs from the inner module.
19-
lib = library()
2017
globals().update(
21-
{k : v for (k, v) in lib.__dict__.items() if not k.startswith("__")}
18+
{k : v for (k, v) in _svs.__dict__.items() if not k.startswith("__")}
2219
)
2320

2421
# Misc types and functions

bindings/python/src/svs/common.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
import os
1919

2020
import numpy as np
21-
from .loader import library
22-
lib = library()
21+
import svs._svs as _svs
2322

2423
def np_to_svs(nptype):
2524
"""
@@ -34,29 +33,29 @@ def np_to_svs(nptype):
3433
"""
3534
# Signed
3635
if nptype == np.int8:
37-
return lib.int8
36+
return _svs.int8
3837
if nptype == np.int16:
39-
return lib.int16
38+
return _svs.int16
4039
if nptype == np.int32:
41-
return lib.int32
40+
return _svs.int32
4241
if nptype == np.int64:
43-
return lib.int64
42+
return _svs.int64
4443
# Unsigned
4544
if nptype == np.uint8:
46-
return lib.uint8
45+
return _svs.uint8
4746
if nptype == np.uint16:
48-
return lib.uint16
47+
return _svs.uint16
4948
if nptype == np.uint32:
50-
return lib.uint32
49+
return _svs.uint32
5150
if nptype == np.uint64:
52-
return lib.uint64
51+
return _svs.uint64
5352
# Float
5453
if nptype == np.float16:
55-
return lib.float16
54+
return _svs.float16
5655
if nptype == np.float32:
57-
return lib.float32
56+
return _svs.float32
5857
if nptype == np.float64:
59-
return lib.float64
58+
return _svs.float64
6059

6160
raise Exception(f"Could not convert {nptype} to a svs.DataType enum!");
6261

@@ -221,7 +220,7 @@ def generate_test_dataset(
221220
query_seed = None,
222221
num_threads = 1,
223222
num_neighbors: int = 100,
224-
distance = lib.DistanceType.L2
223+
distance = _svs.DistanceType.L2
225224
):
226225
"""
227226
Generate a sample dataset consisting of the base data, queries, and groundtruth all in
@@ -259,7 +258,7 @@ def generate_test_dataset(
259258
write_vecs(queries, os.path.join(directory, "queries.fvecs"))
260259

261260
print("Generating Groundtruth")
262-
index = lib.Flat(data, distance, num_threads = num_threads)
261+
index = _svs.Flat(data, distance, num_threads = num_threads)
263262
I, _ = index.search(queries, num_neighbors)
264263
write_vecs(I.astype(np.uint32), os.path.join(directory, "groundtruth.ivecs"))
265264

0 commit comments

Comments
 (0)