usearch 2.26.0 → 2.26.2
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 +2 -2
- package/cpp/test.cpp +66 -3
- package/include/usearch/index.hpp +9 -1
- package/include/usearch/index_dense.hpp +16 -5
- package/numkong/CITATION.cff +13 -9
- package/numkong/CONTRIBUTING.md +29 -24
- package/numkong/Cargo.toml +1 -1
- package/numkong/README.md +21 -7
- package/numkong/binding.gyp +47 -31
- package/numkong/build.rs +15 -5
- package/numkong/c/dispatch.h +0 -1
- package/numkong/c/parallel.c +139 -0
- package/numkong/c/parallel.h +47 -0
- package/numkong/cmake/toolchain-wasi.cmake +39 -5
- package/numkong/include/README.md +24 -17
- package/numkong/include/numkong/attention/sapphireamx.h +6 -6
- package/numkong/include/numkong/attention/sme.h +2 -2
- package/numkong/include/numkong/capabilities.h +2 -2
- package/numkong/include/numkong/cast/README.md +9 -7
- package/numkong/include/numkong/cast/loongsonasx.h +48 -40
- package/numkong/include/numkong/cast/serial.h +89 -11
- package/numkong/include/numkong/cast.h +3 -3
- package/numkong/include/numkong/curved/README.md +10 -10
- package/numkong/include/numkong/curved/neon.h +1 -0
- package/numkong/include/numkong/curved/neonbfdot.h +1 -0
- package/numkong/include/numkong/curved/smef64.h +1 -0
- package/numkong/include/numkong/dot/README.md +18 -18
- package/numkong/include/numkong/dot/alder.h +8 -8
- package/numkong/include/numkong/dot/haswell.h +8 -8
- package/numkong/include/numkong/dot/icelake.h +12 -12
- package/numkong/include/numkong/dot/loongsonasx.h +6 -6
- package/numkong/include/numkong/dot/neon.h +2 -1
- package/numkong/include/numkong/dot/neonbfdot.h +1 -0
- package/numkong/include/numkong/dot/neonfhm.h +1 -0
- package/numkong/include/numkong/dot/neonsdot.h +1 -0
- package/numkong/include/numkong/dot/powervsx.h +3 -3
- package/numkong/include/numkong/dot/serial.h +19 -6
- package/numkong/include/numkong/dot/skylake.h +4 -4
- package/numkong/include/numkong/dot/svehalf.h +1 -1
- package/numkong/include/numkong/dot/v128relaxed.h +24 -23
- package/numkong/include/numkong/dots/README.md +26 -26
- package/numkong/include/numkong/dots/genoa.h +3 -1
- package/numkong/include/numkong/dots/icelake.h +1 -0
- package/numkong/include/numkong/dots/powervsx.h +1 -1
- package/numkong/include/numkong/dots/serial.h +32 -33
- package/numkong/include/numkong/dots/sierra.h +4 -3
- package/numkong/include/numkong/each/README.md +5 -3
- package/numkong/include/numkong/each/sapphire.h +2 -0
- package/numkong/include/numkong/each/skylake.h +1 -0
- package/numkong/include/numkong/geospatial/README.md +6 -6
- package/numkong/include/numkong/geospatial/v128relaxed.h +1 -0
- package/numkong/include/numkong/maxsim/README.md +5 -5
- package/numkong/include/numkong/maxsim/genoa.h +4 -0
- package/numkong/include/numkong/maxsim/icelake.h +1 -0
- package/numkong/include/numkong/maxsim/serial.h +22 -9
- package/numkong/include/numkong/maxsim/sme.h +3 -2
- package/numkong/include/numkong/mesh/README.md +7 -4
- package/numkong/include/numkong/mesh/genoa.h +2 -2
- package/numkong/include/numkong/mesh/haswell.h +8 -8
- package/numkong/include/numkong/mesh/neon.h +6 -6
- package/numkong/include/numkong/mesh/neonbfdot.h +3 -2
- package/numkong/include/numkong/mesh/neonfhm.h +3 -2
- package/numkong/include/numkong/mesh/rvv.h +4 -4
- package/numkong/include/numkong/mesh/serial.h +17 -17
- package/numkong/include/numkong/mesh/skylake.h +4 -4
- package/numkong/include/numkong/mesh/v128relaxed.h +4 -4
- package/numkong/include/numkong/mesh.hpp +2 -2
- package/numkong/include/numkong/numkong.h +1 -1
- package/numkong/include/numkong/numkong.hpp +1 -1
- package/numkong/include/numkong/probability/README.md +17 -15
- package/numkong/include/numkong/probability/skylake.h +1 -1
- package/numkong/include/numkong/reduce/README.md +27 -21
- package/numkong/include/numkong/reduce/genoa.h +2 -1
- package/numkong/include/numkong/reduce/icelake.h +1 -0
- package/numkong/include/numkong/reduce/neon.h +1 -1
- package/numkong/include/numkong/reduce/serial.h +9 -9
- package/numkong/include/numkong/reduce/skylake.h +4 -4
- package/numkong/include/numkong/reduce.hpp +38 -0
- package/numkong/include/numkong/scalar/README.md +29 -28
- package/numkong/include/numkong/set/README.md +17 -14
- package/numkong/include/numkong/set/haswell.h +15 -16
- package/numkong/include/numkong/set/icelake.h +15 -16
- package/numkong/include/numkong/set/loongsonasx.h +12 -10
- package/numkong/include/numkong/set/neon.h +15 -19
- package/numkong/include/numkong/set/powervsx.h +15 -21
- package/numkong/include/numkong/set/serial.h +19 -20
- package/numkong/include/numkong/set/v128relaxed.h +10 -8
- package/numkong/include/numkong/sets/icelake.h +1 -0
- package/numkong/include/numkong/sparse/README.md +4 -4
- package/numkong/include/numkong/sparse/icelake.h +1 -0
- package/numkong/include/numkong/sparse/neon.h +1 -0
- package/numkong/include/numkong/sparse/serial.h +10 -3
- package/numkong/include/numkong/sparse/turin.h +1 -0
- package/numkong/include/numkong/spatial/README.md +25 -24
- package/numkong/include/numkong/spatial/alder.h +2 -1
- package/numkong/include/numkong/spatial/haswell.h +48 -40
- package/numkong/include/numkong/spatial/icelake.h +2 -1
- package/numkong/include/numkong/spatial/neon.h +54 -46
- package/numkong/include/numkong/spatial/neonbfdot.h +1 -1
- package/numkong/include/numkong/spatial/powervsx.h +54 -46
- package/numkong/include/numkong/spatial/serial.h +56 -46
- package/numkong/include/numkong/spatial/sierra.h +4 -3
- package/numkong/include/numkong/spatial/skylake.h +27 -22
- package/numkong/include/numkong/spatial/sve.h +6 -6
- package/numkong/include/numkong/spatial/svebfdot.h +1 -1
- package/numkong/include/numkong/spatial/svehalf.h +1 -1
- package/numkong/include/numkong/spatial/v128relaxed.h +36 -30
- package/numkong/include/numkong/spatials/README.md +15 -15
- package/numkong/include/numkong/spatials/genoa.h +3 -0
- package/numkong/include/numkong/spatials/graniteamx.h +2 -0
- package/numkong/include/numkong/spatials/icelake.h +1 -0
- package/numkong/include/numkong/spatials/sierra.h +1 -0
- package/numkong/include/numkong/tensor.hpp +236 -123
- package/numkong/include/numkong/trigonometry/README.md +4 -4
- package/numkong/include/numkong/trigonometry/serial.h +6 -6
- package/numkong/include/numkong/trigonometry/v128relaxed.h +1 -0
- package/numkong/include/numkong/types.hpp +46 -16
- package/numkong/include/numkong/vector.hpp +116 -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/README.md +18 -16
- package/numkong/javascript/numkong.c +70 -40
- package/numkong/package.json +8 -8
- package/numkong/rust/README.md +19 -9
- package/numkong/rust/cast.rs +52 -1
- package/numkong/rust/each.rs +480 -7
- package/numkong/rust/matrix.rs +877 -216
- package/numkong/rust/numkong.rs +12 -8
- package/numkong/rust/reduce.rs +433 -5
- package/numkong/rust/tensor.rs +450 -842
- package/numkong/rust/vector.rs +520 -101
- package/numkong/swift/README.md +5 -3
- package/numkong/test/README.md +4 -4
- package/numkong/test/test-wasi.mjs +17 -0
- package/numkong/test/test_each.cpp +30 -1
- package/numkong/test/test_each.py +74 -1
- package/numkong/test/test_reduce.cpp +51 -0
- package/numkong/test/test_tensor.cpp +2 -63
- package/numkong/test/test_tensor.py +93 -0
- 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/prebuilds/win32-x64/usearch.node +0 -0
package/README.md
CHANGED
|
@@ -545,11 +545,11 @@ index = Index(ndim=ndim, metric=CompiledMetric(
|
|
|
545
545
|
|
|
546
546
|
```bibtex
|
|
547
547
|
@software{Vardanian_USearch,
|
|
548
|
-
doi = {10.5281/zenodo.
|
|
548
|
+
doi = {10.5281/zenodo.7949415},
|
|
549
549
|
author = {Vardanian, Ash},
|
|
550
550
|
title = {{USearch by Unum Cloud}},
|
|
551
551
|
url = {https://github.com/unum-cloud/USearch},
|
|
552
|
-
version = {2.26.
|
|
552
|
+
version = {2.26.2},
|
|
553
553
|
year = {2026},
|
|
554
554
|
}
|
|
555
555
|
```
|
package/cpp/test.cpp
CHANGED
|
@@ -1112,9 +1112,6 @@ template <typename key_at, typename slot_at> void test_strings() {
|
|
|
1112
1112
|
}
|
|
1113
1113
|
}
|
|
1114
1114
|
|
|
1115
|
-
/**
|
|
1116
|
-
* @brief Tests replacing and updating entries in index_dense_gt to ensure consistency after modifications.
|
|
1117
|
-
*/
|
|
1118
1115
|
/**
|
|
1119
1116
|
* @brief Churns a tightly-reserved index, so `slot_lookup_` accumulates tombstones.
|
|
1120
1117
|
*
|
|
@@ -1181,6 +1178,10 @@ template <typename key_at, typename slot_at> void test_slot_lookup_churn() {
|
|
|
1181
1178
|
expect(copy.contains(key));
|
|
1182
1179
|
}
|
|
1183
1180
|
|
|
1181
|
+
/**
|
|
1182
|
+
* @brief Tests replacing and updating entries in `index_dense_gt`, to ensure consistency
|
|
1183
|
+
* after modifications.
|
|
1184
|
+
*/
|
|
1184
1185
|
template <typename key_at, typename slot_at> void test_replacing_update() {
|
|
1185
1186
|
|
|
1186
1187
|
using vector_key_t = key_at;
|
|
@@ -1427,6 +1428,67 @@ void test_load_after_metric_make() {
|
|
|
1427
1428
|
std::remove(path);
|
|
1428
1429
|
}
|
|
1429
1430
|
|
|
1431
|
+
/**
|
|
1432
|
+
* @brief Feeds `view` a truncated index file.
|
|
1433
|
+
*
|
|
1434
|
+
* The matrix dimensions and the node count come from the file itself, so a short file
|
|
1435
|
+
* describes a matrix and a level array that reach past the mapping. Both must be rejected
|
|
1436
|
+
* rather than walked, and a caller-supplied offset past the end must fail the same way.
|
|
1437
|
+
*/
|
|
1438
|
+
void test_view_of_truncated_file() {
|
|
1439
|
+
std::printf("Testing view of a truncated index file\n");
|
|
1440
|
+
|
|
1441
|
+
using index_t = index_dense_gt<std::int64_t, std::uint32_t>;
|
|
1442
|
+
std::size_t const dimensions = 64;
|
|
1443
|
+
std::size_t const collection = 2048;
|
|
1444
|
+
char const* path = "tmp_truncated.usearch";
|
|
1445
|
+
|
|
1446
|
+
metric_punned_t metric(dimensions, metric_kind_t::l2sq_k, scalar_kind<f32_t>());
|
|
1447
|
+
index_t::state_result_t built = index_t::make(metric);
|
|
1448
|
+
expect(built);
|
|
1449
|
+
expect(built.index.try_reserve(collection));
|
|
1450
|
+
std::vector<float> vector(dimensions);
|
|
1451
|
+
for (std::size_t i = 0; i != collection; ++i) {
|
|
1452
|
+
for (std::size_t d = 0; d != dimensions; ++d)
|
|
1453
|
+
vector[d] = static_cast<float>((i * 31 + d) % 997) / 997.f;
|
|
1454
|
+
expect(built.index.add(static_cast<std::int64_t>(i), vector.data()));
|
|
1455
|
+
}
|
|
1456
|
+
expect(built.index.save(path));
|
|
1457
|
+
|
|
1458
|
+
std::vector<char> prefix;
|
|
1459
|
+
{
|
|
1460
|
+
std::FILE* file = std::fopen(path, "rb");
|
|
1461
|
+
expect(file != nullptr);
|
|
1462
|
+
std::fseek(file, 0, SEEK_END);
|
|
1463
|
+
long const bytes = std::ftell(file);
|
|
1464
|
+
std::fseek(file, 0, SEEK_SET);
|
|
1465
|
+
prefix.resize(static_cast<std::size_t>(bytes) / 8);
|
|
1466
|
+
expect(std::fread(prefix.data(), 1, prefix.size(), file) == prefix.size());
|
|
1467
|
+
std::fclose(file);
|
|
1468
|
+
}
|
|
1469
|
+
{
|
|
1470
|
+
std::FILE* file = std::fopen(path, "wb");
|
|
1471
|
+
expect(file != nullptr);
|
|
1472
|
+
std::fwrite(prefix.data(), 1, prefix.size(), file);
|
|
1473
|
+
std::fclose(file);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
index_t::state_result_t viewed = index_t::make(metric);
|
|
1477
|
+
expect(viewed);
|
|
1478
|
+
serialization_result_t truncated = viewed.index.view(path);
|
|
1479
|
+
expect(!truncated);
|
|
1480
|
+
truncated.error.release();
|
|
1481
|
+
|
|
1482
|
+
// A caller-supplied offset past the end must fail the same way.
|
|
1483
|
+
index_t::state_result_t offset_viewed = index_t::make(metric);
|
|
1484
|
+
expect(offset_viewed);
|
|
1485
|
+
serialization_result_t beyond = offset_viewed.index.view(memory_mapped_file_t(path), prefix.size() * 4);
|
|
1486
|
+
expect(!beyond);
|
|
1487
|
+
beyond.error.release();
|
|
1488
|
+
|
|
1489
|
+
std::remove(path);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1430
1492
|
int main(int, char**) {
|
|
1431
1493
|
install_crash_handlers();
|
|
1432
1494
|
|
|
@@ -1523,5 +1585,6 @@ int main(int, char**) {
|
|
|
1523
1585
|
test_filtered_search();
|
|
1524
1586
|
test_isolate();
|
|
1525
1587
|
test_load_after_metric_make();
|
|
1588
|
+
test_view_of_truncated_file();
|
|
1526
1589
|
return 0;
|
|
1527
1590
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
#define USEARCH_VERSION_MAJOR 2
|
|
11
11
|
#define USEARCH_VERSION_MINOR 26
|
|
12
|
-
#define USEARCH_VERSION_PATCH
|
|
12
|
+
#define USEARCH_VERSION_PATCH 2
|
|
13
13
|
|
|
14
14
|
// Inferring C++ version
|
|
15
15
|
// https://stackoverflow.com/a/61552074
|
|
@@ -2084,6 +2084,10 @@ class memory_mapped_file_t {
|
|
|
2084
2084
|
|
|
2085
2085
|
#if defined(USEARCH_DEFINED_LINUX)
|
|
2086
2086
|
int descriptor = open(path_, O_RDONLY | O_NOATIME);
|
|
2087
|
+
if (descriptor < 0 && errno == EPERM)
|
|
2088
|
+
// `O_NOATIME` requires owning the file or holding `CAP_FOWNER` (see `open(2)`).
|
|
2089
|
+
// Retry without it instead of failing outright.
|
|
2090
|
+
descriptor = open(path_, O_RDONLY);
|
|
2087
2091
|
#else
|
|
2088
2092
|
int descriptor = open(path_, O_RDONLY);
|
|
2089
2093
|
#endif
|
|
@@ -3970,6 +3974,10 @@ class index_gt {
|
|
|
3970
3974
|
: checked_size_overflow();
|
|
3971
3975
|
if (!first_offset)
|
|
3972
3976
|
return result.failed("Index is too large");
|
|
3977
|
+
if (file.size() < first_offset.value) {
|
|
3978
|
+
reset();
|
|
3979
|
+
return result.failed("File is corrupted and can't fit the node levels");
|
|
3980
|
+
}
|
|
3973
3981
|
offsets[0u] = first_offset.value;
|
|
3974
3982
|
for (std::size_t i = 1; i < header_size.value; ++i) {
|
|
3975
3983
|
checked_size_result_t next_offset = checked_add(offsets[i - 1], node_bytes_(levels[i - 1]));
|
|
@@ -1050,17 +1050,18 @@ class index_dense_gt {
|
|
|
1050
1050
|
*/
|
|
1051
1051
|
bool try_reserve(index_limits_t limits) {
|
|
1052
1052
|
|
|
1053
|
+
// Reserving is monotonic: reducing thread limits must not shrink member-addressed storage.
|
|
1054
|
+
limits.members = (std::max)(limits.members, typed_->capacity());
|
|
1055
|
+
limits.members = (std::max)(limits.members, vectors_lookup_.size());
|
|
1056
|
+
|
|
1053
1057
|
// The slot lookup system will generally prefer power-of-two sizes.
|
|
1054
1058
|
if (config_.enable_key_lookups) {
|
|
1055
1059
|
unique_lock_t lock(slot_lookup_mutex_);
|
|
1056
1060
|
if (!slot_lookup_.try_reserve(limits.members))
|
|
1057
1061
|
return false;
|
|
1058
|
-
limits.members = slot_lookup_.capacity();
|
|
1059
1062
|
}
|
|
1060
1063
|
|
|
1061
|
-
//
|
|
1062
|
-
// target for the `vectors_lookup_` to synchronize allocations and
|
|
1063
|
-
// expensive index re-organizations.
|
|
1064
|
+
// Hash-table load-factor slack does not need corresponding vector or graph slots.
|
|
1064
1065
|
if (limits.members != vectors_lookup_.size()) {
|
|
1065
1066
|
vectors_lookup_t new_vectors_lookup(limits.members);
|
|
1066
1067
|
if (!new_vectors_lookup)
|
|
@@ -1358,6 +1359,11 @@ class index_dense_gt {
|
|
|
1358
1359
|
if (!result)
|
|
1359
1360
|
return result;
|
|
1360
1361
|
|
|
1362
|
+
// `offset` is caller-supplied, so every `file.size() - offset` below would
|
|
1363
|
+
// otherwise underflow into a huge span instead of failing.
|
|
1364
|
+
if (offset > file.size())
|
|
1365
|
+
return result.failed("File is corrupted and lacks matrix dimensions");
|
|
1366
|
+
|
|
1361
1367
|
// Infer the new index size
|
|
1362
1368
|
std::uint64_t matrix_rows = 0;
|
|
1363
1369
|
std::uint64_t matrix_cols = 0;
|
|
@@ -1383,7 +1389,12 @@ class index_dense_gt {
|
|
|
1383
1389
|
matrix_cols = dimensions[1];
|
|
1384
1390
|
offset += sizeof(dimensions);
|
|
1385
1391
|
}
|
|
1386
|
-
|
|
1392
|
+
// bound the vectors matrix span against the file (overflow-safe; offset <= file.size() holds here)
|
|
1393
|
+
checked_size_result_t vectors_bytes =
|
|
1394
|
+
checked_mul(static_cast<std::size_t>(matrix_rows), static_cast<std::size_t>(matrix_cols));
|
|
1395
|
+
if (!vectors_bytes || file.size() - offset < vectors_bytes.value)
|
|
1396
|
+
return result.failed("File is corrupted: vectors matrix exceeds file size");
|
|
1397
|
+
vectors_buffer = {file.data() + offset, vectors_bytes.value};
|
|
1387
1398
|
offset += vectors_buffer.size();
|
|
1388
1399
|
}
|
|
1389
1400
|
|
package/numkong/CITATION.cff
CHANGED
|
@@ -3,18 +3,22 @@ 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.8.1
|
|
7
7
|
repository-code: https://github.com/ashvardanian/NumKong
|
|
8
8
|
url: https://ashvardanian.com/posts/numkong/
|
|
9
|
+
doi: 10.5281/zenodo.21480519
|
|
10
|
+
identifiers:
|
|
11
|
+
- type: doi
|
|
12
|
+
value: 10.5281/zenodo.21839607
|
|
13
|
+
description: "v7.8.0 — version DOI"
|
|
9
14
|
abstract: >-
|
|
10
|
-
Portable mixed-precision math, linear-algebra, & retrieval library
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
co-designed with Tensor abstractions in C++, Python, Rust, JavaScript, GoLang, & Swift.
|
|
15
|
+
Portable mixed-precision math, linear-algebra, & retrieval library with 2'000+ SIMD kernels for
|
|
16
|
+
x86, Arm, RISC-V, LoongArch, Power, & WebAssembly, leveraging rare algebraic transforms with
|
|
17
|
+
both 1D & 2D registers like AMX & SME, covering 15+ numeric types from 4-bit integers & 6-bit
|
|
18
|
+
floats to 128-bit complex numbers, validated against 118-bit extended-precision baselines with
|
|
19
|
+
saturation, casting, & rounding edge-case coverage, in a 5-100x smaller binary than other
|
|
20
|
+
BLAS-like alternatives, co-designed with Tensor abstractions in C++, Python, Rust, JavaScript,
|
|
21
|
+
GoLang, & Swift.
|
|
18
22
|
|
|
19
23
|
authors:
|
|
20
24
|
- family-names: Vardanian
|
package/numkong/CONTRIBUTING.md
CHANGED
|
@@ -32,21 +32,20 @@ build_release/nk_bench
|
|
|
32
32
|
build_release/nk_test
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
| CMake Flag
|
|
36
|
-
|
|
|
37
|
-
| `NK_BUILD_TEST`
|
|
38
|
-
| `NK_BUILD_BENCH`
|
|
39
|
-
| `NK_BUILD_SHARED`
|
|
40
|
-
| `NK_BUILD_SHARED_TEST`
|
|
41
|
-
| `NK_COMPARE_TO_BLAS`
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `NK_MARCH_NATIVE` | `OFF` | Tune for host CPU with `-march=native` |
|
|
35
|
+
| CMake Flag | Default | Description |
|
|
36
|
+
| ---------------------- | ------------------ | ----------------------------------------------------------------------------- |
|
|
37
|
+
| `NK_BUILD_TEST` | `OFF` | Compile precision tests with ULP error analysis |
|
|
38
|
+
| `NK_BUILD_BENCH` | `OFF` | Compile micro-benchmarks |
|
|
39
|
+
| `NK_BUILD_SHARED` | `ON`, if top-level | Compile dynamic library |
|
|
40
|
+
| `NK_BUILD_SHARED_TEST` | `OFF` | Compile tests against the shared library |
|
|
41
|
+
| `NK_COMPARE_TO_BLAS` | `AUTO` | Include OpenBLAS, or Apple's Accelerate on macOS, into test/bench comparisons |
|
|
42
|
+
| `NK_COMPARE_TO_MKL` | `AUTO` | Include Intel' MKL into test/bench comparisons |
|
|
43
|
+
| `NK_MARCH_NATIVE` | `OFF` | Tune for host CPU with `-march=native` |
|
|
45
44
|
|
|
46
45
|
### Target Baseline Policy
|
|
47
46
|
|
|
48
47
|
`CMakeLists.txt`, `build.rs`, `setup.py`, and `binding.gyp` pin the TU-level baseline to each architecture's ABI floor so distributable artifacts run on any CPU matching the ABI, not just the build host.
|
|
49
|
-
SIMD kernels live inside `#pragma GCC target(...)` regions and are only called after runtime probing — see the README's [Compile-Time and Run-Time Dispatch](
|
|
48
|
+
SIMD kernels live inside `#pragma GCC target(...)` regions and are only called after runtime probing — see the README's [Compile-Time and Run-Time Dispatch](README.md#compile-time-and-run-time-dispatch) section.
|
|
50
49
|
|
|
51
50
|
| Target arch | GCC/Clang baseline | MSVC baseline | Notes |
|
|
52
51
|
| ------------- | --------------------------- | --------------- | ----------------------------------------------------------- |
|
|
@@ -89,19 +88,25 @@ sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 100
|
|
|
89
88
|
|
|
90
89
|
### Cross-Compilation
|
|
91
90
|
|
|
92
|
-
NumKong ships
|
|
91
|
+
NumKong ships 11 toolchain files in `cmake/`, each named `toolchain-<name>.cmake`.
|
|
93
92
|
Tests and benchmarks run transparently under QEMU via `CMAKE_CROSSCOMPILING_EMULATOR`.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
|
97
|
-
|
|
|
98
|
-
|
|
|
99
|
-
| RISC-V 64
|
|
100
|
-
|
|
|
101
|
-
|
|
|
102
|
-
|
|
|
103
|
-
|
|
|
104
|
-
|
|
|
93
|
+
Targets with a `qemu-*` emulator additionally require `qemu-user`.
|
|
94
|
+
|
|
95
|
+
| Target | Toolchain | Emulator | Prerequisites |
|
|
96
|
+
| ----------------------- | ----------------- | --------------------------- | ------------------------------ |
|
|
97
|
+
| ARM64 Linux | `aarch64-gnu` | `qemu-aarch64 -cpu max` | `gcc-aarch64-linux-gnu` |
|
|
98
|
+
| RISC-V 64 GCC | `riscv64-gnu` | `qemu-riscv64 -cpu max` | [`riscv-gnu-toolchain`][riscv] |
|
|
99
|
+
| RISC-V 64 LLVM | `riscv64-llvm` | `qemu-riscv64 -cpu max` | LLVM 17+, `RISCV_SYSROOT` |
|
|
100
|
+
| ppc64le Linux | `ppc64le-gnu` | `qemu-ppc64le -cpu power9` | `gcc-powerpc64le-linux-gnu` |
|
|
101
|
+
| LoongArch 64 | `loongarch64-gnu` | `qemu-loongarch64 -cpu max` | `LOONGARCH_TOOLCHAIN_PATH` |
|
|
102
|
+
| Android ARM64 | `android-arm64` | — | `ANDROID_NDK_ROOT` |
|
|
103
|
+
| Android ARMv7 | `android-armv7` | — | `ANDROID_NDK_ROOT` |
|
|
104
|
+
| x86_64 on Apple Silicon | `x86_64-llvm` | `arch -x86_64` | Homebrew LLVM |
|
|
105
|
+
| WASM Emscripten | `wasm` | Node.js | Emscripten 3.1.27+ |
|
|
106
|
+
| WASM64 Memory64 | `wasm64` | Node.js | Emscripten 3.1.35+ |
|
|
107
|
+
| WASI | `wasi` | Wasmtime / Wasmer | WASI SDK 24+ |
|
|
108
|
+
|
|
109
|
+
[riscv]: https://github.com/riscv-collab/riscv-gnu-toolchain/releases
|
|
105
110
|
|
|
106
111
|
Set `NK_IN_QEMU=1` to relax half-precision accuracy thresholds under emulation.
|
|
107
112
|
|
|
@@ -236,7 +241,7 @@ See [test/README.md](test/README.md) for test framework details and [bench/READM
|
|
|
236
241
|
Once done editing the code, please run analyzers and formatters:
|
|
237
242
|
|
|
238
243
|
```bash
|
|
239
|
-
git ls-files '*.h' '*.c' '*.hpp' '*.cpp' | xargs clang-format -i # Use Clang Format
|
|
244
|
+
git ls-files '*.h' '*.c' '*.hpp' '*.cpp' | xargs clang-format -i # Use Clang Format 22 or newer
|
|
240
245
|
```
|
|
241
246
|
|
|
242
247
|
## Python
|
package/numkong/Cargo.toml
CHANGED
package/numkong/README.md
CHANGED
|
@@ -68,12 +68,12 @@ A broader throughput comparison is maintained in [NumWars](https://github.com/as
|
|
|
68
68
|
| Python | `pip install` | Linux, macOS, Windows | [python/README.md](python/README.md) |
|
|
69
69
|
| Rust | `cargo add` | Linux, macOS, Windows | [rust/README.md](rust/README.md) |
|
|
70
70
|
| JS | `npm install` & `import` | Node.js, Bun, Deno & browsers | [javascript/README.md](javascript/README.md) |
|
|
71
|
-
| Swift | Swift Package Manager |
|
|
71
|
+
| Swift | Swift Package Manager | Apple platforms & Linux | [swift/README.md](swift/README.md) |
|
|
72
72
|
| Go | `go get` | Linux, macOS, Windows via cGo | [golang/README.md](golang/README.md) |
|
|
73
73
|
|
|
74
74
|
## What's Inside
|
|
75
75
|
|
|
76
|
-
NumKong covers 17 numeric types — from 6-bit floats to
|
|
76
|
+
NumKong covers 17 numeric types — from 6-bit floats to 128-bit complex numbers — across dozens of operations and 30+ SIMD backends, with hardware-aware defaults: Arm prioritizes `f16`, x86 prioritizes `bf16`.
|
|
77
77
|
|
|
78
78
|
### Language Bindings
|
|
79
79
|
|
|
@@ -215,7 +215,7 @@ The [GIL](https://docs.python.org/3/glossary.html#term-global-interpreter-lock)
|
|
|
215
215
|
import concurrent.futures, numkong as nk, numpy as np
|
|
216
216
|
|
|
217
217
|
vectors, num_threads = np.random.randn(1000, 768).astype(np.float32), 4
|
|
218
|
-
output = nk.zeros((1000, 1000), dtype="
|
|
218
|
+
output = nk.zeros((1000, 1000), dtype="float64")
|
|
219
219
|
|
|
220
220
|
def compute_slice(t):
|
|
221
221
|
start = t * (len(vectors) // num_threads)
|
|
@@ -320,7 +320,7 @@ Every such decision — saturation thresholds, Newton-Raphson iteration counts,
|
|
|
320
320
|
|
|
321
321
|
### Calling Convention & Error Handling
|
|
322
322
|
|
|
323
|
-
NumKong never throws exceptions, never sets `errno`, and never calls `setjmp`/`longjmp` — [exceptions bloat call sites with unwind tables](https://monkeywritescode.blogspot.com/p/c-exceptions-under-hood.html) and are invisible to C, Python, Rust, Swift, Go, and JavaScript FFI; `errno` is thread-local state whose [storage model varies across C runtimes](https://en.cppreference.com/w/c/error/errno).
|
|
323
|
+
NumKong never throws exceptions, never sets `errno`, and never calls `setjmp`/`longjmp` on any kernel path — [exceptions bloat call sites with unwind tables](https://monkeywritescode.blogspot.com/p/c-exceptions-under-hood.html) and are invisible to C, Python, Rust, Swift, Go, and JavaScript FFI; `errno` is thread-local state whose [storage model varies across C runtimes](https://en.cppreference.com/w/c/error/errno).
|
|
324
324
|
Instead, every function takes inputs as `const` pointers, writes outputs through caller-provided pointers, and returns `void`:
|
|
325
325
|
|
|
326
326
|
```c
|
|
@@ -352,7 +352,7 @@ nk_capability_t used = nk_cap_serial_k;
|
|
|
352
352
|
nk_find_kernel_punned(
|
|
353
353
|
nk_kernel_angular_k, nk_f32_k, // what functionality? for which input type?
|
|
354
354
|
nk_capabilities(), // which capabilities are viable?
|
|
355
|
-
(nk_kernel_punned_t *)&angular, &used); // the kernel found and
|
|
355
|
+
(nk_kernel_punned_t *)&angular, &used); // the kernel found and capabilities used!
|
|
356
356
|
```
|
|
357
357
|
|
|
358
358
|
The first call to `nk_capabilities()` initializes the dispatch table; all subsequent calls are lock-free.
|
|
@@ -489,8 +489,8 @@ On Arm, NEON FHM extensions bring widening `FMLAL` dot-products for Float16 —
|
|
|
489
489
|
> On NEON+FP8DOT, E3M2 is first promoted to E5M2 and E2M3 to E4M3 before the hardware `FDOT` instruction.
|
|
490
490
|
> Sierra Forest and Alder Lake use native `VPDPBSSD` (signed×signed) and `VPDPBUSD` (unsigned×signed) respectively for E2M3.
|
|
491
491
|
|
|
492
|
-
E4M3 and E5M2 cannot use the integer path.
|
|
493
|
-
E4M3 scaled by 16 reaches 7,
|
|
492
|
+
E4M3 and E5M2 cannot use the same 32-entry LUT integer path.
|
|
493
|
+
E4M3 scaled by 16 reaches 7,168 — too large for Int8, barely fitting Int16 with a 128-entry table.
|
|
494
494
|
E5M2's range (±57,344) makes the scaled product exceed Int32 entirely.
|
|
495
495
|
Without the integer path, E5M2 falls back to Float32 accumulation — where its [2-bit mantissa (only 4 values per binade)](https://developer.nvidia.com/blog/floating-point-8-an-introduction-to-efficient-lower-precision-ai-training/) creates a [catastrophic cancellation risk](https://www.ac.uma.es/arith2024/papers/Fused%20FP8%204-Way%20Dot%20Product%20with%20Scaling%20and%20FP32%20Accumulation.pdf) that E2M3's integer path avoids completely:
|
|
496
496
|
|
|
@@ -564,6 +564,20 @@ Beyond the READMEs in this repository, there are several standalone articles cov
|
|
|
564
564
|
- [Accelerating JavaScript arrays by 10x for Vector Search](https://ashvardanian.com/posts/javascript-ai-vector-search/)
|
|
565
565
|
- [SciPy distances... up to 200x faster with AVX-512 & SVE](https://ashvardanian.com/posts/simsimd-faster-scipy/)
|
|
566
566
|
|
|
567
|
+
## Citation
|
|
568
|
+
|
|
569
|
+
If NumKong helps your research or product, please cite it:
|
|
570
|
+
|
|
571
|
+
```bibtex
|
|
572
|
+
@software{Vardanian_NumKong,
|
|
573
|
+
author = {Vardanian, Ash},
|
|
574
|
+
title = {{NumKong: 2000 Mixed Precision Kernels For All}},
|
|
575
|
+
doi = {10.5281/zenodo.21480519},
|
|
576
|
+
url = {https://github.com/ashvardanian/NumKong},
|
|
577
|
+
license = {Apache-2.0}
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
567
581
|
## License
|
|
568
582
|
|
|
569
583
|
Feel free to use the project under Apache 2.0 or the Three-clause BSD license at your preference.
|
package/numkong/binding.gyp
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"variables": {
|
|
3
|
-
"openssl_fips": ""
|
|
3
|
+
"openssl_fips": "",
|
|
4
|
+
# `NK_MARCH_NATIVE=1` opts into a host-tuned, non-portable build, as in CMakeLists.txt.
|
|
5
|
+
"nk_march_native%": "<!(node -p \"['1','true','TRUE'].includes(process.env.NK_MARCH_NATIVE||'')?1:0\")"
|
|
4
6
|
},
|
|
5
7
|
"targets": [
|
|
6
8
|
{
|
|
@@ -8,6 +10,7 @@
|
|
|
8
10
|
"sources": [
|
|
9
11
|
"javascript/numkong.c",
|
|
10
12
|
"c/numkong.c",
|
|
13
|
+
"c/parallel.c",
|
|
11
14
|
"c/dispatch_f64.c",
|
|
12
15
|
"c/dispatch_f32.c",
|
|
13
16
|
"c/dispatch_f16.c",
|
|
@@ -34,18 +37,17 @@
|
|
|
34
37
|
"c/dispatch_e3m2.c",
|
|
35
38
|
],
|
|
36
39
|
"include_dirs": [
|
|
37
|
-
"include"
|
|
40
|
+
"include",
|
|
41
|
+
"c"
|
|
38
42
|
],
|
|
39
43
|
"defines": [
|
|
40
44
|
"NK_NATIVE_F16=0",
|
|
41
45
|
"NK_NATIVE_BF16=0",
|
|
42
|
-
"NK_DYNAMIC_DISPATCH=1"
|
|
43
|
-
"NK_USE_OPENMP=1"
|
|
46
|
+
"NK_DYNAMIC_DISPATCH=1"
|
|
44
47
|
],
|
|
45
48
|
"cflags": [
|
|
46
49
|
"-std=c11",
|
|
47
50
|
"-O3",
|
|
48
|
-
"-fopenmp",
|
|
49
51
|
"-Wno-unknown-pragmas",
|
|
50
52
|
"-Wno-maybe-uninitialized",
|
|
51
53
|
"-Wno-cast-function-type",
|
|
@@ -54,25 +56,35 @@
|
|
|
54
56
|
"-include",
|
|
55
57
|
"<(module_root_dir)/nk_probes.h",
|
|
56
58
|
],
|
|
57
|
-
"ldflags": [
|
|
58
|
-
"-fopenmp"
|
|
59
|
-
],
|
|
60
59
|
"msvs_settings": {
|
|
61
60
|
"VCCLCompilerTool": {
|
|
62
61
|
"ForcedIncludeFiles": [
|
|
63
62
|
"<(module_root_dir)/nk_probes.h"
|
|
64
63
|
],
|
|
65
64
|
"AdditionalOptions": [
|
|
66
|
-
"/Zc:preprocessor"
|
|
67
|
-
"/openmp:llvm"
|
|
65
|
+
"/Zc:preprocessor"
|
|
68
66
|
],
|
|
69
67
|
},
|
|
70
68
|
},
|
|
71
69
|
"conditions": [
|
|
70
|
+
# Only this branch gets OpenMP; macOS and Windows use their own pools.
|
|
71
|
+
[
|
|
72
|
+
"OS!='mac' and OS!='win'",
|
|
73
|
+
{
|
|
74
|
+
"cflags": [
|
|
75
|
+
"-fopenmp"
|
|
76
|
+
],
|
|
77
|
+
"ldflags": [
|
|
78
|
+
"-fopenmp"
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
],
|
|
72
82
|
# Pin TU baseline to each arch's ABI floor; SIMD kernels use per-function pragmas.
|
|
73
|
-
# Keep per-arch table in sync with
|
|
83
|
+
# Keep per-arch table in sync with CMakeLists.txt, build.rs, setup.py.
|
|
84
|
+
# macOS is excluded: `-arch` already pins the slice, and a per-arch `-march=`
|
|
85
|
+
# conflicts with the other slice of a universal build.
|
|
74
86
|
[
|
|
75
|
-
"OS!='win' and target_arch=='arm64'",
|
|
87
|
+
"nk_march_native==0 and OS!='win' and OS!='mac' and target_arch=='arm64'",
|
|
76
88
|
{
|
|
77
89
|
"cflags": [
|
|
78
90
|
"-march=armv8-a"
|
|
@@ -80,7 +92,7 @@
|
|
|
80
92
|
}
|
|
81
93
|
],
|
|
82
94
|
[
|
|
83
|
-
"OS!='win' and target_arch=='x64'",
|
|
95
|
+
"nk_march_native==0 and OS!='win' and OS!='mac' and target_arch=='x64'",
|
|
84
96
|
{
|
|
85
97
|
"cflags": [
|
|
86
98
|
"-march=x86-64"
|
|
@@ -88,7 +100,7 @@
|
|
|
88
100
|
}
|
|
89
101
|
],
|
|
90
102
|
[
|
|
91
|
-
"OS!='win' and target_arch=='riscv64'",
|
|
103
|
+
"nk_march_native==0 and OS!='win' and OS!='mac' and target_arch=='riscv64'",
|
|
92
104
|
{
|
|
93
105
|
"cflags": [
|
|
94
106
|
"-march=rv64gc"
|
|
@@ -96,7 +108,7 @@
|
|
|
96
108
|
}
|
|
97
109
|
],
|
|
98
110
|
[
|
|
99
|
-
"OS!='win' and target_arch=='ppc64'",
|
|
111
|
+
"nk_march_native==0 and OS!='win' and OS!='mac' and target_arch=='ppc64'",
|
|
100
112
|
{
|
|
101
113
|
"cflags": [
|
|
102
114
|
"-mcpu=power8"
|
|
@@ -104,7 +116,7 @@
|
|
|
104
116
|
}
|
|
105
117
|
],
|
|
106
118
|
[
|
|
107
|
-
"OS!='win' and target_arch=='loong64'",
|
|
119
|
+
"nk_march_native==0 and OS!='win' and OS!='mac' and target_arch=='loong64'",
|
|
108
120
|
{
|
|
109
121
|
"cflags": [
|
|
110
122
|
"-march=loongarch64",
|
|
@@ -112,6 +124,14 @@
|
|
|
112
124
|
]
|
|
113
125
|
}
|
|
114
126
|
],
|
|
127
|
+
[
|
|
128
|
+
"nk_march_native==1 and OS!='win' and OS!='mac'",
|
|
129
|
+
{
|
|
130
|
+
"cflags": [
|
|
131
|
+
"-march=native"
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
],
|
|
115
135
|
# Forbid auto-vectorization so serial fallbacks don't get silently
|
|
116
136
|
# promoted to NEON/SSE2/VSX. SIMD kernels use explicit intrinsics
|
|
117
137
|
# and per-function `target` pragmas; unaffected. MSVC has no
|
|
@@ -125,27 +145,23 @@
|
|
|
125
145
|
]
|
|
126
146
|
}
|
|
127
147
|
],
|
|
148
|
+
# gyp ignores `cflags` on the mac flavor, so every flag above must be
|
|
149
|
+
# repeated here or the addon builds unoptimized and without the probes.
|
|
128
150
|
[
|
|
129
151
|
"OS=='mac'",
|
|
130
152
|
{
|
|
131
153
|
"xcode_settings": {
|
|
132
154
|
"MACOSX_DEPLOYMENT_TARGET": "11.0",
|
|
133
|
-
# Apple Clang ships no `omp.h`; the CI step
|
|
134
|
-
# `brew install libomp` makes it keg-only under
|
|
135
|
-
# `/opt/homebrew/opt/libomp` (arm64) or
|
|
136
|
-
# `/usr/local/opt/libomp` (x86_64). Clang silently
|
|
137
|
-
# ignores `-I` / `-L` dirs that don't exist, so
|
|
138
|
-
# listing both keeps the file arch-agnostic.
|
|
139
155
|
"OTHER_CFLAGS": [
|
|
140
|
-
"-
|
|
141
|
-
"-
|
|
142
|
-
"-
|
|
143
|
-
"-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
"-
|
|
147
|
-
"-
|
|
148
|
-
"
|
|
156
|
+
"-std=c11",
|
|
157
|
+
"-O3",
|
|
158
|
+
"-fno-tree-vectorize",
|
|
159
|
+
"-fno-tree-slp-vectorize",
|
|
160
|
+
"-Wno-unknown-pragmas",
|
|
161
|
+
"-Wno-cast-function-type",
|
|
162
|
+
"-Wno-switch",
|
|
163
|
+
"-include",
|
|
164
|
+
"<(module_root_dir)/nk_probes.h"
|
|
149
165
|
]
|
|
150
166
|
}
|
|
151
167
|
}
|
package/numkong/build.rs
CHANGED
|
@@ -376,21 +376,31 @@ 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
|
|
380
|
-
let
|
|
379
|
+
// Keep per-arch table in sync with CMakeLists.txt, setup.py, binding.gyp.
|
|
380
|
+
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
|
381
|
+
let is_apple = target_os == "macos" || target_os == "ios";
|
|
382
|
+
let is_cross = env::var("HOST").unwrap_or_default() != env::var("TARGET").unwrap_or_default();
|
|
383
|
+
let march_native = env::var("NK_MARCH_NATIVE").is_ok_and(|v| v == "1" || v == "true" || v == "TRUE");
|
|
381
384
|
// Portable baseline: pin TU ISA floor + forbid auto-vectorization so serial
|
|
382
385
|
// fallbacks don't get silently promoted to NEON/SSE2/VSX. SIMD kernels use
|
|
383
386
|
// explicit intrinsics; unaffected. MSVC has no command-line vectorizer
|
|
384
387
|
// toggle; `NK_MARCH_NATIVE=1` opts out for host-tuned builds.
|
|
385
|
-
if march_native && !is_msvc {
|
|
386
|
-
println!("cargo:warning=NK_MARCH_NATIVE=1: building -
|
|
387
|
-
|
|
388
|
+
if march_native && !is_msvc && !is_cross {
|
|
389
|
+
println!("cargo:warning=NK_MARCH_NATIVE=1: building host-tuned, result will not run on older CPUs");
|
|
390
|
+
// Apple Clang's `-march=native` advertises only a subset of host features
|
|
391
|
+
// (no SME/SME2/FP16_FML); `-mcpu=native` is the complete knob on macOS.
|
|
392
|
+
build.flag_if_supported(if is_apple { "-mcpu=native" } else { "-march=native" });
|
|
388
393
|
} else if is_msvc {
|
|
389
394
|
match target_arch.as_str() {
|
|
390
395
|
"x86_64" => { build.flag_if_supported("/arch:SSE2"); }
|
|
391
396
|
"aarch64" => { build.flag_if_supported("/arch:armv8.0"); }
|
|
392
397
|
_ => {}
|
|
393
398
|
}
|
|
399
|
+
} else if is_apple {
|
|
400
|
+
// `-arch` already pins the ABI floor per slice, and a universal build passes both
|
|
401
|
+
// to one clang invocation, where a per-arch `-march=` conflicts with the other slice.
|
|
402
|
+
build.flag_if_supported("-fno-tree-vectorize");
|
|
403
|
+
build.flag_if_supported("-fno-tree-slp-vectorize");
|
|
394
404
|
} else {
|
|
395
405
|
build.flag_if_supported("-fno-tree-vectorize");
|
|
396
406
|
build.flag_if_supported("-fno-tree-slp-vectorize");
|