usearch 2.16.2 → 2.16.4
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 +1 -1
- package/c/lib.cpp +1 -1
- package/include/usearch/index.hpp +1 -1
- package/include/usearch/index_plugins.hpp +14 -9
- package/javascript/dist/cjs/usearch.d.ts +2 -2
- package/javascript/dist/esm/usearch.d.ts +2 -2
- package/javascript/usearch.ts +2 -2
- 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/simsimd/CONTRIBUTING.md +31 -2
- package/simsimd/Cargo.lock +1 -1
- package/simsimd/Cargo.toml +1 -1
- package/simsimd/README.md +93 -39
- package/simsimd/c/lib.c +32 -25
- package/simsimd/include/simsimd/elementwise.h +1068 -49
- package/simsimd/include/simsimd/simsimd.h +78 -73
- package/simsimd/include/simsimd/types.h +34 -17
- package/simsimd/javascript/fallback.ts +27 -4
- package/simsimd/javascript/lib.c +49 -29
- package/simsimd/javascript/simsimd.ts +34 -14
- package/simsimd/package-lock.json +13 -11
- package/simsimd/package.json +3 -3
- package/simsimd/scripts/bench.cxx +2 -5
- package/simsimd/scripts/bench_vectors.py +44 -10
- package/simsimd/scripts/test.c +39 -35
- package/simsimd/scripts/test.mjs +28 -0
- package/simsimd/scripts/test.py +19 -19
- package/simsimd/swift/SimSIMD.swift +12 -7
package/README.md
CHANGED
package/c/lib.cpp
CHANGED
|
@@ -354,7 +354,7 @@ USEARCH_EXPORT void usearch_change_metric(usearch_index_t index, usearch_metric_
|
|
|
354
354
|
USEARCH_ASSERT(index && error && "Missing arguments");
|
|
355
355
|
auto& index_dense = *reinterpret_cast<index_dense_t*>(index);
|
|
356
356
|
auto metric_punned =
|
|
357
|
-
state ? metric_punned_t::stateful(reinterpret_cast<std::uintptr_t>(metric),
|
|
357
|
+
state ? metric_punned_t::stateful(index_dense.dimensions(), reinterpret_cast<std::uintptr_t>(metric),
|
|
358
358
|
reinterpret_cast<std::uintptr_t>(state), metric_kind_to_cpp(kind),
|
|
359
359
|
index_dense.scalar_kind())
|
|
360
360
|
: metric_punned_t::stateless(index_dense.dimensions(), reinterpret_cast<std::uintptr_t>(metric),
|
|
@@ -1651,7 +1651,7 @@ enum class metric_punned_signature_t {
|
|
|
1651
1651
|
/**
|
|
1652
1652
|
* @brief Type-punned metric class, which unlike STL's `std::function` avoids any memory allocations.
|
|
1653
1653
|
* It also provides additional APIs to check, if SIMD hardware-acceleration is available.
|
|
1654
|
-
* Wraps the `
|
|
1654
|
+
* Wraps the `simsimd_metric_dense_punned_t` when available. The auto-vectorized backend otherwise.
|
|
1655
1655
|
*/
|
|
1656
1656
|
class metric_punned_t {
|
|
1657
1657
|
public:
|
|
@@ -1767,20 +1767,22 @@ class metric_punned_t {
|
|
|
1767
1767
|
* @brief Creates a metric using the provided function pointer for a stateful metric.
|
|
1768
1768
|
* The third argument is the state that will be passed to the metric function.
|
|
1769
1769
|
*
|
|
1770
|
+
* @param dimensions The number of elements in the input arrays.
|
|
1770
1771
|
* @param metric_uintptr The function pointer to the metric function.
|
|
1771
1772
|
* @param metric_state The state to pass to the metric function.
|
|
1772
1773
|
* @param metric_kind The kind of metric to use.
|
|
1773
1774
|
* @param scalar_kind The kind of scalar to use.
|
|
1774
1775
|
* @return A metric object that can be used to compute distances between vectors.
|
|
1775
1776
|
*/
|
|
1776
|
-
inline static metric_punned_t stateful(
|
|
1777
|
-
|
|
1778
|
-
|
|
1777
|
+
inline static metric_punned_t stateful( //
|
|
1778
|
+
std::size_t dimensions, std::uintptr_t metric_uintptr, std::uintptr_t metric_state,
|
|
1779
|
+
metric_kind_t metric_kind = metric_kind_t::unknown_k,
|
|
1780
|
+
scalar_kind_t scalar_kind = scalar_kind_t::unknown_k) noexcept {
|
|
1779
1781
|
metric_punned_t metric;
|
|
1780
1782
|
metric.metric_routed_ = &metric_punned_t::invoke_array_array_third;
|
|
1781
1783
|
metric.metric_ptr_ = metric_uintptr;
|
|
1782
1784
|
metric.metric_third_arg_ = metric_state;
|
|
1783
|
-
metric.dimensions_ =
|
|
1785
|
+
metric.dimensions_ = dimensions;
|
|
1784
1786
|
metric.metric_kind_ = metric_kind;
|
|
1785
1787
|
metric.scalar_kind_ = scalar_kind;
|
|
1786
1788
|
return metric;
|
|
@@ -1858,9 +1860,10 @@ class metric_punned_t {
|
|
|
1858
1860
|
case scalar_kind_t::b1x8_k: datatype = simsimd_datatype_b8_k; break;
|
|
1859
1861
|
default: break;
|
|
1860
1862
|
}
|
|
1861
|
-
|
|
1863
|
+
simsimd_metric_dense_punned_t simd_metric = NULL;
|
|
1862
1864
|
simsimd_capability_t simd_kind = simsimd_cap_any_k;
|
|
1863
|
-
|
|
1865
|
+
simsimd_find_kernel_punned(kind, datatype, simd_caps, allowed, (simsimd_kernel_punned_t*)&simd_metric,
|
|
1866
|
+
&simd_kind);
|
|
1864
1867
|
if (simd_metric == nullptr)
|
|
1865
1868
|
return false;
|
|
1866
1869
|
|
|
@@ -1883,7 +1886,7 @@ class metric_punned_t {
|
|
|
1883
1886
|
invoke_simsimd(uptr_t a, uptr_t b) const noexcept {
|
|
1884
1887
|
simsimd_distance_t result;
|
|
1885
1888
|
// Here `reinterpret_cast` raises warning and UBSan reports an issue... we know what we are doing!
|
|
1886
|
-
auto function_pointer = (
|
|
1889
|
+
auto function_pointer = (simsimd_metric_dense_punned_t)(metric_ptr_);
|
|
1887
1890
|
function_pointer(reinterpret_cast<void const*>(a), reinterpret_cast<void const*>(b), metric_third_arg_,
|
|
1888
1891
|
&result);
|
|
1889
1892
|
return (result_t)result;
|
|
@@ -2223,6 +2226,8 @@ template <typename allocator_at = std::allocator<char>> class kmeans_clustering_
|
|
|
2223
2226
|
scalar_kind_t original_scalar_kind, std::size_t dimensions, executor_at&& executor = executor_at{},
|
|
2224
2227
|
progress_at&& progress = progress_at{}) {
|
|
2225
2228
|
|
|
2229
|
+
(void)progress; // TODO
|
|
2230
|
+
|
|
2226
2231
|
// Perform sanity checks for algorithm settings.
|
|
2227
2232
|
kmeans_clustering_result_t result;
|
|
2228
2233
|
if (max_iterations < 1)
|
|
@@ -2332,7 +2337,7 @@ template <typename allocator_at = std::allocator<char>> class kmeans_clustering_
|
|
|
2332
2337
|
|
|
2333
2338
|
// For every point, find the closest centroid.
|
|
2334
2339
|
std::atomic<std::size_t> points_shifted{0};
|
|
2335
|
-
executor.dynamic(points_count, [&](std::size_t
|
|
2340
|
+
executor.dynamic(points_count, [&](std::size_t, std::size_t points_idx) {
|
|
2336
2341
|
byte_t const* quantized_point =
|
|
2337
2342
|
points_quantized_buffer.data() + points_idx * stride_per_vector_quantized;
|
|
2338
2343
|
byte_t const* quantized_centroids = centroids_quantized_buffer.data();
|
|
@@ -72,7 +72,7 @@ export declare class BatchMatches {
|
|
|
72
72
|
*/
|
|
73
73
|
get(i: number): Matches;
|
|
74
74
|
}
|
|
75
|
-
export interface
|
|
75
|
+
export interface IndexConfig {
|
|
76
76
|
dimensions: number;
|
|
77
77
|
metric: MetricKind;
|
|
78
78
|
quantization: ScalarKind;
|
|
@@ -95,7 +95,7 @@ export declare class Index {
|
|
|
95
95
|
* @param {boolean} [multi=false] - Optional, default is false.
|
|
96
96
|
* @throws Will throw an error if any of the parameters are of incorrect type or invalid value.
|
|
97
97
|
*/
|
|
98
|
-
constructor(dimensionsOrConfigs: number | bigint |
|
|
98
|
+
constructor(dimensionsOrConfigs: number | bigint | IndexConfig, metric?: MetricKind, quantization?: ScalarKind, connectivity?: number, expansion_add?: number, expansion_search?: number, multi?: boolean);
|
|
99
99
|
/**
|
|
100
100
|
* Add vectors to the index.
|
|
101
101
|
*
|
|
@@ -72,7 +72,7 @@ export declare class BatchMatches {
|
|
|
72
72
|
*/
|
|
73
73
|
get(i: number): Matches;
|
|
74
74
|
}
|
|
75
|
-
export interface
|
|
75
|
+
export interface IndexConfig {
|
|
76
76
|
dimensions: number;
|
|
77
77
|
metric: MetricKind;
|
|
78
78
|
quantization: ScalarKind;
|
|
@@ -95,7 +95,7 @@ export declare class Index {
|
|
|
95
95
|
* @param {boolean} [multi=false] - Optional, default is false.
|
|
96
96
|
* @throws Will throw an error if any of the parameters are of incorrect type or invalid value.
|
|
97
97
|
*/
|
|
98
|
-
constructor(dimensionsOrConfigs: number | bigint |
|
|
98
|
+
constructor(dimensionsOrConfigs: number | bigint | IndexConfig, metric?: MetricKind, quantization?: ScalarKind, connectivity?: number, expansion_add?: number, expansion_search?: number, multi?: boolean);
|
|
99
99
|
/**
|
|
100
100
|
* Add vectors to the index.
|
|
101
101
|
*
|
package/javascript/usearch.ts
CHANGED
|
@@ -203,7 +203,7 @@ function normalizeVectors(
|
|
|
203
203
|
return flattenedVectors;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
export interface
|
|
206
|
+
export interface IndexConfig {
|
|
207
207
|
dimensions: number;
|
|
208
208
|
metric: MetricKind;
|
|
209
209
|
quantization: ScalarKind;
|
|
@@ -227,7 +227,7 @@ export class Index {
|
|
|
227
227
|
* @throws Will throw an error if any of the parameters are of incorrect type or invalid value.
|
|
228
228
|
*/
|
|
229
229
|
constructor(
|
|
230
|
-
dimensionsOrConfigs: number | bigint |
|
|
230
|
+
dimensionsOrConfigs: number | bigint | IndexConfig,
|
|
231
231
|
metric: MetricKind = MetricKind.Cos,
|
|
232
232
|
quantization: ScalarKind = ScalarKind.F32,
|
|
233
233
|
connectivity: number = 0,
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/simsimd/CONTRIBUTING.md
CHANGED
|
@@ -101,7 +101,7 @@ You can also benchmark against other libraries, filter the numeric types, and di
|
|
|
101
101
|
$ python scripts/bench_vectors.py --help
|
|
102
102
|
> usage: bench.py [-h] [--ndim NDIM] [-n COUNT]
|
|
103
103
|
> [--metric {all,dot,spatial,binary,probability,sparse}]
|
|
104
|
-
> [--dtype {all,
|
|
104
|
+
> [--dtype {all,bin8,int8,uint16,uint32,float16,float32,float64,bfloat16,complex32,complex64,complex128}]
|
|
105
105
|
> [--scipy] [--scikit] [--torch] [--tf] [--jax]
|
|
106
106
|
>
|
|
107
107
|
> Benchmark SimSIMD vs. other libraries
|
|
@@ -119,7 +119,7 @@ $ python scripts/bench_vectors.py --help
|
|
|
119
119
|
> `cdist`.
|
|
120
120
|
> --metric {all,dot,spatial,binary,probability,sparse}
|
|
121
121
|
> Distance metric to use, profiles everything by default
|
|
122
|
-
> --dtype {all,
|
|
122
|
+
> --dtype {all,bin8,int8,uint16,uint32,float16,float32,float64,bfloat16,complex32,complex64,complex128}
|
|
123
123
|
> Defines numeric types to benchmark, profiles everything by default
|
|
124
124
|
> --scipy Profile SciPy, must be installed
|
|
125
125
|
> --scikit Profile scikit-learn, must be installed
|
|
@@ -203,6 +203,35 @@ bun test
|
|
|
203
203
|
swift build && swift test -v
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
+
Running Swift on Linux requires a couple of extra steps, as the Swift compiler is not available in the default repositories.
|
|
207
|
+
Please get the most recent Swift tarball from the [official website](https://www.swift.org/install/).
|
|
208
|
+
At the time of writing, for 64-bit Arm CPU running Ubuntu 22.04, the following commands would work:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
wget https://download.swift.org/swift-5.9.2-release/ubuntu2204-aarch64/swift-5.9.2-RELEASE/swift-5.9.2-RELEASE-ubuntu22.04-aarch64.tar.gz
|
|
212
|
+
tar xzf swift-5.9.2-RELEASE-ubuntu22.04-aarch64.tar.gz
|
|
213
|
+
sudo mv swift-5.9.2-RELEASE-ubuntu22.04-aarch64 /usr/share/swift
|
|
214
|
+
echo "export PATH=/usr/share/swift/usr/bin:$PATH" >> ~/.bashrc
|
|
215
|
+
source ~/.bashrc
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
You can check the available images on [`swift.org/download` page](https://www.swift.org/download/#releases).
|
|
219
|
+
For x86 CPUs, the following commands would work:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
wget https://download.swift.org/swift-5.9.2-release/ubuntu2204/swift-5.9.2-RELEASE/swift-5.9.2-RELEASE-ubuntu22.04.tar.gz
|
|
223
|
+
tar xzf swift-5.9.2-RELEASE-ubuntu22.04.tar.gz
|
|
224
|
+
sudo mv swift-5.9.2-RELEASE-ubuntu22.04 /usr/share/swift
|
|
225
|
+
echo "export PATH=/usr/share/swift/usr/bin:$PATH" >> ~/.bashrc
|
|
226
|
+
source ~/.bashrc
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Alternatively, on Linux, the official Swift Docker image can be used for builds and tests:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
sudo docker run --rm -v "$PWD:/workspace" -w /workspace swift:5.9 /bin/bash -cl "swift build -c release --static-swift-stdlib && swift test -c release --enable-test-discovery"
|
|
233
|
+
```
|
|
234
|
+
|
|
206
235
|
## GoLang
|
|
207
236
|
|
|
208
237
|
```sh
|
package/simsimd/Cargo.lock
CHANGED
package/simsimd/Cargo.toml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "simsimd"
|
|
3
3
|
description = "Portable mixed-precision BLAS-like vector math library for x86 and ARM"
|
|
4
|
-
version = "
|
|
4
|
+
version = "6.0.5"
|
|
5
5
|
edition = "2021"
|
|
6
6
|
license = "Apache-2.0"
|
|
7
7
|
authors = ["Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
|
package/simsimd/README.md
CHANGED
|
@@ -69,9 +69,9 @@ Implemented distance functions include:
|
|
|
69
69
|
|
|
70
70
|
Moreover, SimSIMD...
|
|
71
71
|
|
|
72
|
-
- handles `
|
|
73
|
-
- handles `
|
|
74
|
-
- handles sparse `
|
|
72
|
+
- handles `float64`, `float32`, `float16`, and `bfloat16` real & complex vectors.
|
|
73
|
+
- handles `int8` integral, `int4` sub-byte, and `b8` binary vectors.
|
|
74
|
+
- handles sparse `uint32` and `uint16` sets, and weighted sparse vectors.
|
|
75
75
|
- is a zero-dependency [header-only C 99](#using-simsimd-in-c) library.
|
|
76
76
|
- has [Python](#using-simsimd-in-python), [Rust](#using-simsimd-in-rust), [JS](#using-simsimd-in-javascript), and [Swift](#using-simsimd-in-swift) bindings.
|
|
77
77
|
- has Arm backends for NEON, Scalable Vector Extensions (SVE), and SVE2.
|
|
@@ -95,14 +95,14 @@ You can learn more about the technical implementation details in the following b
|
|
|
95
95
|
For reference, we use 1536-dimensional vectors, like the embeddings produced by the OpenAI Ada API.
|
|
96
96
|
Comparing the serial code throughput produced by GCC 12 to hand-optimized kernels in SimSIMD, we see the following single-core improvements for the two most common vector-vector similarity metrics - the Cosine similarity and the Euclidean distance:
|
|
97
97
|
|
|
98
|
-
| Type
|
|
99
|
-
|
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
98
|
+
| Type | Apple M2 Pro | Intel Sapphire Rapids | AWS Graviton 4 |
|
|
99
|
+
| :--------- | ----------------------------: | -------------------------------: | ------------------------------: |
|
|
100
|
+
| `float64` | 18.5 → 28.8 GB/s <br/> + 56 % | 21.9 → 41.4 GB/s <br/> + 89 % | 20.7 → 41.3 GB/s <br/> + 99 % |
|
|
101
|
+
| `float32` | 9.2 → 29.6 GB/s <br/> + 221 % | 10.9 → 95.8 GB/s <br/> + 779 % | 4.9 → 41.9 GB/s <br/> + 755 % |
|
|
102
|
+
| `float16` | 4.6 → 14.6 GB/s <br/> + 217 % | 3.1 → 108.4 GB/s <br/> + 3,397 % | 5.4 → 39.3 GB/s <br/> + 627 % |
|
|
103
|
+
| `bfloat16` | 4.6 → 26.3 GB/s <br/> + 472 % | 0.8 → 59.5 GB/s <br/> +7,437 % | 2.5 → 29.9 GB/s <br/> + 1,096 % |
|
|
104
|
+
| `int8` | 25.8 → 47.1 GB/s <br/> + 83 % | 33.1 → 65.3 GB/s <br/> + 97 % | 35.2 → 43.5 GB/s <br/> + 24 % |
|
|
105
|
+
| `uint8` | | 32.5 → 66.5 GB/s <br/> + 105 % | |
|
|
106
106
|
|
|
107
107
|
Similar speedups are often observed even when compared to BLAS and LAPACK libraries underlying most numerical computing libraries, including NumPy and SciPy in Python.
|
|
108
108
|
Broader benchmarking results:
|
|
@@ -115,8 +115,8 @@ Broader benchmarking results:
|
|
|
115
115
|
|
|
116
116
|
The package is intended to replace the usage of `numpy.inner`, `numpy.dot`, and `scipy.spatial.distance`.
|
|
117
117
|
Aside from drastic performance improvements, SimSIMD significantly improves accuracy in mixed precision setups.
|
|
118
|
-
NumPy and SciPy, processing `
|
|
119
|
-
The same applies to processing `
|
|
118
|
+
NumPy and SciPy, processing `int8`, `uint8` or `float16` vectors, will use the same types for accumulators, while SimSIMD can combine `int8` enumeration, `int16` multiplication, and `int32` accumulation to avoid overflows entirely.
|
|
119
|
+
The same applies to processing `float16` and `bfloat16` values with `float32` precision.
|
|
120
120
|
|
|
121
121
|
### Installation
|
|
122
122
|
|
|
@@ -155,14 +155,33 @@ dist = simsimd.vdot(vec1.astype(np.complex64), vec2.astype(np.complex64)) # conj
|
|
|
155
155
|
```
|
|
156
156
|
|
|
157
157
|
Unlike SciPy, SimSIMD allows explicitly stating the precision of the input vectors, which is especially useful for mixed-precision setups.
|
|
158
|
+
The `dtype` argument can be passed both by name and as a positional argument:
|
|
158
159
|
|
|
159
160
|
```py
|
|
160
|
-
dist = simsimd.cosine(vec1, vec2, "
|
|
161
|
-
dist = simsimd.cosine(vec1, vec2, "
|
|
162
|
-
dist = simsimd.cosine(vec1, vec2, "
|
|
163
|
-
dist = simsimd.cosine(vec1, vec2, "
|
|
164
|
-
dist = simsimd.hamming(vec1, vec2, "
|
|
165
|
-
|
|
161
|
+
dist = simsimd.cosine(vec1, vec2, "int8")
|
|
162
|
+
dist = simsimd.cosine(vec1, vec2, "float16")
|
|
163
|
+
dist = simsimd.cosine(vec1, vec2, "float32")
|
|
164
|
+
dist = simsimd.cosine(vec1, vec2, "float64")
|
|
165
|
+
dist = simsimd.hamming(vec1, vec2, "bit8")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
With other frameworks, like PyTorch, one can get a richer type-system than NumPy, but the lack of good CPython interoperability makes it hard to pass data without copies.
|
|
169
|
+
|
|
170
|
+
```py
|
|
171
|
+
import numpy as np
|
|
172
|
+
buf1 = np.empty(8, dtype=np.uint16)
|
|
173
|
+
buf2 = np.empty(8, dtype=np.uint16)
|
|
174
|
+
|
|
175
|
+
# View the same memory region with PyTorch and randomize it
|
|
176
|
+
import torch
|
|
177
|
+
vec1 = torch.asarray(memoryview(buf1), copy=False).view(torch.bfloat16)
|
|
178
|
+
vec2 = torch.asarray(memoryview(buf2), copy=False).view(torch.bfloat16)
|
|
179
|
+
torch.randn(8, out=vec1)
|
|
180
|
+
torch.randn(8, out=vec2)
|
|
181
|
+
|
|
182
|
+
# Both libs will look into the same memory buffers and report the same results
|
|
183
|
+
dist_slow = 1 - torch.nn.functional.cosine_similarity(vec1, vec2, dim=0)
|
|
184
|
+
dist_fast = simsimd.cosine(buf1, buf2, "bf16")
|
|
166
185
|
```
|
|
167
186
|
|
|
168
187
|
It also allows using SimSIMD for half-precision complex numbers, which NumPy does not support.
|
|
@@ -183,10 +202,10 @@ import numpy as np
|
|
|
183
202
|
import simsimd as simd
|
|
184
203
|
|
|
185
204
|
length1, length2 = randint(1, 100), randint(1, 100)
|
|
186
|
-
vec1 = np.random.randint(0, 1000, length1).astype(np.uint16)
|
|
187
|
-
vec2 = np.random.randint(0, 1000, length2).astype(np.uint16)
|
|
205
|
+
vec1 = np.sort(np.random.randint(0, 1000, length1).astype(np.uint16))
|
|
206
|
+
vec2 = np.sort(np.random.randint(0, 1000, length2).astype(np.uint16))
|
|
188
207
|
|
|
189
|
-
slow_result = np.intersect1d(vec1, vec2)
|
|
208
|
+
slow_result = len(np.intersect1d(vec1, vec2))
|
|
190
209
|
fast_result = simd.intersect(vec1, vec2)
|
|
191
210
|
assert slow_result == fast_result
|
|
192
211
|
```
|
|
@@ -235,6 +254,48 @@ distances: DistancesTensor = simsimd.cdist(matrix1, matrix2, metric="cosine")
|
|
|
235
254
|
distances_array: np.ndarray = np.array(distances, copy=True) # now managed by NumPy
|
|
236
255
|
```
|
|
237
256
|
|
|
257
|
+
### Elementwise Kernels
|
|
258
|
+
|
|
259
|
+
SimSIMD also provides mixed-precision elementwise kernels, where the input vectors and the output have the same numeric type, but the intermediate accumulators are of a higher precision.
|
|
260
|
+
|
|
261
|
+
```py
|
|
262
|
+
import numpy as np
|
|
263
|
+
from simsimd import fma, wsum
|
|
264
|
+
|
|
265
|
+
# Let's take two FullHD video frames
|
|
266
|
+
first_frame = np.random.randn(1920 * 1024).astype(np.uint8)
|
|
267
|
+
second_frame = np.random.randn(1920 * 1024).astype(np.uint8)
|
|
268
|
+
average_frame = np.empty_like(first_frame)
|
|
269
|
+
wsum(first_frame, second_frame, alpha=0.5, beta=0.5, out=average_frame)
|
|
270
|
+
|
|
271
|
+
# Slow analog with NumPy:
|
|
272
|
+
slow_average_frame = (0.5 * first_frame + 0.5 * second_frame).astype(np.uint8)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Similarly, the `fma` takes three arguments and computes the fused multiply-add operation.
|
|
276
|
+
In applications like Machine Learning you may also benefit from using the "brain-float" format not natively supported by NumPy.
|
|
277
|
+
In 3D Graphics, for example, we can use FMA to compute the [Phong shading model](https://en.wikipedia.org/wiki/Phong_shading):
|
|
278
|
+
|
|
279
|
+
```py
|
|
280
|
+
# Assume a FullHD frame with random values for simplicity
|
|
281
|
+
light_intensity = np.random.rand(1920 * 1080).astype(np.float16) # Intensity of light on each pixel
|
|
282
|
+
diffuse_component = np.random.rand(1920 * 1080).astype(np.float16) # Diffuse reflectance on the surface
|
|
283
|
+
specular_component = np.random.rand(1920 * 1080).astype(np.float16) # Specular reflectance for highlights
|
|
284
|
+
output_color = np.empty_like(light_intensity) # Array to store the resulting color intensity
|
|
285
|
+
|
|
286
|
+
# Define the scaling factors for diffuse and specular contributions
|
|
287
|
+
alpha = 0.7 # Weight for the diffuse component
|
|
288
|
+
beta = 0.3 # Weight for the specular component
|
|
289
|
+
|
|
290
|
+
# Formula: color = alpha * light_intensity * diffuse_component + beta * specular_component
|
|
291
|
+
fma(light_intensity, diffuse_component, specular_component,
|
|
292
|
+
dtype="float16", # Optional, unless it can't be inferred from the input
|
|
293
|
+
alpha=alpha, beta=beta, out=output_color)
|
|
294
|
+
|
|
295
|
+
# Slow analog with NumPy for comparison
|
|
296
|
+
slow_output_color = (alpha * light_intensity * diffuse_component + beta * specular_component).astype(np.float16)
|
|
297
|
+
```
|
|
298
|
+
|
|
238
299
|
### Multithreading and Memory Usage
|
|
239
300
|
|
|
240
301
|
By default, computations use a single CPU core.
|
|
@@ -248,15 +309,15 @@ matrix1 = np.packbits(np.random.randint(2, size=(10_000, ndim)).astype(np.uint8)
|
|
|
248
309
|
matrix2 = np.packbits(np.random.randint(2, size=(1_000, ndim)).astype(np.uint8))
|
|
249
310
|
|
|
250
311
|
distances = simsimd.cdist(matrix1, matrix2,
|
|
251
|
-
metric="hamming",
|
|
252
|
-
out_dtype="
|
|
253
|
-
threads=0,
|
|
254
|
-
dtype="
|
|
312
|
+
metric="hamming", # Unlike SciPy, SimSIMD doesn't divide by the number of dimensions
|
|
313
|
+
out_dtype="uint8", # so we can use `uint8` instead of `float64` to save memory.
|
|
314
|
+
threads=0, # Use all CPU cores with OpenMP.
|
|
315
|
+
dtype="bin8", # Override input argument type to `bin8` eight-bit words.
|
|
255
316
|
)
|
|
256
317
|
```
|
|
257
318
|
|
|
258
|
-
By default, the output distances will be stored in double-precision `
|
|
259
|
-
That behavior may not be space-efficient, especially if you are computing the hamming distance between short binary vectors, that will generally fit into 8x smaller `
|
|
319
|
+
By default, the output distances will be stored in double-precision `float64` floating-point numbers.
|
|
320
|
+
That behavior may not be space-efficient, especially if you are computing the hamming distance between short binary vectors, that will generally fit into 8x smaller `uint8` or `uint16` types.
|
|
260
321
|
To override this behavior, use the `dtype` argument.
|
|
261
322
|
|
|
262
323
|
### Helper Functions
|
|
@@ -575,7 +636,7 @@ Simplest of all, you can include the headers, and the compiler will automaticall
|
|
|
575
636
|
int main() {
|
|
576
637
|
simsimd_f32_t vector_a[1536];
|
|
577
638
|
simsimd_f32_t vector_b[1536];
|
|
578
|
-
|
|
639
|
+
simsimd_kernel_punned_t distance_function = simsimd_metric_punned(
|
|
579
640
|
simsimd_metric_cos_k, // Metric kind, like the angular cosine distance
|
|
580
641
|
simsimd_datatype_f32_k, // Data type, like: f16, f32, f64, i8, b8, and complex variants
|
|
581
642
|
simsimd_cap_any_k); // Which CPU capabilities are we allowed to use
|
|
@@ -663,7 +724,6 @@ int main() {
|
|
|
663
724
|
simsimd_vdot_f16c(f16s, f16s, 1536, &distance);
|
|
664
725
|
simsimd_vdot_f32c(f32s, f32s, 1536, &distance);
|
|
665
726
|
simsimd_vdot_f64c(f64s, f64s, 1536, &distance);
|
|
666
|
-
|
|
667
727
|
return 0;
|
|
668
728
|
}
|
|
669
729
|
```
|
|
@@ -676,13 +736,8 @@ int main() {
|
|
|
676
736
|
int main() {
|
|
677
737
|
simsimd_b8_t b8s[1536 / 8]; // 8 bits per word
|
|
678
738
|
simsimd_distance_t distance;
|
|
679
|
-
|
|
680
|
-
// Hamming distance between two vectors
|
|
681
739
|
simsimd_hamming_b8(b8s, b8s, 1536 / 8, &distance);
|
|
682
|
-
|
|
683
|
-
// Jaccard distance between two vectors
|
|
684
740
|
simsimd_jaccard_b8(b8s, b8s, 1536 / 8, &distance);
|
|
685
|
-
|
|
686
741
|
return 0;
|
|
687
742
|
}
|
|
688
743
|
```
|
|
@@ -707,7 +762,6 @@ int main() {
|
|
|
707
762
|
simsimd_kl_f16(f16s, f16s, 1536, &distance);
|
|
708
763
|
simsimd_kl_f32(f32s, f32s, 1536, &distance);
|
|
709
764
|
simsimd_kl_f64(f64s, f64s, 1536, &distance);
|
|
710
|
-
|
|
711
765
|
return 0;
|
|
712
766
|
}
|
|
713
767
|
```
|
|
@@ -949,10 +1003,10 @@ In NumPy terms, the implementation may look like:
|
|
|
949
1003
|
|
|
950
1004
|
```py
|
|
951
1005
|
import numpy as np
|
|
952
|
-
def wsum(A: np.ndarray, B: np.ndarray, Alpha: float, Beta: float) -> np.ndarray:
|
|
1006
|
+
def wsum(A: np.ndarray, B: np.ndarray, /, Alpha: float, Beta: float) -> np.ndarray:
|
|
953
1007
|
assert A.dtype == B.dtype, "Input types must match and affect the output style"
|
|
954
1008
|
return (Alpha * A + Beta * B).astype(A.dtype)
|
|
955
|
-
def fma(A: np.ndarray, B: np.ndarray, C: np.ndarray, Alpha: float, Beta: float) -> np.ndarray:
|
|
1009
|
+
def fma(A: np.ndarray, B: np.ndarray, C: np.ndarray, /, Alpha: float, Beta: float) -> np.ndarray:
|
|
956
1010
|
assert A.dtype == B.dtype and A.dtype == C.dtype, "Input types must match and affect the output style"
|
|
957
1011
|
return (Alpha * A * B + Beta * C).astype(A.dtype)
|
|
958
1012
|
```
|
|
@@ -1095,7 +1149,7 @@ All of the function names follow the same pattern: `simsimd_{function}_{type}_{b
|
|
|
1095
1149
|
- The type can be `f64`, `f32`, `f16`, `bf16`, `f64c`, `f32c`, `f16c`, `bf16c`, `i8`, or `b8`.
|
|
1096
1150
|
- The function can be `dot`, `vdot`, `cos`, `l2sq`, `hamming`, `jaccard`, `kl`, `js`, or `intersect`.
|
|
1097
1151
|
|
|
1098
|
-
To avoid hard-coding the backend, you can use the `
|
|
1152
|
+
To avoid hard-coding the backend, you can use the `simsimd_kernel_punned_t` to pun the function pointer and the `simsimd_capabilities` function to get the available backends at runtime.
|
|
1099
1153
|
To match all the function names, consider a RegEx:
|
|
1100
1154
|
|
|
1101
1155
|
```regex
|
package/simsimd/c/lib.c
CHANGED
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
#define SIMSIMD_NATIVE_F16 0
|
|
8
8
|
#define SIMSIMD_NATIVE_BF16 0
|
|
9
9
|
|
|
10
|
+
/* Override the primary serial operations to avoid the LibC dependency.
|
|
11
|
+
*/
|
|
12
|
+
#define SIMSIMD_SQRT(x) simsimd_approximate_square_root(x)
|
|
13
|
+
#define SIMSIMD_RSQRT(x) simsimd_approximate_inverse_square_root(x)
|
|
14
|
+
#define SIMSIMD_LOG(x) simsimd_approximate_log(x)
|
|
15
|
+
|
|
10
16
|
/* Depending on the Operating System, the following intrinsics are available
|
|
11
17
|
* on recent compiler toolchains:
|
|
12
18
|
*
|
|
@@ -55,20 +61,21 @@ extern "C" {
|
|
|
55
61
|
// If no metric is found, it returns NaN. We can obtain NaN by dividing 0.0 by 0.0, but that annoys
|
|
56
62
|
// the MSVC compiler. Instead we can directly write-in the signaling NaN (0x7FF0000000000001)
|
|
57
63
|
// or the qNaN (0x7FF8000000000000).
|
|
58
|
-
#define SIMSIMD_DECLARATION_DENSE(name, extension, type)
|
|
59
|
-
SIMSIMD_DYNAMIC void simsimd_##name##_##extension(simsimd_##type##_t const *a, simsimd_##type##_t const *b,
|
|
60
|
-
simsimd_size_t n, simsimd_distance_t *results) {
|
|
61
|
-
static
|
|
62
|
-
if (metric == 0) {
|
|
63
|
-
simsimd_capability_t used_capability;
|
|
64
|
-
|
|
65
|
-
simsimd_capabilities(), simsimd_cap_any_k, &metric,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
#define SIMSIMD_DECLARATION_DENSE(name, extension, type) \
|
|
65
|
+
SIMSIMD_DYNAMIC void simsimd_##name##_##extension(simsimd_##type##_t const *a, simsimd_##type##_t const *b, \
|
|
66
|
+
simsimd_size_t n, simsimd_distance_t *results) { \
|
|
67
|
+
static simsimd_metric_dense_punned_t metric = 0; \
|
|
68
|
+
if (metric == 0) { \
|
|
69
|
+
simsimd_capability_t used_capability; \
|
|
70
|
+
simsimd_find_kernel_punned(simsimd_metric_##name##_k, simsimd_datatype_##extension##_k, \
|
|
71
|
+
simsimd_capabilities(), simsimd_cap_any_k, (simsimd_kernel_punned_t *)&metric, \
|
|
72
|
+
&used_capability); \
|
|
73
|
+
if (!metric) { \
|
|
74
|
+
*(simsimd_u64_t *)results = 0x7FF0000000000001ull; \
|
|
75
|
+
return; \
|
|
76
|
+
} \
|
|
77
|
+
} \
|
|
78
|
+
metric(a, b, n, results); \
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
#define SIMSIMD_DECLARATION_SPARSE(name, extension, type) \
|
|
@@ -78,9 +85,9 @@ extern "C" {
|
|
|
78
85
|
static simsimd_metric_sparse_punned_t metric = 0; \
|
|
79
86
|
if (metric == 0) { \
|
|
80
87
|
simsimd_capability_t used_capability; \
|
|
81
|
-
|
|
88
|
+
simsimd_find_kernel_punned(simsimd_metric_##name##_k, simsimd_datatype_##extension##_k, \
|
|
82
89
|
simsimd_capabilities(), simsimd_cap_any_k, \
|
|
83
|
-
(
|
|
90
|
+
(simsimd_kernel_punned_t *)(&metric), &used_capability); \
|
|
84
91
|
if (!metric) { \
|
|
85
92
|
*(simsimd_u64_t *)result = 0x7FF0000000000001ull; \
|
|
86
93
|
return; \
|
|
@@ -96,9 +103,9 @@ extern "C" {
|
|
|
96
103
|
static simsimd_metric_curved_punned_t metric = 0; \
|
|
97
104
|
if (metric == 0) { \
|
|
98
105
|
simsimd_capability_t used_capability; \
|
|
99
|
-
|
|
106
|
+
simsimd_find_kernel_punned(simsimd_metric_##name##_k, simsimd_datatype_##extension##_k, \
|
|
100
107
|
simsimd_capabilities(), simsimd_cap_any_k, \
|
|
101
|
-
(
|
|
108
|
+
(simsimd_kernel_punned_t *)(&metric), &used_capability); \
|
|
102
109
|
if (!metric) { \
|
|
103
110
|
*(simsimd_u64_t *)result = 0x7FF0000000000001ull; \
|
|
104
111
|
return; \
|
|
@@ -114,9 +121,9 @@ extern "C" {
|
|
|
114
121
|
static simsimd_kernel_fma_punned_t metric = 0; \
|
|
115
122
|
if (metric == 0) { \
|
|
116
123
|
simsimd_capability_t used_capability; \
|
|
117
|
-
|
|
124
|
+
simsimd_find_kernel_punned(simsimd_metric_##name##_k, simsimd_datatype_##extension##_k, \
|
|
118
125
|
simsimd_capabilities(), simsimd_cap_any_k, \
|
|
119
|
-
(
|
|
126
|
+
(simsimd_kernel_punned_t *)(&metric), &used_capability); \
|
|
120
127
|
} \
|
|
121
128
|
metric(a, b, c, n, alpha, beta, result); \
|
|
122
129
|
}
|
|
@@ -128,9 +135,9 @@ extern "C" {
|
|
|
128
135
|
static simsimd_kernel_wsum_punned_t metric = 0; \
|
|
129
136
|
if (metric == 0) { \
|
|
130
137
|
simsimd_capability_t used_capability; \
|
|
131
|
-
|
|
138
|
+
simsimd_find_kernel_punned(simsimd_metric_##name##_k, simsimd_datatype_##extension##_k, \
|
|
132
139
|
simsimd_capabilities(), simsimd_cap_any_k, \
|
|
133
|
-
(
|
|
140
|
+
(simsimd_kernel_punned_t *)(&metric), &used_capability); \
|
|
134
141
|
} \
|
|
135
142
|
metric(a, b, n, alpha, beta, result); \
|
|
136
143
|
}
|
|
@@ -326,14 +333,14 @@ SIMSIMD_DYNAMIC simsimd_capability_t simsimd_capabilities(void) {
|
|
|
326
333
|
return static_capabilities;
|
|
327
334
|
}
|
|
328
335
|
|
|
329
|
-
SIMSIMD_DYNAMIC void
|
|
336
|
+
SIMSIMD_DYNAMIC void simsimd_find_kernel_punned( //
|
|
330
337
|
simsimd_metric_kind_t kind, //
|
|
331
338
|
simsimd_datatype_t datatype, //
|
|
332
339
|
simsimd_capability_t supported, //
|
|
333
340
|
simsimd_capability_t allowed, //
|
|
334
|
-
|
|
341
|
+
simsimd_kernel_punned_t *kernel_output, //
|
|
335
342
|
simsimd_capability_t *capability_output) {
|
|
336
|
-
|
|
343
|
+
_simsimd_find_kernel_punned_implementation(kind, datatype, supported, allowed, kernel_output, capability_output);
|
|
337
344
|
}
|
|
338
345
|
|
|
339
346
|
#ifdef __cplusplus
|