usearch 2.11.0 → 2.13.0
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 +126 -37
- package/binding.gyp +11 -12
- package/c/README.md +9 -3
- package/c/lib.cpp +50 -13
- package/c/test.c +60 -55
- package/c/usearch.h +16 -0
- package/cpp/CMakeLists.txt +6 -0
- package/cpp/README.md +46 -14
- package/cpp/bench.cpp +99 -47
- package/cpp/test.cpp +948 -132
- package/include/usearch/index.hpp +894 -272
- package/include/usearch/index_dense.hpp +419 -152
- package/include/usearch/index_plugins.hpp +99 -49
- package/javascript/dist/cjs/usearch.d.ts +39 -1
- package/javascript/dist/cjs/usearch.js +11 -1
- package/javascript/dist/esm/usearch.d.ts +39 -1
- package/javascript/dist/esm/usearch.js +11 -1
- package/javascript/tsconfig-esm.json +3 -2
- package/javascript/usearch.ts +18 -7
- package/package.json +2 -2
- 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/simsimd/.cmake-format.py +19 -0
- package/simsimd/CONTRIBUTING.md +49 -8
- package/simsimd/Cargo.lock +1 -1
- package/simsimd/Cargo.toml +10 -5
- package/simsimd/Package.swift +16 -0
- package/simsimd/README.md +69 -15
- package/simsimd/binding.gyp +14 -19
- package/simsimd/build.rs +2 -0
- package/simsimd/c/lib.c +18 -0
- package/simsimd/include/module.modulemap +4 -0
- package/simsimd/include/simsimd/binary.h +2 -0
- package/simsimd/include/simsimd/dot.h +466 -32
- package/simsimd/include/simsimd/probability.h +20 -10
- package/simsimd/include/simsimd/simsimd.h +444 -98
- package/simsimd/include/simsimd/spatial.h +480 -44
- package/simsimd/include/simsimd/types.h +161 -39
- package/simsimd/javascript/simsimd.ts +26 -2
- package/simsimd/package.json +1 -1
- package/simsimd/rust/benches/cosine.rs +8 -2
- package/simsimd/rust/benches/native.rs +240 -2
- package/simsimd/rust/benches/sqeuclidean.rs +8 -2
- package/simsimd/rust/lib.rs +50 -13
- package/simsimd/swift/source/SimSIMD.swift +123 -0
- package/simsimd/swift/tests/SimSIMDTests.swift +92 -0
- package/prebuilds/win32-ia32/usearch.node +0 -0
- package/prebuilds/win32-x64/usearch.node +0 -0
- package/simsimd/.releaserc +0 -87
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ Linux • MacOS • Windows • iOS • WebAssembly •
|
|
|
49
49
|
|
|
50
50
|
- ✅ __[10x faster][faster-than-faiss]__ [HNSW][hnsw-algorithm] implementation than [FAISS][faiss].
|
|
51
51
|
- ✅ Simple and extensible [single C++11 header][usearch-header] __library__.
|
|
52
|
-
- ✅ [Trusted](#integrations) by giants like Google and
|
|
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
54
|
- ✅ Hardware-agnostic `f16` & `i8` - [half-precision & quarter-precision support](#memory-efficiency-downcasting-and-quantization).
|
|
55
55
|
- ✅ [View large indexes from disk](#serving-index-from-disk) without loading into RAM.
|
|
@@ -67,6 +67,7 @@ Linux • MacOS • Windows • iOS • WebAssembly •
|
|
|
67
67
|
[simd]: https://en.wikipedia.org/wiki/Single_instruction,_multiple_data
|
|
68
68
|
[faster-than-faiss]: https://www.unum.cloud/blog/2023-11-07-scaling-vector-search-with-intel
|
|
69
69
|
[clickhouse-docs]: https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/annindexes#usearch
|
|
70
|
+
[duckdb-docs]: https://duckdb.org/2024/05/03/vector-similarity-search-vss.html
|
|
70
71
|
|
|
71
72
|
__Technical Insights__ and related articles:
|
|
72
73
|
|
|
@@ -94,9 +95,10 @@ USearch is compact and broadly compatible without sacrificing performance, prima
|
|
|
94
95
|
| Supported metrics ² | 9 fixed metrics | any metric | extendible |
|
|
95
96
|
| Supported languages ³ | C++, Python | 10 languages | portable |
|
|
96
97
|
| Supported ID types ⁴ | 32-bit, 64-bit | 32-bit, 40-bit, 64-bit | efficient |
|
|
97
|
-
|
|
|
98
|
-
|
|
|
99
|
-
|
|
|
98
|
+
| Filtering ⁵ | ban-lists | any predicates | composable |
|
|
99
|
+
| Required dependencies ⁶ | BLAS, OpenMP | - | light-weight |
|
|
100
|
+
| Bindings ⁷ | SWIG | Native | low-latency |
|
|
101
|
+
| Python binding size ⁸ | [~ 10 MB][faiss-weight] | [< 1 MB][usearch-weight] | deployable |
|
|
100
102
|
|
|
101
103
|
[sloc]: https://en.wikipedia.org/wiki/Source_lines_of_code
|
|
102
104
|
[faiss-weight]: https://pypi.org/project/faiss-cpu/#files
|
|
@@ -107,16 +109,17 @@ USearch is compact and broadly compatible without sacrificing performance, prima
|
|
|
107
109
|
> ² User-defined metrics allow you to customize your search for various applications, from GIS to creating custom metrics for composite embeddings from multiple AI models or hybrid full-text and semantic search.
|
|
108
110
|
> ³ With USearch, you can reuse the same preconstructed index in various programming languages.
|
|
109
111
|
> ⁴ The 40-bit integer allows you to store 4B+ vectors without allocating 8 bytes for every neighbor reference in the proximity graph.
|
|
110
|
-
> ⁵
|
|
111
|
-
> ⁶
|
|
112
|
-
> ⁷
|
|
112
|
+
> ⁵ With USearch the index can be combined with arbitrary external containers, like Bloom filters or third-party databases, to filter out irrelevant keys during index traversal.
|
|
113
|
+
> ⁶ Lack of obligatory dependencies makes USearch much more portable.
|
|
114
|
+
> ⁷ Native bindings introduce lower call latencies than more straightforward approaches.
|
|
115
|
+
> ⁸ Lighter bindings make downloads and deployments faster.
|
|
113
116
|
|
|
114
117
|
[intel-benchmarks]: https://www.unum.cloud/blog/2023-11-07-scaling-vector-search-with-intel
|
|
115
118
|
|
|
116
119
|
Base functionality is identical to FAISS, and the interface must be familiar if you have ever investigated Approximate Nearest Neighbors search:
|
|
117
120
|
|
|
118
121
|
```py
|
|
119
|
-
|
|
122
|
+
# pip install numpy usearch
|
|
120
123
|
|
|
121
124
|
import numpy as np
|
|
122
125
|
from usearch.index import Index
|
|
@@ -134,12 +137,13 @@ assert np.allclose(index[42], vector, atol=0.1) # Ensure high tolerance in mixed
|
|
|
134
137
|
```
|
|
135
138
|
|
|
136
139
|
More settings are always available, and the API is designed to be as flexible as possible.
|
|
140
|
+
The default storage/quantization level is hardware-dependant for efficiency, but `f16` is recommended for most modern CPUs.
|
|
137
141
|
|
|
138
142
|
```py
|
|
139
143
|
index = Index(
|
|
140
144
|
ndim=3, # Define the number of dimensions in input vectors
|
|
141
|
-
metric='cos', # Choose 'l2sq', 'haversine' or other metric, default = '
|
|
142
|
-
dtype='
|
|
145
|
+
metric='cos', # Choose 'l2sq', 'ip', 'haversine' or other metric, default = 'cos'
|
|
146
|
+
dtype='f16', # Store as 'f64', 'f32', 'f16', 'i8', 'b1'..., default = None
|
|
143
147
|
connectivity=16, # Optional: Limit number of neighbors per graph node
|
|
144
148
|
expansion_add=128, # Optional: Control the recall of indexing
|
|
145
149
|
expansion_search=64, # Optional: Control the quality of the search
|
|
@@ -192,7 +196,7 @@ When compared to FAISS's `IndexFlatL2` in Google Colab, __[USearch may offer up
|
|
|
192
196
|
- `faiss.IndexFlatL2`: __55.3 ms__.
|
|
193
197
|
- `usearch.index.search`: __2.54 ms__.
|
|
194
198
|
|
|
195
|
-
## User-Defined
|
|
199
|
+
## User-Defined Metrics
|
|
196
200
|
|
|
197
201
|
While most vector search packages concentrate on just two metrics, "Inner Product distance" and "Euclidean distance", USearch allows arbitrary user-defined metrics.
|
|
198
202
|
This flexibility allows you to customize your search for various applications, from computing geospatial coordinates with the rare [Haversine][haversine] distance to creating custom metrics for composite embeddings from multiple AI models, like joint image-text embeddings.
|
|
@@ -228,6 +232,23 @@ So you can apply it in [obscure][obscure] applications, like searching for simil
|
|
|
228
232
|
[cppyy]: https://cppyy.readthedocs.io/en/latest/
|
|
229
233
|
[peachpy]: https://github.com/Maratyszcza/PeachPy
|
|
230
234
|
|
|
235
|
+
## Filtering and Predicate Functions
|
|
236
|
+
|
|
237
|
+
Sometimes you may want to cross-reference search-results against some external database or filter them based on some criteria.
|
|
238
|
+
In most engines, you'd have to manually perform paging requests, successively filtering the results.
|
|
239
|
+
In USearch you can simply pass a predicate function to the search method, which will be applied directly during graph traversal.
|
|
240
|
+
In Rust that would look like this:
|
|
241
|
+
|
|
242
|
+
```rust
|
|
243
|
+
let is_odd = |key: Key| key % 2 == 1;
|
|
244
|
+
let query = vec![0.2, 0.1, 0.2, 0.1, 0.3];
|
|
245
|
+
let results = index.filtered_search(&query, 10, is_odd).unwrap();
|
|
246
|
+
assert!(
|
|
247
|
+
results.keys.iter().all(|&key| key % 2 == 1),
|
|
248
|
+
"All keys must be odd"
|
|
249
|
+
);
|
|
250
|
+
```
|
|
251
|
+
|
|
231
252
|
## Memory Efficiency, Downcasting, and Quantization
|
|
232
253
|
|
|
233
254
|
Training a quantization model and dimension-reduction is a common approach to accelerate vector search.
|
|
@@ -341,41 +362,58 @@ In some cases, like Batch operations, feature parity is meaningless, as the host
|
|
|
341
362
|
|
|
342
363
|
## Application Examples
|
|
343
364
|
|
|
344
|
-
### USearch +
|
|
345
|
-
|
|
346
|
-
[](https://github.com/ashvardanian/usearch-images)
|
|
365
|
+
### USearch + UForm + UCall = Multimodal Semantic Search
|
|
347
366
|
|
|
348
367
|
AI has a growing number of applications, but one of the coolest classic ideas is to use it for Semantic Search.
|
|
349
|
-
One can take an encoder model, like the multi-modal [UForm](https://github.com/unum-cloud/uform), and a web-programming framework, like UCall, and build a text-to-image search platform in just 20 lines of Python.
|
|
368
|
+
One can take an encoder model, like the multi-modal [UForm](https://github.com/unum-cloud/uform), and a web-programming framework, like [UCall](https://github.com/unum-cloud/ucall), and build a text-to-image search platform in just 20 lines of Python.
|
|
350
369
|
|
|
351
370
|
```python
|
|
352
|
-
import
|
|
353
|
-
import
|
|
354
|
-
import
|
|
371
|
+
from ucall import Server
|
|
372
|
+
from uform import get_model, Modality
|
|
373
|
+
from usearch.index import Index
|
|
355
374
|
|
|
356
375
|
import numpy as np
|
|
357
376
|
import PIL as pil
|
|
358
377
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
378
|
+
processors, models = get_model('unum-cloud/uform3-image-text-english-small')
|
|
379
|
+
model_text = models[Modality.TEXT_ENCODER]
|
|
380
|
+
model_image = models[Modality.IMAGE_ENCODER]
|
|
381
|
+
processor_text = processors[Modality.TEXT_ENCODER]
|
|
382
|
+
processor_image = processors[Modality.IMAGE_ENCODER]
|
|
383
|
+
|
|
384
|
+
server = Server()
|
|
385
|
+
index = Index(ndim=256)
|
|
362
386
|
|
|
363
387
|
@server
|
|
364
388
|
def add(key: int, photo: pil.Image.Image):
|
|
365
|
-
image =
|
|
366
|
-
vector =
|
|
389
|
+
image = processor_image(photo)
|
|
390
|
+
vector = model_image(image)
|
|
367
391
|
index.add(key, vector.flatten(), copy=True)
|
|
368
392
|
|
|
369
393
|
@server
|
|
370
394
|
def search(query: str) -> np.ndarray:
|
|
371
|
-
tokens =
|
|
372
|
-
vector =
|
|
395
|
+
tokens = processor_text(query)
|
|
396
|
+
vector = model_text(tokens)
|
|
373
397
|
matches = index.search(vector.flatten(), 3)
|
|
374
398
|
return matches.keys
|
|
375
399
|
|
|
376
400
|
server.run()
|
|
377
401
|
```
|
|
378
402
|
|
|
403
|
+
Similar experiences can also be imlemented in other languages and on the client side, removing the network latency.
|
|
404
|
+
For Swift and iOS, check out the [`ashvardanian/SwiftSemanticSearch`](https://github.com/ashvardanian/SwiftSemanticSearch) repository.
|
|
405
|
+
|
|
406
|
+
<table>
|
|
407
|
+
<tr>
|
|
408
|
+
<td>
|
|
409
|
+
<img src="https://github.com/ashvardanian/ashvardanian/blob/master/demos/SwiftSemanticSearch-Dog.gif?raw=true" alt="SwiftSemanticSearch demo Dog">
|
|
410
|
+
</td>
|
|
411
|
+
<td>
|
|
412
|
+
<img src="https://github.com/ashvardanian/ashvardanian/blob/master/demos/SwiftSemanticSearch-Flowers.gif?raw=true" alt="SwiftSemanticSearch demo with Flowers">
|
|
413
|
+
</td>
|
|
414
|
+
</tr>
|
|
415
|
+
</table>
|
|
416
|
+
|
|
379
417
|
A more complete [demo with Streamlit is available on GitHub](https://github.com/ashvardanian/usearch-images).
|
|
380
418
|
We have pre-processed some commonly used datasets, cleaned the images, produced the vectors, and pre-built the index.
|
|
381
419
|
|
|
@@ -427,32 +465,83 @@ That method was used to build the ["USearch Molecules"](https://github.com/ashva
|
|
|
427
465
|
[smiles]: https://en.wikipedia.org/wiki/Simplified_molecular-input_line-entry_system
|
|
428
466
|
[rdkit-fingerprints]: https://www.rdkit.org/docs/RDKit_Book.html#additional-information-about-the-fingerprints
|
|
429
467
|
|
|
430
|
-
### USearch + POI Coordinates = GIS Applications
|
|
468
|
+
### USearch + POI Coordinates = GIS Applications
|
|
431
469
|
|
|
432
|
-
|
|
470
|
+
Similar to Vector and Molecule search, USearch can be used for Geospatial Information Systems.
|
|
471
|
+
The Haversine distance is available out of the box, but you can also define more complex relationships, like the Vincenty formula, that accounts for the Earth's oblateness.
|
|
433
472
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
473
|
+
```py
|
|
474
|
+
from numba import cfunc, types, carray
|
|
475
|
+
import math
|
|
476
|
+
|
|
477
|
+
# Define the dimension as 2 for latitude and longitude
|
|
478
|
+
ndim = 2
|
|
479
|
+
|
|
480
|
+
# Signature for the custom metric
|
|
481
|
+
signature = types.float32(
|
|
482
|
+
types.CPointer(types.float32),
|
|
483
|
+
types.CPointer(types.float32))
|
|
484
|
+
|
|
485
|
+
# WGS-84 ellipsoid parameters
|
|
486
|
+
a = 6378137.0 # major axis in meters
|
|
487
|
+
f = 1 / 298.257223563 # flattening
|
|
488
|
+
b = (1 - f) * a # minor axis
|
|
489
|
+
|
|
490
|
+
def vincenty_distance(a_ptr, b_ptr):
|
|
491
|
+
a_array = carray(a_ptr, ndim)
|
|
492
|
+
b_array = carray(b_ptr, ndim)
|
|
493
|
+
lat1, lon1, lat2, lon2 = a_array[0], a_array[1], b_array[0], b_array[1]
|
|
494
|
+
L, U1, U2 = lon2 - lon1, math.atan((1 - f) * math.tan(lat1)), math.atan((1 - f) * math.tan(lat2))
|
|
495
|
+
sinU1, cosU1, sinU2, cosU2 = math.sin(U1), math.cos(U1), math.sin(U2), math.cos(U2)
|
|
496
|
+
lambda_, iterLimit = L, 100
|
|
497
|
+
while iterLimit > 0:
|
|
498
|
+
iterLimit -= 1
|
|
499
|
+
sinLambda, cosLambda = math.sin(lambda_), math.cos(lambda_)
|
|
500
|
+
sinSigma = math.sqrt((cosU2 * sinLambda) ** 2 + (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) ** 2)
|
|
501
|
+
if sinSigma == 0: return 0.0 # Co-incident points
|
|
502
|
+
cosSigma, sigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda, math.atan2(sinSigma, cosSigma)
|
|
503
|
+
sinAlpha, cos2Alpha = cosU1 * cosU2 * sinLambda / sinSigma, 1 - (cosU1 * cosU2 * sinLambda / sinSigma) ** 2
|
|
504
|
+
cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cos2Alpha if not math.isnan(cosSigma - 2 * sinU1 * sinU2 / cos2Alpha) else 0 # Equatorial line
|
|
505
|
+
C = f / 16 * cos2Alpha * (4 + f * (4 - 3 * cos2Alpha))
|
|
506
|
+
lambda_, lambdaP = L + (1 - C) * f * (sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM ** 2)))), lambda_
|
|
507
|
+
if abs(lambda_ - lambdaP) <= 1e-12: break
|
|
508
|
+
if iterLimit == 0: return float('nan') # formula failed to converge
|
|
509
|
+
u2 = cos2Alpha * (a ** 2 - b ** 2) / (b ** 2)
|
|
510
|
+
A = 1 + u2 / 16384 * (4096 + u2 * (-768 + u2 * (320 - 175 * u2)))
|
|
511
|
+
B = u2 / 1024 * (256 + u2 * (-128 + u2 * (74 - 47 * u2)))
|
|
512
|
+
deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM ** 2) - B / 6 * cos2SigmaM * (-3 + 4 * sinSigma ** 2) * (-3 + 4 * cos2SigmaM ** 2)))
|
|
513
|
+
s = b * A * (sigma - deltaSigma)
|
|
514
|
+
return s / 1000.0 # Distance in kilometers
|
|
515
|
+
|
|
516
|
+
# Example usage:
|
|
517
|
+
index = Index(ndim=ndim, metric=CompiledMetric(
|
|
518
|
+
pointer=vincenty_distance.address,
|
|
519
|
+
kind=MetricKind.Haversine,
|
|
520
|
+
signature=MetricSignature.ArrayArray,
|
|
521
|
+
))
|
|
522
|
+
```
|
|
438
523
|
|
|
439
|
-
## Integrations
|
|
524
|
+
## Integrations & Users
|
|
440
525
|
|
|
441
|
-
- [x]
|
|
526
|
+
- [x] ClickHouse: [C++](https://github.com/ClickHouse/ClickHouse/pull/53447), [docs](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/annindexes#usearch).
|
|
527
|
+
- [x] DuckDB: [post](https://duckdb.org/2024/05/03/vector-similarity-search-vss.html).
|
|
528
|
+
- [x] Google: [UniSim](https://github.com/google/unisim), [RetSim](https://arxiv.org/abs/2311.17264) paper.
|
|
529
|
+
- [x] LanternDB: [C++](https://github.com/lanterndata/lantern), [Rust](https://github.com/lanterndata/lantern_extras), [docs](https://lantern.dev/blog/hnsw-index-creation).
|
|
442
530
|
- [x] LangChain: [Python](https://github.com/langchain-ai/langchain/releases/tag/v0.0.257) and [JavaScript](https://github.com/hwchase17/langchainjs/releases/tag/0.0.125).
|
|
443
|
-
- [x] ClickHouse: [C++](https://github.com/ClickHouse/ClickHouse/pull/53447).
|
|
444
531
|
- [x] Microsoft Semantic Kernel: [Python](https://github.com/microsoft/semantic-kernel/releases/tag/python-0.3.9.dev) and C#.
|
|
445
|
-
- [x]
|
|
532
|
+
- [x] GPTCache: [Python](https://github.com/zilliztech/GPTCache/releases/tag/0.1.29).
|
|
533
|
+
- [x] Sentence-Transformers: Python [docs](https://www.sbert.net/docs/package_reference/quantization.html#sentence_transformers.quantization.semantic_search_usearch).
|
|
534
|
+
- [x] Pathway: [Rust](https://github.com/pathwaycom/pathway).
|
|
446
535
|
|
|
447
536
|
## Citations
|
|
448
537
|
|
|
449
|
-
```
|
|
538
|
+
```bibtex
|
|
450
539
|
@software{Vardanian_USearch_2023,
|
|
451
540
|
doi = {10.5281/zenodo.7949416},
|
|
452
541
|
author = {Vardanian, Ash},
|
|
453
542
|
title = {{USearch by Unum Cloud}},
|
|
454
543
|
url = {https://github.com/unum-cloud/usearch},
|
|
455
|
-
version = {2.
|
|
544
|
+
version = {2.13.0},
|
|
456
545
|
year = {2023},
|
|
457
546
|
month = oct,
|
|
458
547
|
}
|
package/binding.gyp
CHANGED
|
@@ -25,15 +25,9 @@
|
|
|
25
25
|
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
|
|
26
26
|
"CLANG_CXX_LIBRARY": "libc++",
|
|
27
27
|
"MACOSX_DEPLOYMENT_TARGET": "11.0",
|
|
28
|
-
"OTHER_CFLAGS": [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
],
|
|
32
|
-
"OTHER_LDFLAGS": [
|
|
33
|
-
"-arch arm64",
|
|
34
|
-
"-arch x86_64"
|
|
35
|
-
]
|
|
36
|
-
},
|
|
28
|
+
"OTHER_CFLAGS": ["-arch arm64", "-arch x86_64"],
|
|
29
|
+
"OTHER_LDFLAGS": ["-arch arm64", "-arch x86_64"],
|
|
30
|
+
},
|
|
37
31
|
"msvs_settings": {
|
|
38
32
|
"VCCLCompilerTool": {
|
|
39
33
|
"ExceptionHandling": 1,
|
|
@@ -47,18 +41,19 @@
|
|
|
47
41
|
"cflags_cc": [
|
|
48
42
|
'<!(if [ "$USEARCH_USE_OPENMP" = "1" ]; then echo \'-fopenmp\'; fi)',
|
|
49
43
|
],
|
|
50
|
-
"ldflags": [
|
|
51
|
-
'<!(if [ "$USEARCH_USE_OPENMP" = "1" ]; then echo \'-lgomp\'; fi)'
|
|
52
|
-
],
|
|
44
|
+
"ldflags": ['<!(if [ "$USEARCH_USE_OPENMP" = "1" ]; then echo \'-lgomp\'; fi)'],
|
|
53
45
|
"defines": [
|
|
54
46
|
"USEARCH_USE_OPENMP=<!(echo ${USEARCH_USE_OPENMP:-0})",
|
|
55
47
|
"USEARCH_USE_SIMSIMD=<!(echo ${USEARCH_USE_SIMSIMD:-1})",
|
|
56
48
|
"USEARCH_USE_FP16LIB=<!(echo ${USEARCH_USE_FP16LIB:-1})",
|
|
57
49
|
"SIMSIMD_TARGET_NEON=<!(echo ${SIMSIMD_TARGET_NEON:-1})",
|
|
50
|
+
"SIMSIMD_TARGET_NEON_BF16=<!(echo ${SIMSIMD_TARGET_NEON_BF16:-0})",
|
|
58
51
|
"SIMSIMD_TARGET_SVE=<!(echo ${SIMSIMD_TARGET_SVE:-1})",
|
|
52
|
+
"SIMSIMD_TARGET_SVE_BF16=<!(echo ${SIMSIMD_TARGET_SVE_BF16:-0})",
|
|
59
53
|
"SIMSIMD_TARGET_HASWELL=<!(echo ${SIMSIMD_TARGET_HASWELL:-1})",
|
|
60
54
|
"SIMSIMD_TARGET_SKYLAKE=<!(echo ${SIMSIMD_TARGET_SKYLAKE:-1})",
|
|
61
55
|
"SIMSIMD_TARGET_ICE=<!(echo ${SIMSIMD_TARGET_ICE:-1})",
|
|
56
|
+
"SIMSIMD_TARGET_GENOA=<!(echo ${SIMSIMD_TARGET_GENOA:-1})",
|
|
62
57
|
"SIMSIMD_TARGET_SAPPHIRE=<!(echo ${SIMSIMD_TARGET_SAPPHIRE:-1})",
|
|
63
58
|
],
|
|
64
59
|
},
|
|
@@ -71,10 +66,12 @@
|
|
|
71
66
|
"USEARCH_USE_SIMSIMD=<!(echo ${USEARCH_USE_SIMSIMD:-0})",
|
|
72
67
|
"USEARCH_USE_FP16LIB=<!(echo ${USEARCH_USE_FP16LIB:-1})",
|
|
73
68
|
"SIMSIMD_TARGET_NEON=<!(echo ${SIMSIMD_TARGET_NEON:-1})",
|
|
69
|
+
"SIMSIMD_TARGET_NEON_BF16=<!(echo ${SIMSIMD_TARGET_NEON_BF16:-0})",
|
|
74
70
|
"SIMSIMD_TARGET_SVE=<!(echo ${SIMSIMD_TARGET_SVE:-0})",
|
|
75
71
|
"SIMSIMD_TARGET_HASWELL=<!(echo ${SIMSIMD_TARGET_HASWELL:-1})",
|
|
76
72
|
"SIMSIMD_TARGET_SKYLAKE=<!(echo ${SIMSIMD_TARGET_SKYLAKE:-0})",
|
|
77
73
|
"SIMSIMD_TARGET_ICE=<!(echo ${SIMSIMD_TARGET_ICE:-0})",
|
|
74
|
+
"SIMSIMD_TARGET_GENOA=<!(echo ${SIMSIMD_TARGET_GENOA:-0})",
|
|
78
75
|
"SIMSIMD_TARGET_SAPPHIRE=<!(echo ${SIMSIMD_TARGET_SAPPHIRE:-0})",
|
|
79
76
|
],
|
|
80
77
|
},
|
|
@@ -89,9 +86,11 @@
|
|
|
89
86
|
"SIMSIMD_TARGET_SKYLAKE=0",
|
|
90
87
|
"SIMSIMD_TARGET_ICE=0",
|
|
91
88
|
"SIMSIMD_TARGET_SAPPHIRE=0",
|
|
89
|
+
"SIMSIMD_TARGET_GENOA=0",
|
|
92
90
|
"SIMSIMD_TARGET_SVE=0",
|
|
93
91
|
"SIMSIMD_TARGET_HASWELL=0",
|
|
94
92
|
"SIMSIMD_TARGET_NEON=0",
|
|
93
|
+
"SIMSIMD_TARGET_NEON_BF16=0",
|
|
95
94
|
],
|
|
96
95
|
},
|
|
97
96
|
],
|
package/c/README.md
CHANGED
|
@@ -42,7 +42,7 @@ int main() {
|
|
|
42
42
|
|
|
43
43
|
// Search:
|
|
44
44
|
usearch_key_t found_keys[10];
|
|
45
|
-
|
|
45
|
+
usearch_distance_t found_distances[10];
|
|
46
46
|
size_t found_count = usearch_search(
|
|
47
47
|
index, &vector[0], usearch_scalar_f32_k, 10,
|
|
48
48
|
&found_keys[0], &found_distances[0], &error);
|
|
@@ -113,7 +113,8 @@ simsimd_distance_t callback(void const* a, void const* b, void* state) {
|
|
|
113
113
|
// Your custom metric implementation here
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
void callback_state = NULL;
|
|
117
|
+
usearch_change_metric(index, callback, callback_state, usearch_metric_unknown_k, &error);
|
|
117
118
|
```
|
|
118
119
|
|
|
119
120
|
You can always revert back to one of the native metrics by calling:
|
|
@@ -133,8 +134,10 @@ int is_odd(usearch_key_t key, void* state) {
|
|
|
133
134
|
return key % 2;
|
|
134
135
|
}
|
|
135
136
|
|
|
137
|
+
usearch_key_t found_keys[10];
|
|
138
|
+
usearch_distance_t found_distances[10];
|
|
136
139
|
usearch_filtered_search(
|
|
137
|
-
index, &
|
|
140
|
+
index, &query[0], usearch_scalar_f32_k, 10,
|
|
138
141
|
&is_odd, NULL, // no state needed for this callback
|
|
139
142
|
&found_keys[0], &found_distances[0], &error);
|
|
140
143
|
```
|
|
@@ -204,6 +207,9 @@ By default, as many of these structures are created as there are logical cores o
|
|
|
204
207
|
Then, one can construct the index in parallel using many cores using third-party thread-pools or tools like OpenACC and OpenMP.
|
|
205
208
|
|
|
206
209
|
```c
|
|
210
|
+
usearch_change_threads_add(index, omp_get_max_threads(), &error);
|
|
211
|
+
usearch_change_threads_search(index, omp_get_max_threads(), &error);
|
|
212
|
+
|
|
207
213
|
#pragma omp parallel for
|
|
208
214
|
for (size_t i = 0; i < 1000; i++) {
|
|
209
215
|
usearch_add(index, i, &vector[0], usearch_scalar_f32_k, &error);
|
package/c/lib.cpp
CHANGED
|
@@ -94,7 +94,7 @@ std::size_t get_(index_dense_t* index, usearch_key_t key, size_t count, void* ve
|
|
|
94
94
|
case scalar_kind_t::f16_k: return index->get(key, (f16_t*)vector, count);
|
|
95
95
|
case scalar_kind_t::i8_k: return index->get(key, (i8_t*)vector, count);
|
|
96
96
|
case scalar_kind_t::b1x8_k: return index->get(key, (b1x8_t*)vector, count);
|
|
97
|
-
default: return search_result_t().failed("Unknown scalar kind!");
|
|
97
|
+
default: return search_result_t(*index).failed("Unknown scalar kind!");
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -112,7 +112,7 @@ search_result_t search_(index_dense_t* index, void const* vector, scalar_kind_t
|
|
|
112
112
|
return index->filtered_search((i8_t const*)vector, n, std::forward<predicate_at>(predicate));
|
|
113
113
|
case scalar_kind_t::b1x8_k:
|
|
114
114
|
return index->filtered_search((b1x8_t const*)vector, n, std::forward<predicate_at>(predicate));
|
|
115
|
-
default: return search_result_t().failed("Unknown scalar kind!");
|
|
115
|
+
default: return search_result_t(*index).failed("Unknown scalar kind!");
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -123,19 +123,32 @@ USEARCH_EXPORT char const* usearch_version(void) {
|
|
|
123
123
|
int minor = USEARCH_VERSION_MINOR;
|
|
124
124
|
int patch = USEARCH_VERSION_PATCH;
|
|
125
125
|
static char version[32];
|
|
126
|
-
|
|
126
|
+
std::snprintf(version, sizeof(version), "%d.%d.%d", major, minor, patch);
|
|
127
127
|
return version;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t* options, usearch_error_t* error) {
|
|
131
131
|
|
|
132
|
-
USEARCH_ASSERT(
|
|
132
|
+
USEARCH_ASSERT(error && "Missing arguments");
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
// The user may want to initialize from a file.
|
|
135
|
+
// In that case he may pass NULL options, and we will try to load the metadata from the file.
|
|
136
|
+
if (!options) {
|
|
137
|
+
index_dense_t* result_ptr = new index_dense_t();
|
|
138
|
+
if (!result_ptr)
|
|
139
|
+
*error = "Out of memory!";
|
|
140
|
+
return result_ptr;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
index_dense_config_t config;
|
|
144
|
+
config.connectivity = options->connectivity;
|
|
145
|
+
config.expansion_add = options->expansion_add;
|
|
146
|
+
config.expansion_search = options->expansion_search;
|
|
135
147
|
config.multi = options->multi;
|
|
148
|
+
config.enable_key_lookups = 1;
|
|
149
|
+
|
|
136
150
|
metric_kind_t metric_kind = metric_kind_to_cpp(options->metric_kind);
|
|
137
151
|
scalar_kind_t scalar_kind = scalar_kind_to_cpp(options->quantization);
|
|
138
|
-
|
|
139
152
|
metric_punned_t metric = //
|
|
140
153
|
!options->metric ? metric_punned_t::builtin(options->dimensions, metric_kind, scalar_kind)
|
|
141
154
|
: metric_punned_t::stateless(options->dimensions, //
|
|
@@ -147,10 +160,18 @@ USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t* options, use
|
|
|
147
160
|
return NULL;
|
|
148
161
|
}
|
|
149
162
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (!
|
|
163
|
+
using state_result_t = typename index_dense_t::state_result_t;
|
|
164
|
+
state_result_t state = index_dense_t::make(metric, config);
|
|
165
|
+
if (!state)
|
|
166
|
+
*error = state.error.release();
|
|
167
|
+
index_dense_t* result_ptr = new index_dense_t(std::move(state.index));
|
|
168
|
+
if (!result_ptr)
|
|
153
169
|
*error = "Out of memory!";
|
|
170
|
+
|
|
171
|
+
// Let's immediately make it usable by reserving enough threads for this machine:
|
|
172
|
+
if (!result_ptr->try_reserve(index_limits_t()))
|
|
173
|
+
*error = "Out of memory when preparing contexts!";
|
|
174
|
+
|
|
154
175
|
return result_ptr;
|
|
155
176
|
}
|
|
156
177
|
|
|
@@ -304,6 +325,22 @@ USEARCH_EXPORT void usearch_change_expansion_search(usearch_index_t index, size_
|
|
|
304
325
|
reinterpret_cast<index_dense_t*>(index)->change_expansion_search(expansion);
|
|
305
326
|
}
|
|
306
327
|
|
|
328
|
+
USEARCH_EXPORT void usearch_change_threads_add(usearch_index_t index, size_t threads, usearch_error_t* error) {
|
|
329
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
330
|
+
auto& index_dense = *reinterpret_cast<index_dense_t*>(index);
|
|
331
|
+
index_limits_t limits = index_dense.limits();
|
|
332
|
+
limits.threads_add = threads;
|
|
333
|
+
index_dense.try_reserve(limits);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
USEARCH_EXPORT void usearch_change_threads_search(usearch_index_t index, size_t threads, usearch_error_t* error) {
|
|
337
|
+
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
338
|
+
auto& index_dense = *reinterpret_cast<index_dense_t*>(index);
|
|
339
|
+
index_limits_t limits = index_dense.limits();
|
|
340
|
+
limits.threads_search = threads;
|
|
341
|
+
index_dense.try_reserve(limits);
|
|
342
|
+
}
|
|
343
|
+
|
|
307
344
|
USEARCH_EXPORT void usearch_change_metric_kind(usearch_index_t index, usearch_metric_kind_t kind,
|
|
308
345
|
usearch_error_t* error) {
|
|
309
346
|
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
@@ -317,9 +354,9 @@ USEARCH_EXPORT void usearch_change_metric(usearch_index_t index, usearch_metric_
|
|
|
317
354
|
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
318
355
|
auto& index_dense = *reinterpret_cast<index_dense_t*>(index);
|
|
319
356
|
auto metric_punned =
|
|
320
|
-
state ? metric_punned_t::
|
|
321
|
-
|
|
322
|
-
|
|
357
|
+
state ? metric_punned_t::stateful(reinterpret_cast<std::uintptr_t>(metric),
|
|
358
|
+
reinterpret_cast<std::uintptr_t>(state), metric_kind_to_cpp(kind),
|
|
359
|
+
index_dense.scalar_kind())
|
|
323
360
|
: metric_punned_t::stateless(index_dense.dimensions(), reinterpret_cast<std::uintptr_t>(metric),
|
|
324
361
|
metric_punned_signature_t::array_array_k, metric_kind_to_cpp(kind),
|
|
325
362
|
index_dense.scalar_kind());
|
|
@@ -328,7 +365,7 @@ USEARCH_EXPORT void usearch_change_metric(usearch_index_t index, usearch_metric_
|
|
|
328
365
|
|
|
329
366
|
USEARCH_EXPORT void usearch_reserve(usearch_index_t index, size_t capacity, usearch_error_t* error) {
|
|
330
367
|
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
331
|
-
if (!reinterpret_cast<index_dense_t*>(index)->
|
|
368
|
+
if (!reinterpret_cast<index_dense_t*>(index)->try_reserve(capacity))
|
|
332
369
|
*error = "Out of memory!";
|
|
333
370
|
}
|
|
334
371
|
|