usearch 0.18.8 → 0.19.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.
@@ -3,34 +3,34 @@
3
3
  # Python tooling, linters, and static analyzers. It also embeds JIT
4
4
  # into the primary `Index` class, connecting USearch with Numba.
5
5
  from math import sqrt
6
- from typing import Callable
7
6
 
8
- import numpy as np
9
7
  from numba import cfunc, types, carray
10
8
 
11
- from usearch.compiled import MetricKind, ScalarKind
9
+ from usearch.index import MetricKind, ScalarKind, MetricSignature, CompiledMetric
12
10
 
13
11
 
14
- signature_i8args = types.float32(types.CPointer(
15
- types.int8), types.CPointer(types.int8))
12
+ signature_i8args = types.float32(types.CPointer(types.int8), types.CPointer(types.int8))
16
13
 
17
- signature_f16args = types.float32(types.CPointer(
18
- types.float16), types.CPointer(types.float16))
14
+ signature_f16args = types.float32(
15
+ types.CPointer(types.float16), types.CPointer(types.float16)
16
+ )
19
17
 
20
- signature_f32args = types.float32(types.CPointer(
21
- types.float32), types.CPointer(types.float32))
18
+ signature_f32args = types.float32(
19
+ types.CPointer(types.float32), types.CPointer(types.float32)
20
+ )
22
21
 
23
- signature_f64args = types.float32(types.CPointer(
24
- types.float64), types.CPointer(types.float64))
22
+ signature_f64args = types.float32(
23
+ types.CPointer(types.float64), types.CPointer(types.float64)
24
+ )
25
25
 
26
26
 
27
- def jit(ndim: int,
28
- metric: MetricKind = MetricKind.Cos,
29
- dtype: ScalarKind = ScalarKind.F32) -> Callable:
27
+ def jit(
28
+ ndim: int, metric: MetricKind = MetricKind.Cos, dtype: ScalarKind = ScalarKind.F32
29
+ ) -> CompiledMetric:
30
30
  """JIT-compiles a distance metric specifically tuned for the target hardware
31
31
  and number of dimensions.
32
32
 
33
- Uses Numba `cfunc` functionality, annotating it with Numba `types` instead
33
+ Uses Numba `cfunc` functionality, annotating it with Numba `types` instead
34
34
  of `ctypes` to support half-precision.
35
35
  https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
36
36
  """
