Skip to content

Commit f220975

Browse files
committed
Added clamping and unified stream/non-stream modules. Several other improvements and issues fixed.
1 parent ff8c96f commit f220975

File tree

10 files changed

+174
-240
lines changed

10 files changed

+174
-240
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ packages/node/test-platform-packages/
4343

4444
# System
4545
.DS_Store
46-
Thumbs.db
46+
Thumbs.db
47+
CLAUDE.md

API.md

Lines changed: 32 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ INSERT INTO compressed_vectors(embedding) VALUES(vector_as_u8(X'010203'));
219219

220220
---
221221

222-
## 🔍 `vector_full_scan(table, column, vector, k)`
222+
## 🔍 `vector_full_scan(table, column, vector [, k])`
223223

224224
**Returns:** `Virtual Table (rowid, distance)`
225225

@@ -232,18 +232,38 @@ Since this interface only returns rowid and distance, if you need to access addi
232232
* `table` (TEXT): Name of the target table.
233233
* `column` (TEXT): Column containing vectors.
234234
* `vector` (BLOB or JSON): The query vector.
235-
* `k` (INTEGER): Number of nearest neighbors to return.
235+
* `k` (INTEGER, optional): Number of nearest neighbors to return. When provided, the module collects the top-k results sorted by distance. When omitted, the module operates in **streaming mode** — rows are returned progressively as they are scanned, enabling standard SQL clauses such as `WHERE` and `LIMIT` to control filtering and result count.
236236

237-
**Example:**
237+
**Examples:**
238238

239239
```sql
240+
-- Top-k mode: return the 5 nearest neighbors, sorted by distance
240241
SELECT rowid, distance
241242
FROM vector_full_scan('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]'), 5);
242243
```
243244

245+
```sql
246+
-- Streaming mode: progressively scan all rows, apply SQL filters
247+
SELECT rowid, distance
248+
FROM vector_full_scan('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]'))
249+
LIMIT 5;
250+
```
251+
252+
```sql
253+
-- Streaming mode with JOIN and filtering
254+
SELECT
255+
v.rowid,
256+
row_number() OVER (ORDER BY v.distance) AS rank_number,
257+
v.distance
258+
FROM vector_full_scan('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]')) AS v
259+
JOIN documents ON documents.rowid = v.rowid
260+
WHERE documents.category = 'science'
261+
LIMIT 10;
262+
```
263+
244264
---
245265

246-
## `vector_quantize_scan(table, column, vector, k)`
266+
## `vector_quantize_scan(table, column, vector [, k])`
247267

248268
**Returns:** `Virtual Table (rowid, distance)`
249269

@@ -257,81 +277,31 @@ You **must run `vector_quantize()`** before using `vector_quantize_scan()` and w
257277
* `table` (TEXT): Name of the target table.
258278
* `column` (TEXT): Column containing vectors.
259279
* `vector` (BLOB or JSON): The query vector.
260-
* `k` (INTEGER): Number of nearest neighbors to return.
280+
* `k` (INTEGER, optional): Number of nearest neighbors to return. When provided, the module collects the top-k results sorted by distance. When omitted, the module operates in **streaming mode** — rows are returned progressively, enabling standard SQL clauses such as `WHERE` and `LIMIT`.
261281

262282
**Performance Highlights:**
263283

264284
* Handles **1M vectors** of dimension 768 in a few milliseconds.
265285
* Uses **<50MB** of RAM.
266286
* Achieves **>0.95 recall**.
267287

