usearch 2.5.0 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +4 -3
- package/CITATION.cff +1 -1
- package/Cargo.toml +1 -1
- package/README.md +1 -1
- package/conanfile.py +1 -1
- package/cpp/CMakeLists.txt +4 -0
- package/csharp/nuget/nuget-package.props +1 -1
- package/include/usearch/index.hpp +1 -1
- package/include/usearch/index_dense.hpp +12 -5
- package/include/usearch/index_plugins.hpp +2 -6
- package/package.json +1 -1
- package/python/lib.cpp +26 -23
- package/python/scripts/test_tooling.py +2 -2
- package/python/usearch/index.py +8 -3
- package/setup.py +8 -2
- package/wasmer.toml +1 -1
|
@@ -591,8 +591,10 @@ jobs:
|
|
|
591
591
|
GITHUB_PACKAGES_SOURCE: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json
|
|
592
592
|
steps:
|
|
593
593
|
- uses: actions/checkout@v3
|
|
594
|
+
with:
|
|
595
|
+
ref: 'main'
|
|
594
596
|
- run: git submodule update --init --recursive
|
|
595
|
-
|
|
597
|
+
|
|
596
598
|
- name: Download usearch libs artifact
|
|
597
599
|
uses: actions/download-artifact@v3
|
|
598
600
|
with:
|
|
@@ -606,8 +608,7 @@ jobs:
|
|
|
606
608
|
|
|
607
609
|
- name: Pack project
|
|
608
610
|
run: |
|
|
609
|
-
|
|
610
|
-
dotnet pack "${{ env.SOLUTION }}" -c Release -p:Version="$VERSION_CONTENT" --output ${{ env.NUGET_PACKAGES }}
|
|
611
|
+
dotnet pack "${{ env.SOLUTION }}" -c Release --output ${{ env.NUGET_PACKAGES }}
|
|
611
612
|
working-directory: ${{ github.workspace }}
|
|
612
613
|
|
|
613
614
|
- name: Publish to NuGet
|
package/CITATION.cff
CHANGED
package/Cargo.toml
CHANGED
package/README.md
CHANGED
package/conanfile.py
CHANGED
|
@@ -7,7 +7,7 @@ required_conan_version = ">=1.53.0"
|
|
|
7
7
|
class USearchConan(ConanFile):
|
|
8
8
|
|
|
9
9
|
name = "usearch"
|
|
10
|
-
version = '2.5.
|
|
10
|
+
version = '2.5.1'
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
description = "Smaller & Faster Single-File Vector Search Engine from Unum"
|
|
13
13
|
homepage = "https://github.com/unum-cloud/usearch"
|
package/cpp/CMakeLists.txt
CHANGED
|
@@ -20,6 +20,8 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|
|
20
20
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
|
|
21
21
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmax-errors=1")
|
|
22
22
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic")
|
|
23
|
+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always")
|
|
24
|
+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftime-report")
|
|
23
25
|
|
|
24
26
|
if(${USEARCH_USE_OPENMP})
|
|
25
27
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
|
|
@@ -32,6 +34,8 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
|
32
34
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak -fsanitize=alignment -fsanitize=undefined")
|
|
33
35
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wfatal-errors")
|
|
34
36
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic")
|
|
37
|
+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
|
|
38
|
+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftime-trace")
|
|
35
39
|
|
|
36
40
|
if(${USEARCH_USE_OPENMP})
|
|
37
41
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
#include <stdlib.h> // `aligned_alloc`
|
|
3
3
|
|
|
4
|
-
#include <functional>
|
|
5
|
-
#include <numeric>
|
|
6
|
-
#include <
|
|
7
|
-
#include <
|
|
8
|
-
#include <vector> // `std::vector`
|
|
4
|
+
#include <functional> // `std::function`
|
|
5
|
+
#include <numeric> // `std::iota`
|
|
6
|
+
#include <thread> // `std::thread`
|
|
7
|
+
#include <vector> // `std::vector`
|
|
9
8
|
|
|
10
9
|
#include <usearch/index.hpp>
|
|
11
10
|
#include <usearch/index_plugins.hpp>
|
|
12
11
|
|
|
12
|
+
#if defined(USEARCH_DEFINED_CPP17)
|
|
13
|
+
#include <shared_mutex> // `std::shared_mutex`
|
|
14
|
+
#endif
|
|
15
|
+
|
|
13
16
|
namespace unum {
|
|
14
17
|
namespace usearch {
|
|
15
18
|
|
|
@@ -383,7 +386,11 @@ class index_dense_gt {
|
|
|
383
386
|
/// @brief Mutex, controlling concurrent access to `available_threads_`.
|
|
384
387
|
mutable std::mutex available_threads_mutex_;
|
|
385
388
|
|
|
389
|
+
#if defined(USEARCH_DEFINED_CPP17)
|
|
390
|
+
using shared_mutex_t = std::shared_mutex;
|
|
391
|
+
#else
|
|
386
392
|
using shared_mutex_t = unfair_shared_mutex_t;
|
|
393
|
+
#endif
|
|
387
394
|
using shared_lock_t = shared_lock_gt<shared_mutex_t>;
|
|
388
395
|
using unique_lock_t = std::unique_lock<shared_mutex_t>;
|
|
389
396
|
|
|
@@ -42,14 +42,10 @@
|
|
|
42
42
|
|
|
43
43
|
#if USEARCH_USE_SIMSIMD
|
|
44
44
|
#if defined(USEARCH_DEFINED_LINUX)
|
|
45
|
-
#define SIMSIMD_TARGET_X86_AVX2 1
|
|
46
|
-
#define SIMSIMD_TARGET_X86_AVX512 1
|
|
47
|
-
#define SIMSIMD_TARGET_ARM_NEON 1
|
|
48
|
-
#define SIMSIMD_TARGET_ARM_SVE 1
|
|
49
45
|
#include <simsimd/simsimd.h>
|
|
50
46
|
#elif defined(USEARCH_DEFINED_APPLE)
|
|
51
|
-
#define
|
|
52
|
-
#define
|
|
47
|
+
#define SIMSIMD_TARGET_X86_AVX512 0
|
|
48
|
+
#define SIMSIMD_TARGET_ARM_SVE 0
|
|
53
49
|
#include <simsimd/simsimd.h>
|
|
54
50
|
#endif
|
|
55
51
|
#endif
|
package/package.json
CHANGED
package/python/lib.cpp
CHANGED
|
@@ -526,22 +526,26 @@ static void search_typed_brute_force( //
|
|
|
526
526
|
byte_t const* dataset_data = reinterpret_cast<byte_t const*>(dataset_info.ptr);
|
|
527
527
|
byte_t const* queries_data = reinterpret_cast<byte_t const*>(queries_info.ptr);
|
|
528
528
|
for (std::size_t query_idx = 0; query_idx != queries_count; ++query_idx)
|
|
529
|
-
counts_py1d(query_idx) =
|
|
529
|
+
counts_py1d(query_idx) = wanted;
|
|
530
530
|
|
|
531
531
|
if (!threads)
|
|
532
532
|
threads = std::thread::hardware_concurrency();
|
|
533
533
|
|
|
534
534
|
std::size_t tasks_count = static_cast<std::size_t>(dataset_count * queries_count);
|
|
535
|
-
bitset_t query_mutexes(static_cast<std::size_t>(queries_count));
|
|
536
|
-
if (!query_mutexes)
|
|
537
|
-
throw std::bad_alloc();
|
|
538
535
|
|
|
539
536
|
// Progress status
|
|
540
537
|
progress_t progress_{progress};
|
|
541
538
|
std::atomic<std::size_t> processed{0};
|
|
542
539
|
|
|
540
|
+
// Allocate temporary memory to store the distance matrix
|
|
541
|
+
// Previous version didn't need temporary memory, but the performance was much lower
|
|
542
|
+
struct dense_key_and_distance_t {
|
|
543
|
+
u32_t offset;
|
|
544
|
+
f32_t distance;
|
|
545
|
+
};
|
|
546
|
+
std::vector<dense_key_and_distance_t> keys_and_distances(tasks_count);
|
|
547
|
+
|
|
543
548
|
executor_default_t{threads}.dynamic(tasks_count, [&](std::size_t thread_idx, std::size_t task_idx) {
|
|
544
|
-
//
|
|
545
549
|
std::size_t dataset_idx = task_idx / queries_count;
|
|
546
550
|
std::size_t query_idx = task_idx % queries_count;
|
|
547
551
|
|
|
@@ -549,24 +553,7 @@ static void search_typed_brute_force( //
|
|
|
549
553
|
byte_t const* query = queries_data + query_idx * queries_info.strides[0];
|
|
550
554
|
distance_t distance = metric(dataset, query);
|
|
551
555
|
|
|
552
|
-
{
|
|
553
|
-
auto lock = query_mutexes.lock(query_idx);
|
|
554
|
-
dense_key_t* keys = &keys_py2d(query_idx, 0);
|
|
555
|
-
distance_t* distances = &distances_py2d(query_idx, 0);
|
|
556
|
-
std::size_t& matches = reinterpret_cast<std::size_t&>(counts_py1d(query_idx));
|
|
557
|
-
if (matches == wanted)
|
|
558
|
-
if (distances[wanted - 1] <= distance)
|
|
559
|
-
return true;
|
|
560
|
-
|
|
561
|
-
std::size_t offset = std::lower_bound(distances, distances + matches, distance) - distances;
|
|
562
|
-
|
|
563
|
-
std::size_t count_worse = matches - offset - (wanted == matches);
|
|
564
|
-
std::memmove(keys + offset + 1, keys + offset, count_worse * sizeof(dense_key_t));
|
|
565
|
-
std::memmove(distances + offset + 1, distances + offset, count_worse * sizeof(distance_t));
|
|
566
|
-
keys[offset] = static_cast<dense_key_t>(dataset_idx);
|
|
567
|
-
distances[offset] = distance;
|
|
568
|
-
matches += matches != wanted;
|
|
569
|
-
}
|
|
556
|
+
keys_and_distances[task_idx] = {static_cast<u32_t>(query_idx), static_cast<f32_t>(distance)};
|
|
570
557
|
|
|
571
558
|
// We don't want to check for signals from multiple threads
|
|
572
559
|
++processed;
|
|
@@ -576,6 +563,20 @@ static void search_typed_brute_force( //
|
|
|
576
563
|
return true;
|
|
577
564
|
});
|
|
578
565
|
|
|
566
|
+
// Partial-sort every query result
|
|
567
|
+
executor_default_t{threads}.fixed(queries_count, [&](std::size_t, std::size_t query_idx) {
|
|
568
|
+
auto start = keys_and_distances.data() + query_idx * dataset_count;
|
|
569
|
+
std::partial_sort(start, start + wanted, start + dataset_count,
|
|
570
|
+
[](dense_key_and_distance_t const& a, dense_key_and_distance_t const& b) {
|
|
571
|
+
return a.distance < b.distance;
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
dense_key_t* keys = &keys_py2d(query_idx, 0);
|
|
575
|
+
distance_t* distances = &distances_py2d(query_idx, 0);
|
|
576
|
+
for (std::size_t i = 0; i != wanted; ++i)
|
|
577
|
+
keys[i] = static_cast<dense_key_t>(start[i].offset), distances[i] = start[i].distance;
|
|
578
|
+
});
|
|
579
|
+
|
|
579
580
|
// At the end report the latest numbers, because the reporter thread may be finished earlier
|
|
580
581
|
progress_(processed.load(), tasks_count);
|
|
581
582
|
}
|
|
@@ -602,6 +603,8 @@ static py::tuple search_many_brute_force( //
|
|
|
602
603
|
Py_ssize_t queries_dimensions = queries_info.shape[1];
|
|
603
604
|
if (dataset_dimensions != queries_dimensions)
|
|
604
605
|
throw std::invalid_argument("The number of vector dimensions doesn't match!");
|
|
606
|
+
if (wanted > dataset_count)
|
|
607
|
+
throw std::invalid_argument("You can't request more matches than in the dataset!");
|
|
605
608
|
|
|
606
609
|
scalar_kind_t dataset_kind = numpy_string_to_kind(dataset_info.format);
|
|
607
610
|
scalar_kind_t queries_kind = numpy_string_to_kind(queries_info.format);
|
|
@@ -56,13 +56,13 @@ def test_exact_search(rows: int, cols: int):
|
|
|
56
56
|
:param int cols: The number of columns in the matrix.
|
|
57
57
|
"""
|
|
58
58
|
original = np.random.rand(rows, cols)
|
|
59
|
-
matches: BatchMatches = search(original, original, 10, exact=True)
|
|
59
|
+
matches: BatchMatches = search(original, original, min(10, rows), exact=True)
|
|
60
60
|
top_matches = (
|
|
61
61
|
[int(m.keys[0]) for m in matches] if rows > 1 else int(matches.keys[0])
|
|
62
62
|
)
|
|
63
63
|
assert np.all(top_matches == np.arange(rows))
|
|
64
64
|
|
|
65
|
-
matches: Matches = search(original, original[0], 10, exact=True)
|
|
65
|
+
matches: Matches = search(original, original[0], min(10, rows), exact=True)
|
|
66
66
|
top_match = int(matches.keys[0])
|
|
67
67
|
assert top_match == 0
|
|
68
68
|
|
package/python/usearch/index.py
CHANGED
|
@@ -871,6 +871,10 @@ class Index:
|
|
|
871
871
|
def serialized_length(self) -> int:
|
|
872
872
|
return self._compiled.serialized_length
|
|
873
873
|
|
|
874
|
+
@property
|
|
875
|
+
def metric_kind(self) -> Union[MetricKind, CompiledMetric]:
|
|
876
|
+
return self._metric_jit if self._metric_jit.kind else self._metric_kind
|
|
877
|
+
|
|
874
878
|
@property
|
|
875
879
|
def metric(self) -> Union[MetricKind, CompiledMetric]:
|
|
876
880
|
return self._metric_jit if self._metric_jit else self._metric_kind
|
|
@@ -1190,6 +1194,7 @@ class Index:
|
|
|
1190
1194
|
"size": self.size,
|
|
1191
1195
|
"jit": self.jit,
|
|
1192
1196
|
"hardware_acceleration": self.hardware_acceleration,
|
|
1197
|
+
"metric_kind": self.metric_kind,
|
|
1193
1198
|
"dtype": self.dtype,
|
|
1194
1199
|
"path": self.path,
|
|
1195
1200
|
"compiled_with_openmp": USES_OPENMP,
|
|
@@ -1204,7 +1209,7 @@ class Index:
|
|
|
1204
1209
|
return f.format(
|
|
1205
1210
|
self.dtype,
|
|
1206
1211
|
self.ndim,
|
|
1207
|
-
self.
|
|
1212
|
+
self.metric_kind,
|
|
1208
1213
|
self.connectivity,
|
|
1209
1214
|
self.expansion_add,
|
|
1210
1215
|
self.expansion_search,
|
|
@@ -1225,7 +1230,7 @@ class Index:
|
|
|
1225
1230
|
"- config",
|
|
1226
1231
|
f"-- data type: {self.dtype}",
|
|
1227
1232
|
f"-- dimensions: {self.ndim}",
|
|
1228
|
-
f"-- metric: {self.
|
|
1233
|
+
f"-- metric: {self.metric_kind}",
|
|
1229
1234
|
f"-- connectivity: {self.connectivity}",
|
|
1230
1235
|
f"-- expansion on addition:{self.expansion_add} candidates",
|
|
1231
1236
|
f"-- expansion on search: {self.expansion_search} candidates",
|
|
@@ -1233,7 +1238,7 @@ class Index:
|
|
|
1233
1238
|
f"-- uses OpenMP: {USES_OPENMP}",
|
|
1234
1239
|
f"-- uses SimSIMD: {USES_SIMSIMD}",
|
|
1235
1240
|
f"-- supports half-precision: {USES_NATIVE_F16}",
|
|
1236
|
-
f"-- uses hardware
|
|
1241
|
+
f"-- uses hardware acceleration: {self.hardware_acceleration}",
|
|
1237
1242
|
"- state",
|
|
1238
1243
|
f"-- size: {self.size:,} vectors",
|
|
1239
1244
|
f"-- memory usage: {self.memory_usage:,} bytes",
|
package/setup.py
CHANGED
|
@@ -12,8 +12,11 @@ macros_args = []
|
|
|
12
12
|
if sys.platform == "linux":
|
|
13
13
|
compile_args.append("-std=c++17")
|
|
14
14
|
compile_args.append("-O3") # Maximize performance
|
|
15
|
-
compile_args.append("-g") # Simplify debugging
|
|
16
15
|
compile_args.append("-Wno-unknown-pragmas")
|
|
16
|
+
compile_args.append("-fdiagnostics-color=always")
|
|
17
|
+
|
|
18
|
+
# Simplify debugging, but the normal `-g` may make builds much longer!
|
|
19
|
+
compile_args.append("-g1")
|
|
17
20
|
|
|
18
21
|
macros_args.append(("USEARCH_USE_NATIVE_F16", "0"))
|
|
19
22
|
macros_args.append(("USEARCH_USE_SIMSIMD", "1"))
|
|
@@ -27,9 +30,12 @@ if sys.platform == "darwin":
|
|
|
27
30
|
compile_args.append("-mmacosx-version-min=10.15")
|
|
28
31
|
compile_args.append("-std=c++17")
|
|
29
32
|
compile_args.append("-O3") # Maximize performance
|
|
30
|
-
compile_args.append("-
|
|
33
|
+
compile_args.append("-fcolor-diagnostics")
|
|
31
34
|
compile_args.append("-Wno-unknown-pragmas")
|
|
32
35
|
|
|
36
|
+
# Simplify debugging, but the normal `-g` may make builds much longer!
|
|
37
|
+
compile_args.append("-g1")
|
|
38
|
+
|
|
33
39
|
# Linking OpenMP requires additional preparation in CIBuildWheel
|
|
34
40
|
# macros_args.append(("USEARCH_USE_OPENMP", "1"))
|
|
35
41
|
# compile_args.append("-Xpreprocessor -fopenmp")
|