usearch 2.25.0 → 2.25.3
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/README.md +43 -47
- package/c/README.md +1 -1
- package/c/lib.cpp +28 -13
- package/c/test.c +41 -0
- package/cpp/test.cpp +99 -0
- package/include/usearch/index.hpp +338 -57
- package/include/usearch/index_dense.hpp +155 -50
- package/include/usearch/index_plugins.hpp +94 -35
- package/javascript/README.md +1 -1
- package/javascript/lib.cpp +4 -2
- package/javascript/usearch.test.js +117 -94
- package/numkong/CITATION.cff +1 -1
- package/numkong/Cargo.toml +1 -1
- package/numkong/bench/bench_cross_amx.cpp +12 -0
- package/numkong/bench/bench_mesh.cpp +12 -0
- package/numkong/bench/bench_spatial.cpp +0 -3
- package/numkong/binding.gyp +18 -0
- package/numkong/build.rs +1 -0
- package/numkong/c/dispatch_e5m2.c +23 -3
- package/numkong/include/numkong/capabilities.h +1 -1
- package/numkong/include/numkong/cast/README.md +3 -0
- package/numkong/include/numkong/cast/haswell.h +28 -64
- package/numkong/include/numkong/cast/serial.h +17 -0
- package/numkong/include/numkong/cast/skylake.h +67 -52
- package/numkong/include/numkong/cast.h +1 -0
- package/numkong/include/numkong/dot/README.md +1 -0
- package/numkong/include/numkong/dot/haswell.h +92 -13
- package/numkong/include/numkong/dot/serial.h +15 -0
- package/numkong/include/numkong/dot/skylake.h +61 -14
- package/numkong/include/numkong/dots/README.md +2 -0
- package/numkong/include/numkong/dots/graniteamx.h +434 -0
- package/numkong/include/numkong/dots/haswell.h +28 -28
- package/numkong/include/numkong/dots/sapphireamx.h +1 -1
- package/numkong/include/numkong/dots/serial.h +23 -8
- package/numkong/include/numkong/dots/skylake.h +28 -23
- package/numkong/include/numkong/dots.h +12 -0
- package/numkong/include/numkong/each/serial.h +18 -1
- package/numkong/include/numkong/geospatial/serial.h +14 -3
- package/numkong/include/numkong/maxsim/serial.h +15 -0
- package/numkong/include/numkong/mesh/README.md +50 -44
- package/numkong/include/numkong/mesh/genoa.h +462 -0
- package/numkong/include/numkong/mesh/haswell.h +806 -933
- package/numkong/include/numkong/mesh/neon.h +871 -943
- package/numkong/include/numkong/mesh/neonbfdot.h +382 -522
- package/numkong/include/numkong/mesh/neonfhm.h +676 -0
- package/numkong/include/numkong/mesh/rvv.h +404 -319
- package/numkong/include/numkong/mesh/serial.h +204 -162
- package/numkong/include/numkong/mesh/skylake.h +1029 -1585
- package/numkong/include/numkong/mesh/v128relaxed.h +403 -377
- package/numkong/include/numkong/mesh.h +38 -0
- package/numkong/include/numkong/reduce/serial.h +15 -1
- package/numkong/include/numkong/sparse/serial.h +17 -2
- package/numkong/include/numkong/spatial/genoa.h +0 -68
- package/numkong/include/numkong/spatial/haswell.h +98 -56
- package/numkong/include/numkong/spatial/serial.h +15 -0
- package/numkong/include/numkong/spatial/skylake.h +114 -54
- package/numkong/include/numkong/spatial.h +0 -12
- package/numkong/include/numkong/spatials/graniteamx.h +128 -0
- package/numkong/include/numkong/spatials/serial.h +18 -1
- package/numkong/include/numkong/spatials/skylake.h +2 -2
- package/numkong/include/numkong/spatials.h +17 -0
- package/numkong/include/numkong/tensor.hpp +107 -23
- package/numkong/javascript/@numkong-darwin-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-darwin-x64/package.json +1 -1
- package/numkong/javascript/@numkong-linux-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-linux-x64/package.json +1 -1
- package/numkong/javascript/@numkong-win32-arm64/package.json +1 -1
- package/numkong/javascript/@numkong-win32-x64/package.json +1 -1
- package/numkong/javascript/numkong.c +3 -2
- package/numkong/package.json +7 -7
- package/numkong/test/test.cu +841 -0
- package/numkong/test/test_cross_amx.cpp +13 -0
- package/numkong/test/test_dlpack.py +492 -0
- package/numkong/test/test_mesh.cpp +12 -0
- package/numkong/test/test_spatial.cpp +0 -3
- package/numkong/test/test_tensor.cpp +87 -27
- package/package.json +1 -1
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ Linux • macOS • Windows • iOS • Android • WebAssembly •
|
|
|
42
42
|
<a href="https://crates.io/crates/usearch"> <img alt="Crate" src="https://img.shields.io/crates/d/usearch?label=Rust%20Crate%20installs"> </a>
|
|
43
43
|
<a href="https://www.nuget.org/packages/Cloud.Unum.USearch"> <img alt="NuGet" src="https://img.shields.io/nuget/dt/Cloud.Unum.USearch?label=CSharp%20NuGet%20installs"> </a>
|
|
44
44
|
<!-- Maven Central publishing is deprecated for now; fat-JAR download is the supported path. -->
|
|
45
|
-
<img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/unum-cloud/
|
|
45
|
+
<img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/unum-cloud/USearch?label=Repo%20size">
|
|
46
46
|
</div>
|
|
47
47
|
|
|
48
48
|
---
|
|
@@ -51,7 +51,7 @@ Linux • macOS • Windows • iOS • Android • WebAssembly •
|
|
|
51
51
|
- ✅ Simple and extensible [single C++11 header][usearch-header] __library__.
|
|
52
52
|
- ✅ [Trusted](#integrations) by giants like Google and DBs like [ClickHouse][clickhouse-docs] & [DuckDB][duckdb-docs].
|
|
53
53
|
- ✅ [SIMD][simd]-optimized and [user-defined metrics](#user-defined-functions) with JIT compilation.
|
|
54
|
-
- ✅ Hardware-agnostic `
|
|
54
|
+
- ✅ Hardware-agnostic `bf16`, `e5m2`, & `i8` - [half-precision & quarter-precision support](#memory-efficiency-downcasting-and-quantization).
|
|
55
55
|
- ✅ [View large indexes from disk](#serialization--serving-index-from-disk) without loading into RAM.
|
|
56
56
|
- ✅ Heterogeneous lookups, renaming/relabeling, and on-the-fly deletions.
|
|
57
57
|
- ✅ Binary Tanimoto and Sorensen coefficients for [Genomics and Chemistry applications](#usearch--rdkit--molecular-search).
|
|
@@ -138,7 +138,7 @@ The default storage/quantization level is hardware-dependant for efficiency, but
|
|
|
138
138
|
index = Index(
|
|
139
139
|
ndim=3, # Define the number of dimensions in input vectors
|
|
140
140
|
metric='cos', # Choose 'l2sq', 'ip', 'haversine' or other metric, default = 'cos'
|
|
141
|
-
dtype='bf16', # Store as 'f64', 'f32', 'f16', 'i8', 'b1'..., default = None
|
|
141
|
+
dtype='bf16', # Store as 'f64', 'f32', 'bf16', 'f16', 'e5m2', 'e4m3', 'e3m2', 'e2m3', 'u8', 'i8', 'b1'..., default = None
|
|
142
142
|
connectivity=16, # Optional: Limit number of neighbors per graph node
|
|
143
143
|
expansion_add=128, # Optional: Control the recall of indexing
|
|
144
144
|
expansion_search=64, # Optional: Control the quality of the search
|
|
@@ -251,7 +251,7 @@ assert!(
|
|
|
251
251
|
Training a quantization model and dimension-reduction is a common approach to accelerate vector search.
|
|
252
252
|
Those, however, are only sometimes reliable, can significantly affect the statistical properties of your data, and require regular adjustments if your distribution shifts.
|
|
253
253
|
Instead, we have focused on high-precision arithmetic over low-precision downcasted vectors.
|
|
254
|
-
The same index, and `add` and `search` operations will automatically down-cast or up-cast between `f64_t`, `f32_t`, `f16_t`, `i8_t`, and single-bit `b1x8_t` representations.
|
|
254
|
+
The same index, and `add` and `search` operations will automatically down-cast or up-cast between `f64_t`, `f32_t`, `bf16_t`, `f16_t`, `e5m2_t`, `e4m3_t`, `e3m2_t`, `e2m3_t`, `u8_t`, `i8_t`, and single-bit `b1x8_t` representations.
|
|
255
255
|
You can use the following command to check, if hardware acceleration is enabled:
|
|
256
256
|
|
|
257
257
|
```sh
|
|
@@ -261,7 +261,9 @@ $ python -c 'from usearch.index import Index; print(Index(ndim=166, metric="tani
|
|
|
261
261
|
> ice
|
|
262
262
|
```
|
|
263
263
|
|
|
264
|
-
In most cases,
|
|
264
|
+
In most cases, `bf16` is recommended for modern CPUs.
|
|
265
|
+
For even smaller footprints, USearch supports IEEE & MX-compatible Float8 (`e5m2` and `e4m3`) and Float6 (`e3m2` and `e2m3`) formats.
|
|
266
|
+
You can pass pre-quantized buffers from [NumKong](https://github.com/ashvardanian/numkong) with the explicit `dtype=` parameter on `add` and `search`, or let USearch handle the quantization internally from higher-precision inputs.
|
|
265
267
|
When quantization is enabled, the "get"-like functions won't be able to recover the original data, so you may want to replicate the original vectors elsewhere.
|
|
266
268
|
When quantizing to `i8_t` integers, note that it's only valid for cosine-like metrics.
|
|
267
269
|
As part of the quantization process, the vectors are normalized to unit length and later scaled to [-127, 127] range to occupy the full 8-bit range.
|
|
@@ -479,46 +481,41 @@ The Haversine distance is available out of the box, but you can also define more
|
|
|
479
481
|
from numba import cfunc, types, carray
|
|
480
482
|
import math
|
|
481
483
|
|
|
482
|
-
# Define the dimension as 2 for latitude and longitude
|
|
483
484
|
ndim = 2
|
|
485
|
+
semi_major, flattening = 6378137.0, 1 / 298.257223563
|
|
486
|
+
semi_minor = (1 - flattening) * semi_major
|
|
487
|
+
|
|
488
|
+
def vincenty_distance(first_ptr, second_ptr):
|
|
489
|
+
first, second = carray(first_ptr, ndim), carray(second_ptr, ndim)
|
|
490
|
+
lat1, lon1, lat2, lon2 = first[0], first[1], second[0], second[1]
|
|
491
|
+
diff_lon = lon2 - lon1
|
|
492
|
+
rlat1, rlat2 = math.atan((1 - flattening) * math.tan(lat1)), math.atan((1 - flattening) * math.tan(lat2))
|
|
493
|
+
sin_rlat1, cos_rlat1 = math.sin(rlat1), math.cos(rlat1)
|
|
494
|
+
sin_rlat2, cos_rlat2 = math.sin(rlat2), math.cos(rlat2)
|
|
495
|
+
lon_on_sphere = diff_lon
|
|
496
|
+
for _ in range(100):
|
|
497
|
+
sin_lon, cos_lon = math.sin(lon_on_sphere), math.cos(lon_on_sphere)
|
|
498
|
+
sin_ang = math.sqrt((cos_rlat2 * sin_lon) ** 2 + (cos_rlat1 * sin_rlat2 - sin_rlat1 * cos_rlat2 * cos_lon) ** 2)
|
|
499
|
+
if sin_ang == 0: return 0.0
|
|
500
|
+
cos_ang = sin_rlat1 * sin_rlat2 + cos_rlat1 * cos_rlat2 * cos_lon
|
|
501
|
+
ang = math.atan2(sin_ang, cos_ang)
|
|
502
|
+
sin_az = cos_rlat1 * cos_rlat2 * sin_lon / sin_ang
|
|
503
|
+
cos2_az = 1 - sin_az ** 2
|
|
504
|
+
cos2_mid = cos_ang - 2 * sin_rlat1 * sin_rlat2 / cos2_az if cos2_az != 0 else 0.0
|
|
505
|
+
corr = flattening / 16 * cos2_az * (4 + flattening * (4 - 3 * cos2_az))
|
|
506
|
+
prev = lon_on_sphere
|
|
507
|
+
lon_on_sphere = diff_lon + (1 - corr) * flattening * (
|
|
508
|
+
sin_az * (ang + corr * sin_ang * (cos2_mid + corr * cos_ang * (-1 + 2 * cos2_mid ** 2))))
|
|
509
|
+
if abs(lon_on_sphere - prev) <= 1e-12: break
|
|
510
|
+
else:
|
|
511
|
+
return float('nan')
|
|
512
|
+
u_sq = cos2_az * (semi_major ** 2 - semi_minor ** 2) / (semi_minor ** 2)
|
|
513
|
+
ca = 1 + u_sq / 16384 * (4096 + u_sq * (-768 + u_sq * (320 - 175 * u_sq)))
|
|
514
|
+
cb = u_sq / 1024 * (256 + u_sq * (-128 + u_sq * (74 - 47 * u_sq)))
|
|
515
|
+
delta = cb * sin_ang * (cos2_mid + cb / 4 * (cos_ang * (-1 + 2 * cos2_mid ** 2)
|
|
516
|
+
- cb / 6 * cos2_mid * (-3 + 4 * sin_ang ** 2) * (-3 + 4 * cos2_mid ** 2)))
|
|
517
|
+
return semi_minor * ca * (ang - delta) / 1000.0
|
|
484
518
|
|
|
485
|
-
# Signature for the custom metric
|
|
486
|
-
signature = types.float32(
|
|
487
|
-
types.CPointer(types.float32),
|
|
488
|
-
types.CPointer(types.float32))
|
|
489
|
-
|
|
490
|
-
# WGS-84 ellipsoid parameters
|
|
491
|
-
a = 6378137.0 # major axis in meters
|
|
492
|
-
f = 1 / 298.257223563 # flattening
|
|
493
|
-
b = (1 - f) * a # minor axis
|
|
494
|
-
|
|
495
|
-
def vincenty_distance(a_ptr, b_ptr):
|
|
496
|
-
a_array = carray(a_ptr, ndim)
|
|
497
|
-
b_array = carray(b_ptr, ndim)
|
|
498
|
-
lat1, lon1, lat2, lon2 = a_array[0], a_array[1], b_array[0], b_array[1]
|
|
499
|
-
L, U1, U2 = lon2 - lon1, math.atan((1 - f) * math.tan(lat1)), math.atan((1 - f) * math.tan(lat2))
|
|
500
|
-
sinU1, cosU1, sinU2, cosU2 = math.sin(U1), math.cos(U1), math.sin(U2), math.cos(U2)
|
|
501
|
-
lambda_, iterLimit = L, 100
|
|
502
|
-
while iterLimit > 0:
|
|
503
|
-
iterLimit -= 1
|
|
504
|
-
sinLambda, cosLambda = math.sin(lambda_), math.cos(lambda_)
|
|
505
|
-
sinSigma = math.sqrt((cosU2 * sinLambda) ** 2 + (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) ** 2)
|
|
506
|
-
if sinSigma == 0: return 0.0 # Co-incident points
|
|
507
|
-
cosSigma, sigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda, math.atan2(sinSigma, cosSigma)
|
|
508
|
-
sinAlpha, cos2Alpha = cosU1 * cosU2 * sinLambda / sinSigma, 1 - (cosU1 * cosU2 * sinLambda / sinSigma) ** 2
|
|
509
|
-
cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cos2Alpha if not math.isnan(cosSigma - 2 * sinU1 * sinU2 / cos2Alpha) else 0 # Equatorial line
|
|
510
|
-
C = f / 16 * cos2Alpha * (4 + f * (4 - 3 * cos2Alpha))
|
|
511
|
-
lambda_, lambdaP = L + (1 - C) * f * (sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM ** 2)))), lambda_
|
|
512
|
-
if abs(lambda_ - lambdaP) <= 1e-12: break
|
|
513
|
-
if iterLimit == 0: return float('nan') # formula failed to converge
|
|
514
|
-
u2 = cos2Alpha * (a ** 2 - b ** 2) / (b ** 2)
|
|
515
|
-
A = 1 + u2 / 16384 * (4096 + u2 * (-768 + u2 * (320 - 175 * u2)))
|
|
516
|
-
B = u2 / 1024 * (256 + u2 * (-128 + u2 * (74 - 47 * u2)))
|
|
517
|
-
deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM ** 2) - B / 6 * cos2SigmaM * (-3 + 4 * sinSigma ** 2) * (-3 + 4 * cos2SigmaM ** 2)))
|
|
518
|
-
s = b * A * (sigma - deltaSigma)
|
|
519
|
-
return s / 1000.0 # Distance in kilometers
|
|
520
|
-
|
|
521
|
-
# Example usage:
|
|
522
519
|
index = Index(ndim=ndim, metric=CompiledMetric(
|
|
523
520
|
pointer=vincenty_distance.address,
|
|
524
521
|
kind=MetricKind.Haversine,
|
|
@@ -547,13 +544,12 @@ index = Index(ndim=ndim, metric=CompiledMetric(
|
|
|
547
544
|
## Citations
|
|
548
545
|
|
|
549
546
|
```bibtex
|
|
550
|
-
@software{
|
|
547
|
+
@software{Vardanian_USearch,
|
|
551
548
|
doi = {10.5281/zenodo.7949416},
|
|
552
549
|
author = {Vardanian, Ash},
|
|
553
550
|
title = {{USearch by Unum Cloud}},
|
|
554
551
|
url = {https://github.com/unum-cloud/USearch},
|
|
555
|
-
version = {2.25.
|
|
556
|
-
year = {
|
|
557
|
-
month = oct,
|
|
552
|
+
version = {2.25.3},
|
|
553
|
+
year = {2026},
|
|
558
554
|
}
|
|
559
555
|
```
|
package/c/README.md
CHANGED
|
@@ -19,7 +19,7 @@ int main() {
|
|
|
19
19
|
usearch_error_t error = NULL;
|
|
20
20
|
usearch_init_options_t opts = {
|
|
21
21
|
.metric_kind = usearch_metric_cos_k,
|
|
22
|
-
.scalar_kind = usearch_scalar_f16_k,
|
|
22
|
+
.scalar_kind = usearch_scalar_f16_k, // or f32_k, bf16_k, e5m2_k, e4m3_k, e3m2_k, e2m3_k, i8_k, u8_k
|
|
23
23
|
.dimensions = dimensions,
|
|
24
24
|
.expansion_add = 0, // for defaults
|
|
25
25
|
.expansion_search = 0 // for defaults
|
package/c/lib.cpp
CHANGED
|
@@ -186,16 +186,14 @@ USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t* options, use
|
|
|
186
186
|
|
|
187
187
|
using state_result_t = typename index_dense_t::state_result_t;
|
|
188
188
|
state_result_t state = index_dense_t::make(metric, config);
|
|
189
|
-
if (!state)
|
|
189
|
+
if (!state) {
|
|
190
190
|
*error = state.error.release();
|
|
191
|
-
|
|
191
|
+
return NULL;
|
|
192
|
+
}
|
|
193
|
+
index_dense_t* result_ptr = new (std::nothrow) index_dense_t(std::move(state.index));
|
|
192
194
|
if (!result_ptr)
|
|
193
195
|
*error = "Out of memory!";
|
|
194
196
|
|
|
195
|
-
// Let's immediately make it usable by reserving enough threads for this machine:
|
|
196
|
-
if (!result_ptr->try_reserve(index_limits_t()))
|
|
197
|
-
*error = "Out of memory when preparing contexts!";
|
|
198
|
-
|
|
199
197
|
return result_ptr;
|
|
200
198
|
}
|
|
201
199
|
|
|
@@ -236,8 +234,10 @@ USEARCH_EXPORT void usearch_metadata(char const* path, usearch_init_options_t* o
|
|
|
236
234
|
|
|
237
235
|
USEARCH_ASSERT(path && options && error && "Missing arguments");
|
|
238
236
|
index_dense_metadata_result_t result = index_dense_metadata_from_path(path);
|
|
239
|
-
if (!result)
|
|
237
|
+
if (!result) {
|
|
240
238
|
*error = result.error.release();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
241
|
|
|
242
242
|
options->metric_kind = metric_kind_to_c(result.head.kind_metric);
|
|
243
243
|
options->quantization = scalar_kind_to_c(result.head.kind_scalar);
|
|
@@ -285,8 +285,10 @@ USEARCH_EXPORT void usearch_metadata_buffer(void const* buffer, size_t length, u
|
|
|
285
285
|
USEARCH_ASSERT(buffer && length && options && error && "Missing arguments");
|
|
286
286
|
index_dense_metadata_result_t result =
|
|
287
287
|
index_dense_metadata_from_buffer(memory_mapped_file_t((byte_t*)(buffer), length));
|
|
288
|
-
if (!result)
|
|
288
|
+
if (!result) {
|
|
289
289
|
*error = result.error.release();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
290
292
|
|
|
291
293
|
options->metric_kind = metric_kind_to_c(result.head.kind_metric);
|
|
292
294
|
options->quantization = scalar_kind_to_c(result.head.kind_scalar);
|
|
@@ -354,7 +356,8 @@ USEARCH_EXPORT void usearch_change_threads_add(usearch_index_t index, size_t thr
|
|
|
354
356
|
auto& index_dense = *reinterpret_cast<index_dense_t*>(index);
|
|
355
357
|
index_limits_t limits = index_dense.limits();
|
|
356
358
|
limits.threads_add = threads;
|
|
357
|
-
index_dense.try_reserve(limits)
|
|
359
|
+
if (!index_dense.try_reserve(limits))
|
|
360
|
+
*error = "Out of memory!";
|
|
358
361
|
}
|
|
359
362
|
|
|
360
363
|
USEARCH_EXPORT void usearch_change_threads_search(usearch_index_t index, size_t threads, usearch_error_t* error) {
|
|
@@ -362,15 +365,22 @@ USEARCH_EXPORT void usearch_change_threads_search(usearch_index_t index, size_t
|
|
|
362
365
|
auto& index_dense = *reinterpret_cast<index_dense_t*>(index);
|
|
363
366
|
index_limits_t limits = index_dense.limits();
|
|
364
367
|
limits.threads_search = threads;
|
|
365
|
-
index_dense.try_reserve(limits)
|
|
368
|
+
if (!index_dense.try_reserve(limits))
|
|
369
|
+
*error = "Out of memory!";
|
|
366
370
|
}
|
|
367
371
|
|
|
368
372
|
USEARCH_EXPORT void usearch_change_metric_kind(usearch_index_t index, usearch_metric_kind_t kind,
|
|
369
373
|
usearch_error_t* error) {
|
|
370
374
|
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
371
375
|
auto& index_dense = *reinterpret_cast<index_dense_t*>(index);
|
|
372
|
-
|
|
373
|
-
metric_punned_t::builtin(index_dense.dimensions(), metric_kind_to_cpp(kind), index_dense.scalar_kind())
|
|
376
|
+
auto metric_punned =
|
|
377
|
+
metric_punned_t::builtin(index_dense.dimensions(), metric_kind_to_cpp(kind), index_dense.scalar_kind());
|
|
378
|
+
if (metric_punned.missing()) {
|
|
379
|
+
*error = "Unsupported metric for this index's dimensions and scalar kind!";
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (!index_dense.try_change_metric(std::move(metric_punned)))
|
|
383
|
+
*error = "Failed to grow cast buffer for the new metric!";
|
|
374
384
|
}
|
|
375
385
|
|
|
376
386
|
USEARCH_EXPORT void usearch_change_metric(usearch_index_t index, usearch_metric_t metric, void* state,
|
|
@@ -384,7 +394,12 @@ USEARCH_EXPORT void usearch_change_metric(usearch_index_t index, usearch_metric_
|
|
|
384
394
|
: metric_punned_t::stateless(index_dense.dimensions(), reinterpret_cast<std::uintptr_t>(metric),
|
|
385
395
|
metric_punned_signature_t::array_array_k, metric_kind_to_cpp(kind),
|
|
386
396
|
index_dense.scalar_kind());
|
|
387
|
-
|
|
397
|
+
if (metric_punned.missing()) {
|
|
398
|
+
*error = "Unsupported metric for this index's dimensions and scalar kind!";
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (!index_dense.try_change_metric(std::move(metric_punned)))
|
|
402
|
+
*error = "Failed to grow cast buffer for the new metric!";
|
|
388
403
|
}
|
|
389
404
|
|
|
390
405
|
USEARCH_EXPORT void usearch_reserve(usearch_index_t index, size_t capacity, usearch_error_t* error) {
|
package/c/test.c
CHANGED
|
@@ -448,6 +448,46 @@ void test_view(size_t const collection_size, size_t const dimensions) {
|
|
|
448
448
|
printf("Test: View - PASSED\n");
|
|
449
449
|
}
|
|
450
450
|
|
|
451
|
+
void test_mini_float_quantizations(size_t const collection_size, size_t const dimensions) {
|
|
452
|
+
printf("Test: Mini-float quantizations... %zu vectors, %zu dimensions\n", collection_size, dimensions);
|
|
453
|
+
usearch_scalar_kind_t kinds[] = {
|
|
454
|
+
usearch_scalar_e5m2_k,
|
|
455
|
+
usearch_scalar_e4m3_k,
|
|
456
|
+
usearch_scalar_e3m2_k,
|
|
457
|
+
usearch_scalar_e2m3_k,
|
|
458
|
+
};
|
|
459
|
+
float* data = create_vectors(collection_size, dimensions);
|
|
460
|
+
usearch_key_t* keys = (usearch_key_t*)malloc(collection_size * sizeof(usearch_key_t));
|
|
461
|
+
float* distances = (float*)malloc(collection_size * sizeof(float));
|
|
462
|
+
expect(keys && distances, "Failed to allocate memory");
|
|
463
|
+
|
|
464
|
+
for (size_t k = 0; k < sizeof(kinds) / sizeof(kinds[0]); ++k) {
|
|
465
|
+
usearch_error_t error = NULL;
|
|
466
|
+
usearch_init_options_t opts = create_options(dimensions);
|
|
467
|
+
opts.quantization = kinds[k];
|
|
468
|
+
usearch_index_t index = usearch_init(&opts, &error);
|
|
469
|
+
expect(!error, error);
|
|
470
|
+
usearch_reserve(index, collection_size, &error);
|
|
471
|
+
expect(!error, error);
|
|
472
|
+
for (size_t i = 0; i < collection_size; ++i) {
|
|
473
|
+
usearch_add(index, (usearch_key_t)i, data + i * dimensions, usearch_scalar_f32_k, &error);
|
|
474
|
+
expect(!error, error);
|
|
475
|
+
}
|
|
476
|
+
expect_eq(usearch_size(index, &error), collection_size, error);
|
|
477
|
+
for (size_t i = 0; i < collection_size; ++i) {
|
|
478
|
+
size_t found =
|
|
479
|
+
usearch_search(index, data + i * dimensions, usearch_scalar_f32_k, 1, keys, distances, &error);
|
|
480
|
+
expect(!error, error);
|
|
481
|
+
expect(found >= 1, "Vector not found");
|
|
482
|
+
}
|
|
483
|
+
usearch_free(index, &error);
|
|
484
|
+
}
|
|
485
|
+
free(data);
|
|
486
|
+
free(keys);
|
|
487
|
+
free(distances);
|
|
488
|
+
printf("Test: Mini-float quantizations - PASSED\n");
|
|
489
|
+
}
|
|
490
|
+
|
|
451
491
|
int main(int argc, char const* argv[]) {
|
|
452
492
|
install_crash_handlers();
|
|
453
493
|
printf("Running tests...\n");
|
|
@@ -464,6 +504,7 @@ int main(int argc, char const* argv[]) {
|
|
|
464
504
|
test_remove_vector(collection_sizes[index], dimensions[jdx]);
|
|
465
505
|
test_save_load(collection_sizes[index], dimensions[jdx]);
|
|
466
506
|
test_view(collection_sizes[index], dimensions[jdx]);
|
|
507
|
+
test_mini_float_quantizations(collection_sizes[index], dimensions[jdx]);
|
|
467
508
|
}
|
|
468
509
|
}
|
|
469
510
|
|
package/cpp/test.cpp
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
#include <csignal> // `std::signal`, `SIGSEGV`, ...
|
|
18
18
|
#include <cstdio> // `std::fprintf`
|
|
19
19
|
#include <cstdlib> // `std::_Exit`
|
|
20
|
+
#include <limits> // `std::numeric_limits`
|
|
20
21
|
|
|
21
22
|
#include <algorithm> // `std::shuffle`
|
|
22
23
|
#include <random> // `std::default_random_engine`
|
|
@@ -216,6 +217,47 @@ void test_uint40() {
|
|
|
216
217
|
expect_eq(u40_default, uint40_t(0u));
|
|
217
218
|
}
|
|
218
219
|
|
|
220
|
+
void test_checked_size_arithmetic() {
|
|
221
|
+
|
|
222
|
+
std::printf("Testing checked size arithmetic\n");
|
|
223
|
+
std::size_t max = (std::numeric_limits<std::size_t>::max)();
|
|
224
|
+
|
|
225
|
+
checked_size_result_t cast = checked_size_from_u64(42);
|
|
226
|
+
expect(cast);
|
|
227
|
+
expect_eq(cast.value, static_cast<std::size_t>(42));
|
|
228
|
+
if (sizeof(std::size_t) < sizeof(std::uint64_t))
|
|
229
|
+
expect(!checked_size_from_u64((std::numeric_limits<std::uint64_t>::max)()));
|
|
230
|
+
|
|
231
|
+
checked_size_result_t sum = checked_add(max - 1, 1);
|
|
232
|
+
expect(sum);
|
|
233
|
+
expect_eq(sum.value, max);
|
|
234
|
+
expect(!checked_add(max, 1));
|
|
235
|
+
|
|
236
|
+
checked_size_result_t product = checked_mul(max / 2, 2);
|
|
237
|
+
expect(product);
|
|
238
|
+
expect_eq(product.value, max - 1);
|
|
239
|
+
expect(!checked_mul(max / 2 + 1, 2));
|
|
240
|
+
|
|
241
|
+
checked_size_result_t fused = checked_mul_add(10, 20, 30);
|
|
242
|
+
expect(fused);
|
|
243
|
+
expect_eq(fused.value, static_cast<std::size_t>(230));
|
|
244
|
+
expect(!checked_mul_add(max / 2 + 1, 2, 0));
|
|
245
|
+
expect(!checked_mul_add(max / 2, 2, 2));
|
|
246
|
+
|
|
247
|
+
checked_size_result_t rounded = checked_round_up(17, 8);
|
|
248
|
+
expect(rounded);
|
|
249
|
+
expect_eq(rounded.value, static_cast<std::size_t>(24));
|
|
250
|
+
expect(!checked_round_up(max, 8));
|
|
251
|
+
|
|
252
|
+
checked_size_result_t power = checked_ceil2(17);
|
|
253
|
+
expect(power);
|
|
254
|
+
expect_eq(power.value, static_cast<std::size_t>(32));
|
|
255
|
+
checked_size_result_t zero_power = checked_ceil2(0);
|
|
256
|
+
expect(zero_power);
|
|
257
|
+
expect_eq(zero_power.value, static_cast<std::size_t>(0));
|
|
258
|
+
expect(!checked_ceil2(max));
|
|
259
|
+
}
|
|
260
|
+
|
|
219
261
|
/**
|
|
220
262
|
* @brief Tests the functionality of the custom float16_t type ensuring consistent.
|
|
221
263
|
*/
|
|
@@ -1264,6 +1306,61 @@ static void install_crash_handlers() {
|
|
|
1264
1306
|
std::signal(signal_number, &usearch_crash_handler);
|
|
1265
1307
|
}
|
|
1266
1308
|
|
|
1309
|
+
/**
|
|
1310
|
+
* @brief Regression test: `make(metric, config)` must return an index that is
|
|
1311
|
+
* immediately usable - no explicit `reserve` required before `load` /
|
|
1312
|
+
* `view` / `search`. Previously the typed graph's `{0, 0}` thread
|
|
1313
|
+
* limits leaked into `load_from_stream`, leaving `available_threads_`
|
|
1314
|
+
* empty and making the first `search` throw "No available threads to
|
|
1315
|
+
* lock".
|
|
1316
|
+
*/
|
|
1317
|
+
void test_load_after_metric_make() {
|
|
1318
|
+
std::printf("Testing load and view into a metric-made index\n");
|
|
1319
|
+
|
|
1320
|
+
using index_t = index_dense_gt<std::int64_t, std::uint32_t>;
|
|
1321
|
+
std::size_t const dimensions = 32;
|
|
1322
|
+
std::size_t const collection = 64;
|
|
1323
|
+
|
|
1324
|
+
std::default_random_engine rng(7);
|
|
1325
|
+
std::uniform_real_distribution<float> distribution(-1.f, 1.f);
|
|
1326
|
+
std::vector<std::vector<float>> data(collection);
|
|
1327
|
+
for (auto& vector : data) {
|
|
1328
|
+
vector.resize(dimensions);
|
|
1329
|
+
for (auto& value : vector)
|
|
1330
|
+
value = distribution(rng);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
metric_punned_t metric(dimensions, metric_kind_t::cos_k, scalar_kind<f32_t>());
|
|
1334
|
+
index_dense_config_t config(16);
|
|
1335
|
+
|
|
1336
|
+
// Build an index and persist it to disk.
|
|
1337
|
+
index_t::state_result_t built = index_t::make(metric, config);
|
|
1338
|
+
expect(built);
|
|
1339
|
+
expect(built.index.try_reserve(collection));
|
|
1340
|
+
for (std::size_t i = 0; i != collection; ++i)
|
|
1341
|
+
expect(built.index.add(static_cast<std::int64_t>(i), data[i].data()));
|
|
1342
|
+
char const* path = "tmp_metric_make.usearch";
|
|
1343
|
+
expect(built.index.save(path));
|
|
1344
|
+
|
|
1345
|
+
// Load into a fresh, metric-made index that was never explicitly reserved.
|
|
1346
|
+
// The first `search` must not throw "No available threads to lock".
|
|
1347
|
+
index_t::state_result_t loaded = index_t::make(metric, config);
|
|
1348
|
+
expect(loaded);
|
|
1349
|
+
expect(loaded.index.load(path));
|
|
1350
|
+
expect_eq(loaded.index.size(), collection);
|
|
1351
|
+
std::int64_t found[8];
|
|
1352
|
+
expect(loaded.index.search(data[0].data(), 5).dump_to(found) != 0);
|
|
1353
|
+
|
|
1354
|
+
// Same check for the memory-mapped `view` path.
|
|
1355
|
+
index_t::state_result_t viewed = index_t::make(metric, config);
|
|
1356
|
+
expect(viewed);
|
|
1357
|
+
expect(viewed.index.view(path));
|
|
1358
|
+
expect_eq(viewed.index.size(), collection);
|
|
1359
|
+
expect(viewed.index.search(data[0].data(), 5).dump_to(found) != 0);
|
|
1360
|
+
|
|
1361
|
+
std::remove(path);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1267
1364
|
int main(int, char**) {
|
|
1268
1365
|
install_crash_handlers();
|
|
1269
1366
|
|
|
@@ -1272,6 +1369,7 @@ int main(int, char**) {
|
|
|
1272
1369
|
|
|
1273
1370
|
// Non-default floating-point types may result in many compilation & rounding issues.
|
|
1274
1371
|
test_uint40();
|
|
1372
|
+
test_checked_size_arithmetic();
|
|
1275
1373
|
test_cosine<f32_t, std::int64_t, uint40_t>(10, 10);
|
|
1276
1374
|
test_cosine<bf16_t, std::int64_t, uint40_t>(10, 10);
|
|
1277
1375
|
test_cosine<f16_t, std::int64_t, uint40_t>(10, 10);
|
|
@@ -1354,5 +1452,6 @@ int main(int, char**) {
|
|
|
1354
1452
|
|
|
1355
1453
|
test_filtered_search();
|
|
1356
1454
|
test_isolate();
|
|
1455
|
+
test_load_after_metric_make();
|
|
1357
1456
|
return 0;
|
|
1358
1457
|
}
|