@@ -88,8 +88,7 @@ def jit(ndim: int,
88
88
  b_array = carray(b, ndim)
89
89
  ab_delta_sq = accumulator(0)
90
90
  for i in range(ndim):
91
- ab_delta_sq += (a_array[i] - b_array[i]) * \
92
- (a_array[i] - b_array[i])
91
+ ab_delta_sq += (a_array[i] - b_array[i]) * (a_array[i] - b_array[i])
93
92
  return types.float32(ab_delta_sq)
94
93
 
95
94
  scalar_kind_to_signature = {
@@ -108,4 +107,9 @@ def jit(ndim: int,
108
107
  if dtype == ScalarKind.F8 and metric == MetricKind.IP:
109
108
  metric = MetricKind.Cos
110
109
 
111
- return cfunc(scalar_kind_to_signature[dtype])(metric_kind_to_function[metric])
110
+ pointer = cfunc(scalar_kind_to_signature[dtype])(metric_kind_to_function[metric])
111
+ return CompiledMetric(
112
+ pointer=pointer,
113
+ kind=metric,
114
+ signature=MetricSignature.ArrayArray,
115
+ )
@@ -26,10 +26,13 @@ def _ascii_to_vector(string: str) -> np.ndarray:
26
26
 
27
27
 
28
28
  def serve(
29
- ndim_: int, metric: str = 'ip',
30
- port: int = 8545, threads: int = 1,
31
- path: str = 'index.usearch', immutable: bool = False):
32
-
29
+ ndim_: int,
30
+ metric: str = "ip",
31
+ port: int = 8545,
32
+ threads: int = 1,
33
+ path: str = "index.usearch",
34
+ immutable: bool = False,
35
+ ):
33
36
  server = Server(port=port)
34
37
  index = Index(ndim=ndim_, metric=metric)
35
38
 
@@ -57,7 +60,7 @@ def serve(
57
60
 
58
61
  @server
59
62
  def add_one(label: int, vector: np.ndarray):
60
- print('adding', label, vector)
63
+ print("adding", label, vector)
61
64
  labels = np.array([label], dtype=Label)
62
65
  vectors = vector.flatten().reshape(vector.shape[0], 1)
63
66
  index.add(labels, vectors)
@@ -68,7 +71,7 @@ def serve(
68
71
 
69
72
  @server
70
73
  def search_one(vector: np.ndarray, count: int) -> List[dict]:
71
- print('search', vector, count)
74
+ print("search", vector, count)
72
75
  vectors = vector.reshape(vector.shape[0], 1)
73
76
  results: Matches = index.search(vectors, count)
74
77
  return results.to_list()
@@ -93,33 +96,42 @@ def serve(
93
96
  index.save(path)
94
97
 
95
98
 
96
- if __name__ == '__main__':
97
-
99
+ if __name__ == "__main__":
98
100
  parser = argparse.ArgumentParser()
99
- parser.add_argument('-v', '--verbose', help='log server activity')
100
- parser.add_argument(
101
- '--ndim', type=int,
102
- help='dimensionality of the vectors')
101
+ parser.add_argument("-v", "--verbose", help="log server activity")
102
+ parser.add_argument("--ndim", type=int, help="dimensionality of the vectors")
103
103
  parser.add_argument(
104
- '--immutable', type=bool, default=False,
105
- help='the index can not be updated')
104
+ "--immutable", type=bool, default=False, help="the index can not be updated"
105
+ )
106
106
 
107
107
  parser.add_argument(
108
- '--metric', type=str, default='ip', choices=['ip', 'cos', 'l2sq', 'haversine'],
109
- help='distance function to compare vectors')
108
+ "--metric",
109
+ type=str,
110
+ default="ip",
111
+ choices=["ip", "cos", "l2sq", "haversine"],
112
+ help="distance function to compare vectors",
113
+ )
110
114
  parser.add_argument(
111
- '-p', '--port', type=int, default=8545,
112
- help='port to open for client connections')
115
+ "-p",
116
+ "--port",
117
+ type=int,
118
+ default=8545,
119
+ help="port to open for client connections",
120
+ )
113
121
  parser.add_argument(
114
- '-j', '--threads', type=int, default=1,
115
- help='number of CPU threads to use')
122
+ "-j", "--threads", type=int, default=1, help="number of CPU threads to use"
123
+ )
116
124
  parser.add_argument(
117
- '--path', type=str, default='index.usearch',
118
- help='where to store the index')
125
+ "--path", type=str, default="index.usearch", help="where to store the index"
126
+ )
119
127
 
120
128
  args = parser.parse_args()
121
- assert args.ndim is not None, 'Define the number of dimensions!'
129
+ assert args.ndim is not None, "Define the number of dimensions!"
122
130
  serve(
123
- ndim_=args.ndim, metric=args.metric,
124
- threads=args.threads, port=args.port,
125
- path=args.path, immutable=args.immutable)
131
+ ndim_=args.ndim,
132
+ metric=args.metric,
133
+ threads=args.threads,
134
+ port=args.port,
135
+ path=args.path,
136
+ immutable=args.immutable,
137
+ )
package/setup.py CHANGED
@@ -5,97 +5,94 @@ from setuptools import setup
5
5
  from pybind11.setup_helpers import Pybind11Extension
6
6
 
7
7
 
8
- compile_args = ['-std=c++11', '-O3']
8
+ compile_args = []
9
9
  link_args = []
10
10
  macros_args = [
11
- ('USEARCH_USE_NATIVE_F16', '0'),
12
- ('USEARCH_USE_SIMSIMD', '0'),
11
+ ("USEARCH_USE_NATIVE_F16", "0"),
12
+ ("USEARCH_USE_SIMSIMD", "0"),
13
13
  ]
14
14
 
15
- if sys.platform == 'linux':
16
- compile_args.append('-fopenmp')
17
- link_args.append('-lgomp')
18
- compile_args.append('-Wno-unknown-pragmas')
19
- macros_args.append(('USEARCH_USE_OPENMP', '1'))
15
+ if sys.platform == "linux":
16
+ compile_args.append("-std=c++11")
17
+ compile_args.append("-O3")
18
+ compile_args.append("-Wno-unknown-pragmas")
20
19
 
