From c5ecfad2e8c954a80a5cf5379bc9e484b4b72c88 Mon Sep 17 00:00:00 2001 From: Charles Njoroge Date: Thu, 23 Jul 2026 17:11:40 -0500 Subject: [PATCH 1/2] add enable brute force search --- src/index/hnsw/base_hnsw_config.h | 10 ++- .../hnsw/impl/IndexConditionalWrapper.cc | 11 +++ tests/ut/test_faiss_hnsw.cc | 80 +++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/index/hnsw/base_hnsw_config.h b/src/index/hnsw/base_hnsw_config.h index 1f3c2cbb2..d18c67a0e 100644 --- a/src/index/hnsw/base_hnsw_config.h +++ b/src/index/hnsw/base_hnsw_config.h @@ -33,7 +33,10 @@ class BaseHnswConfig : public BaseConfig { CFG_INT overview_levels; CFG_BOOL disable_fallback_brute_force; // default is false, means we will use fallback brute force when hnsw search // does not get enough topk results - KNOHWERE_DECLARE_CONFIG(BaseHnswConfig) { + CFG_BOOL force_brute_force; // default is false, when true the search bypasses graph traversal and + // scans the index storage exhaustively (e.g. quantized codes for + // HNSW_SQ/PQ), optionally refined when the index was built with refine. + KNOWHERE_DECLARE_CONFIG(BaseHnswConfig) { KNOWHERE_CONFIG_DECLARE_FIELD(M).description("hnsw M").set_default(30).set_range(2, 2048).for_train(); KNOWHERE_CONFIG_DECLARE_FIELD(efConstruction) .description("hnsw efConstruction") @@ -56,6 +59,11 @@ class BaseHnswConfig : public BaseConfig { .description("disable fallback brute force") .set_default(false) .for_search(); + KNOWHERE_CONFIG_DECLARE_FIELD(force_brute_force) + .description("force exhaustive brute force search instead of graph traversal") + .set_default(false) + .for_search() + .for_range_search(); } Status diff --git a/src/index/hnsw/impl/IndexConditionalWrapper.cc b/src/index/hnsw/impl/IndexConditionalWrapper.cc index 69410cbdf..a99b6b879 100644 --- a/src/index/hnsw/impl/IndexConditionalWrapper.cc +++ b/src/index/hnsw/impl/IndexConditionalWrapper.cc @@ -38,6 +38,12 @@ WhetherPerformBruteForceSearch(const faiss::Index* index, const BaseConfig& cfg, return std::nullopt; } + // an explicit user request to force brute force overrides the heuristics below + const auto* hnsw_cfg = dynamic_cast(&cfg); + if (hnsw_cfg != nullptr && hnsw_cfg->force_brute_force.has_value() && hnsw_cfg->force_brute_force.value()) { + return true; + } + // decide const auto k = cfg.k.value(); @@ -71,6 +77,11 @@ WhetherPerformBruteForceRangeSearch(const faiss::Index* index, const FaissHnswCo return std::nullopt; } + // an explicit user request to force brute force overrides the heuristics below + if (cfg.force_brute_force.has_value() && cfg.force_brute_force.value()) { + return true; + } + // decide const auto ef = cfg.ef.value(); diff --git a/tests/ut/test_faiss_hnsw.cc b/tests/ut/test_faiss_hnsw.cc index ebd24e55e..934a6a758 100644 --- a/tests/ut/test_faiss_hnsw.cc +++ b/tests/ut/test_faiss_hnsw.cc @@ -2546,3 +2546,83 @@ TEST_CASE("HNSW RangeSearch BF path with empty bitset", "[faiss_hnsw][range_sear REQUIRE(hnsw_total > 0); } } + +// Verifies the `force_brute_force` search param bypasses HNSW graph traversal +// and performs an exhaustive scan of the index storage. For a flat HNSW the +// scan is exact, so results must match a FLAT (IDMAP) oracle exactly. For a +// quantized HNSW (HNSW_SQ) the scan runs over the quantized codes, so recall +// stays high relative to the fp32 oracle. The flag overrides the topk/filter +// heuristics in WhetherPerformBruteForceSearch, so it triggers even for a +// small topk that would otherwise take the regular graph path. +TEST_CASE("HNSW force_brute_force forces exhaustive search", "[faiss_hnsw][brute_force]") { + const int32_t nb = 2000; + const int32_t dim = 32; + const int32_t nq = 10; + const int32_t topk = 20; + const std::string metric = knowhere::metric::L2; + + const auto version = knowhere::Version::GetCurrentVersion().VersionNumber(); + + auto train_ds = GenDataSet(nb, dim, /*seed=*/42); + auto query_ds = GenDataSet(nq, dim, /*seed=*/43); + + knowhere::Json base_conf; + base_conf[knowhere::meta::METRIC_TYPE] = metric; + base_conf[knowhere::meta::DIM] = dim; + base_conf[knowhere::meta::ROWS] = nb; + base_conf[knowhere::meta::TOPK] = topk; + + // FLAT oracle: exact fp32 ground truth. + auto flat_index = knowhere::IndexFactory::Instance() + .Create(knowhere::IndexEnum::INDEX_FAISS_IDMAP, version) + .value(); + REQUIRE(flat_index.Build(train_ds, base_conf) == knowhere::Status::success); + auto golden = flat_index.Search(query_ds, base_conf, nullptr); + REQUIRE(golden.has_value()); + + // Build an HNSW index of the given type and return recall vs. the FLAT oracle. + // `ef == topk` (the minimum allowed) keeps graph-search recall imperfect so + // that forcing brute force is observably different. + auto build_and_recall = [&](const std::string& index_type, const knowhere::Json& extra_conf, bool force_bf) { + knowhere::Json conf = base_conf; + conf[knowhere::meta::INDEX_TYPE] = index_type; + conf[knowhere::indexparam::HNSW_M] = 8; + conf[knowhere::indexparam::EFCONSTRUCTION] = 100; + conf[knowhere::indexparam::EF] = topk; + for (auto it = extra_conf.begin(); it != extra_conf.end(); ++it) { + conf[it.key()] = it.value(); + } + if (force_bf) { + conf["force_brute_force"] = true; + } + auto index = knowhere::IndexFactory::Instance().Create(index_type, version).value(); + REQUIRE(index.Build(train_ds, conf) == knowhere::Status::success); + auto res = index.Search(query_ds, conf, nullptr); + REQUIRE(res.has_value()); + return GetKNNRecall(*golden.value(), *res.value()); + }; + + SECTION("flat HNSW: forced brute force is exact") { + const float recall_graph = + build_and_recall(knowhere::IndexEnum::INDEX_HNSW, knowhere::Json::object(), /*force_bf=*/false); + const float recall_forced = + build_and_recall(knowhere::IndexEnum::INDEX_HNSW, knowhere::Json::object(), /*force_bf=*/true); + INFO("graph recall=" << recall_graph << " forced recall=" << recall_forced); + + // exhaustive exact scan over the flat storage matches the FLAT oracle + REQUIRE(recall_forced == 1.0f); + // forcing brute force can only help relative to graph traversal + REQUIRE(recall_forced >= recall_graph); + } + + SECTION("quantized HNSW_SQ: forced brute force scans all codes") { + knowhere::Json sq_conf; + sq_conf[knowhere::indexparam::SQ_TYPE] = "SQ8"; + const float recall_forced = build_and_recall(knowhere::IndexEnum::INDEX_HNSW_SQ, sq_conf, /*force_bf=*/true); + INFO("forced recall=" << recall_forced); + + // exhaustive scan over the quantized codes stays high-recall vs the + // fp32 oracle (SQ8 quantization error is tiny for this data). + REQUIRE(recall_forced >= 0.9f); + } +} From f8c1e17da3e2d893cc526c3cc802aaed1dc51934 Mon Sep 17 00:00:00 2001 From: Charles Njoroge Date: Sat, 25 Jul 2026 17:13:49 -0500 Subject: [PATCH 2/2] add ability to compile on macos --- .venv-knowhere/pyvenv.cfg | 5 + CMakeLists.txt | 42 +++ cmake/libs/libmilvus-common.cmake | 8 +- include/knowhere/config.h | 3 + scripts/build_local.sh | 302 ++++++++++++++++++ .../hnsw/impl/IndexConditionalWrapper.cc | 1 - tests/ut/CMakeLists.txt | 8 +- 7 files changed, 364 insertions(+), 5 deletions(-) create mode 100644 .venv-knowhere/pyvenv.cfg create mode 100755 scripts/build_local.sh diff --git a/.venv-knowhere/pyvenv.cfg b/.venv-knowhere/pyvenv.cfg new file mode 100644 index 000000000..9de02c3e2 --- /dev/null +++ b/.venv-knowhere/pyvenv.cfg @@ -0,0 +1,5 @@ +home = /opt/homebrew/opt/python@3.11/bin +include-system-site-packages = false +version = 3.11.15 +executable = /opt/homebrew/Cellar/python@3.11/3.11.15_4/Frameworks/Python.framework/Versions/3.11/bin/python3.11 +command = /opt/homebrew/opt/python@3.11/bin/python3.11 -m venv /Users/charles.njoroge/Code/knowhere/.venv-knowhere diff --git a/CMakeLists.txt b/CMakeLists.txt index ce93c0bc2..f7864a16f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,30 @@ endif() find_package(Boost REQUIRED) include_directories(${Boost_INCLUDE_DIRS}) +# On macOS, Apple/Homebrew clang has no built-in OpenMP; point FindOpenMP at +# Homebrew's libomp so find_package(OpenMP) succeeds without the caller (e.g. +# CLion) having to pass -DOpenMP_* flags. Respects values already provided. +if(APPLE AND NOT OpenMP_CXX_FLAGS) + find_program(BREW_EXECUTABLE brew) + if(BREW_EXECUTABLE) + execute_process(COMMAND ${BREW_EXECUTABLE} --prefix libomp + OUTPUT_VARIABLE LIBOMP_PREFIX OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + endif() + if((NOT LIBOMP_PREFIX) AND (EXISTS "/opt/homebrew/opt/libomp")) + set(LIBOMP_PREFIX "/opt/homebrew/opt/libomp") + elseif((NOT LIBOMP_PREFIX) AND (EXISTS "/usr/local/opt/libomp")) + set(LIBOMP_PREFIX "/usr/local/opt/libomp") + endif() + if(LIBOMP_PREFIX) + set(OpenMP_C_FLAGS "-Xclang -fopenmp -I${LIBOMP_PREFIX}/include") + set(OpenMP_C_LIB_NAMES "omp") + set(OpenMP_CXX_FLAGS "-Xclang -fopenmp -I${LIBOMP_PREFIX}/include") + set(OpenMP_CXX_LIB_NAMES "omp") + set(OpenMP_omp_LIBRARY "${LIBOMP_PREFIX}/lib/libomp.dylib") + endif() +endif() + find_package(OpenMP REQUIRED) find_package(folly REQUIRED) @@ -127,6 +151,24 @@ if(OPENMP_FOUND) "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") endif() +if(APPLE) + # Knowhere is normally built inside milvus, which supplies these macOS flags + # via internal/core/CMakeLists.txt. When building standalone we need them too: + # Boost.Stacktrace (pulled in by milvus-common's EasyAssert) requires + # _GNU_SOURCE on Linux; on macOS _Unwind_Backtrace is available without it. + # -D_DARWIN_C_SOURCE exposes BSD symbols; -Wl,-no_fixup_chains works around + # the stricter macOS 14+ (Sonoma) linker. + add_compile_definitions(BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED=1 _DARWIN_C_SOURCE) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-no_fixup_chains") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-no_fixup_chains") + # knowhere and milvus-common call omp_* directly. Unlike gcc's -fopenmp on + # Linux, clang's "-Xclang -fopenmp" (from find_package(OpenMP)) enables OpenMP + # but does NOT auto-link the runtime, so link libomp into every target here. + if(OpenMP_omp_LIBRARY) + link_libraries(${OpenMP_omp_LIBRARY}) + endif() +endif() + include(cmake/utils/platform_check.cmake) include(cmake/utils/compile_flags.cmake) include(cmake/libs/libmilvus-common.cmake) diff --git a/cmake/libs/libmilvus-common.cmake b/cmake/libs/libmilvus-common.cmake index 4d87838ee..23b2942e2 100644 --- a/cmake/libs/libmilvus-common.cmake +++ b/cmake/libs/libmilvus-common.cmake @@ -35,8 +35,12 @@ if ( NOT milvus-common_POPULATED ) add_subdirectory( ${milvus-common_SOURCE_DIR} ${milvus-common_BINARY_DIR} ) - # Link atomic library to milvus-common to fix atomic operations - target_link_libraries(milvus-common PUBLIC atomic) + # Link atomic library to milvus-common to fix atomic operations. + # libatomic is a GCC/Linux runtime; it does not exist on macOS (clang + # provides atomics intrinsically), so linking -latomic there fails. + if(NOT APPLE) + target_link_libraries(milvus-common PUBLIC atomic) + endif() endif() set( MILVUS_COMMON_INCLUDE_DIR ${milvus-common_SOURCE_DIR}/include CACHE INTERNAL "Path to milvus-common include directory" ) diff --git a/include/knowhere/config.h b/include/knowhere/config.h index 4a56ca928..7ce72a52b 100644 --- a/include/knowhere/config.h +++ b/include/knowhere/config.h @@ -574,6 +574,9 @@ class Config { }; #define KNOHWERE_DECLARE_CONFIG(CONFIG) CONFIG() +// Alias for the correctly-spelled macro name (the original above has a typo: +// "KNOHWERE"). Both names are supported so either spelling compiles. +#define KNOWHERE_DECLARE_CONFIG(CONFIG) KNOHWERE_DECLARE_CONFIG(CONFIG) #define KNOWHERE_CONFIG_DECLARE_FIELD(PARAM) \ __DICT__[#PARAM] = knowhere::Config::VarEntry(std::in_place_type>, &PARAM); \ diff --git a/scripts/build_local.sh b/scripts/build_local.sh new file mode 100755 index 000000000..9b703b2ef --- /dev/null +++ b/scripts/build_local.sh @@ -0,0 +1,302 @@ +#!/usr/bin/env bash + +# Licensed to the LF AI & Data foundation under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build Knowhere locally inside a self-contained Python virtualenv so it can be +# opened / debugged in CLion. All Conan tooling is installed into the venv, so +# nothing pollutes your global Python environment. +# +# Usage: +# scripts/build_local.sh [--debug|--release] [--no-ut] [--clean] +# [--diskann] [--configure-only] +# +# Common examples: +# scripts/build_local.sh # Debug build with unit tests +# scripts/build_local.sh --release # optimized build +# scripts/build_local.sh --clean # wipe venv + build/ first + +set -euo pipefail + +# --------------------------- configuration --------------------------------- +BUILD_TYPE="Debug" +WITH_UT="True" +WITH_DISKANN="False" +DO_CLEAN=0 +CONFIGURE_ONLY=0 + +CONAN_VERSION="1.61.0" +CONAN_REMOTE_NAME="default-conan-local" +CONAN_REMOTE_URL="https://milvus01.jfrog.io/artifactory/api/conan/default-conan-local" + +# --------------------------- arg parsing ----------------------------------- +usage() { + grep '^#' "$0" | sed -E 's/^# ?//' | awk 'NR>15' + exit "${1:-0}" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --debug) BUILD_TYPE="Debug" ;; + --release) BUILD_TYPE="Release" ;; + --no-ut) WITH_UT="False" ;; + --diskann) WITH_DISKANN="True" ;; + --clean) DO_CLEAN=1 ;; + --configure-only) CONFIGURE_ONLY=1 ;; + -h|--help) usage 0 ;; + *) echo "Unknown option: $1" >&2; usage 1 ;; + esac + shift +done + +# --------------------------- paths / platform ------------------------------ +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +VENV_DIR="${REPO_ROOT}/.venv-knowhere" +BUILD_DIR="${REPO_ROOT}/build" + +cd "${REPO_ROOT}" + +case "$(uname -s)" in + Darwin*) IS_MAC=1; LIBCXX="libc++" ;; + *) IS_MAC=0; LIBCXX="libstdc++11" ;; +esac + +# macOS toolchain setup. This mirrors how milvus (internal/core/build.sh) and +# knowhere (scripts/python_deps.sh) build on macOS: use Homebrew LLVM/Clang +# (NOT Apple clang) plus Homebrew libomp for OpenMP. Apple clang ships no +# OpenMP runtime, and its very new releases also trip other issues; the tested +# path is Homebrew LLVM. +OPENMP_ARGS=() # extra -D flags so find_package(OpenMP) locates libomp +CONAN_EXTRA_ARGS=() # macOS-only -s/-o overrides for conan install +if [[ "${IS_MAC}" -eq 1 ]]; then + # Detect the newest installed Homebrew LLVM, same order as milvus + # scripts/setenv.sh (17 -> 16 -> 15 -> 14). Conan 1.61 accepts these. + LLVM_VER="" + for v in 17 16 15 14; do + if brew ls --versions "llvm@${v}" >/dev/null 2>&1; then LLVM_VER="${v}"; break; fi + done + if [[ -z "${LLVM_VER}" ]]; then + echo "==> No Homebrew llvm@{17,16,15,14} found; installing llvm@17" + brew install llvm@17 + LLVM_VER="17" + fi + LLVM_PREFIX="$(brew --prefix "llvm@${LLVM_VER}")" + + if ! brew list libomp >/dev/null 2>&1; then + echo "==> Installing libomp via Homebrew (required for OpenMP)" + brew install libomp + fi + OMP_PREFIX="$(brew --prefix libomp)" + + # Use Homebrew clang + libomp, matching milvus scripts/setenv.sh exactly. + export CC="${LLVM_PREFIX}/bin/clang" + export CXX="${LLVM_PREFIX}/bin/clang++" + export ASM="${LLVM_PREFIX}/bin/clang" + export CFLAGS="-Wno-deprecated-declarations -I${OMP_PREFIX}/include ${CFLAGS:-}" + export CXXFLAGS="${CFLAGS}" + export LDFLAGS="-L${OMP_PREFIX}/lib ${LDFLAGS:-}" + + # -s compiler=clang : the default profile detects apple-clang, + # which would mismatch the Homebrew CC/CXX we just exported. + # -o abseil:shared=True: abseil's static archives are built in GNU `ar` + # format (with a '/' symbol-table member) that Apple's ld cannot link + # ("archive member '/' not a mach-o file"). Building abseil shared avoids + # this; this is exactly what milvus does on macOS (internal/core/conanfile.py). + CONAN_EXTRA_ARGS=( + -s compiler=clang -s compiler.version="${LLVM_VER}" + -o abseil:shared=True + ) + + OPENMP_ARGS=( + "-DOpenMP_C_FLAGS=-Xclang -fopenmp -I${OMP_PREFIX}/include" + "-DOpenMP_C_LIB_NAMES=omp" + "-DOpenMP_CXX_FLAGS=-Xclang -fopenmp -I${OMP_PREFIX}/include" + "-DOpenMP_CXX_LIB_NAMES=omp" + "-DOpenMP_omp_LIBRARY=${OMP_PREFIX}/lib/libomp.dylib" + ) +fi + +echo "==> Knowhere local build" +echo " repo root : ${REPO_ROOT}" +echo " build type: ${BUILD_TYPE}" +echo " with_ut : ${WITH_UT}" +echo " libcxx : ${LIBCXX}" +if [[ "${IS_MAC}" -eq 1 ]]; then + echo " compiler : Homebrew llvm@${LLVM_VER} (${CC})" +fi + +# --------------------------- clean ----------------------------------------- +if [[ "${DO_CLEAN}" -eq 1 ]]; then + echo "==> Cleaning venv and build directories" + rm -rf "${VENV_DIR}" "${BUILD_DIR}" +fi + +# --------------------------- pick a compatible python ---------------------- +# Conan 1.61 imports the stdlib `imp` module, which was removed in Python 3.12, +# so the venv MUST be built with Python <= 3.11. Allow override via $PYTHON. +find_python() { + if [[ -n "${PYTHON:-}" ]] && "${PYTHON}" -c 'import sys; exit(0 if sys.version_info[:2] <= (3,11) else 1)' 2>/dev/null; then + echo "${PYTHON}"; return 0 + fi + local candidates=(python3.11 python3.10 python3.9 python3.8) + if [[ "${IS_MAC}" -eq 1 ]]; then + for v in 3.11 3.10 3.9; do + candidates+=("/opt/homebrew/opt/python@${v}/bin/python${v}" "/usr/local/opt/python@${v}/bin/python${v}") + done + fi + for py in "${candidates[@]}"; do + if command -v "${py}" >/dev/null 2>&1 && "${py}" -c 'import sys; exit(0 if sys.version_info[:2] <= (3,11) else 1)' 2>/dev/null; then + echo "${py}"; return 0 + fi + done + return 1 +} + +PYTHON_BIN="$(find_python || true)" +if [[ -z "${PYTHON_BIN}" ]]; then + if [[ "${IS_MAC}" -eq 1 ]]; then + echo "==> No Python <=3.11 found; installing python@3.11 via Homebrew" + brew install python@3.11 + PYTHON_BIN="$(find_python || true)" + fi +fi +if [[ -z "${PYTHON_BIN}" ]]; then + echo "ERROR: Conan 1.61 needs Python <=3.11 (the 'imp' module was removed in 3.12)." >&2 + echo " Install one (e.g. 'brew install python@3.11') or set PYTHON=/path/to/python3.11." >&2 + exit 1 +fi +echo " using python: ${PYTHON_BIN} ($("${PYTHON_BIN}" --version 2>&1))" + +# --------------------------- virtualenv ------------------------------------ +if [[ ! -d "${VENV_DIR}" ]]; then + echo "==> Creating virtualenv at ${VENV_DIR}" + "${PYTHON_BIN}" -m venv "${VENV_DIR}" +fi + +# shellcheck disable=SC1091 +source "${VENV_DIR}/bin/activate" + +python -m pip install --quiet --upgrade pip + +if ! conan --version 2>/dev/null | grep -q "${CONAN_VERSION}"; then + echo "==> Installing conan ${CONAN_VERSION} into venv" + python -m pip install --quiet "conan==${CONAN_VERSION}" +fi +echo " $(conan --version)" + +# Pin CMake < 4 inside the venv. CMake 4.x de-duplicates the repeated +# "-Xarch_x86_64" token that abseil emits for its randen HWAES flags, which +# leaves "-msse4.1" unguarded and makes it get applied to the arm64 target +# (error: unsupported option '-msse4.1' for target 'arm64-...'). CMake 3.x +# keeps both tokens, so the flag stays correctly gated to x86_64. +if ! cmake --version 2>/dev/null | grep -qE 'version 3\.'; then + echo "==> Installing cmake<4 into venv (avoids abseil -msse4.1/arm64 build error)" + python -m pip install --quiet "cmake<4" +fi +echo " using cmake: $(command -v cmake) ($(cmake --version | head -1))" + +# --------------------------- conan profile --------------------------------- +# A fresh conan (in a clean venv) has no default profile; create one. +if ! conan profile show default >/dev/null 2>&1; then + echo "==> Creating default conan profile" + conan profile new default --detect --force +fi + +# --------------------------- conan remote ---------------------------------- +if ! conan remote list | grep -q "${CONAN_REMOTE_NAME}"; then + echo "==> Adding conan remote ${CONAN_REMOTE_NAME}" + conan remote add "${CONAN_REMOTE_NAME}" "${CONAN_REMOTE_URL}" +fi + +# --------------------------- build ----------------------------------------- +mkdir -p "${BUILD_DIR}" +cd "${BUILD_DIR}" + +echo "==> conan install (${BUILD_TYPE})" +conan install .. \ + --build=missing \ + -o with_ut="${WITH_UT}" \ + -o with_diskann="${WITH_DISKANN}" \ + "${CONAN_EXTRA_ARGS[@]}" \ + -s compiler.libcxx="${LIBCXX}" \ + -s build_type="${BUILD_TYPE}" + +# Locate the conan-generated toolchain and derive the matching cmake build dir. +TOOLCHAIN="$(find "${BUILD_DIR}" -name conan_toolchain.cmake 2>/dev/null | head -n1 || true)" +if [[ -z "${TOOLCHAIN}" ]]; then + echo "ERROR: conan_toolchain.cmake not found after conan install" >&2 + exit 1 +fi +TC_DIR="$(dirname "${TOOLCHAIN}")" +if [[ "$(basename "${TC_DIR}")" == "generators" ]]; then + CMAKE_BUILD_DIR="$(dirname "${TC_DIR}")" +else + CMAKE_BUILD_DIR="${TC_DIR}" +fi + +# We drive cmake directly (instead of `conan build`) so the OpenMP hint flags +# get applied. This mirrors exactly what CLion will do on reload. +echo "==> cmake configure (${CMAKE_BUILD_DIR})" +cmake -S "${REPO_ROOT}" -B "${CMAKE_BUILD_DIR}" \ + -DCMAKE_TOOLCHAIN_FILE="${TOOLCHAIN}" \ + -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ + -DWITH_UT="${WITH_UT}" \ + -DWITH_DISKANN="${WITH_DISKANN}" \ + "${OPENMP_ARGS[@]}" + +if [[ "${CONFIGURE_ONLY}" -eq 0 ]]; then + echo "==> cmake build" + cmake --build "${CMAKE_BUILD_DIR}" -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)" +fi + +# Build a single-line CMake options string for CLion, quoting the OpenMP +# flags (they contain spaces) exactly as CLion expects them. +CLION_OPTS="-DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} -DWITH_UT=${WITH_UT} -DWITH_DISKANN=${WITH_DISKANN}" +if [[ "${IS_MAC}" -eq 1 ]]; then + CLION_OPTS="${CLION_OPTS} \"-DOpenMP_C_FLAGS=-Xclang -fopenmp -I${OMP_PREFIX}/include\" -DOpenMP_C_LIB_NAMES=omp \"-DOpenMP_CXX_FLAGS=-Xclang -fopenmp -I${OMP_PREFIX}/include\" -DOpenMP_CXX_LIB_NAMES=omp -DOpenMP_omp_LIBRARY=${OMP_PREFIX}/lib/libomp.dylib" +fi + +MAC_TOOLCHAIN_HINT="" +if [[ "${IS_MAC}" -eq 1 ]]; then + MAC_TOOLCHAIN_HINT=" +CLion Toolchain (Settings > Build, Execution, Deployment > Toolchains): + The deps above were built with Homebrew LLVM. For a matching build, add a + toolchain whose C/C++ compilers point at: + C compiler : ${CC} + C++ compiler: ${CXX} + (Apple clang also works for knowhere itself since the prebuilt deps are + libc++/ABI-compatible, as long as you pass the OpenMP flags below.) +" +fi + +cat < Build, Execution, Deployment > CMake > add profile): + Build type : ${BUILD_TYPE} + Build dir : ${CMAKE_BUILD_DIR} + CMake options (paste the whole line, incl. the OpenMP flags): + ${CLION_OPTS} + +To reuse the venv in your shell: source "${VENV_DIR}/bin/activate" +============================================================================ +EOF diff --git a/src/index/hnsw/impl/IndexConditionalWrapper.cc b/src/index/hnsw/impl/IndexConditionalWrapper.cc index a99b6b879..fc33ee93b 100644 --- a/src/index/hnsw/impl/IndexConditionalWrapper.cc +++ b/src/index/hnsw/impl/IndexConditionalWrapper.cc @@ -43,7 +43,6 @@ WhetherPerformBruteForceSearch(const faiss::Index* index, const BaseConfig& cfg, if (hnsw_cfg != nullptr && hnsw_cfg->force_brute_force.has_value() && hnsw_cfg->force_brute_force.value()) { return true; } - // decide const auto k = cfg.k.value(); diff --git a/tests/ut/CMakeLists.txt b/tests/ut/CMakeLists.txt index f0425205c..4241c56c9 100644 --- a/tests/ut/CMakeLists.txt +++ b/tests/ut/CMakeLists.txt @@ -49,6 +49,10 @@ set_target_properties(knowhere_tests PROPERTIES target_link_libraries(knowhere_tests PRIVATE Catch2::Catch2WithMain - atomic - stdc++fs knowhere) + +# libatomic and libstdc++fs are GCC/Linux runtimes; on macOS clang/libc++ +# provides atomics and std::filesystem directly, so these libs don't exist. +if(NOT APPLE) + target_link_libraries(knowhere_tests PRIVATE atomic stdc++fs) +endif()