Skip to content

Commit 0f347d7

Browse files
committed
feat: dotpod support for neon builds and test updates
1 parent 79b97cd commit 0f347d7

11 files changed

Lines changed: 383 additions & 158 deletions

File tree

benchmarks/distance_benchmark.cpp

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include <random>
55
#include <vector>
66
#include "../include/sqlite-vec-cpp/distances/cosine.hpp"
7+
#include "../include/sqlite-vec-cpp/distances/hamming.hpp"
8+
#include "../include/sqlite-vec-cpp/distances/inner_product.hpp"
79
#include "../include/sqlite-vec-cpp/distances/l1.hpp"
810
#include "../include/sqlite-vec-cpp/distances/l2.hpp"
911
#include <benchmark/benchmark.h>
@@ -38,7 +40,7 @@ static void BM_L2_Float_128(benchmark::State& state) {
3840

3941
for (auto _ : state) {
4042
float result =
41-
distance_l2_sqeuclidean(std::span<const float>(a), std::span<const float>(b));
43+
l2_distance(std::span<const float>(a), std::span<const float>(b));
4244
benchmark::DoNotOptimize(result);
4345
}
4446
state.SetItemsProcessed(state.iterations() * 128);
@@ -51,7 +53,7 @@ static void BM_L2_Float_256(benchmark::State& state) {
5153

5254
for (auto _ : state) {
5355
float result =
54-
distance_l2_sqeuclidean(std::span<const float>(a), std::span<const float>(b));
56+
l2_distance(std::span<const float>(a), std::span<const float>(b));
5557
benchmark::DoNotOptimize(result);
5658
}
5759
state.SetItemsProcessed(state.iterations() * 256);
@@ -64,7 +66,7 @@ static void BM_L2_Float_512(benchmark::State& state) {
6466

6567
for (auto _ : state) {
6668
float result =
67-
distance_l2_sqeuclidean(std::span<const float>(a), std::span<const float>(b));
69+
l2_distance(std::span<const float>(a), std::span<const float>(b));
6870
benchmark::DoNotOptimize(result);
6971
}
7072
state.SetItemsProcessed(state.iterations() * 512);
@@ -77,7 +79,7 @@ static void BM_L2_Float_1536(benchmark::State& state) {
7779

7880
for (auto _ : state) {
7981
float result =
80-
distance_l2_sqeuclidean(std::span<const float>(a), std::span<const float>(b));
82+
l2_distance(std::span<const float>(a), std::span<const float>(b));
8183
benchmark::DoNotOptimize(result);
8284
}
8385
state.SetItemsProcessed(state.iterations() * 1536);
@@ -90,7 +92,7 @@ static void BM_L2_Int8_128(benchmark::State& state) {
9092

9193
for (auto _ : state) {
9294
float result =
93-
distance_l2_sqeuclidean(std::span<const int8_t>(a), std::span<const int8_t>(b));
95+
l2_distance(std::span<const int8_t>(a), std::span<const int8_t>(b));
9496
benchmark::DoNotOptimize(result);
9597
}
9698
state.SetItemsProcessed(state.iterations() * 128);
@@ -106,7 +108,7 @@ static void BM_L1_Float_128(benchmark::State& state) {
106108
auto b = generate_random_vector<float>(128);
107109

108110
for (auto _ : state) {
109-
float result = distance_l1(std::span<const float>(a), std::span<const float>(b));
111+
auto result = l1_distance(std::span<const float>(a), std::span<const float>(b));
110112
benchmark::DoNotOptimize(result);
111113
}
112114
state.SetItemsProcessed(state.iterations() * 128);
@@ -118,7 +120,7 @@ static void BM_L1_Float_1536(benchmark::State& state) {
118120
auto b = generate_random_vector<float>(1536);
119121

120122
for (auto _ : state) {
121-
float result = distance_l1(std::span<const float>(a), std::span<const float>(b));
123+
auto result = l1_distance(std::span<const float>(a), std::span<const float>(b));
122124
benchmark::DoNotOptimize(result);
123125
}
124126
state.SetItemsProcessed(state.iterations() * 1536);
@@ -134,7 +136,7 @@ static void BM_Cosine_Float_128(benchmark::State& state) {
134136
auto b = generate_random_vector<float>(128);
135137

136138
for (auto _ : state) {
137-
float result = distance_cosine(std::span<const float>(a), std::span<const float>(b));
139+
float result = cosine_distance(std::span<const float>(a), std::span<const float>(b));
138140
benchmark::DoNotOptimize(result);
139141
}
140142
state.SetItemsProcessed(state.iterations() * 128);
@@ -146,7 +148,7 @@ static void BM_Cosine_Float_1536(benchmark::State& state) {
146148
auto b = generate_random_vector<float>(1536);
147149

148150
for (auto _ : state) {
149-
float result = distance_cosine(std::span<const float>(a), std::span<const float>(b));
151+
float result = cosine_distance(std::span<const float>(a), std::span<const float>(b));
150152
benchmark::DoNotOptimize(result);
151153
}
152154
state.SetItemsProcessed(state.iterations() * 1536);
@@ -162,8 +164,8 @@ static void BM_Hamming_128(benchmark::State& state) {
162164
auto b = generate_random_vector<unsigned char>(128, (unsigned char)0, (unsigned char)255);
163165

164166
for (auto _ : state) {
165-
int result =
166-
distance_hamming(std::span<const unsigned char>(a), std::span<const unsigned char>(b));
167+
float result =
168+
hamming_distance_u8(std::span<const unsigned char>(a), std::span<const unsigned char>(b));
167169
benchmark::DoNotOptimize(result);
168170
}
169171
state.SetItemsProcessed(state.iterations() * 128);
@@ -175,8 +177,8 @@ static void BM_Hamming_1536(benchmark::State& state) {
175177
auto b = generate_random_vector<unsigned char>(1536, (unsigned char)0, (unsigned char)255);
176178

177179
for (auto _ : state) {
178-
int result =
179-
distance_hamming(std::span<const unsigned char>(a), std::span<const unsigned char>(b));
180+
float result =
181+
hamming_distance_u8(std::span<const unsigned char>(a), std::span<const unsigned char>(b));
180182
benchmark::DoNotOptimize(result);
181183
}
182184
state.SetItemsProcessed(state.iterations() * 1536);
@@ -196,7 +198,7 @@ static void BM_L2_Float_Batch_1000x128(benchmark::State& state) {
196198

197199
for (auto _ : state) {
198200
for (const auto& query : queries) {
199-
float result = distance_l2_sqeuclidean(std::span<const float>(query),
201+
float result = l2_distance(std::span<const float>(query),
200202
std::span<const float>(target));
201203
benchmark::DoNotOptimize(result);
202204
}
@@ -253,6 +255,82 @@ static void BM_Int8_Cosine_NEON_DotProd_384(benchmark::State& state) {
253255
}
254256
BENCHMARK(BM_Int8_Cosine_NEON_DotProd_384);
255257

258+
static void BM_Int8_InnerProduct_NEON_DotProd_384(benchmark::State& state) {
259+
auto a = generate_random_vector<int8_t>(384, int8_t{-127}, int8_t{127});
260+
auto b = generate_random_vector<int8_t>(384, int8_t{-127}, int8_t{127});
261+
262+
for (auto _ : state) {
263+
float result = simd::inner_product_int8_neon_dotprod(std::span<const int8_t>(a),
264+
std::span<const int8_t>(b));
265+
benchmark::DoNotOptimize(result);
266+
}
267+
state.SetItemsProcessed(state.iterations() * 384);
268+
}
269+
BENCHMARK(BM_Int8_InnerProduct_NEON_DotProd_384);
270+
256271
#endif // SQLITE_VEC_ENABLE_NEON && __ARM_FEATURE_DOTPROD
257272

273+
// ============================================================================
274+
// NEON int8 Benchmarks (widening, no DotProd required)
275+
// ============================================================================
276+
277+
#if defined(SQLITE_VEC_ENABLE_NEON)
278+
#ifndef __ARM_FEATURE_DOTPROD
279+
#include "../include/sqlite-vec-cpp/simd/neon.hpp"
280+
#endif
281+
282+
static void BM_Int8_InnerProduct_NEON_384(benchmark::State& state) {
283+
auto a = generate_random_vector<int8_t>(384, int8_t{-127}, int8_t{127});
284+
auto b = generate_random_vector<int8_t>(384, int8_t{-127}, int8_t{127});
285+
286+
for (auto _ : state) {
287+
float result = simd::inner_product_int8_neon(std::span<const int8_t>(a),
288+
std::span<const int8_t>(b));
289+
benchmark::DoNotOptimize(result);
290+
}
291+
state.SetItemsProcessed(state.iterations() * 384);
292+
}
293+
BENCHMARK(BM_Int8_InnerProduct_NEON_384);
294+
295+
static void BM_Int8_Cosine_NEON_384(benchmark::State& state) {
296+
auto a = generate_random_vector<int8_t>(384, int8_t{-127}, int8_t{127});
297+
auto b = generate_random_vector<int8_t>(384, int8_t{-127}, int8_t{127});
298+
299+
for (auto _ : state) {
300+
float result = simd::cosine_distance_int8_neon(std::span<const int8_t>(a),
301+
std::span<const int8_t>(b));
302+
benchmark::DoNotOptimize(result);
303+
}
304+
state.SetItemsProcessed(state.iterations() * 384);
305+
}
306+
BENCHMARK(BM_Int8_Cosine_NEON_384);
307+
308+
static void BM_Int8_Cosine_Scalar_384(benchmark::State& state) {
309+
auto a = generate_random_vector<int8_t>(384, int8_t{-127}, int8_t{127});
310+
auto b = generate_random_vector<int8_t>(384, int8_t{-127}, int8_t{127});
311+
312+
for (auto _ : state) {
313+
float result = cosine_distance_int(std::span<const int8_t>(a),
314+
std::span<const int8_t>(b));
315+
benchmark::DoNotOptimize(result);
316+
}
317+
state.SetItemsProcessed(state.iterations() * 384);
318+
}
319+
BENCHMARK(BM_Int8_Cosine_Scalar_384);
320+
321+
static void BM_Int8_InnerProduct_Scalar_384(benchmark::State& state) {
322+
auto a = generate_random_vector<int8_t>(384, int8_t{-127}, int8_t{127});
323+
auto b = generate_random_vector<int8_t>(384, int8_t{-127}, int8_t{127});
324+
325+
for (auto _ : state) {
326+
float result = inner_product_distance_int(std::span<const int8_t>(a),
327+
std::span<const int8_t>(b));
328+
benchmark::DoNotOptimize(result);
329+
}
330+
state.SetItemsProcessed(state.iterations() * 384);
331+
}
332+
BENCHMARK(BM_Int8_InnerProduct_Scalar_384);
333+
334+
#endif // SQLITE_VEC_ENABLE_NEON
335+
258336
BENCHMARK_MAIN();

benchmarks/logs/2026-04-12_post-quantization/hnsw_benchmark.log

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,16 @@ BM_HNSW_Search/10000/768/10/50 268651 ns 268171 ns 2772
3131
BM_HNSW_Search/10000/1536/10/50 489237 ns 489178 ns 1434 QPS=1.42556/s corpus=10k ef=50 k=10
3232
BM_Brute_Force_Search/1000/384/10 22194 ns 22189 ns 31576 QPS=1.42729/s corpus=1k k=10
3333
BM_Brute_Force_Search/10000/384/10 224623 ns 224579 ns 3102 QPS=1.43545/s corpus=10k k=10
34+
BM_HNSW_Recall_Quality/10 26754 ns 26745 ns 24455 ef=10 recall=0
35+
BM_HNSW_Recall_Quality/20 35329 ns 35080 ns 18812 ef=20 recall=40
36+
BM_HNSW_Recall_Quality/50 83299 ns 83288 ns 8749 ef=50 recall=60
37+
BM_HNSW_Recall_Quality/100 150649 ns 150248 ns 4461 ef=100 recall=80
38+
BM_HNSW_Recall_Quality/200 257448 ns 257365 ns 2735 ef=200 recall=100
39+
BM_HNSW_Recall_Quality/500 516091 ns 515566 ns 1395 ef=500 recall=100
40+
BM_HNSW_Batch_Search/10000/384/100/1 19052684 ns 19049437 ns 32 QPS=5.2495k/s corpus=10k queries=100 threads=1
41+
BM_HNSW_Batch_Search/10000/384/100/2 9545320 ns 36121 ns 1000 QPS=2.76847M/s corpus=10k queries=100 threads=2
42+
BM_HNSW_Batch_Search/10000/384/100/4 4725941 ns 51713 ns 1000 QPS=1.93375M/s corpus=10k queries=100 threads=4
43+
BM_HNSW_Batch_Search/10000/384/100/8 2757285 ns 85660 ns 1000 QPS=1.16741M/s corpus=10k queries=100 threads=8
44+
BM_HNSW_Batch_Search/10000/384/10/4 622596 ns 44472 ns 10000 QPS=224.86k/s corpus=10k queries=10 threads=4
45+
BM_HNSW_Batch_Search/10000/384/100/4 4908064 ns 54006 ns 1000 QPS=1.85165M/s corpus=10k queries=100 threads=4
46+
BM_HNSW_Batch_Search/10000/384/1000/4 47129073 ns 84360 ns 100 QPS=11.854M/s corpus=10k queries=1k threads=4

benchmarks/meson.build

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,14 @@ if meson.is_subproject()
1010
endif
1111

1212
if benchmark_dep.found()
13-
# Note: distance_benchmark.cpp needs updating for C++ API
14-
# Commented out temporarily
15-
# distance_benchmark_exe = executable(
16-
# 'distance_benchmark',
17-
# 'distance_benchmark.cpp',
18-
# dependencies: [benchmark_dep],
19-
# include_directories: include_directories('../include'),
20-
# cpp_args: cpp_args_list,
21-
# install: false
22-
# )
13+
distance_benchmark_exe = executable(
14+
'distance_benchmark',
15+
'distance_benchmark.cpp',
16+
dependencies: [benchmark_dep],
17+
include_directories: include_directories('../include'),
18+
cpp_args: cpp_args_list,
19+
install: false
20+
)
2321

2422
batch_distance_benchmark_exe = executable(
2523
'batch_distance_benchmark',
@@ -67,7 +65,7 @@ if benchmark_dep.found()
6765
)
6866

6967
message('Benchmarks enabled:')
70-
# message(' ./distance_benchmark') # TODO: Update for C++ API
68+
message(' ./distance_benchmark')
7169
message(' ./batch_distance_benchmark')
7270
message(' ./rag_pipeline_benchmark')
7371
message(' ./hnsw_benchmark')

include/sqlite-vec-cpp/distances/cosine.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,17 @@ float cosine_distance(std::span<const T> a, std::span<const T> b) {
115115
}
116116
#endif
117117
return cosine_distance_float(a, b);
118+
} else if constexpr (std::is_same_v<T, std::int8_t>) {
119+
#if defined(SQLITE_VEC_ENABLE_NEON) && defined(__ARM_FEATURE_DOTPROD)
120+
if (a.size() >= 16) {
121+
return simd::cosine_distance_int8_neon_dotprod(a, b);
122+
}
123+
#elif defined(SQLITE_VEC_ENABLE_NEON)
124+
if (a.size() >= 16) {
125+
return simd::cosine_distance_int8_neon(a, b);
126+
}
127+
#endif
128+
return cosine_distance_int(a, b);
118129
} else if constexpr (concepts::IntegerElement<T>) {
119130
return cosine_distance_int(a, b);
120131
} else {

include/sqlite-vec-cpp/distances/inner_product.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,17 @@ float inner_product_distance(std::span<const T> a, std::span<const T> b) {
163163
}
164164
#endif
165165
return inner_product_distance_float(a, b);
166+
} else if constexpr (std::is_same_v<T, std::int8_t>) {
167+
#if defined(SQLITE_VEC_ENABLE_NEON) && defined(__ARM_FEATURE_DOTPROD)
168+
if (a.size() >= 16) {
169+
return simd::inner_product_int8_neon_dotprod(a, b);
170+
}
171+
#elif defined(SQLITE_VEC_ENABLE_NEON)
172+
if (a.size() >= 16) {
173+
return simd::inner_product_int8_neon(a, b);
174+
}
175+
#endif
176+
return inner_product_distance_int(a, b);
166177
} else if constexpr (concepts::IntegerElement<T>) {
167178
return inner_product_distance_int(a, b);
168179
} else {

0 commit comments

Comments
 (0)