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.
Files changed (80) hide show
  1. package/README.md +43 -47
  2. package/c/README.md +1 -1
  3. package/c/lib.cpp +28 -13
  4. package/c/test.c +41 -0
  5. package/cpp/test.cpp +99 -0
  6. package/include/usearch/index.hpp +338 -57
  7. package/include/usearch/index_dense.hpp +155 -50
  8. package/include/usearch/index_plugins.hpp +94 -35
  9. package/javascript/README.md +1 -1
  10. package/javascript/lib.cpp +4 -2
  11. package/javascript/usearch.test.js +117 -94
  12. package/numkong/CITATION.cff +1 -1
  13. package/numkong/Cargo.toml +1 -1
  14. package/numkong/bench/bench_cross_amx.cpp +12 -0
  15. package/numkong/bench/bench_mesh.cpp +12 -0
  16. package/numkong/bench/bench_spatial.cpp +0 -3
  17. package/numkong/binding.gyp +18 -0
  18. package/numkong/build.rs +1 -0
  19. package/numkong/c/dispatch_e5m2.c +23 -3
  20. package/numkong/include/numkong/capabilities.h +1 -1
  21. package/numkong/include/numkong/cast/README.md +3 -0
  22. package/numkong/include/numkong/cast/haswell.h +28 -64
  23. package/numkong/include/numkong/cast/serial.h +17 -0
  24. package/numkong/include/numkong/cast/skylake.h +67 -52
  25. package/numkong/include/numkong/cast.h +1 -0
  26. package/numkong/include/numkong/dot/README.md +1 -0
  27. package/numkong/include/numkong/dot/haswell.h +92 -13
  28. package/numkong/include/numkong/dot/serial.h +15 -0
  29. package/numkong/include/numkong/dot/skylake.h +61 -14
  30. package/numkong/include/numkong/dots/README.md +2 -0
  31. package/numkong/include/numkong/dots/graniteamx.h +434 -0
  32. package/numkong/include/numkong/dots/haswell.h +28 -28
  33. package/numkong/include/numkong/dots/sapphireamx.h +1 -1
  34. package/numkong/include/numkong/dots/serial.h +23 -8
  35. package/numkong/include/numkong/dots/skylake.h +28 -23
  36. package/numkong/include/numkong/dots.h +12 -0
  37. package/numkong/include/numkong/each/serial.h +18 -1
  38. package/numkong/include/numkong/geospatial/serial.h +14 -3
  39. package/numkong/include/numkong/maxsim/serial.h +15 -0
  40. package/numkong/include/numkong/mesh/README.md +50 -44
  41. package/numkong/include/numkong/mesh/genoa.h +462 -0
  42. package/numkong/include/numkong/mesh/haswell.h +806 -933
  43. package/numkong/include/numkong/mesh/neon.h +871 -943
  44. package/numkong/include/numkong/mesh/neonbfdot.h +382 -522
  45. package/numkong/include/numkong/mesh/neonfhm.h +676 -0
  46. package/numkong/include/numkong/mesh/rvv.h +404 -319
  47. package/numkong/include/numkong/mesh/serial.h +204 -162
  48. package/numkong/include/numkong/mesh/skylake.h +1029 -1585
  49. package/numkong/include/numkong/mesh/v128relaxed.h +403 -377
  50. package/numkong/include/numkong/mesh.h +38 -0
  51. package/numkong/include/numkong/reduce/serial.h +15 -1
  52. package/numkong/include/numkong/sparse/serial.h +17 -2
  53. package/numkong/include/numkong/spatial/genoa.h +0 -68
  54. package/numkong/include/numkong/spatial/haswell.h +98 -56
  55. package/numkong/include/numkong/spatial/serial.h +15 -0
  56. package/numkong/include/numkong/spatial/skylake.h +114 -54
  57. package/numkong/include/numkong/spatial.h +0 -12
  58. package/numkong/include/numkong/spatials/graniteamx.h +128 -0
  59. package/numkong/include/numkong/spatials/serial.h +18 -1
  60. package/numkong/include/numkong/spatials/skylake.h +2 -2
  61. package/numkong/include/numkong/spatials.h +17 -0
  62. package/numkong/include/numkong/tensor.hpp +107 -23
  63. package/numkong/javascript/@numkong-darwin-arm64/package.json +1 -1
  64. package/numkong/javascript/@numkong-darwin-x64/package.json +1 -1
  65. package/numkong/javascript/@numkong-linux-arm64/package.json +1 -1
  66. package/numkong/javascript/@numkong-linux-x64/package.json +1 -1
  67. package/numkong/javascript/@numkong-win32-arm64/package.json +1 -1
  68. package/numkong/javascript/@numkong-win32-x64/package.json +1 -1
  69. package/numkong/javascript/numkong.c +3 -2
  70. package/numkong/package.json +7 -7
  71. package/numkong/test/test.cu +841 -0
  72. package/numkong/test/test_cross_amx.cpp +13 -0
  73. package/numkong/test/test_dlpack.py +492 -0
  74. package/numkong/test/test_mesh.cpp +12 -0
  75. package/numkong/test/test_spatial.cpp +0 -3
  76. package/numkong/test/test_tensor.cpp +87 -27
  77. package/package.json +1 -1
  78. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  79. package/prebuilds/linux-arm64/usearch.node +0 -0
  80. package/prebuilds/linux-x64/usearch.node +0 -0