21
- if sys.platform == 'darwin':
20
+ macros_args.append(("USEARCH_USE_OPENMP", "1"))
21
+ compile_args.append("-fopenmp")
22
+ link_args.append("-lgomp")
23
+
24
+ if sys.platform == "darwin":
22
25
  # MacOS 10.15 or higher is needed for `aligned_alloc` support.
23
26
  # https://github.com/unum-cloud/usearch/actions/runs/4975434891/jobs/8902603392
24
- compile_args.append('-mmacosx-version-min=10.15')
25
- compile_args.append('-Wno-unknown-pragmas')
27
+ compile_args.append("-mmacosx-version-min=10.15")
28
+ compile_args.append("-std=c++11")
29
+ compile_args.append("-O3")
30
+ compile_args.append("-Wno-unknown-pragmas")
26
31
 
27
- if sys.platform == 'win32':
28
- compile_args = ['/std:c++14', '/O2']
32
+ # Linking OpenMP requires additional preparion in CIBuildWheel
33
+ # macros_args.append(("USEARCH_USE_OPENMP", "1"))
34
+ # compile_args.append("-Xpreprocessor -fopenmp")
35
+ # link_args.append("-Xpreprocessor -lomp")
29
36
 
37
+ if sys.platform == "win32":
38
+ compile_args.append("/std:c++14")
39
+ compile_args.append("/O2")
30
40
 
31
41
  ext_modules = [
32
42
  Pybind11Extension(
33
- 'usearch.compiled',
34
- ['python/lib.cpp'],
43
+ "usearch.compiled",
44
+ ["python/lib.cpp"],
35
45
  extra_compile_args=compile_args,
36
46
  extra_link_args=link_args,
37
47
  define_macros=macros_args,
38
48
  ),
39
49
  ]
40
50
 
41
- __version__ = open('VERSION', 'r').read().strip()
42
- __lib_name__ = 'usearch'
51
+ __version__ = open("VERSION", "r").read().strip()
52
+ __lib_name__ = "usearch"
43
53
 
44
54
 
45
55
  this_directory = os.path.abspath(os.path.dirname(__file__))
46
- with open(os.path.join(this_directory, 'README.md')) as f:
56
+ with open(os.path.join(this_directory, "README.md")) as f:
47
57
  long_description = f.read()
48
58
 
49
59
 
50
60
  setup(
51
-
52
61
  name=__lib_name__,
53
62
  version=__version__,
54
- packages=['usearch'],
55
- package_dir={'usearch': 'python/usearch'},
56
-
57
- description='Smaller & Faster Single-File Vector Search Engine from Unum',
63
+ packages=["usearch"],
64
+ package_dir={"usearch": "python/usearch"},
65
+ description="Smaller & Faster Single-File Vector Search Engine from Unum",
58
66
  long_description=long_description,
59
- long_description_content_type='text/markdown',
60
- license='Apache-2.0',
61
-
67
+ long_description_content_type="text/markdown",
68
+ license="Apache-2.0",
62
69
  classifiers=[
63
- 'Development Status :: 5 - Production/Stable',
64
-
65
- 'Natural Language :: English',
66
- 'Intended Audience :: Developers',
67
- 'Intended Audience :: Information Technology',
68
- 'License :: OSI Approved :: Apache Software License',
69
-
70
- 'Programming Language :: C++',
71
- 'Programming Language :: Python :: 3 :: Only',
72
- 'Programming Language :: Python :: Implementation :: CPython',
73
- 'Programming Language :: Java',
74
- 'Programming Language :: JavaScript',
75
- 'Programming Language :: Objective C',
76
- 'Programming Language :: Rust',
77
- 'Programming Language :: Other',
78
-
79
- 'Operating System :: MacOS',
80
- 'Operating System :: Unix',
81
- 'Operating System :: Microsoft :: Windows',
82
-
83
- 'Topic :: System :: Clustering',
84
- 'Topic :: Database :: Database Engines/Servers',
85
- 'Topic :: Scientific/Engineering :: Artificial Intelligence',
70
+ "Development Status :: 5 - Production/Stable",
71
+ "Natural Language :: English",
72
+ "Intended Audience :: Developers",
73
+ "Intended Audience :: Information Technology",
74
+ "License :: OSI Approved :: Apache Software License",
75
+ "Programming Language :: C++",
76
+ "Programming Language :: Python :: 3 :: Only",
77
+ "Programming Language :: Python :: Implementation :: CPython",
78
+ "Programming Language :: Java",
79
+ "Programming Language :: JavaScript",
80
+ "Programming Language :: Objective C",
81
+ "Programming Language :: Rust",
82
+ "Programming Language :: Other",
83
+ "Operating System :: MacOS",
84
+ "Operating System :: Unix",
85
+ "Operating System :: Microsoft :: Windows",
86
+ "Topic :: System :: Clustering",
87
+ "Topic :: Database :: Database Engines/Servers",
88
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
86
89
  ],
87
-
88
- include_dirs=[
89
- 'include',
90
- 'fp16/include', 'robin-map/include', 'simsimd/include'],
91
-
90
+ include_dirs=["include", "fp16/include", "robin-map/include", "simsimd/include"],
92
91
  ext_modules=ext_modules,
93
92
  install_requires=[
94
- 'numpy',
95
- 'pandas',
93
+ "numpy",
94
+ "pandas",
95
+ "tqdm",
96
96
  'ucall; python_version >= "3.9"',
97
97
  ],
98
- extras_require={
99
- 'jit': ['numba', 'cppyy'],
100
- },
101
98
  )
