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/javascript/lib.cpp
CHANGED
|
@@ -320,8 +320,10 @@ Napi::Value CompiledIndex::Remove(Napi::CallbackInfo const& ctx) {
|
|
|
320
320
|
Napi::Array results = Napi::Array::New(env, length);
|
|
321
321
|
for (std::size_t i = 0; i < length; ++i) {
|
|
322
322
|
auto result = native_->remove(static_cast<default_key_t>(keys[i]));
|
|
323
|
-
if (!result)
|
|
324
|
-
Napi::Error::New(
|
|
323
|
+
if (!result) {
|
|
324
|
+
Napi::Error::New(env, result.error.release()).ThrowAsJavaScriptException();
|
|
325
|
+
return env.Null();
|
|
326
|
+
}
|
|
325
327
|
results[i] = Napi::Number::New(env, result.completed);
|
|
326
328
|
}
|
|
327
329
|
return results;
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
const nodeTest = require(
|
|
1
|
+
const nodeTest = require("node:test");
|
|
2
2
|
const realTest = nodeTest.test; // the original function
|
|
3
3
|
|
|
4
4
|
function loggedTest(name, options, fn) {
|
|
5
5
|
// The API has two call signatures:
|
|
6
6
|
// test(name, fn)
|
|
7
7
|
// test(name, options, fn)
|
|
8
|
-
if (typeof options ===
|
|
8
|
+
if (typeof options === "function") {
|
|
9
9
|
fn = options;
|
|
10
10
|
options = undefined;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
// Wrap the body so we can log before / after
|
|
14
14
|
const wrapped = async (t) => {
|
|
15
|
-
console.log(
|
|
15
|
+
console.log("▶", name);
|
|
16
16
|
try {
|
|
17
17
|
await fn(t); // run the user’s test
|
|
18
|
-
console.log(
|
|
18
|
+
console.log("✓", name);
|
|
19
19
|
} catch (err) {
|
|
20
|
-
console.log(
|
|
20
|
+
console.log("✖", name);
|
|
21
21
|
throw err; // re-throw so the runner records the failure
|
|
22
22
|
}
|
|
23
23
|
};
|
|
@@ -30,43 +30,43 @@ function loggedTest(name, options, fn) {
|
|
|
30
30
|
global.test = loggedTest;
|
|
31
31
|
module.exports = loggedTest; // for completeness if this file is `require`d
|
|
32
32
|
|
|
33
|
-
const assert = require(
|
|
34
|
-
const fs = require(
|
|
35
|
-
const os = require(
|
|
36
|
-
const path = require(
|
|
37
|
-
const usearch = require(
|
|
33
|
+
const assert = require("node:assert");
|
|
34
|
+
const fs = require("node:fs");
|
|
35
|
+
const os = require("node:os");
|
|
36
|
+
const path = require("node:path");
|
|
37
|
+
const usearch = require("./dist/cjs/usearch.js");
|
|
38
38
|
|
|
39
39
|
function assertAlmostEqual(actual, expected, tolerance = 1e-6) {
|
|
40
40
|
const lowerBound = expected - tolerance;
|
|
41
41
|
const upperBound = expected + tolerance;
|
|
42
42
|
assert(
|
|
43
43
|
actual >= lowerBound && actual <= upperBound,
|
|
44
|
-
`Expected ${actual} to be almost equal to ${expected}
|
|
44
|
+
`Expected ${actual} to be almost equal to ${expected}`,
|
|
45
45
|
);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const THREADS = 2;
|
|
49
49
|
|
|
50
|
-
test(
|
|
51
|
-
await t.test(
|
|
52
|
-
const index = new usearch.Index(2,
|
|
50
|
+
test("Single-entry operations", async (t) => {
|
|
51
|
+
await t.test("index info", () => {
|
|
52
|
+
const index = new usearch.Index(2, "l2sq");
|
|
53
53
|
|
|
54
|
-
assert.equal(index.connectivity(), 16,
|
|
55
|
-
assert.equal(index.dimensions(), 2,
|
|
56
|
-
assert.equal(index.size(), 0,
|
|
54
|
+
assert.equal(index.connectivity(), 16, "connectivity should be 16");
|
|
55
|
+
assert.equal(index.dimensions(), 2, "dimensions should be 2");
|
|
56
|
+
assert.equal(index.size(), 0, "initial size should be 0");
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
await t.test(
|
|
60
|
-
const index = new usearch.Index(2,
|
|
59
|
+
await t.test("add", () => {
|
|
60
|
+
const index = new usearch.Index(2, "l2sq");
|
|
61
61
|
|
|
62
62
|
index.add(15n, new Float32Array([10, 20]));
|
|
63
63
|
index.add(16n, new Float32Array([10, 25]));
|
|
64
64
|
|
|
65
|
-
assert.equal(index.size(), 2,
|
|
65
|
+
assert.equal(index.size(), 2, "size after adding elements should be 2");
|
|
66
66
|
assert.equal(
|
|
67
67
|
index.contains(15),
|
|
68
68
|
true,
|
|
69
|
-
|
|
69
|
+
"entry must be present after insertion",
|
|
70
70
|
);
|
|
71
71
|
|
|
72
72
|
const results = index.search(new Float32Array([13, 14]), 2);
|
|
@@ -74,17 +74,17 @@ test('Single-entry operations', async (t) => {
|
|
|
74
74
|
assert.deepEqual(
|
|
75
75
|
results.keys,
|
|
76
76
|
new BigUint64Array([15n, 16n]),
|
|
77
|
-
|
|
77
|
+
"keys should be 15 and 16",
|
|
78
78
|
);
|
|
79
79
|
assert.deepEqual(
|
|
80
80
|
results.distances,
|
|
81
81
|
new Float32Array([45, 130]),
|
|
82
|
-
|
|
82
|
+
"distances should be 45 and 130",
|
|
83
83
|
);
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
await t.test(
|
|
87
|
-
const index = new usearch.Index(2,
|
|
86
|
+
await t.test("remove", () => {
|
|
87
|
+
const index = new usearch.Index(2, "l2sq");
|
|
88
88
|
|
|
89
89
|
index.add(15n, new Float32Array([10, 20]));
|
|
90
90
|
index.add(16n, new Float32Array([10, 25]));
|
|
@@ -93,11 +93,11 @@ test('Single-entry operations', async (t) => {
|
|
|
93
93
|
|
|
94
94
|
assert.equal(index.remove(15n), 1);
|
|
95
95
|
|
|
96
|
-
assert.equal(index.size(), 3,
|
|
96
|
+
assert.equal(index.size(), 3, "size after removing elements should be 3");
|
|
97
97
|
assert.equal(
|
|
98
98
|
index.contains(15),
|
|
99
99
|
false,
|
|
100
|
-
|
|
100
|
+
"entry must be absent after insertion",
|
|
101
101
|
);
|
|
102
102
|
|
|
103
103
|
const results = index.search(new Float32Array([13, 14]), 2);
|
|
@@ -105,37 +105,37 @@ test('Single-entry operations', async (t) => {
|
|
|
105
105
|
assert.deepEqual(
|
|
106
106
|
results.keys,
|
|
107
107
|
new BigUint64Array([16n, 25n]),
|
|
108
|
-
|
|
108
|
+
"keys should not include 15",
|
|
109
109
|
);
|
|
110
110
|
});
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
test(
|
|
114
|
-
await t.test(
|
|
115
|
-
const indexBatch = new usearch.Index(2,
|
|
113
|
+
test("Batch operations", async (t) => {
|
|
114
|
+
await t.test("add", () => {
|
|
115
|
+
const indexBatch = new usearch.Index(2, "l2sq");
|
|
116
116
|
|
|
117
117
|
const keys = [15n, 16n];
|
|
118
118
|
const vectors = [new Float32Array([10, 20]), new Float32Array([10, 25])];
|
|
119
119
|
|
|
120
120
|
indexBatch.add(keys, vectors);
|
|
121
|
-
assert.equal(indexBatch.size(), 2,
|
|
121
|
+
assert.equal(indexBatch.size(), 2, "size after adding batch should be 2");
|
|
122
122
|
|
|
123
123
|
const results = indexBatch.search(new Float32Array([13, 14]), 2);
|
|
124
124
|
|
|
125
125
|
assert.deepEqual(
|
|
126
126
|
results.keys,
|
|
127
127
|
new BigUint64Array([15n, 16n]),
|
|
128
|
-
|
|
128
|
+
"keys should be 15 and 16",
|
|
129
129
|
);
|
|
130
130
|
assert.deepEqual(
|
|
131
131
|
results.distances,
|
|
132
132
|
new Float32Array([45, 130]),
|
|
133
|
-
|
|
133
|
+
"distances should be 45 and 130",
|
|
134
134
|
);
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
-
await t.test(
|
|
138
|
-
const indexBatch = new usearch.Index(2,
|
|
137
|
+
await t.test("remove", () => {
|
|
138
|
+
const indexBatch = new usearch.Index(2, "l2sq");
|
|
139
139
|
|
|
140
140
|
const keys = [15n, 16n, 25n, 26n];
|
|
141
141
|
const vectors = [
|
|
@@ -147,53 +147,58 @@ test('Batch operations', async (t) => {
|
|
|
147
147
|
indexBatch.add(keys, vectors);
|
|
148
148
|
|
|
149
149
|
assert.deepEqual(indexBatch.remove([15n, 25n]), [1, 1]);
|
|
150
|
-
assert.equal(indexBatch.size(), 2,
|
|
150
|
+
assert.equal(indexBatch.size(), 2, "size after removing batch should be 2");
|
|
151
151
|
|
|
152
152
|
const results = indexBatch.search(new Float32Array([13, 14]), 2);
|
|
153
153
|
|
|
154
154
|
assert.deepEqual(
|
|
155
155
|
results.keys,
|
|
156
156
|
new BigUint64Array([16n, 26n]),
|
|
157
|
-
|
|
157
|
+
"keys should not include 15 and 25",
|
|
158
158
|
);
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
-
await t.test(
|
|
162
|
-
const indexBatch = new usearch.Index(3,
|
|
161
|
+
await t.test("search", () => {
|
|
162
|
+
const indexBatch = new usearch.Index(3, "l2sq");
|
|
163
163
|
|
|
164
164
|
const keys = [15n, 16n];
|
|
165
|
-
const vectors = [
|
|
165
|
+
const vectors = [
|
|
166
|
+
new Float32Array([10, 20, 30]),
|
|
167
|
+
new Float32Array([10, 25, 35]),
|
|
168
|
+
];
|
|
166
169
|
|
|
167
170
|
indexBatch.add(keys, vectors);
|
|
168
|
-
assert.equal(indexBatch.size(), 2,
|
|
171
|
+
assert.equal(indexBatch.size(), 2, "size after adding batch should be 2");
|
|
172
|
+
|
|
173
|
+
const results = indexBatch.search(
|
|
174
|
+
[new Float32Array([13, 14, 15]), new Float32Array([13, 14, 15])],
|
|
175
|
+
2,
|
|
176
|
+
);
|
|
169
177
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
assert.equal(results.k, 2, 'k should be 2');
|
|
178
|
+
assert.equal(results.k, 2, "k should be 2");
|
|
173
179
|
|
|
174
180
|
assert.deepEqual(
|
|
175
181
|
results.counts,
|
|
176
|
-
new BigUint64Array([
|
|
177
|
-
|
|
182
|
+
new BigUint64Array([2n, 2n]),
|
|
183
|
+
"counts should be 2 for both keys",
|
|
178
184
|
);
|
|
179
185
|
|
|
180
186
|
assert.deepEqual(
|
|
181
187
|
results.keys,
|
|
182
188
|
new BigUint64Array([15n, 16n, 15n, 16n]),
|
|
183
|
-
|
|
189
|
+
"keys should be 15 and 16",
|
|
184
190
|
);
|
|
185
191
|
assert.deepEqual(
|
|
186
192
|
results.distances,
|
|
187
|
-
new Float32Array(
|
|
188
|
-
|
|
193
|
+
new Float32Array([270, 530, 270, 530]),
|
|
194
|
+
"distances should be 270 and 530",
|
|
189
195
|
);
|
|
190
196
|
});
|
|
191
|
-
|
|
192
197
|
});
|
|
193
198
|
|
|
194
|
-
test(
|
|
199
|
+
test("Expected results", () => {
|
|
195
200
|
const index = new usearch.Index({
|
|
196
|
-
metric:
|
|
201
|
+
metric: "l2sq",
|
|
197
202
|
connectivity: 16,
|
|
198
203
|
dimensions: 3,
|
|
199
204
|
});
|
|
@@ -205,9 +210,9 @@ test('Expected results', () => {
|
|
|
205
210
|
assertAlmostEqual(results.distances[0], new Float32Array([0]));
|
|
206
211
|
});
|
|
207
212
|
|
|
208
|
-
test(
|
|
213
|
+
test("Multithread search returns same results", () => {
|
|
209
214
|
const index = new usearch.Index({
|
|
210
|
-
metric:
|
|
215
|
+
metric: "l2sq",
|
|
211
216
|
connectivity: 16,
|
|
212
217
|
dimensions: 3,
|
|
213
218
|
});
|
|
@@ -218,14 +223,14 @@ test('Multithread search returns same results', () => {
|
|
|
218
223
|
const results_2 = index.search(
|
|
219
224
|
new Float32Array([0.2, 0.6, 0.4]),
|
|
220
225
|
10,
|
|
221
|
-
THREADS
|
|
226
|
+
THREADS,
|
|
222
227
|
);
|
|
223
228
|
|
|
224
229
|
assert.deepEqual(results_1.keys, results_2.keys);
|
|
225
230
|
assertAlmostEqual(results_1.distances, results_2.distances);
|
|
226
231
|
});
|
|
227
232
|
|
|
228
|
-
test(
|
|
233
|
+
test("Exact search", async (t) => {
|
|
229
234
|
const dataset = [
|
|
230
235
|
new Float32Array([0.2, 0.6, 0.4]),
|
|
231
236
|
new Float32Array([0.6, 0.6, 0.4]),
|
|
@@ -233,16 +238,16 @@ test('Exact search', async (t) => {
|
|
|
233
238
|
const queries = new Float32Array([0.2, 0.6, 0.4]);
|
|
234
239
|
const dimensions = 3;
|
|
235
240
|
const count = 2;
|
|
236
|
-
const metric =
|
|
241
|
+
const metric = "l2sq";
|
|
237
242
|
|
|
238
|
-
await t.test(
|
|
243
|
+
await t.test("Single core search", () => {
|
|
239
244
|
const result = usearch.exactSearch(
|
|
240
245
|
dataset,
|
|
241
246
|
queries,
|
|
242
247
|
dimensions,
|
|
243
248
|
count,
|
|
244
249
|
metric,
|
|
245
|
-
1 // threads
|
|
250
|
+
1, // threads
|
|
246
251
|
);
|
|
247
252
|
|
|
248
253
|
assert.deepEqual(result.keys, new BigUint64Array([0n, 1n]));
|
|
@@ -250,14 +255,14 @@ test('Exact search', async (t) => {
|
|
|
250
255
|
assertAlmostEqual(new Float32Array([0.16]), result.distances[1]);
|
|
251
256
|
});
|
|
252
257
|
|
|
253
|
-
await t.test(
|
|
258
|
+
await t.test("Multithreaded search", () => {
|
|
254
259
|
const result = usearch.exactSearch(
|
|
255
260
|
dataset,
|
|
256
261
|
queries,
|
|
257
262
|
dimensions,
|
|
258
263
|
count,
|
|
259
264
|
metric,
|
|
260
|
-
THREADS // threads
|
|
265
|
+
THREADS, // threads
|
|
261
266
|
);
|
|
262
267
|
|
|
263
268
|
assert.deepEqual(result.keys, new BigUint64Array([0n, 1n]));
|
|
@@ -266,30 +271,30 @@ test('Exact search', async (t) => {
|
|
|
266
271
|
});
|
|
267
272
|
});
|
|
268
273
|
|
|
269
|
-
test(
|
|
274
|
+
test("Expected count()", async (t) => {
|
|
270
275
|
const index = new usearch.Index({
|
|
271
|
-
metric:
|
|
276
|
+
metric: "l2sq",
|
|
272
277
|
connectivity: 16,
|
|
273
278
|
dimensions: 3,
|
|
274
279
|
});
|
|
275
280
|
index.add(
|
|
276
281
|
[42n, 43n],
|
|
277
|
-
[new Float32Array([0.2, 0.6, 0.4]), new Float32Array([0.2, 0.6, 0.4])]
|
|
282
|
+
[new Float32Array([0.2, 0.6, 0.4]), new Float32Array([0.2, 0.6, 0.4])],
|
|
278
283
|
);
|
|
279
284
|
|
|
280
|
-
await t.test(
|
|
285
|
+
await t.test("Argument is a number", () => {
|
|
281
286
|
assert.equal(1, index.count(43n));
|
|
282
287
|
});
|
|
283
|
-
await t.test(
|
|
288
|
+
await t.test("Argument is a number (does not exist)", () => {
|
|
284
289
|
assert.equal(0, index.count(44n));
|
|
285
290
|
});
|
|
286
|
-
await t.test(
|
|
291
|
+
await t.test("Argument is an array", () => {
|
|
287
292
|
assert.deepEqual([1, 1, 0], index.count([42n, 43n, 44n]));
|
|
288
293
|
});
|
|
289
294
|
});
|
|
290
295
|
|
|
291
|
-
test(
|
|
292
|
-
const indexBatch = new usearch.Index(2,
|
|
296
|
+
test("Operations with invalid values", () => {
|
|
297
|
+
const indexBatch = new usearch.Index(2, "l2sq");
|
|
293
298
|
|
|
294
299
|
const keys = [NaN, 16n];
|
|
295
300
|
const vectors = [new Float32Array([10, 30]), new Float32Array([1, 5])];
|
|
@@ -301,10 +306,10 @@ test('Operations with invalid values', () => {
|
|
|
301
306
|
assert.throws(() => indexBatch.search(NaN, 2));
|
|
302
307
|
});
|
|
303
308
|
|
|
304
|
-
test(
|
|
305
|
-
await t.test(
|
|
309
|
+
test("Invalid operations", async (t) => {
|
|
310
|
+
await t.test("Add the same keys", () => {
|
|
306
311
|
const index = new usearch.Index({
|
|
307
|
-
metric:
|
|
312
|
+
metric: "l2sq",
|
|
308
313
|
connectivity: 16,
|
|
309
314
|
dimensions: 3,
|
|
310
315
|
});
|
|
@@ -312,9 +317,9 @@ test('Invalid operations', async (t) => {
|
|
|
312
317
|
assert.throws(() => index.add(42n, new Float32Array([0.2, 0.6, 0.4])));
|
|
313
318
|
});
|
|
314
319
|
|
|
315
|
-
await t.test(
|
|
320
|
+
await t.test("Batch add containing the same key", () => {
|
|
316
321
|
const index = new usearch.Index({
|
|
317
|
-
metric:
|
|
322
|
+
metric: "l2sq",
|
|
318
323
|
connectivity: 16,
|
|
319
324
|
dimensions: 3,
|
|
320
325
|
});
|
|
@@ -326,18 +331,18 @@ test('Invalid operations', async (t) => {
|
|
|
326
331
|
[0.1, 0.6, 0.4],
|
|
327
332
|
[0.2, 0.6, 0.4],
|
|
328
333
|
[0.3, 0.6, 0.4],
|
|
329
|
-
]
|
|
334
|
+
],
|
|
330
335
|
);
|
|
331
336
|
});
|
|
332
337
|
});
|
|
333
338
|
});
|
|
334
339
|
|
|
335
|
-
test(
|
|
336
|
-
const indexPath = path.join(os.tmpdir(),
|
|
340
|
+
test("Serialization", async (t) => {
|
|
341
|
+
const indexPath = path.join(os.tmpdir(), "usearch.test.index");
|
|
337
342
|
|
|
338
343
|
t.beforeEach(() => {
|
|
339
344
|
const index = new usearch.Index({
|
|
340
|
-
metric:
|
|
345
|
+
metric: "l2sq",
|
|
341
346
|
connectivity: 16,
|
|
342
347
|
dimensions: 3,
|
|
343
348
|
});
|
|
@@ -349,9 +354,9 @@ test('Serialization', async (t) => {
|
|
|
349
354
|
fs.unlinkSync(indexPath);
|
|
350
355
|
});
|
|
351
356
|
|
|
352
|
-
await t.test(
|
|
357
|
+
await t.test("load", () => {
|
|
353
358
|
const index = new usearch.Index({
|
|
354
|
-
metric:
|
|
359
|
+
metric: "l2sq",
|
|
355
360
|
connectivity: 16,
|
|
356
361
|
dimensions: 3,
|
|
357
362
|
});
|
|
@@ -359,7 +364,7 @@ test('Serialization', async (t) => {
|
|
|
359
364
|
const results = index.search(
|
|
360
365
|
new Float32Array([0.2, 0.6, 0.4]),
|
|
361
366
|
10,
|
|
362
|
-
THREADS
|
|
367
|
+
THREADS,
|
|
363
368
|
);
|
|
364
369
|
|
|
365
370
|
assert.equal(index.size(), 1);
|
|
@@ -371,11 +376,11 @@ test('Serialization', async (t) => {
|
|
|
371
376
|
// The following error in afterEach().
|
|
372
377
|
// `error: "EBUSY: resource busy or locked, unlink`
|
|
373
378
|
await t.test(
|
|
374
|
-
|
|
375
|
-
{ skip: process.platform ===
|
|
379
|
+
"view: Read data",
|
|
380
|
+
{ skip: process.platform === "win32" },
|
|
376
381
|
() => {
|
|
377
382
|
const index = new usearch.Index({
|
|
378
|
-
metric:
|
|
383
|
+
metric: "l2sq",
|
|
379
384
|
connectivity: 16,
|
|
380
385
|
dimensions: 3,
|
|
381
386
|
});
|
|
@@ -385,15 +390,15 @@ test('Serialization', async (t) => {
|
|
|
385
390
|
assert.equal(index.size(), 1);
|
|
386
391
|
assert.deepEqual(results.keys, new BigUint64Array([42n]));
|
|
387
392
|
assertAlmostEqual(results.distances[0], new Float32Array([0]));
|
|
388
|
-
}
|
|
393
|
+
},
|
|
389
394
|
);
|
|
390
395
|
|
|
391
396
|
await t.test(
|
|
392
|
-
|
|
393
|
-
{ skip: process.platform ===
|
|
397
|
+
"view: Invalid operations: add",
|
|
398
|
+
{ skip: process.platform === "win32" },
|
|
394
399
|
() => {
|
|
395
400
|
const index = new usearch.Index({
|
|
396
|
-
metric:
|
|
401
|
+
metric: "l2sq",
|
|
397
402
|
connectivity: 16,
|
|
398
403
|
dimensions: 3,
|
|
399
404
|
});
|
|
@@ -401,15 +406,15 @@ test('Serialization', async (t) => {
|
|
|
401
406
|
|
|
402
407
|
// Can't add to an immutable index
|
|
403
408
|
assert.throws(() => index.add(43n, new Float32Array([0.2, 0.6, 0.4])));
|
|
404
|
-
}
|
|
409
|
+
},
|
|
405
410
|
);
|
|
406
411
|
|
|
407
412
|
await t.test(
|
|
408
|
-
|
|
409
|
-
{ skip: process.platform ===
|
|
413
|
+
"view: Invalid operations: remove",
|
|
414
|
+
{ skip: process.platform === "win32" },
|
|
410
415
|
() => {
|
|
411
416
|
const index = new usearch.Index({
|
|
412
|
-
metric:
|
|
417
|
+
metric: "l2sq",
|
|
413
418
|
connectivity: 16,
|
|
414
419
|
dimensions: 3,
|
|
415
420
|
});
|
|
@@ -417,6 +422,24 @@ test('Serialization', async (t) => {
|
|
|
417
422
|
|
|
418
423
|
// Can't remove from an immutable index
|
|
419
424
|
assert.throws(() => index.remove(42n));
|
|
420
|
-
}
|
|
425
|
+
},
|
|
421
426
|
);
|
|
422
427
|
});
|
|
428
|
+
|
|
429
|
+
test("Mini-float quantization types", async (t) => {
|
|
430
|
+
for (const quantization of ["e5m2", "e4m3", "e3m2", "e2m3"]) {
|
|
431
|
+
await t.test(`add and search with ${quantization}`, () => {
|
|
432
|
+
const index = new usearch.Index({
|
|
433
|
+
metric: "cos",
|
|
434
|
+
dimensions: 64,
|
|
435
|
+
quantization: quantization,
|
|
436
|
+
});
|
|
437
|
+
const vector = new Float32Array(64);
|
|
438
|
+
for (let i = 0; i < 64; i++) vector[i] = i * 0.1;
|
|
439
|
+
index.add(1n, vector);
|
|
440
|
+
|
|
441
|
+
const results = index.search(vector, 1);
|
|
442
|
+
assert.equal(results.keys[0], 1n);
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
});
|
package/numkong/CITATION.cff
CHANGED
|
@@ -3,7 +3,7 @@ title: "NumKong: 2000 Mixed Precision Kernels For All"
|
|
|
3
3
|
message: "If you use this software, please cite it as below."
|
|
4
4
|
type: software
|
|
5
5
|
license: Apache-2.0
|
|
6
|
-
version: 7.
|
|
6
|
+
version: 7.6.0
|
|
7
7
|
repository-code: https://github.com/ashvardanian/NumKong
|
|
8
8
|
url: https://ashvardanian.com/posts/numkong/
|
|
9
9
|
abstract: >-
|
package/numkong/Cargo.toml
CHANGED
|
@@ -58,5 +58,17 @@ void bench_cross_amx() {
|
|
|
58
58
|
run_euclideans_packed<f16_k>("euclideans_packed_f16_graniteamx", nk_dots_packed_size_f16_graniteamx,
|
|
59
59
|
nk_dots_pack_f16_graniteamx, nk_euclideans_packed_f16_graniteamx);
|
|
60
60
|
run_euclideans_symmetric<f16_k>("euclideans_symmetric_f16_graniteamx", nk_euclideans_symmetric_f16_graniteamx);
|
|
61
|
+
|
|
62
|
+
run_dots_packed<e5m2_k>("dots_packed_e5m2_graniteamx", nk_dots_packed_size_e5m2_graniteamx,
|
|
63
|
+
nk_dots_pack_e5m2_graniteamx, nk_dots_packed_e5m2_graniteamx);
|
|
64
|
+
run_dots_symmetric<e5m2_k>("dots_symmetric_e5m2_graniteamx", nk_dots_symmetric_e5m2_graniteamx);
|
|
65
|
+
|
|
66
|
+
run_angulars_packed<e5m2_k>("angulars_packed_e5m2_graniteamx", nk_dots_packed_size_e5m2_graniteamx,
|
|
67
|
+
nk_dots_pack_e5m2_graniteamx, nk_angulars_packed_e5m2_graniteamx);
|
|
68
|
+
run_angulars_symmetric<e5m2_k>("angulars_symmetric_e5m2_graniteamx", nk_angulars_symmetric_e5m2_graniteamx);
|
|
69
|
+
|
|
70
|
+
run_euclideans_packed<e5m2_k>("euclideans_packed_e5m2_graniteamx", nk_dots_packed_size_e5m2_graniteamx,
|
|
71
|
+
nk_dots_pack_e5m2_graniteamx, nk_euclideans_packed_e5m2_graniteamx);
|
|
72
|
+
run_euclideans_symmetric<e5m2_k>("euclideans_symmetric_e5m2_graniteamx", nk_euclideans_symmetric_e5m2_graniteamx);
|
|
61
73
|
#endif
|
|
62
74
|
}
|
|
@@ -84,6 +84,12 @@ void bench_mesh() {
|
|
|
84
84
|
run_mesh<bf16_k>("umeyama_bf16_neonbfdot", nk_umeyama_bf16_neonbfdot);
|
|
85
85
|
#endif
|
|
86
86
|
|
|
87
|
+
#if NK_TARGET_NEONFHM
|
|
88
|
+
run_mesh<f16_k>("rmsd_f16_neonfhm", nk_rmsd_f16_neonfhm);
|
|
89
|
+
run_mesh<f16_k>("kabsch_f16_neonfhm", nk_kabsch_f16_neonfhm);
|
|
90
|
+
run_mesh<f16_k>("umeyama_f16_neonfhm", nk_umeyama_f16_neonfhm);
|
|
91
|
+
#endif
|
|
92
|
+
|
|
87
93
|
#if NK_TARGET_HASWELL
|
|
88
94
|
run_mesh<f32_k>("rmsd_f32_haswell", nk_rmsd_f32_haswell);
|
|
89
95
|
run_mesh<f32_k>("kabsch_f32_haswell", nk_kabsch_f32_haswell);
|
|
@@ -114,6 +120,12 @@ void bench_mesh() {
|
|
|
114
120
|
run_mesh<bf16_k>("umeyama_bf16_skylake", nk_umeyama_bf16_skylake);
|
|
115
121
|
#endif
|
|
116
122
|
|
|
123
|
+
#if NK_TARGET_GENOA
|
|
124
|
+
run_mesh<bf16_k>("rmsd_bf16_genoa", nk_rmsd_bf16_genoa);
|
|
125
|
+
run_mesh<bf16_k>("kabsch_bf16_genoa", nk_kabsch_bf16_genoa);
|
|
126
|
+
run_mesh<bf16_k>("umeyama_bf16_genoa", nk_umeyama_bf16_genoa);
|
|
127
|
+
#endif
|
|
128
|
+
|
|
117
129
|
#if NK_TARGET_RVV
|
|
118
130
|
run_mesh<f32_k>("rmsd_f32_rvv", nk_rmsd_f32_rvv);
|
|
119
131
|
run_mesh<f32_k>("kabsch_f32_rvv", nk_kabsch_f32_rvv);
|
|
@@ -232,9 +232,6 @@ void bench_spatial() {
|
|
|
232
232
|
run_dense<bf16_k, f32_k>("angular_bf16_genoa", nk_angular_bf16_genoa);
|
|
233
233
|
run_dense<bf16_k, f32_k>("sqeuclidean_bf16_genoa", nk_sqeuclidean_bf16_genoa);
|
|
234
234
|
run_dense<bf16_k, f32_k>("euclidean_bf16_genoa", nk_euclidean_bf16_genoa);
|
|
235
|
-
run_dense<e5m2_k, f32_k>("angular_e5m2_genoa", nk_angular_e5m2_genoa);
|
|
236
|
-
run_dense<e5m2_k, f32_k>("sqeuclidean_e5m2_genoa", nk_sqeuclidean_e5m2_genoa);
|
|
237
|
-
run_dense<e5m2_k, f32_k>("euclidean_e5m2_genoa", nk_euclidean_e5m2_genoa);
|
|
238
235
|
#endif
|
|
239
236
|
|
|
240
237
|
#if NK_TARGET_DIAMOND
|
package/numkong/binding.gyp
CHANGED
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"conditions": [
|
|
72
72
|
# Pin TU baseline to each arch's ABI floor; SIMD kernels use per-function pragmas.
|
|
73
|
+
# Keep per-arch table in sync with cmake/nk_compiler_flags.cmake, build.rs, setup.py.
|
|
73
74
|
[
|
|
74
75
|
"OS!='win' and target_arch=='arm64'",
|
|
75
76
|
{
|
|
@@ -94,6 +95,23 @@
|
|
|
94
95
|
]
|
|
95
96
|
}
|
|
96
97
|
],
|
|
98
|
+
[
|
|
99
|
+
"OS!='win' and target_arch=='ppc64'",
|
|
100
|
+
{
|
|
101
|
+
"cflags": [
|
|
102
|
+
"-mcpu=power8"
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
[
|
|
107
|
+
"OS!='win' and target_arch=='loong64'",
|
|
108
|
+
{
|
|
109
|
+
"cflags": [
|
|
110
|
+
"-march=loongarch64",
|
|
111
|
+
"-mlasx"
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
],
|
|
97
115
|
# Forbid auto-vectorization so serial fallbacks don't get silently
|
|
98
116
|
# promoted to NEON/SSE2/VSX. SIMD kernels use explicit intrinsics
|
|
99
117
|
# and per-function `target` pragmas; unaffected. MSVC has no
|
package/numkong/build.rs
CHANGED
|
@@ -376,6 +376,7 @@ fn build_numkong() -> Result<HashMap<String, bool>, String> {
|
|
|
376
376
|
|
|
377
377
|
// Pin TU baseline to each arch's ABI floor; SIMD kernels carry per-function pragmas.
|
|
378
378
|
// `NK_MARCH_NATIVE=1` opts into a host-tuned, non-portable build (ignored on MSVC).
|
|
379
|
+
// Keep per-arch table in sync with cmake/nk_compiler_flags.cmake, setup.py, binding.gyp.
|
|
379
380
|
let march_native = env::var("NK_MARCH_NATIVE").is_ok_and(|v| v == "1" || v == "true");
|
|
380
381
|
// Portable baseline: pin TU ISA floor + forbid auto-vectorization so serial
|
|
381
382
|
// fallbacks don't get silently promoted to NEON/SSE2/VSX. SIMD kernels use
|
|
@@ -113,6 +113,29 @@ void nk_dispatch_e5m2_find_(nk_capability_t v, nk_kernel_kind_t k, nk_kernel_pun
|
|
|
113
113
|
default: break;
|
|
114
114
|
}
|
|
115
115
|
#endif
|
|
116
|
+
#if NK_TARGET_GRANITEAMX
|
|
117
|
+
if (v & nk_cap_graniteamx_k) switch (k) {
|
|
118
|
+
case nk_kernel_dots_packed_size_k:
|
|
119
|
+
*m = (m_t)&nk_dots_packed_size_e5m2_graniteamx, *c = nk_cap_graniteamx_k;
|
|
120
|
+
return;
|
|
121
|
+
case nk_kernel_dots_pack_k: *m = (m_t)&nk_dots_pack_e5m2_graniteamx, *c = nk_cap_graniteamx_k; return;
|
|
122
|
+
case nk_kernel_dots_packed_k: *m = (m_t)&nk_dots_packed_e5m2_graniteamx, *c = nk_cap_graniteamx_k; return;
|
|
123
|
+
case nk_kernel_angulars_packed_k:
|
|
124
|
+
*m = (m_t)&nk_angulars_packed_e5m2_graniteamx, *c = nk_cap_graniteamx_k;
|
|
125
|
+
return;
|
|
126
|
+
case nk_kernel_euclideans_packed_k:
|
|
127
|
+
*m = (m_t)&nk_euclideans_packed_e5m2_graniteamx, *c = nk_cap_graniteamx_k;
|
|
128
|
+
return;
|
|
129
|
+
case nk_kernel_dots_symmetric_k: *m = (m_t)&nk_dots_symmetric_e5m2_graniteamx, *c = nk_cap_graniteamx_k; return;
|
|
130
|
+
case nk_kernel_angulars_symmetric_k:
|
|
131
|
+
*m = (m_t)&nk_angulars_symmetric_e5m2_graniteamx, *c = nk_cap_graniteamx_k;
|
|
132
|
+
return;
|
|
133
|
+
case nk_kernel_euclideans_symmetric_k:
|
|
134
|
+
*m = (m_t)&nk_euclideans_symmetric_e5m2_graniteamx, *c = nk_cap_graniteamx_k;
|
|
135
|
+
return;
|
|
136
|
+
default: break;
|
|
137
|
+
}
|
|
138
|
+
#endif
|
|
116
139
|
#if NK_TARGET_SAPPHIREAMX
|
|
117
140
|
if (v & nk_cap_sapphireamx_k) switch (k) {
|
|
118
141
|
case nk_kernel_dots_packed_size_k:
|
|
@@ -162,9 +185,6 @@ void nk_dispatch_e5m2_find_(nk_capability_t v, nk_kernel_kind_t k, nk_kernel_pun
|
|
|
162
185
|
#if NK_TARGET_GENOA
|
|
163
186
|
if (v & nk_cap_genoa_k) switch (k) {
|
|
164
187
|
case nk_kernel_dot_k: *m = (m_t)&nk_dot_e5m2_genoa, *c = nk_cap_genoa_k; return;
|
|
165
|
-
case nk_kernel_euclidean_k: *m = (m_t)&nk_euclidean_e5m2_genoa, *c = nk_cap_genoa_k; return;
|
|
166
|
-
case nk_kernel_sqeuclidean_k: *m = (m_t)&nk_sqeuclidean_e5m2_genoa, *c = nk_cap_genoa_k; return;
|
|
167
|
-
case nk_kernel_angular_k: *m = (m_t)&nk_angular_e5m2_genoa, *c = nk_cap_genoa_k; return;
|
|
168
188
|
case nk_kernel_dots_packed_size_k: *m = (m_t)&nk_dots_packed_size_e5m2_genoa, *c = nk_cap_genoa_k; return;
|
|
169
189
|
case nk_kernel_dots_pack_k: *m = (m_t)&nk_dots_pack_e5m2_genoa, *c = nk_cap_genoa_k; return;
|
|
170
190
|
case nk_kernel_dots_packed_k: *m = (m_t)&nk_dots_packed_e5m2_genoa, *c = nk_cap_genoa_k; return;
|