@@ -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(ctx.Env(), result.error.release()).ThrowAsJavaScriptException();
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('node:test');
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 === 'function') {
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('▶', name);
15
+ console.log("▶", name);
16
16
  try {
17
17
  await fn(t); // run the user’s test
18
- console.log('✓', name);
18
+ console.log("✓", name);
19
19
  } catch (err) {
20
- console.log('✖', name);
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('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');
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('Single-entry operations', async (t) => {
51
- await t.test('index info', () => {
52
- const index = new usearch.Index(2, 'l2sq');
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, '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');
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('add', () => {
60
- const index = new usearch.Index(2, 'l2sq');
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, 'size after adding elements should be 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
- 'entry must be present after insertion'
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
- 'keys should be 15 and 16'
77
+ "keys should be 15 and 16",
78
78
  );
79
79
  assert.deepEqual(
80
80
  results.distances,
81
81
  new Float32Array([45, 130]),
82
- 'distances should be 45 and 130'
82
+ "distances should be 45 and 130",
83
83
  );
84
84
  });
85
85
 
86
- await t.test('remove', () => {
87
- const index = new usearch.Index(2, 'l2sq');
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, 'size after removing elements should be 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
- 'entry must be absent after insertion'
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
- 'keys should not include 15'
108
+ "keys should not include 15",
109
109
  );
110
110
  });
111
111
  });
112
112
 
113
- test('Batch operations', async (t) => {
114
- await t.test('add', () => {
115
- const indexBatch = new usearch.Index(2, 'l2sq');
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, 'size after adding batch should be 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
- 'keys should be 15 and 16'
128
+ "keys should be 15 and 16",
129
129
  );
130
130
  assert.deepEqual(
131
131
  results.distances,
132
132
  new Float32Array([45, 130]),
133
- 'distances should be 45 and 130'
133
+ "distances should be 45 and 130",
134
134
  );
135
135
  });
136
136
 
137
- await t.test('remove', () => {
138
- const indexBatch = new usearch.Index(2, 'l2sq');
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, 'size after removing batch should be 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
- 'keys should not include 15 and 25'
157
+ "keys should not include 15 and 25",
158
158
  );
159
159
  });
160
160
 
161
- await t.test('search', () => {
162
- const indexBatch = new usearch.Index(3, 'l2sq');
161
+ await t.test("search", () => {
162
+ const indexBatch = new usearch.Index(3, "l2sq");
163
163
 
164
164
  const keys = [15n, 16n];
165
- const vectors = [new Float32Array([10, 20, 30]), new Float32Array([10, 25, 35])];
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, 'size after adding batch should be 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
- const results = indexBatch.search([new Float32Array([13, 14, 15]), new Float32Array([13, 14, 15])], 2);
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([ 2n, 2n ]) ,
177
- 'counts should be 2 for both keys'
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
- 'keys should be 15 and 16'
189
+ "keys should be 15 and 16",
184
190
  );
185
191
  assert.deepEqual(
186
192
  results.distances,
187
- new Float32Array( [ 270, 530, 270, 530 ]),
188
- 'distances should be 270 and 530'
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('Expected results', () => {
199
+ test("Expected results", () => {
195
200
  const index = new usearch.Index({
196
- metric: 'l2sq',
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('Multithread search returns same results', () => {
213
+ test("Multithread search returns same results", () => {
209
214
  const index = new usearch.Index({
210
- metric: 'l2sq',
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('Exact search', async (t) => {
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 = 'l2sq';
241
+ const metric = "l2sq";
237
242
 
238
- await t.test('Single core search', () => {
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('Multithreaded search', () => {
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('Expected count()', async (t) => {
274
+ test("Expected count()", async (t) => {
270
275
  const index = new usearch.Index({
271
- metric: 'l2sq',
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('Argument is a number', () => {
285
+ await t.test("Argument is a number", () => {
281
286
  assert.equal(1, index.count(43n));
282
287
  });
283
- await t.test('Argument is a number (does not exist)', () => {
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('Argument is an array', () => {
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('Operations with invalid values', () => {
292
- const indexBatch = new usearch.Index(2, 'l2sq');
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('Invalid operations', async (t) => {
305
- await t.test('Add the same keys', () => {
309
+ test("Invalid operations", async (t) => {
310
+ await t.test("Add the same keys", () => {
306
311
  const index = new usearch.Index({
307
- metric: 'l2sq',
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('Batch add containing the same key', () => {
320
+ await t.test("Batch add containing the same key", () => {
316
321
  const index = new usearch.Index({
317
- metric: 'l2sq',
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('Serialization', async (t) => {
336
- const indexPath = path.join(os.tmpdir(), 'usearch.test.index');
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: 'l2sq',
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('load', () => {
357
+ await t.test("load", () => {
353
358
  const index = new usearch.Index({
354
- metric: 'l2sq',
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
- 'view: Read data',
375
- { skip: process.platform === 'win32' },
379
+ "view: Read data",
380
+ { skip: process.platform === "win32" },
376
381
  () => {
377
382
  const index = new usearch.Index({
378
- metric: 'l2sq',
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
- 'view: Invalid operations: add',
393
- { skip: process.platform === 'win32' },
397
+ "view: Invalid operations: add",
398
+ { skip: process.platform === "win32" },
394
399
  () => {
395
400
  const index = new usearch.Index({
396
- metric: 'l2sq',
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
- 'view: Invalid operations: remove',
409
- { skip: process.platform === 'win32' },
413
+ "view: Invalid operations: remove",
414
+ { skip: process.platform === "win32" },
410
415
  () => {
411
416
  const index = new usearch.Index({
412
- metric: 'l2sq',
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
+ });
@@ -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.5.0
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: >-
@@ -19,7 +19,7 @@ name = "numkong"
19
19
  readme = "rust/README.md"
20
20
  repository = "https://github.com/ashvardanian/NumKong"
21
21
  rust-version = "1.64" # Introduced Core C FFI in stable Rust
22
- version = "7.5.0"
22
+ version = "7.6.0"
23
23
 
24
24
  [lib]
25
25
  name = "numkong"
@@ -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
@@ -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;