Merge branch 'main' into pr/rescore

This commit is contained in:
Alex Garcia 2026-03-31 01:12:50 -07:00
commit 45d1375602
8 changed files with 122 additions and 27 deletions

View file

@ -43,10 +43,12 @@ ifndef OMIT_SIMD
CFLAGS += -mcpu=apple-m1 -DSQLITE_VEC_ENABLE_NEON
endif
ifeq ($(shell uname -s),Linux)
ifeq ($(findstring android,$(CC)),)
ifneq ($(filter avx,$(shell grep -o 'avx[^ ]*' /proc/cpuinfo 2>/dev/null | head -1)),)
CFLAGS += -mavx -DSQLITE_VEC_ENABLE_AVX
endif
endif
endif
endif
ifdef USE_BREW_SQLITE

73
TODO.md Normal file
View file

@ -0,0 +1,73 @@
# TODO: `ann` base branch + consolidated benchmarks
## 1. Create `ann` branch with shared code
### 1.1 Branch setup
- [x] `git checkout -B ann origin/main`
- [x] Cherry-pick `624f998` (vec0_distance_full shared distance dispatch)
- [x] Cherry-pick stdint.h fix for test header
- [ ] Pull NEON cosine optimization from ivf-yolo3 into shared code
- Currently only in ivf branch but is general-purpose (benefits all distance calcs)
- Lives in `distance_cosine_float()` — ~57 lines of ARM NEON vectorized cosine
### 1.2 Benchmark infrastructure (`benchmarks-ann/`)
- [x] Seed data pipeline (`seed/Makefile`, `seed/build_base_db.py`)
- [x] Ground truth generator (`ground_truth.py`)
- [x] Results schema (`schema.sql`)
- [x] Benchmark runner with `INDEX_REGISTRY` extension point (`bench.py`)
- Baseline configs (float, int8-rescore, bit-rescore) implemented
- Index branches register their types via `INDEX_REGISTRY` dict
- [x] Makefile with baseline targets
- [x] README
### 1.3 Rebase feature branches onto `ann`
- [x] Rebase `diskann-yolo2` onto `ann` (1 commit: DiskANN implementation)
- [x] Rebase `ivf-yolo3` onto `ann` (1 commit: IVF implementation)
- [x] Rebase `annoy-yolo2` onto `ann` (2 commits: Annoy implementation + schema fix)
- [x] Verify each branch has only its index-specific commits remaining
- [ ] Force-push all 4 branches to origin
---
## 2. Per-branch: register index type in benchmarks
Each index branch should add to `benchmarks-ann/` when rebased onto `ann`:
### 2.1 Register in `bench.py`
Add an `INDEX_REGISTRY` entry. Each entry provides:
- `defaults` — default param values
- `create_table_sql(params)` — CREATE VIRTUAL TABLE with INDEXED BY clause
- `insert_sql(params)` — custom insert SQL, or None for default
- `post_insert_hook(conn, params)` — training/building step, returns time
- `run_query(conn, params, query, k)` — custom query, or None for default MATCH
- `describe(params)` — one-line description for report output
### 2.2 Add configs to `Makefile`
Append index-specific config variables and targets. Example pattern:
```makefile
DISKANN_CONFIGS = \
"diskann-R48-binary:type=diskann,R=48,L=128,quantizer=binary" \
...
ALL_CONFIGS += $(DISKANN_CONFIGS)
bench-diskann: seed
$(BENCH) --subset-size 10000 -k 10 -o runs/diskann $(BASELINES) $(DISKANN_CONFIGS)
...
```
### 2.3 Migrate existing benchmark results/docs
- Move useful results docs (RESULTS.md, etc.) into `benchmarks-ann/results/`
- Delete redundant per-branch benchmark directories once consolidated infra is proven
---
## 3. Future improvements
- [ ] Reporting script (`report.py`) — query results.db, produce markdown comparison tables
- [ ] Profiling targets in Makefile (lift from ivf-yolo3's Instruments/perf wrappers)
- [ ] Pre-computed ground truth integration (use GT DB files instead of on-the-fly brute-force)

View file

@ -1 +1 @@
0.1.8
0.1.9

View file

@ -49,6 +49,7 @@ bench-rescore: seed
$(BENCH) --subset-size 10000 -k 10 -o runs/rescore \
$(RESCORE_CONFIGS)
# --- Standard sizes ---
bench-10k: seed
$(BENCH) --subset-size 10000 -k 10 -o runs/10k $(ALL_CONFIGS)

View file

@ -2553,6 +2553,7 @@ struct Vec0RescoreConfig {
};
#endif
struct VectorColumnDefinition {
char *name;
int name_length;
@ -8899,11 +8900,17 @@ int vec0Update_Delete_ClearMetadata(vec0_vtab *p, int metadata_idx, i64 rowid, i
}
sqlite3_bind_int64(stmt, 1, rowid);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if(rc != SQLITE_DONE) {
rc = SQLITE_ERROR;
goto done;
}
sqlite3_finalize(stmt);
// Fix for https://github.com/asg017/sqlite-vec/issues/274
// sqlite3_step returns SQLITE_DONE (101) on DML success, but the
// `done:` epilogue treats anything other than SQLITE_OK as an error.
// Without this, SQLITE_DONE propagates up to vec0Update_Delete,
// which aborts the DELETE scan and silently drops remaining rows.
rc = SQLITE_OK;
}
break;
}

