usearch 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/prerelease.yml +12 -3
- package/Cargo.toml +1 -1
- package/conanfile.py +1 -1
- package/cpp/bench.cpp +1 -0
- package/include/usearch/index.hpp +48 -12
- package/include/usearch/index_dense.hpp +43 -20
- package/include/usearch/index_plugins.hpp +59 -13
- package/package.json +1 -1
- package/python/lib.cpp +133 -61
- package/python/usearch/index.py +112 -79
- package/simsimd/include/simsimd/simsimd.h +106 -40
- package/wasmer.toml +1 -1
|
@@ -148,7 +148,10 @@ jobs:
|
|
|
148
148
|
- uses: actions/setup-node@v3
|
|
149
149
|
with:
|
|
150
150
|
node-version: 18
|
|
151
|
-
- run:
|
|
151
|
+
- run: |
|
|
152
|
+
export CC=/usr/bin/gcc-12
|
|
153
|
+
export CXX=/usr/bin/g++-12
|
|
154
|
+
npm install
|
|
152
155
|
- run: npm ci
|
|
153
156
|
- run: npm test
|
|
154
157
|
|
|
@@ -176,7 +179,10 @@ jobs:
|
|
|
176
179
|
- name: Setup Gradle
|
|
177
180
|
uses: gradle/gradle-build-action@v2.4.2
|
|
178
181
|
- name: Execute Gradle build
|
|
179
|
-
run:
|
|
182
|
+
run:
|
|
183
|
+
export CC=/usr/bin/gcc-12
|
|
184
|
+
export CXX=/usr/bin/g++-12
|
|
185
|
+
gradle clean build
|
|
180
186
|
|
|
181
187
|
test_swift:
|
|
182
188
|
name: Test ObjC & Swift
|
|
@@ -251,7 +257,10 @@ jobs:
|
|
|
251
257
|
|
|
252
258
|
- name: Build library
|
|
253
259
|
run: |
|
|
254
|
-
cd golang
|
|
260
|
+
cd golang/
|
|
261
|
+
export CC=/usr/bin/gcc-12
|
|
262
|
+
export CXX=/usr/bin/g++-12
|
|
263
|
+
make -C ../c libusearch_c.so
|
|
255
264
|
sudo mv ../c/libusearch_c.so /usr/local/lib/libusearch.a
|
|
256
265
|
sudo mv ../c/usearch.h /usr/local/include/usearch.h
|
|
257
266
|
|
package/Cargo.toml
CHANGED
package/conanfile.py
CHANGED
|
@@ -4,7 +4,7 @@ from conans import ConanFile
|
|
|
4
4
|
class USearchConan(ConanFile):
|
|
5
5
|
|
|
6
6
|
name = 'USearch'
|
|
7
|
-
version = '2.
|
|
7
|
+
version = '2.3.0'
|
|
8
8
|
license = 'Apache 2.0'
|
|
9
9
|
url = 'https://github.com/unum-cloud/usearch'
|
|
10
10
|
description = 'Smaller & Faster Single-File Vector Search Engine from Unum'
|
package/cpp/bench.cpp
CHANGED
|
@@ -372,6 +372,7 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
|
|
|
372
372
|
executor, [&](std::size_t progress, std::size_t total) {
|
|
373
373
|
if (progress % 1000 == 0)
|
|
374
374
|
printer.print(progress, total);
|
|
375
|
+
return true;
|
|
375
376
|
});
|
|
376
377
|
join_attempts = result.visited_members;
|
|
377
378
|
}
|
|
@@ -1212,9 +1212,10 @@ struct dummy_callback_t {
|
|
|
1212
1212
|
*
|
|
1213
1213
|
* This is particularly helpful when handling long-running tasks, like serialization,
|
|
1214
1214
|
* saving, and loading from disk, or index-level joins.
|
|
1215
|
+
* The reporter checks return value to continue or stop the process, `false` means need to stop.
|
|
1215
1216
|
*/
|
|
1216
1217
|
struct dummy_progress_t {
|
|
1217
|
-
inline
|
|
1218
|
+
inline bool operator()(std::size_t /*processed*/, std::size_t /*total*/) const noexcept { return true; }
|
|
1218
1219
|
};
|
|
1219
1220
|
|
|
1220
1221
|
/**
|
|
@@ -2690,6 +2691,10 @@ class index_gt {
|
|
|
2690
2691
|
if (!output(&header, sizeof(header)))
|
|
2691
2692
|
return result.failed("Failed to serialize the header into stream");
|
|
2692
2693
|
|
|
2694
|
+
// Progress status
|
|
2695
|
+
std::size_t processed = 0;
|
|
2696
|
+
std::size_t const total = 2 * header.size;
|
|
2697
|
+
|
|
2693
2698
|
// Export the number of levels per node
|
|
2694
2699
|
// That is both enough to estimate the overall memory consumption,
|
|
2695
2700
|
// and to be able to estimate the offsets of every entry in the file.
|
|
@@ -2697,15 +2702,18 @@ class index_gt {
|
|
|
2697
2702
|
node_t node = node_at_(i);
|
|
2698
2703
|
level_t level = node.level();
|
|
2699
2704
|
if (!output(&level, sizeof(level)))
|
|
2700
|
-
return result.failed("Failed to serialize
|
|
2705
|
+
return result.failed("Failed to serialize into stream");
|
|
2706
|
+
if (!progress(++processed, total))
|
|
2707
|
+
return result.failed("Terminated by user");
|
|
2701
2708
|
}
|
|
2702
2709
|
|
|
2703
2710
|
// After that dump the nodes themselves
|
|
2704
2711
|
for (std::size_t i = 0; i != header.size; ++i) {
|
|
2705
2712
|
span_bytes_t node_bytes = node_bytes_(node_at_(i));
|
|
2706
2713
|
if (!output(node_bytes.data(), node_bytes.size()))
|
|
2707
|
-
return result.failed("Failed to serialize
|
|
2708
|
-
progress(
|
|
2714
|
+
return result.failed("Failed to serialize into stream");
|
|
2715
|
+
if (!progress(++processed, total))
|
|
2716
|
+
return result.failed("Terminated by user");
|
|
2709
2717
|
}
|
|
2710
2718
|
|
|
2711
2719
|
return {};
|
|
@@ -2763,7 +2771,8 @@ class index_gt {
|
|
|
2763
2771
|
return result.failed("Failed to pull nodes from the stream");
|
|
2764
2772
|
}
|
|
2765
2773
|
nodes_[i] = node_t{node_bytes.data()};
|
|
2766
|
-
progress(i, header.size)
|
|
2774
|
+
if (!progress(i + 1, header.size))
|
|
2775
|
+
return result.failed("Terminated by user");
|
|
2767
2776
|
}
|
|
2768
2777
|
return {};
|
|
2769
2778
|
}
|
|
@@ -2936,7 +2945,8 @@ class index_gt {
|
|
|
2936
2945
|
// Rapidly address all the nodes
|
|
2937
2946
|
for (std::size_t i = 0; i != header.size; ++i) {
|
|
2938
2947
|
nodes_[i] = node_t{(byte_t*)file.data() + offsets[i]};
|
|
2939
|
-
progress(i, header.size)
|
|
2948
|
+
if (!progress(i + 1, header.size))
|
|
2949
|
+
return result.failed("Terminated by user");
|
|
2940
2950
|
}
|
|
2941
2951
|
viewed_file_ = std::move(file);
|
|
2942
2952
|
return {};
|
|
@@ -2986,8 +2996,13 @@ class index_gt {
|
|
|
2986
2996
|
using slot_level_allocator_t = typename dynamic_allocator_traits_t::template rebind_alloc<slot_level_t>;
|
|
2987
2997
|
buffer_gt<slot_level_t, slot_level_allocator_t> slots_and_levels(size());
|
|
2988
2998
|
|
|
2999
|
+
// Progress status
|
|
3000
|
+
std::atomic<bool> do_tasks{true};
|
|
3001
|
+
std::atomic<std::size_t> processed{0};
|
|
3002
|
+
std::size_t const total = 3 * slots_and_levels.size();
|
|
3003
|
+
|
|
2989
3004
|
// For every bottom level node, determine its parent cluster
|
|
2990
|
-
executor.
|
|
3005
|
+
executor.dynamic(slots_and_levels.size(), [&](std::size_t thread_idx, std::size_t old_slot) {
|
|
2991
3006
|
context_t& context = contexts_[thread_idx];
|
|
2992
3007
|
std::size_t cluster = search_for_one_( //
|
|
2993
3008
|
values[citerator_at(old_slot)], //
|
|
@@ -2997,7 +3012,13 @@ class index_gt {
|
|
|
2997
3012
|
static_cast<compressed_slot_t>(old_slot), //
|
|
2998
3013
|
static_cast<compressed_slot_t>(cluster), //
|
|
2999
3014
|
node_at_(old_slot).level()};
|
|
3015
|
+
++processed;
|
|
3016
|
+
if (thread_idx == 0)
|
|
3017
|
+
do_tasks = progress(processed.load(), total);
|
|
3018
|
+
return do_tasks.load();
|
|
3000
3019
|
});
|
|
3020
|
+
if (!do_tasks.load())
|
|
3021
|
+
return;
|
|
3001
3022
|
|
|
3002
3023
|
// Where the actual permutation happens:
|
|
3003
3024
|
std::sort(slots_and_levels.begin(), slots_and_levels.end(), [](slot_level_t const& a, slot_level_t const& b) {
|
|
@@ -3027,8 +3048,8 @@ class index_gt {
|
|
|
3027
3048
|
neighbor = static_cast<compressed_slot_t>(old_slot_to_new[compressed_slot_t(neighbor)]);
|
|
3028
3049
|
|
|
3029
3050
|
reordered_nodes[new_slot] = new_node;
|
|
3030
|
-
|
|
3031
|
-
|
|
3051
|
+
if (!progress(++processed, total))
|
|
3052
|
+
return;
|
|
3032
3053
|
}
|
|
3033
3054
|
|
|
3034
3055
|
for (std::size_t new_slot = 0; new_slot != slots_and_levels.size(); ++new_slot) {
|
|
@@ -3036,6 +3057,8 @@ class index_gt {
|
|
|
3036
3057
|
slot_transition(node_at_(old_slot).ckey(), //
|
|
3037
3058
|
static_cast<compressed_slot_t>(old_slot), //
|
|
3038
3059
|
static_cast<compressed_slot_t>(new_slot));
|
|
3060
|
+
if (!progress(++processed, total))
|
|
3061
|
+
return;
|
|
3039
3062
|
}
|
|
3040
3063
|
|
|
3041
3064
|
nodes_ = std::move(reordered_nodes);
|
|
@@ -3064,9 +3087,13 @@ class index_gt {
|
|
|
3064
3087
|
executor_at&& executor = executor_at{}, //
|
|
3065
3088
|
progress_at&& progress = progress_at{}) noexcept {
|
|
3066
3089
|
|
|
3090
|
+
// Progress status
|
|
3091
|
+
std::atomic<bool> do_tasks{true};
|
|
3092
|
+
std::atomic<std::size_t> processed{0};
|
|
3093
|
+
|
|
3067
3094
|
// Erase all the incoming links
|
|
3068
3095
|
std::size_t nodes_count = size();
|
|
3069
|
-
executor.
|
|
3096
|
+
executor.dynamic(nodes_count, [&](std::size_t thread_idx, std::size_t node_idx) {
|
|
3070
3097
|
node_t node = node_at_(node_idx);
|
|
3071
3098
|
for (level_t level = 0; level <= node.level(); ++level) {
|
|
3072
3099
|
neighbors_ref_t neighbors = neighbors_(node, level);
|
|
@@ -3079,8 +3106,14 @@ class index_gt {
|
|
|
3079
3106
|
neighbors.push_back(neighbor_slot);
|
|
3080
3107
|
}
|
|
3081
3108
|
}
|
|
3082
|
-
|
|
3109
|
+
++processed;
|
|
3110
|
+
if (thread_idx == 0)
|
|
3111
|
+
do_tasks = progress(processed.load(), nodes_count);
|
|
3112
|
+
return do_tasks.load();
|
|
3083
3113
|
});
|
|
3114
|
+
|
|
3115
|
+
// At the end report the latest numbers, because the reporter thread may be finished earlier
|
|
3116
|
+
progress(processed.load(), nodes_count);
|
|
3084
3117
|
}
|
|
3085
3118
|
|
|
3086
3119
|
private:
|
|
@@ -3710,7 +3743,10 @@ static join_result_t join( //
|
|
|
3710
3743
|
passed_rounds = ++rounds;
|
|
3711
3744
|
total_rounds = passed_rounds + free_men.size();
|
|
3712
3745
|
}
|
|
3713
|
-
progress(passed_rounds, total_rounds)
|
|
3746
|
+
if (thread_idx == 0 && !progress(passed_rounds, total_rounds)) {
|
|
3747
|
+
atomic_error.store("Terminated by user");
|
|
3748
|
+
break;
|
|
3749
|
+
}
|
|
3714
3750
|
while (men_locks.atomic_set(free_man_slot))
|
|
3715
3751
|
;
|
|
3716
3752
|
|
|
@@ -767,7 +767,9 @@ class index_dense_gt {
|
|
|
767
767
|
* @brief Saves serialized binary index representation to a stream.
|
|
768
768
|
*/
|
|
769
769
|
template <typename output_callback_at, typename progress_at = dummy_progress_t>
|
|
770
|
-
serialization_result_t save_to_stream(output_callback_at&& output,
|
|
770
|
+
serialization_result_t save_to_stream(output_callback_at&& output, //
|
|
771
|
+
serialization_config_t config = {}, //
|
|
772
|
+
progress_at&& progress = {}) const {
|
|
771
773
|
|
|
772
774
|
serialization_result_t result;
|
|
773
775
|
std::uint64_t matrix_rows = 0;
|
|
@@ -831,7 +833,7 @@ class index_dense_gt {
|
|
|
831
833
|
}
|
|
832
834
|
|
|
833
835
|
// Save the actual proximity graph
|
|
834
|
-
return typed_->save_to_stream(std::forward<output_callback_at>(output));
|
|
836
|
+
return typed_->save_to_stream(std::forward<output_callback_at>(output), std::forward<progress_at>(progress));
|
|
835
837
|
}
|
|
836
838
|
|
|
837
839
|
/**
|
|
@@ -853,8 +855,10 @@ class index_dense_gt {
|
|
|
853
855
|
* @param[in] config Configuration parameters for imports.
|
|
854
856
|
* @return Outcome descriptor explicitly convertible to boolean.
|
|
855
857
|
*/
|
|
856
|
-
template <typename input_callback_at>
|
|
857
|
-
serialization_result_t load_from_stream(input_callback_at&& input,
|
|
858
|
+
template <typename input_callback_at, typename progress_at = dummy_progress_t>
|
|
859
|
+
serialization_result_t load_from_stream(input_callback_at&& input, //
|
|
860
|
+
serialization_config_t config = {}, //
|
|
861
|
+
progress_at&& progress = {}) {
|
|
858
862
|
|
|
859
863
|
// Discard all previous memory allocations of `vectors_tape_allocator_`
|
|
860
864
|
reset();
|
|
@@ -915,7 +919,7 @@ class index_dense_gt {
|
|
|
915
919
|
}
|
|
916
920
|
|
|
917
921
|
// Pull the actual proximity graph
|
|
918
|
-
result = typed_->load_from_stream(std::forward<input_callback_at>(input));
|
|
922
|
+
result = typed_->load_from_stream(std::forward<input_callback_at>(input), std::forward<progress_at>(progress));
|
|
919
923
|
if (!result)
|
|
920
924
|
return result;
|
|
921
925
|
if (typed_->size() != static_cast<std::size_t>(matrix_rows))
|
|
@@ -931,7 +935,10 @@ class index_dense_gt {
|
|
|
931
935
|
* @param[in] config Configuration parameters for imports.
|
|
932
936
|
* @return Outcome descriptor explicitly convertible to boolean.
|
|
933
937
|
*/
|
|
934
|
-
|
|
938
|
+
template <typename progress_at = dummy_progress_t>
|
|
939
|
+
serialization_result_t view(memory_mapped_file_t file, //
|
|
940
|
+
std::size_t offset = 0, serialization_config_t config = {}, //
|
|
941
|
+
progress_at&& progress = {}) {
|
|
935
942
|
|
|
936
943
|
// Discard all previous memory allocations of `vectors_tape_allocator_`
|
|
937
944
|
reset();
|
|
@@ -997,7 +1004,7 @@ class index_dense_gt {
|
|
|
997
1004
|
}
|
|
998
1005
|
|
|
999
1006
|
// Pull the actual proximity graph
|
|
1000
|
-
result = typed_->view(std::move(file), offset);
|
|
1007
|
+
result = typed_->view(std::move(file), offset, std::forward<progress_at>(progress));
|
|
1001
1008
|
if (!result)
|
|
1002
1009
|
return result;
|
|
1003
1010
|
if (typed_->size() != static_cast<std::size_t>(matrix_rows))
|
|
@@ -1019,7 +1026,9 @@ class index_dense_gt {
|
|
|
1019
1026
|
* @param[in] config Configuration parameters for exports.
|
|
1020
1027
|
* @return Outcome descriptor explicitly convertible to boolean.
|
|
1021
1028
|
*/
|
|
1022
|
-
|
|
1029
|
+
template <typename progress_at = dummy_progress_t>
|
|
1030
|
+
serialization_result_t save(output_file_t file, serialization_config_t config = {},
|
|
1031
|
+
progress_at&& progress = {}) const {
|
|
1023
1032
|
|
|
1024
1033
|
serialization_result_t io_result = file.open_if_not();
|
|
1025
1034
|
if (!io_result)
|
|
@@ -1030,7 +1039,7 @@ class index_dense_gt {
|
|
|
1030
1039
|
io_result = file.write(buffer, length);
|
|
1031
1040
|
return !!io_result;
|
|
1032
1041
|
},
|
|
1033
|
-
config);
|
|
1042
|
+
config, std::forward<progress_at>(progress));
|
|
1034
1043
|
|
|
1035
1044
|
if (!stream_result)
|
|
1036
1045
|
return stream_result;
|
|
@@ -1041,8 +1050,11 @@ class index_dense_gt {
|
|
|
1041
1050
|
* @brief Memory-maps the serialized binary index representation from disk,
|
|
1042
1051
|
* @b without copying data into RAM, and fetching it on-demand.
|
|
1043
1052
|
*/
|
|
1044
|
-
|
|
1045
|
-
|
|
1053
|
+
template <typename progress_at = dummy_progress_t>
|
|
1054
|
+
serialization_result_t save(memory_mapped_file_t file, //
|
|
1055
|
+
std::size_t offset = 0, //
|
|
1056
|
+
serialization_config_t config = {}, //
|
|
1057
|
+
progress_at&& progress = {}) const {
|
|
1046
1058
|
|
|
1047
1059
|
serialization_result_t io_result = file.open_if_not();
|
|
1048
1060
|
if (!io_result)
|
|
@@ -1056,7 +1068,7 @@ class index_dense_gt {
|
|
|
1056
1068
|
offset += length;
|
|
1057
1069
|
return true;
|
|
1058
1070
|
},
|
|
1059
|
-
config);
|
|
1071
|
+
config, std::forward<progress_at>(progress));
|
|
1060
1072
|
|
|
1061
1073
|
return stream_result;
|
|
1062
1074
|
}
|
|
@@ -1067,7 +1079,8 @@ class index_dense_gt {
|
|
|
1067
1079
|
* @param[in] config Configuration parameters for imports.
|
|
1068
1080
|
* @return Outcome descriptor explicitly convertible to boolean.
|
|
1069
1081
|
*/
|
|
1070
|
-
|
|
1082
|
+
template <typename progress_at = dummy_progress_t>
|
|
1083
|
+
serialization_result_t load(input_file_t file, serialization_config_t config = {}, progress_at&& progress = {}) {
|
|
1071
1084
|
|
|
1072
1085
|
serialization_result_t io_result = file.open_if_not();
|
|
1073
1086
|
if (!io_result)
|
|
@@ -1078,7 +1091,7 @@ class index_dense_gt {
|
|
|
1078
1091
|
io_result = file.read(buffer, length);
|
|
1079
1092
|
return !!io_result;
|
|
1080
1093
|
},
|
|
1081
|
-
config);
|
|
1094
|
+
config, std::forward<progress_at>(progress));
|
|
1082
1095
|
|
|
1083
1096
|
if (!stream_result)
|
|
1084
1097
|
return stream_result;
|
|
@@ -1089,7 +1102,11 @@ class index_dense_gt {
|
|
|
1089
1102
|
* @brief Memory-maps the serialized binary index representation from disk,
|
|
1090
1103
|
* @b without copying data into RAM, and fetching it on-demand.
|
|
1091
1104
|
*/
|
|
1092
|
-
|
|
1105
|
+
template <typename progress_at = dummy_progress_t>
|
|
1106
|
+
serialization_result_t load(memory_mapped_file_t file, //
|
|
1107
|
+
std::size_t offset = 0, //
|
|
1108
|
+
serialization_config_t config = {}, //
|
|
1109
|
+
progress_at&& progress = {}) {
|
|
1093
1110
|
|
|
1094
1111
|
serialization_result_t io_result = file.open_if_not();
|
|
1095
1112
|
if (!io_result)
|
|
@@ -1103,17 +1120,23 @@ class index_dense_gt {
|
|
|
1103
1120
|
offset += length;
|
|
1104
1121
|
return true;
|
|
1105
1122
|
},
|
|
1106
|
-
config);
|
|
1123
|
+
config, std::forward<progress_at>(progress));
|
|
1107
1124
|
|
|
1108
1125
|
return stream_result;
|
|
1109
1126
|
}
|
|
1110
1127
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1128
|
+
template <typename progress_at = dummy_progress_t>
|
|
1129
|
+
serialization_result_t save(char const* file_path, //
|
|
1130
|
+
serialization_config_t config = {}, //
|
|
1131
|
+
progress_at&& progress = {}) const {
|
|
1132
|
+
return save(output_file_t(file_path), config, std::forward<progress_at>(progress));
|
|
1113
1133
|
}
|
|
1114
1134
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1135
|
+
template <typename progress_at = dummy_progress_t>
|
|
1136
|
+
serialization_result_t load(char const* file_path, //
|
|
1137
|
+
serialization_config_t config = {}, //
|
|
1138
|
+
progress_at&& progress = {}) {
|
|
1139
|
+
return load(input_file_t(file_path), config, std::forward<progress_at>(progress));
|
|
1117
1140
|
}
|
|
1118
1141
|
|
|
1119
1142
|
/**
|
|
@@ -217,19 +217,47 @@ inline bool hardware_supports(isa_kind_t isa_kind) noexcept {
|
|
|
217
217
|
#endif
|
|
218
218
|
|
|
219
219
|
// When compiling with GCC, one may use the "built-ins", including ones
|
|
220
|
-
// designed for CPU capability detection.
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
// designed for CPU capability detection, but they are not fully compatible with Clang.
|
|
221
|
+
// So instead I've decided to add a pinch of inline assembly.
|
|
222
|
+
#if defined(USEARCH_DEFINED_X86)
|
|
223
|
+
auto check_cpu_feature = [](std::uint64_t feature_mask, int function_id, int register_id) -> bool {
|
|
224
|
+
std::uint32_t eax, ebx, ecx, edx;
|
|
225
|
+
// Execute CPUID instruction and store results in eax, ebx, ecx, edx
|
|
226
|
+
// Setting %ecx to 0 as well
|
|
227
|
+
__asm__ volatile("cpuid" : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx) : "a"(function_id), "c"(0));
|
|
228
|
+
std::uint32_t register_value;
|
|
229
|
+
switch (register_id) {
|
|
230
|
+
case 0: register_value = eax; break;
|
|
231
|
+
case 1: register_value = ebx; break;
|
|
232
|
+
case 2: register_value = ecx; break;
|
|
233
|
+
case 3: register_value = edx; break;
|
|
234
|
+
default: return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return (register_value & feature_mask) != 0;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Check for AVX2 (Function ID 7, EBX register)
|
|
241
|
+
// https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L148
|
|
242
|
+
bool supports_avx2 = check_cpu_feature(1 << 5, 7, 1);
|
|
243
|
+
|
|
244
|
+
// Check for F16C (Function ID 1, ECX register)
|
|
245
|
+
// https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L107
|
|
246
|
+
bool supports_f16c = check_cpu_feature(1 << 29, 1, 2);
|
|
247
|
+
|
|
248
|
+
// Check for AVX512F (Function ID 7, EBX register)
|
|
249
|
+
// https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L155
|
|
250
|
+
bool supports_avx512f = check_cpu_feature(1 << 16, 7, 1);
|
|
251
|
+
|
|
252
|
+
// Check for AVX512FP16 (Function ID 7, EDX register)
|
|
253
|
+
// https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L198C9-L198C23
|
|
254
|
+
bool supports_avx512fp16 = check_cpu_feature(1 << 23, 7, 3);
|
|
255
|
+
|
|
223
256
|
switch (isa_kind) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
case isa_kind_t::
|
|
227
|
-
|
|
228
|
-
#if __GNUC__ > 12 || (__GNUC__ == 12 && __GNUC_MINOR__ >= 0)
|
|
229
|
-
case isa_kind_t::avx512f16_k: return __builtin_cpu_supports("avx512f") && __builtin_cpu_supports("avx512fp16");
|
|
230
|
-
#endif
|
|
231
|
-
case isa_kind_t::avx2_k: return __builtin_cpu_supports("avx2");
|
|
232
|
-
case isa_kind_t::avx512_k: return __builtin_cpu_supports("avx512f");
|
|
257
|
+
case isa_kind_t::avx2f16_k: return supports_avx2 && supports_f16c;
|
|
258
|
+
case isa_kind_t::avx512f16_k: return supports_avx512fp16 && supports_avx512f;
|
|
259
|
+
case isa_kind_t::avx2_k: return supports_avx2;
|
|
260
|
+
case isa_kind_t::avx512_k: return supports_avx512f;
|
|
233
261
|
default: return false;
|
|
234
262
|
}
|
|
235
263
|
#endif
|
|
@@ -1426,6 +1454,21 @@ class metric_punned_t {
|
|
|
1426
1454
|
return {to_stl_<metric_cos_gt<f16_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::f16_k, isa_kind_t::auto_k};
|
|
1427
1455
|
}
|
|
1428
1456
|
|
|
1457
|
+
static metric_punned_t ip_metric_f16_(std::size_t bytes_per_vector) {
|
|
1458
|
+
#if USEARCH_USE_SIMSIMD
|
|
1459
|
+
#if SIMSIMD_TARGET_X86_AVX512
|
|
1460
|
+
if (hardware_supports(isa_kind_t::avx512f16_k) && bytes_per_vector % 32 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_dot_f16x16_avx512(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f16_k, isa_kind_t::avx512f16_k};
|
|
1461
|
+
#endif
|
|
1462
|
+
#if SIMSIMD_TARGET_X86_AVX2
|
|
1463
|
+
if (hardware_supports(isa_kind_t::avx2f16_k) && bytes_per_vector % 16 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_dot_f16x8_avx2(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f16_k, isa_kind_t::avx2f16_k};
|
|
1464
|
+
#endif
|
|
1465
|
+
#if SIMSIMD_TARGET_ARM_NEON
|
|
1466
|
+
if (hardware_supports(isa_kind_t::neon_k) && bytes_per_vector % 8 == 0) return {pun_stl_<simsimd_f16_t>([=](simsimd_f16_t const* a, simsimd_f16_t const* b) { return simsimd_dot_f16x4_neon(a, b, bytes_per_vector / 2); }), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f16_k, isa_kind_t::neon_k};
|
|
1467
|
+
#endif
|
|
1468
|
+
#endif
|
|
1469
|
+
return {to_stl_<metric_cos_gt<f16_t, f32_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f16_k, isa_kind_t::auto_k};
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1429
1472
|
static metric_punned_t l2sq_metric_f16_(std::size_t bytes_per_vector) {
|
|
1430
1473
|
#if USEARCH_USE_SIMSIMD
|
|
1431
1474
|
#if SIMSIMD_TARGET_X86_AVX512
|
|
@@ -1446,6 +1489,9 @@ class metric_punned_t {
|
|
|
1446
1489
|
#if SIMSIMD_TARGET_ARM_NEON
|
|
1447
1490
|
if (hardware_supports(isa_kind_t::neon_k) && bytes_per_vector % 16 == 0) return {pun_stl_<int8_t>([=](int8_t const* a, int8_t const* b) { return simsimd_cos_i8x16_neon(a, b, bytes_per_vector); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::i8_k, isa_kind_t::neon_k};
|
|
1448
1491
|
#endif
|
|
1492
|
+
#if SIMSIMD_TARGET_X86_AVX2
|
|
1493
|
+
if (hardware_supports(isa_kind_t::avx2_k) && bytes_per_vector % 32 == 0) return {pun_stl_<int8_t>([=](int8_t const* a, int8_t const* b) { return simsimd_cos_i8x32_avx2(a, b, bytes_per_vector); }), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::i8_k, isa_kind_t::avx2_k};
|
|
1494
|
+
#endif
|
|
1449
1495
|
#endif
|
|
1450
1496
|
return {to_stl_<cos_i8_t>(bytes_per_vector), bytes_per_vector, metric_kind_t::cos_k, scalar_kind_t::i8_k, isa_kind_t::auto_k};
|
|
1451
1497
|
}
|
|
@@ -1453,7 +1499,7 @@ class metric_punned_t {
|
|
|
1453
1499
|
static metric_punned_t ip_metric_(std::size_t bytes_per_vector, scalar_kind_t scalar_kind) {
|
|
1454
1500
|
switch (scalar_kind) { // The two most common numeric types for the most common metric have optimized versions
|
|
1455
1501
|
case scalar_kind_t::f32_k: return ip_metric_f32_(bytes_per_vector);
|
|
1456
|
-
case scalar_kind_t::f16_k: return
|
|
1502
|
+
case scalar_kind_t::f16_k: return ip_metric_f16_(bytes_per_vector);
|
|
1457
1503
|
case scalar_kind_t::i8_k: return cos_metric_i8_(bytes_per_vector);
|
|
1458
1504
|
case scalar_kind_t::f64_k: return {to_stl_<metric_ip_gt<f64_t>>(bytes_per_vector), bytes_per_vector, metric_kind_t::ip_k, scalar_kind_t::f64_k, isa_kind_t::auto_k};
|
|
1459
1505
|
default: return {};
|