// When opened standalone in an editor, pull in sqlite-vec.c so the LSP // can resolve all types (vec0_vtab, VectorColumnDefinition, etc.). // When #include'd from sqlite-vec.c, SQLITE_VEC_H is already defined. #ifndef SQLITE_VEC_H #include"sqlite-vec.c"// IWYU pragma: keep #endif
// Read value — can be digit or identifier
rc = vec0_scanner_next(scanner, &token); if (rc != VEC0_TOKEN_RESULT_SOME) return SQLITE_ERROR; if (token.token_type != TOKEN_TYPE_DIGIT &&
token.token_type != TOKEN_TYPE_IDENTIFIER) return SQLITE_ERROR;
char *val = token.start; int valLength = token.end - token.start;
if (sqlite3_strnicmp(key, "nlist", keyLength) == 0) { if (token.token_type != TOKEN_TYPE_DIGIT) return SQLITE_ERROR; int v = atoi(val); if (v < 0 || v > VEC0_IVF_MAX_NLIST) return SQLITE_ERROR;
config->nlist = v;
} elseif (sqlite3_strnicmp(key, "nprobe", keyLength) == 0) { if (token.token_type != TOKEN_TYPE_DIGIT) return SQLITE_ERROR; int v = atoi(val); if (v < 1 || v > VEC0_IVF_MAX_NLIST) return SQLITE_ERROR;
config->nprobe = v;
nprobe_explicit = 1;
} elseif (sqlite3_strnicmp(key, "quantizer", keyLength) == 0) { if (token.token_type != TOKEN_TYPE_IDENTIFIER) return SQLITE_ERROR; if (sqlite3_strnicmp(val, "none", valLength) == 0) {
config->quantizer = VEC0_IVF_QUANTIZER_NONE;
} elseif (sqlite3_strnicmp(val, "int8", valLength) == 0) {
config->quantizer = VEC0_IVF_QUANTIZER_INT8;
} elseif (sqlite3_strnicmp(val, "binary", valLength) == 0) {
config->quantizer = VEC0_IVF_QUANTIZER_BINARY;
} else { return SQLITE_ERROR;
}
} elseif (sqlite3_strnicmp(key, "oversample", keyLength) == 0) { if (token.token_type != TOKEN_TYPE_DIGIT) return SQLITE_ERROR; int v = atoi(val); if (v < 1) return SQLITE_ERROR;
config->oversample = v;
} else { return SQLITE_ERROR;
}
rc = vec0_scanner_next(scanner, &token); if (rc != VEC0_TOKEN_RESULT_SOME) return SQLITE_ERROR; if (token.token_type == TOKEN_TYPE_RPAREN) break; if (token.token_type != TOKEN_TYPE_COMMA) return SQLITE_ERROR;
rc = vec0_scanner_next(scanner, &token);
}
if (config->nprobe < 0) config->nprobe = VEC0_IVF_DEFAULT_NPROBE; if (config->nlist > 0 && config->nprobe > config->nlist) { if (nprobe_explicit) return SQLITE_ERROR;
config->nprobe = config->nlist;
}
// Validation: oversample > 1 only makes sense with quantization if (config->oversample > 1 && config->quantizer == VEC0_IVF_QUANTIZER_NONE) { return SQLITE_ERROR;
}
/** *Sizeofastoredvectorinbytes,accountingforquantization.
*/ staticint ivf_vec_size(vec0_vtab *p, int col_idx) { int D = (int)p->vector_columns[col_idx].dimensions; switch (p->vector_columns[col_idx].ivf.quantizer) { case VEC0_IVF_QUANTIZER_INT8: return D; case VEC0_IVF_QUANTIZER_BINARY: return D / 8; default: return D * (int)sizeof(float);
}
}
/** *Quantizefloat32vectortoint8. *Usesunitnormalization:clampto[-1,1],scaleto[-127,127].
*/
IVF_STATIC void ivf_quantize_int8(constfloat *src, int8_t *dst, int D) { for (int i = 0; i < D; i++) { float v = src[i]; if (v > 1.0f) v = 1.0f; if (v < -1.0f) v = -1.0f;
dst[i] = (int8_t)(v * 127.0f);
}
}
/** *Quantizefloat32vectortobinary(sign-bitquantization). *Eachbit=1ifsrc[i]>0,else0.
*/
IVF_STATIC void ivf_quantize_binary(constfloat *src, uint8_t *dst, int D) {
memset(dst, 0, D / 8); for (int i = 0; i < D; i++) { if (src[i] > 0.0f) {
dst[i / 8] |= (1 << (i % 8));
}
}
}
/** *Quantizeafloat32vectortothetargettypebasedonconfig. *dstmustbepre-allocatedtoivf_vec_size()bytes. *Ifquantizer=none,copiessrcas-is.
*/ staticvoid ivf_quantize(vec0_vtab *p, int col_idx, constfloat *src, void *dst) { int D = (int)p->vector_columns[col_idx].dimensions; switch (p->vector_columns[col_idx].ivf.quantizer) { case VEC0_IVF_QUANTIZER_INT8:
ivf_quantize_int8(src, (int8_t *)dst, D); break; case VEC0_IVF_QUANTIZER_BINARY:
ivf_quantize_binary(src, (uint8_t *)dst, D); break; default:
memcpy(dst, src, D * sizeof(float)); break;
}
}
/** *Createanewcellrow.Returnsthenewcell_id(rowid)via*out_cell_id.
*/ staticint ivf_cell_create(vec0_vtab *p, int col_idx, i64 centroid_id,
i64 *out_cell_id) {
sqlite3_stmt *stmt = NULL; int rc; int cap = VEC0_IVF_CELL_MAX_VECTORS; int vecSize = ivf_vec_size(p, col_idx); char *zSql = sqlite3_mprintf( "INSERT INTO " VEC0_SHADOW_IVF_CELLS_NAME " (centroid_id, n_vectors, validity, rowids, vectors) VALUES (?, 0, ?, ?, ?)",
p->schemaName, p->tableName, col_idx); if (!zSql) return SQLITE_NOMEM;
rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL);
sqlite3_free(zSql); if (rc != SQLITE_OK) return rc;
sqlite3_bind_int64(stmt, 1, centroid_id);
sqlite3_bind_zeroblob(stmt, 2, cap / 8);
sqlite3_bind_zeroblob(stmt, 3, cap * (int)sizeof(i64));
sqlite3_bind_zeroblob(stmt, 4, cap * vecSize);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt); if (rc != SQLITE_DONE) return SQLITE_ERROR; if (out_cell_id) *out_cell_id = sqlite3_last_insert_rowid(p->db); return SQLITE_OK;
}
/** *Findacellwithspaceforthegivencentroid,orcreateone. *Returnscell_id(rowid)andcurrentn_vectors.
*/ staticint ivf_cell_find_or_create(vec0_vtab *p, int col_idx, i64 centroid_id,
i64 *out_cell_id, int *out_n) { int rc; // Find existing cell with space
rc = ivf_ensure_stmt(p, &p->stmtIvfCellMeta[col_idx], "SELECT rowid, n_vectors FROM " VEC0_SHADOW_IVF_CELLS_NAME " WHERE centroid_id = ? AND n_vectors < %d LIMIT 1",
col_idx); // The %d in the format won't work with ivf_ensure_stmt since it only has 3 // format args. Use a direct approach instead.
sqlite3_finalize(p->stmtIvfCellMeta[col_idx]);
p->stmtIvfCellMeta[col_idx] = NULL;
char *zSql = sqlite3_mprintf( "SELECT rowid, n_vectors FROM " VEC0_SHADOW_IVF_CELLS_NAME " WHERE centroid_id = ? AND n_vectors < %d LIMIT 1",
p->schemaName, p->tableName, col_idx, VEC0_IVF_CELL_MAX_VECTORS); if (!zSql) return SQLITE_NOMEM; // Cache this manually if (!p->stmtIvfCellMeta[col_idx]) {
rc = sqlite3_prepare_v2(p->db, zSql, -1, &p->stmtIvfCellMeta[col_idx], NULL);
sqlite3_free(zSql); if (rc != SQLITE_OK) return rc;
} else {
sqlite3_free(zSql);
}
staticint ivf_insert(vec0_vtab *p, int col_idx, i64 rowid, constvoid *vectorData, int vectorSize) {
UNUSED_PARAMETER(vectorSize); int quantizer = p->vector_columns[col_idx].ivf.quantizer; int qvecSize = ivf_vec_size(p, col_idx); int rc;
// Quantize the input vector (or copy as-is if no quantization) void *qvec = sqlite3_malloc(qvecSize); if (!qvec) return SQLITE_NOMEM;
ivf_quantize(p, col_idx, (constfloat *)vectorData, qvec);
if (!ivf_is_trained(p, col_idx)) {
rc = ivf_cell_insert(p, col_idx, VEC0_IVF_UNASSIGNED_CENTROID_ID,
rowid, qvec, qvecSize);
} else { // Find nearest centroid using quantized distance int best_centroid = -1; float min_dist = FLT_MAX;
// Decrement n_vectors if (p->stmtIvfCellUpdateN[col_idx]) { // This stmt does +1, but we want -1. Use a different cached stmt.
} // Just use inline for decrement (not hot path)
{
sqlite3_stmt *stmtDec = NULL; char *zSql = sqlite3_mprintf( "UPDATE " VEC0_SHADOW_IVF_CELLS_NAME " SET n_vectors = n_vectors - 1 WHERE rowid = ?",
p->schemaName, p->tableName, col_idx); if (zSql) {
sqlite3_prepare_v2(p->db, zSql, -1, &stmtDec, NULL); sqlite3_free(zSql); if (stmtDec) { sqlite3_bind_int64(stmtDec, 1, cell_id); sqlite3_step(stmtDec); sqlite3_finalize(stmtDec); }
}
}
// Delete from rowid_map
ivf_ensure_stmt(p, &p->stmtIvfRowidMapDelete[col_idx], "DELETE FROM " VEC0_SHADOW_IVF_ROWID_MAP_NAME " WHERE rowid = ?", col_idx); if (p->stmtIvfRowidMapDelete[col_idx]) {
sqlite3_stmt *sd = p->stmtIvfRowidMapDelete[col_idx];
sqlite3_reset(sd);
sqlite3_bind_int64(sd, 1, rowid);
sqlite3_step(sd);
}
// Delete from _ivf_vectors (full-precision KV) when quantized if (p->vector_columns[col_idx].ivf.quantizer != VEC0_IVF_QUANTIZER_NONE) {
sqlite3_stmt *stmtDelVec = NULL; char *zSql = sqlite3_mprintf( "DELETE FROM " VEC0_SHADOW_IVF_VECTORS_NAME " WHERE rowid = ?",
p->schemaName, p->tableName, col_idx); if (zSql) {
sqlite3_prepare_v2(p->db, zSql, -1, &stmtDelVec, NULL); sqlite3_free(zSql); if (stmtDelVec) { sqlite3_bind_int64(stmtDelVec, 1, rowid); sqlite3_step(stmtDelVec); sqlite3_finalize(stmtDelVec); }
}
}
return SQLITE_OK;
}
// ============================================================================ // Point query // ============================================================================
staticint ivf_get_vector_data(vec0_vtab *p, i64 rowid, int col_idx, void **outVector, int *outVectorSize) { int rc; int vecSize = ivf_vec_size(p, col_idx);
i64 cell_id = 0; int slot = -1;
staticint ivf_load_all_vectors(vec0_vtab *p, int col_idx, float **out_vectors, i64 **out_rowids, int *out_N) {
sqlite3_stmt *stmt = NULL; int rc; int D = (int)p->vector_columns[col_idx].dimensions; int vecSize = D * (int)sizeof(float); int quantizer = p->vector_columns[col_idx].ivf.quantizer;
// When quantized, load full-precision vectors from _ivf_vectors KV table if (quantizer != VEC0_IVF_QUANTIZER_NONE) { int total = 0; char *zSql = sqlite3_mprintf( "SELECT count(*) FROM " VEC0_SHADOW_IVF_VECTORS_NAME,
p->schemaName, p->tableName, col_idx); if (!zSql) return SQLITE_NOMEM;
rc = sqlite3_prepare_v2(p->db, zSql, -1, &stmt, NULL); sqlite3_free(zSql); if (rc == SQLITE_OK && sqlite3_step(stmt) == SQLITE_ROW) total = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt); if (total == 0) { *out_vectors = NULL; *out_rowids = NULL; *out_N = 0; return SQLITE_OK; }
staticint ivf_cmd_compute_centroids(vec0_vtab *p, int col_idx, int nlist_override, int max_iter, uint32_t seed) { int rc; int D = (int)p->vector_columns[col_idx].dimensions; int vecSize = D * (int)sizeof(float); int quantizer = p->vector_columns[col_idx].ivf.quantizer; int nlist = nlist_override > 0 ? nlist_override : p->vector_columns[col_idx].ivf.nlist; if (nlist <= 0) { vtab_set_error(&p->base, "nlist must be specified"); return SQLITE_ERROR; }
float *vectors = NULL; i64 *rowids = NULL; int N = 0;
rc = ivf_load_all_vectors(p, col_idx, &vectors, &rowids, &N); if (rc != SQLITE_OK) return rc; if (N == 0) { vtab_set_error(&p->base, "No vectors"); sqlite3_free(vectors); sqlite3_free(rowids); return SQLITE_ERROR; } if (nlist > N) nlist = N;
float *centroids = sqlite3_malloc64((i64)nlist * D * sizeof(float)); if (!centroids) { sqlite3_free(vectors); sqlite3_free(rowids); return SQLITE_NOMEM; } if (ivf_kmeans(vectors, N, D, nlist, max_iter, seed, centroids) != 0) {
sqlite3_free(vectors); sqlite3_free(rowids); sqlite3_free(centroids); return SQLITE_ERROR;
}
// Compute assignments int *assignments = sqlite3_malloc64((i64)N * sizeof(int)); if (!assignments) { sqlite3_free(vectors); sqlite3_free(rowids); sqlite3_free(centroids); return SQLITE_NOMEM; } // Assignment uses float32 distances (k-means operates in float32 space) for (int i = 0; i < N; i++) { float min_d = FLT_MAX; int best = 0; for (int c = 0; c < nlist; c++) { float d = ivf_distance_float(p, col_idx, &vectors[i * D], ¢roids[c * D]); if (d < min_d) { min_d = d; best = c; }
}
assignments[i] = best;
}
// Invalidate all cached stmts before dropping/recreating tables
ivf_invalidate_cached(p, col_idx);
// Clear all data
ivf_exec(p, "DELETE FROM " VEC0_SHADOW_IVF_CENTROIDS_NAME, col_idx);
ivf_exec(p, "DELETE FROM " VEC0_SHADOW_IVF_CELLS_NAME, col_idx);
ivf_exec(p, "DELETE FROM " VEC0_SHADOW_IVF_ROWID_MAP_NAME, col_idx);
// Write centroids (quantized if quantizer is set) int qvecSize = ivf_vec_size(p, col_idx); void *qbuf = sqlite3_malloc(qvecSize > vecSize ? qvecSize : vecSize); if (!qbuf) { rc = SQLITE_NOMEM; goto train_error; }
staticint ivf_cmd_set_centroid(vec0_vtab *p, int col_idx, int centroid_id, constvoid *vectorData, int vectorSize) {
sqlite3_stmt *stmt = NULL; int rc; int D = (int)p->vector_columns[col_idx].dimensions; if (vectorSize != (int)(D * sizeof(float))) { vtab_set_error(&p->base, "Dimension mismatch"); return SQLITE_ERROR; }
// Invalidate cached stmts since we'll be modifying cells
ivf_invalidate_cached(p, col_idx);
while (sqlite3_step(stmt) == SQLITE_ROW) { int n = sqlite3_column_int(stmt, 1); constunsignedchar *val = (constunsignedchar *)sqlite3_column_blob(stmt, 2); const i64 *rids = (const i64 *)sqlite3_column_blob(stmt, 3); constfloat *vecs = (constfloat *)sqlite3_column_blob(stmt, 4); int valBytes = sqlite3_column_bytes(stmt, 2); int ridsBytes = sqlite3_column_bytes(stmt, 3); int vecsBytes = sqlite3_column_bytes(stmt, 4); if (!val || !rids || !vecs) continue; int cap = valBytes * 8; if (ridsBytes / (int)sizeof(i64) < cap) cap = ridsBytes / (int)sizeof(i64); if (vecsBytes / vecSize < cap) cap = vecsBytes / vecSize;
for (int i = 0; i < cap && n > 0; i++) { if (!(val[i / 8] & (1 << (i % 8)))) continue;
n--; int cid = ivf_find_nearest_centroid(p, col_idx, &vecs[i * D], centroids, D, nlist);
// Delete old rowid_map entry
sqlite3_stmt *sd = NULL; char *zd = sqlite3_mprintf("DELETE FROM " VEC0_SHADOW_IVF_ROWID_MAP_NAME " WHERE rowid = ?",
p->schemaName, p->tableName, col_idx); if (zd) { sqlite3_prepare_v2(p->db, zd, -1, &sd, NULL); sqlite3_free(zd);
sqlite3_bind_int64(sd, 1, rids[i]); sqlite3_step(sd); sqlite3_finalize(sd); }
staticint ivf_cmd_clear_centroids(vec0_vtab *p, int col_idx) { float *vectors = NULL; i64 *rowids = NULL; int N = 0; int vecSize = ivf_vec_size(p, col_idx); int D = (int)p->vector_columns[col_idx].dimensions; int rc;
sqlite3_stmt *stmt = NULL; char *zSql;
staticint ivf_candidate_cmp(constvoid *a, constvoid *b) { float da = ((conststruct IvfCandidate *)a)->distance; float db = ((conststruct IvfCandidate *)b)->distance; if (da < db) return -1; if (da > db) return1; return0;
}
/** *Scancellrowsfromapreparedstatement,computingdistancesin-memory. *Thestatementmustreturn(n_vectors,validity,rowids,vectors)columns. *queryVecQisthequantizedquery(sametypeascellvectors). *qvecSizeisthesizeofonequantizedvectorinbytes.
*/ staticint ivf_scan_cells_from_stmt(vec0_vtab *p, int col_idx,
sqlite3_stmt *stmt, constvoid *queryVecQ, int qvecSize, struct IvfCandidate **candidates, int *nCandidates, int *cap) { while (sqlite3_step(stmt) == SQLITE_ROW) { int n = sqlite3_column_int(stmt, 0); if (n == 0) continue; constunsignedchar *validity = (constunsignedchar *)sqlite3_column_blob(stmt, 1); const i64 *rowids = (const i64 *)sqlite3_column_blob(stmt, 2); constunsignedchar *vectors = (constunsignedchar *)sqlite3_column_blob(stmt, 3); int valBytes = sqlite3_column_bytes(stmt, 1); int ridsBytes = sqlite3_column_bytes(stmt, 2); int vecsBytes = sqlite3_column_bytes(stmt, 3); if (!validity || !rowids || !vectors) continue; int cell_cap = valBytes * 8; if (ridsBytes / (int)sizeof(i64) < cell_cap) cell_cap = ridsBytes / (int)sizeof(i64); if (vecsBytes / qvecSize < cell_cap) cell_cap = vecsBytes / qvecSize;
int found = 0; for (int i = 0; i < cell_cap && found < n; i++) { if (!(validity[i / 8] & (1 << (i % 8)))) continue;
found++; if (*nCandidates >= *cap) {
*cap *= 2; struct IvfCandidate *tmp = sqlite3_realloc64(*candidates, (i64)*cap * sizeof(struct IvfCandidate)); if (!tmp) return SQLITE_NOMEM;
*candidates = tmp;
}
(*candidates)[*nCandidates].rowid = rowids[i];
(*candidates)[*nCandidates].distance = ivf_distance(p, col_idx,
queryVecQ, &vectors[i * qvecSize]);
(*nCandidates)++;
}
} return SQLITE_OK;
}
staticint ivf_query_knn(vec0_vtab *p, int col_idx, constvoid *queryVector, int queryVectorSize,
i64 k, struct vec0_query_knn_data *knn_data) {
UNUSED_PARAMETER(queryVectorSize); int rc; int nprobe = p->vector_columns[col_idx].ivf.nprobe; int trained = ivf_is_trained(p, col_idx); int quantizer = p->vector_columns[col_idx].ivf.quantizer; int oversample = p->vector_columns[col_idx].ivf.oversample; int qvecSize = ivf_vec_size(p, col_idx);
// Quantize query vector for scanning void *queryQ = sqlite3_malloc(qvecSize); if (!queryQ) return SQLITE_NOMEM;
ivf_quantize(p, col_idx, (constfloat *)queryVector, queryQ);
// With oversample, collect more candidates for re-ranking
i64 collect_k = (oversample > 1) ? k * oversample : k;
int cap = (collect_k < 1024) ? 1024 : (int)collect_k * 2; int nCandidates = 0; struct IvfCandidate *candidates = sqlite3_malloc64((i64)cap * sizeof(struct IvfCandidate)); if (!candidates) { sqlite3_free(queryQ); return SQLITE_NOMEM; }
if (trained) { // Find top nprobe centroids using quantized distance int nlist = 0;
rc = ivf_ensure_stmt(p, &p->stmtIvfCentroidsAll[col_idx], "SELECT centroid_id, centroid FROM " VEC0_SHADOW_IVF_CENTROIDS_NAME, col_idx); if (rc != SQLITE_OK) { sqlite3_free(queryQ); sqlite3_free(candidates); return rc; }
sqlite3_stmt *stmt = p->stmtIvfCentroidsAll[col_idx];
sqlite3_reset(stmt);
int centroid_cap = 64; struct IvfCentroidDist *cd = sqlite3_malloc64(centroid_cap * sizeof(*cd)); if (!cd) { sqlite3_free(queryQ); sqlite3_free(candidates); return SQLITE_NOMEM; }
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.