268-
**Example:**
269-
270-
```sql
271-
SELECT rowid, distance
272-
FROM vector_quantize_scan('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]'), 10);
273-
```
274-
275-
---
276-
277-
## 🔁 Streaming Interfaces
278-
279-
### `vector_full_scan_stream` and `vector_quantize_scan_stream`
280-
281-
**Returns:** `Virtual Table (rowid, distance)`
282-
283-
**Description:**
284-
These streaming interfaces provide the same functionality as `vector_full_scan` and `vector_quantize_scan`, respectively, but are designed for incremental or filtered processing of results.
285-
286-
Unlike their non-streaming counterparts, these functions **omit the fourth parameter (`k`)** and allow you to use standard SQL clauses such as `WHERE` and `LIMIT` to control filtering and result count. Since this interface only returns rowid and distance, if you need to access additional columns from the original table, you must use a SELF JOIN.
287-
288-
This makes them ideal for combining vector search with additional query conditions or progressive result consumption in streaming applications.
289-
290-
**Parameters:**
291-
292-
* `table` (TEXT): Name of the target table.
293-
* `column` (TEXT): Column containing vectors.
294-
* `vector` (BLOB or JSON): The query vector.
295-
296-
**Key Differences from Non-Streaming Variants:**
297-
298-
| Function | Equivalent To | Requires `k` | Supports `WHERE` | Supports `LIMIT` |
299-
| ----------------------------- | ---------------------- | ------------ | ---------------- | ---------------- |
300-
| `vector_full_scan_stream` | `vector_full_scan` ||||
301-
| `vector_quantize_scan_stream` | `vector_quantize_scan` ||||
302-
303288
**Examples:**
304289

305290
```sql
306-
-- Perform a filtered full scan
291+
-- Top-k mode: return the 10 nearest neighbors, sorted by distance
307292
SELECT rowid, distance
308-
FROM vector_full_scan_stream('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]'))
309-
LIMIT 5;
293+
FROM vector_quantize_scan('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]'), 10);
310294
```
311295

312296
```sql
313-
-- Perform a filtered approximate scan using quantized data
297+
-- Streaming mode: progressively scan using quantized data
314298
SELECT rowid, distance
315-
FROM vector_quantize_scan_stream('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]'))
316-
LIMIT 10;
317-
```
318-
319-
**Accessing Additional Columns:**
320-
321-
```sql
322-
-- Perform a filtered full scan with additional columns
323-
SELECT
324-
v.rowid,
325-
row_number() OVER (ORDER BY v.distance) AS rank_number,
326-
v.distance
327-
FROM vector_full_scan_stream('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]')) AS v
328-
JOIN documents ON documents.rowid = v.rowid
329-
WHERE documents.category = 'science'
299+
FROM vector_quantize_scan('documents', 'embedding', vector_as_f32('[0.1, 0.2, 0.3]'))
330300
LIMIT 10;
331301
```
332302

333303
**Usage Notes:**
334304