View file

@ -27,8 +27,8 @@
OrderedDict({
'chunk_id': 1,
'size': 8,
'validity': b'\x06',
'rowids': b'\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
'validity': b'\x02',
'rowids': b'\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
}),
]),
}),
@ -37,7 +37,7 @@
'rows': list([
OrderedDict({
'rowid': 1,
'data': b'\x06',
'data': b'\x02',
}),
]),
}),
@ -46,7 +46,7 @@
'rows': list([
OrderedDict({
'rowid': 1,
'data': b'\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
'data': b'\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
}),
]),
}),
@ -55,7 +55,7 @@
'rows': list([
OrderedDict({
'rowid': 1,
'data': b'\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x99\x99\x99\x99\x99\x01@ffffff\n@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
'data': b'\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x99\x99\x99\x99\x99\x01@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
}),
]),
}),
@ -64,17 +64,13 @@
'rows': list([
OrderedDict({
'rowid': 1,
'data': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00test2\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00123456789012\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
'data': b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00test2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
}),
]),
}),
'v_metadatatext03': OrderedDict({
'sql': 'select * from v_metadatatext03',
'rows': list([
OrderedDict({
'rowid': 3,
'data': '1234567890123',
}),
]),
}),
'v_rowids': OrderedDict({
@ -86,12 +82,6 @@
'chunk_id': 1,
'chunk_offset': 1,
}),
OrderedDict({
'rowid': 3,
'id': None,
'chunk_id': 1,
'chunk_offset': 2,
}),
]),
}),
'v_vector_chunks00': OrderedDict({
@ -99,7 +89,7 @@
'rows': list([
OrderedDict({
'rowid': 1,
'vectors': b'\x00\x00\x00\x00""""3333\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
'vectors': b'\x00\x00\x00\x00""""\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
}),
]),
}),
@ -370,14 +360,6 @@
'f': 2.2,
't': 'test2',
}),
OrderedDict({
'rowid': 3,
'vector': b'3333',
'b': 1,
'n': 3,
'f': 3.3,
't': '1234567890123',
}),
]),
})
# ---

View file

@ -82,6 +82,7 @@ struct Vec0RescoreConfig {
};
#endif
struct VectorColumnDefinition {
char *name;
int name_length;

View file

@ -265,6 +265,35 @@ def test_deletes(db, snapshot):
assert vec0_shadow_table_contents(db, "v") == snapshot()
def test_delete_by_metadata_with_long_text(db):
"""Regression for https://github.com/asg017/sqlite-vec/issues/274.
ClearMetadata left rc=SQLITE_DONE after the long-text DELETE, which
propagated as an error and silently aborted the DELETE scan.
"""
db.execute(
"create virtual table v using vec0("
" tag text, embedding float[4], chunk_size=8"
")"
)
for i in range(6):
db.execute(
"insert into v(tag, embedding) values (?, zeroblob(16))",
[f"long_text_value_{i}"],
)
for i in range(4):
db.execute(
"insert into v(tag, embedding) values (?, zeroblob(16))",
[f"long_text_value_0"],
)
assert db.execute("select count(*) from v").fetchone()[0] == 10
# DELETE by metadata WHERE — the pattern from the issue
db.execute("delete from v where tag = 'long_text_value_0'")
assert db.execute("select count(*) from v where tag = 'long_text_value_0'").fetchone()[0] == 0
assert db.execute("select count(*) from v").fetchone()[0] == 5
def test_knn(db, snapshot):
db.execute(
"create virtual table v using vec0(vector float[1], name text, chunk_size=8)"