@@ -63,7 +63,8 @@
63
63
  "streambuf": "cpp",
64
64
  "thread": "cpp",
65
65
  "typeinfo": "cpp",
66
- "complex": "cpp"
66
+ "complex": "cpp",
67
+ "immintrin.h": "c"
67
68
  },
68
69
  "cSpell.words": [
69
70
  "dtype",
@@ -52,7 +52,7 @@ endif()
52
52
 
53
53
  # Define our header-only library.
54
54
  add_library(simsimd INTERFACE)
55
- target_sources(simsimd INTERFACE include/simsimd/simsimd.hpp)
55
+ target_sources(simsimd INTERFACE include/simsimd/simsimd.h)
56
56
  target_include_directories(simsimd INTERFACE "${PROJECT_SOURCE_DIR}/include")
57
57
 
58
58
  # Build the benchmarks if needed.
package/simsimd/README.md CHANGED
@@ -66,8 +66,20 @@ With AVX2 and 256-bit registers the results should be better, but would be less
66
66
 
67
67
  ---
68
68
 
69
- To replicate on your hardware, please run:
69
+ To replicate on your hardware, please run following on Linux:
70
70
 
71
71
  ```sh
72
- cmake -DCMAKE_BUILD_TYPE=Release -DUNSW_BUILD_BENCHMARKS=1 -B ./build && make -C ./build && ./build/simsimd_bench
72
+ cmake -DCMAKE_BUILD_TYPE=Release -DSIMSIMD_BUILD_BENCHMARKS=1 -B ./build && make -C ./build && ./build/simsimd_bench
73
73
  ```
74
+
75
+ MacOS:
76
+
77
+ ```sh
78
+ brew install llvm
79
+ cmake -B ./build \
80
+ -DCMAKE_C_COMPILER="/opt/homebrew/opt/llvm/bin/clang" \
81
+ -DCMAKE_CXX_COMPILER="/opt/homebrew/opt/llvm/bin/clang++" \
82
+ -DSIMSIMD_BUILD_BENCHMARKS=1 \
83
+ && \
84
+ make -C ./build -j && ./build/simsimd_bench
85
+ ```
package/simsimd/bench.cxx CHANGED
@@ -1,46 +1,35 @@
1
+ #include <thread>
2
+
1
3
  #include <benchmark/benchmark.h>
2
4
 
3
- #include <simsimd/simsimd.hpp>
5
+ #include <simsimd/simsimd.h>
4
6
 
5
7
  namespace bm = benchmark;
6
- using namespace av::simsimd;
7
8
 
8
- static constexpr std::size_t threads_k = 64;
9
- static constexpr std::size_t time_k = 100;
9
+ static const std::size_t threads_k = std::thread::hardware_concurrency();
10
+ static const std::size_t time_k = 10;
10
11
 
11
- template <typename metric_at, typename scalar_at, std::size_t bytes_per_vector_ak,
12
+ template <typename scalar_at, typename metric_at, std::size_t bytes_per_vector_ak = 256,
12
13
  std::size_t dimensions_ak = bytes_per_vector_ak / sizeof(scalar_at)> //
13
- static void measure(bm::State& state) {
14
+ static void measure(bm::State& state, metric_at metric) {
14
15
 
15
- constexpr std::size_t buffer_size_k = bytes_per_vector_ak / sizeof(scalar_at);
16
- alignas(64) scalar_at a[buffer_size_k]{};
17
- alignas(64) scalar_at b[buffer_size_k]{};
18
- scalar_at c{};
16
+ alignas(64) scalar_at a[dimensions_ak]{};
17
+ alignas(64) scalar_at b[dimensions_ak]{};
18
+ float c{};
19
19
 
20
- std::fill_n(a, buffer_size_k, static_cast<scalar_at>(1));
21
- std::fill_n(b, buffer_size_k, static_cast<scalar_at>(2));
20
+ std::fill_n(a, dimensions_ak, static_cast<scalar_at>(1));
21
+ std::fill_n(b, dimensions_ak, static_cast<scalar_at>(2));
22
22
 
23
23
  for (auto _ : state)
24
- bm::DoNotOptimize((c = metric_at{}(a, b, dimensions_ak)));
24
+ bm::DoNotOptimize((c = metric(a, b, dimensions_ak)));
25
25
 
26
26
  state.SetBytesProcessed(state.iterations() * bytes_per_vector_ak * 2u);
27
27
  state.SetItemsProcessed(state.iterations());
28
28
  }
29
29
 
30
- BENCHMARK_TEMPLATE(measure, euclidean_distance_t, f32_t, 32)->Threads(threads_k)->MinTime(time_k);
31
- BENCHMARK_TEMPLATE(measure, euclidean_distance_t, f32_t, 256)->Threads(threads_k)->MinTime(time_k);
32
- BENCHMARK_TEMPLATE(measure, euclidean_distance_t, f16_t, 32)->Threads(threads_k)->MinTime(time_k);
33
- BENCHMARK_TEMPLATE(measure, euclidean_distance_t, f16_t, 256)->Threads(threads_k)->MinTime(time_k);
34
-
35
- BENCHMARK_TEMPLATE(measure, cosine_similarity_t, f32_t, 32)->Threads(threads_k)->MinTime(time_k);
36
- BENCHMARK_TEMPLATE(measure, cosine_similarity_t, f32_t, 256)->Threads(threads_k)->MinTime(time_k);
37
- BENCHMARK_TEMPLATE(measure, cosine_similarity_f32x4k_t, f32_t, 32)->Threads(threads_k)->MinTime(time_k);
38
- BENCHMARK_TEMPLATE(measure, cosine_similarity_f32x4k_t, f32_t, 256)->Threads(threads_k)->MinTime(time_k);
39
-
40
- BENCHMARK_TEMPLATE(measure, hamming_bits_distance_t, u64_t, 32, 256)->Threads(threads_k)->MinTime(time_k);
41
- BENCHMARK_TEMPLATE(measure, hamming_bits_distance_t, u64_t, 256, 2048)->Threads(threads_k)->MinTime(time_k);
42
- BENCHMARK_TEMPLATE(measure, hamming_bits_distance_u1x128k_t, u64_t, 32, 256)->Threads(threads_k)->MinTime(time_k);
43
- BENCHMARK_TEMPLATE(measure, hamming_bits_distance_u1x128k_t, u64_t, 256, 2048)->Threads(threads_k)->MinTime(time_k);
30
+ template <typename scalar_at, typename metric_at> void register_(char const* name, metric_at distance_func) {
31
+ bm::RegisterBenchmark(name, measure<scalar_at, metric_at>, distance_func)->Threads(threads_k)->MinTime(time_k);
32
+ }
44
33
 
45
34
  int main(int argc, char** argv) {
46
35
 
@@ -76,6 +65,34 @@ int main(int argc, char** argv) {
76
65
  bm::Initialize(&argc, argv);
77
66
  if (bm::ReportUnrecognizedArguments(argc, argv))
78
67
  return 1;
68
+
69
+ #if defined(__ARM_FEATURE_SVE)
70
+ register_<simsimd_f32_t>("dot_f32sve", simsimd_dot_f32sve);
71
+ register_<simsimd_f32_t>("cos_f32sve", simsimd_cos_f32sve);
72
+ register_<simsimd_f32_t>("l2sq_f32sve", simsimd_l2sq_f32sve);
73
+ register_<std::int16_t>("l2sq_f16sve", simsimd_l2sq_f16sve);
74
+ register_<std::uint8_t>("hamming_b1x8sve", simsimd_hamming_b1x8sve);
75
+ register_<std::uint8_t>("hamming_b1x128sve", simsimd_hamming_b1x128sve);
76
+ #endif
77
+
78
+ #if defined(__ARM_NEON)
79
+ register_<simsimd_f32_t>("dot_f32x4neon", simsimd_dot_f32x4neon);
80
+ register_<std::int16_t>("cos_f16x4neon", simsimd_cos_f16x4neon);
81
+ register_<std::int8_t>("cos_i8x16neon", simsimd_cos_i8x16neon);
82
+ register_<simsimd_f32_t>("cos_f32x4neon", simsimd_cos_f32x4neon);
83
+ #endif
84
+
85
+ #if defined(__AVX2__)
86
+ register_<simsimd_f32_t>("dot_f32x4avx2", simsimd_dot_f32x4avx2);
87
+ register_<std::int8_t>("dot_i8x16avx2", simsimd_dot_i8x16avx2);
88
+ register_<simsimd_f32_t>("cos_f32x4avx2", simsimd_cos_f32x4avx2);
89
+ #endif
90
+
91
+ #if defined(__AVX512F__)
92
+ register_<std::int16_t>("cos_f16x16avx512", simsimd_cos_f16x16avx512);
93
+ register_<std::uint8_t>("hamming_b1x128avx512", simsimd_hamming_b1x128avx512);
94
+ #endif
95
+
79
96
  bm::RunSpecifiedBenchmarks();
80
97
  bm::Shutdown();
81
98
  return 0;
@@ -10,7 +10,8 @@
10
10
  */
11
11
 
12
12
  #pragma once
13
- #include <math.h> // `sqrt`
13
+ #include <math.h> // `sqrt`
14
+ #include <stddef.h> // `size_t`
14
15
 
15
16
  #if defined(__AVX2__)
16
17
  #include <immintrin.h>
package/swift/Test.swift CHANGED
@@ -6,16 +6,20 @@
6
6
  //
7
7
 
8
8
  import Foundation
9
+ import XCTest
9
10
  import USearch
10
11
 
11
12
  @available(iOS 13, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
12
- func testUnit() throws {
13
- let index = USearchIndex.make(metric:USearchMetric.l2sq, dimensions: 4, connectivity: 8, quantization:USearchScalar.F32)
14
- let vectorA: [Float32] = [0.3, 0.5, 1.2, 1.4]
15
- let vectorB: [Float32] = [0.4, 0.2, 1.2, 1.1]
16
- index.add(label: 42, vector: vectorA[...])
17
- index.add(label: 43, vector: vectorB[...])
18
-
19
- let results = index.search(vector: vectorA[...], count: 10)
20
- assert(results.0[0] == 42)
13
+ class Test: XCTestCase {
14
+ func testUnit() throws {
15
+ let index = USearchIndex.make(metric:USearchMetric.l2sq, dimensions: 4, connectivity: 8, quantization:USearchScalar.F32)
16
+ let vectorA: [Float32] = [0.3, 0.5, 1.2, 1.4]
17
+ let vectorB: [Float32] = [0.4, 0.2, 1.2, 1.1]
18
+ index.reserve(2)
19
+ index.add(label: 42, vector: vectorA[...])
20
+ index.add(label: 43, vector: vectorB[...])
21
+
22
+ let results = index.search(vector: vectorA[...], count: 10)
23
+ assert(results.0[0] == 42)
24
+ }
21
25
  }