335-
* These interfaces return rows progressively and can efficiently combine vector similarity with SQL-level filters.
336-
* The `LIMIT` clause can be used to control how many rows are read or returned.
337-
* The query planner integrates the streaming virtual table into the overall SQL execution plan, enabling hybrid filtering and ranking operations.
305+
* In **top-k mode** (with `k`), results are sorted by distance. The query planner knows the output is pre-sorted, so no additional `ORDER BY` is needed.
306+
* In **streaming mode** (without `k`), rows are returned in scan order. Use `ORDER BY distance` and `LIMIT` as needed.
307+
* Streaming mode is ideal for combining vector similarity with additional SQL-level filters or progressive result consumption.

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ INSERT INTO images (embedding, label) VALUES (vector_as_f32('[0.3, 1.0, 0.9, 3.2
7474

7575
-- Initialize the vector. By default, the distance function is L2.
7676
-- To use a different metric, specify one of the following options:
77-
-- distance=L1, distance=COSINE, distance=DOT, or distance=SQUARED_L2.
77+
-- distance=L1, distance=COSINE, distance=DOT, distance=SQUARED_L2, or distance=HAMMING.
7878
SELECT vector_init('images', 'embedding', 'type=FLOAT32,dimension=384');
7979

8080
-- Quantize vector
@@ -87,6 +87,13 @@ SELECT vector_quantize_preload('images', 'embedding');
8787
SELECT e.id, v.distance FROM images AS e
8888
JOIN vector_quantize_scan('images', 'embedding', ?, 20) AS v
8989
ON e.id = v.rowid;
90+
91+
-- Streaming mode: omit k to get rows progressively, use SQL to filter and limit
92+
SELECT e.id, v.distance FROM images AS e
93+
JOIN vector_quantize_scan('images', 'embedding', ?) AS v
94+
ON e.id = v.rowid
95+
WHERE e.label = 'cat'
96+
LIMIT 10;
9097
```
9198

9299
### Swift Package
@@ -117,7 +124,7 @@ sqlite3_close(db)
117124
Add the [following](https://central.sonatype.com/artifact/ai.sqlite/vector) to your Gradle dependencies:
118125

119126
```gradle
120-
implementation 'ai.sqlite:vector:0.9.34'
127+
implementation 'ai.sqlite:vector:0.9.80'
121128
```
122129

123130
Here's an example of how to use the package:

src/distance-avx2.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
#include <math.h>
1515

1616
extern distance_function_t dispatch_distance_table[VECTOR_DISTANCE_MAX][VECTOR_TYPE_MAX];
17-
extern char *distance_backend_name;
17+
extern const char *distance_backend_name;
1818

1919
#define _mm256_abs_ps(x) _mm256_andnot_ps(_mm256_set1_ps(-0.0f), (x))
2020

@@ -158,6 +158,8 @@ float float32_distance_cosine_avx2 (const void *a, const void *b, int n) {
158158
if (norm_a == 0.0f || norm_b == 0.0f) return 1.0f;
159159

160160
float cosine_similarity = dot / (norm_a * norm_b);
161+
if (cosine_similarity > 1.0f) cosine_similarity = 1.0f;
162+
if (cosine_similarity < -1.0f) cosine_similarity = -1.0f;
161163
return 1.0f - cosine_similarity;
162164
}
163165

@@ -749,6 +751,8 @@ float uint8_distance_cosine_avx2 (const void *a, const void *b, int n) {
749751
if (norm_a == 0.0f || norm_b == 0.0f) return 1.0f;
750752

751753
float cosine_similarity = dot / (norm_a * norm_b);
754+
if (cosine_similarity > 1.0f) cosine_similarity = 1.0f;
755+
if (cosine_similarity < -1.0f) cosine_similarity = -1.0f;
752756
return 1.0f - cosine_similarity;
753757
}
754758

@@ -946,6 +950,8 @@ float int8_distance_cosine_avx2 (const void *a, const void *b, int n) {
946950
if (norm_a == 0.0f || norm_b == 0.0f) return 1.0f;
947951

948952
float cosine_similarity = dot / (norm_a * norm_b);
953+
if (cosine_similarity > 1.0f) cosine_similarity = 1.0f;
954+
if (cosine_similarity < -1.0f) cosine_similarity = -1.0f;
949955
return 1.0f - cosine_similarity;
950956
}
951957

src/distance-avx512.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#include <math.h>
1616

1717
extern distance_function_t dispatch_distance_table[VECTOR_DISTANCE_MAX][VECTOR_TYPE_MAX];
18-
extern char* distance_backend_name;
18+
extern const char *distance_backend_name;
1919

2020
// Abs for f32 (AVX512F has native abs)
2121
#define _mm512_abs_ps(x) _mm512_abs_ps(x)
@@ -159,6 +159,8 @@ float float32_distance_cosine_avx512(const void* a, const void* b, int n) {
159159
if (norm_a == 0.0f || norm_b == 0.0f) return 1.0f;
160160

161161
float cosine_similarity = dot / (norm_a * norm_b);
162+
if (cosine_similarity > 1.0f) cosine_similarity = 1.0f;
163+
if (cosine_similarity < -1.0f) cosine_similarity = -1.0f;
162164
return 1.0f - cosine_similarity;
163165
}
164166

@@ -712,6 +714,8 @@ float uint8_distance_cosine_avx512(const void* a, const void* b, int n) {
712714
if (norm_a == 0.0f || norm_b == 0.0f) return 1.0f;
713715

714716
float cosine_similarity = dot / (norm_a * norm_b);
717+
if (cosine_similarity > 1.0f) cosine_similarity = 1.0f;
718+
if (cosine_similarity < -1.0f) cosine_similarity = -1.0f;
715719
return 1.0f - cosine_similarity;
716720
}
717721

@@ -843,6 +847,8 @@ float int8_distance_cosine_avx512(const void* a, const void* b, int n) {
843847
if (norm_a == 0.0f || norm_b == 0.0f) return 1.0f;
844848

845849
float cosine_similarity = dot / (norm_a * norm_b);
850+
if (cosine_similarity > 1.0f) cosine_similarity = 1.0f;
851+
if (cosine_similarity < -1.0f) cosine_similarity = -1.0f;
846852
return 1.0f - cosine_similarity;
847853
}
848854

src/distance-cpu.c

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include "distance-avx2.h"
1919
#include "distance-avx512.h"
2020

21-
char *distance_backend_name = "CPU";
21+
const char *distance_backend_name = "CPU";
2222
distance_function_t dispatch_distance_table[VECTOR_DISTANCE_MAX][VECTOR_TYPE_MAX] = {0};
2323

2424
#define LASSQ_UPDATE(ad_) do { \
@@ -106,8 +106,11 @@ float float32_distance_cosine_cpu (const void *v1, const void *v2, int n) {
106106
if (norm_x == 0.0f || norm_y == 0.0f) {
107107
return 1.0f;
108108
}
109-
110-
return 1.0f - (dot / (sqrtf(norm_x) * sqrtf(norm_y)));
109+
110+
float cosine_similarity = dot / (sqrtf(norm_x) * sqrtf(norm_y));
111+
if (cosine_similarity > 1.0f) cosine_similarity = 1.0f;
112+
if (cosine_similarity < -1.0f) cosine_similarity = -1.0f;
113+
return 1.0f - cosine_similarity;
111114
}
112115

113116
float float32_distance_dot_cpu (const void *v1, const void *v2, int n) {
@@ -250,7 +253,10 @@ float bfloat16_distance_cosine_cpu(const void *v1, const void *v2, int n) {
250253
return 1.0f;
251254
}
252255

253-
return 1.0f - (dot / (sqrtf(norm_x) * sqrtf(norm_y)));
256+
float cosine_similarity = dot / (sqrtf(norm_x) * sqrtf(norm_y));
257+
if (cosine_similarity > 1.0f) cosine_similarity = 1.0f;
258+
if (cosine_similarity < -1.0f) cosine_similarity = -1.0f;
259+
return 1.0f - cosine_similarity;
254260
}
255261

256262
float bfloat16_distance_dot_cpu (const void *v1, const void *v2, int n) {
@@ -536,6 +542,8 @@ float uint8_distance_cosine_cpu (const void *v1, const void *v2, int n) {
536542
}
537543

538544
float cosine_similarity = dot / (sqrtf((float)norm_a2) * sqrtf((float)norm_b2));
545+
if (cosine_similarity > 1.0f) cosine_similarity = 1.0f;
546+
if (cosine_similarity < -1.0f) cosine_similarity = -1.0f;
539547
return 1.0f - cosine_similarity;
540548
}
541549

@@ -648,6 +656,8 @@ float int8_distance_cosine_cpu (const void *v1, const void *v2, int n) {
648656
}
649657

650658
float cosine_similarity = dot / (sqrtf((float)norm_a2) * sqrtf((float)norm_b2));
659+
if (cosine_similarity > 1.0f) cosine_similarity = 1.0f;
660+
if (cosine_similarity < -1.0f) cosine_similarity = -1.0f;
651661
return 1.0f - cosine_similarity;
652662
}
653663

@@ -716,8 +726,9 @@ float bit1_distance_hamming_cpu (const void *v1, const void *v2, int n) {
716726

717727
// process 8 bytes at a time
718728
for (; i + 8 <= n; i += 8) {
719-
uint64_t xa = *(const uint64_t *)(a + i);
720-
uint64_t xb = *(const uint64_t *)(b + i);
729+
uint64_t xa, xb;
730+
memcpy(&xa, a + i, sizeof(uint64_t));
731+
memcpy(&xb, b + i, sizeof(uint64_t));
721732
distance += popcount64(xa ^ xb);
722733
}
723734

0 commit comments

Comments
 (0)